ChatGPT解决这个技术问题 Extra ChatGPT

git: merge two branches: what direction?

We have the following situation:

             A --- B --- C --- ... --- iphone
           /
  ... --- last-working --- ... --- master

Between last-working and iPhone, 32 commits were made. Between last-working and master, a lot of commits were made.

What I want now is a new branch where I have iphone and current master merged together. And at some later time, this should be merged into the master.

First, I planned to do:

git checkout iphone -b iphone31
git merge master

But then I thought, if it would be better to do:

git checkout master -b iphone31
git merge iphone

Now I am wondering. What would be the difference in the result? Would the merge behave different?

I already tried both and as I have expected, I got many conflicts because iphone is really old compared to master. Now I wonder about the easiest way to merge them.

Maybe even starting with master and merging each single commit of iphone into it would be easier? Like doing this:

git checkout master -b iphone31
git merge A
git merge B
git merge C
...
git merge iphone

At the very end, when this merge is done (i.e. all conflicts are resolved and it is working), I want to do this:

git checkout master
git merge iphone31
related (at least to me): stackoverflow.com/questions/1241720/…
is git checkout -b iphone31 equivalent to git checkout iphone -b iphone31?
git log --first-parent will behave differently

h
hlovdal

Regarding the alternatives

git checkout iphone -b iphone31
git merge master

and

git checkout master -b iphone31
git merge iphone

they will have identical ease or difficulty, it is like arguing whether a glass is half full or half empty.

Version tree perception

How we look at version trees are in some way just our arbitrary perception. Let's say that we have a version tree like the following:

    A----------+
    |          |
    |          |
   \_/        \_/
    B          X
    |          |
    |          |
   \_/        \_/
    C          Y
    |
    |
   \_/
    D
    |
    |
   \_/
    E

And let's say that we want to make a new version Z checked out from Y based on the changes from C to E but not including the changes made from A to C.

"Oh, that will be difficult because there is no common starting point." Well not really. If we just place the objects a little differently in the graphical layout like this

      /
    C+---------+
    | \        |
    |          |
   \_/         |
    D          B
    |         / \
    |          |
   \_/         |
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

now things are starting to look promising. Notice that I have not changed any relation ships here, the arrows all point the same way as in the previous picture and version A is still the common base. Only the layout is changed.

But it now trivial to imagine a different tree

    C'---------+
    |          |
    |          |
   \_/        \_/
    D          B'
    |          |
    |          |
   \_/        \_/
    E          A
               |
               |
              \_/
               X
               |
               |
              \_/
               Y

where the task would just be to merge version E normally.

So you can merge anything you want, the only thing that influence the ease or difficulty is the aggregate of changes done between where you select as a starting point or common base and where you merge to. You are not limited to using the natural starting point the your versioning tool suggest.

This might not be simple with some version control systems/tools, but if all else fails there is nothing that stops you from doing this manually by checking out version C and save the file as file1, checking out version E and save the file as file2, checking out version Y and save the file as file3, and run kdiff3 -o merge_result file1 file2 file3.

Answer

Now for your specific situation it is difficult to say exactly what strategy that will produce the least amount of problems, but if there are many changes that create some kind of conflict it probably is easier to split up and merge smaller parts.

My suggestion would be that since there are 32 commits between last-working and iphone, you could for instance start by branching of master and then merge in the first 16 commits. If that turns out to be too much trouble, revert and try to merge the 8 first commits. And so on. In worst case you end up merging each of the 32 commits one by one, but it would probably be easier than having to handle all the accumulated conflicts in one single merge operation (and in that case you are working with a really diverging code base).

Tips:

Draw on paper a version tree and note with arrows what you want to merge. Cross off things as they are done if you split up the process in several steps. This will give you a clearer picture of what you want to achieve, what you have done so far and what is left.

I can really recommend KDiff3, it is an excellent diff/merge tool.


"And let's say that we want to make a new version Z checked out from Y based on the changes from C to E but not including the changes made from A to C." I don't want to exclude any commits. I don't really see how that is related to what I want. I just want to merge master with iphone.
"My suggestion would be that since there are 32 commits between last-working and iphone, you could for instance start by branching of master and then merge in the first 16 commits." Would that be easier? Wouldn't I have to resolve the same conflicts again and again then for each commit? Like I have added a single line in one commit and another line right after it in another commit. Now handling these both together would be easier than doing it twice, wouldn't it?
"they will have identical ease or difficulty," That exactly was my question. So both ways I have suggested in the beginning will produce exactly the same results? So Git merging is 100% symmetric?
Just to quote again my main question: "Now I am wondering. What would be the difference in the result? Would the merge behave different?"
"I don't want to exclude any commits." Yes, I understand that and that part of my answer was more general than strictly needed, sorry for not making that clearer. My point is that you can merge anything you want, it is just a matter of pointing out a start/end point.
R
Ryuu

There is a difference.

Always check out the target branch during a merge (i.e. if merging A into B, checkout B).

Folks often say that the merge direction does not matter, but this is wrong. While the resulting content will be the same regardless of the merge direction, there are several aspects that are different:

The diffs listed in the resulting merge-commit will be different depending on the direction. Most branch visualizers will decide which branch is "primary" using the merge direction.

To elaborate, imagine this exaggerated example:

You branched off from MASTER at 1000 commits behind, and named it DEVELOP (or in a tracking-branch scenario, you have not fetch for quite some time).

You add one commit into DEVELOP. You know there are no conflicts for this change.

You want to push your changes into MASTER.

