Skip to content

flipdiff: Add option to fail loudly if patches don't commute#177

Open
ralokt wants to merge 4 commits into
twaugh:masterfrom
ralokt:feature/flipdiff-err-no-commute
Open

flipdiff: Add option to fail loudly if patches don't commute#177
ralokt wants to merge 4 commits into
twaugh:masterfrom
ralokt:feature/flipdiff-err-no-commute

Conversation

@ralokt
Copy link
Copy Markdown

@ralokt ralokt commented Jun 3, 2026

Pull Request Guidelines

Have been read and applied, this is a new feature and indeed targets master.

Description

Adds a new option --err-no-commute.

If two patches don't commute because they modify the same lines, instead of putting the changes into the first patch, we error altogether.

This is useful whenever the integrity of the individual patches is in some way important, and failing loudly is preferable to possibly clobbering them.

I also have a concrete use case for this that I will put into a comment in order to keep the description brief.

Type of Change

New feature

Testing

I adapted two existing tests with non-commuting patches (flip15 and flip1) to

  • call the binary with the new option (--err-no-commute)
  • make sure that this results in an error and an empty patch

@ralokt
Copy link
Copy Markdown
Author

ralokt commented Jun 3, 2026

Long description of concrete use case

A concrete use case can be demonstrated by the following tool I wrote to swap the previous two git commits during interactive rebases:

# git-swapcommits
#!/bin/bash

set -e

if ! (git diff-files --quiet && git diff-index --quiet HEAD --) ; then
        echo "working directory is dirty, exiting!"
        exit 1
fi

hsh () {
        git show -s --pretty=%H "$1"
}

mkdiff () {
        git diff "$1~" "$1" > "$2"
}

appl () {
        git apply "$1"
        git add -u .
        git commit -C "$2" --no-verify
}

PAR=$(hsh HEAD~)
HD=$(hsh HEAD)
D1="/tmp/gsc1.diff"
D2="/tmp/gsc2.diff"

mkdiff "$PAR" "$D1"
mkdiff "$HD" "$D2"

if flipdiff --in-place --debug "$D1" "$D2" | grep -P '^@\d+: removed \(' ; then
        echo "diffs don't commute!"
        exit 2
fi

git reset --hard "$PAR~"
appl "$D2" "$HD"
appl "$D1" "$PAR"

(ignore the hardcoded tempfile paths, use of git porcelain commands, and other flaws, this is a WIP that isn't meant to be production ready - focus on the flipdiff invocation!).

This works, but only by running in debug mode and parsing the debug output. This is a massive hack, and pretty brittle, it could easily be broken if the debug output changes for some reason.

OK but why?

This is useful because when reordering commits when performing an interactive rebase, git will just take the patches and apply them in a different order - it won't actually flip them. This often leads to merge conflicts, even just the diffs for the following changes can't be flipped with stock git:

a

->

a
b

->

a
b
c

because the b line is part of the diff context for the second patch.

With this script, if I have

pick abcdef // first commit
pick 123456 // second commit

and reordering to

pick 123456 // second commit
pick abcdef // first commit

leads to a merge conflict, but the patches commute, I can instead

pick 123456 // second commit
pick abcdef // first commit
x git swapcommits

This is a bit abstract, but consider

pick abc // add test1
pick def // add test2 right after test1
pick 123 // fix test1, we want to squash this into abc

especially with tests that have identical boilerplate lines that trigger generation of diffs with multiple hunks, reordering vs swapping commits can make the difference between complex merge conflicts that are non-trivial to resolve, and an operation that just applies with zero issues.

ETA: I think the last paragraph can use some clarification as to why this is relevant: the goal here would be to swap def and 123 so that we can squash the changes introduced in 123 into abc.

@ralokt ralokt marked this pull request as ready for review June 3, 2026 09:58
@ralokt ralokt changed the title Feature/flipdiff err no commute flipdiff: Add option to fail loudly if patches don't commute Jun 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant