ChatGPT解决这个技术问题 Extra ChatGPT

How does git merge after cherry-pick work?

Let's imagine that we have a master branch.

Then we create a newbranch

git checkout -b newbranch

and make two new commits to newbranch: commit1 and commit2

Then we switch to master and make cherry-pick

git checkout master
git cherry-pick hash_of_commit1

Looking into gitk we see that commit1 and its cherry-picked version have different hashes, so technically they are two different commits.

Finally we merge newbranch into master:

git merge newbranch

and see that these two commits with different hashes were merged without problems although they imply that the same changes should be applied twice, so one of them should fail.

Does git really do a smart analysis of commit's content while merging and decide that changes shouldn't be applied twice or these commits are marked internally as linked together?


P
Paul

Short answer

Don't worry, Git will handle it.

Long answer

Unlike e.g. SVN1, Git does not store commits in delta format, but is snapshot-based2,3. While SVN would naively try to apply each merged commit as a patch (and fail, for the exact reason you described), Git is generally able to handle this scenario.

When merging, Git will try to combine the snapshots of both HEAD commits into a new snapshot. If a portion of code or a file is identical in both snapshots (i.e. because a commit was already cherry-picked), Git won't touch it.

Sources

1 Skip-Deltas in Subversion
2 Git Basics
3 The Git object model


Actually, I'd say you should worry about merging and "git will handle it" is not a good rule of thumb.
In fact the merge CAN result in duplicate content in some cases. Git does handle it sometimes, but sometimes it does not.
This is horribly wrong. Got pretty much stores the file files in all condevable forms. And if I recall correctly SVN used to store snapshots.
@he_the_great, no. SVN's skip-delta storage format (!= snapshots) is well documented in the manual. And I really don't get what you mean by condevable. I'm not a native speaker, but I'm pretty sure that's not a real word.
@he_the_great But even as packfiles any given hash for a file results in the full file. Yes it compresses using deltas, but it is not a delta for the changes in a commit, but instead a delta between hashes for a file. As far as the commit object is concerned it is referencing a tree that is referencing the hash for a full file. That under the hood the data is compressed does not effect the way git works. Git stores complete files as far as a commit is concerned, SVN stores deltas for commits as I understand.
e
ephemerr

After such merge you may have cherry-picked commits in history twice.

Solution to prevent this I quote from article which recommends for branches with duplicate(cherry-picked) commits use rebase before merge:

git merge after git cherry-pick: avoiding duplicate commits Imagine we have the master branch and a branch b: o---X <-- master \ b1---b2---b3---b4 <-- b Now we urgently need the commits b1 and b3 in master, but not the remaining commits in b. So what we do is checkout the master branch and cherry-pick commits b1 and b3: $ git checkout master $ git cherry-pick "b1's SHA" $ git cherry-pick "b3's SHA" The result would be: o---X---b1'---b3' <-- master \ b1---b2---b3---b4 <-- b Let’s say we do another commit on master and we get: o---X---b1'---b3'---Y <-- master \ b1---b2---b3---b4 <-- b If we would now merge branch b into master: $ git merge b We would get the following: o---X---b1'---b3'---Y--- M <-- master \ / b1----b2----b3----b4 <-- b That means the changes introduced by b1 and b3 would appear twice in the history. To avoid that we can rebase instead of merge: $ git rebase master b Which would yield: o---X---b1'---b3'---Y <-- master \ b2'---b4' <-- b Finally: $ git checkout master $ git merge b gives us: o---X---b1'---b3'---Y---b2'---b4' <-- master, b

EDIT Corrections supposed by David Lemon's comment


great hint about rebase! it will 'skip' all cherry-picked commits automatically.
Honestly, it sounds too good to be true, I have to see it with my eyes. Also rebase modifies commits, your last timeline should be ---Y---b2'---b4'
Works perfectly. Very helpful if you don't want to have the cherry-picked commits twice in history.
Shouldn't it be noted that while rebase is beautiful, the danger with using it is that any forks or branches created from the old b will be out of sync, and users may need to resort to stuff like git reset --hard and git push -f?
@JHH thats why we rebase local branch here