From 28d630ec71a2813d8f9d215d6374fb8fdccbab6f Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 01:04:59 +0800 Subject: [PATCH 01/21] implement exercise glossary-branch-rename using Codex --- .../.gitmastery-exercise.json | 16 +++++++++ glossary_branch_rename/README.md | 1 + glossary_branch_rename/__init__.py | 0 glossary_branch_rename/download.py | 36 +++++++++++++++++++ glossary_branch_rename/test_verify.py | 12 +++++++ glossary_branch_rename/verify.py | 11 ++++++ 6 files changed, 76 insertions(+) create mode 100644 glossary_branch_rename/.gitmastery-exercise.json create mode 100644 glossary_branch_rename/README.md create mode 100644 glossary_branch_rename/__init__.py create mode 100644 glossary_branch_rename/download.py create mode 100644 glossary_branch_rename/test_verify.py create mode 100644 glossary_branch_rename/verify.py diff --git a/glossary_branch_rename/.gitmastery-exercise.json b/glossary_branch_rename/.gitmastery-exercise.json new file mode 100644 index 00000000..8e876a74 --- /dev/null +++ b/glossary_branch_rename/.gitmastery-exercise.json @@ -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": true + } +} \ No newline at end of file diff --git a/glossary_branch_rename/README.md b/glossary_branch_rename/README.md new file mode 100644 index 00000000..7a8bd468 --- /dev/null +++ b/glossary_branch_rename/README.md @@ -0,0 +1 @@ +See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-rename.html diff --git a/glossary_branch_rename/__init__.py b/glossary_branch_rename/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py new file mode 100644 index 00000000..f91ceadc --- /dev/null +++ b/glossary_branch_rename/download.py @@ -0,0 +1,36 @@ +import os +import shutil +from pathlib import Path + +from exercise_utils.cli import run_command +from exercise_utils.github_cli import clone_repo_with_gh, fork_repo, get_github_username +from exercise_utils.git import add_remote +from exercise_utils.gitmastery import create_start_tag + +__resources__ = {} + + +def setup(verbose: bool = False): + upstream_repo = "git-mastery/samplerepo-funny-glossary" + + repo_dir = Path.cwd() + parent_dir = repo_dir.parent + repo_name = repo_dir.name + + os.chdir(parent_dir) + if repo_dir.exists(): + shutil.rmtree(repo_dir) + + username = get_github_username(verbose) + assert username is not None + fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" + + fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) + clone_repo_with_gh(f"{username}/{fork_name}", verbose, name=repo_name) + + os.chdir(parent_dir / repo_name) + add_remote("upstream", f"https://github.com/{upstream_repo}.git", verbose) + run_command(["git", "fetch", "upstream", "--prune"], verbose) + run_command(["git", "push", "origin", "--all"], verbose) + + create_start_tag(verbose) diff --git a/glossary_branch_rename/test_verify.py b/glossary_branch_rename/test_verify.py new file mode 100644 index 00000000..44bb71e2 --- /dev/null +++ b/glossary_branch_rename/test_verify.py @@ -0,0 +1,12 @@ +from exercise_utils.test import GitAutograderTestLoader + +from .verify import verify + +REPOSITORY_NAME = "glossary-branch-rename" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_base(): + with loader.start() as (test, rs): + pass diff --git a/glossary_branch_rename/verify.py b/glossary_branch_rename/verify.py new file mode 100644 index 00000000..1288d3de --- /dev/null +++ b/glossary_branch_rename/verify.py @@ -0,0 +1,11 @@ +from git_autograder import ( + GitAutograderOutput, + GitAutograderExercise, + GitAutograderStatus, +) + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # INSERT YOUR GRADING CODE HERE + + return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) From 3d87724fcc5a265e6a00be1a1500b0de661f9f16 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 01:19:44 +0800 Subject: [PATCH 02/21] update exercise to simplify workflow --- glossary_branch_rename/download.py | 38 ++++++++++++++++++------------ 1 file changed, 23 insertions(+), 15 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index f91ceadc..94fd2555 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -1,9 +1,5 @@ -import os -import shutil -from pathlib import Path - from exercise_utils.cli import run_command -from exercise_utils.github_cli import clone_repo_with_gh, fork_repo, get_github_username +from exercise_utils.github_cli import fork_repo, get_github_username from exercise_utils.git import add_remote from exercise_utils.gitmastery import create_start_tag @@ -13,24 +9,36 @@ def setup(verbose: bool = False): upstream_repo = "git-mastery/samplerepo-funny-glossary" - repo_dir = Path.cwd() - parent_dir = repo_dir.parent - repo_name = repo_dir.name - - os.chdir(parent_dir) - if repo_dir.exists(): - shutil.rmtree(repo_dir) - username = get_github_username(verbose) assert username is not None fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) - clone_repo_with_gh(f"{username}/{fork_name}", verbose, name=repo_name) - os.chdir(parent_dir / repo_name) + fork_url = f"https://github.com/{username}/{fork_name}.git" + if run_command(["git", "remote", "get-url", "origin"], verbose) is None: + add_remote("origin", fork_url, verbose) + else: + run_command(["git", "remote", "set-url", "origin", fork_url], verbose) + add_remote("upstream", f"https://github.com/{upstream_repo}.git", verbose) run_command(["git", "fetch", "upstream", "--prune"], verbose) + + branch_list = run_command( + [ + "git", + "for-each-ref", + "refs/remotes/upstream", + "--format=%(refname:strip=3)", + ], + verbose, + ) + if branch_list: + for branch in branch_list.splitlines(): + if branch == "HEAD": + continue + run_command(["git", "branch", "-f", branch, f"upstream/{branch}"], verbose) + run_command(["git", "push", "origin", "--all"], verbose) create_start_tag(verbose) From 966b28b53614b8a7824fac13f35e48c05ed5aa39 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 01:33:21 +0800 Subject: [PATCH 03/21] update exercise --- glossary_branch_rename/download.py | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 94fd2555..78b34d1c 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -1,4 +1,4 @@ -from exercise_utils.cli import run_command +from exercise_utils.cli import run_command, run_command_no_exit from exercise_utils.github_cli import fork_repo, get_github_username from exercise_utils.git import add_remote from exercise_utils.gitmastery import create_start_tag @@ -16,12 +16,20 @@ def setup(verbose: bool = False): fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) fork_url = f"https://github.com/{username}/{fork_name}.git" - if run_command(["git", "remote", "get-url", "origin"], verbose) is None: + origin_url = run_command_no_exit(["git", "remote", "get-url", "origin"], verbose) + if origin_url is None: add_remote("origin", fork_url, verbose) else: run_command(["git", "remote", "set-url", "origin", fork_url], verbose) - add_remote("upstream", f"https://github.com/{upstream_repo}.git", verbose) + upstream_url = f"https://github.com/{upstream_repo}.git" + existing_upstream = run_command_no_exit( + ["git", "remote", "get-url", "upstream"], verbose + ) + if existing_upstream is None: + add_remote("upstream", upstream_url, verbose) + else: + run_command(["git", "remote", "set-url", "upstream", upstream_url], verbose) run_command(["git", "fetch", "upstream", "--prune"], verbose) branch_list = run_command( @@ -39,6 +47,10 @@ def setup(verbose: bool = False): continue run_command(["git", "branch", "-f", branch, f"upstream/{branch}"], verbose) - run_command(["git", "push", "origin", "--all"], verbose) + if run_command_no_exit( + ["git", "show-ref", "--verify", "refs/remotes/upstream/main"], + verbose, + ): + run_command(["git", "checkout", "-B", "main", "upstream/main"], verbose) - create_start_tag(verbose) + run_command(["git", "push", "origin", "--all"], verbose) From 0dec2c9c34d6aebafe231016f109b565cbb55db1 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 02:52:30 +0800 Subject: [PATCH 04/21] update download logic --- glossary_branch_rename/download.py | 69 ++++++++++++------------------ 1 file changed, 28 insertions(+), 41 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 78b34d1c..88ad6a28 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -1,6 +1,8 @@ +import os +from pathlib import Path + from exercise_utils.cli import run_command, run_command_no_exit -from exercise_utils.github_cli import fork_repo, get_github_username -from exercise_utils.git import add_remote +from exercise_utils.github_cli import get_github_username from exercise_utils.gitmastery import create_start_tag __resources__ = {} @@ -8,49 +10,34 @@ def setup(verbose: bool = False): upstream_repo = "git-mastery/samplerepo-funny-glossary" + local_repo_name = "funny-glossary" username = get_github_username(verbose) assert username is not None fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" - fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) - - fork_url = f"https://github.com/{username}/{fork_name}.git" - origin_url = run_command_no_exit(["git", "remote", "get-url", "origin"], verbose) - if origin_url is None: - add_remote("origin", fork_url, verbose) - else: - run_command(["git", "remote", "set-url", "origin", fork_url], verbose) - - upstream_url = f"https://github.com/{upstream_repo}.git" - existing_upstream = run_command_no_exit( - ["git", "remote", "get-url", "upstream"], verbose - ) - if existing_upstream is None: - add_remote("upstream", upstream_url, verbose) + local_repo_path = Path(local_repo_name) + if local_repo_path.exists(): + # If the repo already exists, refresh refs instead of recloning. + run_command(["git", "-C", local_repo_name, "fetch", "--all", "--prune"], verbose) else: - run_command(["git", "remote", "set-url", "upstream", upstream_url], verbose) - run_command(["git", "fetch", "upstream", "--prune"], verbose) - - branch_list = run_command( - [ - "git", - "for-each-ref", - "refs/remotes/upstream", - "--format=%(refname:strip=3)", - ], - verbose, + run_command( + [ + "gh", + "repo", + "fork", + upstream_repo, + "--clone", + "--fork-name", + fork_name, + "--", + local_repo_name, + ], + verbose, + ) + + run_command( + ["git", "-C", local_repo_name, "remote", "remove", "upstream"], verbose ) - if branch_list: - for branch in branch_list.splitlines(): - if branch == "HEAD": - continue - run_command(["git", "branch", "-f", branch, f"upstream/{branch}"], verbose) - - if run_command_no_exit( - ["git", "show-ref", "--verify", "refs/remotes/upstream/main"], - verbose, - ): - run_command(["git", "checkout", "-B", "main", "upstream/main"], verbose) - - run_command(["git", "push", "origin", "--all"], verbose) + + create_start_tag(verbose) From c05bcb5e627fb975b96ef0c10dc0bf98f30b59fd Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 10:53:59 +0800 Subject: [PATCH 05/21] update download logic --- glossary_branch_rename/download.py | 56 +++++++++++++++++------------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 88ad6a28..f5039c7c 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -1,9 +1,10 @@ import os from pathlib import Path -from exercise_utils.cli import run_command, run_command_no_exit -from exercise_utils.github_cli import get_github_username +from exercise_utils.cli import run_command +from exercise_utils.github_cli import delete_repo, fork_repo, get_github_username, has_repo from exercise_utils.gitmastery import create_start_tag +from exercise_utils.git import clone_repo_with_git __resources__ = {} @@ -14,30 +15,35 @@ def setup(verbose: bool = False): username = get_github_username(verbose) assert username is not None - fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" - - local_repo_path = Path(local_repo_name) - if local_repo_path.exists(): - # If the repo already exists, refresh refs instead of recloning. - run_command(["git", "-C", local_repo_name, "fetch", "--all", "--prune"], verbose) - else: - run_command( - [ - "gh", - "repo", - "fork", - upstream_repo, - "--clone", - "--fork-name", - fork_name, - "--", - local_repo_name, - ], - verbose, - ) + fork_name = "gitmastery-samplerepo-funny-glossary" + full_repo_name = f"{username}-{fork_name}" + if has_repo(full_repo_name, True, verbose): + delete_repo(full_repo_name, verbose) + + fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) + + clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose) + + repo_path = Path(local_repo_name) + + run_command(["git", "-C", str(repo_path), "fetch", "--all", "--prune"], verbose) + + run_command( + ["git", "-C", str(repo_path), "branch", "--track", "ABC", "origin/ABC"], + verbose, + ) + run_command( + ["git", "-C", str(repo_path), "branch", "--track", "DEF", "origin/DEF"], + verbose, + ) + run_command( + ["git", "-C", str(repo_path), "branch", "--track", "STU", "origin/STU"], + verbose, + ) run_command( - ["git", "-C", local_repo_name, "remote", "remove", "upstream"], verbose + ["git", "-C", str(repo_path), "branch", "--track", "VWX", "origin/VWX"], + verbose, ) - create_start_tag(verbose) + run_command(["git", "-C", str(repo_path), "checkout", "main"], verbose) From 09de6b110745de5f05d7709a8148f3da408a9372 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 10:59:34 +0800 Subject: [PATCH 06/21] update download logic --- glossary_branch_rename/download.py | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index f5039c7c..e22882ba 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -25,25 +25,21 @@ def setup(verbose: bool = False): clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose) - repo_path = Path(local_repo_name) - - run_command(["git", "-C", str(repo_path), "fetch", "--all", "--prune"], verbose) + run_command(["git", "fetch", "--all", "--prune"], verbose) run_command( - ["git", "-C", str(repo_path), "branch", "--track", "ABC", "origin/ABC"], + ["git","branch", "--track", "ABC", "origin/ABC"], verbose, ) run_command( - ["git", "-C", str(repo_path), "branch", "--track", "DEF", "origin/DEF"], + ["git", "branch", "--track", "DEF", "origin/DEF"], verbose, ) run_command( - ["git", "-C", str(repo_path), "branch", "--track", "STU", "origin/STU"], + ["git","branch", "--track", "STU", "origin/STU"], verbose, ) run_command( - ["git", "-C", str(repo_path), "branch", "--track", "VWX", "origin/VWX"], + ["git","branch", "--track", "VWX", "origin/VWX"], verbose, ) - - run_command(["git", "-C", str(repo_path), "checkout", "main"], verbose) From bafc026fedd7e78dbaa5a6ea2831a58c7798a0f5 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:06:20 +0800 Subject: [PATCH 07/21] fix github repo name error --- glossary_branch_rename/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index e22882ba..217869ff 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -21,7 +21,7 @@ def setup(verbose: bool = False): if has_repo(full_repo_name, True, verbose): delete_repo(full_repo_name, verbose) - fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) + fork_repo(upstream_repo, full_repo_name, verbose, default_branch_only=False) clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose) From f39b398380de5ee6d1babdd55b0b9a5e7d8d6f89 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:09:32 +0800 Subject: [PATCH 08/21] fix local repo name --- glossary_branch_rename/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 217869ff..b14f443b 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -23,7 +23,7 @@ def setup(verbose: bool = False): fork_repo(upstream_repo, full_repo_name, verbose, default_branch_only=False) - clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose) + clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose, local_repo_name) run_command(["git", "fetch", "--all", "--prune"], verbose) From 34fad848433fe09c4fa025b25d8c40ba5064e33e Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:13:47 +0800 Subject: [PATCH 09/21] fix download error --- glossary_branch_rename/download.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index b14f443b..1e344d32 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -21,10 +21,15 @@ def setup(verbose: bool = False): if has_repo(full_repo_name, True, verbose): delete_repo(full_repo_name, verbose) - fork_repo(upstream_repo, full_repo_name, verbose, default_branch_only=False) + fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) - clone_repo_with_git(f"https://github.com/{full_repo_name}.git", verbose, local_repo_name) + clone_repo_with_git( + f"https://github.com/{username}/{full_repo_name}.git", + verbose, + local_repo_name, + ) + os.chdir(local_repo_name) run_command(["git", "fetch", "--all", "--prune"], verbose) run_command( @@ -43,3 +48,6 @@ def setup(verbose: bool = False): ["git","branch", "--track", "VWX", "origin/VWX"], verbose, ) + + run_command(["git", "checkout", "main"], verbose) + create_start_tag(verbose) From 31ce652cb42544ef24cf2802d73b2bf881b8ed0f Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:18:47 +0800 Subject: [PATCH 10/21] fix download error --- glossary_branch_rename/download.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 1e344d32..66e5ff61 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -26,10 +26,9 @@ def setup(verbose: bool = False): clone_repo_with_git( f"https://github.com/{username}/{full_repo_name}.git", verbose, - local_repo_name, + ".", ) - os.chdir(local_repo_name) run_command(["git", "fetch", "--all", "--prune"], verbose) run_command( From 55101a35f8e7724bc91632776b4fb22e99d3802b Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:22:50 +0800 Subject: [PATCH 11/21] update forking --- glossary_branch_rename/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 66e5ff61..a6105ea2 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -21,7 +21,7 @@ def setup(verbose: bool = False): if has_repo(full_repo_name, True, verbose): delete_repo(full_repo_name, verbose) - fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) + fork_repo(upstream_repo, full_repo_name, verbose, default_branch_only=False) clone_repo_with_git( f"https://github.com/{username}/{full_repo_name}.git", From 1d1b5c49446e0be984b9d70873ec6e38ad844ea5 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 11:52:02 +0800 Subject: [PATCH 12/21] update exercise set up logic --- glossary_branch_rename/.gitmastery-exercise.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_rename/.gitmastery-exercise.json b/glossary_branch_rename/.gitmastery-exercise.json index 8e876a74..6ca3803b 100644 --- a/glossary_branch_rename/.gitmastery-exercise.json +++ b/glossary_branch_rename/.gitmastery-exercise.json @@ -11,6 +11,6 @@ "repo_name": "funny-glossary", "repo_title": null, "create_fork": null, - "init": true + "init": false } } \ No newline at end of file From a1881a62012d34be4b3a50a0d088fce5ebff88fb Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 15:07:26 +0800 Subject: [PATCH 13/21] implement verify.py using Codex --- glossary_branch_rename/download.py | 11 ++++----- glossary_branch_rename/verify.py | 37 ++++++++++++++++++++++++++++-- 2 files changed, 40 insertions(+), 8 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index a6105ea2..fe269b77 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -15,16 +15,15 @@ def setup(verbose: bool = False): username = get_github_username(verbose) assert username is not None - fork_name = "gitmastery-samplerepo-funny-glossary" - full_repo_name = f"{username}-{fork_name}" + fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" - if has_repo(full_repo_name, True, verbose): - delete_repo(full_repo_name, verbose) + if has_repo(fork_name, True, verbose): + delete_repo(fork_name, verbose) - fork_repo(upstream_repo, full_repo_name, verbose, default_branch_only=False) + fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) clone_repo_with_git( - f"https://github.com/{username}/{full_repo_name}.git", + f"https://github.com/{username}/{fork_name}.git", verbose, ".", ) diff --git a/glossary_branch_rename/verify.py b/glossary_branch_rename/verify.py index 1288d3de..7ec30139 100644 --- a/glossary_branch_rename/verify.py +++ b/glossary_branch_rename/verify.py @@ -4,8 +4,41 @@ 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: - # INSERT YOUR GRADING CODE HERE + 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([], GitAutograderStatus.SUCCESSFUL) + return exercise.to_output( + ["Nice work renaming the branch locally and on the remote!"], + GitAutograderStatus.SUCCESSFUL, + ) From 6580f7988cf4afe349f6cc785306ae788a0c0b1e Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 21:23:46 +0800 Subject: [PATCH 14/21] improve code quality --- glossary_branch_rename/download.py | 19 +++---------------- 1 file changed, 3 insertions(+), 16 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index fe269b77..53f67814 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -30,22 +30,9 @@ def setup(verbose: bool = False): run_command(["git", "fetch", "--all", "--prune"], verbose) - run_command( - ["git","branch", "--track", "ABC", "origin/ABC"], - verbose, - ) - run_command( - ["git", "branch", "--track", "DEF", "origin/DEF"], - verbose, - ) - run_command( - ["git","branch", "--track", "STU", "origin/STU"], - verbose, - ) - run_command( - ["git","branch", "--track", "VWX", "origin/VWX"], - verbose, - ) + branches = ["ABC", "DEF", "STU", "VWX"] + for branch in branches: + run_command(["git", "branch", "--track", branch, f"origin/{branch}"], verbose) run_command(["git", "checkout", "main"], verbose) create_start_tag(verbose) From b68fa264cff61ab9db4a4e4b5f03d5ba641a874a Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 21:24:42 +0800 Subject: [PATCH 15/21] improve code quality --- glossary_branch_rename/download.py | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 53f67814..c9ba3e93 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -6,12 +6,9 @@ from exercise_utils.gitmastery import create_start_tag from exercise_utils.git import clone_repo_with_git -__resources__ = {} - def setup(verbose: bool = False): - upstream_repo = "git-mastery/samplerepo-funny-glossary" - local_repo_name = "funny-glossary" + UPSTREAM_REPO = "git-mastery/samplerepo-funny-glossary" username = get_github_username(verbose) assert username is not None @@ -20,7 +17,7 @@ def setup(verbose: bool = False): if has_repo(fork_name, True, verbose): delete_repo(fork_name, verbose) - fork_repo(upstream_repo, fork_name, verbose, default_branch_only=False) + fork_repo(UPSTREAM_REPO, fork_name, verbose, default_branch_only=False) clone_repo_with_git( f"https://github.com/{username}/{fork_name}.git", From 40b1136ade3dd9d42c5dbfc08d0fc3694af6421a Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 21:39:28 +0800 Subject: [PATCH 16/21] update README --- glossary_branch_rename/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_rename/README.md b/glossary_branch_rename/README.md index 7a8bd468..83229cf5 100644 --- a/glossary_branch_rename/README.md +++ b/glossary_branch_rename/README.md @@ -1 +1 @@ -See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-rename.html +See https://git-mastery.org/lessons/remoteBranchRename/exercise-glossary-branch-rename.html From 4cb06bc6e43dd93aa1acd0c1858016a0017e71dc Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Tue, 20 Jan 2026 22:01:57 +0800 Subject: [PATCH 17/21] improve code readability --- glossary_branch_rename/verify.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/glossary_branch_rename/verify.py b/glossary_branch_rename/verify.py index 7ec30139..e68ff3b7 100644 --- a/glossary_branch_rename/verify.py +++ b/glossary_branch_rename/verify.py @@ -28,10 +28,13 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: 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) From 1cd04fb04cec7192eae2c4b419e1299a508dd6ef Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Fri, 30 Jan 2026 13:07:48 +0800 Subject: [PATCH 18/21] implement test_verify --- glossary_branch_rename/test_verify.py | 131 +++++++++++++++++++++++++- 1 file changed, 127 insertions(+), 4 deletions(-) diff --git a/glossary_branch_rename/test_verify.py b/glossary_branch_rename/test_verify.py index 44bb71e2..293d3b33 100644 --- a/glossary_branch_rename/test_verify.py +++ b/glossary_branch_rename/test_verify.py @@ -1,12 +1,135 @@ -from exercise_utils.test import GitAutograderTestLoader +from contextlib import contextmanager +import os +from typing import Iterator, Tuple -from .verify import verify +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): + # get the current set up repo, so that we can navigate back to this + # directory after setting up "remote" repo, and proceed with "local" repo + root_path = os.getcwd() + + remote_worktree_dir = rs_remote.repo.working_tree_dir + if remote_worktree_dir is None: + raise ValueError("Remote repo has no working tree.") + + remote_worktree = os.fspath(remote_worktree_dir) + os.chdir(remote_worktree) + rs_remote.git.commit(message="Empty", allow_empty=True) + for remote_branch_name in BRANCHES: + rs_remote.git.branch(remote_branch_name) + + os.chdir(root_path) + + # set up local repo + rs.git.commit(message="Empty", allow_empty=True) + rs.git.remote_add("origin", str(remote_worktree_dir)) + rs.git.fetch("origin") + + for remote_branch_names in BRANCHES: + rs.git.branch(remote_branch_names, f"origin/{remote_branch_names}") + + os.chdir(root_path) + + yield test, rs def test_base(): - with loader.start() as (test, rs): - pass + 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, rs): + 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]) From 23d49196c2313d542243f6d419c0607ff614764d Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Mon, 2 Feb 2026 13:40:14 +0800 Subject: [PATCH 19/21] improve code quality --- glossary_branch_rename/download.py | 30 +++++++++------------------ glossary_branch_rename/test_verify.py | 27 ++++++------------------ 2 files changed, 16 insertions(+), 41 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index c9ba3e93..37ae48d5 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -2,34 +2,24 @@ from pathlib import Path from exercise_utils.cli import run_command -from exercise_utils.github_cli import delete_repo, fork_repo, get_github_username, has_repo +from exercise_utils.github_cli import clone_repo_with_gh, delete_repo, fork_repo, get_github_username, has_repo from exercise_utils.gitmastery import create_start_tag -from exercise_utils.git import clone_repo_with_git +UPSTREAM_REPO = "git-mastery/samplerepo-funny-glossary" -def setup(verbose: bool = False): - UPSTREAM_REPO = "git-mastery/samplerepo-funny-glossary" +def setup(verbose: bool = False): username = get_github_username(verbose) - assert username is not None - fork_name = f"{username}-gitmastery-samplerepo-funny-glossary" + FORK_NAME = f"{username}-gitmastery-samplerepo-funny-glossary" - if has_repo(fork_name, True, verbose): - delete_repo(fork_name, verbose) + if has_repo(FORK_NAME, True, verbose): + delete_repo(FORK_NAME, verbose) - fork_repo(UPSTREAM_REPO, fork_name, verbose, default_branch_only=False) + fork_repo(UPSTREAM_REPO, FORK_NAME, verbose, default_branch_only=False) - clone_repo_with_git( - f"https://github.com/{username}/{fork_name}.git", + clone_repo_with_gh( + f"{username}/{FORK_NAME}", verbose, ".", ) - - run_command(["git", "fetch", "--all", "--prune"], verbose) - - branches = ["ABC", "DEF", "STU", "VWX"] - for branch in branches: - run_command(["git", "branch", "--track", branch, f"origin/{branch}"], verbose) - - run_command(["git", "checkout", "main"], verbose) - create_start_tag(verbose) + \ No newline at end of file diff --git a/glossary_branch_rename/test_verify.py b/glossary_branch_rename/test_verify.py index 293d3b33..09c03fa1 100644 --- a/glossary_branch_rename/test_verify.py +++ b/glossary_branch_rename/test_verify.py @@ -24,32 +24,17 @@ @contextmanager def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: with loader.start(include_remote_repo=True) as (test, rs, rs_remote): - # get the current set up repo, so that we can navigate back to this - # directory after setting up "remote" repo, and proceed with "local" repo - root_path = os.getcwd() - remote_worktree_dir = rs_remote.repo.working_tree_dir - if remote_worktree_dir is None: - raise ValueError("Remote repo has no working tree.") - - remote_worktree = os.fspath(remote_worktree_dir) - os.chdir(remote_worktree) - rs_remote.git.commit(message="Empty", allow_empty=True) - for remote_branch_name in BRANCHES: - rs_remote.git.branch(remote_branch_name) - - os.chdir(root_path) - + # set up local repo rs.git.commit(message="Empty", allow_empty=True) rs.git.remote_add("origin", str(remote_worktree_dir)) - rs.git.fetch("origin") - - for remote_branch_names in BRANCHES: - rs.git.branch(remote_branch_names, f"origin/{remote_branch_names}") - os.chdir(root_path) + 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(): @@ -124,7 +109,7 @@ def test_local_old_branch_still_exists(): rs.git.push("origin", f":{BRANCH_TO_RENAME}") output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL,[STU_LOCAL_PRESENT,]) + assert_output(output, GitAutograderStatus.UNSUCCESSFUL,[STU_LOCAL_PRESENT]) def test_remote_old_branch_still_exists(): with base_setup() as (test, rs): From 6ed3d5fd3365412fdd861cba91743033e9c3e998 Mon Sep 17 00:00:00 2001 From: SAN-MUYUN Date: Mon, 2 Feb 2026 13:54:07 +0800 Subject: [PATCH 20/21] fix download issues --- glossary_branch_rename/download.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/glossary_branch_rename/download.py b/glossary_branch_rename/download.py index 37ae48d5..95360198 100644 --- a/glossary_branch_rename/download.py +++ b/glossary_branch_rename/download.py @@ -1,11 +1,8 @@ -import os -from pathlib import Path - -from exercise_utils.cli import run_command from exercise_utils.github_cli import clone_repo_with_gh, delete_repo, fork_repo, get_github_username, has_repo -from exercise_utils.gitmastery import create_start_tag +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): @@ -22,4 +19,7 @@ def setup(verbose: bool = False): verbose, ".", ) - \ No newline at end of file + remove_remote("upstream", verbose) + + for branch in BRANCHES: + track_remote_branch("origin", branch, verbose) From 2e80024581e8a55890846e159b9882c623d30a02 Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:06:00 +0800 Subject: [PATCH 21/21] chore: remove unnecessary import and format code --- glossary_branch_rename/test_verify.py | 46 ++++++++++++++++----------- 1 file changed, 27 insertions(+), 19 deletions(-) diff --git a/glossary_branch_rename/test_verify.py b/glossary_branch_rename/test_verify.py index 09c03fa1..1196d669 100644 --- a/glossary_branch_rename/test_verify.py +++ b/glossary_branch_rename/test_verify.py @@ -1,12 +1,15 @@ from contextlib import contextmanager -import os from typing import Iterator, Tuple from git_autograder import GitAutograderStatus -from exercise_utils.test import GitAutograderTest, GitAutograderTestLoader, assert_output +from exercise_utils.test import ( + GitAutograderTest, + GitAutograderTestLoader, + assert_output, +) from repo_smith.repo_smith import RepoSmith -from .verify import( +from .verify import ( verify, STU_LOCAL_PRESENT, STU_REMOTE_PRESENT, @@ -21,33 +24,36 @@ 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 - + # 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, rs): + with base_setup() as (test, _): output = test.run() assert_output( output, @@ -56,10 +62,11 @@ def test_no_change(): STU_LOCAL_PRESENT, RENAMED_LOCAL_MISSING, STU_REMOTE_PRESENT, - RENAMED_REMOTE_MISSING - ] + 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) @@ -67,12 +74,10 @@ def test_changed_local_only(): assert_output( output, GitAutograderStatus.UNSUCCESSFUL, - [ - STU_REMOTE_PRESENT, - RENAMED_REMOTE_MISSING - ] + [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) @@ -84,9 +89,10 @@ def test_changed_local_wrong_name(): 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) @@ -99,9 +105,10 @@ def test_changed_remote_wrong_name(): [ 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) @@ -109,7 +116,8 @@ def test_local_old_branch_still_exists(): rs.git.push("origin", f":{BRANCH_TO_RENAME}") output = test.run() - assert_output(output, GitAutograderStatus.UNSUCCESSFUL,[STU_LOCAL_PRESENT]) + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [STU_LOCAL_PRESENT]) + def test_remote_old_branch_still_exists(): with base_setup() as (test, rs):