From 71a164a590ec06a03fc74c44a0c0b3d395d859ee Mon Sep 17 00:00:00 2001 From: MousseDm Date: Mon, 27 Oct 2025 17:24:13 +0800 Subject: [PATCH 1/2] Exercise(tags-add): add download and verify (tag v1.0.0) --- tags_add/.gitmastery-exercise.json | 16 ++++++++++++++++ tags_add/README.md | 18 ++++++++++++++++++ tags_add/__init__.py | 0 tags_add/download.py | 16 ++++++++++++++++ tags_add/tests/__init__.py | 0 tags_add/tests/specs/base.yml | 6 ++++++ tags_add/tests/test_verify.py | 12 ++++++++++++ tags_add/verify.py | 26 ++++++++++++++++++++++++++ 8 files changed, 94 insertions(+) create mode 100644 tags_add/.gitmastery-exercise.json create mode 100644 tags_add/README.md create mode 100644 tags_add/__init__.py create mode 100644 tags_add/download.py create mode 100644 tags_add/tests/__init__.py create mode 100644 tags_add/tests/specs/base.yml create mode 100644 tags_add/tests/test_verify.py create mode 100644 tags_add/verify.py diff --git a/tags_add/.gitmastery-exercise.json b/tags_add/.gitmastery-exercise.json new file mode 100644 index 0000000..c3f5a25 --- /dev/null +++ b/tags_add/.gitmastery-exercise.json @@ -0,0 +1,16 @@ +{ + "exercise_name": "tags-add", + "tags": [ + "tags" + ], + "requires_git": true, + "requires_github": false, + "base_files": {}, + "exercise_repo": { + "repo_type": "local", + "repo_name": "project", + "repo_title": null, + "create_fork": false, + "init": true + } +} \ No newline at end of file diff --git a/tags_add/README.md b/tags_add/README.md new file mode 100644 index 0000000..0d05582 --- /dev/null +++ b/tags_add/README.md @@ -0,0 +1,18 @@ +# tags-add + + + +## Task + + + +## Hints + + + diff --git a/tags_add/__init__.py b/tags_add/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tags_add/download.py b/tags_add/download.py new file mode 100644 index 0000000..a765a86 --- /dev/null +++ b/tags_add/download.py @@ -0,0 +1,16 @@ +from exercise_utils.file import create_or_update_file +from exercise_utils.git import add, commit +from exercise_utils.gitmastery import create_start_tag + +__resources__ = [] + +def setup(verbose: bool = False): + create_or_update_file("README.md", "# T4L2: Add a tag\n") + add(["README.md"], verbose) + commit("chore: init README", verbose) + + create_or_update_file("main.txt", "v1 content\n") + add(["main.txt"], verbose) + commit("feat: initial content", verbose) + + create_start_tag(verbose) diff --git a/tags_add/tests/__init__.py b/tags_add/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tags_add/tests/specs/base.yml b/tags_add/tests/specs/base.yml new file mode 100644 index 0000000..00c3a53 --- /dev/null +++ b/tags_add/tests/specs/base.yml @@ -0,0 +1,6 @@ +initialization: + steps: + - type: commit + empty: true + message: Empty commit + id: start diff --git a/tags_add/tests/test_verify.py b/tags_add/tests/test_verify.py new file mode 100644 index 0000000..1e0a9b8 --- /dev/null +++ b/tags_add/tests/test_verify.py @@ -0,0 +1,12 @@ +from git_autograder import GitAutograderTestLoader + +from ..verify import verify + +REPOSITORY_NAME = "tags-add" + +loader = GitAutograderTestLoader(__file__, REPOSITORY_NAME, verify) + + +def test_base(): + with loader.load("specs/base.yml", "start"): + pass diff --git a/tags_add/verify.py b/tags_add/verify.py new file mode 100644 index 0000000..750345c --- /dev/null +++ b/tags_add/verify.py @@ -0,0 +1,26 @@ +from typing import List +from git_autograder.core import GitAutograderExercise, GitAutograderStatus + +MSG_NO_TAG = "Tag 'v1.0.0' was not found." +MSG_NOT_AT_HEAD = "Tag 'v1.0.0' does not point to HEAD." + +def verify(exercise: GitAutograderExercise): + """ + Pass condition: + 1) exist 1.0.0 tag + 2) the tag is pointing to current head + """ + repo = exercise.repo.repo + target = next((t for t in repo.tags if t.name == "v1.0.0"), None) + + messages: List[str] = [] + status = GitAutograderStatus.SUCCESSFUL + + if target is None: + messages.append(MSG_NO_TAG) + status = GitAutograderStatus.FAILED + elif target.commit.hexsha != repo.head.commit.hexsha: + messages.append(MSG_NOT_AT_HEAD) + status = GitAutograderStatus.FAILED + + return exercise.to_output(messages, status) From ab9a8b2c0fb4399008ebdd760f3e851c219c8ad4 Mon Sep 17 00:00:00 2001 From: MousseDm Date: Mon, 27 Oct 2025 18:19:58 +0800 Subject: [PATCH 2/2] Correct exercise configuration and verify logic --- tags_add/.gitmastery-exercise.json | 18 ++++----- tags_add/README.md | 25 +++++++----- tags_add/download.py | 14 +------ tags_add/verify.py | 64 +++++++++++++++++++++--------- 4 files changed, 70 insertions(+), 51 deletions(-) diff --git a/tags_add/.gitmastery-exercise.json b/tags_add/.gitmastery-exercise.json index c3f5a25..c28e3e2 100644 --- a/tags_add/.gitmastery-exercise.json +++ b/tags_add/.gitmastery-exercise.json @@ -1,16 +1,14 @@ { - "exercise_name": "tags-add", - "tags": [ - "tags" - ], + "exercise_name": "tags_add", + "tags": ["git-tag"], "requires_git": true, - "requires_github": false, + "requires_github": true, "base_files": {}, "exercise_repo": { - "repo_type": "local", - "repo_name": "project", - "repo_title": null, + "repo_type": "remote", + "repo_name": "duty-roster", "create_fork": false, - "init": true + "repo_title": "gm-duty-roster", + "init": false } -} \ No newline at end of file +} diff --git a/tags_add/README.md b/tags_add/README.md index 0d05582..d50129a 100644 --- a/tags_add/README.md +++ b/tags_add/README.md @@ -1,18 +1,23 @@ # tags-add - +1. Add a **lightweight** tag `first-pilot` to the **first commit**. +2. Add an **annotated** tag `v1.0` to the commit that updates March duty roster, with message `first full duty roster`. ## Task - +This exercise clones `git-mastery/gm-duty-roster` into the `duty-roster/` folder. ## Hints - - +```bash +# first commit +git rev-list --max-parents=0 HEAD + +# add lightweight tag +git tag first-pilot + +# find the commit for "Update roster for March" +git log --oneline | grep -i "Update roster for March" + +# add annotated tag with message +git tag -a v1.0 -m "first full duty roster" \ No newline at end of file diff --git a/tags_add/download.py b/tags_add/download.py index a765a86..604c9e6 100644 --- a/tags_add/download.py +++ b/tags_add/download.py @@ -1,16 +1,4 @@ -from exercise_utils.file import create_or_update_file -from exercise_utils.git import add, commit -from exercise_utils.gitmastery import create_start_tag - __resources__ = [] def setup(verbose: bool = False): - create_or_update_file("README.md", "# T4L2: Add a tag\n") - add(["README.md"], verbose) - commit("chore: init README", verbose) - - create_or_update_file("main.txt", "v1 content\n") - add(["main.txt"], verbose) - commit("feat: initial content", verbose) - - create_start_tag(verbose) + pass \ No newline at end of file diff --git a/tags_add/verify.py b/tags_add/verify.py index 750345c..b4e90d2 100644 --- a/tags_add/verify.py +++ b/tags_add/verify.py @@ -1,26 +1,54 @@ -from typing import List +from git import TagObject from git_autograder.core import GitAutograderExercise, GitAutograderStatus -MSG_NO_TAG = "Tag 'v1.0.0' was not found." -MSG_NOT_AT_HEAD = "Tag 'v1.0.0' does not point to HEAD." +FIRST_TAG = "first-pilot" +SECOND_TAG = "v1.0" +SECOND_TAG_MSG = "first full duty roster" +MARCH_MSG_FRAGMENT = "Update roster for March" def verify(exercise: GitAutograderExercise): - """ - Pass condition: - 1) exist 1.0.0 tag - 2) the tag is pointing to current head - """ repo = exercise.repo.repo - target = next((t for t in repo.tags if t.name == "v1.0.0"), None) - messages: List[str] = [] - status = GitAutograderStatus.SUCCESSFUL + root_sha = next(repo.iter_commits(rev="HEAD", reverse=True)).hexsha - if target is None: - messages.append(MSG_NO_TAG) - status = GitAutograderStatus.FAILED - elif target.commit.hexsha != repo.head.commit.hexsha: - messages.append(MSG_NOT_AT_HEAD) - status = GitAutograderStatus.FAILED + march_commit = None + for c in repo.iter_commits("HEAD"): + if MARCH_MSG_FRAGMENT in c.message: + march_commit = c + break - return exercise.to_output(messages, status) + comments = [] + + t_first = next((t for t in repo.tags if t.name == FIRST_TAG), None) + if not t_first: + comments.append(f"Missing lightweight tag `{FIRST_TAG}`.") + else: + if t_first.commit.hexsha != root_sha: + comments.append( + f"`{FIRST_TAG}` should point to first commit {root_sha[:7]}, " + f"but points to {t_first.commit.hexsha[:7]} instead." + ) + if isinstance(getattr(t_first, "tag", None), TagObject): + comments.append(f"`{FIRST_TAG}` must be a lightweight tag (not annotated).") + + t_v1 = next((t for t in repo.tags if t.name == SECOND_TAG), None) + if not t_v1: + comments.append(f"Missing annotated tag `{SECOND_TAG}`.") + else: + if not isinstance(getattr(t_v1, "tag", None), TagObject): + comments.append(f"`{SECOND_TAG}` must be an annotated tag.") + else: + msg = (t_v1.tag.message or "").strip() + if msg != SECOND_TAG_MSG: + comments.append(f"`{SECOND_TAG}` message must be exactly `{SECOND_TAG_MSG}`.") + if not march_commit: + comments.append(f"Could not find the commit containing '{MARCH_MSG_FRAGMENT}'.") + else: + if t_v1.commit.hexsha != march_commit.hexsha: + comments.append( + f"`{SECOND_TAG}` should point to March commit {march_commit.hexsha[:7]}, " + f"but points to {t_v1.commit.hexsha[:7]} instead." + ) + + status = GitAutograderStatus.SUCCESSFUL if not comments else GitAutograderStatus.FAILED + return exercise.to_output(comments, status)