Git Cheatsheet
Last updated 14th June 2026

Git Cheatsheet

What it is

Git is the version-control system every modern deployment workflow assumes. The main branch is the source of truth; tags are immutable release pointers; a git push to a configured remote is what kicks off the build pipeline, the SSH transport to your servers, and the atomic release swap that puts new code in front of users.

This sheet covers the commands and patterns you reach for when Git is part of a deployment system — not just the day-to-day clone/commit/push loop, but the tagging and branching conventions deploy tools key off, the git archive pattern that produces clean release tarballs, and the recovery commands (reflog, restore, fsck) that get you out of trouble when a force push or a stale main puts the wrong code in production.

Quick reference

Repository basics

git init                                            # new repo in cwd
git init --bare deploy.git                          # server-side bare repo (no working tree)
git clone git@github.com:org/app.git                # SSH clone (auth via deploy/personal key)
git clone https://github.com/org/app.git            # HTTPS clone (auth via token in URL or credential helper)
git clone --depth 1 git@github.com:org/app.git      # shallow clone — faster, no history
git clone --branch v1.4.2 --single-branch git@github.com:org/app.git

git status                                          # working tree state
git status -s                                       # short format (scriptable)
git status -b                                       # include branch info
git status --porcelain                              # machine-readable (stable across versions)

git log                                             # full history
git log --oneline -20                               # one-line summary, last 20
git log --graph --oneline --all --decorate          # branch topology
git log --since="2 weeks ago" --author=facundo
git log --grep="hotfix"                             # match commit message
git log -S "function_name"                          # pickaxe: find commits that touched the string
git log --follow path/to/file                       # history of one file through renames
git log <branch>..HEAD                              # commits on HEAD not on <branch>

git status --porcelain is the version that's safe to parse in deploy scripts. The human-readable git status output reformats across releases; --porcelain won't.

Staging and committing

git add file.txt                                    # stage one file
git add -p                                          # interactive: choose hunks to stage
git add -A                                          # stage everything (incl. deletions)
git add -u                                          # only stage modified+deleted, not new

git restore --staged file.txt                       # unstage (modern equivalent of `git reset HEAD`)
git restore file.txt                                # discard unstaged changes (DESTRUCTIVE)

git commit -m "fix: handle null user agent"
git commit -am "..."                                # add tracked + commit in one step
git commit --amend                                  # rewrite last commit (NEVER after pushing to shared)
git commit --amend --no-edit                        # add files to last commit without editing message
git commit --fixup <SHA>                            # marker for an interactive autosquash later
git commit --allow-empty -m "trigger CI"            # empty commit (CI/deploy trigger pattern)

git diff                                            # unstaged changes
git diff --staged                                   # staged but not committed
git diff HEAD~5 HEAD -- path/                       # changes in last 5 commits under path/
git diff --stat                                     # summary (files + line counts)
git diff --check                                    # find whitespace errors before committing

Branches

git branch                                          # list local
git branch -a                                       # include remotes
git branch -vv                                      # show upstream tracking
git branch --merged main                            # branches already merged into main (prune candidates)
git branch --no-merged main                         # branches NOT yet in main

git switch main                                     # check out main (modern alias for checkout)
git switch -c feature/login                         # create + switch
git checkout main                                   # older form, still works
git checkout -b feature/login

git merge feature/login                             # merge into current branch
git merge --no-ff feature/login                     # always create a merge commit
git merge --squash feature/login                    # collapse history of branch into one commit

git rebase main                                     # replay current branch on top of main
git rebase -i HEAD~5                                # interactive: edit/reorder/squash last 5
git rebase --abort                                  # bail out of a rebase mid-flight
git rebase --continue                               # after resolving conflicts
git rebase --skip                                   # skip current commit (DESTRUCTIVE if mid-merge)

git branch -d feature/login                         # safe delete (refuses if unmerged)
git branch -D feature/login                         # force delete (DESTRUCTIVE)
git push origin --delete feature/login              # delete remote branch

Remotes

