Git Archive: Exporting Clean Releases Without the .git Directory
AI generated
git
HEAD
Git
Git Archive
exporting clean releases without the .git directory

git archive turns any commit, tag, or tree into a plain file snapshot packaged as a zip or tar archive, with no commit history and no .git directory attached. For anyone still hand-copying files out of a checkout to build a release package, this built-in Git tool offers a deterministic, pipeline-friendly alternative.

9 min read Git Release

1. What sets git archive apart from a regular checkout

A regular git clone always drags along the full .git directory, meaning the entire commit history, every branch, and every object ever created. For a release artifact that gets shipped to customers or packaged into a deployment, that is usually unwanted baggage, and depending on the history it can even be a privacy risk if an earlier commit once contained sensitive content that was later removed but never purged from history.

git archive solves exactly that problem by working directly against the object database and writing only the file contents of a single, precisely chosen commit, tag, or tree into an archive. Internally it reads the same tree that git ls-tree would show, and it needs neither a working directory nor a local checkout at all, a bare repository is entirely sufficient.

The result is deterministic in terms of file content: the same commit produces the exact same file listing with the exact same content on every invocation, regardless of which branch happens to be checked out or what else is sitting in the working directory.


# Minimal example: export the current state as a zip archive
git archive --format=zip HEAD -o release.zip

# Same snapshot as a tar archive, straight out of a bare repository
git --git-dir=/path/to/bare-repo.git archive HEAD -o release.tar

2. Output formats: zip, tar, and compressed tar.gz

Via the --format option, git archive supports zip and tar as its standard formats, with zip typically the better fit for Windows recipients while tar tends to be the common choice on Linux and inside containers. When the target filename is instead given through -o with a recognizable extension such as .zip, Git infers the format from that extension automatically, so the explicit --format flag becomes optional.

For compressed tar archives, the tar.gz format can be requested directly, and Git relies on its own built-in zlib compression, so no external gzip binary is required. Anyone who wants control over the compression level can append a number from 1 to 9, where higher values trade extra computation time for a smaller output file, and a one-off release build usually benefits from the highest setting.


# Compressed tar.gz at maximum compression
git archive --format=tar.gz -9 HEAD -o release.tar.gz

# Format is inferred automatically from the file extension
git archive HEAD -o release.zip

3. A clean folder layout with --prefix

Without any further options, an archive's files sit directly at the top level, which quickly turns into a messy folder once it is unpacked, mixing release files with whatever else already lives in the target directory. The --prefix option adds a leading path to every entry in the archive, so unpacking it automatically creates its own root folder.

It is common practice to build that prefix from the project name and version, for example myshop-2.4.1/, so that unpacking several releases side by side never overwrites files and it stays obvious at a glance which folder holds which version.


# Build an archive with its own root directory
git archive --format=tar.gz --prefix=myshop-2.4.1/ v2.4.1 -o myshop-2.4.1.tar.gz

4. Excluding files deliberately with .gitattributes

Not every file in a repository belongs in a release archive. Test directories, internal documentation, CI configuration files, or developer tooling often need to stay under version control without ever being shipped. Git covers exactly that case with the export-ignore attribute, set inside a .gitattributes file, which applies only when archiving and leaves a normal checkout completely untouched.

A complementary attribute is export-subst: it enables substitution of placeholders such as $Format:%H$ inside a marked file with the actual commit hash at archive time, which works nicely to keep a version file traceable even without access to .git.


# .gitattributes at the project root
tests/            export-ignore
internal-docs/    export-ignore
.gitlab-ci.yml    export-ignore
VERSION           export-subst

5. Archiving a remote repository without a local checkout

The --remote option lets you request an archive directly from a remote repository, in theory without first needing a local clone. The server itself builds the archive and only transfers the finished package, which can noticeably save bandwidth and time on very large repositories with a long history.

In practice, most hosted platforms such as GitHub and GitLab disable this feature server side by default for security reasons, since it would otherwise let an attacker request arbitrary commits without that showing up clearly in access logs. For everyday use that means a local checkout followed by a local git archive call remains the more reliable path.


# Only works if the server explicitly allows uploadarchive
git archive --remote=ssh://git@server/project.git --format=tar HEAD | tar -x

6. Tying archives to Git tags for traceable releases

Instead of always archiving whatever HEAD currently points at, a release artifact should be tied to a concrete, immutable tag. An annotated tag permanently marks a commit with a version number, and git archive accepts that tag name in exactly the same position a commit hash would go.

That makes for a simple, repeatable flow: as soon as a tag like v2.4.1 gets pushed, a pipeline job automatically builds the matching archive and uploads it as a named release artifact, with nobody needing to manually check which commit currently represents the release.


# Build a release archive directly from a tag
git archive --format=tar.gz --prefix=myshop-2.4.1/ tags/v2.4.1 \
  -o myshop-2.4.1.tar.gz

7. Reproducibility and checksums for release artifacts

