🎉 GoReplay is now part of Probe Labs. 🎉

Published on 9/8/2026

Bash Git Commands: The Definitive Guide for Developers 2026

A natural, realistic photograph of a minimalist developer workspace with a laptop showing a blurred Bash terminal, a coffee mug, and a notebook in soft ambient light; at the golden ratio position is a solid dark blue rectangular block with sharp edges containing bold white text "Bash Git Commands"; true-to-life colors, simple uncluttered background to keep focus on the text block.

You’re probably here because Git isn’t failing in a dramatic way. It’s failing in a confusing way.

You ran a pull and now your branch looks wrong. You checked out a commit to inspect something, then realized you’re in a detached HEAD state. You fixed a conflict once, but you’re not sure whether you preserved the right change. Most Git frustration starts there. Not with command syntax, but with uncertainty about repository state.

That’s why Bash Git commands still matter. In Git Bash on Windows, you get Git plus familiar Unix-style tools in one environment, which makes the terminal a practical place to inspect, compare, filter, and fix source control problems without hopping across menus or guessing what the UI did for you. When things are clean, the command line is fast. When things are messy, it’s precise.

Why Master Bash Git Commands

The biggest gap in most Git guides isn’t the list of commands. It’s the missing explanation of how commands behave when a real team is pushing, fetching, rebasing, fixing conflicts, and recovering from mistakes at the same time.

A lot of cheat sheets teach isolated commands. They tell you git pull, git push, git branch, and git checkout, but they don’t help much when you ask the question that blocks work: how do I update my local branch safely, understand what changed upstream, and avoid wrecking my own in-progress changes? That workflow gap is well described in the PlexoSoft Git command notes on branch state and recovery.

The command line shows Git’s real model

Git isn’t hard because the commands are obscure. It’s hard because Git tracks multiple things at once: your files, your staging area, your current branch tip, and the remote-tracking references that tell you what upstream looked like the last time you fetched.

Once you work from that mental model, Bash Git commands stop feeling like incantations. They become direct instructions:

  • Inspect state with git status
  • Record intent with git add and git commit
  • Read history with git log and git show
  • Move work safely with git fetch, git merge, git rebase, and git stash

Most day-to-day Git pain comes from branch state, directionality, and mistake recovery, not from remembering the spelling of a command.

That’s also why terminal fluency has become a practical engineering skill, not a niche one. Teams increasingly hire for demonstrated ability rather than keyword familiarity, and LatoJobs’ skills-based hiring insights are a good reminder that execution matters more than claiming you “know Git” on a resume. If you can diagnose a bad merge, recover from an accidental reset, and synchronize cleanly with remotes, you’re already operating at a higher level than most checkbox-level Git users.

Core Repository and Snapshot Commands

The local commit cycle is the pulse of Git work. If you understand that cycle, every advanced workflow gets easier.

Atlassian describes Git Bash as a Windows-oriented environment that provides a Git command-line experience on Microsoft Windows and also includes Unix tools such as ssh, scp, cat, and find in /usr/bin. The same guide notes that foundational commands like git init, git add, git commit, and git status underpin the standard commit cycle, and that git status is used by approximately 95% of developers daily according to the same source’s cited industry compilation. That’s why Atlassian’s Git Bash overview is still a useful practical reference.

Start with the three places Git cares about

When a junior developer says “Git lost my changes,” the issue is usually that they don’t know where the changes are.

Think in terms of three areas:

AreaWhat it containsWhat you use it for
Working directoryYour edited files on diskActive development
Staging areaChanges selected for the next commitPreparing a clean snapshot
Repository historyCommitted snapshotsLong-term record

That model explains the everyday commands better than any cheat sheet.

The basic commit cycle

A new local repo usually starts like this:

  1. Create a repository git init

  2. Check what Git sees git status

  3. Stage selected files git add app.py or git add .

  4. Create a snapshot git commit -m "Add initial app skeleton"

If you’re starting from an existing remote project, you’ll usually use git clone <repo-url> instead of git init. Cloning gives you the files, the commit history, and the remote connection in one step.

What works and what causes trouble

The command that saves the most confusion is still git status. Run it before you add, before you commit, before you pull, and after you resolve conflicts. It tells you what branch you’re on, what’s staged, what isn’t, and whether your branch is ahead or behind its upstream.

A few habits help immediately:

  • Stage intentionally: git add . is fast, but it also stages things you forgot you changed.
  • Commit by logical unit: Don’t mix refactors, dependency changes, and bug fixes in one commit if they solve different problems.
  • Read status after every state change: If you switch branches, apply a stash, or resolve conflicts, check again.

