Git is awesome! I used Subversion a lot when doing Java projects in school, and we used Mercurial at Qualtrics but Git is my favorite by far. I like Git and Mercurial over Subversion because of the distributed design: every repository contains the entire codebase and version data. Mercurial does have a little (IMHO very little) better command line interface than Git, but I feel Git has far more power especially when editing commit history (interactive rebase FTW). For a more in-depth discussion of Git vs. Mercurial (and why Git is better), I recommend this Atlassian blog post.
This post however is not about why Git is better than other DVCS. I would like to point out a few helpful commands that I use often (like all the time and you should to) or commands I wish I knew about when I first started using Git. I will skip some common commands like add, commit, push, merge, fetch, and pull and try to focus on non-essential but useful commands; the ones you could work without but really shouldn’t.
You probably know about this command, but some of you might under-utilize it. You should run $ git status at every opportunity! Every time I look at my terminal and even consider interacting with Git, I run that command. It shows what files you altered, what files are staged for the next commit, and what files are tracked/untracked by Git. I have a friend (*cough* Conan) who is an amazing engineer but occasionally (read “often” 🙂 pushes up commits that he forgot to add all the necessary files to. By using the status command all the time, he could easily see that some important files are not staged for the commit. Amazing code has to be in the repository before anyone can see it!
Fairly straightforward: $ git log shows the list of all commits in the repository complete with ID, author, date, and message. I look at the log every time I update my repo. One way I like to use it is to pass a filename to see which commit was the last to modify or delete a file:
$ git log -n 1 -- path/to/file
I mention it mostly to lay the groundwork for the next command.
Git IMO is one of the safest of all DVCS because it never lets you delete objects, only create new ones. Git also records everything you do! Modify files; record. Switch branches; record. Move around the history; record. Completely rewrite history; record. The objects are only deleted when they are garbage collected and they are only garbage collected when nothing references them anymore. The reflog is like the regular log except it shows what you have been doing outside of making commits. Specifically it records when the tip of branches change which might not sound interesting but includes every time you checkout, rebase, merge, or reset a branch. This is very useful when you accidentally force delete a branch , rewrite the history in a bad way, or navigate away from a commit that took you forever to track down. All it takes to make the pain go away is to reset to the HEAD position you were before your mistake.
I wish I knew about $ git stash, $ git stash list, and $ git stash pop back when I first started. OMG it is amazing! Ever wonder to yourself, “self, I really need to update my repository but I have changes that I do not want to throw away and are not ready to commit; what to do?!” Say hello to the stash command. It takes all the modifications that Git is tracking, and “stashes” them in a safe place. When you run git status next (which you should be doing all the time), you will see a clean working directory. To see the things in your stash, run $ git stash list. To bring your last set of changes back from the stash, run $ git stash pop. I encourage you to read the manual about the stash command because it is great.
I run $ git pull –rebase far more often than I do $ git pull. If you did not know, the pull command actually runs two sub-commands: git fetch and git merge. Using the –rebase flag changes the sub-commmands like so: git fetch and git rebase. The difference is rebase vs. merge. A merge will take your commits and combine them with any new commits in the repo with a merge commit; a new commit that combines the differences of your changes and new repo changes. IMO these merge commits clutter up the history. A rebase on the other hand will take your commits and just tack them on at the end of the history, after any new commits from the repo. This makes for a much cleaner history which is useful when you need to do reverts or investigate previous commits later on. Things to remember: (a)merge is more intuitive than rebase when handling conflicts and (b) you can mess up your collaborators if you use rebase to change commits that have already been pushed up. All these things can be learned, and the benefit really pays off.
Running $ git diff shows a blob of the changes for each file you altered in the working directory. It is handy after you have been working awhile or before you write your commit message to review what you actually edited. After looking at your changes you might even decide to use interactive staging to only commit certain changes in a file rather than the whole thing so that the commit is as logically cohesive as possible. You can also supply commit numbers or filenames to git diff and get specific change information. Handy command!
Knowing how $ git reset works is imperative to using Git. I normally use it with either the –soft or –hard flag (respectively keep or delete changes in my working tree) and then supply a commit ID to go back to. Read up on reset so you can take advantage of all its options and understand how it works to avoid mistakes when you do need to use it: because the day will come (or you wouldn’t have put your code in Git in the first place)!
How do you fix a commit that breaks your code it in a serious way but was pushed up and everybody now has in their repositories (and it is probably buried under several commits of their own)? You can not use reset because that would wreak havoc on everyone’s history. Revert to the rescue! Just pass the ID of the offending commit (or multiple commit IDs) to the revert command and Git will create a new commit that undoes the changes in that commit. Now you can push up the fix like a normal patch, because it is!
Git is great at making branches. Because branches just point to commits and commits are just snapshots of modifications, creating branches is a very lightweight process and you should do it often. Working in separate branches from the master is very helpful to avoid the update problem I used when introducing stashing. Also, if you use branches you have more information saved in your reflog which means that if something goes wrong it is easy come back from. Branches are a strong point of Git, so use them to the fullest!
When you use branches a lot, $ git cherry-pick <commit-id> becomes very useful. It lets you grab any commit and copy it over to your branch. This is useful if you only want a few select commits from a topic branch to merge in or maybe you ran a reset/revert on a set of commits and then realize you need one of them back. Check out the cherry-pick command!
These are some commands that not every Git user knows about or takes advantage of. The more I have used Git the happier I get. The control it gives you is incredible. At first, Git was just a tool I used to share code with coworkers/collaborators. Now it has become an integral part of my workflow. Even when I work on projects that I know will never be shared with another programmer, the first thing I do is create an empty Git repository. Thanks for all you do Git!