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/.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..ee4a61e --- /dev/null +++ b/tags_update/download.py @@ -0,0 +1,7 @@ +from exercise_utils.git import tag + + +def setup(verbose: bool = False): + tag("first-update", verbose, "HEAD~4") + tag("april-update", verbose) + tag("may-update", verbose) 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..b0dc52a --- /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: start + - 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/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 new file mode 100644 index 0000000..082522f --- /dev/null +++ b/tags_update/tests/test_verify.py @@ -0,0 +1,46 @@ +from git_autograder import GitAutograderTestLoader, assert_output +from git_autograder.status import GitAutograderStatus + +from ..verify import ( + verify, + MISSING_JANUARY_TAG, + WRONG_JANUARY_TAG_COMMIT, + 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: + 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_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]) + + +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")]) diff --git a/tags_update/verify.py b/tags_update/verify.py new file mode 100644 index 0000000..30a40e0 --- /dev/null +++ b/tags_update/verify.py @@ -0,0 +1,64 @@ +from typing import Optional +from git.objects.commit import Commit + +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: str) -> Optional[Commit]: + """Find a commit with the given message.""" + commits = list(exercise.repo.repo.iter_commits(all=True)) + for commit in commits: + if message.strip() == commit.message.strip(): + return commit + return None + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + tags = exercise.repo.repo.tags + + # Ensure first-update tag does not exist + if "first-update" in tags: + 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]) + + # 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")]) + + 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]) + + return exercise.to_output( + [SUCCESS_MESSAGE], + GitAutograderStatus.SUCCESSFUL, + )