Skip to content
Go back

Leveling up your git game

Published:  at  12:31 AM

alt text

Table of contents

Open Table of contents

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

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"

backup — cheap safety net

# ~/.config/git/config
[alias]
  backup = !git branch bp/$(git branch --show-current)

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"

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"

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"

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

Named stashes

git stash -m "wip: parsing fix"

Don’t rely only on pop

git stash list
git stash show stash@{0}
git stash apply stash@{1}

Turn stash into a branch

git stash branch fix-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

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:

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

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

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.

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.


Cheers and happy coding!


Suggest Changes

Previous Post
Building a webpage content generation pipeline