From 8af75eb5f01465080f5c6db0e733e75ec78aeaaf Mon Sep 17 00:00:00 2001 From: jia xin Date: Thu, 15 Jan 2026 12:37:18 +0800 Subject: [PATCH 1/9] Implement setup --- .../.gitmastery-exercise.json | 14 ++++++ glossary_branch_push/README.md | 1 + glossary_branch_push/__init__.py | 0 glossary_branch_push/download.py | 43 +++++++++++++++++++ glossary_branch_push/test_verify.py | 12 ++++++ glossary_branch_push/verify.py | 11 +++++ 6 files changed, 81 insertions(+) create mode 100644 glossary_branch_push/.gitmastery-exercise.json create mode 100644 glossary_branch_push/README.md create mode 100644 glossary_branch_push/__init__.py create mode 100644 glossary_branch_push/download.py create mode 100644 glossary_branch_push/test_verify.py create mode 100644 glossary_branch_push/verify.py diff --git a/glossary_branch_push/.gitmastery-exercise.json b/glossary_branch_push/.gitmastery-exercise.json new file mode 100644 index 00000000..5204d783 --- /dev/null +++ b/glossary_branch_push/.gitmastery-exercise.json @@ -0,0 +1,14 @@ +{ + "exercise_name": "glossary-branch-push", + "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 + } +} diff --git a/glossary_branch_push/README.md b/glossary_branch_push/README.md new file mode 100644 index 00000000..b95d6c74 --- /dev/null +++ b/glossary_branch_push/README.md @@ -0,0 +1 @@ +See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-push.html diff --git a/glossary_branch_push/__init__.py b/glossary_branch_push/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/glossary_branch_push/download.py b/glossary_branch_push/download.py new file mode 100644 index 00000000..010a7054 --- /dev/null +++ b/glossary_branch_push/download.py @@ -0,0 +1,43 @@ +import os + +from exercise_utils.cli import run_command +from exercise_utils.file import create_or_update_file +from exercise_utils.git import add, checkout, clone_repo_with_git, commit +from exercise_utils.github_cli import ( + delete_repo, + get_github_username, + has_repo, +) + +__requires_git__ = True +__requires_github__ = True + + +def setup(verbose: bool = False): + REPO_OWNER = "git-mastery" + REPO_NAME = "samplerepo-funny-glossary" + username = get_github_username(verbose) + FORK_NAME = f"{username}-gitmastery-samplerepo-funny-glossary" + CLONE_DIR = "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, "." + ) + + checkout("PQR", True, verbose) + + create_or_update_file( + "r.txt", + "refactoring: Improving the code without changing what it does... in theory.\n", + ) + + add(["r.txt"], verbose) + commit("Add 'refactoring'", verbose) diff --git a/glossary_branch_push/test_verify.py b/glossary_branch_push/test_verify.py new file mode 100644 index 00000000..40bd1f3e --- /dev/null +++ b/glossary_branch_push/test_verify.py @@ -0,0 +1,12 @@ +from exercise_utils.test import GitAutograderTestLoader + +from .verify import verify + +REPOSITORY_NAME = "glossary-branch-push" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_base(): + with loader.start() as (test, rs): + pass diff --git a/glossary_branch_push/verify.py b/glossary_branch_push/verify.py new file mode 100644 index 00000000..1288d3de --- /dev/null +++ b/glossary_branch_push/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 c01c2f6c80cedc3384c6af23b7c328a0e4de014c Mon Sep 17 00:00:00 2001 From: jia xin Date: Thu, 15 Jan 2026 12:53:39 +0800 Subject: [PATCH 2/9] Implement verify logic --- glossary_branch_push/verify.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/glossary_branch_push/verify.py b/glossary_branch_push/verify.py index 1288d3de..9ee67ee9 100644 --- a/glossary_branch_push/verify.py +++ b/glossary_branch_push/verify.py @@ -1,11 +1,27 @@ from git_autograder import ( - GitAutograderOutput, GitAutograderExercise, + GitAutograderOutput, GitAutograderStatus, ) +PQR_BRANCH_MISSING = "Branch 'PQR' is missing locally! Did you delete it?" +PQR_BRANCH_NOT_PUSHED = "Branch 'PQR' has not been pushed to the remote! Remember to push it to origin." + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # INSERT YOUR GRADING CODE HERE + has_local_pqr_branch = exercise.repo.branches.has_branch("PQR") + if not has_local_pqr_branch: + raise exercise.wrong_answer([PQR_BRANCH_MISSING]) + + origin_remote = exercise.repo.remotes.remote("origin") + origin_remote.remote.fetch() + + try: + remote_pqr = exercise.repo.repo.refs["origin/PQR"] + except (IndexError, KeyError): + raise exercise.wrong_answer([PQR_BRANCH_NOT_PUSHED]) - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) + return exercise.to_output( + ["Great work pushing the PQR branch to your fork!"], + GitAutograderStatus.SUCCESSFUL, + ) From 0c3706a83ee641b91629151b5e2acb675cf689da Mon Sep 17 00:00:00 2001 From: jia xin Date: Thu, 15 Jan 2026 14:00:40 +0800 Subject: [PATCH 3/9] Add tests --- glossary_branch_push/download.py | 6 ++-- glossary_branch_push/test_verify.py | 55 ++++++++++++++++++++++++++--- glossary_branch_push/verify.py | 7 +--- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/glossary_branch_push/download.py b/glossary_branch_push/download.py index 010a7054..ade59a7d 100644 --- a/glossary_branch_push/download.py +++ b/glossary_branch_push/download.py @@ -12,13 +12,13 @@ __requires_git__ = True __requires_github__ = True +REPO_OWNER = "git-mastery" +REPO_NAME = "samplerepo-funny-glossary" + def setup(verbose: bool = False): - REPO_OWNER = "git-mastery" - REPO_NAME = "samplerepo-funny-glossary" username = get_github_username(verbose) FORK_NAME = f"{username}-gitmastery-samplerepo-funny-glossary" - CLONE_DIR = "funny-glossary" if has_repo(FORK_NAME, True, verbose): delete_repo(FORK_NAME, verbose) diff --git a/glossary_branch_push/test_verify.py b/glossary_branch_push/test_verify.py index 40bd1f3e..750bbfb6 100644 --- a/glossary_branch_push/test_verify.py +++ b/glossary_branch_push/test_verify.py @@ -1,12 +1,59 @@ -from exercise_utils.test import GitAutograderTestLoader +from contextlib import contextmanager +from typing import Iterator, Tuple +import tempfile +import os -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 repo_smith.steps.bash_step import BashStep + +from .verify import PQR_BRANCH_NOT_PUSHED, verify REPOSITORY_NAME = "glossary-branch-push" loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) -def test_base(): +@contextmanager +def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: with loader.start() as (test, rs): - pass + rs.git.commit(message="Initial commit", allow_empty=True) + + # Create a bare repo to use as remote + remote_dir = tempfile.mkdtemp() + remote_path = os.path.join(remote_dir, "remote.git") + os.makedirs(remote_path) + BashStep(name=None, description=None, id=None, body=f"git init --bare {remote_path}").execute(rs.repo) + + rs.git.remote_add("origin", remote_path) + + rs.git.checkout("PQR", branch=True) + rs.files.create_or_update("r.txt", "refactoring: Improving the code without changing what it does... in theory.\n") + rs.git.add(all=True) + rs.git.commit(message="Add 'refactoring'") + rs.git.checkout("main") + + yield test, rs + + +def test_base(): + with base_setup() as (test, rs): + BashStep(name=None, description=None, id=None, body="git push origin PQR").execute(rs.repo) + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_pqr_not_pushed(): + with base_setup() as (test, rs): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [PQR_BRANCH_NOT_PUSHED], + ) diff --git a/glossary_branch_push/verify.py b/glossary_branch_push/verify.py index 9ee67ee9..ed6614eb 100644 --- a/glossary_branch_push/verify.py +++ b/glossary_branch_push/verify.py @@ -4,15 +4,10 @@ GitAutograderStatus, ) -PQR_BRANCH_MISSING = "Branch 'PQR' is missing locally! Did you delete it?" -PQR_BRANCH_NOT_PUSHED = "Branch 'PQR' has not been pushed to the remote! Remember to push it to origin." +PQR_BRANCH_NOT_PUSHED = "Branch 'PQR' has not been pushed to the remote." def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - has_local_pqr_branch = exercise.repo.branches.has_branch("PQR") - if not has_local_pqr_branch: - raise exercise.wrong_answer([PQR_BRANCH_MISSING]) - origin_remote = exercise.repo.remotes.remote("origin") origin_remote.remote.fetch() From b22ba314ddb98c001f63b8f51a52282e92685345 Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 14:53:10 +0800 Subject: [PATCH 4/9] Add test code --- glossary_branch_push/test_verify.py | 23 +++++++++-------------- 1 file changed, 9 insertions(+), 14 deletions(-) diff --git a/glossary_branch_push/test_verify.py b/glossary_branch_push/test_verify.py index 750bbfb6..f96703e0 100644 --- a/glossary_branch_push/test_verify.py +++ b/glossary_branch_push/test_verify.py @@ -1,7 +1,5 @@ from contextlib import contextmanager from typing import Iterator, Tuple -import tempfile -import os from exercise_utils.test import ( GitAutograderTest, @@ -10,7 +8,6 @@ ) from git_autograder import GitAutograderStatus from repo_smith.repo_smith import RepoSmith -from repo_smith.steps.bash_step import BashStep from .verify import PQR_BRANCH_NOT_PUSHED, verify @@ -21,19 +18,17 @@ @contextmanager def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: - with loader.start() as (test, rs): + with loader.start(include_remote_repo=True) as (test, rs, rs_remote): rs.git.commit(message="Initial commit", allow_empty=True) - - # Create a bare repo to use as remote - remote_dir = tempfile.mkdtemp() - remote_path = os.path.join(remote_dir, "remote.git") - os.makedirs(remote_path) - BashStep(name=None, description=None, id=None, body=f"git init --bare {remote_path}").execute(rs.repo) - + + remote_path = str(rs_remote.repo.git_dir) rs.git.remote_add("origin", remote_path) - + rs.git.checkout("PQR", branch=True) - rs.files.create_or_update("r.txt", "refactoring: Improving the code without changing what it does... in theory.\n") + rs.files.create_or_update( + "r.txt", + "refactoring: Improving the code without changing what it does... in theory.\n", + ) rs.git.add(all=True) rs.git.commit(message="Add 'refactoring'") rs.git.checkout("main") @@ -43,7 +38,7 @@ def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: def test_base(): with base_setup() as (test, rs): - BashStep(name=None, description=None, id=None, body="git push origin PQR").execute(rs.repo) + rs.git.push("origin", "PQR") output = test.run() assert_output(output, GitAutograderStatus.SUCCESSFUL) From fda4352f1cbf097d1125c51c2ceddbcf2377aa59 Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 14:59:21 +0800 Subject: [PATCH 5/9] Clean up code --- glossary_branch_push/test_verify.py | 2 -- glossary_branch_push/verify.py | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/glossary_branch_push/test_verify.py b/glossary_branch_push/test_verify.py index f96703e0..398cdbd8 100644 --- a/glossary_branch_push/test_verify.py +++ b/glossary_branch_push/test_verify.py @@ -19,8 +19,6 @@ @contextmanager def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: with loader.start(include_remote_repo=True) as (test, rs, rs_remote): - rs.git.commit(message="Initial commit", allow_empty=True) - remote_path = str(rs_remote.repo.git_dir) rs.git.remote_add("origin", remote_path) diff --git a/glossary_branch_push/verify.py b/glossary_branch_push/verify.py index ed6614eb..07f6a758 100644 --- a/glossary_branch_push/verify.py +++ b/glossary_branch_push/verify.py @@ -12,7 +12,7 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: origin_remote.remote.fetch() try: - remote_pqr = exercise.repo.repo.refs["origin/PQR"] + exercise.repo.repo.refs["origin/PQR"] except (IndexError, KeyError): raise exercise.wrong_answer([PQR_BRANCH_NOT_PUSHED]) From 90f2b02d2cef4538399a0857d09db115e9b3191e Mon Sep 17 00:00:00 2001 From: jia xin Date: Tue, 27 Jan 2026 17:01:12 +0800 Subject: [PATCH 6/9] Remove fetch --- glossary_branch_push/verify.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/glossary_branch_push/verify.py b/glossary_branch_push/verify.py index 07f6a758..43896014 100644 --- a/glossary_branch_push/verify.py +++ b/glossary_branch_push/verify.py @@ -8,9 +8,6 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - origin_remote = exercise.repo.remotes.remote("origin") - origin_remote.remote.fetch() - try: exercise.repo.repo.refs["origin/PQR"] except (IndexError, KeyError): From 25332c4bd5bb2f29cd9e9279e036ca1d0603ae68 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 28 Jan 2026 16:15:57 +0800 Subject: [PATCH 7/9] Address PR comments --- glossary_branch_push/README.md | 2 +- glossary_branch_push/download.py | 16 +++++----------- 2 files changed, 6 insertions(+), 12 deletions(-) diff --git a/glossary_branch_push/README.md b/glossary_branch_push/README.md index b95d6c74..6b0ba697 100644 --- a/glossary_branch_push/README.md +++ b/glossary_branch_push/README.md @@ -1 +1 @@ -See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-push.html +https://git-mastery.org/lessons/remoteBranchPush/exercise-glossary-branch-push.html \ No newline at end of file diff --git a/glossary_branch_push/download.py b/glossary_branch_push/download.py index ade59a7d..91dd7c7d 100644 --- a/glossary_branch_push/download.py +++ b/glossary_branch_push/download.py @@ -1,10 +1,9 @@ -import os - -from exercise_utils.cli import run_command from exercise_utils.file import create_or_update_file -from exercise_utils.git import add, checkout, clone_repo_with_git, commit +from exercise_utils.git import add, checkout, commit from exercise_utils.github_cli import ( + clone_repo_with_gh, delete_repo, + fork_repo, get_github_username, has_repo, ) @@ -23,14 +22,9 @@ 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, ".") checkout("PQR", True, verbose) From f3ee3a1542da3dc42a17bfd87cb3a61703f9de53 Mon Sep 17 00:00:00 2001 From: jia xin Date: Wed, 28 Jan 2026 16:28:04 +0800 Subject: [PATCH 8/9] Change fork repo call --- glossary_branch_push/download.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_push/download.py b/glossary_branch_push/download.py index 91dd7c7d..c3da0259 100644 --- a/glossary_branch_push/download.py +++ b/glossary_branch_push/download.py @@ -22,7 +22,7 @@ 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 034321668acbf6f30a34d97a9193b7b0bde0677f Mon Sep 17 00:00:00 2001 From: jia xin Date: Sun, 1 Feb 2026 14:50:08 +0800 Subject: [PATCH 9/9] Remove config --- glossary_branch_push/download.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/glossary_branch_push/download.py b/glossary_branch_push/download.py index c3da0259..485c1f05 100644 --- a/glossary_branch_push/download.py +++ b/glossary_branch_push/download.py @@ -8,9 +8,6 @@ has_repo, ) -__requires_git__ = True -__requires_github__ = True - REPO_OWNER = "git-mastery" REPO_NAME = "samplerepo-funny-glossary"