From 4ff8fd89b550cba200ff0808274d071dd091d91a Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 15:20:03 +0800 Subject: [PATCH 1/9] Implement setup --- .../.gitmastery-exercise.json | 16 ++++++++++ glossary_branch_delete/README.md | 1 + glossary_branch_delete/__init__.py | 0 glossary_branch_delete/download.py | 30 +++++++++++++++++++ glossary_branch_delete/test_verify.py | 12 ++++++++ glossary_branch_delete/verify.py | 11 +++++++ 6 files changed, 70 insertions(+) create mode 100644 glossary_branch_delete/.gitmastery-exercise.json create mode 100644 glossary_branch_delete/README.md create mode 100644 glossary_branch_delete/__init__.py create mode 100644 glossary_branch_delete/download.py create mode 100644 glossary_branch_delete/test_verify.py create mode 100644 glossary_branch_delete/verify.py diff --git a/glossary_branch_delete/.gitmastery-exercise.json b/glossary_branch_delete/.gitmastery-exercise.json new file mode 100644 index 00000000..aaacf5e6 --- /dev/null +++ b/glossary_branch_delete/.gitmastery-exercise.json @@ -0,0 +1,16 @@ +{ + "exercise_name": "glossary-branch-delete", + "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 + } +} \ No newline at end of file diff --git a/glossary_branch_delete/README.md b/glossary_branch_delete/README.md new file mode 100644 index 00000000..fad78034 --- /dev/null +++ b/glossary_branch_delete/README.md @@ -0,0 +1 @@ +See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-delete.html diff --git a/glossary_branch_delete/__init__.py b/glossary_branch_delete/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py new file mode 100644 index 00000000..955d11b1 --- /dev/null +++ b/glossary_branch_delete/download.py @@ -0,0 +1,30 @@ +from exercise_utils.cli import run_command +from exercise_utils.git import clone_repo_with_git +from exercise_utils.github_cli import ( + delete_repo, + get_github_username, + has_repo, +) +from exercise_utils.gitmastery import create_start_tag + +REPO_OWNER = "git-mastery" +REPO_NAME = "samplerepo-funny-glossary" + + +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) + + run_command( + ["gh", "repo", "fork", f"{REPO_OWNER}/{REPO_NAME}", "--fork-name", FORK_NAME], + verbose, + ) + + clone_repo_with_git( + f"https://github.com/{username}/{FORK_NAME}", verbose, "funny-glossary" + ) + + create_start_tag(verbose) diff --git a/glossary_branch_delete/test_verify.py b/glossary_branch_delete/test_verify.py new file mode 100644 index 00000000..d727c97e --- /dev/null +++ b/glossary_branch_delete/test_verify.py @@ -0,0 +1,12 @@ +from exercise_utils.test import GitAutograderTestLoader + +from .verify import verify + +REPOSITORY_NAME = "glossary-branch-delete" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_base(): + with loader.start() as (test, rs): + pass diff --git a/glossary_branch_delete/verify.py b/glossary_branch_delete/verify.py new file mode 100644 index 00000000..1288d3de --- /dev/null +++ b/glossary_branch_delete/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 93f9a816c56f954d61b3808a7a3c9be49c01a512 Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 15:31:03 +0800 Subject: [PATCH 2/9] Add verify logic --- glossary_branch_delete/verify.py | 22 ++++++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/glossary_branch_delete/verify.py b/glossary_branch_delete/verify.py index 1288d3de..a457631c 100644 --- a/glossary_branch_delete/verify.py +++ b/glossary_branch_delete/verify.py @@ -4,8 +4,26 @@ GitAutograderStatus, ) +VWX_BRANCH_EXISTS_LOCALLY = "Branch 'VWX' still exists locally! Remember to delete it." +VWX_BRANCH_EXISTS_REMOTELY = ( + "Branch 'VWX' still exists on the remote! Remember to delete it from the remote." +) + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # INSERT YOUR GRADING CODE HERE + if exercise.repo.branches.has_branch("VWX"): + raise exercise.wrong_answer([VWX_BRANCH_EXISTS_LOCALLY]) + + origin_remote = exercise.repo.remotes.remote("origin") + origin_remote.remote.fetch() + + try: + exercise.repo.repo.refs["origin/VWX"] + raise exercise.wrong_answer([VWX_BRANCH_EXISTS_REMOTELY]) + except (IndexError, KeyError): + pass - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) + return exercise.to_output( + ["Great job deleting the VWX branch locally and remotely!"], + GitAutograderStatus.SUCCESSFUL, + ) From d192eca660047bd9db0c04ccfdd5c98d35c08b46 Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 15:34:38 +0800 Subject: [PATCH 3/9] Change clone target --- glossary_branch_delete/download.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py index 955d11b1..ef825fde 100644 --- a/glossary_branch_delete/download.py +++ b/glossary_branch_delete/download.py @@ -23,8 +23,6 @@ def setup(verbose: bool = False): verbose, ) - clone_repo_with_git( - f"https://github.com/{username}/{FORK_NAME}", verbose, "funny-glossary" - ) + clone_repo_with_git(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") create_start_tag(verbose) From d124d284e2103dac856e7cdaac78e4e5a0f6e52d Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 16:59:43 +0800 Subject: [PATCH 4/9] Implement tests --- glossary_branch_delete/download.py | 3 -- glossary_branch_delete/test_verify.py | 48 ++++++++++++++++++++++++--- glossary_branch_delete/verify.py | 11 ++---- 3 files changed, 46 insertions(+), 16 deletions(-) diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py index ef825fde..1fd63372 100644 --- a/glossary_branch_delete/download.py +++ b/glossary_branch_delete/download.py @@ -5,7 +5,6 @@ get_github_username, has_repo, ) -from exercise_utils.gitmastery import create_start_tag REPO_OWNER = "git-mastery" REPO_NAME = "samplerepo-funny-glossary" @@ -24,5 +23,3 @@ def setup(verbose: bool = False): ) clone_repo_with_git(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") - - create_start_tag(verbose) diff --git a/glossary_branch_delete/test_verify.py b/glossary_branch_delete/test_verify.py index d727c97e..450ad597 100644 --- a/glossary_branch_delete/test_verify.py +++ b/glossary_branch_delete/test_verify.py @@ -1,12 +1,52 @@ -from exercise_utils.test import GitAutograderTestLoader +from contextlib import contextmanager +from typing import Iterator, Tuple -from .verify import verify +from exercise_utils.test import ( + GitAutograderTest, + GitAutograderTestLoader, + assert_output, +) +from git_autograder import GitAutograderStatus +from repo_smith.repo_smith import RepoSmith + +from .verify import ( + VWX_BRANCH_EXISTS_REMOTELY, + verify, +) REPOSITORY_NAME = "glossary-branch-delete" 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_path = str(rs_remote.repo.git_dir) + rs.git.remote_add("origin", remote_path) + + rs.git.checkout("VWX", branch=True) + rs.git.commit(allow_empty=True, message="Empty commit") + rs.git.push("origin", "VWX") + + rs.git.checkout("main") + + yield test, rs + + def test_base(): - with loader.start() as (test, rs): - pass + with base_setup() as (test, rs): + rs.git.push("origin", ":VWX") + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_vwx_exists_remotely(): + with base_setup() as (test, rs): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [VWX_BRANCH_EXISTS_REMOTELY], + ) diff --git a/glossary_branch_delete/verify.py b/glossary_branch_delete/verify.py index a457631c..0d0537d2 100644 --- a/glossary_branch_delete/verify.py +++ b/glossary_branch_delete/verify.py @@ -4,26 +4,19 @@ GitAutograderStatus, ) -VWX_BRANCH_EXISTS_LOCALLY = "Branch 'VWX' still exists locally! Remember to delete it." VWX_BRANCH_EXISTS_REMOTELY = ( "Branch 'VWX' still exists on the remote! Remember to delete it from the remote." ) def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - if exercise.repo.branches.has_branch("VWX"): - raise exercise.wrong_answer([VWX_BRANCH_EXISTS_LOCALLY]) - - origin_remote = exercise.repo.remotes.remote("origin") - origin_remote.remote.fetch() - try: exercise.repo.repo.refs["origin/VWX"] raise exercise.wrong_answer([VWX_BRANCH_EXISTS_REMOTELY]) except (IndexError, KeyError): - pass + pass # Branch doesn't exist on remote, which is what we want return exercise.to_output( - ["Great job deleting the VWX branch locally and remotely!"], + ["Great job deleting the VWX branch!"], GitAutograderStatus.SUCCESSFUL, ) From 64fcf95da4b25d2ac7730b84f84eb1f5e2b41cd5 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 28 Jan 2026 16:10:42 +0800 Subject: [PATCH 5/9] Address PR comments --- glossary_branch_delete/README.md | 2 +- glossary_branch_delete/download.py | 11 ++++------- glossary_branch_delete/test_verify.py | 5 +++++ 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/glossary_branch_delete/README.md b/glossary_branch_delete/README.md index fad78034..6b0ba697 100644 --- a/glossary_branch_delete/README.md +++ b/glossary_branch_delete/README.md @@ -1 +1 @@ -See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-delete.html +https://git-mastery.org/lessons/remoteBranchPush/exercise-glossary-branch-push.html \ No newline at end of file diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py index 1fd63372..dc3fe1c5 100644 --- a/glossary_branch_delete/download.py +++ b/glossary_branch_delete/download.py @@ -1,7 +1,7 @@ -from exercise_utils.cli import run_command -from exercise_utils.git import clone_repo_with_git from exercise_utils.github_cli import ( + clone_repo_with_gh, delete_repo, + fork_repo, get_github_username, has_repo, ) @@ -17,9 +17,6 @@ def setup(verbose: bool = False): if has_repo(FORK_NAME, True, verbose): delete_repo(FORK_NAME, verbose) - run_command( - ["gh", "repo", "fork", f"{REPO_OWNER}/{REPO_NAME}", "--fork-name", FORK_NAME], - verbose, - ) + fork_repo(f"{REPO_OWNER}/{REPO_NAME}", FORK_NAME, verbose, True) - clone_repo_with_git(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") + clone_repo_with_gh(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") diff --git a/glossary_branch_delete/test_verify.py b/glossary_branch_delete/test_verify.py index 450ad597..aaf23283 100644 --- a/glossary_branch_delete/test_verify.py +++ b/glossary_branch_delete/test_verify.py @@ -25,6 +25,11 @@ def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: remote_path = str(rs_remote.repo.git_dir) rs.git.remote_add("origin", remote_path) + # doesnt create a branch on remote + rs_remote.git.commit(allow_empty=True, message="Initial commit") + rs_remote.git.checkout("TEST", branch=True) + + # works rs.git.checkout("VWX", branch=True) rs.git.commit(allow_empty=True, message="Empty commit") rs.git.push("origin", "VWX") From 8c9667e3aab31a503eb541ede15878d40c01bf00 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 28 Jan 2026 16:16:40 +0800 Subject: [PATCH 6/9] Edit lesson link --- glossary_branch_delete/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_delete/README.md b/glossary_branch_delete/README.md index 6b0ba697..c32afa84 100644 --- a/glossary_branch_delete/README.md +++ b/glossary_branch_delete/README.md @@ -1 +1 @@ -https://git-mastery.org/lessons/remoteBranchPush/exercise-glossary-branch-push.html \ No newline at end of file +https://git-mastery.org/lessons/remoteBranchDelete/exercise-glossary-branch-delete.html \ No newline at end of file From 21ddb28d8285d05568691c2d70e229c60af21aa3 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 28 Jan 2026 16:23:50 +0800 Subject: [PATCH 7/9] Change fork repo call --- glossary_branch_delete/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py index dc3fe1c5..2bdbadfa 100644 --- a/glossary_branch_delete/download.py +++ b/glossary_branch_delete/download.py @@ -17,6 +17,6 @@ def setup(verbose: bool = False): if has_repo(FORK_NAME, True, verbose): delete_repo(FORK_NAME, verbose) - fork_repo(f"{REPO_OWNER}/{REPO_NAME}", FORK_NAME, verbose, True) + fork_repo(f"{REPO_OWNER}/{REPO_NAME}", FORK_NAME, verbose, False) clone_repo_with_gh(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") From 5424b39a8fc8fb28a2b12137062eedecc21fc68c Mon Sep 17 00:00:00 2001 From: jia xin Date: Sun, 1 Feb 2026 15:12:45 +0800 Subject: [PATCH 8/9] Add local branch --- glossary_branch_delete/download.py | 5 +++++ glossary_branch_delete/test_verify.py | 14 ++++++++++++++ glossary_branch_delete/verify.py | 14 +++++++++++++- 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/glossary_branch_delete/download.py b/glossary_branch_delete/download.py index 2bdbadfa..c40ea884 100644 --- a/glossary_branch_delete/download.py +++ b/glossary_branch_delete/download.py @@ -1,3 +1,4 @@ +from exercise_utils.git import checkout from exercise_utils.github_cli import ( clone_repo_with_gh, delete_repo, @@ -20,3 +21,7 @@ def setup(verbose: bool = False): fork_repo(f"{REPO_OWNER}/{REPO_NAME}", FORK_NAME, verbose, False) clone_repo_with_gh(f"https://github.com/{username}/{FORK_NAME}", verbose, ".") + + # creates VWX branch locally, tracking remote branch + checkout("VWX", False, verbose) + checkout("main", False, verbose) diff --git a/glossary_branch_delete/test_verify.py b/glossary_branch_delete/test_verify.py index aaf23283..77e1a690 100644 --- a/glossary_branch_delete/test_verify.py +++ b/glossary_branch_delete/test_verify.py @@ -10,6 +10,7 @@ from repo_smith.repo_smith import RepoSmith from .verify import ( + VWX_BRANCH_EXISTS_LOCALLY, VWX_BRANCH_EXISTS_REMOTELY, verify, ) @@ -42,6 +43,7 @@ def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: def test_base(): with base_setup() as (test, rs): rs.git.push("origin", ":VWX") + rs.repo.delete_head("VWX", force=True) output = test.run() assert_output(output, GitAutograderStatus.SUCCESSFUL) @@ -55,3 +57,15 @@ def test_vwx_exists_remotely(): GitAutograderStatus.UNSUCCESSFUL, [VWX_BRANCH_EXISTS_REMOTELY], ) + + +def test_vwx_exists_locally(): + with base_setup() as (test, rs): + rs.git.checkout("VWX") + + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [VWX_BRANCH_EXISTS_LOCALLY], + ) diff --git a/glossary_branch_delete/verify.py b/glossary_branch_delete/verify.py index 0d0537d2..353e85a0 100644 --- a/glossary_branch_delete/verify.py +++ b/glossary_branch_delete/verify.py @@ -1,3 +1,4 @@ +from typing import List from git_autograder import ( GitAutograderOutput, GitAutograderExercise, @@ -8,14 +9,25 @@ "Branch 'VWX' still exists on the remote! Remember to delete it from the remote." ) +VWX_BRANCH_EXISTS_LOCALLY = "Branch 'VWX' still exists locally! Remember to delete it from your local repository." + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + comments: List[str] = [] + + local_branches = [h.name for h in exercise.repo.repo.heads] + if "VWX" in local_branches: + comments.append(VWX_BRANCH_EXISTS_LOCALLY) + try: exercise.repo.repo.refs["origin/VWX"] - raise exercise.wrong_answer([VWX_BRANCH_EXISTS_REMOTELY]) + comments.append(VWX_BRANCH_EXISTS_REMOTELY) except (IndexError, KeyError): pass # Branch doesn't exist on remote, which is what we want + if comments: + raise exercise.wrong_answer(comments) + return exercise.to_output( ["Great job deleting the VWX branch!"], GitAutograderStatus.SUCCESSFUL, From 6617a4632b3b487a3bbab9d23f91ced84e13b4f7 Mon Sep 17 00:00:00 2001 From: jia xin Date: Sun, 1 Feb 2026 15:19:04 +0800 Subject: [PATCH 9/9] Add test --- glossary_branch_delete/test_verify.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/glossary_branch_delete/test_verify.py b/glossary_branch_delete/test_verify.py index 77e1a690..8d0fba25 100644 --- a/glossary_branch_delete/test_verify.py +++ b/glossary_branch_delete/test_verify.py @@ -69,3 +69,15 @@ def test_vwx_exists_locally(): GitAutograderStatus.UNSUCCESSFUL, [VWX_BRANCH_EXISTS_LOCALLY], ) + + +def test_vwx_exists_both(): + with base_setup() as (test, rs): + rs.git.checkout("VWX") + + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [VWX_BRANCH_EXISTS_LOCALLY, VWX_BRANCH_EXISTS_REMOTELY], + )