git remote -v                                       # list remotes
git remote add deploy git@deploy.example.com:app.git
git remote set-url origin git@github.com:org/app.git
git remote rename origin upstream

git fetch                                           # download new commits without merging
git fetch --all --prune                             # all remotes + remove deleted branches
git pull                                            # fetch + merge (or rebase if configured)
git pull --rebase                                   # fetch + rebase (cleaner history)

git push                                            # push current branch
git push -u origin feature/login                    # push + set upstream tracking
git push origin :feature/login                      # delete remote branch (old syntax)
git push --tags                                     # push annotated tags
git push --follow-tags                              # push + only the tags reachable from pushed commits
git push --force-with-lease                         # safer force push (see notes below)
git push --force                                    # nuclear force push (NEVER on shared branches)

Never --force push to main. --force-with-lease is the rule for force-pushing anywhere collaborators might be — it refuses to overwrite a remote that has new commits you haven't seen, preventing the classic "I just nuked Alex's work" failure.

History rewriting

git rebase -i HEAD~5                                # interactive rebase: reorder, squash, edit, drop
git rebase -i --autosquash main                     # auto-apply --fixup commits during rebase
git cherry-pick <SHA>                               # apply one commit onto current branch
git cherry-pick <SHA1>..<SHA2>                      # apply a range
git cherry-pick -x <SHA>                            # include "cherry picked from..." in message

git reset --soft HEAD~1                             # uncommit, keep staged
git reset --mixed HEAD~1                            # uncommit, unstage (default)
git reset --hard HEAD~1                             # uncommit + discard changes (DESTRUCTIVE)
git reset --hard origin/main                        # nuke local main to match remote (DESTRUCTIVE)

git revert <SHA>                                    # create a new commit that reverses <SHA>
git revert -n <SHA>                                 # stage the revert without committing yet
git revert HEAD~3..HEAD                             # revert a range

revert is the production-safe choice — it adds a new commit that undoes the bad one, leaving history intact. reset --hard rewrites history, which only works on commits nobody else has pulled.

Stash

git stash                                           # save unstaged + staged, clean working tree
git stash -u                                        # also include untracked files
git stash push -m "wip on login flow"               # named stash
git stash list                                      # show all stashes
git stash show -p stash@{0}                         # full diff of a stash
git stash pop                                       # apply top stash + remove from list
git stash apply stash@{1}                           # apply without removing
git stash drop stash@{0}
git stash clear                                     # nuke all stashes (DESTRUCTIVE)

git stash branch new-branch stash@{0}               # create a branch from a stash (recovers conflicts)

Tags (releases)

git tag                                             # list tags
git tag -l 'v1.*'                                   # filter by pattern
git tag v1.4.2                                      # lightweight tag (just a ref)
git tag -a v1.4.2 -m "release v1.4.2"               # annotated tag (recommended for releases)
git tag -a v1.4.2 <SHA>                             # tag a specific commit
git tag -s v1.4.2 -m "..."                          # signed tag (GPG)
git tag -d v1.4.2                                   # delete local
git push origin v1.4.2                              # push one tag
git push origin --tags                              # push all tags
git push origin :refs/tags/v1.4.2                   # delete remote tag (rarely needed)

git describe --tags                                 # nearest tag + commits since (e.g. v1.4.2-3-gabc123)
git describe --tags --abbrev=0                      # just the nearest tag (for release builds)

Annotated tags carry tagger, date, message, and (optionally) a signature. Lightweight tags don't — they're just pointers. Production releases should always be annotated; you want the metadata when you git show v1.4.2 six months later.

Submodules

git submodule add git@github.com:org/lib.git vendor/lib
git submodule update --init --recursive             # initial fetch after clone
git submodule update --remote                       # advance submodule to its remote tip
git submodule foreach 'git pull origin main'        # bulk pull
git submodule deinit vendor/lib                     # un-register submodule
git rm vendor/lib                                   # remove submodule from repo

Deploy pipelines must git clone --recurse-submodules or submodule update --init after clone — without it, the submodule directories are present but empty, and your build silently picks up no code from them.

