Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions exercise_utils/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Copy link
Contributor Author

@jovnc jovnc Nov 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is repeated multiple times, could we get it as a utility function for future use?

There is an existing tag function under exercise_utils.git, however, it doesn't support tagging a specific commit hash.

We have 2 options:

  1. extend current utility of the tag function to take in optional commit_ref
  2. create a command with a more verbose name tag_with_commit that serves the same purpose as tag but takes in compulsory commit_ref

I have chosen the first option, as it keeps backward compatibility, while ensuring simplicity.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do I need to open a new PR to add this change for the util, or can I include it in this PR? Is there anywhere I need to document this change?

"""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:
Expand Down
16 changes: 16 additions & 0 deletions tags_update/.gitmastery-exercise.json
Original file line number Diff line number Diff line change
@@ -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
}
}
8 changes: 8 additions & 0 deletions tags_update/README.md
Original file line number Diff line number Diff line change
@@ -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.
Empty file added tags_update/__init__.py
Empty file.
7 changes: 7 additions & 0 deletions tags_update/download.py
Original file line number Diff line number Diff line change
@@ -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)
Empty file added tags_update/tests/__init__.py
Empty file.
28 changes: 28 additions & 0 deletions tags_update/tests/specs/base.yml
Original file line number Diff line number Diff line change
@@ -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
24 changes: 24 additions & 0 deletions tags_update/tests/specs/missing_january_commit.yml
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions tags_update/tests/specs/missing_tags.yml
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions tags_update/tests/specs/old_tag_still_exists.yml
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions tags_update/tests/specs/wrong_april_tag.yml
Original file line number Diff line number Diff line change
@@ -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
28 changes: 28 additions & 0 deletions tags_update/tests/specs/wrong_january_tag.yml
Original file line number Diff line number Diff line change
@@ -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
46 changes: 46 additions & 0 deletions tags_update/tests/test_verify.py
Original file line number Diff line number Diff line change
@@ -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])
Comment on lines +34 to +36
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we would also want to ensure that the same check for the January tag commit should exist too, what do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point, would make the tests more comprehensive!



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")])
64 changes: 64 additions & 0 deletions tags_update/verify.py
Original file line number Diff line number Diff line change
@@ -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,
)