From 0fda5679f7fc90c841d1aa4d7e4f6977bf4af649 Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:07:39 +0800 Subject: [PATCH 1/6] [tags-update] Implement download and verify --- tags_update/.gitmastery-exercise.json | 16 ++++++++ tags_update/README.md | 8 ++++ tags_update/__init__.py | 0 tags_update/download.py | 9 +++++ tags_update/verify.py | 57 +++++++++++++++++++++++++++ 5 files changed, 90 insertions(+) create mode 100644 tags_update/.gitmastery-exercise.json create mode 100644 tags_update/README.md create mode 100644 tags_update/__init__.py create mode 100644 tags_update/download.py create mode 100644 tags_update/verify.py diff --git a/tags_update/.gitmastery-exercise.json b/tags_update/.gitmastery-exercise.json new file mode 100644 index 0000000..da019c1 --- /dev/null +++ b/tags_update/.gitmastery-exercise.json @@ -0,0 +1,16 @@ +{ + "exercise_name": "tags-update", + "tags": [ + "git-tag" + ], + "requires_git": true, + "requires_github": true, + "base_files": {}, + "exercise_repo": { + "repo_type": "remote", + "repo_name": "duty-roster", + "repo_title": "gm-duty-roster", + "create_fork": false, + "init": null + } +} \ No newline at end of file diff --git a/tags_update/README.md b/tags_update/README.md new file mode 100644 index 0000000..a697fa0 --- /dev/null +++ b/tags_update/README.md @@ -0,0 +1,8 @@ +# tags-update + +The `duty-roster` repo contains text files that track which people are assigned for duties on which days of the week. Some of tags added earlier needs found to be incorrect. + +## Task + +1. To make tag names consistent, change `first-update` tag to `january-update`. +2. The `april-update` tag is currently pointing to the commit that updates the duty roster for May. Move it to the correct commit. diff --git a/tags_update/__init__.py b/tags_update/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tags_update/download.py b/tags_update/download.py new file mode 100644 index 0000000..78de9c5 --- /dev/null +++ b/tags_update/download.py @@ -0,0 +1,9 @@ +from exercise_utils.cli import run_command + +__resources__ = {} + + +def setup(verbose: bool = False): + run_command(["git", "tag", "first-update", "HEAD~4"], verbose) + run_command(["git", "tag", "april-update"], verbose) + run_command(["git", "tag", "may-update"], verbose) diff --git a/tags_update/verify.py b/tags_update/verify.py new file mode 100644 index 0000000..d6d3e5b --- /dev/null +++ b/tags_update/verify.py @@ -0,0 +1,57 @@ +from git_autograder import ( + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) + +MISSING_JANUARY_TAG = "The 'january-update' tag is missing! You need to rename 'first-update' to 'january-update'." +WRONG_JANUARY_TAG_COMMIT = "The 'january-update' tag is pointing to the wrong commit! It should point to the January commit." +MISSING_APRIL_TAG = "The 'april-update' tag is missing!" +WRONG_APRIL_TAG_COMMIT = "The 'april-update' tag is pointing to the wrong commit! It should point to the April commit, not the May commit." +OLD_FIRST_UPDATE_TAG = "The old 'first-update' tag still exists! You need to delete it after renaming to 'january-update'." +SUCCESS_MESSAGE = "Great work! You have successfully updated the tags to point to the correct commits." +MISSING_COMMIT_MESSAGE = "Could not find a commit with '{message}' in the message" + + +def find_commit_by_message(exercise: GitAutograderExercise, message_fragment: str): + """Find a commit containing the given message fragment.""" + commits = list(exercise.repo.repo.iter_commits(all=True)) + for commit in commits: + if message_fragment in commit.message: + return commit + return None + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + tags = exercise.repo.repo.tags + + # Verify that the tags exist and that the old first-update tag is deleted + if "first-update" in tags: + raise exercise.wrong_answer([OLD_FIRST_UPDATE_TAG]) + if "january-update" not in tags: + raise exercise.wrong_answer([MISSING_JANUARY_TAG]) + if "april-update" not in tags: + raise exercise.wrong_answer([MISSING_APRIL_TAG]) + + # Get correct commits that the tags should point to + january_commit = find_commit_by_message(exercise, "January") + if january_commit is None: + raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="January")]) + + april_commit = find_commit_by_message(exercise, "April") + if april_commit is None: + raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="April")]) + + # Verify that the tags point to the correct commits + january_tag_commit = tags["january-update"].commit + if january_tag_commit.hexsha != january_commit.hexsha: + raise exercise.wrong_answer([WRONG_JANUARY_TAG_COMMIT]) + + april_tag_commit = tags["april-update"].commit + if april_tag_commit.hexsha != april_commit.hexsha: + raise exercise.wrong_answer([WRONG_APRIL_TAG_COMMIT]) + + return exercise.to_output( + [SUCCESS_MESSAGE], + GitAutograderStatus.SUCCESSFUL, + ) \ No newline at end of file From 4942bf7373da822c43832edc990455802618ae54 Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:07:53 +0800 Subject: [PATCH 2/6] [tags-update] Add tests --- tags_update/tests/__init__.py | 0 tags_update/tests/specs/base.yml | 28 +++++++++++++ .../tests/specs/missing_january_commit.yml | 24 +++++++++++ tags_update/tests/specs/missing_tags.yml | 22 ++++++++++ .../tests/specs/old_tag_still_exists.yml | 28 +++++++++++++ tags_update/tests/specs/wrong_april_tag.yml | 28 +++++++++++++ tags_update/tests/test_verify.py | 41 +++++++++++++++++++ 7 files changed, 171 insertions(+) create mode 100644 tags_update/tests/__init__.py create mode 100644 tags_update/tests/specs/base.yml create mode 100644 tags_update/tests/specs/missing_january_commit.yml create mode 100644 tags_update/tests/specs/missing_tags.yml create mode 100644 tags_update/tests/specs/old_tag_still_exists.yml create mode 100644 tags_update/tests/specs/wrong_april_tag.yml create mode 100644 tags_update/tests/test_verify.py diff --git a/tags_update/tests/__init__.py b/tags_update/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tags_update/tests/specs/base.yml b/tags_update/tests/specs/base.yml new file mode 100644 index 0000000..09e2d69 --- /dev/null +++ b/tags_update/tests/specs/base.yml @@ -0,0 +1,28 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: january-update + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: tag + tag-name: april-update + - type: commit + empty: true + message: Update roster for May + id: may-commit + - type: tag + tag-name: may-update diff --git a/tags_update/tests/specs/missing_january_commit.yml b/tags_update/tests/specs/missing_january_commit.yml new file mode 100644 index 0000000..98a0f8b --- /dev/null +++ b/tags_update/tests/specs/missing_january_commit.yml @@ -0,0 +1,24 @@ +initialization: + steps: + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: tag + tag-name: january-update + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: tag + tag-name: april-update + - type: commit + empty: true + message: Update roster for May + id: may-commit + - type: tag + tag-name: may-update diff --git a/tags_update/tests/specs/missing_tags.yml b/tags_update/tests/specs/missing_tags.yml new file mode 100644 index 0000000..f804239 --- /dev/null +++ b/tags_update/tests/specs/missing_tags.yml @@ -0,0 +1,22 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: commit + empty: true + message: Update roster for May + id: may-commit diff --git a/tags_update/tests/specs/old_tag_still_exists.yml b/tags_update/tests/specs/old_tag_still_exists.yml new file mode 100644 index 0000000..b9f2935 --- /dev/null +++ b/tags_update/tests/specs/old_tag_still_exists.yml @@ -0,0 +1,28 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-update + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: commit + empty: true + message: Update roster for May + id: may-commit + - type: tag + tag-name: april-update + - type: tag + tag-name: may-update diff --git a/tags_update/tests/specs/wrong_april_tag.yml b/tags_update/tests/specs/wrong_april_tag.yml new file mode 100644 index 0000000..9333399 --- /dev/null +++ b/tags_update/tests/specs/wrong_april_tag.yml @@ -0,0 +1,28 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: january-update + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: commit + empty: true + message: Update roster for May + id: may-commit + - type: tag + tag-name: april-update + - type: tag + tag-name: may-update diff --git a/tags_update/tests/test_verify.py b/tags_update/tests/test_verify.py new file mode 100644 index 0000000..9798840 --- /dev/null +++ b/tags_update/tests/test_verify.py @@ -0,0 +1,41 @@ +from git_autograder import GitAutograderTestLoader, assert_output +from git_autograder.status import GitAutograderStatus + +from ..verify import ( + verify, + MISSING_JANUARY_TAG, + WRONG_APRIL_TAG_COMMIT, + OLD_FIRST_UPDATE_TAG, + SUCCESS_MESSAGE, + MISSING_COMMIT_MESSAGE, +) + +REPOSITORY_NAME = "tags-update" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml", "start") as output: + print(output) + assert_output(output, GitAutograderStatus.SUCCESSFUL, [SUCCESS_MESSAGE]) + + +def test_missing_tags(): + with loader.load("specs/missing_tags.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MISSING_JANUARY_TAG]) + + +def test_wrong_april_tag(): + with loader.load("specs/wrong_april_tag.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_APRIL_TAG_COMMIT]) + + +def test_old_tag_still_exists(): + with loader.load("specs/old_tag_still_exists.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [OLD_FIRST_UPDATE_TAG]) + + +def test_missing_january_commit(): + with loader.load("specs/missing_january_commit.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MISSING_COMMIT_MESSAGE.format(message="January")]) From d1f48dbb7aa4ebe38cf48f9b015bf7e930ff412b Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:10:27 +0800 Subject: [PATCH 3/6] [tags-update] Add ending line --- tags_update/verify.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tags_update/verify.py b/tags_update/verify.py index d6d3e5b..c372b72 100644 --- a/tags_update/verify.py +++ b/tags_update/verify.py @@ -54,4 +54,4 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: return exercise.to_output( [SUCCESS_MESSAGE], GitAutograderStatus.SUCCESSFUL, - ) \ No newline at end of file + ) From c7998e683cf5a302deaf9bf800ea2e2a81db6124 Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Tue, 28 Oct 2025 21:29:53 +0800 Subject: [PATCH 4/6] [tags-update] Add more strict verification --- tags_update/tests/specs/missing_january_commit.yml | 2 +- tags_update/verify.py | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tags_update/tests/specs/missing_january_commit.yml b/tags_update/tests/specs/missing_january_commit.yml index 98a0f8b..b0dc52a 100644 --- a/tags_update/tests/specs/missing_january_commit.yml +++ b/tags_update/tests/specs/missing_january_commit.yml @@ -3,7 +3,7 @@ initialization: - type: commit empty: true message: Update duty roster for February - id: february-commit + id: start - type: tag tag-name: january-update - type: commit diff --git a/tags_update/verify.py b/tags_update/verify.py index c372b72..1c6f1cf 100644 --- a/tags_update/verify.py +++ b/tags_update/verify.py @@ -13,11 +13,11 @@ MISSING_COMMIT_MESSAGE = "Could not find a commit with '{message}' in the message" -def find_commit_by_message(exercise: GitAutograderExercise, message_fragment: str): - """Find a commit containing the given message fragment.""" +def find_commit_by_message(exercise: GitAutograderExercise, message: str): + """Find a commit with the given message.""" commits = list(exercise.repo.repo.iter_commits(all=True)) for commit in commits: - if message_fragment in commit.message: + if message.strip() == commit.message.strip(): return commit return None @@ -34,11 +34,11 @@ def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: raise exercise.wrong_answer([MISSING_APRIL_TAG]) # Get correct commits that the tags should point to - january_commit = find_commit_by_message(exercise, "January") + january_commit = find_commit_by_message(exercise, "Add January duty roster") if january_commit is None: raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="January")]) - april_commit = find_commit_by_message(exercise, "April") + april_commit = find_commit_by_message(exercise, "Update duty roster for April") if april_commit is None: raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="April")]) From 82b50cabbb75a9af51b10ac62e1a80d65259c14e Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:33:33 +0800 Subject: [PATCH 5/6] [tags-update] Enhance tagging functionality --- exercise_utils/git.py | 12 +++++++++--- tags_update/download.py | 10 ++++------ tags_update/tests/test_verify.py | 1 - tags_update/verify.py | 5 ++++- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/exercise_utils/git.py b/exercise_utils/git.py index 5df9919..79e0e3f 100644 --- a/exercise_utils/git.py +++ b/exercise_utils/git.py @@ -5,9 +5,15 @@ from exercise_utils.cli import run_command -def tag(tag_name: str, verbose: bool) -> None: - """Tags the latest commit with the given tag_name.""" - run_command(["git", "tag", tag_name], verbose) +def tag(tag_name: str, verbose: bool, commit_ref: str | None = None) -> None: + """Tags the given commit with the given tag_name. + + If no commit_ref is provided, the latest commit is tagged. + """ + if commit_ref: + run_command(["git", "tag", tag_name, commit_ref], verbose) + else: + run_command(["git", "tag", tag_name], verbose) def add(files: List[str], verbose: bool) -> None: diff --git a/tags_update/download.py b/tags_update/download.py index 78de9c5..ee4a61e 100644 --- a/tags_update/download.py +++ b/tags_update/download.py @@ -1,9 +1,7 @@ -from exercise_utils.cli import run_command - -__resources__ = {} +from exercise_utils.git import tag def setup(verbose: bool = False): - run_command(["git", "tag", "first-update", "HEAD~4"], verbose) - run_command(["git", "tag", "april-update"], verbose) - run_command(["git", "tag", "may-update"], verbose) + tag("first-update", verbose, "HEAD~4") + tag("april-update", verbose) + tag("may-update", verbose) diff --git a/tags_update/tests/test_verify.py b/tags_update/tests/test_verify.py index 9798840..5913ab8 100644 --- a/tags_update/tests/test_verify.py +++ b/tags_update/tests/test_verify.py @@ -17,7 +17,6 @@ def test_base(): with loader.load("specs/base.yml", "start") as output: - print(output) assert_output(output, GitAutograderStatus.SUCCESSFUL, [SUCCESS_MESSAGE]) diff --git a/tags_update/verify.py b/tags_update/verify.py index 1c6f1cf..1420775 100644 --- a/tags_update/verify.py +++ b/tags_update/verify.py @@ -1,3 +1,6 @@ +from typing import Optional +from git.objects.commit import Commit + from git_autograder import ( GitAutograderExercise, GitAutograderOutput, @@ -13,7 +16,7 @@ MISSING_COMMIT_MESSAGE = "Could not find a commit with '{message}' in the message" -def find_commit_by_message(exercise: GitAutograderExercise, message: str): +def find_commit_by_message(exercise: GitAutograderExercise, message: str) -> Optional[Commit]: """Find a commit with the given message.""" commits = list(exercise.repo.repo.iter_commits(all=True)) for commit in commits: From c53cbe54563079196a26f5686a38cf4c6c8d1447 Mon Sep 17 00:00:00 2001 From: jovnc <95868357+jovnc@users.noreply.github.com> Date: Mon, 10 Nov 2025 20:47:27 +0800 Subject: [PATCH 6/6] [tags-update] Reorder verification process and add more tests --- tags_update/tests/specs/wrong_january_tag.yml | 28 +++++++++++++++++++ tags_update/tests/test_verify.py | 6 ++++ tags_update/verify.py | 24 +++++++++------- 3 files changed, 48 insertions(+), 10 deletions(-) create mode 100644 tags_update/tests/specs/wrong_january_tag.yml diff --git a/tags_update/tests/specs/wrong_january_tag.yml b/tags_update/tests/specs/wrong_january_tag.yml new file mode 100644 index 0000000..7a3f032 --- /dev/null +++ b/tags_update/tests/specs/wrong_january_tag.yml @@ -0,0 +1,28 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: commit + empty: true + message: Update duty roster for February + id: february-commit + - type: tag + tag-name: january-update + - type: commit + empty: true + message: Update roster for March + id: march-commit + - type: commit + empty: true + message: Update duty roster for April + id: april-commit + - type: tag + tag-name: april-update + - type: commit + empty: true + message: Update roster for May + id: may-commit + - type: tag + tag-name: may-update diff --git a/tags_update/tests/test_verify.py b/tags_update/tests/test_verify.py index 5913ab8..082522f 100644 --- a/tags_update/tests/test_verify.py +++ b/tags_update/tests/test_verify.py @@ -4,6 +4,7 @@ from ..verify import ( verify, MISSING_JANUARY_TAG, + WRONG_JANUARY_TAG_COMMIT, WRONG_APRIL_TAG_COMMIT, OLD_FIRST_UPDATE_TAG, SUCCESS_MESSAGE, @@ -25,6 +26,11 @@ def test_missing_tags(): assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MISSING_JANUARY_TAG]) +def test_wrong_january_tag(): + with loader.load("specs/wrong_january_tag.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_JANUARY_TAG_COMMIT]) + + def test_wrong_april_tag(): with loader.load("specs/wrong_april_tag.yml", "start") as output: assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_APRIL_TAG_COMMIT]) diff --git a/tags_update/verify.py b/tags_update/verify.py index 1420775..30a40e0 100644 --- a/tags_update/verify.py +++ b/tags_update/verify.py @@ -28,28 +28,32 @@ def find_commit_by_message(exercise: GitAutograderExercise, message: str) -> Opt def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: tags = exercise.repo.repo.tags - # Verify that the tags exist and that the old first-update tag is deleted + # Ensure first-update tag does not exist if "first-update" in tags: - raise exercise.wrong_answer([OLD_FIRST_UPDATE_TAG]) + raise exercise.wrong_answer([OLD_FIRST_UPDATE_TAG]) + + # Ensure january-update tag exists if "january-update" not in tags: raise exercise.wrong_answer([MISSING_JANUARY_TAG]) - if "april-update" not in tags: - raise exercise.wrong_answer([MISSING_APRIL_TAG]) - # Get correct commits that the tags should point to + # Ensure january-update tag points to the correct commit january_commit = find_commit_by_message(exercise, "Add January duty roster") if january_commit is None: raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="January")]) - april_commit = find_commit_by_message(exercise, "Update duty roster for April") - if april_commit is None: - raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="April")]) - - # Verify that the tags point to the correct commits january_tag_commit = tags["january-update"].commit if january_tag_commit.hexsha != january_commit.hexsha: raise exercise.wrong_answer([WRONG_JANUARY_TAG_COMMIT]) + # Ensure april-update tag exists + if "april-update" not in tags: + raise exercise.wrong_answer([MISSING_APRIL_TAG]) + + # Ensure april-update tag points to the correct commit + april_commit = find_commit_by_message(exercise, "Update duty roster for April") + if april_commit is None: + raise exercise.wrong_answer([MISSING_COMMIT_MESSAGE.format(message="April")]) + april_tag_commit = tags["april-update"].commit if april_tag_commit.hexsha != april_commit.hexsha: raise exercise.wrong_answer([WRONG_APRIL_TAG_COMMIT])