How to change snapshot (commit) in linear history without conflict related errors?
During interactive rewrite, you may receive an error like this:
% git rebase --continue
error: could not apply be3679b... shrink the kids
When you have resolved this problem, run "git rebase --continue".
If you prefer to skip this patch, run "git rebase --skip" instead.
To check out the original branch and stop rebasing, run "git rebase --abort".
Could not apply be3679bd31a31bd3869e17a66dc0b1b908282d94... shrink the kids
The verb "apply" (in could not apply be3679b...
) explicitly implies that it git
processes as patches. This clearly contradicts the often made claim that it is git
recording "snapshots, not differences" (since a patch is simply a difference between two states).
For example, suppose I have the following story on master
:
HEAD
...---A-----B-----C-----D
master
... and I ran git rebase -i A
. Suppose I choose to edit B
, nothing else. I believe that even if the changes made to B
are not related to deleting or adding files, I can still get errors like the one described above ("cannot be applied ...").
To illustrate why these errors don't make sense to me, here's a more time-consuming but (extremely) conflict-free way to get the result I was trying to achieve with the interactive permutation above:
% git branch grmbl A
% git checkout grmbl
% git checkout B -- .
% # modify files in working directory to my heart content
% git commit -a -m foo
% git checkout C -- .
% git commit -m bar
% git checkout D -- .
% git commit -m baz
(To make this equivalent to the original git rebase -i
, let's assume the commit messages I used were the original commit messages for B
, C
and D
.)
There git diff grmbl master
will be no difference at this point . This is just as I would like, since all I wanted to do was change one shot along the sequence of shots, leaving all the other shots unchanged.
The key point is that the above sequence does what I want without git
reporting any conflict resolution errors. Instead of "applying" the commit (as if it were a patch) to the current WD, I simply check the content of that post (with git checkout <commit> -- .
). The fact that this does not lead to conflicts makes the error message git
during a seemingly equivalent interactive redirect operation all the more difficult to understand.
Is there a less time consuming but still conflict-free way to achieve the effect achieved by the above sequence?
source to share
The verb "apply" (in
could not apply be3679b...
) explicitly implies that git treats commits as patches. This clearly contradicts the often made claim that git writes "snapshots, not differences" (since a patch is just a difference between two states).
I agree that the phrase
could not apply <commit>
confuse. What you are applying is indeed a patch that matches the difference between <commit>
and her parent (or more generally one of his parents). Therefore, the following message will be more accurate and possibly make sense:
could not apply patch from <commit-A> to <commit-B>
However, this is simply an abuse of the language, which becomes harmless as you get used to it. In English, you can say things like
Bob drank the entire bottle of wine on his own!
and everyone will report that you are reporting Bob's wine consumption without claiming that he himself swallowed the container (bottle).
You have not been lied to; git does write snapshots / revisions / commits / states. Differences / fixes are generated only when committing; for example when using git diff
or git cherry-pick
.
[...] these errors don't make any sense to me [...] The fact that it doesn't lead to conflicts makes the git error message during a seemingly equivalent interactive redirect operation all the more difficult to understand.
To explain why the interactive permutation approach causes conflicts when your approach does not, consider a simple example: Let's say that the only tracked file in your repo is a file named README
, and that history looks like this.
(The sheet of paper above / below the commit represents the contents of the file README
as written in the linked commit.)
Your conflict-free approach
If you run
git branch grmbl A
git checkout grmbl
git checkout B -- .
# replace 'foo' by 'FOO'
printf "FOO\n" > README
git commit -a -m foo
git checkout C -- .
git commit -m bar
git checkout D -- .
git commit -m baz
you get this:
Note that the version written to B
and pulled from your working tree at startup time
git checkout C -- .
is simply overwritten by the version written in commit C
. There is no patch here; only destructive check is involved; therefore there is no conflict.
Also, as you rightly pointed out, in this case
git diff grmbl master
really nothing to return, because those branches master
and grmbl
indicate commits ( D
and D'
, respectively), which recorded an identical version of the file README
.
Interactive permutation approach
What happens at startup
git rebase -i A
is very different as it involves patching. Everything is unrecognizable so far,
i.e. right after manually replacing "foo" with "FOO" and creating a commit B'
.
After that, however, things go straight up: interactively try to reinstall in make-pick commit C
(in other words, apply the patch B -> C
) on top of the commit B'
, but it doesn't find the "bindings." More specifically, it tries to add a line bar
right after the line foo
, but of course the latter is nowhere to be found, because at this stage only the checked version of the file README
contains the string foo
. Therefore, a conflict arises:
So...
How to change snapshot (commit) in linear history without conflict related errors?
Well, even if interactive rebase can lead to conflicts, it is preferable because it is more reliable and automated. Your approach may be conflict-free, but it is also tedious (all these manual checks!) And error-prone.
source to share
As Jubobs said, git keeps history in snapshot format. However, when you recreate a commit using the command git rebase
, it uses diff / apply to recreate the commits.
If you really want to do what you suggest, it's pretty simple, but requires you to write your own script to replace the rebase.
So what is currently going on (simplified) is this rebase generates an ORIG_SHA list in its todo list and then calls git diff SHA^1
for each SHA and tries to apply that diff. You don't want this behavior. You just want to invoke the compile tree with ORIG_SHA ^ {tree}. You can do this by manually creating a commit, or by making a git read-tree
source tree and dropping the existing index.
source to share