<!--order=9-->
<h1>Git: Tags, Archives, and Bundles</h1>
<p>Tags mark specific points in history — typically releases. Archives let you export a snapshot of the repository as a tarball or zip without the <code>.git</code> directory. Bundles package a repository or part of one into a single file for transfer without a network connection.</p>

<h2>Commands Covered</h2>
<p><code>git tag</code> · <code>git archive</code> · <code>git bundle</code></p>

<h2>git tag</h2>
<p>Tags are references to specific commits. There are two kinds: lightweight tags (just a pointer) and annotated tags (a full object with tagger, date, and message). Use annotated tags for releases.</p>
<pre>
# List all tags
$ git tag
$ git tag -l
$ git tag --list

# List tags matching a pattern
$ git tag -l "v1.*"
$ git tag -l "v2.0*"

# Create a lightweight tag (just a pointer — no metadata)
$ git tag v1.0.0

# Create a lightweight tag on a specific commit
$ git tag v1.0.0 abc1234

# Create an annotated tag (recommended for releases)
$ git tag -a v1.0.0 -m "Release version 1.0.0"
$ git tag --annotate v1.0.0 -m "Release 1.0.0"

# Create a signed annotated tag (requires GPG key)
$ git tag -s v1.0.0 -m "Signed release 1.0.0"

# Tag a past commit with annotation
$ git tag -a v0.9.0 abc1234 -m "Retroactive tag for 0.9.0"

# Show tag info (for annotated tags shows tagger, date, message + commit)
$ git show v1.0.0

# Verify a signed tag
$ git tag -v v1.0.0

# Delete a local tag
$ git tag -d v1.0.0-beta
$ git tag --delete v1.0.0-beta

# Push a specific tag to remote
$ git push origin v1.0.0

# Push all tags
$ git push origin --tags
$ git push --follow-tags   # push tags reachable from pushed commits

# Delete a remote tag
$ git push origin --delete v1.0.0-beta
$ git push origin :refs/tags/v1.0.0-beta

# Fetch tags from remote
$ git fetch --tags

# Check out the code at a tag (detached HEAD)
$ git checkout v1.0.0
# To work on a tagged release, create a branch:
$ git switch -c hotfix-1.0 v1.0.0
</pre>

<h3>Tag Naming Conventions</h3>
<table>
  <thead><tr><th>Format</th><th>Example</th><th>Notes</th></tr></thead>
  <tbody>
    <tr><td>Semantic versioning</td><td><code>v1.2.3</code></td><td>Most common; MAJOR.MINOR.PATCH</td></tr>
    <tr><td>With pre-release</td><td><code>v1.2.3-rc1</code>, <code>v2.0.0-beta.2</code></td><td>Release candidates, betas</td></tr>
    <tr><td>Date-based</td><td><code>2024-11-15</code></td><td>Calver; useful for rolling releases</td></tr>
  </tbody>
</table>

<h2>git archive</h2>
<p>Creates a tar or zip archive of the repository contents at a specific commit or tag, without the <code>.git</code> directory. Useful for distributing source tarballs for releases.</p>
<pre>
# Create a .tar.gz of HEAD
$ git archive --format=tar.gz HEAD &gt; project.tar.gz

# Create a .zip of HEAD
$ git archive --format=zip HEAD &gt; project.zip

# Archive a specific tag or commit
$ git archive --format=tar.gz v1.0.0 &gt; project-1.0.0.tar.gz
$ git archive --format=tar.gz abc1234 &gt; project-snapshot.tar.gz

# Archive with a prefix (adds a directory prefix to all files in the archive)
$ git archive --format=tar.gz --prefix=project-1.0.0/ v1.0.0 \
    &gt; project-1.0.0.tar.gz
# When extracted, all files are inside project-1.0.0/ directory

# Archive only a subdirectory
$ git archive --format=tar.gz HEAD:src/ &gt; src-only.tar.gz

# Archive a specific subdirectory with prefix
$ git archive --format=zip --prefix=myproject/ HEAD -- docs/ \
    &gt; docs.zip

# List files that would be in the archive (dry run)
$ git archive HEAD | tar t

# Use .gitattributes to control what gets exported:
# In .gitattributes:
# tests/ export-ignore
# .github/ export-ignore
# *.md export-ignore
# Files/dirs with export-ignore attribute are excluded from git archive
</pre>

<h2>git bundle</h2>
<p>Creates a binary file containing a packaged Git repository or subset of one. Bundles can be cloned, fetched from, and pulled from — they're portable repositories. Useful for transferring repository history when no network connection is available (air-gapped systems, offline handoffs).</p>
<pre>
# Create a full bundle of the entire repository
$ git bundle create repo.bundle --all
# repo.bundle contains all branches, tags, and history

# Create a bundle of just the main branch
$ git bundle create main.bundle main

# Create a bundle with specific branches and tags
$ git bundle create release.bundle main v1.0.0 v1.1.0

# Create an incremental bundle (only new commits since a tag)
$ git bundle create update.bundle v1.0.0..main
# Contains only commits on main since v1.0.0

# Create a bundle from a date
$ git bundle create recent.bundle --since="1 month ago" main

# Verify a bundle file
$ git bundle verify repo.bundle

# List what's in a bundle
$ git bundle list-heads repo.bundle

# Clone from a bundle
$ git clone repo.bundle my-local-copy

# Fetch from a bundle into an existing repo
$ git fetch repo.bundle main:main

# Pull from a bundle
$ git pull repo.bundle main

# Typical offline workflow:
# Machine A (with internet):
$ git bundle create transfer.bundle --all
# Copy transfer.bundle to USB/sneakernet

# Machine B (air-gapped):
$ git clone transfer.bundle project/
# Or if repo exists already:
$ git fetch /media/usb/transfer.bundle main:main
</pre>

<h2>References</h2>
<ul>
  <li><a href="https://git-scm.com/docs/git-tag" target="_blank" rel="noopener">git-tag documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-archive" target="_blank" rel="noopener">git-archive documentation ↗</a></li>
  <li><a href="https://git-scm.com/docs/git-bundle" target="_blank" rel="noopener">git-bundle documentation ↗</a></li>
  <li><a href="https://semver.org/" target="_blank" rel="noopener">Semantic Versioning specification ↗</a></li>
</ul>
