git
is the most used version-control tool today.
Although having a learning curve, the command line will always be the most powerful way to use it.
Here I want to share a few git
features and conclusions that make my experience with git
really great.
1. Interactive mode
Use git add -p
to be guided through all your changes and select which ones you want to add.
It does not work with untracked files though, only modified.
The -p
flag also works with git reset
to unstage, git checkout
to discard, and git stash
to partially stash.
An unknown, yet in my opinion essential git feature.
2. Inspecting changes on a branch
Seeing the changes on my branch
How to show the changes of my branch, like in Github PR view ?
You can use the 3 dot comparison to display only how you branch deviates from the common parent of your branch and master.
git diff origin/master...HEAD
Seeing the changes on my branch commit-by-commit
git log -p --reverse HEAD ^origin/master
3. Understanding how branches are related
git log --graph
I recommend setting this alias: git alias logg=log --oneline --graph
.
Now, try it with your feature branch based on a previous ref of master: git logg origin/master feature3
.
* b6c4aea (HEAD, origin/master) feat: add frontend
| * fa165b8 (feature3) fix foo
|/
* 12ea3bc feat: create bar
* 05c7cb7 feat: create foo
You can even specify multiple branches with git logg origin/main feature-branch feature-branch2
, or see all branches with git logg --branches
.
This is especially handy with multiple levels of branches based on other branches.
git cherry
If you did a few cherry-picks here and there, you may end up with branches that are similar but different.
To compare them, git cherry
shows a list of differing commits.
4. Advanced rebasing
Rebasing from one base to another
You can rebase commits from one parent onto another, with the --onto
flag.
git rebase <source-upstream> --onto <target-upstream>
Useful especially to rebase a feature branch on top of another feature branch which got squash-merged.
Patching previous commits
Although there are some fair arguments against rewriting history in git, very often, you just want to make your log of changes understandable for your reviewers, and avoid redundant steps.
12ea3bc (HEAD -> my-branch) feat: create bar
05c7cb7 feat: create foo
05c7cb7 (origin/master) initial commit
Let's say you made a mistake on create foo
.
To edit it, you can create fixup or squash commits.
Once your code corrected, do git commit --fixup 05c7cb7
(the value of --fixup
being the hash of the commit that is being fixed).
It will create a commit named fixup! feat: create foo
, which carry a special meaning.
When doing the rebase,
git rebase -i --autosquash origin/master
...the editor will immediately show the following rebase instructions.
pick 05c7cb7 feat: create foo
fixup 957113f fixup! feat: create foo
pick 12ea3bc feat: create bar
Which are exactly what we want, and it's ready to be applied. The fixup!
commit we created is identified as fixup
next to its target commit.
This technique is quite useful, but requires some care, and is not always worth it.
5. Merge strategy: merge commit vs squash
Squash for all ephemeral branches, merge commits for branches which will keep on existing.
A squash makes the history log simpler/cleaner on the main branch, but does not have any notion of relationship with the source branch.
Once squashed merged, the two merged branch and the main branch are in conflict.
6. git stash
Stashing is for temporarily putting your changes in a drawer.
You can then restore them anytime.
As said below, it's incredibly useful to do partial interactive stash with git stash -p
.
You can list stashes with git stash list
.
And perform the following operations:
git stash show -p
: show the detail of a stashgit stash apply
apply the stash on your working tree but leave it existinggit stash pop
: apply the stash and remove itgit stash drop
: remove the stash
You can specify the stash number, for example git stash show -p 3
.
If none is provided, 0 is assumed, i.e. the latest stash.
7. Using the CLI
You're missing out if you use the default basic shell.
I like using ohmyzsh. It has tons of features I don't use, but what I like is:
- history search (with up arrow) filtered by the beginning of input
- git aliases
gst
:git status
gf
:git fetch
gco
:git checkout
gd
:git diff
gp
:git push
grb
:git rebase
glo
:git log
- and many more...
I did not use them in this article for better readability.
Using the pager
A pager is a basic terminal tool used to browse through long content in a more user-friendly manner.
The default pager on most systems is less
, and more
.
You'll use it very often,
A few very useful features are:
Q
to quit :)Ctrl+D
to go down half a screen, andCtrl+U
to go up./foo
searchesfoo
in the displayed content. Usen
andN
to go to next and previous instance.
Knowing how to effectively use it is great for productivity. Many of them are similar to vim
, of which I also recommend to learn the basics.
8. Avoiding local branches
If you're using Github or another system for managing git repositories, chances are you use a workflow with a master branch, branches, and pull-requests.
In that case, the master branch is a remote branch, that you'll never edit directly locally.
Which means, you should not have a local master branch.
You can delete your local branch with git branch -d master
.
And then only use origin/master
.
Which approach is simpler ?
git checkout master
git pull
git checkout my-feature-branch
git rebase master
git fetch
git rebase origin/master
It just makes much more sense to use remote branches when possible. You can update all of them anytime with just git fetch
.
Why ?
The first time you checkout a remote branch using git checkout some-branch
, you are greeted with this message.
branch 'some-branch' set up to track 'origin/some-branch'.
Switched to a new branch 'some-branch'
A local branch is created from the remote branch. It is on your system, and under your responsibility. You have to keep it up-to-date, and delete it when you don't need it anymore.
Local branches exist so that their state can differ from their remote state.
In other words, they are useful only when you modify a branch.
Removing branches of which remote branches have been removed
By default, git
doesn't remove remote branches even when they have been deleted remotely (for example in case of a merged PR).
To remove all removed remote branches, You need to add this option to git fetch
:
-p, --prune
After fetching, remove any remote-tracking branches which no longer exist on the remote.
Then, you'll see with git branch -v
that your local branches are still there, but are marked as [gone]
. To remove them, you can use this script:
git branch -D $(git for-each-ref --format='%(if:equals=[gone])%(upstream:track)%(then)%(refname:short)%(end)' refs/heads)
Conclusion
Git plays a central part in development workflows.
Don't stop at the basics, and feel free to explore and find ways to optimize the way you use it.
Mastering the tools you use daily is worth the investment.