# How to Use Advanced Git Commands Safely [2026 Alias Kit]

> A reflog-first way to rewrite history without fear: undo reset --hard, undo rebases, enable rerere, use worktrees daily, speed up clones, and install a hardened alias kit.

- Canonical: https://www.kunalganglani.com/blog/advanced-git-commands-aliases-rewrite
- Author: Kunal Ganglani
- Published: 2026-08-29 · Updated: 2026-08-29
- Category: Developer Tools · Tags: git, workflow, developer-productivity, tooling, developer-tools

## TL;DR

Git can be fast without being scary. The trick is pairing every “dangerous” move like rebasing or reset --hard with a reliable recovery plan. This guide shows a reflog-first workflow to undo mistakes, a safer way to force push, and two features most teams ignore: worktrees (two branches at once) and rerere (Git remembers how you fixed conflicts). You’ll also get a copy‑paste alias kit that bakes in safe defaults like force-with-lease. After this, you can clean up history more often and lose less time to panic and branch juggling.

You can learn **advanced git commands aliases rewrite** in an afternoon. The hard part is trusting yourself to use them on a random Tuesday at 4:47pm, five minutes before a deploy, with Slack popping off.

Here’s my stance: if you still treat `rebase`, `reset --hard`, and `commit --amend` like occult rituals, you’re leaving speed on the table. But if you’re rewriting history without **safety nets**, you’re basically waving around a loaded nail gun.

This post is a “rewrites + recovery” workflow. Every destructive move comes with an escape hatch that actually works. Then I’ll give you an alias kit that bakes the good habits in.

Also, if you copy-paste anything from this post, copy-paste the recovery bits first.

## What is a “reflog-first” Git workflow?

A **reflog-first Git workflow is the habit of treating Git reflogs as your primary recovery mechanism** when you rewrite history, because reflogs record updates to references like `HEAD` and branch tips and let you get back to previous states after resets, rebases, and checkouts.