Practical rule: If you aren’t sure what Git is about to commit, stop and run git status before you do anything else.

That habit sounds basic, but it prevents a surprising number of bad commits, wrong-branch edits, and accidental merges.

When a bug appears after a merge, your job changes. You’re not writing code anymore. You’re investigating history.

A professional developer sitting at a desk and analyzing bash git command history on a computer monitor.

The Git book’s history chapter is still the canonical reference here. It explains that git log lists commits in reverse chronological order, shows each commit’s SHA-1 checksum, author, date, and message, and can be extended with flags like --stat and --since=2.weeks. It also notes that git log -S function_name helps search for commits that changed the number of occurrences of a string. In larger projects, where the Git book’s history guidance becomes more important, branch and history complexity can rise quickly.

Use git log to answer a specific question

Don’t run git log just to scroll. Run it with a purpose.

If you want a quick summary: git log --oneline

If you want to see file-change counts: git log --stat

If you only care about recent work: git log --since=2.weeks

If you need to find when a string changed: git log -S "function_name"

A few practical uses:

  • “What landed recently?”
    git log --since=2.weeks --stat

  • “Which commit likely introduced this config change?”
    git log -S "FEATURE_FLAG_NAME"

  • “Who touched this area last?”
    git log, path/to/file

Diffs tell you what changed right now

git diff complements git log. Log tells you about recorded history. Diff tells you about differences between states.

Common patterns:

  • Working tree vs staging area: git diff
  • Staged changes vs last commit: git diff --staged
  • Two branches: git diff main..feature/login
  • Specific file: git diff, app/services/auth.rb

The mistake I see most often is developers jumping straight to commit without reviewing the staged snapshot. git diff --staged is the last clean checkpoint before history is written.

git show is the fastest inspection command

git show is what you use when you already know which commit or reference you care about.

Try these:

  • git show HEAD
  • git show <commit-sha>
  • git show main:path/to/file

That makes it easy to inspect a commit, review a patch, or compare an old file version without checking out anything.

If git log is your index, git show is your microscope.

In real team work, these commands matter because the hard problems are rarely “how do I commit.” They’re “which commit changed this behavior,” “what exactly is in my staged set,” and “what am I about to merge.”

Branching and Merging Workflows

A branch is just a movable pointer to a line of commits, but the workflow around it determines whether a team moves smoothly or spends its week in conflict resolution.

An educational infographic illustrating common Git branching and merging workflows with command examples and best practices.

A clean feature branch story usually looks like this.

A developer starts from an updated main, creates a branch for one piece of work, commits locally as the feature takes shape, then merges that branch back once the change is ready. That sounds simple, but the value comes from isolation. You can break, rework, and refine on the feature branch without destabilizing the main line.

The everyday feature branch path

Typical commands:

  • git branch feature/login
  • git checkout feature/login

Or, on newer Git versions:

  • git switch -c feature/login

Then you work normally:

  • edit files
  • git add
  • git commit -m "Implement login form validation"

When it’s time to integrate:

  • git checkout main
  • git merge feature/login

Fast-forward versus merge commit

Sometimes Git can move main forward directly because no one changed main since your branch split. That’s a fast-forward merge.

Other times, main and your feature branch both moved. Git then creates a merge commit to join the two histories. Neither is wrong. What matters is whether your team values a very linear history or wants explicit merge points preserved.

A practical rule of thumb:

SituationUsually fineUsually avoid
Small local branch, not yet sharedrebasing before integrationnoisy merge commits
Shared branch with active collaborationmergingrewriting history casually
Main or release branchpreserving clear integration historyforceful history edits

Later in the workflow, rebase can clean history. During shared integration, merge is often the safer tool because it preserves what happened.

Here’s a visual walkthrough before you try it yourself:

Handling conflicts without panic

A merge conflict isn’t Git breaking. It’s Git refusing to guess.

You’ll usually see conflict markers inside the file. Resolve them by editing the file into the final desired content, then stage the resolved file and complete the merge.

The reliable sequence is:

  1. Check the conflicted files with git status
  2. Open each conflicted file and resolve the markers
  3. Stage resolved files with git add <file>
  4. Finish the merge with git commit if Git hasn’t auto-completed it

What doesn’t work is trying random resets while half-resolved conflicts are still in the tree. That usually turns one understandable conflict into a state problem.

Treat a conflict as a review task. Decide what the final code should be, then tell Git the conflict is resolved by staging the file.

Synchronizing with Remotes

