
Table of contents
Open Table of contents
- Why should you bother to learn advanced git?
- Quick aside: analyze your current workflow
- 1. Branch Movement
- 2. Common Aliases
- 3. Custom Commands: DRY egronomics
- 4. Smart stashing
- 5. Interactive Rebase: Edit Commit History
- 6. Quick note on Worktrees
- 7. Having Smoother Defaults
- Final Thoughts & Exercises
Why should you bother to learn advanced git?
All developers use Git, but most only explore the shallow waters of what it offers. The stuff you need for daily driving—like pull, push, stash, and sometimes cherry-pick. There’s a large portion of the development world that’s blissfully unaware of advanced Git capabilities.
This could be used as an argument against learning advanced Git: if you don’t need it for everyday usage, why bother? Surely there are better ways to impress suits at the next programming-themed convention. I think that’s fair. It’s just that for me, Git becomes super fun once you go beyond the basics, because once you have the knowledge, you naturally start creating your own workflows that are very low friction.
Some might say you “become faster”, but I think time is the wrong metric here. Even if you save a few minutes every day, I’m not sure that makes you more productive. A better way to put it is that Git starts to fit so well with your aims that every interaction with it feels smooth and enjoyable, so you stay fresh and have your energy intact for the more important tasks.
Quick aside: analyze your current workflow
If you have a terminal open, try this with Claude Code / Codex / Opencode. It looks at your actual Git usage and suggests improvements based on what you’re already doing.
Analyze this user's Git habits.
Read their global Git config and shell history (last 2–3 months).
Count command usage and look for repeated patterns, especially chained commands.
Cross-check against existing aliases to find gaps or redundant manual steps.
Suggest:
1. missing automation in their setup
2. repeated patterns worth aliasing
3. risky habits
4. useful Git features they’re not using
Keep everything grounded in actual usage.
If they agree, write the changes to their Git config.
With that in mind, here’s what I actually use day to day.
1. Branch Movement
git switch my-branch
git switch -
git switch -c my-new-branch
git checkoutcovers too much ground - you can safely replace it withswitch, which only covers the branch switching part ofcheckoutand has more predictable behavior.git switch -toggles to the previous branch. You’ll be spamming this in no time because most branch switches are just “go back to the other branch”.
2. Common Aliases
You’d want to set a basic git alias in your ~/.zshrc / ~/.bashrc config to reduce brain-to-terminal latency.
# ~/.zshrc
alias g='git'
You also want to shorten high-frequency commands to reduce friction - some of the commands I use:
# ~/.config/git/config
[alias]
l = log --oneline
sw = switch
bk = switch -
c = commit
pl = pull
ps = push
rb = rebase
br = branch
3. Custom Commands: DRY egronomics
Just like in React, it helps to know some advanced hooks, but eventually you’ll have your own logic that you’ll want to encapsulate. We often stress DRY code, but somehow don’t see the benefit of applying it to daily development - the place where we repeat ourselves the most.
The best improvements to your workflow come from observing how you work and identifying areas of friction and repetition.
Some ideas:
hop — run commands on another branch
# ~/.config/git/config
[alias]
hop = "!f(){ t=\"$1\"; shift; git switch \"$t\" || exit; git \"$@\"; git switch -; }; f"
- Execute something elsewhere and immediately return
- Avoid spamming
git switch
backup — cheap safety net
# ~/.config/git/config
[alias]
backup = !git branch bp/$(git branch --show-current)
- Useful snapshot
- So you can have fun rewriting history without extra worries
wtr / wtg — run across all worktrees
# ~/.config/git/config
[alias]
wtr = "!f(){ git worktree list --porcelain | sed -n 's/^worktree //p' | while read -r wt; do echo \"==> $wt\"; (cd \"$wt\" && \"$@\"); done; }; f"
wtg = "!f(){ git worktree list --porcelain | sed -n 's/^worktree //p' | while read -r wt; do echo \"==> $wt\"; (cd \"$wt\" && git \"$@\"); done; }; f"
wtrruns any shell command in every worktree, for examplegit wtr npm installwtgdoes the same but prefixes git, for examplegit wtg status- saves a lot of manual
cd-ing when you have work on several worktrees
rbm — smart merge
# ~/.config/git/config
[alias]
rbm = "!f(){ target=\"$1\"; cur=$(git branch --show-current); git rebase \"$target\" && git switch \"$target\" && git merge --ff-only \"$cur\" && git switch \"$cur\"; }; f"
- Rebase + merge + go back to initial branch in one command
- Uses fast-forward merge which avoids that extra merge commit
- Keeps history linear and merges without spamming the commit history
fixup — edit history instantly
# ~/.config/git/config
[alias]
fixup = "!f() { target=${1:-HEAD}; base=$(git rev-parse \"$target^\"); git commit --fixup=\"$target\" --no-edit && git -c sequence.editor=: rebase -i --autosquash \"$base\"; }; f"
- Attach a fix to any previous commit (default is HEAD)
- immediately autosquash to clean history
- No manual rebase step later
Note: There’s a cool git absorb command that’s pretty similar, only it automatically infers the commit to which your changes belong. Check it out here.
4. Smart stashing
Stash is one of those commands that didn’t click for me at first. If you can reset and restore commits, why do you need an extra command to fake-commit?
But stash is really about making quick changes to your working directory that are easily reversible. It’s useful when you have a short interruption and need to make parts of your work disappear for a few minutes. That’s why selective stashing and named stashes matter - they let you cleanly separate work and easily recover it later.
Selective stashing
git stash --staged
git stash --keep-index
--stagedto stash only what you staged--keep-indexto stash everything except what’s staged
Named stashes
git stash -m "wip: parsing fix"
- Important to label your stashes if you’re going above that first stash
Don’t rely only on pop
git stash list
git stash show stash@{0}
git stash apply stash@{1}
- Inspect before applying
- Apply exactly what you need, not just the latest
Turn stash into a branch
git stash branch fix-branch
- Clean way to pop the stash without polluting your current branch
5. Interactive Rebase: Edit Commit History
The key thing to understand about interactive rebase is that you can very easily edit your work history before your merge it. You can genuinely run git commit -m "wip 3" without feeling guilty anymore - because this is easily cleaned up later.
A typical flow would be:
git rebase -i HEAD~5
This opens your last 5 commits in an editor, where you can reorder, squash, or drop them. This is how you decide what your final commit history should look like.
With interactive rebase you can
- Reorder commits
- Drop commits
- Edit messages
- Squash multiple commits into one
- Split one commit into multiple
I find this rebase config to be clearer than the original:
# ~/.config/git/config
[rebase]
abbreviateCommands = true
instructionFormat = %h %s
The key is to regularly rebase your feature branches before merging into main so it becomes second nature. With AI-generated commits, it’s even more important to review before merge.
If the default “edit a text file” flow feels clunky, try:
- lazygit - a Git TUI with a visual UI for reordering, squashing, and dropping commits
- fugitive - a Neovim plugin that lets you rebase from the git log, inspect commits inline, and navigate everything with Vim motions
6. Quick note on Worktrees
Worktrees get a lot of talk these days, especially with AI workflows, but the core Git UX here is pretty minimal: creating one means attaching to an existing branch or explicitly creating a new one, then cd-ing into it, with no real support for setup or cleanup.
In practice, you usually want some automation to install dependencies, set up local state, and clean up when done, so if you’re using multiple worktrees this quickly turns into repetitive manual work.
Instead I’d recommend using tools like worktrunk which wrap this into a more complete workflow:
wt new feature-x
wt list
wt rm feature-x
wt new feature-x- create a branch + worktree and run your custom setupwt list- shows all active worktrees in one placewt rm feature-x- remove the worktree and clean up the branch
7. Having Smoother Defaults
Git has some annoying defaults - it can block you from doing certain things, or behave in ways you don’t expect.
Some better alternatives:
# ~/.config/git/config
[rebase]
autosquash = true
autoStash = true
[stash]
includeUntracked = true
[pull]
rebase = true
-
pull.rebase = true- pulling rebases your work on top of upstream by default, so yourgit pulldoesn’t get blocked when your branch has local commits and has diverged from upstream. -
includeUntracked = true- stash includes new files too, so your working directory is fully cleared instead of leaving things behind. -
autoStash = true- git normally blocks a rebase if you have local changes. this stashes them automatically, rebases, then restores them. -
autosquash = true- fixup commits are placed and squashed automatically during rebase, so cleanup is consistent and not something you have to manage manually. If you’re using thefixupcommand above, this is mostly redundant - but it keeps behavior consistent if you ever run a manual rebase.
Final Thoughts & Exercises
So this is what I use day to day. Most of it is small, but together it makes Git more predictable and fun. If you want to try this out, here are two quick exercises:
1. Turn one messy commit into many
Make a single large commit with a few unrelated changes. Then run: git rebase -i HEAD~1. Split it into multiple smaller commits.
- You might need to look up the exact steps - the key idea is to stop at that commit, undo it, and then recreate it as several smaller ones.
2. Fix a commit that isn’t HEAD
Make a few commits, then go back and add a small change that belongs to an earlier commit. Use your fixup command to attach it to the correct commit and autosquash it.
- Once you’ve done this once, it becomes a natural way to fix things in place instead of adding new commits at the end.
Cheers and happy coding!