1. 程式人生 > >Git Tips and Tricks

Git Tips and Tricks

Git Tips and Tricks

When I think about Git, I think about it as a time capsule one can travel with at a specific point of time in the source code history. Even though it might seem pretty simple, Git is an extremely powerful tool which, handled inappropriately, could generate a big mess. I strongly consider that before you start playing and trying different tricks with Git, you should have an understanding of its basics, knowing how it works under the hood and mastering the basic commands (e.g. init, clone, push, pull, status, log, etc) and concepts (e.g. repository, branch, commit, etc).

Here, at Algotech Solutions, we love the tips and tricks that make developer life easier. So, considering you are already familiar with Git, here is the list of some of the most interesting and helpful Git tips and tricks I am aware of.

But… Let’s define some key concepts first:

  • public branch — a branch where several people push the changes to (with or without using merge/pull requests);
  • private branch — a branch where only you (and only you) push changes to.

Reflog. Revert operations

I remember that in the beginnings, when I started to use Git, I was sometimes afraid of running commands. I was thinking that could mess the things up. For instance, the first contact with git rebase command generated a few “Ohh ****! What did I just do?!” reactions due to some duplicated or even missing commits. Ah,

to be young and inexperienced! Soon after that, I realized that I should start getting deeper into the Git world.

Personally I don’t like using a tool in a certain manner just because this way I will get the result I expect, without knowing what exactly happened on the way to that result. It’s like driving a car without a steering wheel. Everything is working fine until you reach the first curve on the road. What will happen next? I guess you can imagine.

So, back to our business, it’s extremely important to understand the concepts. I read a lot about Git, followed some tutorials, and the Git world completely changed into my mind. While using it, I started to feel more comfortable than before, and more important I started becoming aware of its power. And one of the most powerful things Git is offering is git reflog command.

Using reflog you can go back in time by reverting almost every action you already have done with Git. Even if it was a rebase or a reset or other “damaging” operations. Even more, it’s extremely easy to use it. In order to prove that to you, let’s take a quick example.

$ git reflog
1234571 [email protected]{0}: checkout: moving from one-branch to another-branch
1234570 [email protected]{1}: merge origin/feature-branch: Fast-forward
1234569 [email protected]{2}: commit: The commit message goes here.
1234568 [email protected]{3}: reset: moving to 2234567
1234567 [email protected]{4}: rebase finished: returning to refs/heads/one-branch

To move to the desired state you just need to run a checkout using the absolute reference:

$ git checkout 1234568

or using the relative reference:

$ git checkout [email protected]{3}

and you will end up in a detached HEAD state you could create a new branch from.

And that’s it. All the operations from the specified state above are now reverted.

Revert. Undo commit changes

You must have encountered at some point the situation when the revert of some changes to be requested. Here comes to help the revert command:

$ git revert <commit>

What does it do? It just reverses the effect of some earlier commits (often faulty ones) by creating a new commit containing all the reversed changes. Simple as that. Why use it instead of other practices for reverting changes? It’s the only way to do a safe changes revert, since it doesn’t alter the commit history. It is usually used for reverting changes on public branches where changing the commit history is inadvisable.

Let’s take a look at the other commands capable of doing reverts.

Rebase

$ git rebase --interactive

You can run this command and then just remove the line(s) corresponding to the unwanted commit(s). This way they will disappear. It’s probably the simplest and cleanest solution for doing commit reverts. However it has one flaw which prevents you from using it on public branches: it alters the commit history.

Reset

$ git reset --hard <commit>

It removes all the commits applied on top of the specified one. It’s a cleaner way to revert changes, but the commit history is changed as well, so it’s generally applicable only on private branches.

Checkout

$ git checkout <commit> -- <filename>

It brings the specified file to an earlier version (of the specified commit). You will have the revert changes in the working directory, so you can commit them as you wish.

The conclusion is that even though it’s not the cleanest one, git revert is the “safest” operation to do a revert. Also, please note that the above commands are not 100% interchangeable (in regards to the cases they can treat), but there are situations when all four approaches lead to the same result.

Log. Prettier format

When it comes to git log things are pretty simple. You just type in the command, hit enter, and the commit history will show up, the commit history being the list of the commits presented in a chronological descending order, each with its summary including by default the commit hash, the author, the date when the commit has been recorded, and the commit message. Not much information, but presented in a sprawling way (6 rows for each commit).

Let’s take a look at the default log:

$ git log
commit 01ba45f789abcdef9876543210fedcba01234567
Author: First Last <[email protected]>
Date: Wed Apr 13 17:00:01 2016 +0300
The last commit message goes here.
commit 01cf4a6789abcdef9876543210fedcba01234568
Author: First Last <[email protected]>
Date: Wed Apr 12 16:24:03 2016 +0300
Another commit message goes here.
commit 01bf456789abcdef9876543210fedcba01234568
Author: First Last <[email protected]>
Date: Wed Apr 11 15:30:33 2016 +0300
Another commit message goes here.

I would say that it could be better.

I generally prefer to keep things simple. So, instead of using the basic log command, I add two options:

  • — oneline — shows each commit on a single line. Besides, instead of displaying the full 40-byte hexadecimal commit ID, it shows only a partial prefix.
  • — decorate — prints out the ref names of any commits that are shown.

Here is how it looks like:

$ git log --oneline --decorate
01ba45f (HEAD -> origin/feature-branch, feature-branch) The last commit message goes here.
01cf4a6 Another commit message goes here.
01bf456 (origin/dev, dev) Another commit message goes here.

The option “–oneline” reduces the space in rows filled by a commit from 6 rows to just one. The option “–decorate” adds the ref names (local/remote branches + HEAD). Using this two options we create a more compact and meaningful git log. We still have the commit hash in its short version, we don’t have the author and the commit date, but generally these are things we don’t care about. We still have the commit message, and, even more, we have the refs which are often useful. All of this in just one line. Much better than the default one, don’t you think?

Well… Though there are cases when we need to know the commit date and the author. In order to add those I prefer to use:

$ git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate

Wow! There’s a pretty complicated command, right? First, let’s see how it looks like:

$ git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate
01ba45f The last commit message goes here. (HEAD -> origin/feature-branch, feature-branch) 3 seconds ago [First Last]
01cf4a6 Another commit message goes here. 1 day ago [First Last]
01bf456 Another commit message goes here. (origin/dev, dev) 2 days ago [First Last]

Pretty good I’d say. There’s a color format also to help you better distinct different types of information, but I can’t show it to you here.

Ok, but it would be a real burden to type the above command in every time you need it, right? So, let’s do the alias trick. In regards to aliases I personally considered them an extremely simple, but powerful technique to burst your productivity. So, in order to register the aliases we will add the following lines to “~/.bash_profile” (considering you are using a *nix OS):

alias gl='git log --oneline --decorate'
alias gh='git log --color --pretty=format:"%C(yellow)%h%C(reset) %s%C(bold red)%d%C(reset) %C(green)%ad%C(reset) %C(blue)[%an]%C(reset)" --relative-date --decorate'

We are now reaching the point where we have not only a better look for the commit history, but we are having a quicker way to access it by using the commands gl and gh. For more ideas on how to make dull histories look better and say more at a glance, you can check out our article on 10 bite-sized tips and tricks for improving your development process.

Diff. Between commits, cached

Git diff is a pretty straightforward command showing the differences between the working tree and the last commit, in it’s basic form git diff. I guess you already know how it looks like.

It can be used though for slightly different scenarios or needs. One of them is showing the differences between two commits:

$ git diff <commit1>..<commit2>

Another one is showing the differences between the last commit and all the changes from the working directory and the staging area. I found this useful when I don’t necessarily want to unstage some files, but I still want to compare all the changes within them. In order to do that we will have to run:

$ git diff --cached

These are two simple ways to use git diff which helps you to cover a couple of diff scenarios encountered even in a basic use of git.

Branch. Check which branch is merged

I suppose you already know what the basic git branch command does, so I will get straight to the point. The following is a simple, but often useful command. I guess that at least once in a while it is the time to clean up branches. I mean to cut the old and/or redundant branches from your git “tree”.

Using the following:

$ git branch --merged

you get the list of all branches already merged with the currently checkout branch.

You can also do the opposite:

$ git branch --no-merged

to get the list of all the unmerged branches.

So, you will always know if it’s safe or not to remove a specific branch.

Checkout. Fetch a file from another branch

It probably happened to you to need a specific version for a file, version found on another branch which was not merged at that moment. Do you remember the checkout command listed above that brings the file to one of its earlier versions? It looks like this:

$ git checkout <commit> -- <filename>

The specified file will have the version specified by the commit argument after the above command is run. We used it earlier in the context of the same branch when we wanted to revert some of the changes applied to the file. The good thing is that the specified commit does not necessarily need to belong to the currently checkout branch. So, you can bring whatever version you want for whatever file you want from whatever commit (branch) you want.

The alternative I could see is to:

  • stash your eventual changes from the working tree;
  • checkout the commit where the version you need is;
  • copy the code;
  • go back to the original branch (checkout);
  • pop the stash;
  • paste the code you just copied into the file.

It’s not complicated, but a waste of time, since all these steps could be squashed into a single one.

I hope you have found some useful stuff in here that could help you to become more proficient and productive in regards to Git usage. Please note that all the things I presented have a considerably dose of subjectivity, since they are mold to my needs so far.