Timestamped archive snapshots of git repositories. One script, zero dependencies beyond git and tar.
Creates .tar.gz archives of git repos with timestamps and branch/commit info baked into the filename. Perfect for cron-based backups, manual checkpoints before risky rebases, or just keeping history of your work outside of git itself.
git clone https://github.com/cappy-dev/git-snapshot.git
cd git-snapshot
chmod +x git-snapshot.sh
# Snapshot the current repo
./git-snapshot.sh./git-snapshot.sh [OPTIONS] [REPO_PATH]If REPO_PATH is omitted, the current directory is used.
-o, --output DIR- Output directory for the archive (default: repo parent directory)-g, --include-dotgit- Include the.gitdirectory in the archive-u, --skip-untracked- Exclude untracked files from the snapshot-n, --no-compress- Create an uncompressed.tarinstead of.tar.gz-p, --prefix NAME- Custom prefix for the archive filename--dry-run- Print what would happen without creating anything-v, --verbose- Show detailed output-h, --help- Show help message--version- Show version number
Snapshot the current repo (archive goes to the parent directory):
./git-snapshot.shSnapshot a specific repo into ~/backups:
./git-snapshot.sh -o ~/backups /path/to/my-repoSnapshot including the .git directory (full backup):
./git-snapshot.sh --include-dotgitOnly include tracked files (skip untracked):
./git-snapshot.sh --skip-untrackedCustom prefix for the archive name:
./git-snapshot.sh --prefix myprojectDry run to see what would be archived:
./git-snapshot.sh --dry-runUncompressed tar output:
./git-snapshot.sh --no-compressDefault format:
<repo-name>-<branch>-<short-sha>-<timestamp>.tar.gz
Examples:
myapp-main-a3f7c2b-20260628-153000.tar.gz
myapp-main-a3f7c2b-dirty-20260628-153000.tar.gz
The -dirty suffix appears when the working tree has uncommitted changes, so you can tell snapshot apart from a clean checkout.
Add a line to your crontab for automatic daily snapshots:
# Daily at 2 AM
0 2 * * * /path/to/git-snapshot.sh -o /mnt/backups/git /home/user/projects/myappFor multiple repos:
0 2 * * * for repo in /home/user/projects/*; do /path/to/git-snapshot.sh -o /mnt/backups/git "$repo"; doneBy default, the script uses git archive to package tracked files. This means:
- Only files git knows about are included (unless you use
--skip-untrackedto explicitly exclude untracked, or--include-dotgitfor full backups) - Untracked files are added automatically unless you pass
--skip-untracked - Submodules are included as their current committed state
- The archive uses a prefix directory matching the repo name
When --include-dotgit is used, the entire directory (including .git) is archived with standard tar. This gives you a complete mirror of the repo at that point in time, including all history, reflogs, and stash entries.
Without .git (default snapshot):
mkdir my-repo && tar -xzf my-repo-main-a3f7c2b-20260628-153000.tar.gz -C my-repo
cd my-repo/my-repo
git init
git add .
git commit -m "Restored from snapshot"With .git (full backup):
tar -xzf my-repo-main-a3f7c2b-20260628-153000.tar.gz
cd my-repo
# The .git directory is preserved, so all history is intact
git log # shows full history- Bash 4.0+
- Git
- tar and gzip (standard on any Linux/macOS)
No Python, no Node.js, no extra packages. Just git and tar.
git bundle creates a single-file clone of the repo including history, which is great for transferring repos. But git-snapshot serves a different purpose:
git bundlerequires a separategit clonestep to inspect the contentsgit-snapshotcreates a regular.tar.gzyou can browse immediatelygit-snapshotcan include untracked files and build artifacts thatgit bundleignoresgit-snapshotincludes the full.gitdirectory when you want it, preserving reflogs and stash
Use git bundle for creating portable repo clones. Use git-snapshot for point-in-time backup archives you can open like any other tarball.
MIT