Worktrees

git worktree add ../app-hotfix hotfix/auth-bug      # check out a branch in a separate dir
git worktree list                                   # show all worktrees
git worktree remove ../app-hotfix                   # clean up
git worktree prune                                  # remove stale worktree references

Worktrees let you have multiple branches checked out simultaneously without a second clone — useful for emergency hotfix work on production code while your main checkout is mid-refactor.

Configuration

git config --global user.name "Facundo Farias"
git config --global user.email "facundo@example.com"
git config --global init.defaultBranch main         # new repos default to `main`, not `master`
git config --global pull.rebase true                # `pull` rebases instead of merging
git config --global push.autoSetupRemote true       # `push` on new branches sets upstream automatically
git config --global rerere.enabled true             # remember resolved conflicts; auto-resolve next time
git config --global core.autocrlf input             # macOS/Linux: never write CRLF
git config --global commit.gpgsign true             # sign every commit

git config --list                                   # all effective config
git config --list --show-origin                     # which file each setting came from
git config --get remote.origin.url
git config --unset alias.co                         # remove a setting

Per-repo config lives in .git/config; global in ~/.gitconfig; system in /etc/gitconfig. The right level matters for shared CI runners — set CI-specific config per-repo, not globally.

Inspecting history

git show <SHA>                                      # commit + diff
git show <SHA>:path/to/file                         # file at that commit (no checkout needed)
git show <SHA> --stat                               # just the file stats

git blame path/to/file                              # who changed each line, when
git blame -L 10,30 path/to/file                     # only lines 10-30
git blame -w path/to/file                           # ignore whitespace-only changes

git log -p path/to/file                             # patch history of one file
git log --all --source -- path/to/file              # find any branch that touched the file
git log --diff-filter=D -- path/to/file             # commits that DELETED the file

git shortlog -sn                                    # commits per author (sorted)
git shortlog -sne --since="last month"              # with email, recent only

Recovery

git reflog                                          # every HEAD change in the last 90 days
git reflog show feature/login                       # reflog for one branch
git reset --hard HEAD@{2}                           # rewind HEAD to 2 reflog entries ago

git fsck --lost-found                               # find unreachable commits/blobs (orphaned after reset)
git cat-file -p <SHA>                               # raw object content

git restore --source=HEAD~3 path/to/file            # restore file from 3 commits ago, no reset
git restore --staged --worktree path/to/file        # both unstage AND discard changes

# Recover a deleted branch from reflog
git reflog | grep feature/login                     # find the SHA where it was
git branch feature/login <SHA>                      # recreate it pointing at that SHA

The reflog is the Get-Out-of-Jail-Free card. As long as the bad operation was local and within the last 90 days (default gc.reflogExpire), the previous HEAD is recoverable. If you've just run reset --hard and panicked: stop, git reflog, and you're probably fine.


Deployment workflows (the moat)

The reference above is portable — every Git tutorial has those commands. The patterns below are the ones that matter once Git stops being a developer tool and becomes the trigger for production releases.

1. Branching strategy: production-first vs trunk-based

The branching model is a deploy decision, not a Git decision. Three patterns that actually hold up at small-to-medium team size:

Trunk-based (recommended for ≤10 devs). Everyone commits to main. Feature flags gate unfinished work. Releases tag a SHA on main. Pull requests are still mandatory; they're just short-lived.

git switch main
git pull
git switch -c facundo/feature-X
# ... work, commit ...
git push -u origin facundo/feature-X
# open PR, get review, merge into main
# tag a release when ready
git tag -a v1.4.2 -m "release v1.4.2"
git push --follow-tags

GitFlow (medium teams with scheduled releases). Long-lived develop branch holds work-in-progress; main holds production code; release branches stage what's about to ship; hotfix branches go straight off main.

Production-environment branching (multi-tenant agencies, complex compliance). A branch per environment (staging, production, production-eu). Deploys are merges into the environment branch. Useful when different environments must run different code for compliance reasons — overkill otherwise.

