Most people run git tag -a and never think about the object underneath it. git mktag lets you build and validate that tag object by hand, which matters when you are debugging a signing workflow or a tool that generates tags itself. This guide uses the installed git-mktag from Git 2.43.0, packaged as git-man 1:2.43.0-1ubuntu7.3. Allow about 15 minutes for a first test, including creating a disposable repository.
Checkpoint: This command creates an object in the current repository's object database. It does not create or move a human-readable tag name such as v1.0.0. If you only need an ordinary annotated tag, git tag -a is usually the simpler interface. Use git mktag when you need to validate and store the tag object's exact contents.
Run this as your normal user. No elevated privileges are required for a repository you own:
$ command -v git
/usr/bin/git
$ git --version
git version 2.43.0
$ dpkg-query -W -f='${Package} ${Version}\n' git-man git
git-man 1:2.43.0-1ubuntu7.3
git 1:2.43.0-1ubuntu7.3
The manual's synopsis has no positional options: git mktag reads the complete tag from standard input and prints one object identifier on standard output. Option parsing still accepts --strict and --no-strict, covered below.
Use a temporary repository while learning the format. This creates one commit and stores its ID in a shell variable, touching only the directory named by mktemp:
$ workdir=$(mktemp -d /tmp/git-mktag-guide.XXXXXX)
$ git -C "$workdir" init
$ git -C "$workdir" config user.name 'Guide Test'
$ git -C "$workdir" config user.email [email protected]
$ printf 'sample\n' > "$workdir/file.txt"
$ git -C "$workdir" add file.txt
$ git -C "$workdir" commit -m 'Create test commit'
$ commit=$(git -C "$workdir" rev-parse HEAD)
$ printf '%s\n' "$commit"
6f... # your commit ID will differ
Git prints its normal commit summary. That shortened ID above is a shape example, not a value to paste. Keep the directory around until every check passes; there is no cleanup command here, since deleting a path before checking it is an avoidable way to lose your test data.
Checkpoint: Confirm the target is really a commit before constructing the tag:
$ git -C "$workdir" cat-file -t "$commit"
commit
A tag record names an existing object, gives its Git type, supplies the tag name, and identifies the tagger. A blank line separates those headers from the optional message. The tagger value carries a name, email address, Unix timestamp, and numeric timezone.
$ cat > "$workdir/tag.txt" <<EOF
object $commit
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000
Test release.
EOF
$ sed -n '1,8p' "$workdir/tag.txt"
object 6f...
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000
Test release.
Use the exact object ID from your own repository. type must describe that object's actual type. You can omit the message, but keeping a blank line and a short message makes the record easy to inspect. Do not add arbitrary headers after tagger: this installed command treats extra headers as an error by default.
Redirect standard output to a new file first. A successful run prints the new tag object's ID and writes the object into the repository:
$ git -C "$workdir" mktag < "$workdir/tag.txt" > "$workdir/tag-id.txt"
$ tag_object=$(sed -n '1p' "$workdir/tag-id.txt")
$ printf '%s\n' "$tag_object"
a60453650015dec1370605d21a2d6f1bd46b2ca7
Your hash will differ. There is no success message apart from that identifier. The output file is new state, so do not point the redirection at a file you actually need unless overwriting it is deliberate. If the input fails validation, the command should fail before writing the tag object, but a shell redirection can still truncate its destination before Git even starts, so choose a disposable output path or check for an existing file first.
Checkpoint: Verify both the exit result and the stored object type:
$ test -n "$tag_object" && git -C "$workdir" cat-file -t "$tag_object"
tag
$ git -C "$workdir" cat-file -p "$tag_object"
object 6f...
type commit
tag release-1.0
tagger Guide Test <[email protected]> 1720000000 +0000
Test release.
cat-file -p checks the object contents, not merely that a command returned zero. Compare the headers with the source record if you are debugging a generated tag or a signing workflow.
Here is the surprising bit: mktag runs an fsck-style check before writing, and in this version its default is equivalent to strict mode. Messages that ordinary git fsck might only warn about get promoted to errors, so missing required metadata such as tagger can stop the write outright.
It also rejects extra object headers that git fsck would otherwise ignore. If a known, compatible repository deliberately contains such a header, you can relax only that check for one invocation:
$ git -C "$workdir" -c fsck.extraHeaderEntry=ignore mktag < "$workdir/tag.txt"
a60453650015dec1370605d21a2d6f1bd46b2ca7
Do not copy this configuration into a general Git configuration without understanding why the header exists: ignoring validation can let malformed or surprising objects into a repository. Likewise, --no-strict disables the equivalent of git fsck --strict, it does not make an invalid object reference valid. Prefer fixing the tag record and keeping the default checks.
A missing or misspelt target produces an object lookup error. Check the target without changing anything:
$ git -C "$workdir" cat-file -t "$commit"
commit
git rev-parse HEAD or another object that actually exists in this repository. Elevated privileges will not create a missing object and can leave root-owned files behind, so do not reach for sudo as a first response.extraHeaderEntry, inspect the record for a line after tagger that is not part of the message. Remove it if it is accidental; document the compatibility reason before using the one-command override if it is intentional.The object ID printed by mktag is not the same thing as a repository ref. Listing it with git cat-file proves the object exists, but git tag will not show release-1.0 until a ref actually points at it. For a normal annotated tag, create the ref only after checking the object:
$ git -C "$workdir" update-ref "refs/tags/release-1.0" "$tag_object"
$ git -C "$workdir" show-ref --tags
a60453650015dec1370605d21a2d6f1bd46b2ca7 refs/tags/release-1.0
This changes the repository's refs, so treat it as a deliberate state change. To undo this exact test ref, confirm the name first, then remove only that ref:
$ git -C "$workdir" update-ref -d refs/tags/release-1.0
$ git -C "$workdir" show-ref --verify --quiet refs/tags/release-1.0; printf 'status: %s\n' "$?"
status: 1
Deleting the ref makes the tag object unreachable from that name; it does not immediately erase the object itself. Do not use ref deletion as a substitute for checking which tag a production repository should actually retain.
git-man package.git mktag returned an object ID and git cat-file -t reported tag.