A practical Git reference for day-to-day work. Focus: modern commands, clean commits, safe history, and recovery when things go wrong.

Setup Once

git config --global init.defaultBranch main
git config --global push.autoSetupRemote true

# Safe pull behavior: only fast-forward, never auto-merge or auto-rebase
git config --global pull.ff only

# Rebase helpers, only when you manually run rebase
git config --global rebase.autoStash true
git config --global rebase.autoSquash true

# Cleanup / conflict quality
git config --global fetch.prune true
git config --global rerere.enabled true
git config --global merge.conflictStyle zdiff3
git config --global diff.colorMoved zebra

# Editor
git config --global core.editor "nvim"

This setup makes git pull safe by default. It only allows fast-forward updates and refuses to guess whether you wanted a merge or a rebase. The configuration commands are Git commands rather than shell-specific cleanup logic, so they work consistently across common shells.

rebase.autoStash is convenient, but it is not a substitute for checking git status. Reapplying the temporary stash can still produce conflicts.


Mental Model

Place What it contains Inspect with
Working tree files you are editing git status, git diff
Index changes selected for the next commit git diff --staged
HEAD currently checked-out commit, normally the tip of the current branch git show, git log

Git usually moves changes through:

Working tree → Index → Commit

Your working tree can be messy. Your commit history should explain how the system changed.


Commit Structure

A good commit is one logical change that can be reviewed, reverted, or transferred safely.

<type>: <imperative verb> <specific change>

<why the change is needed, when the subject is not enough>

Examples:

feat: add resumable uploads
fix: preserve retry count after worker restart
refactor: extract storage interface
test: cover interrupted transfers
docs: document recovery procedure
build: update package lock file
ci: run integration tests on pull requests
chore: remove obsolete development script

Common types:

Type Use for
feat new behavior
fix bug fix
refactor code change without behavior change
test tests only
docs documentation
build dependencies or build system
ci CI configuration
chore general maintenance

Start with a verb such as add, fix, remove, prevent, rename, or update. Keep the subject concise and omit the trailing period.

Before committing:

git add -p
git diff --staged
git diff --staged --check
make check                   # or the project's test command
git commit

Tests, migrations, lock files, and required configuration should normally travel with the change.

WIP commits are fine locally:

git commit -m "WIP: debug interrupted uploads"

Clean them before merging:

git fetch origin
git rebase -i origin/main

For automatic cleanup, use fixup! commits:

git add -p
git commit --fixup <commit>
git rebase -i --autosquash origin/main

Daily Workflow

git switch main
git pull --ff-only

git switch -c feature/x
# edit...

git status
git diff
git add -p
git diff --staged
make check
git commit

git fetch origin
git rebase origin/main

git log --oneline origin/main..HEAD
git diff --stat origin/main...HEAD
git push

Use small branches with one clear purpose. Do not rebase a branch other people are using.


Worktrees

Use a worktree for parallel work, reviews, or hotfixes without disturbing the current directory.

git fetch origin
git worktree add -b hotfix/critical ../repo-hotfix origin/main
git worktree add --detach ../repo-review origin/feature/pr-123

git worktree list
git worktree remove ../repo-review
git worktree prune

The detached review worktree avoids claiming a local branch that may already be checked out elsewhere. Create a branch inside it only when you decide to make changes.

For a quick interruption, a stash is fine. For longer or parallel work, prefer a worktree.


Clone and Create Repositories

Task Command
clone git clone <url>
clone into folder git clone <url> mydir
initialize git init
add remote git remote add origin <url>
inspect remotes git remote -v
first push git push -u origin HEAD

Branches

Task Command
list branches git branch -vv
create and switch git switch -c name
switch git switch name
create local branch from remote git switch --track origin/name
rename current git branch -m newname
delete local safely git branch -d name
force-delete local git branch -D name
delete remote git push origin --delete name

Prefer -d. Use -D only when you intentionally want to bypass Git’s safety check.


Stage, Unstage, and Discard

Stage

Task Command
stage selected hunks git add -p
stage file git add path
stage everything git add -A

Undo

Task Command
unstage file git restore --staged path
discard unstaged changes in file git restore path
discard staged and unstaged changes git restore --source=HEAD --staged --worktree path
restore file from branch git restore --source <branch> -- path
preview untracked cleanup git clean -nd
delete untracked files git clean -fd

Always preview git clean with -n first.


Inspect Changes and History

Task Command
current state git status
unstaged changes git diff
staged changes git diff --staged
whitespace errors git diff --check
show commit git show <sha>
compact graph git log --oneline --decorate --graph --all
commits on this branch git log --oneline origin/main..HEAD
branch summary git diff --stat origin/main...HEAD
blame file git blame path

Fetch, Pull, and Push

Task Command Notes
update remote refs git fetch safe; does not change your files
update local main git pull --ff-only refuses accidental merge commits
update private feature branch run git fetch origin, then git rebase origin/main keeps branch linear
update shared feature branch run git fetch origin, then git merge origin/main avoids rewriting shared history
push current branch git push auto-upstream when configured
push new branch explicitly git push -u origin HEAD works without global config

push does not choose merge or rebase. That decision happens when you integrate remote changes into your local branch.