In DeployHQ's for-agencies workflow, environment-per-branch is the common pattern; for a single-product startup, trunk-based is almost always the right call. Whichever you pick, the 5 effective Git branching strategies post covers the trade-offs in more depth.

2. Tag-based releases

Production deploys should point at immutable tags, not at main. A tag pins a SHA forever; main moves with every push. Deploy pipelines that target main will silently ship the next merge while you're not looking.

# At release time:
git switch main
git pull
git tag -a v1.4.2 -m "release v1.4.2"   # annotated tag
git push --follow-tags                  # push commits + the tag

# DeployHQ (or any deploy tool) then targets the tag, not the branch
# e.g. configure the project to deploy `v1.4.*` tags automatically

Three properties that make tags the production-safe choice:

  1. Immutable by convention. Pushing the same tag name twice requires --force, and most CI/CD tools reject it.
  2. Searchable in tooling. git describe produces human-readable version strings (v1.4.2-3-gabc123 = three commits past v1.4.2).
  3. Survives history rewrites. A git rebase on main invalidates branch pointers; tagged commits keep their tags.

For semantic versioning workflows, the rule is vMAJOR.MINOR.PATCH. Pre-release builds use v1.4.2-rc.1; build metadata appends with +sha-abc123. Pick a convention and let your CI parse it — the convention matters more than which one you pick.

3. The git archive release artifact

The cleanest way to produce a release tarball for a deploy: git archive. It exports a tree at a specific SHA or tag, with no .git/ directory and no working-tree noise:

# Produce a tarball of the v1.4.2 tag, no .git/, no untracked files
git archive --format=tar.gz --prefix=app-1.4.2/ v1.4.2 > app-1.4.2.tar.gz

# Same, from a remote without cloning (some hosts support this)
git archive --format=tar.gz --remote=git@github.com:org/app.git v1.4.2 > app-1.4.2.tar.gz

# Pipe directly over SSH to a deploy host
git archive --format=tar v1.4.2 | ssh deploy@host "tar -xf - -C /var/www/releases/v1.4.2"

Compared to git clone + rm -rf .git:

  • No .git/. .git/ on a long-lived repo is 50-500 MB. A clean git archive of the same tree might be 5-50 MB.
  • Honours .gitattributes export-ignore. Mark tests/, .github/, and docs/ as export-ignore and they're excluded from the tarball automatically.
  • Deterministic. Same tag → identical bytes. Useful for build cache keys.
# .gitattributes — checked into the repo
tests/      export-ignore
docs/       export-ignore
.github/    export-ignore
.gitignore  export-ignore
.editorconfig export-ignore

For atomic releases on the deploy host, pair git archive with the symlink-swap pattern: tar -xf release.tar -C /var/www/releases/v1.4.2/, then ln -sfn /var/www/releases/v1.4.2 /var/www/current. DeployHQ's atomic deployments implements the same flow — but the underlying primitive is git archive + tar + ln.

4. Force-push safely: --force-with-lease

git push --force overwrites the remote with your local view, no questions asked. If a teammate pushed between your last fetch and your force, their work is gone. The fix is --force-with-lease:

# Safer force-push: refuses if the remote has new commits you haven't seen
git push --force-with-lease origin feature/login

# Even safer: specify the expected remote SHA
git push --force-with-lease=feature/login:<SHA-you-expect> origin feature/login

The mental model: --force says "make the remote match my local." --force-with-lease says "make the remote match my local, but only if the remote is currently where I last saw it." The second form catches the race condition that produces the lost-work incident.

Three rules that make force-pushing non-destructive in practice:

  1. Never force-push to main. Configure GitHub/GitLab to reject it via branch protection rules — defense in depth.
  2. Always --force-with-lease, never --force. Make the safe one your muscle memory.
  3. git fetch before every force-push to feature branches. The lease is fresh; the surprise is smaller.

5. Atomic commits and conventional messages

A commit message convention isn't about aesthetics — it's about what your changelog generator, your CI, and your future-self can parse. The Conventional Commits format is the de-facto standard:

