diff --git a/tags_add/.gitmastery-exercise.json b/tags_add/.gitmastery-exercise.json new file mode 100644 index 00000000..b2bab841 --- /dev/null +++ b/tags_add/.gitmastery-exercise.json @@ -0,0 +1,14 @@ +{ + "exercise_name": "tags-add", + "tags": ["git-tag"], + "requires_git": true, + "requires_github": true, + "base_files": {}, + "exercise_repo": { + "repo_type": "remote", + "repo_name": "duty-roster", + "create_fork": false, + "repo_title": "gm-duty-roster", + "init": false + } +} diff --git a/tags_add/README.md b/tags_add/README.md new file mode 100644 index 00000000..8c0a521f --- /dev/null +++ b/tags_add/README.md @@ -0,0 +1 @@ +See https://git-mastery.github.io/lessons/tag/exercise-tags-add.html diff --git a/tags_add/__init__.py b/tags_add/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tags_add/download.py b/tags_add/download.py new file mode 100644 index 00000000..e19bc835 --- /dev/null +++ b/tags_add/download.py @@ -0,0 +1 @@ +def setup(verbose: bool = False): ... diff --git a/tags_add/tests/__init__.py b/tags_add/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tags_add/tests/specs/base.yml b/tags_add/tests/specs/base.yml new file mode 100644 index 00000000..2aad982d --- /dev/null +++ b/tags_add/tests/specs/base.yml @@ -0,0 +1,23 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + tag-message: first full duty roster + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/missing_first_pilot_tag.yml b/tags_add/tests/specs/missing_first_pilot_tag.yml new file mode 100644 index 00000000..a5b7f6af --- /dev/null +++ b/tags_add/tests/specs/missing_first_pilot_tag.yml @@ -0,0 +1,21 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + tag-message: first full duty roster + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/missing_v1_tag.yml b/tags_add/tests/specs/missing_v1_tag.yml new file mode 100644 index 00000000..f0453976 --- /dev/null +++ b/tags_add/tests/specs/missing_v1_tag.yml @@ -0,0 +1,20 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/wrong_commit_first_pilot_tag.yml b/tags_add/tests/specs/wrong_commit_first_pilot_tag.yml new file mode 100644 index 00000000..0d59312a --- /dev/null +++ b/tags_add/tests/specs/wrong_commit_first_pilot_tag.yml @@ -0,0 +1,23 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: commit + empty: true + message: Update duty roster for February + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + tag-message: first full duty roster + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/wrong_commit_v1_tag.yml b/tags_add/tests/specs/wrong_commit_v1_tag.yml new file mode 100644 index 00000000..da762891 --- /dev/null +++ b/tags_add/tests/specs/wrong_commit_v1_tag.yml @@ -0,0 +1,23 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May + - type: tag + tag-name: v1.0 + tag-message: first full duty roster diff --git a/tags_add/tests/specs/wrong_message_v1_tag.yml b/tags_add/tests/specs/wrong_message_v1_tag.yml new file mode 100644 index 00000000..3351f579 --- /dev/null +++ b/tags_add/tests/specs/wrong_message_v1_tag.yml @@ -0,0 +1,23 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + tag-message: wrong message + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/wrong_tag_type_first_pilot.yml b/tags_add/tests/specs/wrong_tag_type_first_pilot.yml new file mode 100644 index 00000000..5e989d3b --- /dev/null +++ b/tags_add/tests/specs/wrong_tag_type_first_pilot.yml @@ -0,0 +1,24 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + tag-message: test message + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + tag-message: first full duty roster + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/specs/wrong_tag_type_v1_tag.yml b/tags_add/tests/specs/wrong_tag_type_v1_tag.yml new file mode 100644 index 00000000..3f8534eb --- /dev/null +++ b/tags_add/tests/specs/wrong_tag_type_v1_tag.yml @@ -0,0 +1,22 @@ +initialization: + steps: + - type: commit + empty: true + message: Add January duty roster + id: start + - type: tag + tag-name: first-pilot + - type: commit + empty: true + message: Update duty roster for February + - type: commit + empty: true + message: Update roster for March + - type: tag + tag-name: v1.0 + - type: commit + empty: true + message: Update duty roster for April + - type: commit + empty: true + message: Update roster for May diff --git a/tags_add/tests/test_verify.py b/tags_add/tests/test_verify.py new file mode 100644 index 00000000..e74aef30 --- /dev/null +++ b/tags_add/tests/test_verify.py @@ -0,0 +1,70 @@ +from git_autograder import GitAutograderStatus, GitAutograderTestLoader, assert_output + +from ..verify import ( + FIRST_TAG_NOT_LIGHTWEIGHT, + SECOND_TAG_NOT_ANNOTATED, + verify, + FIRST_TAG_WRONG_COMMIT, + MISSING_FIRST_TAG, + MISSING_SECOND_TAG, + SECOND_TAG_WRONG_COMMIT, + WRONG_SECOND_TAG_MESSAGE, +) + +REPOSITORY_NAME = "tags-add" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml", "start") as output: + assert_output(output, GitAutograderStatus.SUCCESSFUL) + + +def test_missing_first_pilot_tag(): + with loader.load("specs/missing_first_pilot_tag.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MISSING_FIRST_TAG]) + + +def test_missing_v1_tag(): + with loader.load("specs/missing_v1_tag.yml", "start") as output: + assert_output(output, GitAutograderStatus.UNSUCCESSFUL, [MISSING_SECOND_TAG]) + + +def test_wrong_message_v1_tag(): + with loader.load("specs/wrong_message_v1_tag.yml", "start") as output: + assert_output( + output, GitAutograderStatus.UNSUCCESSFUL, [WRONG_SECOND_TAG_MESSAGE] + ) + + +def test_wrong_commit_first_pilot_tag(): + with loader.load("specs/wrong_commit_first_pilot_tag.yml", "start") as output: + assert_output( + output, GitAutograderStatus.UNSUCCESSFUL, [FIRST_TAG_WRONG_COMMIT] + ) + + +def test_wrong_commit_v1_tag(): + with loader.load("specs/wrong_commit_v1_tag.yml", "start") as output: + assert_output( + output, GitAutograderStatus.UNSUCCESSFUL, [SECOND_TAG_WRONG_COMMIT] + ) + + +def test_wrong_tag_type_first_pilot(): + with loader.load("specs/wrong_tag_type_first_pilot.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [FIRST_TAG_NOT_LIGHTWEIGHT], + ) + + +def test_wrong_tag_type_v1_tag(): + with loader.load("specs/wrong_tag_type_v1_tag.yml", "start") as output: + assert_output( + output, + GitAutograderStatus.UNSUCCESSFUL, + [SECOND_TAG_NOT_ANNOTATED], + ) diff --git a/tags_add/verify.py b/tags_add/verify.py new file mode 100644 index 00000000..f28bb2b8 --- /dev/null +++ b/tags_add/verify.py @@ -0,0 +1,81 @@ +from typing import List, Optional +from git_autograder import ( + GitAutograderCommit, + GitAutograderExercise, + GitAutograderOutput, + GitAutograderStatus, +) + +FIRST_TAG_NOT_LIGHTWEIGHT = ( + '"first-pilot" should be a lightweight tag, not an annotated tag.' +) +SECOND_TAG_NOT_ANNOTATED = '"v1.0" should be an annotated tag, not a lightweight tag.' +MISSING_FIRST_TAG = 'Missing lightweight tag "first-pilot".' +MISSING_SECOND_TAG = 'Missing annotated tag "v1.0".' +WRONG_SECOND_TAG_MESSAGE = '"v1.0" message must be exactly "first full duty roster".' +FIRST_TAG_WRONG_COMMIT = '"first-pilot" should point to the first commit.' +SECOND_TAG_WRONG_COMMIT = ( + '"v1.0" should point to the commit that updates March duty roster.' +) +MISSING_FIRST_COMMIT = "Missing commit that adds January duty roster." +MISSING_MARCH_COMMIT = "Missing commit that updates March duty roster." + + +def get_commit_from_message( + commits: List[GitAutograderCommit], message: str +) -> Optional[GitAutograderCommit]: + """Find a commit with the given message from a list of commits.""" + for commit in commits: + if message.strip() == commit.commit.message.strip(): + return commit + return None + + +def verify(exercise: GitAutograderExercise) -> GitAutograderOutput: + # Task 1: Verify lightweight tag "first-pilot" on the first commit + tags = exercise.repo.repo.tags + if "first-pilot" not in tags: + raise exercise.wrong_answer([MISSING_FIRST_TAG]) + + # Verify that "first-pilot" is a lightweight tag + first_pilot_tag = tags["first-pilot"] + if first_pilot_tag.tag is not None: + raise exercise.wrong_answer([FIRST_TAG_NOT_LIGHTWEIGHT]) + + main_branch = exercise.repo.branches.branch("main") + main_branch_commits = main_branch.commits + if len(main_branch_commits) == 0: + raise exercise.wrong_answer([MISSING_FIRST_COMMIT]) + + first_commit = main_branch_commits[-1] + first_pilot_tag_commit = first_pilot_tag.commit + if first_pilot_tag_commit.hexsha != first_commit.hexsha: + raise exercise.wrong_answer([FIRST_TAG_WRONG_COMMIT]) + + # Task 2: Verify annotated tag "v1.0" on March commit with correct message + if "v1.0" not in tags: + raise exercise.wrong_answer([MISSING_SECOND_TAG]) + + # Verify that "v1.0" is an annotated tag + v1_tag = tags["v1.0"] + if v1_tag.tag is None: + raise exercise.wrong_answer([SECOND_TAG_NOT_ANNOTATED]) + + march_commit = get_commit_from_message( + main_branch_commits, "Update roster for March" + ) + if march_commit is None: + raise exercise.wrong_answer([MISSING_MARCH_COMMIT]) + + v1_tag_commit = v1_tag.commit + if v1_tag_commit.hexsha != march_commit.hexsha: + raise exercise.wrong_answer([SECOND_TAG_WRONG_COMMIT]) + + # Use strip() and lower() for less strict comparison of tag message + if v1_tag.tag.message.strip().lower() != "first full duty roster": + raise exercise.wrong_answer([WRONG_SECOND_TAG_MESSAGE]) + + return exercise.to_output( + ["Great work using git tag to annotate various commits in the repository!"], + GitAutograderStatus.SUCCESSFUL, + )