Tips and Tricks using Git

Earlier, for maintaining Linux kernel, Linus Torvalds and his team were using BitKeeper as SCM system, but they had to opt out for another system because their were some concerns about the OSL and the support for the free version of BitKeeper had been dropped. Torvalds and his team wanted a SCM which is more faster in terms of performance than the existing systems, he wanted it to be fast enough that it can apply a patch in 3 seconds and update its associated metadata, but none of the systems were meeting his needs at that time, so he decided to develop his own.

So, Torvalds and his team started the development of new SCM in April 2005. He wanted it to be named after himself, like Linux. He said that he considers himself as egotistical bastard, so he preferred the name Git (English meaning of git is an unpleasant person 🙂 ) and also, man pages describes Git as the stupid content tracker. But, considering the efficiency of Git, it is order of magnitude times faster than some of the versioning systems and its also influenced by distributed design of BitKeeper. Now, not much of the Git history, I am writing this blog to list few of the Git tricks that I have accidentally met while maintaining some repositories.

♦ cherry-pick

In one of our projects, we had taken out some functionality from one repository and had added it to the other. But whenever their was any bug fix or new feature committed in old repository, which is also related to the new one, we wanted those commits to be present in new repository.

In that case, Git provides one awesome command to pick existing commit(s) and apply those changes on HEAD which is introduced by a new commit.

$ git cherry-pick [ref][ref1^..ref2]

You can pick a commit using its SHA1 from current branch or any other branch present in a repository. In my case, I had to cherry pick commits across the repositories. So, I first created a remote in new repo which was pointing to the old repo using,

$ git remote add old_origin git@github.com:yogesh/old_repo.git
$ git remote -v
origin    git@github.com:yogesh/new_repo.git (fetch)
origin    git@github.com:yogesh/new_repo.git (push)
old_origin    git@github.com:yogesh/old_repo.git (fetch)
old_origin    git@github.com:yogesh/old_repo.git (push)

and then fetched the branch from old_repo in which my changes were present.

$ git fetch old_origin master
remote: Counting objects: 44, done.
remote: Compressing objects: 100% (19/19), done.
remote: Total 44 (delta 5), reused 3 (delta 3), pack-reused 22
Unpacking objects: 100% (44/44), done.
From git@github.com:yogesh/old_repo
 * [new branch]       master     -> old_origin/master

Now, the new repo have all commits in place, I cherry picked the required commits from old_origin/master branch using their SHA1 like,

$ git cherry-pick 769f074
[new_repo 74ad936] Spelling fix
 1 file changed, 2 insertions(+), 2 deletions(-)

PS: You can take a few characters from SHA1, as long as its 4 characters long and unambiguous.

If their are any conflicts present while cherry-picking the commits, Git adds those changes to the staged area and tells you to resolve those conflicts first. You can then either abort those changes using,

$ git cherry-pick –-abort

or, resolve all conflicts and continue the merge using,

$ git cherry-pick –-continue

♦ rebase -i We people hate(or maybe fear 🙂 ) using rebase maybe because it would make our repository history look like a hell, but at some point, we have to use it to rewrite the history and its good if we conquer the power to use it. Git provides this interactive utility to modify, squash, reorder or remove your previous commits and helps to maintain repo history. Once, I encountered a situation where I had to modify some files and recognized that those changes were better suited in a 3rd last commit present from HEAD. So to make those changes, I had to first move branch HEAD to that commit using,

$ git rebase -i HEAD~3

which caused an interactive shell to open in my configured editor listing previous three commits(including HEAD). Git adds a nice documentation their with the list of available commands to use.

pick 2e759e6 Adding support for fsck
pick e731699 Adding #option_attributes argument to option_from_collection_for_select
pick 74ad936 Spelling fix

# Rebase 7b667ec..74ad936 onto 7b667ec
#
# Commands:
#  p, pick = use commit
#  r, reword = use commit, but edit the commit message
#  e, edit = use commit, but stop for amending
#  s, squash = use commit, but meld into previous commit
#  f, fixup = like "squash", but discard this commit's log message
#  x, exec = run command (the rest of the line) using shell

To edit the 3rd commit, I placed edit command before it(Git list commits in the reverse order), saved and existed the editor.

edit 2e759e6 Adding support for fsck
pick e731699 Adding #option_attributes argument to option_from_collection_for_select
pick 74ad936 Spelling fix
$ git rebase -i HEAD~3
Stopped at 2e759e6424bd251182fe8c2dd747e29e06ba5f45... Adding support for fsck

HEAD was now pointing to the 3rd commit. At this moment, Git allows to modify or remove any file, amend commit message and committing those changes with,

$ git commit –-amend
[detached HEAD 3539819] Adding support for fsck
 2 files changed, 27 insertions(+), 2 deletions(-)
$
$ git status
# rebase in progress; onto 7b667ec
# You are currently editing a commit while rebasing branch 'dev' on '7b667ec'.

Now, HEAD was still pointing to the 3rd commit, so to return to the previous state,

$ git rebase –-continue
Successfully rebased and updated refs/heads/dev

Note here that Git changes SHA1 of all those commits which were picked/edited in rebase, so if you have already pushed your commits before making rebase and pushed again the rebased commits, it might happen that your colleagues will see duplicate commits in their log and they may curse you for that. To avoid it, here is the lifetime quote you should never forget, never rewrite your history if its been already shared with others. ♦ stash Most of us have came across the situation where we have made some changes in staging area, didn’t want to commit it and pull new commits from remote repository, but Git don’t allow us to do that. So we stash those changes which pushes staged area to the stack and moves HEAD to the previous commit. I came across a situation where when I stashed my changes and pulled latest changes, it created a large list of conflicts. I didn’t had much time to resolve those conflicts and test again my stashed changes. In such case, Git provides an easier way to reapply stashed changes without causing any conflicts on current branch by applying stashed changes onto new branch,

$ git stash branch stashed_changes
Switched to a new branch 'stashed_changes'
# On branch stashed_changes
# Changes to be committed:
#   (use "git reset HEAD ..." to unstage)
#
#    modified:   config/deploy/staging.rb
#    modified:   config/deploy/production.rb
#    modified:   lib/short_code.rb
#    modified:   lib/tasks/daily_mailer.rake
#
# Changes not staged for commit:
#   (use "git add ..." to update what will be committed)
#   (use "git checkout -- ..." to discard changes in working directory)
#
#    modified:   Gemfile
#    modified:   Gemfile.lock
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped refs/stash@{0} (53af809c7a84370baffcd8e395b361cad933b2b6)

Their are lot of such things which are yet to be learned from Git and it have made managing our source code much easier by continuously evolving in last 10 years, if you have also came across some situations where you tried googling for that, you can mention those below in comments. Thanks for reading.

Advertisements