Low Hanging Fruit
Here are some tips that you can immediately start incorporating into your daily work for some quick wins.
Version everything
Even if you just work on something alone, put it in a Git repository.
If you don't require it to be hosted online, you can still get all the
benefits of versioning and safe rollbacks when using just a local
repository. Many developers think of a local repository as something you
get from cloning a remote repository, but creating a local repository
using git init
is an underrated habit.
Quickly switch between branches using git switch -
Normally you would switch to another branch using git switch my-other-branch
. If you need
to temporarily switch to another branch, or checkout some commit (git checkout a1b2c3d
),
then at the point when you need to switch back you might need to look up the branch name again
(for example using git reflog
) and then type the possibly long branch name. Instead,
you can switch to the last branch using git switch -
. You can also use it to quickly
switch back and forth between two branches.
Visualize the commit graph in the terminal
Although Git GUIs have their place, there's no need to use them just to get a nice visualization of the commit graph.
You're probably familiar with git log
. You can use git log --graph
to render a nice
and clear graph in the terminal.
Most UNIX-like terminals use less
for paging by default, so you can
page up and down using the "u" and "d" keys, scroll (in smaller steps) with the up and down arrow keys, and close with the "q" key.
If you use Git via the CLI but don't frequently use git log --graph
or
git log --graph --oneline
, you might want to make it a habit, especially
before and after operations like merge, rebase, force push, etc. It halps you
quickly see where you are and what's happening around you.
Avoid unnecessary branch switching
Many developers, when rebasing my-feature
onto main
(or merging main
into my-feature
),
would start by switching to main
, then git pull
, then switch back to my-feature
, and
finally git rebase main
(or git merge main
).
Instead, make it a habit to simply do git fetch
and then git rebase origin/main
(or
git merge origin/main
). This way you not only avoid unnecessary extra steps, but it's also
more semantically correct:
- The intent is to rebase on
origin/main
, notmain
, and updating the localmain
ref is something many developers do because they misunderstand remote tracking branches. - Commit messages that Git automatically creates for merge commits will say Merge remote-tracking branch 'origin/main' into my-feature, which correctly communicates the intent.
- A common mistake is to accidentally merge an outdated local
main
branch. The less complicated the workflow and the fewer extra steps, the less likely you forget an importent step. - You're also less likely to accidentally commit directly to
main
(assuming a workflow where that is not your goal). You probably don't need a local branch that you never intend to commit to.
Whenever you need to checkout the current state of origin/main
, you can always do
git checkout origin/main
, then create a new branch from there or switch back to your
feature branch afterwards. There's no need to keep a redundant local branch just to "track"
your remote tracking branch (you probably see the irony in manually tracking a tracking branch).
If git checkout origin/main
looks weird or dangerous to you because you think it lets
you actually work in a remote repository, make sure to (re-)read the fundamental chapters
on refs and remotes.
Enable rerere
rerere stands for "reuse recorded resolution". Have you ever had to resolve the same merge conflict multiple times? With rerere, Git records your merge conflict resolutions and then compares them to future conflicts. If a merge conflict exactly matches a conflict resolution recorded earlier, Git then resolves it automatically and informs you that it applied a recorded conflict resolution.
You might ask: If rerere can be so useful, why is it not enabled by default? That's because Git is very conservative when it comes to enabling new features that change existing workflows. There's no good reason to not enable it, it just doesn't want to suprise you with changed default behavior when you upgrade from an older version of Git.
Go ahead, you can enable it by running git config --global rerere.enabled true
.
When force pushing, always --force-with-lease
Assuming that force pushing is actually what you want, if you would so far have used
git push --force
, make it a habit to use the slightly longer git push --force-with-lease
by default. With tab completion in your CLI, you just type "--force-w *tab*" anyway and
let tab completion do the rest.
Chances are you'll never need git push --force
and you should think of it as deprecated.
Git is very conservative when it comes to changing default behavior.
New features that are meant to replace existing ones are often added as
an alternative command or an option for an existing command.
Here's the difference between both options:
--force
, as you probably know, updates a remote branch even if it results in commits being dropped.--force-with-lease
, the lesser known option, does the same, except it ensures that commits that don't also exist in your local repository cannot be dropped. Thus, if you realize you accidentally force pushed the wrong branch or otherwise dropped the wrong commits, you can always recover from your mistake by simply pushing the same commits again. If commits would be dropped that don't exist locally, Git[^1] will simply throw an error and not execute the force push.
Good habits not only reduce mistakes, but also make it easier to recover from the ones that do happen.
You should also make sure to check the commit history of the affected branches (local and remote) before force pushing, so that you're actually aware that you're dropping commits. Remember that only commits that don't exist locally will be protected.
Further reading:
- Consider reading the official documentation for
--force-with-lease
and the different ways it can be used: https://git-scm.com/docs/git-push#Documentation/git-push.txt---force-with-leaserefname --force-if-includes
is related and good to know.- An in depth explanation of both options: https://stackoverflow.com/a/65839129/3726133
- https://stackoverflow.com/questions/52823692/git-push-force-with-lease-vs-force
[^1]: It's actually the remote repository rejecting the force push, because your local Git will notify the remote which commits you allow to be dropped.
Further reading
If you want more details and an example, read the chapter on rerere in the Git Book.
Once you've enabled rerere you can simply benefit passively from less hassle with merge conflicts, but if want to know how you can reset recorded resolutions (e.g., if you realize you accidentally resolved a conflict incorrectly), you can look up the docs of the rerere
command.