![What is a “reflog-first” Git workflow? — section illustration](https://cdn.sanity.io/images/vzekdneq/production/4884cd09d61e86a0a3a1c92b8ece4f0c96f88e32-1200x675.webp)

That’s not my cute philosophy. It’s straight out of the docs: reflogs “record when the tips of branches and other references were updated in the local repository” ([git-reflog documentation](https://git-scm.com/docs/git-reflog)). That one sentence is why I’m comfortable being aggressive with history cleanup.

A reflog-first workflow has three rules:

1. **Before I rewrite anything, I create a restore point.** A lightweight tag or backup branch is enough.
1. **When I panic, I don’t guess. I open the reflog.** I find the exact pre-disaster SHA.
1. **I force push like an adult.** `--force-with-lease` is the default. `--force` is the exception.
If you operate this way, Git stops feeling “fragile” and starts behaving like what it is. A local database with an audit trail.

## Undo `git reset --hard` with reflog (step-by-step)

Let’s do the classic stomach-drop moment: you ran `git reset --hard`, your working tree is “clean,” and the changes are gone.

![A close up of a computer screen with a mouse](https://cdn.sanity.io/images/vzekdneq/production/f63b9f793a0d602ccf72c3bf5e38c88a111c4358-1200x675.webp)

If those changes lived in commits, they’re probably still there. You didn’t delete history. You moved a pointer and checked out a different state.

Here’s the recipe I actually use.

1. **Inspect your recent `HEAD` positions**
```bash    git reflog --date=local -n 25    ```

You’ll see entries like `HEAD@{0}`, `HEAD@{1}`, etc. That’s your timeline.

1. **Identify the entry right before the reset**
Look for something like:

  - `reset: moving to HEAD~3`
  - or `checkout: moving from ...`
1. **Restore your branch tip to that SHA**
If you’re on the branch you want to restore:

```bash    git reset --hard <sha-from-reflog>    ```

If you want to be extra cautious, create a rescue branch first:

```bash    git branch rescue/<name> <sha-from-reflog>    git switch rescue/<name>    ```

1. **If you only need one commit back, cherry-pick it**
```bash    git cherry-pick <sha>    ```

Why this works is boring, and that’s the point. Reflogs exist specifically to record reference movement locally ([git-reflog documentation](https://git-scm.com/docs/git-reflog)). `reset --hard` is a ref update plus a checkout.

Two practical notes:

- This is **local** recovery. If you rewrote something and pushed it, your reflog doesn’t automatically save your teammates.
- Reflogs expire. On many setups, unreachable entries are kept for **30 days** and reachable ones for **90 days** by default. Don’t treat reflog as a backup strategy. Treat it like roadside assistance.
If you like the “make safety the default” idea, you’ll probably enjoy my post on [safer defaults](/blog/code-review-automation-defaults) for code review automation. Different tool, same philosophy.

## Undo a git rebase (and recover the pre-rebase branch tip)

Rebase is the rewrite you’ll use the most. It’s also the one people fear the most.

![a man sitting at a desk in front of a computer](https://cdn.sanity.io/images/vzekdneq/production/b10fa006083b54774106729251d3c823a9ab9658-1200x675.webp)

Mostly because they think the recovery is mysterious. It isn’t.

The warning that matters: rebasing rewrites commits by replaying them onto a new base. That changes commit IDs. That’s why rebasing pushed or shared commits is a collaboration hazard. The official reference spells out the behavior and caveats ([git-rebase documentation](https://git-scm.com/docs/git-rebase)), and *Pro Git* goes deep on why rewriting public history is dangerous ([Scott Chacon](https://github.com/schacon) and [Ben Straub](https://github.com/bstraub)).

Now the practical recovery.

### Case A: you’re mid-rebase and want to bail

```bash
git rebase --abort
```

That’s it.

### Case B: you finished the rebase, but it was a mistake

1. **Find the pre-rebase `HEAD` in the reflog**
```bash    git reflog --date=local | head -n 30    ```

Look for entries like:

  - `rebase (start)`
  - `rebase (finish)`
The SHA *before* `rebase (start)` is your old branch tip.

1. **Move your branch back**
```bash    git reset --hard <pre-rebase-sha>    ```

1. **If you already force-pushed the rebased branch**
You can still repair it, but now you’re in “talk to humans” territory. You’ll likely need to force push the restored tip.

This is exactly why I’m militant about `--force-with-lease`. It turns “I just deleted someone’s commits” into “push rejected, go coordinate.”

### Case C: you rebased and dropped a commit accidentally

If it existed locally, it’s probably in the reflog. Create a branch from it and cherry-pick forward.

```bash
git branch rescue/dropped <sha>
```

That’s the reflog-first mindset in action.

If you’re doing stacked changes a lot, you’ll get even more mileage out of clean rebases. My [stacked PRs workflow](/blog/stacked-prs-github-workflow) pairs nicely with the “fixup + autosquash” approach below.

## Rewrite Git history safely: restore points, `--force-with-lease`, and a clean rebase loop

“Don’t rewrite history” is lazy advice. The correct advice is: **rewrite history, but make it reversible and socially safe**.

Here’s the loop I teach.

### 1) Create a restore point before you rewrite

Two easy options:

- A backup branch:
```bash   git branch backup/<branch>-before-rewrite   ```

- Or a tag:
```bash   git tag rewrite-safety/<branch>/$(date +%Y-%m-%d)   ```

This costs you two seconds and saves you twenty minutes of reflog archaeology.

### 2) Do a safe interactive rebase for cleanup (autosquash, fixup!, reword)

My default cleanup flow before opening a PR:

1. Make small commits while working.
1. The moment I notice “this should have been part of commit X,” I make a fixup commit right then:
```bash    git commit --fixup <sha>    ```

1. Before I push for review, I rewrite:
```bash    git rebase -i --autosquash origin/main    ```

Inside the interactive list:

- `reword` for the one commit message that will confuse future-you
- `fixup` / `squash` for obvious cleanup
This is less error-prone than trying to craft perfect commits in real time.

If you have local changes while rebasing, `--autostash` can keep you moving:

```bash
git rebase --autostash origin/main
```

That flag is documented in the official reference (git-rebase documentation).

### 3) Force push safely: `--force-with-lease` vs `--force`

If you force push rewritten history, you’re overwriting the remote branch.

- `--force` says: “I don’t care what happened on the remote. Replace it.”
- `--force-with-lease` says: “Replace it **only if** the remote branch still points where I think it does.”
That second behavior is the difference between “I fixed my branch” and “I deleted a teammate’s work.”

I want `--force-with-lease` to be muscle memory. So I alias it.

One more safety habit that prevents Slack incidents: if a branch is truly shared, don’t rebase it. Merge it. Rebase your own topic branches. Yes, it’s boring. That’s why it works.

If you’re trying to bring the same guardrails-first mindset into AI tooling too, I’ve written a lot about shipping [AI in production](/pillars/ai-engineering-production) safely. Different domain, same theme. Defaults matter.

## `git rerere` in practice: stop resolving the same conflict 12 times

If you’ve ever maintained a long-lived branch, you know this pain: you resolve the same conflict, you rebase tomorrow, and Git asks you to resolve the exact same conflict again.

That is a terrible use of a human brain.

`rerere` fixes it.

The manual defines it plainly: “reuse recorded resolution of conflicted merges” (git-rerere documentation). Git records the conflict hunks and how you resolved them. Next time the same conflict shows up, Git can apply your previous resolution automatically.

### Enable rerere (global)

```bash
git config --global rerere.enabled true
```

You need `rerere.enabled` set for it to work (git-rerere documentation).

### What rerere actually does (the mental model)

- On the first conflict, you resolve it manually.
- When you stage the resolution (`git add ...`) and continue the merge/rebase, Git stores a “before/after” record.
- On future conflicts with the same “before” shape, it can replay the “after.”
In real workflows, this matters most when you:

- rebase a feature branch onto `main` daily for **10+ days**
- maintain a release branch that regularly cherry-picks fixes
- have generated files that conflict predictably
### Inspect, clear, and forget resolutions

You don’t need to poke rerere daily, but you should know how to debug it when it’s not doing what you expect.

- See what it has recorded / what’s pending:
```bash   git rerere status   git rerere remaining   ```

- Diff what rerere would apply:
```bash   git rerere diff   ```

- Blow away rerere’s metadata (rare, but sometimes you want a clean slate):
```bash   git rerere clear   ```

- Forget a resolution for a specific path:
```bash   git rerere forget path/to/file   ```

These commands are all in the manual (git-rerere documentation).

If you’ve never tried rerere, enable it for a week. The first time it auto-resolves a nasty conflict, you’ll wonder why this isn’t on by default.

## Git worktrees: the fastest way to juggle hotfix + feature + PR review

Most devs solve “I need two branches at once” with stashing.

It works. It also makes your working directory feel like a junk drawer, and it’s way too easy to stash the wrong thing, apply the wrong stash, or forget what’s inside.

`git worktree` is the grown-up move. It lets you have multiple working directories attached to the same repo, sharing the `.git` object database. The docs describe it as managing “multiple working trees” (git-worktree documentation).

### My three worktree workflows (the ones I actually use)

1) **Hotfix while your feature branch is mid-rebase**

- Worktree A: your feature branch with conflicts half-resolved
- Worktree B: clean `main` for the hotfix
Commands:

```bash
# from the main repo
mkdir -p ../wt

git worktree add ../wt/hotfix -b hotfix/issue-123 origin/main
cd ../wt/hotfix
```

Now you can patch, test, and ship without touching your half-broken rebase.

2) **Review a PR locally while continuing your own work**

If your teammate’s PR needs a local run, don’t torch your current working directory.

```bash
# fetch the branch first if needed
git fetch origin feature/something

git worktree add ../wt/review-feature origin/feature/something
```

3) **Run two versions side-by-side**

Criminally underrated for debugging.

- Worktree A: `main`
- Worktree B: your branch
Run tests, benchmarks, or repro steps in both directories without switching. If you do performance work, this is the only sane way to compare.

### Worktree hygiene

A couple of habits keep worktrees from turning into a pile of abandoned folders:

- List them:
```bash   git worktree list   ```

- Remove when done:
```bash   git worktree remove ../wt/review-feature   ```

I treat worktrees like disposable environments. Same mindset as dev containers. If you want that style of setup consistency, my [self-hosted DevContainers guide](/blog/self-hosted-devcontainers-ssh-vscode) is basically “worktrees for your entire toolchain.”

Here’s a short video that explains the concept visually if you want a 5-minute primer:

[Watch: learn git worktrees in under 5 minutes](https://www.youtube.com/watch?v=8vsRb2mTBA8)

## Clone a large repo faster with partial clone (and know the tradeoffs)

If you’re working in a monorepo, “clone takes forever” is not a fake problem. It shows up in CI agents, on fresh laptops, and in ephemeral dev environments where you rebuild from scratch.

Git has a built-in answer: **partial clone**.

The `git clone` docs describe `--filter=<filter-spec>` for omitting objects initially and fetching them on demand (see git-clone documentation). The practical filter most teams start with is `blob:none`.

### The command I reach for

```bash
git clone --filter=blob:none <repo-url>
```

This says: “clone commits and trees, but don’t download file blobs until needed.”

Two scenarios where this matters:

- **CI runners:** you might only need a subset of files to build/test. Why download every blob?
- **Dev containers:** you recreate environments frequently. Saving even **30–60 seconds** per clone compounds quickly.
### Tradeoffs and limitations

Partial clone is not magic.

- The first time you `checkout` paths you haven’t fetched blobs for, Git will fetch them. You’re moving time from “clone” to “first use.”
- Some tooling that assumes a fully-populated working copy can behave strangely.
- If you combine this with sparse checkout, you can get even more aggressive. Separate rabbit hole.
I like partial clone because it’s a workflow improvement that doesn’t require team coordination. You can do it solo today.

## My hardened Git alias kit (safe by default)

Most alias guides give you cute shortcuts. I don’t care about cute. I want aliases that change behavior.

Git aliases are a first-class feature in `git config`, including shell-command aliases that start with `!` (git-config documentation).

Below is an opinionated kit designed around:

- **Recovery-first:** “undo” points you at reflog.
- **Safe force push:** `--force-with-lease` is the default.
- **Readable history:** logs tuned for code review.
Paste this into `~/.gitconfig` or run the `git config --global` equivalents.

### Alias table (what to install)

| Alias | Expands to | Why it exists |
| --- | --- | --- |
| `lg` | `log --graph --decorate --oneline --date=relative` | Fast mental model of what happened |
| `lga` | `log --graph --decorate --oneline --all` | When you’re lost across branches |
| `st` | `status -sb` | Compact status (`-sb` matters) |
| `fixup` | `commit --fixup` | Makes autosquash a habit |
| `ri` | `rebase -i --autosquash` | The cleanup flow I actually use |
| `fp` | `push --force-with-lease` | Safe force push by default |
| `undo` | `reflog --date=local -n 30` | “Don’t guess. Open the reflog.” |
| `wt` | `worktree` | Makes worktrees feel normal |

### The actual config block

```ini
[alias]
  st = status -sb
  lg = log --graph --decorate --oneline --date=relative
  lga = log --graph --decorate --oneline --all

  # Rewrite workflow
  fixup = commit --fixup
  ri = rebase -i --autosquash

  # Safe pushing
  fp = push --force-with-lease

  # Recovery
  undo = reflog --date=local -n 30

  # Worktrees
  wt = worktree
```

### Which aliases are too dangerous to install

Here are the ones I refuse to normalize on teams:

- `pushf = push --force` (it turns a rare emergency tool into a reflex)
- `nuke = reset --hard` (it makes it way too easy to delete state casually)
If you really want a “nuke,” make it loud and interactive with a shell alias that prints the target branch and requires confirmation. But honestly, I’d rather you build the habit of backup branches + reflog.

If you’re already investing in developer ergonomics, you might also like my collection of [tools](/tools) on this site. Building the [LLM pricing tracker](/llm-prices) and shipping **25+ tools** taught me the same lesson over and over. Small, sharp defaults compound faster than grand process changes.

### Team adoption tip: aliases should be optional, log formats should be shared

I don’t force my personal alias kit on teams. People have their own shell setups.

What I do standardize:

- a shared `git log` format for reviews
- a documented “how we rewrite history” policy (`--force-with-lease`, backup branch naming)
Same principle as my [gitleaks + pre-commit + CI setup](/blog/gitleaks-pre-commit-ci-setup). Tooling works when the defaults are shared.

## Putting it together: my daily workflow (worktrees + rerere + safe rewrite)

Once you combine all of this, Git stops being a bag of party tricks. It becomes a system you can trust.

Here’s the loop:

1. Start work on a feature branch.
1. Enable rerere once. Conflicts get cheaper over time.
1. Use worktrees when you need parallelism. No stashing. No thrash.
1. Before opening a PR, clean up with interactive rebase + autosquash.
1. Force push with lease.
1. If anything goes sideways, reflog first.
A concrete example from my day-to-day building this site: I maintain a bunch of small utilities and datasets (the [LLM pricing tracker](/llm-prices) and **25+ tools** under [/tools](/tools)). I’m constantly bouncing between “ship a tiny fix” and “keep a bigger refactor moving.” Worktrees keep me from context switching myself to death, and reflog is what lets me clean up history aggressively without getting punished.

If you take one thing from this post, take this: rewrite history *more*, not less. Just do it the way you’d do database migrations. Restore points. An audit trail. A rollback plan.

The engineers who move fastest in 2026 aren’t the ones who memorized the most Git commands. They’re the ones who made the dangerous ones boring.

Photo by Gabriel Heinzer on Unsplash.

## FAQ

### How do I recover a deleted commit in Git?

If the commit existed locally, check `git reflog` first. Reflog entries show where `HEAD` and branch tips used to point. Once you find the SHA, create a rescue branch from it or cherry-pick it onto your current branch.

### What is the safest way to force push?

Use `git push --force-with-lease` instead of `--force`. It only overwrites the remote branch if it still points where you think it does, which helps prevent deleting a teammate’s new commits. I like aliasing it so it becomes the default habit.

### How do I squash commits properly before opening a PR?

Use interactive rebase against your base branch, typically `git rebase -i origin/main`. Prefer `--autosquash` with `fixup!` commits so Git does most of the ordering for you. If you make a mistake, the pre-rebase state is usually recoverable via `git reflog`.
