Back
Featured image of post Getting Used to GitHub, Eps. 3

Getting Used to GitHub, Eps. 3

Merge and Merge Conflicts

Hi again. This is Dung Dao Minh, vice president of SoICT Innovation Club, and in this third post of series “Github 101”, I will talk about some kind of scenarios that can happen when you work on multiple branches in your work repositories, or work in collaboration with a team on a project.

First words

In many cases, you (and your collaborators) create multiple branches for different purpose: creating features, bug fixing, code versioning,… Most of the issues happen when you try to combine the code between those branches: you may realize that you still commit to the main branch while fixing bugs/adding features, another developer may add a little documentation on your code (which is great), or your evil coworker might mess up with your code and make it full of bugs. In short, there may be some modification on the branch you are trying to merge into, that you need to resolve to (happily) combine the two different commits.

I will create (with additional references from other sources) some different scenarios here that represent the problem you might face when working on different branches, and make it easy to introduce some new definition, as well as some good practices when working with Git and Github

Scenario #1: Working on new feature (no file conflict yet)

Suppose that you are working on a project about real estate recommendation system, and you want to try using location information for your system. In the middle of your work, your boss want you to focus 100% of your time on improving system based on demographic information.

Step 1: assume that you are on the develop branch. You should create a new branch location for developing the new feature:

# Create and switch to new branch
git checkout -b location
# Commit as you go 
git commit -m "add recommendation based on city'

Step 2: however, you boss want you to focus on demographic features. You have not completed the location feature yet

# Switch back to the develop branch
git checkout develop
# Create and switch to `demographic` branch
git checkout -b demographic
# Working on that branch and commit changes
git commit -m "finalized demographic-based recommendation"

Step 3: after you finish your work on demographic feature, you can merge it to the develop branch and push this to the remote origin/develop branch (assuming that both develop and origin/develop have no additional commit in the mean time)

# Switch back to the develop branch
git checkout develop
# Merge the `demographic` branch into the `develop` branch
git merge --no-ff demographic
# Push to the remote repository
git push origin develop

Quick note on merge: fast-forward vs. no fast-forward

You might see the --no-ff (no fast-forward) in the step 3 above. The new demographic code seems to only add new code, without any modification.

A picture is worth a thousand words:

--no-ff will create a new commit (with new id, but the file content is the same), while by default, git merge will fast-forward - moving your branch pointer (in this case, at develop) to point at the incoming commit (from demographic).

Note: when you merge with --no-ff, as a new commit is created, you might be asked to create message for that commit.

In many cases, you want to prevent fast-forward from happening, because you want to maintain a specific branch “shape”, and someone (your manager) can read the history of changes that you made from creating the demographic branch (instead of losing information when fast-forward)

You can change you command or do some setups to merge with or without feed-forward:

# Execute for once: add the `--ff-only`, or `--no-ff` flag
# Apply for both pull and merge (pull = fetch + merge)
git pull --ff-only
git pull --no-ff
# Using git config to do it by default 
# (git pull or git merge won't need specific flag)
git config --add merge.ff false
git config --add merge.ff only

Scenario #2: Same file names, different file contents on different branches (conflict)

You might wonder what’s the purpose of the location branch created above. Now I will use it to demonstrate merge conflict.

In a bad mood, you forgot that you create that location branch for experimenting with location data, and you do some commit directly on the develop branch to work with that data. Let review some of the recent commits you made (notice the HEAD->location (current branch) at commit f3b1b57 and the develop branch at commit 9a5187c)

git log --graph --decorate --oneline --all

* 9a5187c (develop) add recommendation based on city in develop
*   1fe34c7 Merge branch 'demographic' into develop
|\  
| * 0883fd7 (demographic) finalized demographic-based recommendation
|/  
| * f3b1b57 (HEAD -> location) add recommendation based on city
|/  
* b3a463a (origin/develop) Create develop branch

For simple demonstration purpose, here are the 2 versions of location.txt on that 2 branches:

# On `develop` branch
location file in develop

# On `location` branch
location file

Some ways you might consider to resolve the conflict

When you realize the mistake, there are some cases here:

Case 1: You recognize that there are part of the features you develop from both branches would be useful, and you want to combine them together.

Run the command git merge develop will create the following message:

Auto-merging location.txt
CONFLICT (add/add): Merge conflict in location.txt
Automatic merge failed; fix conflicts and then commit the result.

This is inevitable, since the 2 files have different contents inside. Inside the location.txt file, you can see that the content has been modified. The upper part is the line of the current commit pointed by HEAD and the lower part is the

<<<<<<< HEAD
location file
=======
location file in develop
>>>>>>> develop

Check git status for additional understanding:

On branch location
You have unmerged paths.
...
Unmerged paths:
  (use "git add <file>..." to mark resolution)
        both added:      location.txt

To resolve the problem, simply modify the location.txt file as you wish (obviously, you don’t want those “«<” or “===” characters anyway) and add it to the staging area, then create a new commit. Here I will modify the text into “location file after resolve conflict”:

git add location.txt
git status
# On branch location
# All conflicts fixed but you are still merging.
# ...
git commit -m "resolve conflict in location feature"
git log --graph --decorate --oneline --all

# Now we see that the `location` branch has new commit
*   1f79c56 (location) resolve conflict in location feature
|\  
| * 9a5187c (HEAD -> develop) add recommendation based on city in develop
| *   1fe34c7 Merge branch 'demographic' into develop

Case 2: You have done a far better work in the develop branch, and you don’t need the same code on location branch anymore. Or the other way around, you soon realize the mistake, and the location branch has more valuable code

The obvious solution is to use the code in develop instead of that code on location, or the other way around. You can use the approach in Case 1, observing that in some IDE, they support you with choosing the respective version you want. An example in VSCode (you can accept either change or both changes):

A quicker way is to use additional flags, in favor of one of the branch (ours strategy refer to preference of the current branch, and theirs is the preference of the incoming branch):

git merge --strategy=ours develop
git merge -X theirs develop

Note: -s or --strategy completely ignore the other side, while -X or --strategy-option resolve any conflicts using the chosen side.

Additional note: merge vs rebase, and options for git pull

Perhaps some animation is great here (acknowledgement to Nicola Paolucci’s post with the animations). Click on animated images to see how it works:

You should use git rebase when your changes do not “deserve” a separate branch for its own. All the commands with merge above can also be done with rebase; note that it has a different behaviour as in the animations

When working with remote directories, if you want to rebase instead of merge after fetching changes, you can add --rebase flag to the pull command:

  • git pull = git fetch + git merge against tracking upstream branch
  • git pull --rebase = git fetch + git rebase against tracking upstream branch

Final words

The conflict in reality can be more complex, but in this episode, I have list the common cases you might face when you need to merge 2 branches together.

There are some images in the post, some are mine, some are from the Internet, and it’s free to use as in the LICENSE below. But I always leave credit here to support authors, and so do you if you want to use content in my article.

MIT
Last updated on May 04, 2022 22:08 GMT+7
Built with Hugo
Theme Stack designed by Jimmy