Git log – Quick Reference

Some useful git log and git show options which I am bound to forget and will be happy to have in a singe place!

Limit the log entries

$ git log -<number of entries>
ex: git log -2 shows the last two commit details
$ git log -2
commit 474ea8e80dc30576b8b8f764a4f52c72e54277bd
Author: Nikhil Kuriakose <nikhilkuria@hotmail.com>
Date: Tue Sep 16 20:38:39 2014 +0530

draft v0.1 changes added

commit 3ac4e6fae7098951746dc46947bd12baec4b1f22
Author: nikhilkuria <nikhilkuria@hotmail.com>
Date: Fri Aug 29 09:55:53 2014 +0530

Bug fix : Some nodes were having trouble finding their parent.. phew!!

OK, so how about if we want to look at all the commits made since yesterday? All we need is the --since option:

git log --since="yesterday"

We can also use --until to fetch all commits up to a certain date; or of course we can use both options in tandem:

git log --since="1 week ago" --until="yesterday"

There is also a neat way to search the commits. Suppose you are working on a method and you need to know the history of changes made on it. Say, I am interested in the method, updateMetaDataMaps

git log -SupdateMetaDataMaps

This shows the commits that introduced a change to the code that added or removed that string.

Neat formats to the log

A simple git log statement gives us so much information that i need at the moment.

$ git log
commit 474ea8e80dc30576b8b8f764a4f52c72e54277bd
Author: Nikhil Kuriakose <nikhilkuria@hotmail.com>
Date: Tue Sep 16 20:38:39 2014 +0530

draft v0.1 changes added

commit 3ac4e6fae7098951746dc46947bd12baec4b1f22
Author: nikhilkuria <nikhilkuria@hotmail.com>
Date: Fri Aug 29 09:55:53 2014 +0530

Bug fix : Some nodes were having trouble finding their parent.. phew!!

commit 0051792ee0808e781a17d95cfb0d7b56a4402335
Author: = <=>
Date: Wed Aug 27 18:07:28 2014 +0530

Sensible to split the attributes

commit 1da57b8e21ace5238a6303920f52cf7363882067
Author: ueec6tv <extern_kuriakose.nikhil@allianz.com>
Date: Tue Aug 19 17:20:12 2014 +0530

extra lines!!!

And if you think you need even more information, the file changes can be shown using

$ git log -p

But, if you just need to see a minimal view, the –pretty=oneline option will help,

$ git log --pretty=oneline
474ea8e80dc30576b8b8f764a4f52c72e54277bd draft v0.1 changes added
3ac4e6fae7098951746dc46947bd12baec4b1f22 Bug fix : Some nodes were having trouble finding their parent.. phew!!
0051792ee0808e781a17d95cfb0d7b56a4402335 Sensible to split the attributes
1da57b8e21ace5238a6303920f52cf7363882067 extra lines!!!
eeb5c9a33462c2bfb6c058b55bbf6cb97d799607 Adding a sample xml file

–pretty also comes with options short, full and fuller options to show the output in roughly the same format but with less or more information, respectively

$ git log --pretty=short
commit 474ea8e80dc30576b8b8f764a4f52c72e54277bd
Author: Nikhil Kuriakose <nikhilkuria@hotmail.com>

draft v0.1 changes added

commit 3ac4e6fae7098951746dc46947bd12baec4b1f22
Author: nikhilkuria <nikhilkuria@hotmail.com>

Bug fix : Some nodes were having trouble finding their parent.. phew!!
$ git log --pretty=fuller
commit 474ea8e80dc30576b8b8f764a4f52c72e54277bd
Author: Nikhil Kuriakose <nikhilkuria@hotmail.com>
AuthorDate: Tue Sep 16 20:38:39 2014 +0530
Commit: Nikhil Kuriakose <nikhilkuria@hotmail.com>
CommitDate: Tue Sep 16 20:38:39 2014 +0530

draft v0.1 changes added

commit 3ac4e6fae7098951746dc46947bd12baec4b1f22
Author: nikhilkuria <nikhilkuria@hotmail.com>
AuthorDate: Fri Aug 29 09:55:53 2014 +0530
Commit: nikhilkuria <nikhilkuria@hotmail.com>
CommitDate: Fri Aug 29 09:55:53 2014 +0530

Bug fix : Some nodes were having trouble finding their parent.. phew!!

One of the most frequent things I have to do is find a commit based on a comment and see the changes. For example, I need to see the changes I made for a defect XYZ. I dont remember when I made the change, but I am sure, I will have the defect ID in the comment.

This is where the –pretty=oneline comes in handy, because the comment and the hash will be in one line

$ git log --pretty=oneline --grep="5716"
3546c34211dfa2de1477ee8a1c12d075ccc3729b fix for td 5716. adding extra validation

now, if i get the hash, i can quickly do a git show to see the changes i made,

$ git show 3546c34211dfa2de1477ee8a1c12d075ccc3729b

Git Internals – Basic object data storage

Well, what makes git super fast? A look into git’s underbelly..

Before i begin, i will be setting up an empty repository.

nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git init
Initialized empty Git repository in /home/nikhil/dev/blog/git/.git/
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ ls -a
. .. .git

Also, it can be seen that initializing the repository creates a .git directory, and see the contents of the directory. As you can see, the objects folder is empty. Git has initialized the objects directory and created pack and info subdirectories in it, but there are no regular files.