Most local Git habits fall apart when a remote enters the picture. The reason is simple. Now you’re dealing with two timelines: yours and the upstream one.

That’s where developers get tripped up by origin/main, remote-tracking branches, and the difference between “I updated my local knowledge of the remote” versus “I merged remote changes into my branch.”

fetch gives you control and pull hides a decision

git pull is convenient. It usually performs a fetch and then integrates the changes right away.

That’s also why I don’t recommend it as the default reflex when someone is learning. git fetch is safer because it updates your remote-tracking references without changing your current branch. You get a chance to inspect first, then choose whether to merge or rebase.

Use this pattern instead:

  1. git fetch origin
  2. inspect what changed
  3. integrate deliberately with merge or rebase

That extra step prevents a lot of “I pulled and now everything is weird” moments.

What origin/main actually means

origin is usually the default remote name. main is the branch on that remote. So origin/main means your local record of the remote’s main branch as of the last fetch.

That local record matters because many decisions depend on it:

  • Is your branch behind upstream?
  • Has someone rewritten a branch?
  • Are you about to rebase onto stale information?

A useful comparison is:

CommandWhat it updatesDoes it change your current branch
git fetchremote-tracking refsNo
git pullremote-tracking refs and local branch integrationYes
git pushremote repositoryNo local history change

Advanced refresh before integration

For more deliberate workflows, git remote update is worth knowing. It refreshes remote-tracking references without merging and creates a temporary FETCH_HEAD. You can inspect that with git show FETCH_HEAD before deciding what to do next, which is exactly why this advanced Git command reference on git remote update is so useful in practice.

This becomes especially valuable before a rebase or when reviewing upstream changes that might affect deployment sequencing.

If your team is tightening release discipline, there’s a useful companion read on CI/CD pipeline optimization practices because synchronization problems often show up first at integration time, not while you’re coding locally.

Pushing with care

Pushing a new branch is straightforward:

  • git push -u origin feature/login

That sets the upstream so future pushes and pulls can infer the branch relationship.

Force pushing is where judgment matters. Sometimes it’s appropriate on your own feature branch after an interactive rebase. On a shared branch, it can erase someone else’s work from the visible branch history. If you must force, --force-with-lease is safer than plain --force because it checks whether the remote changed unexpectedly before overwriting it.

One more subtle workflow worth knowing is that some specialist Git notes point out less-common patterns, such as fetching first and then pushing refs in unusual directions for one-off synchronization tasks. That’s not normal daily work, but it’s a reminder that Git is fundamentally about moving references carefully, not memorizing one “correct” button path.

Rewriting History with Rebase and Stash

Two commands separate developers who merely use Git from developers who stay productive when their day gets interrupted: git stash and git rebase.

They solve very different problems. Stash protects unfinished local work. Rebase cleans up commit history by replaying commits onto a new base.

Use git stash when context switches hit

You’re halfway through a feature. A teammate asks you to hotfix main. Your changes aren’t ready to commit, and you don’t want to pollute history with a throwaway checkpoint commit.

That’s what stash is for.

Typical flow:

  • git stash
  • switch branches
  • fix the urgent issue
  • switch back
  • git stash pop

You can also keep multiple stashes and inspect them before applying. The key point is that stash is temporary storage for work in progress, not a permanent backlog. If you leave stashes around for weeks, they turn into hidden debt.

Rebase makes local history easier to read

git rebase takes your commits and replays them on top of another base commit. The practical result is a more linear history.

That’s useful when your feature branch drifted behind main and you want the branch to look like it was built on the latest upstream state all along. It’s also useful before opening a pull request if your local history has messy “fix typo,” “oops,” and “second fix” commits that should really be cleaned up.

A common workflow is:

  1. git fetch origin
  2. git checkout feature/login
  3. git rebase origin/main

The rule you shouldn’t break

Rebasing shared public history is where people create chaos.

If another developer has based work on a branch, and you rebase that branch and push rewritten history, their commit graph no longer matches the remote. They now have to recover from a moving target.

So the working rule is simple:

  • Rebase local, private, not-yet-shared work when you want cleaner history.
  • Merge shared branch history when preserving collaboration state matters more than aesthetics.

Clean history is valuable. Breaking a teammate’s branch to get it is not.

When a rebase does hit conflicts, the workflow is manageable: resolve the file, stage it, then continue with git rebase --continue. If you realize the rebase was the wrong move, abort it with git rebase --abort and return to the pre-rebase state.

Undoing Changes and Recovering Mistakes

