From 032dc4a99d39dcdedb57a5b49e1a3a8542b98e26 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Fri, 16 Jan 2026 20:56:02 +0800 Subject: [PATCH 01/15] Implement download.py --- .../.gitmastery-exercise.json | 16 +++++++ glossary_branch_pull/README.md | 1 + glossary_branch_pull/__init__.py | 0 glossary_branch_pull/download.py | 42 +++++++++++++++++++ glossary_branch_pull/test_verify.py | 12 ++++++ glossary_branch_pull/verify.py | 11 +++++ 6 files changed, 82 insertions(+) create mode 100644 glossary_branch_pull/.gitmastery-exercise.json create mode 100644 glossary_branch_pull/README.md create mode 100644 glossary_branch_pull/__init__.py create mode 100644 glossary_branch_pull/download.py create mode 100644 glossary_branch_pull/test_verify.py create mode 100644 glossary_branch_pull/verify.py diff --git a/glossary_branch_pull/.gitmastery-exercise.json b/glossary_branch_pull/.gitmastery-exercise.json new file mode 100644 index 00000000..e13c71a7 --- /dev/null +++ b/glossary_branch_pull/.gitmastery-exercise.json @@ -0,0 +1,16 @@ +{ + "exercise_name": "glossary-branch-pull", + "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_pull/README.md b/glossary_branch_pull/README.md new file mode 100644 index 00000000..753c29c4 --- /dev/null +++ b/glossary_branch_pull/README.md @@ -0,0 +1 @@ +See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-pull.html diff --git a/glossary_branch_pull/__init__.py b/glossary_branch_pull/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py new file mode 100644 index 00000000..bf369513 --- /dev/null +++ b/glossary_branch_pull/download.py @@ -0,0 +1,42 @@ +import os + +from exercise_utils.cli import run_command +from exercise_utils.file import create_or_update_file +from exercise_utils.gitmastery import create_start_tag +from exercise_utils.git import add, checkout, clone_repo_with_git, commit, push +from exercise_utils.github_cli import ( + get_github_username, + fork_repo, + has_repo, + delete_repo, +) + +__resources__ = {} + +TARGET_REPO = "git-mastery/samplerepo-funny-glossary" +FORK_NAME = "gitmastery-samplerepo-funny-glossary" +LOCAL_DIR = "funny-glossary" + + +def setup(verbose: bool = False): + username = get_github_username(verbose) + full_repo_name = f"{username}/{FORK_NAME}" + + if has_repo(full_repo_name, True, verbose): + delete_repo(full_repo_name, verbose) + + fork_repo(TARGET_REPO, FORK_NAME, verbose, False) + clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, LOCAL_DIR) + + os.chdir(LOCAL_DIR) + + run_command(["git", "branch", "-dr", "origin/VWX"], verbose) + + checkout("ABC", False, verbose) + run_command(["git", "reset", "--hard", "HEAD~1"], verbose) + + checkout("DEF", False, verbose) + run_command(["git", "reset", "--hard", "HEAD~1"], verbose) + create_or_update_file("e.txt", "documentation: Evidence that someone once cared.\n") + add(["e.txt"], verbose) + commit("Add 'documentation'", verbose) diff --git a/glossary_branch_pull/test_verify.py b/glossary_branch_pull/test_verify.py new file mode 100644 index 00000000..383f8943 --- /dev/null +++ b/glossary_branch_pull/test_verify.py @@ -0,0 +1,12 @@ +from exercise_utils.test import GitAutograderTestLoader + +from .verify import verify + +REPOSITORY_NAME = "glossary-branch-pull" + +loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) + + +def test_base(): + with loader.start() as (test, rs): + pass diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py new file mode 100644 index 00000000..1288d3de --- /dev/null +++ b/glossary_branch_pull/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 4ae24d095816d8cb90edfec3ccad4f65f8b2a44d Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Fri, 16 Jan 2026 21:00:09 +0800 Subject: [PATCH 02/15] Update README.md --- glossary_branch_pull/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_pull/README.md b/glossary_branch_pull/README.md index 753c29c4..db082fda 100644 --- a/glossary_branch_pull/README.md +++ b/glossary_branch_pull/README.md @@ -1 +1 @@ -See https://git-mastery.github.io/lessons/{LESSON_ID}/exercise-glossary-branch-pull.html +See https://git-mastery.github.io/lessons/remoteBranchPull/exercise-glossary-branch-pull.html From 568337baf09cc8fbb9e7b3e6f57644496c4a528b Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Mon, 19 Jan 2026 13:54:22 +0800 Subject: [PATCH 03/15] Implement setup logic --- glossary_branch_pull/.gitmastery-exercise.json | 2 +- glossary_branch_pull/download.py | 12 +++--------- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/glossary_branch_pull/.gitmastery-exercise.json b/glossary_branch_pull/.gitmastery-exercise.json index e13c71a7..e5f78bf5 100644 --- a/glossary_branch_pull/.gitmastery-exercise.json +++ b/glossary_branch_pull/.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 diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py index bf369513..cfc3a2c1 100644 --- a/glossary_branch_pull/download.py +++ b/glossary_branch_pull/download.py @@ -1,8 +1,5 @@ -import os - from exercise_utils.cli import run_command from exercise_utils.file import create_or_update_file -from exercise_utils.gitmastery import create_start_tag from exercise_utils.git import add, checkout, clone_repo_with_git, commit, push from exercise_utils.github_cli import ( get_github_username, @@ -11,11 +8,10 @@ delete_repo, ) -__resources__ = {} - TARGET_REPO = "git-mastery/samplerepo-funny-glossary" FORK_NAME = "gitmastery-samplerepo-funny-glossary" -LOCAL_DIR = "funny-glossary" + +__resources__ = {} def setup(verbose: bool = False): @@ -26,9 +22,7 @@ def setup(verbose: bool = False): delete_repo(full_repo_name, verbose) fork_repo(TARGET_REPO, FORK_NAME, verbose, False) - clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, LOCAL_DIR) - - os.chdir(LOCAL_DIR) + clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, ".") run_command(["git", "branch", "-dr", "origin/VWX"], verbose) From a52163f80a91a246054c981d8baf596a82f9e8aa Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 20 Jan 2026 00:06:20 +0800 Subject: [PATCH 04/15] Implement verify logic --- glossary_branch_pull/verify.py | 50 ++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 1288d3de..2110ee05 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -4,8 +4,54 @@ GitAutograderStatus, ) +BRANCH_NOT_CREATED = "The local {branch} branch is not created." +BRANCH_NOT_TRACKING = "The local {branch} branch does not track origin/{branch}." +BRANCH_MISSING = "The local {branch} branch does not exist." +COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch." + def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: - # INSERT YOUR GRADING CODE HERE + repo = exercise.repo + comments = [] + + if not repo.branches.has_branch("STU"): + comments.append(BRANCH_NOT_CREATED.format(branch="STU")) + else: + stu_branch = repo.branches.branch("STU").branch + tracking = stu_branch.tracking_branch() + if not tracking or tracking.name != 'origin/STU': + comments.append(BRANCH_NOT_TRACKING.format(branch="STU")) + + if not repo.branches.has_branch("VWX"): + comments.append(BRANCH_NOT_CREATED.format(branch="VWX")) + else: + vwx_branch = repo.branches.branch("VWX").branch + tracking = vwx_branch.tracking_branch() + if not tracking or tracking.name != 'origin/VWX': + comments.append(BRANCH_NOT_TRACKING.format(branch="VWX")) + + if not repo.branches.has_branch("ABC"): + comments.append(BRANCH_MISSING.format(branch="ABC")) + else: + abc_branch = repo.branches.branch("ABC").branch + remote_abc = abc_branch.tracking_branch() + local_commits = set(commit.hexsha for commit in repo.branches.branch("ABC").commits) + remote_commit_hexsha = remote_abc.commit.hexsha + if remote_commit_hexsha not in local_commits: + comments.append(COMMIT_MISSING.format(branch="ABC")) + + if not repo.branches.has_branch("DEF"): + comments.append(BRANCH_MISSING.format(branch="DEF")) + else: + def_branch = repo.branches.branch("DEF").branch + remote_def = def_branch.tracking_branch() + local_commits = set(commit.hexsha for commit in repo.branches.branch("DEF").commits) + remote_commit_hexsha = remote_def.commit.hexsha + if remote_commit_hexsha not in local_commits: + comments.append(COMMIT_MISSING.format(branch="DEF")) - return exercise.to_output([], GitAutograderStatus.SUCCESSFUL) + if comments: + return exercise.to_output(comments, GitAutograderStatus.UNSUCCESSFUL) + return exercise.to_output([ + "Great work! All required branches are present and correctly set up." + ], GitAutograderStatus.SUCCESSFUL) From 2eddc7a9801e9f520c2250774d4d21e34d5ec536 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 20 Jan 2026 00:13:38 +0800 Subject: [PATCH 05/15] Improve verify logic --- glossary_branch_pull/verify.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 2110ee05..1266a180 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -35,20 +35,26 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: else: abc_branch = repo.branches.branch("ABC").branch remote_abc = abc_branch.tracking_branch() - local_commits = set(commit.hexsha for commit in repo.branches.branch("ABC").commits) - remote_commit_hexsha = remote_abc.commit.hexsha - if remote_commit_hexsha not in local_commits: - comments.append(COMMIT_MISSING.format(branch="ABC")) + if remote_abc: + local_commits = set(commit.hexsha for commit in repo.branches.branch("ABC").commits) + remote_commit_hexsha = remote_abc.commit.hexsha + if remote_commit_hexsha not in local_commits: + comments.append(COMMIT_MISSING.format(branch="ABC")) + else: + comments.append(BRANCH_NOT_TRACKING.format(branch="ABC")) if not repo.branches.has_branch("DEF"): comments.append(BRANCH_MISSING.format(branch="DEF")) else: def_branch = repo.branches.branch("DEF").branch remote_def = def_branch.tracking_branch() - local_commits = set(commit.hexsha for commit in repo.branches.branch("DEF").commits) - remote_commit_hexsha = remote_def.commit.hexsha - if remote_commit_hexsha not in local_commits: - comments.append(COMMIT_MISSING.format(branch="DEF")) + if remote_def: + local_commits = set(commit.hexsha for commit in repo.branches.branch("DEF").commits) + remote_commit_hexsha = remote_def.commit.hexsha + if remote_commit_hexsha not in local_commits: + comments.append(COMMIT_MISSING.format(branch="DEF")) + else: + comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) if comments: return exercise.to_output(comments, GitAutograderStatus.UNSUCCESSFUL) From 30b07503cdb813762268ffbb5bdfb0322a9dae2c Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 20 Jan 2026 09:50:33 +0800 Subject: [PATCH 06/15] Remove resources --- glossary_branch_pull/download.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py index cfc3a2c1..53981566 100644 --- a/glossary_branch_pull/download.py +++ b/glossary_branch_pull/download.py @@ -11,8 +11,6 @@ TARGET_REPO = "git-mastery/samplerepo-funny-glossary" FORK_NAME = "gitmastery-samplerepo-funny-glossary" -__resources__ = {} - def setup(verbose: bool = False): username = get_github_username(verbose) From 6e81193d551db40bbc70ecc65b090b860c1588fb Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 20 Jan 2026 10:23:57 +0800 Subject: [PATCH 07/15] Add wrong_answer --- glossary_branch_pull/verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 1266a180..df4e33b8 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -57,7 +57,7 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) if comments: - return exercise.to_output(comments, GitAutograderStatus.UNSUCCESSFUL) + return exercise.wrong_answer(comments) return exercise.to_output([ "Great work! All required branches are present and correctly set up." ], GitAutograderStatus.SUCCESSFUL) From b5fe84c35c77d59ce950b581f65b4bbd26adb05f Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Tue, 20 Jan 2026 11:00:55 +0800 Subject: [PATCH 08/15] Fix bug --- glossary_branch_pull/verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index df4e33b8..2a5a2801 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -57,7 +57,7 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) if comments: - return exercise.wrong_answer(comments) + raise exercise.wrong_answer(comments) return exercise.to_output([ "Great work! All required branches are present and correctly set up." ], GitAutograderStatus.SUCCESSFUL) From 375a2b84df56d8de7268b3aec8cdd00e5607c627 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Thu, 29 Jan 2026 00:00:08 +0800 Subject: [PATCH 09/15] Improve logic --- glossary_branch_pull/verify.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 2a5a2801..1b31b320 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -8,7 +8,14 @@ BRANCH_NOT_TRACKING = "The local {branch} branch does not track origin/{branch}." BRANCH_MISSING = "The local {branch} branch does not exist." COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch." +LOCAL_COMMIT_MISSING = "The original local commit on DEF is missing. You may have lost your work instead of merging." +def get_commit_from_message(commits, message): + """Find a commit with the given message from a list of commits.""" + for commit in commits: + if message.strip() == commit.message.strip(): + return commit + return None def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: repo = exercise.repo @@ -49,10 +56,12 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: def_branch = repo.branches.branch("DEF").branch remote_def = def_branch.tracking_branch() if remote_def: - local_commits = set(commit.hexsha for commit in repo.branches.branch("DEF").commits) + local_commits = repo.branches.branch("DEF").commits remote_commit_hexsha = remote_def.commit.hexsha - if remote_commit_hexsha not in local_commits: + if remote_commit_hexsha not in set(commit.hexsha for commit in local_commits): comments.append(COMMIT_MISSING.format(branch="DEF")) + if not get_commit_from_message(local_commits, "Add 'documentation'"): + comments.append(LOCAL_COMMIT_MISSING) else: comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) From e023ba2728c6798a7c9cf327ab1e07154803f75d Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Thu, 29 Jan 2026 09:13:43 +0800 Subject: [PATCH 10/15] Change e.txt to d.txt --- glossary_branch_pull/download.py | 4 ++-- glossary_branch_pull/verify.py | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py index 53981566..b553b635 100644 --- a/glossary_branch_pull/download.py +++ b/glossary_branch_pull/download.py @@ -29,6 +29,6 @@ def setup(verbose: bool = False): checkout("DEF", False, verbose) run_command(["git", "reset", "--hard", "HEAD~1"], verbose) - create_or_update_file("e.txt", "documentation: Evidence that someone once cared.\n") - add(["e.txt"], verbose) + create_or_update_file("d.txt", "documentation: Evidence that someone once cared.\n") + add(["d.txt"], verbose) commit("Add 'documentation'", verbose) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 1b31b320..1842c7a2 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -10,6 +10,7 @@ COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch." LOCAL_COMMIT_MISSING = "The original local commit on DEF is missing. You may have lost your work instead of merging." + def get_commit_from_message(commits, message): """Find a commit with the given message from a list of commits.""" for commit in commits: From 190bc3a036fb1abed042bf4c619aba01b5516161 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Sun, 1 Feb 2026 16:43:45 +0800 Subject: [PATCH 11/15] Improve verify logic --- glossary_branch_pull/verify.py | 77 +++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index 1842c7a2..a305ab58 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -1,20 +1,31 @@ +from typing import List from git_autograder import ( + GitAutograderCommit, GitAutograderOutput, GitAutograderExercise, GitAutograderStatus, ) -BRANCH_NOT_CREATED = "The local {branch} branch is not created." +BRANCH_MISSING = "The local {branch} branch is not created." BRANCH_NOT_TRACKING = "The local {branch} branch does not track origin/{branch}." -BRANCH_MISSING = "The local {branch} branch does not exist." -COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch." -LOCAL_COMMIT_MISSING = "The original local commit on DEF is missing. You may have lost your work instead of merging." +REMOTE_COMMIT_MISSING = "New commit in the remote {branch} branch is not pulled to the local {branch} branch." +LOCAL_COMMIT_MISSING = "The original local commit on DEF is missing. " \ +"You may have lost your work instead of merging." -def get_commit_from_message(commits, message): +def get_commit_from_message(commits: List[GitAutograderCommit], message: str) \ + -> GitAutograderCommit | None: """Find a commit with the given message from a list of commits.""" for commit in commits: - if message.strip() == commit.message.strip(): + if message.strip() == commit.commit.message.strip(): + return commit + return None + +def get_commit_from_hexsha(commits: List[GitAutograderCommit], hexsha: str) \ + -> GitAutograderCommit | None: + """Find a commit with the given hexsha from a list of commits.""" + for commit in commits: + if hexsha.strip() == commit.commit.hexsha.strip(): return commit return None @@ -23,48 +34,48 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: comments = [] if not repo.branches.has_branch("STU"): - comments.append(BRANCH_NOT_CREATED.format(branch="STU")) + comments.append(BRANCH_MISSING.format(branch="STU")) else: - stu_branch = repo.branches.branch("STU").branch - tracking = stu_branch.tracking_branch() - if not tracking or tracking.name != 'origin/STU': + try: + exercise.repo.repo.refs["origin/STU"] + except (IndexError, KeyError): comments.append(BRANCH_NOT_TRACKING.format(branch="STU")) + pass if not repo.branches.has_branch("VWX"): - comments.append(BRANCH_NOT_CREATED.format(branch="VWX")) + comments.append(BRANCH_MISSING.format(branch="VWX")) else: - vwx_branch = repo.branches.branch("VWX").branch - tracking = vwx_branch.tracking_branch() - if not tracking or tracking.name != 'origin/VWX': + try: + exercise.repo.repo.refs["origin/VWX"] + except (IndexError, KeyError): comments.append(BRANCH_NOT_TRACKING.format(branch="VWX")) - + pass + if not repo.branches.has_branch("ABC"): comments.append(BRANCH_MISSING.format(branch="ABC")) else: - abc_branch = repo.branches.branch("ABC").branch - remote_abc = abc_branch.tracking_branch() - if remote_abc: - local_commits = set(commit.hexsha for commit in repo.branches.branch("ABC").commits) - remote_commit_hexsha = remote_abc.commit.hexsha - if remote_commit_hexsha not in local_commits: - comments.append(COMMIT_MISSING.format(branch="ABC")) - else: + try: + remote_abc = exercise.repo.repo.refs["origin/ABC"] + abc_commits = repo.branches.branch("ABC").commits + if not get_commit_from_hexsha(abc_commits, remote_abc.commit.hexsha): + comments.append(REMOTE_COMMIT_MISSING.format(branch="ABC")) + except (IndexError, KeyError): comments.append(BRANCH_NOT_TRACKING.format(branch="ABC")) - + pass + if not repo.branches.has_branch("DEF"): comments.append(BRANCH_MISSING.format(branch="DEF")) else: - def_branch = repo.branches.branch("DEF").branch - remote_def = def_branch.tracking_branch() - if remote_def: - local_commits = repo.branches.branch("DEF").commits - remote_commit_hexsha = remote_def.commit.hexsha - if remote_commit_hexsha not in set(commit.hexsha for commit in local_commits): - comments.append(COMMIT_MISSING.format(branch="DEF")) - if not get_commit_from_message(local_commits, "Add 'documentation'"): + try: + remote_def = exercise.repo.repo.refs["origin/DEF"] + def_commits = repo.branches.branch("DEF").commits + if not get_commit_from_hexsha(def_commits, remote_def.commit.hexsha): + comments.append(REMOTE_COMMIT_MISSING.format(branch="DEF")) + if not get_commit_from_message(def_commits, "Add 'documentation'"): comments.append(LOCAL_COMMIT_MISSING) - else: + except (IndexError, KeyError): comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) + pass if comments: raise exercise.wrong_answer(comments) From 1378c76e55617437df9f34743a7c7848e68b90e5 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Sun, 1 Feb 2026 17:03:31 +0800 Subject: [PATCH 12/15] Implement test_verify --- glossary_branch_pull/test_verify.py | 108 ++++++++++++++++++++++++++-- glossary_branch_pull/verify.py | 6 +- 2 files changed, 106 insertions(+), 8 deletions(-) diff --git a/glossary_branch_pull/test_verify.py b/glossary_branch_pull/test_verify.py index 383f8943..da254950 100644 --- a/glossary_branch_pull/test_verify.py +++ b/glossary_branch_pull/test_verify.py @@ -1,12 +1,110 @@ -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 ( + verify, + BRANCH_MISSING, + BRANCH_NOT_TRACKING, + REMOTE_COMMIT_MISSING, + LOCAL_COMMIT_MISSING, +) REPOSITORY_NAME = "glossary-branch-pull" loader = GitAutograderTestLoader(REPOSITORY_NAME, verify) -def test_base(): - with loader.start() as (test, rs): - pass +@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.commit(allow_empty=True, message="Initial commit") + + rs.git.checkout("ABC", branch=True) + rs.git.commit(allow_empty=True, message="Add 'cache'") + rs.git.push("origin", "ABC") + rs.git.reset("HEAD~1", hard=True) + + rs.git.checkout("DEF", branch=True) + rs.git.commit(allow_empty=True, message="Add 'exception'") + rs.git.push("origin", "DEF") + rs.git.reset("HEAD~1", hard=True) + rs.git.commit(allow_empty=True, message="Add 'documentation'") + + yield (test, rs) + +def test_no_changes(): + with base_setup() as (test, rs): + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + BRANCH_MISSING.format(branch="STU"), + BRANCH_MISSING.format(branch="VWX"), + REMOTE_COMMIT_MISSING.format(branch="ABC"), + REMOTE_COMMIT_MISSING.format(branch="DEF") + ]) + + +def test_branch_not_tracking(): + with base_setup() as (test, rs): + rs.git.checkout("STU", branch=True) + rs.git.checkout("VWX", branch=True) + + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [ + BRANCH_NOT_TRACKING.format(branch="STU"), + BRANCH_NOT_TRACKING.format(branch="VWX"), + REMOTE_COMMIT_MISSING.format(branch="ABC"), + REMOTE_COMMIT_MISSING.format(branch="DEF") + ]) + + +def test_def_local_commit_missing(): + with base_setup() as (test, rs): + rs.git.checkout("STU", branch=True) + rs.git.push("origin", "STU") + + rs.git.checkout("VWX", branch=True) + rs.git.push("origin", "VWX") + + rs.git.checkout("ABC") + rs.git.commit(allow_empty=True, message="Add 'cache'") + + rs.git.checkout("DEF") + rs.git.reset("HEAD~1", hard=True) + rs.git.run(["git", "pull", "origin", "DEF"]) + + output = test.run() + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [LOCAL_COMMIT_MISSING]) + + +def test_successful_changes(): + with base_setup() as (test, rs): + rs.git.checkout("VWX", branch=True) + rs.git.push("origin", "VWX") + + rs.git.checkout("STU", branch=True) + rs.git.push("origin", "STU") + + rs.git.checkout("ABC") + rs.git.run(["git", "pull", "origin", "ABC"]) + + rs.git.checkout("DEF") + rs.git.run(["git", "pull", "origin", "DEF"]) + + output = test.run() + assert_output(output, GitAutograderStatus.SUCCESSFUL) \ No newline at end of file diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index a305ab58..dc74a4a5 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -66,13 +66,13 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: if not repo.branches.has_branch("DEF"): comments.append(BRANCH_MISSING.format(branch="DEF")) else: + def_commits = repo.branches.branch("DEF").commits + if not get_commit_from_message(def_commits, "Add 'documentation'"): + comments.append(LOCAL_COMMIT_MISSING) try: remote_def = exercise.repo.repo.refs["origin/DEF"] - def_commits = repo.branches.branch("DEF").commits if not get_commit_from_hexsha(def_commits, remote_def.commit.hexsha): comments.append(REMOTE_COMMIT_MISSING.format(branch="DEF")) - if not get_commit_from_message(def_commits, "Add 'documentation'"): - comments.append(LOCAL_COMMIT_MISSING) except (IndexError, KeyError): comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) pass From 7d3c756f06f9221eed6857f30d5a40888d9f003f Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Sun, 1 Feb 2026 17:27:58 +0800 Subject: [PATCH 13/15] Change pull to fetch and merge --- glossary_branch_pull/test_verify.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/glossary_branch_pull/test_verify.py b/glossary_branch_pull/test_verify.py index da254950..37fe2b9f 100644 --- a/glossary_branch_pull/test_verify.py +++ b/glossary_branch_pull/test_verify.py @@ -83,7 +83,8 @@ def test_def_local_commit_missing(): rs.git.checkout("DEF") rs.git.reset("HEAD~1", hard=True) - rs.git.run(["git", "pull", "origin", "DEF"]) + rs.git.fetch("origin") + rs.git.merge("origin/DEF") output = test.run() assert_output( @@ -101,10 +102,12 @@ def test_successful_changes(): rs.git.push("origin", "STU") rs.git.checkout("ABC") - rs.git.run(["git", "pull", "origin", "ABC"]) + rs.git.fetch("origin") + rs.git.merge("origin/ABC") rs.git.checkout("DEF") - rs.git.run(["git", "pull", "origin", "DEF"]) + rs.git.fetch("origin") + rs.git.merge("origin/DEF") output = test.run() assert_output(output, GitAutograderStatus.SUCCESSFUL) \ No newline at end of file From 5e0bb359d673cbdefdcb43dcc13bc57bd83a6d5b Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Fri, 6 Feb 2026 22:11:06 +0800 Subject: [PATCH 14/15] Change to clone_repo_with_gh --- glossary_branch_pull/download.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py index b553b635..fe8e5228 100644 --- a/glossary_branch_pull/download.py +++ b/glossary_branch_pull/download.py @@ -1,7 +1,8 @@ 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, push +from exercise_utils.git import add, checkout, commit, remove_remote from exercise_utils.github_cli import ( + clone_repo_with_gh, get_github_username, fork_repo, has_repo, @@ -20,7 +21,8 @@ def setup(verbose: bool = False): delete_repo(full_repo_name, verbose) fork_repo(TARGET_REPO, FORK_NAME, verbose, False) - clone_repo_with_git(f"https://github.com/{full_repo_name}", verbose, ".") + clone_repo_with_gh(f"{username}/{FORK_NAME}", verbose, ".") + remove_remote("upstream", verbose) run_command(["git", "branch", "-dr", "origin/VWX"], verbose) From 004ab7fa36aa468d94144f1577acfd982db7c447 Mon Sep 17 00:00:00 2001 From: desmondwong1215 Date: Sat, 7 Feb 2026 00:31:48 +0800 Subject: [PATCH 15/15] Improve verify logic --- glossary_branch_pull/download.py | 6 ++++- glossary_branch_pull/test_verify.py | 8 +++--- glossary_branch_pull/verify.py | 38 +++++++++++++---------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/glossary_branch_pull/download.py b/glossary_branch_pull/download.py index fe8e5228..a033e71c 100644 --- a/glossary_branch_pull/download.py +++ b/glossary_branch_pull/download.py @@ -31,6 +31,10 @@ def setup(verbose: bool = False): checkout("DEF", False, verbose) run_command(["git", "reset", "--hard", "HEAD~1"], verbose) - create_or_update_file("d.txt", "documentation: Evidence that someone once cared.\n") + create_or_update_file( + "d.txt", + """ + documentation: Evidence that someone once cared. + """) add(["d.txt"], verbose) commit("Add 'documentation'", verbose) diff --git a/glossary_branch_pull/test_verify.py b/glossary_branch_pull/test_verify.py index 37fe2b9f..2111d111 100644 --- a/glossary_branch_pull/test_verify.py +++ b/glossary_branch_pull/test_verify.py @@ -28,12 +28,12 @@ def base_setup() -> Iterator[Tuple[GitAutograderTest, RepoSmith]]: rs.git.checkout("ABC", branch=True) rs.git.commit(allow_empty=True, message="Add 'cache'") - rs.git.push("origin", "ABC") + rs.git.push("origin", "ABC", set_upstream=True) rs.git.reset("HEAD~1", hard=True) rs.git.checkout("DEF", branch=True) rs.git.commit(allow_empty=True, message="Add 'exception'") - rs.git.push("origin", "DEF") + rs.git.push("origin", "DEF", set_upstream=True) rs.git.reset("HEAD~1", hard=True) rs.git.commit(allow_empty=True, message="Add 'documentation'") @@ -96,10 +96,10 @@ def test_def_local_commit_missing(): def test_successful_changes(): with base_setup() as (test, rs): rs.git.checkout("VWX", branch=True) - rs.git.push("origin", "VWX") + rs.git.push("origin", "VWX", set_upstream=True) rs.git.checkout("STU", branch=True) - rs.git.push("origin", "STU") + rs.git.push("origin", "STU", set_upstream=True) rs.git.checkout("ABC") rs.git.fetch("origin") diff --git a/glossary_branch_pull/verify.py b/glossary_branch_pull/verify.py index dc74a4a5..725add3c 100644 --- a/glossary_branch_pull/verify.py +++ b/glossary_branch_pull/verify.py @@ -36,32 +36,29 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: if not repo.branches.has_branch("STU"): comments.append(BRANCH_MISSING.format(branch="STU")) else: - try: - exercise.repo.repo.refs["origin/STU"] - except (IndexError, KeyError): + stu_branch = repo.branches.branch("STU").branch + remote_stu = stu_branch.tracking_branch() + if not remote_stu or remote_stu.name != "origin/STU": comments.append(BRANCH_NOT_TRACKING.format(branch="STU")) - pass if not repo.branches.has_branch("VWX"): comments.append(BRANCH_MISSING.format(branch="VWX")) else: - try: - exercise.repo.repo.refs["origin/VWX"] - except (IndexError, KeyError): + vwx_branch = repo.branches.branch("VWX").branch + remote_vwx = vwx_branch.tracking_branch() + if not remote_vwx or remote_vwx.name != "origin/VWX": comments.append(BRANCH_NOT_TRACKING.format(branch="VWX")) - pass if not repo.branches.has_branch("ABC"): comments.append(BRANCH_MISSING.format(branch="ABC")) else: - try: - remote_abc = exercise.repo.repo.refs["origin/ABC"] - abc_commits = repo.branches.branch("ABC").commits - if not get_commit_from_hexsha(abc_commits, remote_abc.commit.hexsha): - comments.append(REMOTE_COMMIT_MISSING.format(branch="ABC")) - except (IndexError, KeyError): + abc_commits = repo.branches.branch("ABC").commits + abc_branch = repo.branches.branch("ABC").branch + remote_abc = abc_branch.tracking_branch() + if not remote_abc or remote_abc.name != "origin/ABC": comments.append(BRANCH_NOT_TRACKING.format(branch="ABC")) - pass + elif not get_commit_from_hexsha(abc_commits, remote_abc.commit.hexsha): + comments.append(REMOTE_COMMIT_MISSING.format(branch="ABC")) if not repo.branches.has_branch("DEF"): comments.append(BRANCH_MISSING.format(branch="DEF")) @@ -69,13 +66,12 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: def_commits = repo.branches.branch("DEF").commits if not get_commit_from_message(def_commits, "Add 'documentation'"): comments.append(LOCAL_COMMIT_MISSING) - try: - remote_def = exercise.repo.repo.refs["origin/DEF"] - if not get_commit_from_hexsha(def_commits, remote_def.commit.hexsha): - comments.append(REMOTE_COMMIT_MISSING.format(branch="DEF")) - except (IndexError, KeyError): + def_branch = repo.branches.branch("DEF").branch + remote_def = def_branch.tracking_branch() + if not remote_def or remote_def.name != "origin/DEF": comments.append(BRANCH_NOT_TRACKING.format(branch="DEF")) - pass + elif not get_commit_from_hexsha(def_commits, remote_def.commit.hexsha): + comments.append(REMOTE_COMMIT_MISSING.format(branch="DEF")) if comments: raise exercise.wrong_answer(comments)