git-didi


Namegit-didi JSON
Version 0.1.0 PyPI version JSON
download
home_pageNone
SummaryCompare diffs between two git ranges - a 'diff of diffs' tool for verifying rebases and tracking changes
upload_time2025-11-08 18:20:00
maintainerNone
docs_urlNone
authorNone
requires_python>=3.10
licenseMIT
keywords git diff rebase cli
VCS
bugtrack_url
requirements No requirements were recorded.
Travis-CI No Travis.
coveralls test coverage No coveralls.
            # git-didi

Compare diffs between two git ranges - a "diff of diffs" tool.

> **Demo Branches**: See [test scenarios](docs/test-strategy.md#proposed-test-branch-structure) 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

```bash
pip install git-didi
```

Or with `uv`:

```bash
uv tool install git-didi
```

## Usage

### Common use case - checking a rebase

After fetching and rebasing your branch onto main:

```bash
git fetch
git rebase main
```

You can verify the rebase preserved your changes:

```bash
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:

```bash
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:

```bash
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:

```bash
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:

```bash
git-didi swatches --color=always
```

### Filtering by path

All commands support filtering to specific paths:

```bash
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:

```ini
[alias]
    didi = !git-didi
    gdds = !git-didi stat
    gddp = !git-didi patch
    gddc = !git-didi commits
```

Then use:

```bash
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:

```bash
# 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](docs/test-strategy.md) for details on the test scenarios and how to generate more.

## Development

```bash
# 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.

[LICENSE]: LICENSE

            

Raw data

            {
    "_id": null,
    "home_page": null,
    "name": "git-didi",
    "maintainer": null,
    "docs_url": null,
    "requires_python": ">=3.10",
    "maintainer_email": null,
    "keywords": "git, diff, rebase, cli",
    "author": null,
    "author_email": "Ryan Williams <ryan@runsascoded.com>",
    "download_url": "https://files.pythonhosted.org/packages/c8/97/c47c1cc913f191fefcdbea570f46001d9f3ec4f29e925cd06a5606b7f761/git_didi-0.1.0.tar.gz",
    "platform": null,
    "description": "# git-didi\n\nCompare diffs between two git ranges - a \"diff of diffs\" tool.\n\n> **Demo Branches**: See [test scenarios](docs/test-strategy.md#proposed-test-branch-structure) for examples you can try. Test scenario 01 is live in this repo!\n\nThis 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.\n\n## Installation\n\n```bash\npip install git-didi\n```\n\nOr with `uv`:\n\n```bash\nuv tool install git-didi\n```\n\n## Usage\n\n### Common use case - checking a rebase\n\nAfter fetching and rebasing your branch onto main:\n\n```bash\ngit fetch\ngit rebase main\n```\n\nYou can verify the rebase preserved your changes:\n\n```bash\ngit-didi patch main@{1}..branch@{1} main..branch\n```\n\nThis compares:\n- **Left side**: Your changes before the rebase (`main@{1}..branch@{1}`)\n- **Right side**: Your changes after the rebase (`main..branch`)\n\nThe `@{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.\n\n### Commands\n\n#### `stat` - Compare diff stats\n\nCompare `git diff --stat` output between two refspecs:\n\n```bash\ngit-didi stat main..feature upstream/main..feature\n```\n\nShows only the files where the diff statistics differ.\n\n#### `patch` - Compare patches file-by-file\n\nCompare patches between two refspecs, showing differences for each file:\n\n```bash\ngit-didi patch main..feature upstream/main..feature\n```\n\nThis shows a \"diff of diffs\" with sophisticated coloring to distinguish:\n- Changes in the outer diff (what changed between the two versions)\n- Changes in the inner diffs (the actual patches)\n\nOptions:\n- `-U N` / `--unified N`: Set context lines (default: 3)\n- `-q` / `--quiet`: Only list files with differences\n- `-w` / `--ignore-whitespace`: Ignore whitespace changes\n- `-M[n]` / `--find-renames[=n]`: Detect renames\n- `-C[n]` / `--find-copies[=n]`: Detect copies\n- `--color {auto,always,never}`: Control colored output\n- `--pager {auto,always,never}`: Control pager usage\n\n#### `commits` - Compare commits\n\nCompare individual commits between two refspecs:\n\n```bash\ngit-didi commits main..feature upstream/main..feature\n```\n\nFirst verifies that commits correspond (same count and messages), then shows per-commit differences.\n\n#### `swatches` - Display color palette\n\nShow color swatches demonstrating the diff-of-diffs coloring scheme:\n\n```bash\ngit-didi swatches --color=always\n```\n\n### Filtering by path\n\nAll commands support filtering to specific paths:\n\n```bash\ngit-didi patch main..feature upstream/main..feature -- src/\ngit-didi stat main..feature upstream/main..feature -- \"*.py\"\n```\n\n## Git Aliases\n\nYou can add these to your `~/.gitconfig` for convenient access:\n\n```ini\n[alias]\n    didi = !git-didi\n    gdds = !git-didi stat\n    gddp = !git-didi patch\n    gddc = !git-didi commits\n```\n\nThen use:\n\n```bash\ngit didi patch main@{1}..branch@{1} main..branch\ngit gddp main..feature upstream/main..feature\n```\n\n## How it works\n\nThe 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.\n\nWhen comparing patches, it uses a sophisticated 256-color palette to make nested diffs easy to read:\n- Bright backgrounds for added/removed lines within the outer diff\n- Dark backgrounds for context lines\n- Mixed colors for lines that changed type (+ to - or vice versa)\n\n## Try it yourself - Test Scenarios\n\nThis repo includes test branches demonstrating various rebase/merge scenarios. Try:\n\n```bash\n# Clone this repo\ngit clone https://github.com/ryan-williams/git-didi.git\ncd git-didi\n\n# Example: Clean rebase with disjoint changes\n# Alice adds priority field, Bob adds JSON format - no conflicts!\ngit-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice \\\n               tests/01-clean-disjoint/bob..tests/01-clean-disjoint/alice-rebased-on-bob\n# Output: \"No differences in patches\" \u2713\n\n# See what each developer changed\ngit log --oneline tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice\ngit log --oneline tests/01-clean-disjoint/base..tests/01-clean-disjoint/bob\n\n# Try the other direction\ngit-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/bob \\\n               tests/01-clean-disjoint/alice..tests/01-clean-disjoint/bob-rebased-on-alice\n\n# Or verify a merge commit preserved both changes\ngit-didi patch tests/01-clean-disjoint/base..tests/01-clean-disjoint/alice \\\n               tests/01-clean-disjoint/merged^1..tests/01-clean-disjoint/merged\n```\n\nSee [Test Strategy](docs/test-strategy.md) for details on the test scenarios and how to generate more.\n\n## Development\n\n```bash\n# Clone and setup\ngit clone https://github.com/ryan-williams/git-didi.git\ncd git-didi\n\n# Setup with uv\nuv sync --extra test\n\n# Run tests\npytest tests/ -v\n\n# Generate additional test scenarios\n./scripts/generate-test-scenario.py 01-clean-disjoint\n\n# Install locally\nuv pip install -e .\n```\n\n## License\n\nMIT License - see [LICENSE] for details.\n\n[LICENSE]: LICENSE\n",
    "bugtrack_url": null,
    "license": "MIT",
    "summary": "Compare diffs between two git ranges - a 'diff of diffs' tool for verifying rebases and tracking changes",
    "version": "0.1.0",
    "project_urls": {
        "Issues": "https://github.com/runsascoded/git-didi/issues",
        "Repository": "https://github.com/runsascoded/git-didi"
    },
    "split_keywords": [
        "git",
        " diff",
        " rebase",
        " cli"
    ],
    "urls": [
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "9fdbaf247b2c7c0852e8b21066d010776c2c33442129208d7ac1e636836ccad9",
                "md5": "f7d685751d76d8b45bfddc79abbeff88",
                "sha256": "4314a0706ba8545acbf5e6f9c6fcc17e3f3f85832e40c1b2ec916d6ec9fe4f7e"
            },
            "downloads": -1,
            "filename": "git_didi-0.1.0-py3-none-any.whl",
            "has_sig": false,
            "md5_digest": "f7d685751d76d8b45bfddc79abbeff88",
            "packagetype": "bdist_wheel",
            "python_version": "py3",
            "requires_python": ">=3.10",
            "size": 14984,
            "upload_time": "2025-11-08T18:19:59",
            "upload_time_iso_8601": "2025-11-08T18:19:59.362209Z",
            "url": "https://files.pythonhosted.org/packages/9f/db/af247b2c7c0852e8b21066d010776c2c33442129208d7ac1e636836ccad9/git_didi-0.1.0-py3-none-any.whl",
            "yanked": false,
            "yanked_reason": null
        },
        {
            "comment_text": null,
            "digests": {
                "blake2b_256": "c897c47c1cc913f191fefcdbea570f46001d9f3ec4f29e925cd06a5606b7f761",
                "md5": "61407cc7e7c34d18f3fa46c13ad0025f",
                "sha256": "c965f8682a166efd4676b250c905125bdfa164a1323a458a49f3ad0bb4d5777c"
            },
            "downloads": -1,
            "filename": "git_didi-0.1.0.tar.gz",
            "has_sig": false,
            "md5_digest": "61407cc7e7c34d18f3fa46c13ad0025f",
            "packagetype": "sdist",
            "python_version": "source",
            "requires_python": ">=3.10",
            "size": 16117,
            "upload_time": "2025-11-08T18:20:00",
            "upload_time_iso_8601": "2025-11-08T18:20:00.922490Z",
            "url": "https://files.pythonhosted.org/packages/c8/97/c47c1cc913f191fefcdbea570f46001d9f3ec4f29e925cd06a5606b7f761/git_didi-0.1.0.tar.gz",
            "yanked": false,
            "yanked_reason": null
        }
    ],
    "upload_time": "2025-11-08 18:20:00",
    "github": true,
    "gitlab": false,
    "bitbucket": false,
    "codeberg": false,
    "github_user": "runsascoded",
    "github_project": "git-didi",
    "travis_ci": false,
    "coveralls": false,
    "github_actions": true,
    "lcname": "git-didi"
}
        
Elapsed time: 1.17791s