Rebase and Merge

  • Rebase a private feature branch to keep it current and linear.
  • Merge when preserving shared history matters.
  • Never rewrite published commits on main, release branches, or branches other people use.

Clean a private feature branch:

git fetch origin
git rebase origin/main

# first push
git push

# after rewriting an already-pushed private branch
git push --force-with-lease --force-if-includes

Use --force-with-lease, never plain --force. --force-if-includes adds another check that remote updates have been incorporated locally before Git replaces the remote branch. It is most useful for the normal private-branch rebase case; deliberate recovery may require a carefully scoped lease instead.

Resolve a conflict

git status
# edit conflicted files
git add <resolved-files>
make check
git rebase --continue

Cancel the operation:

git rebase --abort

Do not resolve conflicts mechanically. The result must still make sense on the target branch.


Transfer Changes Deliberately

Copy selected files from another branch

Use this when you want file content, not the original commit:

git restore --source <source-branch> -- path/to/file
git diff
git add -p
make check
git commit

Apply a patch

Use git apply for plain file changes:

git apply --check change.patch
git apply change.patch

git diff
make check
git add -p
git commit

For patches created by git format-patch, preserve the commit metadata:

git am -3 0001-change.patch

Cancel a failed mail patch:

git am --abort

Cherry-pick a controlled backport

Cherry-pick is best for a known, self-contained fix that must be copied to a release or hotfix branch.

Inspect first:

git show --stat <sha>
git show <sha>

Apply without committing immediately:

git cherry-pick --no-commit <sha>
git diff --staged
make check
git commit -c <sha>

For an intentional backport with source traceability:

git cherry-pick -x <sha>

If dependencies are unclear, use a worktree and inspect both branches instead of cherry-picking blindly.

Cancel:

git cherry-pick --abort

Fix Commits

Amend the latest commit

git add -p
git commit --amend

Correct an earlier commit

git add -p
git commit --fixup <commit>
git fetch origin
git rebase -i --autosquash origin/main

To update the target commit’s message as part of the fixup:

git commit --fixup=amend:<commit>
git rebase -i --autosquash origin/main

Squash, reorder, or edit commits

git rebase -i origin/main
# or:
git rebase -i HEAD~5

Only rewrite commits that have not become shared history.


Stash

Task Command
stash tracked changes git stash push -m "wip"
include untracked files git stash push -u -m "wip"
list git stash list
inspect git stash show -p stash@{0}
apply and keep git stash apply stash@{0}
apply and remove git stash pop
recover on a new branch git stash branch recovery/wip stash@{0}
delete git stash drop stash@{0}

Bisect

Use git bisect to find the commit that introduced a bug.

Start from a known bad commit and a known good commit:

git bisect start
git bisect bad
git bisect good <known-good-sha>

Git checks out a commit in the middle. Test it, then mark it:

git bisect good
# or:
git bisect bad

When finished:

git bisect reset

Automate the search with a test command:

git bisect start
git bisect bad
git bisect good <known-good-sha>
git bisect run make test
git bisect reset

A test command should return 0 for good, 1127 except 125 for bad, and 125 when the current commit cannot be tested. Any other exit status aborts the bisect run.


Tags

Task Command
list git tag
create annotated tag git tag -a v1.2.3 -m "v1.2.3"
push one tag git push origin v1.2.3
push commits plus reachable annotated tags git push --follow-tags
push every local tag git push origin --tags

Prefer annotated tags for releases. Use --follow-tags for normal release pushes; use --tags only when you intentionally want to publish every local tag.


Safety Habits

Before risky history changes:

git branch backup/before-rewrite

Before destructive cleanup:

git clean -nd

Before pushing:

git log --oneline origin/main..HEAD
git diff --stat origin/main...HEAD

Protect shared branches in the hosting platform:

  • require pull requests
  • require tests and reviews
  • block direct pushes
  • block force pushes
  • block branch deletion

Branches are cheap. Recovery work is not.


Recovery

Undo a published commit

git revert <sha>
git push

Undo the latest local commit, keep changes

git reset --soft HEAD~1

Find apparently lost work

git reflog
git branch recovery/<name> <sha>

Create a recovery branch first. Investigate before using reset --hard.

Abort an operation

git rebase --abort
git merge --abort
git cherry-pick --abort
git am --abort

Common Situations

Committed to main by mistake

git switch -c fix/moved-work
git switch main
git fetch origin
git reset --hard origin/main

Confirm the new branch contains the commit before resetting main. Fetch first so origin/main reflects the current remote branch rather than an older local snapshot.

Need a hotfix while other work is unfinished

git fetch origin
git worktree add -b hotfix/critical ../repo-hotfix origin/main
cd ../repo-hotfix

Review another branch without touching current work

git fetch origin
git worktree add --detach ../repo-review origin/<branch>
cd ../repo-review

Force-pushed the wrong history

git reflog
git branch recovery/force-push <sha>
git fetch origin
git push --force-with-lease origin recovery/force-push:<target-branch>

Verify the recovery commit before updating the remote branch. Fetch immediately before the push so the lease rejects a concurrent update made after your last check. This deliberate recovery command does not add --force-if-includes, because replacing the bad remote history is the point of the operation.


Minimal .gitignore

.DS_Store
*.log
.env
dist/
build/
__pycache__/
*.pyc
node_modules/