Compare diffs between two git ranges - a 'diff of diffs' tool for verifying rebases and tracking changes
Project description
git-didi
Compare diffs between two git ranges - a "diff of diffs" tool.
Demo Branches: See test scenarios for examples you can try. Test scenario 01 is live in this repo!
This tool is particularly useful for verifying rebases and merges, especially when complex conflict resolution was involved. It helps ensure that the actual changes in your branch remain approximately the same before and after rebasing onto a new upstream.
Installation
pip install git-didi
Or with uv:
uv tool install git-didi
Usage
Common use case - checking a rebase
After fetching and rebasing your branch onto main:
git fetch
git rebase main
You can verify the rebase preserved your changes:
git-didi patch main@{1}..branch@{1} main..branch
This compares:
- Left side: Your changes before the rebase (
main@{1}..branch@{1}) - Right side: Your changes after the rebase (
main..branch)
The @{1} syntax refers to the previous position in the reflog. If both refs moved exactly once during the rebase, @{1} will work. If you've done multiple operations, you may need @{2}, @{3}, etc. Use git reflog to find the right positions.
Commands
stat - Compare diff stats
Compare git diff --stat output between two refspecs:
git-didi stat main..feature upstream/main..feature
Shows only the files where the diff statistics differ.
patch - Compare patches file-by-file
Compare patches between two refspecs, showing differences for each file:
git-didi patch main..feature upstream/main..feature
This shows a "diff of diffs" with sophisticated coloring to distinguish:
- Changes in the outer diff (what changed between the two versions)
- Changes in the inner diffs (the actual patches)
Options:
-U N/--unified N: Set context lines (default: 3)-q/--quiet: Only list files with differences-w/--ignore-whitespace: Ignore whitespace changes-M[n]/--find-renames[=n]: Detect renames-C[n]/--find-copies[=n]: Detect copies--color {auto,always,never}: Control colored output--pager {auto,always,never}: Control pager usage
commits - Compare commits
Compare individual commits between two refspecs:
git-didi commits main..feature upstream/main..feature
First verifies that commits correspond (same count and messages), then shows per-commit differences.
swatches - Display color palette
Show color swatches demonstrating the diff-of-diffs coloring scheme:
git-didi swatches --color=always
Filtering by path
All commands support filtering to specific paths:
git-didi patch main..feature upstream/main..feature -- src/
git-didi stat main..feature upstream/main..feature -- "*.py"
Git Aliases
You can add these to your ~/.gitconfig for convenient access:
[alias]
didi = !git-didi
gdds = !git-didi stat
gddp = !git-didi patch
gddc = !git-didi commits
Then use:
git didi patch main@{1}..branch@{1} main..branch
git gddp main..feature upstream/main..feature
How it works
The tool automatically filters out spurious differences like git index SHAs that change even when the actual patch content is identical. This makes it easy to verify that a rebase or cherry-pick truly preserved your changes without introducing unexpected modifications.
When comparing patches, it uses a sophisticated 256-color palette to make nested diffs easy to read:
- Bright backgrounds for added/removed lines within the outer diff
- Dark backgrounds for context lines
- Mixed colors for lines that changed type (+ to - or vice versa)
Try it yourself - Test Scenarios
This repo includes test branches demonstrating various rebase/merge scenarios. Try:
# Clone this repo
git clone https://github.com/ryan-williams/git-didi.git
cd git-didi
# Example: Clean rebase with disjoint changes
# Alice adds priority field, Bob adds JSON format - no conflicts!
git-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice \
tests/01-clean-disjoint/bob..tests/01-clean-disjoint/alice-rebased-on-bob
# Output: "No differences in patches" ✓
# See what each developer changed
git log --oneline tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice
git log --oneline tests/01-clean-disjoint/base..tests/01-clean-disjoint/bob
# Try the other direction
git-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/bob \
tests/01-clean-disjoint/alice..tests/01-clean-disjoint/bob-rebased-on-alice
# Or verify a merge commit preserved both changes
git-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice \
tests/01-clean-disjoint/merged^1..tests/01-clean-disjoint/merged
See Test Strategy for details on the test scenarios and how to generate more.
Development
# Clone and setup
git clone https://github.com/ryan-williams/git-didi.git
cd git-didi
# Setup with uv
uv sync --extra test
# Run tests
pytest tests/ -v
# Generate additional test scenarios
./scripts/generate-test-scenario.py 01-clean-disjoint
# Install locally
uv pip install -e .
License
MIT License - see LICENSE for details.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file git_didi-0.1.1.tar.gz.
File metadata
- Download URL: git_didi-0.1.1.tar.gz
- Upload date:
- Size: 16.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7bf948f17204a5b526abf447161160f70907c6e7712d249e40dc97a70082af02
|
|
| MD5 |
26a4a50de711579564c57629acef1088
|
|
| BLAKE2b-256 |
9765d299d197233d493223d1a4f9566442ae9f4eefde4d440467741745704d25
|
Provenance
The following attestation bundles were made for git_didi-0.1.1.tar.gz:
Publisher:
release.yml on runsascoded/git-didi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_didi-0.1.1.tar.gz -
Subject digest:
7bf948f17204a5b526abf447161160f70907c6e7712d249e40dc97a70082af02 - Sigstore transparency entry: 685379622
- Sigstore integration time:
-
Permalink:
runsascoded/git-didi@5887f3ace899256abe24193be562982cdbb4c508 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/runsascoded
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5887f3ace899256abe24193be562982cdbb4c508 -
Trigger Event:
push
-
Statement type:
File details
Details for the file git_didi-0.1.1-py3-none-any.whl.
File metadata
- Download URL: git_didi-0.1.1-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a08128e0493e08bee55aa2e8052b68aaae03926bebd1498115c46178e8d09873
|
|
| MD5 |
7d7fa3e66feebcd011eefe918fb4adba
|
|
| BLAKE2b-256 |
d643d38be6ab71ca586ad0389d37910fdc0c6138184485dd68c604a9a8ef3205
|
Provenance
The following attestation bundles were made for git_didi-0.1.1-py3-none-any.whl:
Publisher:
release.yml on runsascoded/git-didi
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
git_didi-0.1.1-py3-none-any.whl -
Subject digest:
a08128e0493e08bee55aa2e8052b68aaae03926bebd1498115c46178e8d09873 - Sigstore transparency entry: 685379630
- Sigstore integration time:
-
Permalink:
runsascoded/git-didi@5887f3ace899256abe24193be562982cdbb4c508 -
Branch / Tag:
refs/tags/v0.1.1 - Owner: https://github.com/runsascoded
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5887f3ace899256abe24193be562982cdbb4c508 -
Trigger Event:
push
-
Statement type: