jump to navigation

Git Tutorial Part II – Sharpen your Git-Fu with 10 more commands. June 6, 2009

Posted by xk0der in : Programming , 7comments

Some time back I had posted a simple Git tutorial that introduced you to 10 git commands, to get you started with this wonderful piece of tool. The response that I got for that post was great. I also got comments asking me for how to perform few other tasks. So here it is, the second installment of the git-tutorial. I hope you enjoy it as much as the first part.

Remember, the text presented here targets a single developer setup (as the first part did). I intend to do a distributed git setup in the third installment.

Note : Few commands have been broken into two or more lines to fit into the blog’s layout.

Let’s begin!

Command 1 : Time Travel: Moving back and forth between revisions.

The whole point of a version control system is to maintain revision history of your source code. And permitting you to view, how your code looked, at any point of time in the recorded history.

Git records your revisions in the form of commits. Every commit is assigned a unique hash to identify it. (Git uses SHA1 hashes). You can view the commit hash by issuing the git-log command (Check part one). The hash is displayed as a hexadecimal string on the line beginning with the word “commit” in the log.

Now that we know all this technical mumbo-jumbo, let’s use it to do some real work.

Move to a commit in the past.

To move to a particular commit back in time we use the command git-checkout, introduced in part-I for moving between branches. The syntax is similar, but instead of the branch name we use the commit hash. Always make sure you commit any changes or stash them (see next command) before you issue the checkout command; Else the changes will move across!

here’s the syntax:

$ git checkout commit_hash

Let’s see an example.

First I issue the git-log command to find the hash of a commit I want to switch to.

$ git log
commit 1ef801f70a99b07bb578bac4a3c2edb52b367e1d
Author: xk0der <amit .. AT .. xkoder .. com>
Date:   Fri Jun 5 12:52:16 2009 +0530

Added few comments

commit ccbaeec43300f19dd04308b6c62a3f03f6233725
Author: xk0der <amit .. AT .. xkoder .. com>
Date:   Fri Jun 5 12:51:30 2009 +0530

Initial import

(email address obfuscated for spam bots)

As you can see, I have just two commits here. I want to move to the commit with the log message “Initial import” i.e. my first commit. So I issue the following command.

$ git checkout ccbaeec4

as you can see I just typed in the first 8 characters of the hash. That is the minimum that Git requires to successfully identify the commits uniquely. You may supply more than eight characters or the full hash if you wish.

After the checkout is complete, you can view your files to see how they looked in the past :). If you want to modify your files and want to propagate the changes to the master’s HEAD, check command 6 below.

Moving back to the present
To go back to the master’s HEAD; which is the present state of your code; issue the command:

$ git checkout master

As I’ve said earlier, always remember to commit changes or stash them before moving between branches or commits.

Command 2: git-stash – Moving without committing.

This is one handy command you’ll find very useful. It lets you store your current changes to a temporary location. These stashed changes can then be recalled back when required. Thus alleviating the need to commit every time you need to move across branches or commits.

Stashing changes
the syntax is:

$ git stash

When issued, all changes will be stashed to a temporary location and you will have a clean current HEAD. You can verify this by issuing the git-status command.

When done with your other work, you can pop back the stashed changes by issuing the following command.

$ git stash pop

Pretty cool ‘eh! But when to use it?

When to use git-stash; Here is a one typical scenario
Suppose you are working on one of the feature branch and you get a mail that something is not fine with the code on your trunk, the ‘master branch‘. Now one way to to fix something on the master branch is to:

  • First commit changes on the ‘feature’ branch.
  • Next, checkout the master branch and fix what’s required.
  • Commit changes to master.
  • Checkout the ‘feature’ branch.

But, what if, you do not want to commit the changes on the ‘feature’ branch? Probably because you are halfway through something, or you want to test your code before you commit it. You you stash all changes as shown below.

$ git stash
$ git checkout master
...Fix some stuff...
$ git commit -a -m "Bug:12 fixed - thread no longer dies prematurely"
$ git checkout feature
$ git stash pop
...Continue working normally...

Multiple stashes
You can stash more than one set of changes, if required, by issuing the git-stash command multiple times.

When you use the `git-stash pop` command, the latest stash comes out first, much like a stack.

Viewing stored stashes

To view a list of stashed changes issue the following command

$ git stash list

There are more fancy things, that you can do with this command (checkout the man pages for git-stash). But for normal daily use the first two commands will suffice.

Command 3: git-diff – some switches to differentiate better.

I’ve already explained this command in the first part. Here I’ll discuss some command line options that you can supply, to help find the right information.

Finding which files differ (instead of what differs inside files)

Here’s the command:

$ git diff --name-status

This will list the files that have been modified(M), added(A) or deleted(D) with respect to the current HEAD and uncommitted changes, if any. You may supply commit hash(es) to find difference between those two commits.

$ git diff --name-status hash_1 hash_2

Using the HEAD symbol

You can use the HEAD symbol to view diff’s between the current HEAD and previous revisions, as follows:

$ git diff HEAD~1

This command will show you the diff between current HEAD and the immediate previous commit. This command is a shortcut for

$ git diff commit_hash_for_HEAD
  commit_hash_just_before_HEAD

What is the HEAD?

If you remember, as discussed in part one of this tutorial, HEAD refers to the latest commit on the current branch. 

You can change the number after the tilde (~) to find differences between HEAD and ‘that’ many commits before the HEAD commit. experiment and see what you find. This is a totally safe command to play with 🙂

Command 4 : git-add revisited.

In part one of this tutorial we learned how to make git keep track of a file or number of files by issuing the git-add command. Well that is one work git-add does. Let’s find out what’s the other thing git-add is used for.

Before that, let’s see how we have been committing changes up till now.

$ git commit -a -m "Commit Message"

We know that the -m switch is to specify the commit message, what is that -a switch doing there?

Before I explain; The above command is (almost) equivalent to the following two commands, in sequence:

$ git add .
$ git commit -m "Commit Message"

So you see, that little -a switch is actually a shortcut to add all files. But haven’t we already added the files for git, to keep track of?

Yes we have but git also wants you to tell it Which files to commit; and this is what the first ‘git add .‘ command is doing. It is telling git to add all new, modified or deleted files to the ‘commit stage‘. So after the files have be staged for committing, we actually commit i.e. register our changes, by issuing the second command (git-commit).

One small but important quirk.
As I said the command is almost equivalent to the latter sequence; so what’s the catch?

The switch -a with git-commit DOES NOT ADD files that are not currently being tracked by git.
WHEREAS; ‘git add .‘ adds files not being tracked by git for commit as well.

Why to use git-add this way? Read ahead.

Committing only selected files

Suppose you are working on, say 3 source files. You realize that you are pretty much content with the changes you have made to two of the files but the third file is still work in progress. You can commit just these two files by issuing the following commands:

$ git add path/to/file1
$ git add path/to/file2
$ git commit -m "Some feature done"

When you are done with the third file; You can commit changes by either issuing the following commands

$ git add path/to/file3
$ git commit -m "Third feature done"

OR, by issuing the following command (Since we want to commit all modified files.)

$ git commit -a -m "Third feature done"

You can use ‘*’ and ‘?’ wild-cards to stage multiple files for commit with the git-add command.

example:

$ git add header/*

This command will add all files inside the folder “header” to the ‘commit stage’.

Remember: git-add just stages the changes for commit (i.e tells git which files to commit), the actual commit i.e. write to the repository happens when you issue the git-commit command.

Also checkout the git-reset command listed below; to learn, how to remove files from ‘commit stage’.

Command 5 : git-rm ; Few more things you should know.

The –cached switch:

When git-rm is used with this switch the file are removed from git’s repository but are no deleted from the disk.

Example:

$ git rm --cached
  path/to/file/or/folder

The above command will remove the particular file or folder but they will continue to exist on the disk. If you issue git-status command the file will be shown as deleted, as well as un-tracked.

git-rm without the –cached switch:

When used without the – -cached switch git-rm removes the file from the repository as well as from the disk.

You need to commit changes after using the git-rm command with or without the – -cached switch.

All isn’t lost!

Remember that the file(s) that you removed with git-rm and then commit changes, only apply to that commit. All your previous revisions will still have the removed file(s) or removed folder(s). So if you checkout previous revisions you can get the files or folder back.

You cannot alter the commit history(well! see git-reset below), as that goes against the version control system’s primary functionality; which is to preserve history of changes to the source code repository.

Command 6 : Managing branches.

We already know how to create, delete and move between branches. Here are a few more commands to help you manage your branches.

Creating a branch out of some older commit.

Suppose you want to move back to a particular commit and create a branch out of it. You can issue the following commands

$ git checkout commit_hash
$ git branch branch_name
$ git checkout branch_name

A convenient shortcut for the above command-set is:

$ git checkout
  -b branch_name commit_hash

The above command is exactly equivalent to the three commands listed before.

An example with the shortcut command:

$ git checkout
  -b bugfix_branch HEAD~2

The above command will create a branch named bugfix_branch which will be positioned at two commits bellow the current HEAD.

You can use the gitk gui to visually checkout the branches. Try it, it’s really cool! (Checkout the part one of this tutorial to know more about gitk).

Command 7 : git reset command

Clearing changes
You made some changes to your source code and realize you don’t need those changes. Maybe you were just testing something. What to do now?

Just use the following command to clear any changes and move back to the latest clean state, i.e. the last commit.

$ git reset –hard

BE VERY CAUTIOUS while issuing this command as it is IRREVERSIBLE.

This command will clear all un-committed changes and revert back all your files to the state reflected by your last commit.

The above command can be quiet useful if you do a merge between branches or from other people repository and get some conflicts; and you want to restore your repository back to the clean state. Just issue this command you’ll be back to the un-merged state.

Clearing files staged for commit
After adding files to the ‘commit stage’ you realize you do not want to commit those files. You can issue the following command to un-stage them.

$ git reset

This command un-stages files staged for commits ONLY, so all your changes are still there.

Deleting commits!

This command is provided for those rare cases where you REALLY REALLY DO need to ‘alter’ the history. Or for those occasional cases where you did some bad commits.

Issue the following command to delete a commit forever and ever.

$ git reset –hard HEAD~2

This command gets rid of the last two commits; kind of rewinds back to the third last commit.

If you have shared your repository, you are strongly discouraged to use this command. But as a single developer you can use it if really required.

Command 8: Garbage collection; Compress your repo and get some free space.

I’ve saved the simplest command for the end! Smile now 🙂

Issue the following command to make git compress your repository and clean it up.

$ git gc

This operation might take some time, a couple of minutes or so.

When finished, you’ll probably have more disk space 🙂

You should gc once is a while.

gc = garbage collection.

Yeah, that’s it! Just eight commands, but I’ve got two more things to make life easier for git users. So it adds up to ten right! 😉

Extra 1 : Ignore some files; .gitignore explained.

Not all files inside your project folder are required to be tracked by git. Some example include: object files, swap files etc.

Create a file name ‘.gitignore‘ in the base folder of your git repository and put in the name of files inside ‘.gitignore‘ as explained below, to make git ignore them.

Simple wild-cards are recognized.
*‘ and ‘?‘ wild-cards plus regular expression’s square bracket `[ ]‘ notation is recognized.

So, for example, if you want to ignore all files ending with the extension ‘bak‘ you can specify the following in your .gitignore file.

*.bak

The above line, if present, in your .gitignore files will ignore all files ending with ‘.bak’ anywhere inside the project folders.

Ignoring a particular file or folder using absolute path notation.

Suppose you have the following directory structure for your project, where “Project” is the base directory of your project’s repository.

Project
    |-- .gitignore
    |
    |-- .git
    |
    +-- Folder_1
    |
    +-- Folder_2
    |       |
    |       --- Folder_3
    |
    -- Folder_4
            |
            +--- File_1
            +--- File_2
            +--- File_3

Now suppose you want to ignore the folder `Folder_3‘ put following in your .gitignore file:

/folder_2/folder_3/

Notice the forward slashes at the beginning and at the end:

  • If you remove the preceding forward slash, git will ignore all folders named ‘folder_3‘ inside any folder named folder_2.
  • Removing the forward slash at the end will make git treat the name ‘folder_3‘ as a file name instead of a folder. So in case if we had a file named ‘folder_3‘ inside folder_2, that would be ignored.

Now, if you wanted to ignore the file File_2 as shown in the diagram above, you place the following in your .gitignore file:

/folder_4/file_2

Notice we did not put a forward slash at the end, if we had, git will treat the name ‘file_2‘ as a folder name.

So here are the rules, re-iterated.

  1. Absolute paths begin with a forward slash ‘/’.
  2. Folders names should always be suffixed with a forward slash ‘/’.
  3. File names should NEVER be suffixed with a forward slash ‘/’.

The use of square brackets as shown in the example below. They work very much like regular expression notation.

An example .gitignore file.

# git ignore file
# comments start with hash

# ignore all object files, all
# files ending either with '.o' or '.a'
*.[oa]

# Ignore all files with
# the extension *.swp
*.swp

# Ignore a single file
/folderA/folderB/build.info

# Ignore a folder named
# temporary in the base folder.
/temporary/

# Ignore folders named _object
#  anywhere inside the project.
_object/

What’s the use of .gitignore, after all?

Well, if still haven’t figured out let’s look at a typical coding scenario.

You have your project, added all files to your .git repository, and did and initial commit. Now you start editing some files, your favorite editor creates some backup files ending with ‘.bak’ extension.

Now you do git-status to check what all files changed, git tell you which files have been changed but also lists a the ‘.bak’ files as being untracked.

After few days, the list of untracked files becomes so long, that doing git-status scrolls past your screen. Not pretty right! 🙂

Also, you cannot do ‘git add .’ to stage files for commit, as this command will add the ‘.bak’ files to be committed as well, unnecessary bloating your repository.

What’s the solution? Just create a .gitignore file inside your project’s base directory and place a line containing ‘*.bak’ in it. Now do git-status; wow! neat ‘eh. 🙂

Extra 2 : Throw in some colors and introduces yourself to git.

Your name and email address in commits
Edit the file .git/config inside your base folder (project folder) and enter the following lines.

[user]
    name = Your Name
    email = your email Id

example:

[user]
    name = xk0der
    email = amit@xkoder.com

After placing these details in your file, the next time you commit any changes, your name and email id will be registered as the author of that commit.

Colorizing git’s output.

Gary has a small and simple post on how to get color’s in git’s output here:

http://scie.nti.st/2007/5/2/colors-in-git

That’s it! Go do some Git-fu.

In the next installment I intend to cover distributed git setup and few more commands. Till then enjoy! 🙂

Like this post?