Welcome to Software Development on Codidact!
Will you help us build our independent community of developers helping developers? We're small and trying to grow. We welcome questions about all aspects of software development, from design to code to QA and more. Got questions? Got answers? Got code you'd like someone to review? Please join us.
What is the general process for merging two git branches, reviewing edits on each branch?
Suppose you have a largish git repository with many files of different types (both text and images) distributed among a number of nested directories. Parallel development has occurred on two branches, each editing, creating, and removing files at various points within the directory tree.
Now you want to merge the two branches, reviewing each of the edits to ensure that the correct information is preserved to maintain the desired content and functionality from each of the two branches.
What is the process for doing this?
2 answers
For completeness, I'll add some more obscure ways:
If your goal is to simply move some changes from one branch to another, you can also use git rebase
and git cherry-pick
. These give you more control over what exactly you are moving. For example, git rebase
has an interactive mode where you can pick and choose exactly which commits you want and also tweak them (message, squash).
I would say Michael's answer (with git merge
) is the normal and better way to do it. It makes git take care of a lot of the tedious steps by automatically resolving trivial merges without bothering you for each one. Usually, it's easier to keep your branches from diverging too much, make minimal/atomic commits, and follow other git best practices and then just trust the algorithm of git merge
.
But if you really do want the nitty gritty, perhaps something like rebase or cherry pick is more suited for your usage.
One notable caveat: When you use git merge
, git will record the merge (by creating a special "merge commit"), so that when visualize the history with various tools the branches will actually come together. If you use things like rebase or cherry-pick, this creates new ordinary commits and the link is somewhat broken. It will not be clear just from looking at the branch diagram that the branch has been partly combined into another, you will have to rely on commit messages and descriptions to indicate that, which are not always well understood by tools.
These days, many Git hosts like Github have taken to implement "pull request" workflows where you can merge via the interface and choose to squash or rebase. The git host will produce the illusion that the history is not broken in contradiction of my previous paragraph. But what's actually happening is that the Web UI and the host's internal database (eg. Postgres) is acting as a layer on top of git that provides extra features like understanding squash/rebases. When you clone the repo and try to look at it without the git host, you will notice that none of the pull request information is working properly, and if you simply push it to a new host, you will discover that the PRs must be migrated separately from the git data.
This "breakage" for rebase/squash commits is arguably a limitation of git, which the utilities (Git hosts) have attempted to solve by adding their own bandaids on top. It was never part of Git as it was originally designed (no central server, and people just email patch to each other). There are other VCS that try to track such things as well, I think Fossil is one. But now we are going on quite a tangent, and if you want to learn more I think you should ask separate questions for it.
There are two major sections to this answer: the Git part and the conflict resolution part. It wasn't clear at first which one was intended by the question, but both are important for a full answer.
Git workflow
Workflow summary
- Make a temporary branch in case things go badly.
- Start the merge.
- Resolve conflicts.
- Iterate bug fixes until it works.
- Consolidate the merge.
- Merge back the changes from the temporary branch to your primary.
For a simple merge, you can get by with 2, 3, and 6. But if this is as big a merge as you imply, the extra steps can be really helpful.
Workflow details
-
Make an
integration
branch from your "primary" (typicallymain
ormaster
) branch.git switch -c integration
-
Merge your other branch into
integration
.git merge other-branch
This will probably result in merge conflicts if what you describe is true. If it doesn't result in merge conflicts, the merge will finish right here and you can skip to Step 2.3.
-
Deal with the merge conflicts in an appropriate merge tool.
git mergetool
- This is probably the most complicated part of the process. See the dedicated section below.
- You may have to consult with the submitters of text and images on both branches to determine which files to keep.
- You may even have to write new code to rectify changes from both sides.
- You may make mistakes. That's okay! You can fix those.
-
Commit the merge.
git commit
-
Test the code yourself.
-
Push
integration
to be evaluated by your CI/CD and your testers.git push
-
-
Fix any mistakes on
integration
.Use new commits for these for now. It's lots easier to comprehend your fixes when they're separate from the huge merge.
-
Repeat Steps 2.3 to 3 until satisfied.
-
Squash all the fixup commits on
integration
.This is not strictly necessary, but anyone using Git ReReRe will appreciate that you consolidate any extra conflict resolution directly into the merge commit. You can even share rerere solutions across a team.
-
The easiest way is to use a GUI Git client to combine the fixups with your merge commit.
-
In the absence of a GUI, this is done with an interactive rebase, which can be a little intimidating with the
--rebase-merges
option:-
git rebase -i --rebase-merges 3fe8aa^
…where
3fe8aa
is the merge commit's SHA. Note the^
to get the commit before it. -
Your text editor will then present a list of commits in the format below.
label onto # Branch other-branch reset 57a270 # Merge branch 'other-branch' into integration pick a27157 So many commits from... pick f00ba2 ...the other branch here label other-branch reset onto merge -C 3fe8aa other-branch # Merge branch 'other-branch' onto integration pick ed170r Fix the build pick de1e7e Really fix the build this time pick c0ffee Fix the backend pick dec0de Fix the UI
-
Change all the
pick
lines in the bottom section to sayfixup
[1] instead ofpick
to fold them into themerge
commit. There are instructions at the bottom of the file in a big comment, in case you forget.
-
-
-
Test again, just to be sure.
-
Merge
integration
into your primary branch.git switch main git merge integration
Merge conflict resolution
Congratulations! You've found one of the hardest parts of SCM. Git's pretty good at slotting in new lines or taking things out when a merge occurs. But sometimes the additions or subtractions occur in the same location in a file, and Git needs human intervention.
Types of conflict
- Two branches changed the same part (or parts) of a file.
- One branch edited a file, but the other branch moved or deleted that file.
- Two branches independently added a file to the same place.
This is not quite a comprehensive list, but it covers most cases. To see all the possibilities, see the "unmerged" lines in the git status --short
documentation's XY chart.
Conflict markers
In text files with conflicts, Git will add conflict markers to designate the pre-existing conditions. These are usually paired <<<<<<<
and >>>>>>>
separated by a =======
. There is optionally a |||||||
section showing the last shared content of that section. These are labeled with the source branch or commit.
You'll see these if you open a text file in your editor that contains conflicts before that file has been resolved. Most of the time, you're using a tool that reconstructs the full file from each side of the merge and you don't see the markers, but it's worth knowing what they are in case you encounter them.
Sometimes with simple conflicts, you can identify what ought to be in a conflicted section and fix it directly with a text editor in a pinch.
Merge tools
You may have noticed that instructions on how to actually deal with conflicts is pretty sparse in Git's documentation. That's because it pawns off conflict resolution onto "mergetools," and which one is right for you or right for a certain situation is a matter of preference.
These are sometimes terminal-based programs like vimdiff
,[2] or GUI programs like Kdiff3, Meld, Kaleidoscope, WinMerge, Sourcetree, Sublime Merge, and on and on. Git's documentation has a list of already-configured mergetools, though you may or may not have any particular one installed.
Git's documentation isn't going to comprehensively cover how these work or how to use them (and neither can I), but in general they have some common components. You run git mergetool
, and the one configured in Git config will spawn. To pick a different one, use git mergetool --tool=winmerge
or whatever. You could conceivably use one for text conflicts and another for binary conflicts.
Merge tool layout
(Optionally) List of files to be resolved
If you're working your way through a whole set of files, there may be a way to select the next one for handling.
File contents on each branch
On one side, you're going to see the code from one branch. On the other side, you're going to see the code from the other branch. The sides ("ours" and "theirs") change depending on whether you're doing a merge or a rebase, so look for the descriptive labels to know which side contains which branch.
For each set of conflicted lines, there will be a way to select the lines from the left or the right (maybe both or neither as well), to add to the output.
A mergetool may or may not be able to handle and/or render non-text files.
(Optionally) The merge base
You may have a mergetool that also shows the original file from before it diverged, for context, in case that is helpful.
The editor pane
This is where you can actually edit the outcome of the merge. If the left/right selection is insufficient for what the file should actually look like, you can tweak it directly.
Merge tool use
-
You're going to go through each text file and decide each conflicted section.
If the other file was moved, your mergetool may be smart enough to ask you about conflicts against the file in its moved location. Or it may not.
-
For images and binary files, you're basically going to choose which whole file is appropriate.
As you "handle" each file in the mergetool, it will report back to Git what the content should be for that file. Each file has to be handled in its entirety. The mergetool reporting has a pass/fail component, but you can "fail" it and come back to (re)fix it after working on some others.
For any files that can't be handled by the mergetool(s), you need to get the files into the shape you desire and git add
them to the staging area of the merge commit.
Tips
-
Don't accidentally create empty files. Sometimes a mergetool will incorrectly recreate a "deleted" file with no content. Make sure those are really deleted.
-
Whitespace differences, particularly line endings, can make merging especially difficult. It can wildly expand the "different" lines. There are options when doing a
git merge
that can alter treatment of whitespace, which may collapse the divergent sections to their appropriate locations. -
Git ReReRe ("reuse recorded resolutions") is disabled by default. You may want to turn it on to record pre-image and outcome for merge resolutions. Then if it encounters those exact situations again, the conflict can be resolved automatically in the same way.
-
You also have some control over the conflict marker sections. I personally use
zdiff3
as mymerge.conflictstyle
.
1 comment thread