1. Introduction

Git commits are the basic unit of repository history.

They can be referred to in three main ways:

While the identifier is fixed and refs (references) are fairly static, tags are relatively easy to change.

In this tutorial, we talk about commit tags and how to rename them. First, we briefly refresh our knowledge about Git commits and tagging. After that, we explore the simpler tag type and how to modify such tags. Finally, we delve into the more complex tag type and how to handle it.

We tested the code in this tutorial on Debian 12 (Bookworm) with GNU Bash 5.2.15. Unless otherwise specified, it should work in most POSIX-compliant environments.

2. Git Commit Identification

Normally, a commit is identified via a 140-bit SHA-1 hash:

$ git log
commit 5474d2774b473dc5b76e14a66606dcb66cf6ff83 (HEAD -> master)
Author: x <[email protected]>
Date:   Thu Feb 15 15:00:01 2024 -0500

    late modifications

commit e76fd96c911d0ab2d36660dead84b691efa86aa1 (tag: antag, branch1)
Author: x <[email protected]>
Date:   Sat Feb 10 17:01:00 2024 -0500

    major modifications

commit dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59 (tag: minor)
Author: x <[email protected]>
Date:   Sat Feb 10 10:01:40 2024 -0500

    minor modifications

commit 2dac0d5faf151885a0ed14301041c4dabee88168
Author: x <[email protected]>
Date:   Sat Feb 10 10:01:00 2024 -0500

    init commit

In this case, we see four (4) commits.

Top to bottom, the current HEAD is in the default master branch. Thus, we can refer to it in three ways:

  • ID: 5474d2774b473dc5b76e14a66606dcb66cf6ff83
  • ref: HEAD
  • branch: master

Refs (references) enable the use of more human-friendly strings such as branch names to indicate certain commits. On the other hand, tagging is a practice whereby commits get assigned alternative names.

Continuing with the output above, the major modifications commit has the antag tag, but also marks the branch1 branch. Similarly, we have a minor modificatons commit with an ID of dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59, tagged as minor. Finally, the [init]ial commit only has an ID as a reference.

Thus, the convenience of tagging is obvious. Because of this, tags are usually a good way to indicate versions along the commit tree.

However, typos and incorrect tagging can result in problems with the structure and organization of a repository, not to mention potential commit issues. Therefore, we might need to modify tags that are already in place.

Although tags are immutable, we can delete and recreate them. Since there are two types of tags, we look at ways to handle both.

3. Create or Modify Lightweight Tag

Lightweight tags are simple names that we can assign to commits. They don’t carry any other information.

Because of this, we rename a lightweight tag in three steps:

  1. assign new tag to the same commit via an old tag, ID, or ref
  2. delete old tag
  3. push changes (optional)

Now, let’s see this in practice.

First, we verify the current tags for our commit:

$ git show minor
commit dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59 (tag: minor)
[...]

In this case, commit dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59 is tagged as minor.

Next, we assign the tag we’d like this commit to have:

$ git tag v0.1 minor

Notably, the new tag comes right after the tag subcommand, while the current or old tag should be at the end:

$ git tag <NEW_TAG> <CURRENT_TAG>

At this stage, both tags should be assigned and the new tag should work as expected:

$ git show v0.1
commit dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59 (tag: v0.1, tag: minor)
[...]

Finally, we can delete the old tag:

$ git tag --delete minor
Deleted tag 'minor' (was dbe16c5)

Let’s verify the results:

$ git show v0.1
commit dbe16c5120beefa93d43cbb5a3c83e5f7e2b3b59 (tag: v0.1)
[...]

As expected, the commit is now left with only the new v0.1 tag.

4. Create or Modify Annotated Tag

Similar to commits themselves, annotated tags are entire objects with a tagger name and email, as well as a date and tag message.

So, renaming an annotated tag can be a bit more involved:

  1. extract tagged commit date via an old tag, ID, or ref
  2. assign commit date to GIT_COMMITTER_DATE
  3. assign new annotated tag to the commit
  4. delete the old annotated tag

As we did before, we first check the current tag of our commit:

$ git show antag
tag antag
Tagger: x <[email protected]>
Date:   Sat Feb 10 17:01:00 2024 -0500

annotated tag

commit e76fd96c911d0ab2d36660dead84b691efa86aa1 (tag: antag, branch1)
[...]

Naturally, using show with the annotated tag shows data about the tag before displaying information about the commit object itself. In essence, antag* is one tag of commit *e76fd96c911d0ab2d36660dead84b691efa86aa1.

Now, we use the log subcommand to only extract the current date of the tag:

$ git log -1 --format=%aI <COMMIT>

Let’s break down this command:

  • log is the subcommand that shows the commit history
  • -1 ensures we only get one version of the tag (the most recent)
  • –format=%aI extracts the author date in strict ISO 8601 format
  • COMMIT is the desired commit we want to check

In particular, we want to assign the value of the resulting date to GIT_COMMITTER_DATE and export it:

$ export GIT_COMMITTER_DATE=$(git log -1 --format=%aI <COMMIT>)

In this case, we use the date of antag:

$ export GIT_COMMITTER_DATE=$(git log -1 --format=%aI antag)

At this point, any new annotated tag should have the date in GIT_COMMITER_DATE:

$ git tag --annotate v0.2 --message='version v0.2 with major improvements' antag^{}

Critically, we use antag^{} instead of just antag, because the TAG^{} syntax prevents the creation of a nested tag, i.e., tagging a tag. In fact, if we attempt to use an annotated tag to specify a commit we want to create another annotated tag for, we get a hint:

hint: You have created a nested tag. The object referred to by your new tag is
hint: already a tag. If you meant to tag the object that it points to, use:
[...]
hint: Disable this message with "git config advice.nestedTag false"

Let’s verify the application of our new tag:

$ git show v0.2
tag v0.2
Tagger: x <[email protected]>
Date:   Sat Feb 10 17:01:00 2024 -0500

version v0.2 with major improvements

commit e76fd96c911d0ab2d36660dead84b691efa86aa1 (tag: v0.2, tag: antag, branch1)
[...]

Finally, we can delete any unwanted tags:

$ git tag --delete antag
Deleted tag 'antag' (was fd83e3b)

Thus, we only have v0.2 as the annotated tag of commit e76fd96c911d0ab2d36660dead84b691efa86aa1, which also starts branch1.

5. Summary

In this article, we talked about Git commit tagging and how to modify tags.

In conclusion, although tags are immutable, each tag type can be recreated, thereby effectively reapplying a tag with a different name.