Skip to content

Repository files navigation

Git-ArchiveBranchDiffs

Create a self-contained ZIP archive of just the files that differ between two git branches — perfect for offline code review without needing a pull request.

  • No PR required — compare any two related branches, tags, or commits
  • Working tree & staged diffs — archive uncommitted or staged changes
  • Three-way diffs — see what each side changed relative to the merge-base
  • Offline review — extract the archive and use your favorite diff tool
  • Cross-platform — runs on Windows, Linux, and macOS
  • Binary-safe — handles text and binary files without corruption
  • Tab completion — branches, tags, stashes, and repo paths auto-complete in PowerShell
  • Subdirectory launch — run from anywhere inside a git repo

Table of Contents


Quick Start

Prerequisite: git must be on your PATH

Windows (PowerShell)

# Interactive — prompts for each input
pwsh ./Git-ArchiveBranchDiffs.ps1

# With parameters
pwsh ./Git-ArchiveBranchDiffs.ps1 -repositoryPath "C:\repos\myRepo" -leftBranch main -rightBranch feature/my-branch -outputDirectory "C:\output"

# Non-interactive — uses smart defaults
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive

# Archive uncommitted working tree changes
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -workingTree

# Archive staged changes only
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -staged

# Three-way diff showing merge-base
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -threeWay

# Compare two tags
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch v1.0.0 -rightBranch v2.0.0

# Compare a tag to a branch
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch v1.0.0 -rightBranch main

Linux / macOS (Bash)

If PowerShell Core is not installed, the bash wrapper will download and install it automatically:

chmod +x ./Git-ArchiveBranchDiffs.sh
sudo bash ./Git-ArchiveBranchDiffs.sh

Or if pwsh is already installed:

pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive

Parameters