Git recovery gets easier once you stop asking “how do I undo this?” and start asking “what exactly am I trying to undo?”

An infographic titled Developer's First-Aid Kit showing the differences between git checkout, git reset, and git revert.

The answer determines the command. Are you discarding uncommitted local edits, removing a local commit, or safely undoing something that’s already been pushed?

Use the right undo tool for the right problem

Here’s the simplest map:

ProblemBest toolWhy
You changed a file locally and want to discard itgit checkout, <file> or newer restore-style workflowhistory stays untouched
You committed locally and want to move the branch backgit resetrewinds local history
You already pushed a bad commit and need to undo it safelygit revertcreates a new inverse commit

git clean belongs in this group too, but it solves a narrower problem. It removes untracked files from your working tree.

git reset for private local correction

git reset moves HEAD, and depending on the mode, it also affects the staging area and working tree.

The practical differences:

  • git reset --soft HEAD~1
    Undo the last commit, keep changes staged.

  • git reset --mixed HEAD~1
    Undo the last commit, keep changes in your working directory but unstage them.

  • git reset --hard HEAD~1
    Undo the last commit and discard associated local changes.

Use --hard carefully. It’s a delete operation for local work, not a cleanup convenience.

git revert for shared history

If a bad commit is already on a shared branch, revert it instead of resetting the branch backward.

git revert <commit-sha>

That creates a new commit that undoes the selected commit while preserving the public history. Teammates can pull it normally. No one has to reconcile rewritten branch tips.

git clean removes the clutter Git doesn’t track

Build artifacts, generated folders, scratch files, and leftover test output often survive across branch switches because Git doesn’t track them.

Use:

  • git clean -n to preview
  • git clean -f to remove untracked files

Always preview first. The command is blunt by design.

Recovery goes better when you change one layer at a time. First identify whether the issue is in the working tree, the index, or commit history.

That’s the same mental model from the basic section, and it keeps panic low when a mistake lands at the worst possible moment.

Power Up Your Workflow with Bash Aliases and Scripts

The main advantage of Bash Git commands isn’t that you can type Git manually forever. It’s that Bash lets you compress your own repeatable workflow into commands you’ll truly use.

A developer working on code and git commands on two computer monitors at a clean desk.

Aliases are the first step. Scripts are the second. Together they turn Git from a pile of commands into a toolbelt that fits the way you work.

Start with aliases that remove friction

You can define aliases in shell config like .bashrc or in Git config with git config --global alias.<name> ....

Useful low-friction examples:

  • Shorten repetitive commands
    alias gs='git status'
    alias ga='git add'
    alias gc='git commit'

  • Create a readable log shortcut
    alias gl='git log --oneline --graph --decorate'

  • Review staged work fast
    alias gds='git diff --staged'

These don’t change behavior. They just reduce typing and make good habits easier to repeat.

Package multi-step safety checks

The more valuable aliases combine commands in a sequence you trust.

For example, many developers regularly want to update remote knowledge, inspect status, and then choose integration. Instead of hiding that behind a risky one-liner, build a helper that preserves visibility.

Conceptually, a helper script might:

  1. fetch from origin
  2. show status
  3. show the incoming commit summary
  4. then let you decide whether to merge or rebase

That’s the kind of shell-level customization that belongs in a well-considered local setup, especially if you’re standardizing across a team. If you’re refining the full workstation side of your tooling, this guide to development environment setup strategies is worth reading alongside your Git configuration work.

Simple scripts worth keeping around

Good scripts do one boring thing well.

A few examples that pay off:

  • Delete merged local branches after you’ve integrated feature work.
  • Show commits between tags for quick release note drafting.
  • Audit changed files relative to a base branch before review.
  • Refresh remotes and summarize branch divergence so you spot stale branches quickly.

What doesn’t work is building a giant shell function that hides every Git step behind custom logic no one else understands. If your alias makes it harder to know what happened, it isn’t helping.

A good custom command should meet two tests:

TestGood signBad sign
Visibilityyou can still see the Git action being takenthe alias hides risky history edits
Repeatabilityyou run it often enough to justify itit automates a rare edge case
Team claritysomeone else can read and understand itonly its author knows what it does

The best Bash Git commands are often the ones you write for yourself after repeating the same safe workflow enough times to know where the friction is.


If you’re tightening your engineering workflow beyond source control, GoReplay is worth a look. It lets teams capture and replay real HTTP traffic into test environments, which is useful when you want to validate application behavior against production-like request patterns before release.

Ready to Get Started?

Join these successful companies in using GoReplay to improve your testing and deployment processes.