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?

+3


source to share


2 answers


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.

enter image description here

(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:

enter image description here



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,

enter image description here

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:

enter image description here


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.

+1


source


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.

+1


source







All Articles