質問

So I've run into an issue with git where when I rebase or cherry pick a specific commit, I will get much more information than is in that specific commit. That issue is outlined really nicely in this question here:

Am I misunderstanding git cherry-pick?

And that is all well and good, I understand that it is picking up history because the commit I want depends on that history being there. What I don't get is how to get around it. I really don't want cherry pick to apply all of that history, what I would much rather is for it to use that history to figure out where the lines should go and then raise a conflict saying that it had to do something fishy in order to make it fit.

Is there any way for me to do this? The only way I have found is I can make a patch out of that commit and apply the patch, but I really want to be able to use cherry-pick and rebase for my trees.

To illustrate my example, you can iniitalize a git repository in an empty directory then add a text file with 1 line to your repository. Then add 3 more lines to the file in 3 seperate commits, so you will have 4 commits and 4 lines in your file. My file lookes like:

line 1
line 2
line 3
line 4

Now do git checkout HEAD~3 so that you have only

line 1

Then call git cherry-pick HEAD and you will get a conflict. Why this happens is outlined in the link I posted above. Your file will look like this:

line 1
<<<<<<< HEAD
=======
line 2
line 3
line 4

>>>>>>> 6fe905b... Commit 4

But this is bad because if this comes up in a real example, your merge conflict is saying that Commit 4 had Much more stuff than was actually in Commit 4. So is there any way for me to fix it so that cherry-pick will give me a more logical merge conflict? In my real world issue where this is coming up, it's occuring when I am doing a rebase. If I can fix this for cherry-pick, my logic is that the underlying cause for this should be fixable for rebase as well.

役に立ちましたか?

解決

[Note: edited to add diff3, re the latest edit to the question; see bottom-most section]

The cherry-pick command takes, as its arguments, a list of commit IDs:

git cherry-pick [--edit] [-n] [-m parent-number] [-s] [-x] [--ff] <commit>...

But you say you are using:

git cherry-pick branch-name commit-hash

That means you're asking git to pick two cherries: the one associated with the tip of the named branch (the SHA-1 ID to which branch-name resolves), and the one associated with the given commit-hash.

To put it another way, run this first:

git rev-list --no-walk branch-name commit-hash

The output from rev-list is what cherry-pick "sees", in effect.


git rebase does something rather more extensive than git cherry-pick. Typically, people run this to move a "branch arm" (this is a term I just made up) from one point to another:

A - B - C - D - E   <-- origin/master
          \
            K - L   <-- local

This kind of situation arises all the time when you make a local branch to do some work (commits K and L on local), and meanwhile someone else does their own work (commits D and E on master) and you decide you want to "re-base your local branch" on top of their work, so you do something like:

git fetch

(then you look around and see commits D and E, so you want to bring your local master up to date):

git checkout master
git merge --ff-only origin/master

(local master now up to date, now you decide to rebase branch local):

git checkout local
git rebase master

Rebase is basically an automated "repeatedly cherry-pick until done, then change branch labels" operation. Given the commit graph I drew above, it starts by doing a detached-HEAD git checkout to get to commit E, then it does a git cherry-pick of commit K, repeats for commit L, and (if all goes well) finally adjusts the local branch label local to point to the last of the new cherry-picked commits:

[start]

A - B - C - D - E       <-- master, origin/master
          \
            K - L       <-- HEAD=local

[step 1, get HEAD set directly to E and then cherry-pick a copy of K, giving:]

A - B - C - D - E       <-- master, origin/master
         \       \
          \       K'    <-- HEAD [detached]
           \
            K - L       <-- local

[step 2, cherry-pick a copy of L, giving:]

A - B - C - D - E       <-- master, origin/master
         \       \
          \       K'-L' <-- HEAD [detached]
           \
            K - L       <-- local

[done with cherry pick sequence, move label, giving:]

A - B - C - D - E       <-- master, origin/master
         \       \
          \       K'-L' <-- HEAD=local
           \
            K - L       <-- [no label, abandoned]

The repeated cherry-pick sequence will stop (with a merge conflict) if needed, but if so, it leaves files in the .git directory to remember what it was doing and how far along it had gotten, so that you can run git rebase --continue after you fix the problem.

Essentially, the two big differences between cherry-pick and rebase are:

  • cherry-pick uses --no-walk when asking git rev-list for the commits to pick up; rebase walks commits (though rebase shuffles arguments around so you can't just run git rev-list to see them in advance this way).
  • cherry-pick never moves branch labels; rebase does when done.

The new example in the question for illustrating the diff issue is slightly broken: when you git checkout HEAD~3 and then try to git cherry-pick HEAD, HEAD will refer to the commit whose ID is master~3 at that point. But cherry-pick master will do the trick. Here's an example where I set merge.conflictstyle to diff3 so that you can see the specifics of what cherry-pick is applying:

$ echo 'line 1' > file && git add file && git commit -m 'add file with 1 line'
[master 72f46c5] add file with 1 line
 1 file changed, 1 insertion(+)
 create mode 100644 file
$ echo 'line 2' >> file && git add file && git commit -m 'add line 2' 
[master 134cdd1] add line 2
 1 file changed, 1 insertion(+)
$ echo 'line 3' >> file && git add file && git commit -m 'add line 3'
[master e8634fc] add line 3
 1 file changed, 1 insertion(+)
$ echo 'line 4' >> file && git add file && git commit -m 'add line 4'
[master 50810b6] add line 4
 1 file changed, 1 insertion(+)
$ git checkout master~3
...
HEAD is now at 72f46c5... add file with 1 line
$ git config merge.conflictstyle diff3
$ git cherry-pick master
error: could not apply 50810b6... add line 4
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add <paths>' or 'git rm <paths>'
hint: and commit the result with 'git commit'
$ cat file
line 1
<<<<<<< HEAD
||||||| parent of 50810b6... add line 4
line 2
line 3
=======
line 2
line 3
line 4
>>>>>>> 50810b6... add line 4

Some like the diff3 style enough to git config --global (I'm still considering this myself :-) ).

他のヒント

The respective results of running git rebase and git cherry-pick are not typically the same.

Running git rebase typically rewrites history and can appear to move entire branches around. This is likely the source of the undesired history you are seeing.

In contrast, git cherry-pick replays the delta from one or more commits elsewhere in a repository’s history. The typical use case is cherry picking a single commit to create another new commit. I would need to know more specific information about the commands you are running to see how you might be frustrated with the results.

A useful command for condensing a multi–commit topic branch into a single commit is git merge --squash. The alternative is a rebase followed by another interactive rebase.

ライセンス: CC-BY-SA帰属
所属していません StackOverflow
scroll top