feat(auth): add SAML SSO provider
fix(api): handle null user-agent in rate limiter
chore: bump deps
docs: update README deploy section

BREAKING CHANGE: removes /v1/users endpoint

Why this matters for deploys:

  • feat and fix bump SemVer differently when fed into semantic-release or similar tooling.
  • BREAKING CHANGE triggers a major bump and surfaces in release notes.
  • chore, docs, test, refactor can be filtered out of changelogs automatically.

Pair this with commit --fixup and rebase --autosquash to keep PR history clean before merge:

# Notice a typo in commit abc123
git commit --fixup abc123          # marker commit
# ... continue working ...
# Before opening PR, squash the fixup into its target
git rebase -i --autosquash main    # interactive rebase auto-organises fixups

6. Hooks for pre-push checks

Local hooks catch the same class of errors that CI catches, but five seconds earlier and without burning runner minutes:

# .git/hooks/pre-push — runs before every git push
#!/usr/bin/env bash
set -euo pipefail

# Block pushes that include a stray console.log
if git diff --cached origin/main | grep -E '^\+.*console\.log'; then
  echo "ERROR: console.log in staged changes" >&2
  exit 1
fi

# Block pushes that include "FIXME" or "XXX"
if git diff --cached origin/main | grep -E '^\+.*(FIXME|XXX)'; then
  echo "ERROR: FIXME/XXX in staged changes" >&2
  exit 1
fi

# Run quick tests
npm test || exit 1

