Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions glossary_branch_rename/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"exercise_name": "glossary-branch-rename",
"tags": [
"git-push"
],
"requires_git": true,
"requires_github": true,
"base_files": {},
"exercise_repo": {
"repo_type": "local",
"repo_name": "funny-glossary",
"repo_title": null,
"create_fork": null,
"init": false
}
}
1 change: 1 addition & 0 deletions glossary_branch_rename/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
See https://git-mastery.org/lessons/remoteBranchRename/exercise-glossary-branch-rename.html
Empty file.
25 changes: 25 additions & 0 deletions glossary_branch_rename/download.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
from exercise_utils.github_cli import clone_repo_with_gh, delete_repo, fork_repo, get_github_username, has_repo
from exercise_utils.git import remove_remote, track_remote_branch

UPSTREAM_REPO = "git-mastery/samplerepo-funny-glossary"
BRANCHES = ["ABC", "DEF", "STU", "VWX"]


def setup(verbose: bool = False):
username = get_github_username(verbose)
FORK_NAME = f"{username}-gitmastery-samplerepo-funny-glossary"

if has_repo(FORK_NAME, True, verbose):
delete_repo(FORK_NAME, verbose)

fork_repo(UPSTREAM_REPO, FORK_NAME, verbose, default_branch_only=False)

clone_repo_with_gh(
f"{username}/{FORK_NAME}",
verbose,
".",
)
remove_remote("upstream", verbose)

for branch in BRANCHES:
track_remote_branch("origin", branch, verbose)
128 changes: 128 additions & 0 deletions glossary_branch_rename/test_verify.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
from contextlib import contextmanager
from typing import Iterator, Tuple

from git_autograder import GitAutograderStatus
from exercise_utils.test import (
GitAutograderTest,
GitAutograderTestLoader,
assert_output,
)
from repo_smith.repo_smith import RepoSmith

from .verify import (
verify,
STU_LOCAL_PRESENT,
STU_REMOTE_PRESENT,
RENAMED_LOCAL_MISSING,
RENAMED_REMOTE_MISSING,
)

REPOSITORY_NAME = "glossary-branch-rename"
BRANCHES = ["ABC", "DEF", "STU", "VWX"]
EXPECTED_NEW_BRANCH_NAME = "S-to-Z"
BRANCH_TO_RENAME = "STU"

loader = GitAutograderTestLoader(REPOSITORY_NAME, verify)


@contextmanager
def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]:
with loader.start(include_remote_repo=True) as (test, rs, rs_remote):
remote_worktree_dir = rs_remote.repo.working_tree_dir
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems to be unnecessary now, why not just set rs_remote as the origin remote of rs first, then push the necessary updates from rs to rs_remote?


# set up local repo
rs.git.commit(message="Empty", allow_empty=True)
rs.git.remote_add("origin", str(remote_worktree_dir))

for remote_branch_name in BRANCHES:
rs_remote.git.branch(remote_branch_name)

rs.git.push("origin", all=True)

yield test, rs


def test_base():
with base_setup() as (test, rs):
rs.git.branch(EXPECTED_NEW_BRANCH_NAME, old_branch=BRANCH_TO_RENAME, move=True)
rs.git.push("origin", EXPECTED_NEW_BRANCH_NAME)
rs.git.push("origin", f":{BRANCH_TO_RENAME}")

output = test.run()
assert_output(output, GitAutograderStatus.SUCCESSFUL)


def test_no_change():
with base_setup() as (test, _):
output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[
STU_LOCAL_PRESENT,
RENAMED_LOCAL_MISSING,
STU_REMOTE_PRESENT,
RENAMED_REMOTE_MISSING,
],
)


def test_changed_local_only():
with base_setup() as (test, rs):
rs.git.branch(EXPECTED_NEW_BRANCH_NAME, old_branch=BRANCH_TO_RENAME, move=True)
output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[STU_REMOTE_PRESENT, RENAMED_REMOTE_MISSING],
)


def test_changed_local_wrong_name():
with base_setup() as (test, rs):
rs.git.branch("S-to-X", old_branch=BRANCH_TO_RENAME, move=True)
output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[
RENAMED_LOCAL_MISSING,
STU_REMOTE_PRESENT,
RENAMED_REMOTE_MISSING,
],
)


def test_changed_remote_wrong_name():
with base_setup() as (test, rs):
rs.git.branch("S-to-X", old_branch=BRANCH_TO_RENAME, move=True)
rs.git.push("origin", "S-to-X")
rs.git.push("origin", f":{BRANCH_TO_RENAME}")
output = test.run()
assert_output(
output,
GitAutograderStatus.UNSUCCESSFUL,
[
RENAMED_LOCAL_MISSING,
RENAMED_REMOTE_MISSING,
],
)


def test_local_old_branch_still_exists():
with base_setup() as (test, rs):
rs.git.branch(EXPECTED_NEW_BRANCH_NAME)
rs.git.push("origin", EXPECTED_NEW_BRANCH_NAME)
rs.git.push("origin", f":{BRANCH_TO_RENAME}")

output = test.run()
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [STU_LOCAL_PRESENT])


def test_remote_old_branch_still_exists():
with base_setup() as (test, rs):
rs.git.branch(EXPECTED_NEW_BRANCH_NAME, old_branch=BRANCH_TO_RENAME, move=True)
rs.git.push("origin", EXPECTED_NEW_BRANCH_NAME)

output = test.run()
assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [STU_REMOTE_PRESENT])
47 changes: 47 additions & 0 deletions glossary_branch_rename/verify.py
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM verified that it works as expected

Image

Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
from git_autograder import (
GitAutograderOutput,
GitAutograderExercise,
GitAutograderStatus,
)

STU_BRANCH = "STU"
RENAMED_BRANCH = "S-to-Z"

STU_LOCAL_PRESENT = f"Local branch {STU_BRANCH} still exists."
RENAMED_LOCAL_MISSING = f"Local branch {RENAMED_BRANCH} is missing."
STU_REMOTE_PRESENT = f"Remote branch {STU_BRANCH} still exists."
RENAMED_REMOTE_MISSING = f"Remote branch {RENAMED_BRANCH} is missing."


def verify(exercise: GitAutograderExercise) -> GitAutograderOutput:
local_raw = exercise.repo.repo.git.branch("--list")
local_branches = [line.strip().lstrip("* ").strip() for line in local_raw.splitlines()]

remote_raw = exercise.repo.repo.git.ls_remote("--heads", "origin")
remote_branches = []
for line in remote_raw.splitlines():
parts = line.split()
if len(parts) == 2 and parts[1].startswith("refs/heads/"):
remote_branches.append(parts[1].split("/")[-1])

comments = []

if STU_BRANCH in local_branches:
comments.append(STU_LOCAL_PRESENT)

if RENAMED_BRANCH not in local_branches:
comments.append(RENAMED_LOCAL_MISSING)

if STU_BRANCH in remote_branches:
comments.append(STU_REMOTE_PRESENT)

if RENAMED_BRANCH not in remote_branches:
comments.append(RENAMED_REMOTE_MISSING)

if comments:
raise exercise.wrong_answer(comments)

return exercise.to_output(
["Nice work renaming the branch locally and on the remote!"],
GitAutograderStatus.SUCCESSFUL,
)