Since an archive's file content is derived unambiguously from the chosen commit, a checksum can be computed for every generated release package as reliable proof that a downloaded archive truly matches the tagged commit unchanged. Given the same Git version and identical options, the file content stays stable across repeated runs.

Minor differences in pure archive format metadata, such as timestamps inside the zip structure, are possible depending on the Git version in use, but they do not affect the actual file content or the integrity of the release package. A companion sha256sum file next to the archive belongs in any serious release pipeline.


# Keep a checksum alongside the release archive
sha256sum myshop-2.4.1.tar.gz > myshop-2.4.1.tar.gz.sha256

8. Using git archive inside CI/CD pipelines

Inside a pipeline, a single git archive call often replaces several lines of manual copy logic that used to exclude certain directories and pack the rest into an archive by hand. Since the checkout already exists on the runner anyway, one job step is enough to build the finished artifact and either upload it as a build artifact or push it into an artifact store.

One useful side effect: since the archive never contains a .git directory, a full commit history can never accidentally end up inside a publicly accessible release package, which meaningfully reduces a real risk for open source downloads and customer deployments alike.


# Example job step inside a CI pipeline
build-release:
  script:
    - git archive --format=tar.gz --prefix=app-${CI_COMMIT_TAG}/ \
        ${CI_COMMIT_TAG} -o app-${CI_COMMIT_TAG}.tar.gz
  artifacts:
    paths:
      - app-*.tar.gz

9. Limits of git archive: submodules and generated files

Two limitations are worth knowing before relying on this in production. First, submodules are not automatically unpacked by git archive, the archive only contains the reference to the submodule's checked-out commit, not its actual content. Projects that use submodules need an additional tool such as the well known git-archive-all script, which walks recursively through every submodule and merges their contents afterward.

Second, the archive only ever contains versioned files. Generated or installed dependencies such as directories holding Composer or npm packages are normally not part of the repository at all, and therefore need to be produced in a separate build step after archiving, before the finished package is actually runnable.

Criterion git archive git clone --depth 1 Manual zip/tar
Contains a .git directory No Yes, even if shrunk Depends on the script
Deterministic content Yes, exact to the commit Yes, but includes metadata No, error prone
Server side without a checkout Yes, if the remote allows it No No
Deliberate file exclusion Yes, via .gitattributes No, only manual cleanup afterward Yes, but manually maintained
Fits automated pipelines Yes, a single command Somewhat, needs an extra cleanup step No, high maintenance

Mironsoft

Git workflows, branching strategies, and CI hooks

Chaotic Git history and unclear branching rules across the team?

We set up clean Git workflows, clarify branching strategies for the team, and automate quality checks via Git hooks and CI pipelines so the history stays traceable.

Workflow Audit

Review the existing branching strategy and merge practice for weak spots.

Hook Automation

Set up pre-commit and pre-push hooks for linting, tests, and commit conventions.

Team Training

Teach rebase, cherry-pick, and conflict resolution hands-on across the team.

10. Summary

Git Archive

Core idea

git archive produces a plain file snapshot of a commit as zip or tar, with no .git directory and no history at all.

Clean layout

--prefix creates a dedicated root folder inside the archive, .gitattributes with export-ignore excludes specific files.

Typical use

A CI job builds a named, checksummed release artifact from the tagged commit automatically on every tag push.

Known limit

Submodules and generated dependencies such as vendor or node_modules directories are not archived automatically.

11. FAQ: Git Archive

1Does a package built with git archive include the full commit history?
No, the archive only contains the file content of the chosen commit as a flat snapshot, neither the history nor the .git directory are transferred.
2Can git archive export just a single subfolder?
Yes, by appending a path after the commit or tag, for example git archive HEAD:src, so the archive only contains the content of that subdirectory.
3Are uncommitted changes reflected in the archive?
No, git archive reads exclusively from the object database of the specified commit, changes in the working directory or the staging area are never included.
4How do I reliably exclude a directory like tests from the release archive?
With an entry such as tests/ export-ignore in the .gitattributes file, which only applies during archiving and leaves a normal checkout untouched.
5Does git archive --remote work against GitHub and GitLab?
In practice usually not, since both platforms disable server side archive export by default for security reasons, a local checkout followed by a local git archive call is the more reliable path there.
6Can I set the compression level for tar.gz archives?
Yes, by appending a number from 1 to 9, where a higher value trades extra computation time for a smaller output file, a one-off release build usually benefits from the highest setting.
7Does git archive replace a build system for release artifacts?
No, it only delivers the plain source code state as a snapshot, installed dependencies still need to be produced in a separate build step afterward.
8What happens to submodules when archiving?
They are not resolved automatically, the archive only contains the reference to the submodule's checked-out commit, the actual submodule content is missing without an extra tool.
9Can the commit hash be inserted into a file inside the archive automatically?
Yes, through the export-subst attribute in .gitattributes combined with a placeholder such as $Format:%H$ inside the target file, which gets replaced during archiving.
10Is an archive built with git archive identical on every run?
The file content stays stable given the same Git version and identical options, minor differences in pure format metadata such as timestamps are still possible depending on the version.