chmod +x .git/hooks/pre-push to enable. For team-wide hooks (since .git/hooks/ isn't checked into the repo), use husky, lefthook, or pre-commit — they install hooks from a checked-in config file so every clone gets them automatically.

7. git bisect for production-incident triage

When a deploy ships a regression, git bisect finds the breaking commit in O(log n) — a 200-commit range takes 7-8 bisection steps:

git bisect start
git bisect bad                                      # current SHA is broken
git bisect good v1.4.0                              # v1.4.0 was fine
# Git checks out a SHA halfway between
# Run your reproducer:
./reproduce-bug.sh && git bisect good || git bisect bad
# Git checks out a new midpoint; repeat
# Eventually:
# abc123def is the first bad commit
git bisect reset                                    # return to where you started

For deploy regressions specifically: git bisect between the last good tag and the broken one, and the reproducer is your smoke test. Within 5-10 minutes you have a one-line answer to "what shipped that broke prod?"

8. Deploy-key auth from CI

CI runners and production hosts must authenticate to Git with deploy keys, never personal keys. A deploy key:

  • Is generated on the runner/host, never copied from a laptop.
  • Is scoped to one repo (GitHub) or one host (GitLab).
  • Is read-only by default — only repos that need write access (release-tagger bots) get write.
# On a fresh CI runner: generate a per-repo deploy key
ssh-keygen -t ed25519 -f ~/.ssh/deploy_myapp -N "" -C "ci-deploy:myapp"
cat ~/.ssh/deploy_myapp.pub
# Upload that public key to GitHub: Settings → Deploy keys → Add deploy key

# Configure ~/.ssh/config so `git clone` picks the right key
cat >> ~/.ssh/config <<'EOF'
Host github-myapp
    HostName github.com
    User git
    IdentityFile ~/.ssh/deploy_myapp
    IdentitiesOnly yes
EOF

# Clone via the host alias
git clone git@github-myapp:org/myapp.git

The SSH cheatsheet has the full key-rotation playbook for the moment one of these keys is compromised or an employee leaves.


Common errors and fixes

Error / symptom Cause Fix
fatal: refusing to merge unrelated histories Two repos with no common ancestor git pull --allow-unrelated-histories (rare — usually means you cloned the wrong repo)
error: failed to push some refs to ... (non-fast-forward) Remote has commits you don't have git pull --rebase then push again
error: Your local changes to the following files would be overwritten by merge Uncommitted changes block the merge git stash, git pull, git stash pop (resolve any conflicts after pop)
fatal: Not possible to fast-forward, aborting pull.ff=only is set and remote diverged git pull --rebase or git merge --no-ff depending on convention
Permission denied (publickey) on push/clone Wrong SSH key being offered ssh -T git@github.com to confirm auth; add IdentitiesOnly yes and the right IdentityFile to ~/.ssh/config
remote: Repository not found (with valid key) The key authenticated as a different user who can't see the repo Check ssh -T git@github.com — the auth message shows whose account; switch keys via ~/.ssh/config host alias
Detached HEAD warnings You checked out a tag or SHA, not a branch git switch -c branch-name to start tracking work, or git switch main to leave
fatal: ambiguous argument 'main': unknown revision Default branch is still master Either git switch master or rename: git branch -m master main && git push -u origin main
Force-push wiped a teammate's commits Used --force instead of --force-with-lease Recover from reflog: git reflog to find the lost SHA, git push origin <SHA>:refs/heads/<branch> to restore
git pull overwrote my changes Local changes lived in working tree; pull's merge committed over them Recover from reflog: git refloggit reset --hard HEAD@{1} (your pre-pull state)
Submodule directory empty after clone --recurse-submodules not passed git submodule update --init --recursive
error: cannot lock ref ...: ref ... is at A but expected B Stale local ref vs remote (often after a force-push upstream) git fetch --prune origin to clean up
Auto-merging X CONFLICT (content): Merge conflict in X Two branches edited the same lines Resolve conflict markers in the file, git add X, git rebase --continue or git commit
fatal: bad object HEAD Corrupted .git/HEAD (rare) git fsck to assess damage; restore from .git/logs/HEAD or re-clone if irrecoverable
Push hangs forever on a large repo Git LFS misconfigured or large binary in history git lfs ls-files to verify; if a binary slipped into history, git filter-repo --strip-blobs-bigger-than 50M
error: GH001: Large files detected from GitHub A single file >100MB pushed without LFS Install Git LFS, rewrite the offending commit: git lfs migrate import --include="*.bin"
Tags pushed by accident, need to remove git push --tags shipped a draft tag git push origin :refs/tags/v1.4.2-draft to delete remote tag
Wrong author on commits in a long branch user.email was unset or wrong git rebase -i --exec 'git commit --amend --no-edit --author="Name <email>"' main to rewrite all authors

Companion: full DeployHQ deploy workflow

A real Git-based deploy is a webhook from your Git host, a build pipeline that tests + builds the artifact, an SSH transport that ships it to your servers, and an atomic symlink swap that puts new code in front of users. DeployHQ wires this together so you don't script the orchestration yourself.

The end-to-end flow:

  1. Push to main on GitHub or GitLab, or tag a vX.Y.Z release.
  2. DeployHQ's webhook fires and pulls the SHA from your repo.
  3. The build pipeline runs your install/build/test steps in a clean container, produces the release artifact, and caches node_modules / vendor/ between deploys.
  4. DeployHQ SSHes into your servers and runs your deploy hook against the new release directory.
  5. The current symlink swaps atomically — zero-downtime by construction.
  6. If the smoke test fails, one-click rollback restores the previous SHA in one filesystem operation.

Setup walkthroughs: the deploy from GitHub guide covers the GitHub side end-to-end, and the deploy from GitLab guide covers GitLab. Both end with the same atomic-release pattern under the hood.

For teams comparing Git-based deploys to CI-platform deploys, What Is GitOps? covers the principles, and the DeployHQ vs GitHub Actions comparison goes deeper on the trade-offs.

Start a free DeployHQ trial to wire Git into a production deploy pipeline.


  • SSH cheatsheet — for the deploy keys and ~/.ssh/config aliases every git clone from CI depends on.
  • Bash scripting cheatsheet — for the set -euo pipefail deploy hooks that wrap git archive + tar + ln.
  • Docker cheatsheet — for the build pipelines that turn a git archive into a SHA-tagged container image.
  • rsync cheatsheet — for the file-transport pattern that git archive | tar | ssh replaces on simple deploys.
  • Cheatsheets hub — every DeployHQ cheatsheet in one place.

Need help? Email support@deployhq.com or follow @deployhq on X.