Parameter Required Default Description
-repositoryPath No Current directory or auto-detected from subdirectory Path to any directory inside a git repository
-leftBranch No Default remote branch (e.g., origin/main) Branch, tag, or commit ref for the left side of the diff. Also accepts the tokens WORKING and STAGED (see Uncommitted Tokens).
-rightBranch No Currently checked-out branch Branch, tag, or commit ref for the right side of the diff. Also accepts WORKING / STAGED.
-outputDirectory No Prompted (interactive) or current directory Where the ZIP file will be created
-archiveFileName No Auto-generated from branch names Custom name for the ZIP file
-nonInteractive No $false Skip all prompts and use smart defaults
-workingTree No $false Compare uncommitted working tree changes against the left branch
-staged No $false Compare staged (indexed) changes against the left branch
-threeWay No $false Produce a three-way diff with base, left, and right directories
-versionedName No $false Include commit hashes and a version timestamp in the archive filename
-patchContextLines No 5 Number of context lines in CHANGES.patch (git diff -U<N>). Increase to 10–15 for deeper AI review.
-noMergesInHistory No $false Omit merge commits from HISTORY.md. Useful on PR-workflow branches where merge commits add noise.
-fetch No $false Fetch any remote-tracking ref named on either side before resolving — retrieves it if missing, refreshes it if stale. Targeted and non-destructive (updates refs/remotes/* only).
-reveal No $false After creating the archive, open the OS file manager with the archive selected (Explorer /select, Finder open -R; folder on Linux). Non-blocking; non-fatal if it can't launch.
-open No $false After creating the archive, open it with the default .zip handler. Combinable with -reveal. Non-blocking; non-fatal.
-includeUntracked No $false Include untracked (never-added) files in working-tree comparisons. Off by default.

Comparison Modes

The tool compares two endpoints — each a committed ref or an uncommitted-state token:

Normal (default)

Compares two committed refs (branches, tags, or commit hashes). The archive contains left and right directories with the differing files.

pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch main -rightBranch feature/foo

# Tags work the same way
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch v1.0.0 -rightBranch v2.0.0

Uncommitted state (WORKING / STAGED)

Besides a branch, tag, or commit, either side accepts two special ref tokens that point at your uncommitted state. Git tracks your code in three snapshots, and these are how you bookend a comparison against the live, not-yet-committed ones:

Token Means Git equivalent
HEAD The last commit on the checked-out branch (your most recent save). HEAD
STAGED Changes you have git add-ed but not committed — the index. git diff --staged
WORKING Files on disk right now — staged and unstaged edits together. git diff

The progression is: you edit files (WORKING) → git add (STAGED) → git commit (HEAD). Common pairings:

# Everything changed since the last commit (staged + unstaged) — the "what am I about to commit" view
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch HEAD -rightBranch WORKING

# Exactly what is staged for the next commit
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch HEAD -rightBranch STAGED

# What you've edited but have NOT staged yet
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch STAGED -rightBranch WORKING

Aliases: WORKING = WORKTREE / WT; STAGED = INDEX / STG (all case-insensitive).

A token always means your live state. WORKING/STAGED reflect the one working tree / index in the folder, which sits on top of HEAD. Comparing a token to any base just runs git diff <base>, so pairing it with a base that isn't HEAD (e.g. origin/main when your branch is ahead of it) naturally shows everything between that base and your live files — the commits since the base plus your uncommitted edits. The tool prints a neutral note saying so; it's the intended "what does my work look like vs the base" review.

# What my branch + my uncommitted edits look like against the upstream base
pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch origin/main -rightBranch WORKING

To get a committed-only comparison (exclude your uncommitted edits), name HEAD instead of a token: origin/main ⟷ HEAD.

Untracked files (brand-new, never git add-ed) are not included by default; add -includeUntracked to archive them too.

Fetching a base that isn't local. If a base names a remote-tracking ref (origin/main, upstream/release) that's missing or stale, add -fetch to retrieve/refresh it first — a targeted, non-destructive git fetch that updates refs/remotes/* only (never your HEAD or working tree). In interactive mode a missing remote ref also prompts to fetch; in -nonInteractive mode the network is only touched when -fetch is given. Bare SHAs can't be auto-fetched.

pwsh ./Git-ArchiveBranchDiffs.ps1 -leftBranch origin/main -rightBranch WORKING -fetch

Back-compat aliases (-workingTree / -staged)

These switches still work and are shorthand for the tokens above:

pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -workingTree   # = -rightBranch WORKING
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -staged        # = -rightBranch STAGED

When used without an explicit -leftBranch, the left side defaults to HEAD (so the comparison is your pure uncommitted delta). They are mutually exclusive with each other and with an explicit -rightBranch.

Three-Way (-threeWay)

Produces a three-way diff with a base/ directory showing the merge-base, plus left and right directories showing what each side changed. Mutually exclusive with -workingTree and -staged.

pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -threeWay -leftBranch main -rightBranch feature/foo

Non-Interactive Mode

The -nonInteractive switch enables fully scripted usage with no prompts. Smart defaults are applied for any parameter not explicitly provided:

Parameter Default When -nonInteractive
-repositoryPath Auto-detected from current directory (works from any subdirectory)
-leftBranch Default remote branch via git symbolic-ref
-rightBranch Currently checked-out branch (falls back to HEAD on detached HEAD)
-outputDirectory Current working directory
-archiveFileName <leftBranch> ⟷ <rightBranch>.zip (or versioned, see below)

Versioned Archive Names (-versionedName)

When -versionedName is set, the auto-generated filename includes short commit hashes and a version timestamp derived from the commit date. This prevents overwrites when re-running the tool after new commits land on the same branches.

Format: {leftName} ⟷ {rightName} ({leftHash}..{rightHash} {Year}.{Quarter}.{MMdd}.{HHmm}).zip

Examples:

Mode Filename
Branch main ⟷ f_my-feature (abc1234..def5678 2026.2.0413.1430).zip
Three-way 3way main ⟷ f_my-feature (abc1234..def5678 2026.2.0413.1430).zip
Working tree main ⟷ WORKING-TREE (abc1234..abc1234+wt 2026.2.0413.1502).zip
Staged main ⟷ STAGED (abc1234..abc1234+stg 2026.2.0413.1502).zip

The version uses the newer commit's date (adapted from the BuildVersion algorithm): Year.Quarter.MMdd.HHmm where Quarter = ⌈Month / 3⌉. An explicit -archiveFileName always takes precedence over versioned naming.

Patch Quality (-patchContextLines, -noMergesInHistory)

CHANGES.patch is produced with several enhancements over a plain git diff:

  • Rename/copy detection--find-renames --find-copies is always active, so renamed files appear as proper rename hunks (similarity index N% / rename from / rename to) instead of a delete + add pair.
  • Stat summary header — The patch opens with a git diff --stat block listing each changed file and its line counts. This gives any reader an immediate overview before the first diff hunk and is safe for git apply (which ignores pre-diff --git lines).
  • Configurable context lines — Default is 5 lines of context (vs. git's built-in 3). Pass -patchContextLines 10 (or higher) when you want more surrounding code visible per hunk — useful for AI review.
  • Merge commit filter — Pass -noMergesInHistory to suppress merge commits from HISTORY.md. On GitHub-flow repos where every PR lands as a merge commit, this removes the noise and shows only real work commits.

Examples

# Run from inside a git repo (any subdirectory) — all defaults
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive

# Override just the output directory
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -outputDirectory /tmp

# Fully specified (nonInteractive prevents any prompts for missing values)
pwsh ./Git-ArchiveBranchDiffs.ps1 -nonInteractive -repositoryPath /c/myRepo -leftBranch main -rightBranch feature/foo -outputDirectory /tmp

Output Format

The tool creates a ZIP archive with this structure:

Normal / Working Tree / Staged

archive.zip
├── leftBranch/                         # Files from the left (base) branch
│   ├── src/
│   │   ├── App.cs                      # Original version of modified file
│   │   └── NewFeature.cs-added         # Placeholder (file was added in right)
│   ├── OldName.cs-renamed-R095         # Placeholder showing original name before rename
│   └── Removed.cs                      # Original content of deleted file
│
├── rightBranch/                        # Files from the right (feature) branch
│   ├── src/
│   │   ├── App.cs                      # Modified version
│   │   └── NewFeature.cs               # New file content
│   ├── NewName.cs                      # File after rename
│   └── Removed.cs-deleted              # Placeholder (file was deleted)
│
├── HISTORY.md                          # Commit log, churn summary, per-commit file lists
├── CHANGES.patch                       # Unified diff (git diff output)
│
└── manifest/                           # Metadata generated by the tool
    ├── Δ leftBranch ⟷ rightBranch      # Branch comparison info
    └── commit# abc1234.manifest        # Change summary and file list

Three-Way (-threeWay)

3way archive.zip
├── base/                               # Files at the merge-base commit
│   └── ...
├── leftBranch/                         # Files from the left branch
│   └── ...
├── rightBranch/                        # Files from the right branch
│   └── ...
├── HISTORY.md
├── CHANGES.patch
└── manifest/
    └── ...

HISTORY.md

The history file includes:

  • Churn Summary — top 10 files by insertions + deletions, with binary file detection
  • Commit log — per-side commit lists with per-commit file breakdowns (capped at 200 commits per side)

CHANGES.patch

A unified diff (git diff output) covering all changed files. To apply the patch:

# Strict — fails on any context mismatch
git apply CHANGES.patch

# Forgiving — skips hunks that don't apply cleanly
git apply --3way CHANGES.patch

# Outside a git repo
patch -p1 < CHANGES.patch

Placeholder Files

Placeholder files ensure both sides of the diff have a representative file for every change, so directory-diff tools can display them side-by-side.

Git Status Left Branch Right Branch
Added NewFile.cs-added (zero bytes) NewFile.cs (new content)
Deleted OriginalFile.cs (original content) OriginalFile.cs-deleted (zero bytes)
Modified File.cs (original content) File.cs (modified content)
Renamed OldName.cs-renamed-R095 (original content) NewName.cs (same content, new name)
Copied Original.cs (original content) CopiedFile.cs (copied content)

For renames, the suffix is -renamed-R<NN>, where R<NN> is the raw git rename status code indicating the similarity percentage (e.g., -renamed-R095 = 95% similar).


Tab Completion

When using PowerShell, tab completion is available for key parameters:

Parameter Completes To
-repositoryPath Directories containing a .git folder
-leftBranch Local and remote branch names, tags, and stash refs
-rightBranch Local and remote branch names, tags, and stash refs
# Type and press Tab to cycle through matching branches
./Git-ArchiveBranchDiffs.ps1 -leftBranch ma<Tab>
# Completes to: main

./Git-ArchiveBranchDiffs.ps1 -rightBranch feat<Tab>
# Completes to: feature/my-branch

# Tags and stashes also complete
./Git-ArchiveBranchDiffs.ps1 -leftBranch v1.<Tab>
# Completes to: v1.0, v1.1, etc.

Note: Branch completion uses the repository specified by -repositoryPath, or the current directory if not specified.


Diff Tool Recommendations

Extract the archive and open the left/right branch directories in a directory-diff tool:

Tool Platform Notes
Beyond Compare Windows, Linux, macOS Excellent rename detection, can treat placeholder-suffixed files as comparable
Meld Windows, Linux, macOS Free and open-source
VS Code Windows, Linux, macOS Use with a folder-diff extension
WinMerge Windows Free, lightweight

Troubleshooting

git not found

Ensure git is installed and on your PATH. Verify with git --version.

Branch not found / defaults to HEAD

  • Check branch name spelling: git branch -a to list all branches
  • Remote-only branches need a fetch first: git fetch origin
  • The tool will try the remote version (e.g., origin/branchName) if the local branch isn't found

"Left and right refer to the same commit"

The two refs resolve to the same commit hash. The tool exits early since there is nothing to compare. Verify you specified different branches.

"Consider swapping -leftBranch and -rightBranch"

The tool detected that rightBranch is an ancestor of leftBranch (i.e., left has the newer commits). This usually means the arguments are reversed — the left branch should be the base (e.g., main) and the right branch should be the feature branch.

"Working tree is clean — nothing to archive"

When using -workingTree, there are no uncommitted changes to archive. Make some changes first, or use normal mode to compare committed branches.

"Index is empty — nothing to archive"

When using -staged, there are no staged changes. Stage files with git add first.

-threeWay fails with "no common ancestor"

The two branches have unrelated histories (no merge-base). This can happen with orphan branches or repos initialized separately. Use normal mode instead.

Permission denied

  • Windows: Run PowerShell as Administrator
  • Linux/macOS: Use sudo for the bash wrapper (needed for PowerShell Core installation)

Empty archive / no differences

The branches may be identical. Verify differences exist: git diff --stat <leftBranch>...<rightBranch>

PowerShell version issues

  • Windows: PowerShell 5.1+ (built-in) or PowerShell Core 7.3.4+
  • Linux/macOS: PowerShell Core 7.3.4+ (auto-installed by the bash wrapper)
  • Check version: $PSVersionTable.PSVersion

License

MIT

About

A tool for `git` repositories that creates a `ZIP` archive containing JUST the files representing the diff/delta between two related branches

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages