GIT Advanced usage

GIT Rebase

In this article I will try to explain some of the real world use-cases of really useful command in GIT, rebase. So let’s take a look at some of them which you can need in everyday scenarios:

  • Combine multiple commits into one commit with a custom commit message
  • Combine multiple commits into one commit while preserving all commit messages
  • Remove one or multiple commits
  • Change commit messages
  • Prepare feature branch for the merge

Note: Rebase is an advanced command and it is commonly used to rewrite history. In some teams, it is not allowed to change the history of the branch, especially if it is a dev or master. You can do whatever you want to your local feature branch. So be careful when using rebase.

Note: In my environment, I have used VS Code as editor and some aliases for the git. You can do the same things with the following.

Set global editor for the git VS Code:

$ git config --global core.editor code

Set difftool from default editor VS Code:

$ git config --global diff.tool default-difftool
$ git config --global difftool.code.cmd "code --wait --diff $LOCAL $REMOTE"

Disable question prompt:

$ git config --global difftool.prompt false

Set default merge editor:

$ git config --global merge.tool code
$ git config --global mergetool.code.cmd "code --wait $MERGED"

Disable question prompt and keep the backup for merge tool:

$ git config --global mergetool.prompt false
$ git config --global mergetool.keepbackup false

It is the preparation of the long command to the shorter. Create a new alias for the git log –oneline:

$ git config --global alias.onelinegraph 'log --oneline --graph --decorate'

Create new alias for the unreachable reflogs:

$ git config --global alias.expireunreachablenow 'reflog expire --expire-unreachable=now --all'
$ git config --global alias.gcunreachablenow 'gc --prune=now'

Change the global configuration which will automatically prune next time when we will use the fetch command:

$ git config --global fetch.prune true

If you don’t want to individually push your tags to the remote, just change your global settings as the following syntax:

$ git config --global push.followTags true

It will GitHub resource with the following repository:

$ git clone git@github.com:jamalshahverdiev/gitexplain.git
Cloning into 'gitexplain'...
remote: Counting objects: 3, done.
remote: Total 3 (delta 0), reused 0 (delta 0), pack-reused 0
Receiving objects: 100% (3/3), done.
$ cd gitexplain

Combine multiple commits into one commit with the custom commit message

We do everything in the master branch but, it is only for test purpose and to learn.

For test purpose to create the new file and add some commits we can use the following script:

$ cat ./autocommit.sh
#!/usr/bin/env bash
touch commitfile.txt
git add commitfile.txt
for i in `seq 1 5`
do
    echo "Commit #$i - Change line $i" >> commitfile.txt
    git commit -am "Commit message #$i"
done

Execute this script:

$ ./autocommit.sh

Ok, now we have the following commits:

$ git onelinegraph
* ed0d1ea (HEAD -> master) Commit message #5
* db0e768 Commit message #4
* b8acd91 Commit message #3
* bac858e Commit message #2
* 83eee95 Commit message #1
* 7922260 (origin/master, origin/HEAD) Initial commit

Let’s say we want to combine these last 5 commits (From 83eee95 – to ed0d1ea) into one with a meaningful commit message. For this purpose, we can use rebase command like this:

$ git rebase -i HEAD~5

Let’s explain above command in detail.

-i – means interactive. It will open the graphical page with the options which we need to change.

HEAD~5 – Here 5 means that we want to edit last 5 commits from the top. HEAD is the top of your current branch.

It will open your default git text editor. In my case, I have configured it to be VS Code.
git-rebase-todo1

We will modify these lines as follows (Save and close file):
git-rebase-todo2

Let’s explain what all these words mean.

reword or r – Allows you to edit commit’s message. When running rebase in interactive mode it will stop on this line and open a new window in git text editor and will ask you to change the commit message.

fixup or f – Merges commit into the previous commit, but discards commit’s message. In our case, it means that all commits marked as fixup after reword will be merged into the first commit marked as reword with its commit message that we will provide in the next window.
commit-edit-msg1

Edit commit message as you want. Save file and close:
commit-edit-msg2

After closing we should get following output:
[detached HEAD 6249cfc] Meaningful commit message
Date: Sat Apr 7 12:02:47 2018 +0300
1 file changed, 6 insertions(+)
create mode 100644 commitfile.txt
Successfully rebased and updated refs/heads/master.

In the following output we can see the result of our work in the current branch:

$ git onelinegraph
* a49d127 (HEAD -> master) Meaningful commit message
* 7922260 (origin/master, origin/HEAD) Initial commit

Combine multiple commits into one commit while preserving all commit messages

This part almost identical to the previous one, with only one difference. While rebasing instead of fixup keyword, we need to use squash or s. This keyword does the same thing as fixup, but while merging commit into the previous commit it preserves its own commit message. So, in the end, you will get one commit with the multiline commit message.

You can use the ‘autocommit.sh‘ but, don’t forget to change sequence numbers between 6-10.

$ ./autocommit.sh
 [master 18ec9af] Commit message #6
 1 file changed, 1 insertion(+)
 [master 3a38238] Commit message #7
 1 file changed, 1 insertion(+)
 [master 8404656] Commit message #8
 1 file changed, 1 insertion(+)
 [master 3556a84] Commit message #9
 1 file changed, 1 insertion(+)
 [master c8e74ff] Commit message #10
 1 file changed, 1 insertion(+)

Note: We need to use the following command again because we added 5 new commit messages.
$ git rebase -i HEAD~5

git-rebase-todo3

And opened a new window and edited new commit message:
commit-edit-msg3

It will open a new window with the all previous commit messages (Save and close file):
commit-edit-msg4

After closing VS Code window GIT command output will be as following:
[detached HEAD 2d19888] Squashed commit message
Date: Sat Apr 7 12:58:51 2018 +0300
1 file changed, 1 insertion(+)
[detached HEAD 9391343] Squashed commit message
Date: Sat Apr 7 12:58:51 2018 +0300
1 file changed, 5 insertions(+)
Successfully rebased and updated refs/heads/master.

Look at the last commit message with the following command (As we see all commit messages stored into one commit):

$ git log -1
commit 9391343e1304b351f1a08cbb0588011320879483 (HEAD -> master)
Author: Developer
Date:   Sat Apr 7 12:58:51 2018 +0300

    Squashed commit message

    Commit message #7

    Commit message #8

    Commit message #9

    Commit message #10

Remove one or multiple commits

There are times when you want to remove some commits for whatever reason. These commits can be sequential or can have different positions in the branch. In any case, you can accomplish this task using rebase.

If your commits that you want to delete are sequential from HEAD then instead of rebase you can use git reset –hard where the is the hash of the commit till which you want to delete everything. Commit with will not be deleted.

So far we have 3 commits in our branch master. Let’s say we want to remove second and third commits from our branch.

$ git onelinegraph
* 9391343 (HEAD -> master) Squashed commit message
* a49d127 Meaningful commit message
* 7922260 (origin/master, origin/HEAD) Initial commit

Again, to play with commits we will use rebase in interactive mode.

$ git rebase -i HEAD~2

git-rebase-todo4

This time we will use new keyword drop. Change all commits which you want to delete from pick to drop or d (Save and close file).

git-rebase-todo5

Command execution result will be as following:
Successfully rebased and updated refs/heads/master.

The result of changes:

$ git onelinegraph
* 7922260 (HEAD -> master, origin/master, origin/HEAD) Initial commit

Change commit messages

As you know you can use commit –amend to change last commit’s message. But there are times when you want to change multiple commits’ messages or you want to change some specific commit’s message somewhere in the middle of your history. In such cases, you can use rebase with reword keyword. Let’s try it out.

Execute the autocommit.sh script with sequence number 1-5 to create new commits.

$ ./autocommit.sh
[master 8fc7438] Commit message #1
 1 file changed, 1 insertion(+)
 create mode 100644 commitfile.txt
[master 84ef4cf] Commit message #2
 1 file changed, 1 insertion(+)
[master 57b6e84] Commit message #3
 1 file changed, 1 insertion(+)
[master 4d0ae5a] Commit message #4
 1 file changed, 1 insertion(+)
[master d5b24e2] Commit message #5
 1 file changed, 1 insertion(+)

Commit logs:

$ git onelinegraph
* d5b24e2 (HEAD -> master) Commit message #5
* 4d0ae5a Commit message #4
* 57b6e84 Commit message #3
* 84ef4cf Commit message #2
* 8fc7438 Commit message #1
* 7922260 (origin/master, origin/HEAD) Initial commit

Let’s take 3 commits from top and change commit message for first and third commits:

$ git rebase -i HEAD~3

git-rebase-todo6

Change from pick to reword or r in first and third lines (Save and close):
git-rebase-todo7.png

The new window will open, here change commit message for commit 3 from
commit-edit-msg5
to (It will open editor 2 times because, we have edited 2 commits. Save and close)
commit-edit-msg6.png

From:
commit-edit-msg7.png

To:
commit-edit-msg8.png

Output of git command for changed to commits:
[detached HEAD 328c895] Modified commit message for commit #3
Date: Sat Apr 7 14:09:37 2018 +0300
1 file changed, 1 insertion(+)
[detached HEAD 6474608] Modified commit message for commit #5
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/master.

Changed result:

$ git onelinegraph
* 6474608 (HEAD -> master) Modified commit message for commit #5
* 8307085 Commit message #4
* 328c895 Modified commit message for commit #3
* 84ef4cf Commit message #2
* 8fc7438 Commit message #1
* 7922260 (origin/master, origin/HEAD) Initial commit

Prepare feature branch for the merge

When working with Feature Branch Flow it is possible that your team conventions force master branch history to be linear and allows only fast-forward merges. In such cases before we merge feature branch to the master we need to follow some rules. In this section, I will show how we can achieve this using rebase.

To get started we need to create a feature branch:

$ git checkout -b featureA
Switched to a new branch 'featureA'

Add some commits with the autocommit.sh script but, change sequence number from 6 to 8.

$ ./autocommit.sh
[featureA 37ab9a4] Commit message #6
 1 file changed, 1 insertion(+)
[featureA 7044314] Commit message #7
 1 file changed, 1 insertion(+)
[featureA 23038fa] Commit message #8
 1 file changed, 1 insertion(+)

But, before rebase featureA onto the master branch we need checkout to the master branch, fetch and pull everything just to be sure we are up to date.

$ git checkout master
$ git fetch origin
$ git pull origin master

We need switch back to the featureA branch and rebase onto the local master branch.
$ git checkout featureA
Switched to branch ‘featureA’

Then rebase onto the master branch.

$ git rebase -i master

From this point we will do everything as in the ‘Combine multiple commits into one commit with custom commit message‘ section:
git-rebase-todo8

Change to:
git-rebase-todo9

Change this commit:
commit-edit-msg9

Change commit message as in real environment because we will merge this to the master branch:
commit-edit-msg10.png

Git output:
[detached HEAD 498f7be] Change main variable to the parameter – CORE-226
1 file changed, 1 insertion(+)
Successfully rebased and updated refs/heads/featureA.

Result:

$ git onelinegraph
* 83125fd (HEAD -> featureA) Change main variable to the parameter - CORE-226
* 6474608 (master) Modified commit message for commit #5
* 8307085 Commit message #4
* 328c895 Modified commit message for commit #3
* 84ef4cf Commit message #2
* 8fc7438 Commit message #1
* 7922260 (origin/master, origin/HEAD) Initial commit

As you can see we have our commit on top of current master. So, we can perform fast-forward merge.

$ git checkout master
Switched to branch 'master'
Your branch is ahead of 'origin/master' by 5 commits.
  (use "git push" to publish your local commits)

$ git merge featureA
Updating 6474608..83125fd
Fast-forward
 commitfile.txt | 3 +++
 1 file changed, 3 insertions(+)

As we see our master and featureA branch in the same state:

$ git onelinegraph
* 83125fd (HEAD -> master, featureA) Change main variable to the parameter - CORE-226
* 6474608 Modified commit message for commit #5
* 8307085 Commit message #4
* 328c895 Modified commit message for commit #3
* 84ef4cf Commit message #2
* 8fc7438 Commit message #1
* 7922260 (origin/master, origin/HEAD) Initial commit

Cherry Pick

When working with Feature Branch Flow you most probably will have a master, dev, feature branches. master is the branch where you take a snapshot of dev at the time Production release. Let’s say there is a bug in Production and you need to introduce a hotfix. So, you create a hotfix branch from master. You introduce a fix for bug, commit, create a Pull Request (PR) to master. After your fix is tested and PR is approved you prepare and merge your hotfix branch into master. But how you will get those changes in hotfix to dev which ahead of master by many commits? Here comes to play cherry-pick. cherry-pick allows you to select specific commit from another branch and put it on top of your current branch. Let’s see how we can use it for the above situation. We will simulate work in master, dev and hotfix branches.

Create branch dev and execute autocommit.sh script to add some commits (Don’t forget to change sequence number from 9 to 12).

$ git checkout -b dev
Switched to a new branch 'dev'

$ ./autocommit.sh
[dev 220e6be] Commit message #9
 1 file changed, 1 insertion(+)
[dev 3bb09a1] Commit message #10
 1 file changed, 1 insertion(+)
[dev 7d55040] Commit message #11
 1 file changed, 1 insertion(+)
[dev 5d5035a] Commit message #12
 1 file changed, 1 insertion(+)

While developers adding new commits to dev, we found a bug in Production (master branch). So let’s create a hotfix for that bug:

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

$ git checkout -b hotfix-CORE-222
Switched to a new branch 'hotfix-CORE-222'

Add some changes, commit this changes to the new branch, switch back to the master and merge changes from hotfix-CORE-222 to the master:

$ echo "Fix: Resolved icon page bug from master" >> commitfile.txt
$ git commit -am "Fix: Hotfix for CORE-222"
[hotfix-CORE-222 a443b6f] Fix: Hotfix for CORE-222
 1 file changed, 1 insertion(+)

$ git checkout master
Switched to branch 'master'
Your branch is up to date with 'origin/master'.

$ git merge hotfix-CORE-222
Updating 83125fd..a443b6f
Fast-forward
 commitfile.txt | 1 +
 1 file changed, 1 insertion(+)

Now we need to go to our dev branch and get commit for hotfix from the master into dev branch (This commitId is the last commit in the master branch which we did before):

$ git checkout dev
Switched to branch 'dev'

As we see we have a conflict. We resolve this conflict and then continue cherry-pick operation.

$ git cherry-pick a443b6f
error: could not apply a443b6f... Fix: Hotfix for CORE-222
hint: after resolving the conflicts, mark the corrected paths
hint: with 'git add ' or 'git rm '
hint: and commit the result with 'git commit'

Open commitfile.txt file in your text editor and resolve it as you want (For test purpose I have accepted both changes):
resolve-conflicts

$ git add commitfile.txt
$ git cherry-pick --continue

Save and close the file:
commit-edit-msg11

Look at the command output:
[dev 6c54776] Fix: Hotfix for CORE-222
Date: Sat Apr 7 16:33:08 2018 +0300
1 file changed, 1 insertion(+)

And look at the log results:

$ git onelinegraph
* 6c54776 (HEAD -> dev) Fix: Hotfix for CORE-222
* 5d5035a Commit message #12
* 7d55040 Commit message #11
* 3bb09a1 Commit message #10
* 220e6be Commit message #9
* 83125fd (origin/master, origin/HEAD, featureA) Change main variable to the parameter - CORE-226
* 6474608 Modified commit message for commit #5
* 8307085 Commit message #4
* 328c895 Modified commit message for commit #3
* 84ef4cf Commit message #2
* 8fc7438 Commit message #1
* 7922260 Initial commit

Thanks for reading.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google+ photo

You are commenting using your Google+ account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s