$linuxjunkies
>

Use Git Effectively from the Command Line

Master Git from the terminal: branches, merge vs rebase, interactive rebase to clean history, stash for context switching, and reflog to recover lost commits.

BeginnerUbuntuDebianFedoraArch9 min readUpdated June 7, 2026

Before you start

  • Git 2.23 or later installed (check with: git --version)
  • A local Git repository initialized or cloned
  • Basic familiarity with git add, git commit, and git push

Git's command-line interface gives you direct, precise control that no GUI can fully replicate. This guide covers the everyday workflows that separate a user who only knows git commit from one who can untangle history, recover lost work, and keep a clean project timeline. Each section builds on real scenarios you'll hit within weeks of working on any non-trivial project.

Branches: Creating, Switching, and Cleaning Up

Branches are cheap pointers to commits. Make one for every feature or fix—always. Never work directly on main or master.

Create and switch in one step

git switch -c feature/user-auth

git switch is the modern replacement for git checkout for branch operations (Git 2.23+). Use git checkout only if you're on a system running Git older than 2.23.

List branches

# Local branches
git branch

# Remote-tracking branches too
git branch -a

Delete merged branches

# Safe delete — fails if branch is not fully merged
git branch -d feature/user-auth

# Force delete (use when you've merged via a PR and hashes differ)
git branch -D feature/old-experiment

Prune stale remote-tracking refs

git fetch --prune

Run this regularly. Remote branches deleted by teammates will otherwise clutter git branch -a forever.

Merge vs. Rebase: Choosing the Right Tool

Both integrate changes from one branch into another. The difference is what the history looks like afterwards.

  • Merge creates a new merge commit with two parents. History is truthful—you can see exactly when branches diverged and reunited.
  • Rebase replays your commits on top of another branch's tip. History looks linear, as if you always branched from the latest point.

Standard merge

git switch main
git merge feature/user-auth

Rebase your branch onto main before merging

git switch feature/user-auth
git rebase main

After rebasing, a merge into main becomes a fast-forward—no extra merge commit, clean linear history.

The golden rule: never rebase commits that have already been pushed to a shared remote branch. Rebase rewrites commit hashes; anyone else working from those commits will have a broken history.

Interactive Rebase: Rewriting Local History

Interactive rebase lets you edit, squash, reorder, or drop commits before they're shared. Use it to clean up a messy local branch before opening a pull request.

Open the interactive editor

# Rewrite the last 4 commits
git rebase -i HEAD~4

Your $EDITOR opens with a list of commits and commands. The most useful actions:

  • pick — keep the commit as-is (default)
  • reword — keep the commit but edit the message
  • squash — fold into the previous commit, combining messages
  • fixup — like squash but silently discards this commit's message
  • drop — delete the commit entirely

Typical cleanup: squash WIP commits

Say your last four commits are: feat: add login form, wip, fix typo, wip 2. Change the file so the three noisy commits say fixup:

pick a1b2c3 feat: add login form
fixup d4e5f6 wip
fixup 7890ab fix typo
fixup cdef12 wip 2

Save and close the editor. Git replays the commits, producing a single tidy commit.

Abort if things go wrong

git rebase --abort

Stash: Shelving Unfinished Work

You're mid-feature when you need to switch context urgently. git stash saves your dirty working tree and index, leaving a clean checkout.

Save and restore

# Stash everything tracked (modified + staged)
git stash push -m "half-done login validation"

# Switch context, do your urgent work, then come back
git switch main
# ... fix the bug ...
git switch feature/user-auth

# Restore the most recent stash
git stash pop

Include untracked files

git stash push -u -m "includes new files"

Inspect and manage multiple stashes

# List all stashes
git stash list

# Show what's in stash entry 1
git stash show -p stash@{1}

# Apply a specific stash without removing it from the list
git stash apply stash@{1}

# Delete a specific stash
git stash drop stash@{1}

# Nuke all stashes
git stash clear

git stash pop is equivalent to apply + drop on the most recent entry. Prefer apply when you're unsure—you can always drop manually after confirming the restore worked.

Reflog: Your Safety Net

The reflog records every position HEAD has ever pointed to on your local machine. It's how you recover commits that appear to be gone—after a bad reset, an accidental branch delete, or a rebase that went sideways.

View the reflog

git reflog

Output looks like this (yours will differ):

e3f1a2b HEAD@{0}: rebase finished: returning to refs/heads/feature/user-auth
a1b2c3d HEAD@{1}: rebase: feat: add login form
7890abc HEAD@{2}: checkout: moving from main to feature/user-auth
...

Recover a dropped commit

# Identify the hash of the commit you want back, then:
git checkout -b recovery/lost-work 7890abc

This creates a new branch at that commit so you can inspect or cherry-pick from it.

Undo a hard reset

# You ran: git reset --hard HEAD~3 by mistake
# Find the pre-reset position in the reflog
git reflog

# Reset back to it
git reset --hard HEAD@{2}

Reflog entries expire after 90 days by default (gc.reflogExpire). If you need to recover old work, act quickly and don't run git gc in the meantime.

Verification: Inspect the Graph

After rebasing, squashing, or merging, inspect the actual commit graph to confirm what you intended:

git log --oneline --graph --decorate --all

A clean rebase produces a straight vertical line of commits. A merge produces a visible fork-and-join. If something looks wrong, the reflog is your undo button.

Troubleshooting

Rebase conflict loop

During a rebase, Git pauses at each conflicting commit. Resolve the conflict in your editor, then:

git add path/to/resolved/file
git rebase --continue

If a commit becomes empty after resolution (because the change already exists upstream), skip it:

git rebase --skip

Stash apply conflict

If git stash pop produces conflicts, the stash entry is not removed automatically. Resolve conflicts, git add the files, and then drop the stash manually:

git stash drop stash@{0}

Pushed to the wrong branch

If you accidentally pushed commits to a shared branch and need to remove them, communicate with your team first. A forced push rewrites shared history and will break everyone else's clones:

# Only safe on branches no one else uses
git push --force-with-lease origin feature/my-branch

--force-with-lease is safer than --force: it refuses to overwrite if someone else has pushed since your last fetch.

tested on:Ubuntu 24.04Fedora 40Arch rollingDebian 12

Frequently asked questions

When should I use merge instead of rebase?
Use merge on long-lived shared branches where an accurate record of integration points matters. Use rebase to tidy up short-lived personal feature branches before they're reviewed or merged.
Is it safe to interactive-rebase commits I've already pushed?
Only if no one else has based work on those commits. Rebase rewrites commit hashes; force-pushing will break teammates' local histories. On solo feature branches where you're the only pusher, it's generally fine with --force-with-lease.
What's the difference between 'git stash pop' and 'git stash apply'?
'pop' applies the most recent stash and removes it from the stash list in one step. 'apply' leaves the stash entry intact, which is safer when you're not sure the apply will succeed cleanly.
How long does the reflog keep entries?
By default, reachable reflog entries expire after 90 days and unreachable ones after 30 days. The clock resets each time you access an entry. Don't rely on it as a long-term backup.
What does 'git fetch --prune' actually delete?
It deletes your local remote-tracking refs (e.g., origin/old-feature) for branches that no longer exist on the remote. It does not delete your local branches—those must be removed manually with 'git branch -d'.

Related guides