25 great tips for intermediate Git users

25 great tips for intermediate Git users

I had been using Git for about 18 months and thought I had a pretty good handle on it, but when we had Scott Chacon from GitHub come to LVS (a gaming/gaming software vendor/developer) to do a special training session, I learned a ton on the first day.

Since some people always feel good about using Git, I wanted to share some of the Git gems I got from the community, so that it might help those people find the answer directly without wasting a lot of research time.

Basic Tips

1. The first step after installation

After installing Git, the first thing you should do is to configure your name and email address, as this information is required for every commit:

  1. $ git config --global user.name "Some One"  
  2. $ git config --global user.email "[email protected]"  

2. Git is pointer-based

Everything stored in git is contained in a single file. When you commit, git creates a file containing the commit message and relevant data (name, email, date/time, last commit, etc.), and links it to a tree file. The tree file contains a list of objects or other trees. The object or binary large data object (BLOB) is the real content of the commit (a file, if you will, although the file name is not stored in the object, but it is stored in the tree). All of these files are stored with the SHA-1 hash of the object as the file name.

Branches and tags are just files that contain (basically) a SHA-1 hash pointing to a commit. Using these references gives you a huge increase in flexibility and speed; creating a new branch is as simple as creating a file with the branch name and a SHA-1 hash pointing to the commit you're branching from. Of course, you'll never do this when using the Git command-line tool (or a GUI), but it's that simple.

You may have heard of references to HEAD. It's just a file that contains a SHA-1 reference to your current commit. If you're resolving a merge conflict, take a look at HEAD and you'll see that it has nothing to do with a specific branch or point on a branch, just where you are right now.

All branch pointers are stored in .git/refs/heads, HEAD is in .git/HEAD, and tags are in .git/refs/tags - feel free to look around.

3. Two Parents - Of course!

When viewing a merge commit's message in the log file, you'll see two parents (compared to a normal commit). The first parent is the branch you were on, and the second parent is the branch you merged into.

4. Merge Conflicts

By now I'm sure you have a merge conflict that needs to be resolved. Usually this is done by editing the file, removing the <<<<, ====, >>>> markers from the file, and then saving the code you want to keep. Sometimes it's advisable to review the code before making any changes, for example, before you take action on two conflicting branches. Here's another command:

  1. $ git diff --merge
  2. diff --cc dummy.rb
  3. index 5175dde,0c65895..4a00477
  4. --- a/dummy.rb
  5. +++ b/dummy.rb
  6. @@@ - 1 , 5 - 1 , 5 + 1 , 5 @@@
  7. class MyFoo
  8. def say
  9. - puts "Bonjour"  
  10. - puts "Hello world"  
  11. ++ puts "Annyong Haseyo"  
  12. end
  13. end

If the files are binary, file comparison is not so easy... What you usually do is try each version of the binary and decide which one to use (or manually copy parts of it in a binary editor). Pull a copy of the files from a specific branch (if you are merging master with branch 132):

  1. $ git checkout master flash/foo.fla # or...
  2. $ git checkout feature132 flash/foo.fla
  3. $ # Then...
  4. $ git add flash/foo.fla

Another way is to view this file from git - you can view it as a different file name, then copy the correct file (when you're sure which one it is) to the normal file name:

  1. $ git show master:flash/foo.fla > master-foo.fla
  2. $ git show feature132:flash/foo.fla > feature132-foo.fla
  3. $ # Check out master-foo.fla and feature132-foo.fla
  4. $ # Let 's say we decide that feature132' s is correct
  5. $ rm flash/foo.fla
  6. $ mv feature132-foo.fla flash/foo.fla
  7. $ rm master-foo.fla
  8. $ git add flash/foo.fla

Update: Thanks to Carl for the heads-up in an earlier blog comment, you can actually use "git checkout --ours flash/foo.fla" and "git checkout --theirs flash/foo.fla" to check out a specific revision without having to remember which branch you were merging into. I personally prefer to be more explicit, but take your pick...

After resolving the merge conflicts (like I did above), remember to add this file to the index.

Servers, branches, and tags

5. Remote Server

One of the most powerful features of Git is the ability to have more than one remote server (another fact to the fact that you can always run a local repository). You don't always need write access, you can read from multiple servers (for merging) and write to another. Adding a remote server is simple:

  1. $ git remote add john git @github .com:johnsomeone/someproject.git

If you want to view information about the remote server, you can do this:

  1. # shows URLs of each remote server
  2. $ git remote -v
  3.  
  4. # gives more details about each
  5. $ git remote show name

You can view the differences between your local branch and the remote branch:

  1. $ git diff master..john/master

You can also view changes to HEAD that is not on a remote branch:

  1. $ git log remote/branch ..
  2. # Note: no final refspec after ..

6. Tags

There are two types of tags in Git - a lightweight tag and an annotated tag. Remember from the second tip that Git is pointer-based, the difference between the two is simple. A lightweight tag is nothing more than a named pointer to a commit. You can change it to point to another commit. An annotated tag is a named pointer to a tag object, which has its own message and history. The tag object's message can be cryptographically signed with GPG if desired.

Creating both types of tags is easy (just a difference in command line options):

  1. $ git tag to-be-tested
  2. $ git tag -a v1. 1.0 # Prompts for a tag message

7. Create a branch

Creating branches in Git is very easy (lightning fast, since it only requires creating a file of less than 100 bytes). The general syntax for creating a new branch and switching to it is:

  1. $ git branch feature132
  2. $ git checkout feature132

Of course, if you know you want to switch to it right away, you can do it with a single command:

  1. $ git checkout -b feature132

If you want to rename a local branch, it's also easy (the long command is used to show the specific execution process):

  1. $ git checkout -b twitter-experiment feature132
  2. $ git branch -d feature132

Update: Or you can (as Brian Palmer points out in the blog post comments) do it all in one go by just using "git branch" with the -m option:

  1. $ git branch -m twitter-experiment
  2. $ git branch -m feature132 twitter-experiment

#p#

8. Merge branches

At some point in the future, you will want to merge your changes. There are two ways to do this:

  1. $ git checkout master
  2. $ git merge feature83 # Or...
  3. $ git rebase feature83

The difference between merge and rebase is that merge tries to resolve changes and create a new commit that is amalgamated, while rebase tries to reproduce the changes you made since last time on the other branch, on top of the other branch's HAED. However, do not rebase after you have pushed a branch to a remote server - this will cause confusion/problems.

If you are unsure which branches still have independent work in progress - so that you know which branches you need to merge and which branches you need to delete, the git branch command has two options to help with this:

  1. # Shows branches that are all merged in to your current branch
  2. $ git branch --merged
  3.  
  4. # Shows branches that are not merged in to your current branch
  5. $ git branch --no-merged

9. Remote branches

If you have a local branch that you want to make available on the remote server, you can use a push command:

  1. $ git push origin twitter-experiment:refs/heads/twitter-experiment
  2. # Where origin is our server name and twitter-experiment is the branch

Update: Thanks to Erlend for pointing this out in the comments to the blog post - this is effectively the same as git push origin twitter-experiment , but by using the full syntax you can see that you're actually using different names on both ends (your local name might be add-ssl-support, and the remote name might be issue-1723).

If you want to delete a branch on a remote server (note the colon before the branch name):

  1. $ git push origin :twitter-experiment

If you want to show the status of all remote branches, you can view them like this:

  1. $ git remote show origin

This may list some branches that used to exist on the server but no longer exist. If this is the case, you can easily check them out locally and delete them with the following command:

  1. $ git remote prune

Finally, if you have a remote branch that you want to track locally, the usual approach is:

  1. $ git branch --track myfeature origin/myfeature
  2. $ git checkout myfeature

However, newer versions of Git will automatically set up tracking if you check out using the -b flag:

  1. $ git checkout -b myfeature origin/myfeature

Saving content in temporary storage, index, and file system

10. Stashing

In Git, you can store the current working state in a temporary storage area called a stack and reuse it later. A simple example is as follows:

  1. $ git stash # Do something...
  2. $ git stash pop

Many people recommend using git stash apply instead of "pop", however if you do this you will end up with a long list of useless stashes. If you clean it up, "pop" will just remove it from the stash. If you have already used git stash apply, you can remove the last item from the stash with the following command:

  1. $ git stash drop

Git will automatically create a comment based on the current commit message. If you prefer to use a custom message (because it may not be relevant to the previous commit):

  1. $ git stash save "My stash message"  

If you want to make use of a specific stash from your list (not necessarily the last one), you can list them and use it like this:

  1. $ git stash list
  2. stash@{ 0 }: On master: Changed to German
  3. stash@{ 1 }: On master: Language is now Italian
  4. $ git stash apply stash@{ 1 }

11. Interactive Addition

In the Subversion world, you modify files and then commit only the changed files. In the Git world, you have more control over committing certain files or even certain patches. In order to commit certain files or certain parts of files, you must enter interactive mode.

  1. $ git add -i
  2. staged unstaged path
  3.  
  4.  
  5. *** Commands ***
  6. 1 : status 2 : update 3 : revert 4 : add untracked
  7. 5 : patch 6 : diff 7 : quit 8 : help
  8. What now>

This will put you into a menu-based interactive command mode. You can use the command number sign or the highlighted character (if you have color highlighting turned on) to enter the corresponding mode, and then it's a matter of entering the file number normally (you can use a format like 1 or 1-4 or 2,4,7).

If you want to enter patch mode (type 'p' or '5' in interactive mode), you can also enter that mode directly:

  1. $ git add -p
  2. diff --git a/dummy.rb b/dummy.rb
  3. index 4a00477..f856fb0 100644    
  4. --- a/dummy.rb
  5. +++ b/dummy.rb
  6. @@ - 1 , 5 + 1 , 5 @@
  7. class MyFoo
  8. def say
  9. - puts "Annyong Haseyo"  
  10. + puts "Guten Tag"  
  11. end
  12. end
  13. Stage this hunk [y,n,q,a,d,/,e,?]?

As you can see, at the bottom you get a series of options to choose to add only the changed parts of the file, all changes to the file, etc. Use the '?' command to get an explanation of the different options.

12. Store/Retrieve from File System

Some projects (such as the Git project itself) store additional files directly in the Git file system without necessarily checking them in.

Let's start by storing an arbitrary file in Git:

  1. $ echo "Foo" | git hash-object -w --stdin
  2. 51fc03a9bb365fae74fd2bf66517b30bf48020cb

At this point, the file object is in the database, but if you don't set (something) to point to that file object, it will be garbage collected. The easiest way to do this is to mark it:

  1. $ git tag myfile 51fc03a9bb365fae74fd2bf66517b30bf48020cb

Now that we have marked myfile here, when we need to get the file, we can do it like this:

  1. $ git cat-file blob myfile

This is useful for programmers who may frequently use utility files (passwords, GPG keys, etc.) without having to check them out to disk every time (especially in a production environment).

Logging

13. View logs

If you don't use 'git log' to see the recent commit history, you won't be able to use Git successfully for long. However, there are some suggestions on how to use it better. For example, you can view a patch changed in each commit:

  1. $ git log -p

Or you can just see an overview of which files have changed:

  1. $ git log --stat

You can set up a nice alias for showing short commits and a nice branch graph with messages in one line (like gitk, but on the command line):

  1. $ git config --global alias.lol "log --pretty=oneline --abbrev-commit --graph --decorate"  
  2. $ git lol
  3. * 4d2409a (master) Oops, meant that to be in Korean
  4. * 169b845 Hello world

#p#

14. Retrieve Logs

If you want to query the logs for a specific author, you can specify that like this:

  1. $ git log --author=Andy

Update: Thanks to Johannes's comment, I finally solved some of the confusion.

Or if you have a search term that appears in the commit message:

  1. $ git log --grep= "Something in the message"  

There is a more powerful command called pickaxe, which finds entries for adding or removing a particular content (that is, when it first appeared or was removed). This lets you know when a line was added (but you won't know if the characters in that line are subsequently changed):

  1. $ git log -S "TODO: Check for admin status"  

What happens if you change a specific file, say lib/foo.rb

  1. $ git log lib/foo.rb

For example, if you have a feature/132 branch and a feature/145 branch, and you want to see the commits that are on these branches but not on the master branch (note: ^ stands for not):

  1. $ git log feature/ 132 feature/ 145 ^master

You can also use the ActiveSupport style dates to narrow the date range:

  1. $ git log --since= 2 .months.ago --until= 1 .day.ago

It uses OR mode to combine queries by default, but you can easily change it to AND mode (if you have more than one query term)

  1. $ git log --since= 2 .months.ago --until= 1 .day.ago --author=andy -S "something" --all-match

15. Select the version to view/modify

When referencing a revision, you have a number of options, depending on how familiar you are with the feature:

  1. $ git show 12a86bc38 # By revision
  2. $ git show v1. 0.1 # By tag
  3. $ git show feature132 # By branch name
  4. $ git show 12a86bc38^ # Parent of a commit
  5. $ git show 12a86bc38~ 2 # Grandparent of a commit
  6. $ git show feature132@{yesterday} # Time relative
  7. $ git show feature132@{ 2 .hours.ago} # Time relative

Note that, unlike in the previous section, the caret at the end of the line indicates the parent of the commit - a caret at the beginning indicates that it is not on this branch.

16. Select a range

The simplest way is to use it like this:

  1. $ git log origin/master.. new  
  2. # [old]..[ new ] - everything you haven't pushed yet

You can also remove [new], which will use the current HEAD.

Time rollback and bug fixes

17. Reset changes

If you haven't committed a change yet, you can easily reset it:

  1. $ git reset HEAD lib/foo.rb

It is usually better to use 'unstage' as an alias because it is less obvious.

  1. $ git config --global alias.unstage "reset HEAD"  
  2. $ git unstage lib/foo.rb

If you have already committed the file, you can do two things - if it is the last commit, you can modify it like this:

  1. $ git commit --amend

This will roll back the last commit, returning your working copy to the state where the changes were stored in the staging area, and you can edit the commit message and prepare the next commit.

If you have more than one commit and just want to roll them back completely, you can reset the branch to go back to an earlier point in time.

  1. $ git checkout feature132
  2. $ git reset --hard HEAD~ 2  

If you really want to point a branch to a completely different SHA-1 (perhaps you're pointing the HEAD of one branch to another branch, or one commit further), you can do it in the following way:

  1. $ git checkout FOO
  2. $ git reset --hard SHA

There is actually a more convenient way (because it doesn't change your working copy back to the original FOO state and then point to the SHA):

  1. $ git update-ref refs/heads/FOO SHA

18. Committing to the wrong branch

OK, let's assume you're committed to the master branch, but you should have created a topic branch called experimental. To remove those changes, you can create a branch at the current point, revert HEAD, and then check out the new branch:

  1. $ git branch experimental # Creates a pointer to the current master state
  2. $ git reset --hard master~ 3 # Moves the master branch pointer back to 3 revisions ago
  3. $ git checkout experimental

This is more complicated if you have made changes on a branch on a branch on a branch on a branch etc. Then all you need to do is rebase the changes on that branch onto the other one:

  1. $ git branch newtopic STARTPOINT
  2. $ git rebase oldtopic --onto newtopic

19. Interactive rebasing

This is a cool feature that I've seen demonstrated before but didn't really understand at the time, but now it's actually pretty simple. Let's say you've made 3 commits, but you want to reorder or edit them (or merge them):

  1. $ git rebase -i master~ 3  

Then you have your editor open. All you have to do is change the "pick/squash/edit" instructions to how you want to commit, then save/exit. After editing, you can use git rebase --continue to run your instructions one by one.

If you choose to edit a file, this will leave you at the state you committed, so you will need to use git commit --amend to edit it.

Note: Do not commit work during a REBASE - only add and then use the --continue, --skip or --abort options.

20. Clean up

If you have already committed some content to your branch (perhaps you imported it from an old repository in SVN), and you want to remove all commits from your history:

  1. $ git filter-branch --tree-filter 'rm -f *.class' HEAD

If you've already pushed code to the remote server, but have committed garbage since then, you can do this on your local system before pushing:

  1. $ git filter-branch --tree-filter 'rm -f *.class' origin/master..HEAD

#p#

Various tips

21. Quotes you have read before

If you know you've viewed a SHA-1 before, but you've done some resetting/rewinding work, you can use the reflog command to see the most recently viewed SHA-1:

  1. $ git reflog
  2. $ git log -g # Same as above, but shows in 'log' format

22. Branch Naming

A nice little tip - remember that branch names are not limited to characters like az and 0-9. You can use / and . in the name to disguise the namespace or revision number, for example:

  1. $ # Generate a changelog of Release 132  
  2. $ git shortlog release/ 132 ^release/ 131  
  3. $ # Tag this as v1. 0.1  
  4. $ git tag v1. 0.1 release/ 132  

23. Find out who is the culprit

It is often used to find who changed a line of code in a file. The simple command is as follows:

  1. $ git blame FILE

Sometimes the changes come from a previous file (if you've merged two files, or you've moved a function), so you can use:

  1. $ # shows which file names the content came from
  2. $ git blame -C FILE

Sometimes it's nice to track changes by clicking forward or backward. There's a built-in GUI program designed for this:

  1. $ git gui blame FILE

24. Database Maintenance

Git does not usually require a lot of maintenance and is mostly self-maintaining. However, you can view database statistics using the following command:

  1. $ git count-objects -v

If the number is high, you can choose to garbage collect your duplicates. This won't affect pushes or other users, but it will make your commands run faster and take up less space:

  1. $ git gc

It is also recommended to run consistency checks frequently:

  1. $ git fsck --full

You can also add an --auto parameter to the end of the line (if you run it frequently, or run it daily from crontab on your server) to only fsck the command if statistics indicate that a consistency check is necessary.

If the check says "dangling" or "unreachable" everything is fine, this is often the result of reverting HEAD or rebasing. If the check says "missing" or "sha1 mismatch" something is wrong... get professional help!

25. Recovering a lost branch

If you deleted a branch experimental using the -D option, you can recreate it:

  1. $ git branch experimental SHA1_OF_HASH

You can use git reflog to discover a SHA-1 hash if you have visited it recently.

Another way is to use git fsck --lost-found. A dangling commit is a lost HEAD (it would only be the HEAD of a deleted branch, since when a HEAD^ is referenced by a HEAD, it is not dangling)

<<:  Don’t miss it if you pass by! Four major impacts of Windows 10 on web design

>>:  Listen to stories and learn Swift series - Xiao Ming and the red envelope (optionals - optional type)

Recommend

Speculate on some trends in content marketing in 2017!

Some time ago, the Content Marketing Institute (C...

The God of Wealth we worship actually has so many identities!

It is the day to welcome the God of Wealth on the...

Nine blogs to watch for hybrid mobile app developers

[51CTO.com Quick Translation] The rapid populariz...

Formulas, logic and methods of 8 super hit product cases

Chinese companies are facing an inflection point ...

In 2018, here are 3 essential marketing promotion rules!

Where will marketing go in 2018? Consumption and ...

I gained 180,000 followers in 3 days. How did I plan the free giveaway?

Editor's note: When running an event , giving...