You incorrectly merge MASTER into DEVELOP (i.e. DEVELOP is checked out during the merge). Then, you push DEVELOP as the new MASTER.

The diffs in the resulting merge-commit will show all 1000 commits that happened in MASTER, because DEVELOP is the reference point.

Not only is this data useless, it's hard to read what's going on. Most visualizer will make it look like your DEVELOP line was the primary all along, with 1000 commits brought into it.

My suggestion is this: Always check out the target branch during a merge (i.e. if merging A into B, checkout B).

If you are working on a parallel branch and want to periodically bring in changes from a main branch, then checkout your parallel branch. The diffs will make sense -- you will see the changes done in the main branch w.r.t to your branch.

When you are done working in parallel and wish to merge your changes into the main branch, checkout the main branch and merge with your parallel branch. The diff again will make sense -- it will show what your parallel changes are w.r.t to the main branch.

The readability of the log, in my opinion, matters.


What you are talking about is the concept of --first-parent in git, which will behave differently when you show log graph with first parent. I also made a sample repository for it github.com/ChuckGitMerge/FirstParentTest
s
seanhodges

The best approach really depends on whether other people have remote copies of your code. If the master branch is only on your local machine, you can use the rebase command to interactively apply the commits from the feature branch into master:

git checkout master -b iphone-merge-branch
git rebase -i iphone

Note that this alters the commit history of your new iphone-merge-branch branch, which may cause problems for anyone else trying to pull your changes into their checkout later on. By contrast, the merge command applies the changes as a new commit, which is safer when collaborating because it doesn't affect the branch history. See this article for some useful tips on using rebase.

If you need to keep your commit history in sync, you are better off performing a merge. You can use git mergetool to interactively fix conflicts one-by-one using a visual diff tool (a tutorial on this can be found here):

git checkout master -b iphone-merge-branch
git merge iphone
git mergetool -t kdiff3

A third option, if you want absolute control over the process, would be to use git cherry-pick. You can use gitk (or your favourite history viewer) to view the commit hashes in the iphone branch, note them down, and cherry pick them individually into the merging branch - fixing conflicts as you go. An explanation of this process can be found here. This process will be the slowest, but might be the best fall-back option if the other methods do not work out:

gitk iphone
<note down the 35 commit SHA hashes in this branch>
git checkout master -b iphone-merge-branch
git cherry-pick b50788b
git cherry-pick g614590
...

What advantage would that rebase have over the merge? I don't quite understand. Why would the git rebase be more intelligent over the git merge? But anyway, rebasing here wouldn't be an option.
Rebasing applies the commits individually into your branch in chronological order, this significantly reduces conflicts by inserting the commits at the point in history that they fit. Considering that rebasing is not an option for you, I suggest you investigate the git-mergetool approach.
But I could do the same also, merging each commit individually. What would be the difference to rebase then?
When you merge, you are applying each commit on top of HEAD, which has since diverged from the other branch - causing conflicts. When you rebase you effectively rewind time, and apply (not merge) the commits at the position that they were made. For example, a commit in "iphone" made on 12 Jan, 2010 would be applied before a commit made on 14 Feb, 2010 in the "master" branch.
No, the result is very VERY different. The only thing that will be the same is your final working copy. I recommend you read the link I mentioned in the solution for some background on the rebase command: gitready.com/intermediate/2009/01/31/intro-to-rebase.html
G
Gordon

You say:

iphone branch is very old compared to master

Do you really want to merge them both forming a new branch?

The purpose of master and iphone branches would now have been very different ( because iphone is very old ). A new branch merging iphone with an ancestor of master would be better? Think about it.

I highly recommend that you read Fun with merges and purposes of branches.

After reading that article if you still feel you want to merge iphone and master then @seanhodges explains how to handle the conflicts really well.


What do you mean with "the purpose"? Really, what I want is to have the changes I made in iphone to be in master. When I started that iphone branch, it was to add iPhone support. That is finished and works fine, so I want to have it in the master branch now. My question was if there is any difference in the two approaches I have suggested. Or if there are easier ways to get what I want.
That link starts insightfully: "When merging branches in git, all branches are taken as equals." That's often the issue, isn't it? Though see where the link suggests doing the opposite of what you suggest: If you started working on... 'frotz' back when the 'master' release was 1.0, and later the codebase of 'master' has substantially changed and [branch 'add-frotz' doesn't] cleanly merge anymore into 'master'... you could choose to change the purpose of your 'add-frotz'... to a more specific "add frotz feature in a way that it merges cleanly to the 2.0 codebase" [& merge with master first].
This is not answering the OP's questions of "is there a difference". This answer is more of a "why not do this instead?". And yes, sometimes we do want to merge something that is very far behind. For example, merging gitignore file changes.
D
Doug Chamberlain

You may want to make a local backup of your work before experimenting, so you can restart, if you stop understanding what has happened.

Make a backup branch of your work so not lose it during rebase, if you want to keep it for reference.

git checkout iphone -b iphone_backup

Create a new branch from master

git checkout master -b iphone31

then rebase on top of it.

git rebase -i --onto iphone31 iphone

Sean above is correct about rebase applying one commit at a time. But don't worry about existing commits on the branch you rebase onto - they will not be changed. Rebase puts new (adapted) commits on top of them. While done one commit at a time you have better control over conflicts. What is important though, you must rebase your work on top of the master and not vice versa, so the history of master won't change.

Alternatively, you can do only

git rebase -i master iphone

if you don't care about backing up the iphone branch. Look into rebase documentation for details and alternatives http://git-scm.com/docs/git-rebase.