This interactive tutorial about the resolution of conflicts in git is a continuation of my first blog in the series titled And then we resolve a merge conflict.... If you have not read it, please check it out here.
The aim of this tutorial is to show how you can use rebasing as an alternative to merging so to combine to branches in git. It will demonstrate how you resolve conflicts that occur thereof.
Every commit in github is based on its preceding commit. When you trace the history you realize that every commit has an ancestor.
Practically this is shown by that whenever you log the history of a branch, all the commits are listed in chronological order. With first commit at the bottom and the most recent commit which is the HEAD at the top.
Now, what rebasing does is it rewrites history of your current branch, through assigning a commit of your choice as the ancestor of the HEAD.
As indicated in the first blog to get started, we create a repo on github and initialize it with a README. Then clone it locally on my machine. After that, I open the project on an editor. My editor of choice is Visual Studio Code.
I then create and check out a new branch called list-interests. And create bio.txt. In bio.txt I add a list of my personal interests. Then voila:
To stage the changes, on the terminal I run the command,
git add .
Then to commit the changes to my local repo I run the command,
git commit -m "Adds interests to bio"
Then to look up the history of the list-interests branch, on the terminal I run the command;
git log --oneline
5d944ac (HEAD -> list-interests) Adds interest to bio 305d937 (origin/master, origin/HEAD, master) Initial commit
Now let's imagine that a bunch of devs was assigned to this project. One dev, in particular, was assigned to add an intro to the bio. The dev successfully created the intro and then proceed to merge it to master.
To mimic that, on the terminal run the command,
git checkout -
The command above listed above allows you to navigate to the branch you were in before you checked out your current branch.
On master I then create the file bio.txt. Then add a basic intro about myself.
I then add the changes to the staging area by running the command below on the terminal.
git add .
To add the changes to my local repo,
git commit -m "Adds intro to bio"
Then I log the history by running the command;
git log --oneline
4586948 (HEAD -> master) Adds intro to bio 305d937 (origin/master, origin/HEAD) Initial commit
Then to sync master to my origin/master which is the branch that corresponds to master on the remote repository;
I then check out the branch list-interests to continue.
If you log the history of the branch list-interests you will realize that it was based on the old version of master that did not have the intro. I intend to rebase it to the new version of master so as to include the intro.
To do that on the terminal I then run the command;
git rebase master
On the terminal I get the error message:
First, rewinding head to replay your work on top of it... Applying: Adds interest to bio Using index info to reconstruct a base tree... Falling back to patching base and 3-way merge... CONFLICT (add/add): Merge conflict in bio.txt Auto-merging bio.txt error: Failed to merge in the changes. hint: Use 'git am --show-current-patch' to see the failed patch Patch failed at 0001 Adds interest to bio "git add/rm <conflicted_files>", then run "git rebase --continue". You can instead skip this commit: run "git rebase --skip". To abort and get back to the state before "git rebase", run "git rebase --abort".
The above message informs me that git failed to merge then provides me commands I can use to resolve the issue.
On the editor, this is what is there:
Because I want to keep both the changes, I select the option Accept Both Changes on VS code.
To continue I then add the change to the staging area, by running the command;
git add .
Then on the terminal, I run the command;
git rebase --continue
Then get the message
Applying: Adds interest to bio
Under the hood when you rebase, git basically rewinds fetches the tip of master, places it in the position of the last commit of the branch you had checked out, then applies the tip of list_interests on master.
To visualize it imagine your history was a deck of cards, a stack. You pop the topmost card which is the HEAD of your current branch, in its place you push the HEAD of master then finally you push the HEAD of the current branch in its rightful position.
If you have conflicts you resolve them and then stage the changes. But instead of making a new merge commit when you run the command git rebase --continue the changes are applied to the HEAD, which in this case had the message Adds interests to bio. When you log the history you get:
1a378f3 (HEAD -> list-interests) Adds interest to bio 4586948 (origin/master, origin/HEAD, master) Adds intro to bio 305d937 Initial commit
I have successfully made a rebase. Rebasing offers a clean way to combine diverging branches and its main advantage is the history is kept linear, unlike when you do merge commits.
I then sync my local and remote. Make a pull request. Then merge my pull request to origin/master. On my local I checkout master then on the terminal run the command;
So as to sync master to origin/master. Then voila:
With that done, hopefully, you now have an understanding of how rebasing works in git. We now proceed to the main business of the day which is using rebasing to implement what we did on the first blog in the series.
Well, I would love to add a new feature to my bio. This time I will add a list of programming languages I have worked on. To do that I create and check out a new branch called list-languages. On the terminal, I run the command,
git checkout -b list-languages
Then I edit bio.txt by adding a list of programming languages.
To proceed I then add the changes to the staging area by running the command;
git add .
To commit the changes to my local repo, on the terminal I then run the command;
"git commit -m "Adds programming languages to bio"
To create a branch on the remote corresponding to list--languages and push changes associated with it. On the terminal I then run the command;
git push --set-upstream origin list-languages
I just realized I made a blooper on my bio. Instead of adding C++, I meant to add C. But in the same regard, I want to keep my commit message.
So back on my editor, I edit my bio.
Now to stage the changes I run:
git add .
And to commit to my local, on my terminal I run the command:
git commit --amend --no-edit
As we know from the first tutorial git commit --amend --no-edit, alters the history of the branch. We end up with a scenario whereby the local and remote diverge from each other with the remote branch always leading.
When I attempt to sync my local and remote using:
I get this message as a response:
To github.com:JackieBinya/git-tut-2.git ! [rejected] list-languages -> list-languages (non-fast-forward) error: failed to push some refs to 'firstname.lastname@example.org:JackieBinya/git-tut-2.git' hint: Updates were rejected because the tip of your current branch is behind hint: its remote counterpart. Integrate the remote changes (e.g. hint: 'git pull ...') before pushing again. hint: See the 'Note about fast-forwards' in 'git push --help' for details.
Before we proceed let's take a quick look at the history of the branch list-languages, using the command:
git log --oneline
c0fa933 (HEAD -> list-languages) Adds programming languages to bio d2c5e0f (origin/master, origin/HEAD, master) Merge pull request #1 from JackieBinya/list-interests 1a378f3 (origin/list-interests, list-interests) Adds interest to bio 4586948 Adds intro to bio 305d937 Initial commit
Basically when I run the command git push. Under the hood git attempts to sync my local list-languages to the corresponding branch on the remote origin/list-languages. Git is clever enough to realize that the branches have diverged, the remote is ahead of the local branch so it suggests I pull changes on the remote to my local, resolve any conflicts and push again.
To resolve the issue highlighted I run the command:
git pull --rebase
I added the flag --rebase to the git pull command so as to rebase the HEAD of my local on the tip of origin/list-laguages. This offers an alternative to merging branches.
On my editor this is what my file looks like:
To resolve the conflict, on VS Code I select the option to keep incoming changes. I then add the changes to the staging area then run the command;
_git rebase --continue_
This is what bio.txt looks like on my editor:
When I log the history on the terminal I get the output below:
b584e1c (HEAD -> list-languages) Adds programming languages to bio 7b06700 (origin/list-languages) Adds programming languages to bio d2c5e0f (origin/master, origin/HEAD, master) Merge pull request #1 from JackieBinya/list-interests 1a378f3 (origin/list-interests, list-interests) Adds interest to bio 4586948 Adds intro to bio 305d937 Initial commit
I then sync my local and remote. Make a PR then merge it to master.
Finally my bio:
Thank you for reading my article.