nikhil@nikhil-Inspiron-3537:~/dev/blog/git/.git$ tree
.
|-- branches
|-- config
|-- description
|-- HEAD
|-- hooks
| |-- applypatch-msg.sample
| |-- commit-msg.sample
| |-- post-update.sample
| |-- pre-applypatch.sample
| |-- pre-commit.sample
| |-- prepare-commit-msg.sample
| |-- pre-rebase.sample
| `-- update.sample
|-- info
| `-- exclude
|-- objects
| |-- info
| `-- pack
`-- refs
|-- heads
`-- tags

9 directories, 12 files

At the core of Git is a simple key-value data store. You can insert any kind of content into it, and it will give you back a key that you can use to retrieve the content again at any time. To demonstrate, you can use the plumbing command hash-object, which takes some data, stores it in your .git directory, and gives you back the key the data is stored as. Note that the hash-object is a plumbing command and is not meant to be used in a regular day.


nikhil@nikhil-Inspiron-3537:~/dev/blog/git/.git$ echo 'supercompiler' | git hash-object -w --stdin
755eb4004ee1ac36d0dd51008ed6279c2fb200e5

The -w tells hash-object to store the object; otherwise, the command simply tells you what the key would be. --stdin tells the command to read the content from stdin; if you don’t specify this, hash-object expects the path to a file. The output from the command is a 40-character checksum hash. This is the SHA-1 hash — a checksum of the content you’re storing plus a header.

Let us move to the objects directory and see how the file is stored,


nikhil@nikhil-Inspiron-3537:~/dev/blog/git/.git/objects$ tree
.
|-- 75
| `-- 5eb4004ee1ac36d0dd51008ed6279c2fb200e5
|-- info
`-- pack

3 directories, 1 file

You can see a file in the objects directory. This is how Git stores the content initially — as a single file per piece of content, named with the SHA-1 checksum of the content and its header. The subdirectory is named with the first 2 characters of the SHA, and the filename is the remaining 38 characters.

You can pull the content back out of Git with the cat-file command. This command is sort of a Swiss army knife for inspecting Git objects. Passing -p to it instructs the cat-file command to figure out the type of content and display it nicely for you.


nikhil@nikhil-Inspiron-3537:~/dev/blog/git/.git/objects$ git cat-file -p 755eb4004ee1ac36d0dd51008ed6279c2fb200e5
supercompiler

Ok, let us play around a bit.

I am creating a v1 of a file and writing it to the repository, followed by modifying the file and writing the v2 to the repository. We can see both the file contents using the cat-file command and see a total of three different hashes stored within the objects directory.


nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ echo "version 1" > manual.txt
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git hash-object -w manual.txt
83baae61804e65cc73a7201a7252750c76066a30
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git cat-file -p 83baae61804e65cc73a7201a7252750c76066a30
version 1
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ echo "version 2" > manual.txt
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git hash-object -w manual.txt
1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git cat-file -p 1f7a7a472abf3dd9643fd615f6da379c4acb3e3a
version 2
nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ tree .git/objects/
.git/objects/
|-- 1f
| `-- 7a7a472abf3dd9643fd615f6da379c4acb3e3a
|-- 75
| `-- 5eb4004ee1ac36d0dd51008ed6279c2fb200e5
|-- 83
| `-- baae61804e65cc73a7201a7252750c76066a30
|-- info
`-- pack

5 directories, 3 files

You can have Git tell you the object type of any object in Git, given its SHA-1 key, with cat-file -t:

nikhil@nikhil-Inspiron-3537:~/dev/blog/git$ git cat-file -t 83baae61804e65cc73a7201a7252750c76066a30
blob

Now, there are two things that has to be mentioned here.

  1. Git does not store the file. Git stores only the contents.
  2. The contents are stored as a blob object

How to : git stash

Stashing is a great way to pause what you’re currently working on and come back to it later. Suppose you are working on something, and suddenly, something high priority comes up, like a production bug. Ah, lets see.. a storyboard can make things light..

So, wonderful morning, and you wanted to do some performance improvements to the code..

So, you create a new branch and make the changes, and you see that the changes are seen when you run a git status

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

vi JobPersist.java

$ git status
# On branch performance
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: JobPersist.java
#
no changes added to commit (use "git add" and/or "git commit -a")

But, I am not done and not ready to commit the data. Then, something important comes up. You don’t have time to complete your work and will have do something else.

So, to get the latest code, you do a pull, and see what happens.

$ git pull
remote: Counting objects: 51, done.
remote: Compressing objects: 100% (34/34), done.
remote: Total 51 (delta 15), reused 37 (delta 9)
Unpacking objects: 100% (51/51), done.
From https://github.com/nikhilkuria/jobRunner
f175a2b..987a41f master -> origin/master
* [new branch] performance -> origin/performance
Updating f175a2b..987a41f
error: Your local changes to the following files would be overwritten by merge:
jobRunner/src/com/job/persist/JobPersist.java
Please, commit your changes or stash them before you can merge.
Aborting

So, git tells me to either commit or stash. Since, I am not ready to commit, I just shove all the work away under the sheet and resume working when I am done with the new work. Stashing is the best way to do this.

The stash command in Git is kind of like a clipboard; you can stash away any changes in your current branch to work on something else for a while. You can change branches and perform other commits.

$ git stash
Saved working directory and index state WIP on master: f175a2b Adding relationsh
ips
HEAD is now at f175a2b Adding relationships

So, now the branch moved back to the previous commit and moved all my tracked files to a ‘stash’, making the working directory clean

$ git status
# On branch master
nothing to commit, working directory clean

I can also see the list of all my stashes,

$ git stash list
stash@{0}: WIP on master: f175a2b Adding relationships

So, and finally, you are done with the new job and is ready to continue working on the performance issue. You can bring back the stash using the command, git stash apply

$ git stash apply
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: JobPersist.java
#
no changes added to commit (use "git add" and/or "git commit -a")

So, this brought back my previous work and I can complete this at peace.

There are cases when you can have multiple stashes,

$ git stash list
stash@{0}: WIP on master: eec8b6f fix for prod issue
stash@{1}: WIP on master: ba8de07 initial commit

So, if you need to go back to a specific stash, you could pass the stash id as a param to git stash apply

$ git stash apply stash@{1}
# On branch master
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: JobPersist.java
#
no changes added to commit (use "git add" and/or "git commit -a")

The changes to your files were reapplied, but the file you staged before wasn’t restaged. To do that, you must run the git stash apply command with a --index option to tell the command to try to reapply the staged changes. If you had run that instead, you’d have gotten back to your original position:

$ git stash apply stash@{4} --index
# On branch master
# Changes to be committed:
# (use "git reset HEAD <file>..." to unstage)
#
# modified: JobPersist.java

You could also resume your work by creating a new branch from stash. (highly recommended). Running
git stash branch <branchname> [<stash>]
which creates a new branch for you, checks out the commit you were on when you stashed your work, reapplies your work there.

$ git stash branch performance-new stash@{4}
Switched to a new branch 'performance-new'
# On branch performance-new
# Changes not staged for commit:
# (use "git add <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: JobPersist.java
#
no changes added to commit (use "git add" and/or "git commit -a")
Dropped stash@{4} (448b5faf608729000757f65a8d309a8e3fc3aa8d)

You could read further here and here.

Removing a file from git repository

One thing I have seen in all the projects that I have worked on is that all of them require a couple of configuration changes before they can be run in the local development environment.

This could range from server configuration files to specific user login hacks. And if you are lucky, the list of changes would be too long. This means, if you are working with git, your change file list would always have this long list of configuration changes.

So, there are a couple of things you can do here. One of them is add them to .gitignore file. When you tell Git to ignore files, it’s going to only stop watching changes for that file, and nothing else. This means that the history will still remember the file and have it.

I would rather go with removing the file from repository altogether. You could use the command,

$ git rm --cached <file>

This would remove the file from repository, but will keep it in the workspace or file system.

This can be reverted by manually adding the file back using add.

Or, if you want to tell the repository to stop tracking a file, you could use

$ git update-index --assume-unchanged <file>

And this can be reverted using

$ git update-index --no-assume-unchanged <file>

Read more about this here and here..

Branching in Git

I switched companies 6 months ago. What was more interesting was, I came in that recherché moment when the entire team was switching repositories. We were moving to git. So, it was fun, going through all those self learning and stumble upon stumble over one’s mistakes. But it was all worth it, git is great. It is easy and it is fast. It is worthwhile to read about the history and the basic concepts. Enough said, onto git branching.

Well, you could get this wonderful tutorial on branching, or learn one command a day. But, let us see something simple. My task was simple, create a local branch and push it to remote.

So, if i want to create a new branch, ‘stopgap01’, key in the following command in the git bash
$ git branch stopgap01

This creates a branch called ‘stopgap01’ . Both master and stopgap01 point to the same commit.

Note we are still in the ‘master’ branch, to move to the newly created branch, use the checkout command,
$ git checkout stopgap01
Switched to branch 'stopgap01'

So, let us make a few changes and commit,
$ git commit -m 'fixing a typo error'
[stopgap01 dd8cf4c] fixing a typo error
1 file changed, 1 insertion(+), 1 deletion(-)

But note that, it is still in your local machine. In real life, all teams work with a remote repository. You should be able to push the locally created branch to the remote repository so that your teammates should be able to work on it. Read more about working with remotes here.

To push to the remote, use the command,
$ git push origin stopgap01
Total 0 (delta 0), reused 0 (delta 0)
To nba_gitlab@sl123456.dcm.allstar:roster/list.git
* [new branch] stopgap01-> stopgap01

Here ‘orgin’ is the alias of the remote repository.

Deleting a branch

So, if you made your changes and merged in the changes to the parent branch, it is recommended you delete the branch.

Deleting from local is easy, just add a -d parameter to the branch command
$ git branch -d stopgap01
Deleted branch stopgap01 (was dd8cf4c).

To be noted that if there are unmerged changes, you will have to merge or stash them. Or force delete by using -D instead of -d.

Now, this deletes the branch from your local machine, but it is still available in your remote. The command for removing from remote is very queer,
$ git push origin :stopgap01
To nba_gitlab@sl123456.dcm.allstar:roster/list.git
- [deleted] stopgap01

We use the same push command, but with a ‘:’ before the branch name.

So, your branch is fully deleted.