Everything needed to go from a fresh machine to a fully configured, productive git setup: installation, identity/SSH config, the core daily workflow, branching, undoing mistakes, and a full command reference with tips. Written as a single reference note so it can be searched top to bottom or jumped into via the table of contents below.
# Debian / Ubuntusudo apt update && sudo apt install git# macOS (Homebrew)brew install git# Windows# Download from https://git-scm.com/download/win# or via winget:winget install --id Git.Git -e --source winget
Verify installation:
git --version
Windows users
Install Git Bash alongside Git for Windows. It gives a Unix-like shell so every command in this guide works identically, instead of translating to PowerShell equivalents.
2. Initial Configuration
Git config exists at three levels, checked in this order of priority:
graph TD
A["--local
(this repo only)"] -->|overrides| B["--global
(this user, all repos)"]
B -->|overrides| C["--system
(all users on machine)"]
Use SSH-style remote URLs from now on: git@github.com:username/repo.git instead of https://github.com/username/repo.git.
4. Core Concepts
graph LR
A["Working Directory
(your actual files)"] -->|git add| B["Staging Area
(the index)"]
B -->|git commit| C["Local Repository
(.git history)"]
C -->|git push| D["Remote Repository
(GitHub/GitLab)"]
D -->|git pull / fetch| A
Term
Meaning
Working directory
The actual files you see and edit on disk
Staging area (index)
A holding zone for changes you’re about to commit
Repository (repo)
The .git folder containing full project history
Commit
A saved snapshot of staged changes, with a message and unique hash
Branch
A movable pointer to a commit, letting you diverge from the main line of history
HEAD
A pointer to the commit your working directory currently reflects (usually the tip of the current branch)
Remote
A version of the repo hosted elsewhere (e.g. GitHub), referenced by a short name like origin
5. Starting a Repository
New project
mkdir my-project && cd my-projectgit init
Cloning an existing project
git clone git@github.com:username/repo.gitgit clone git@github.com:username/repo.git custom-folder-name # clone into a specific folder namegit clone --depth 1 git@github.com:username/repo.git # shallow clone, only latest commit (faster, smaller)
6. The Daily Workflow
git status # see what's changed, staged, or untrackedgit add file.txt # stage a specific filegit add . # stage everything in the current directorygit add -p # stage interactively, chunk by chunk (great for reviewing your own diff before committing)git commit -m "Add feature X" # commit staged changesgit commit -am "Fix bug" # stage AND commit all tracked, modified files in one step (skips new/untracked files)git push # send commits to the remotegit pull # fetch + merge remote changes into current branch
Commit often, in small logical chunks
Small commits with clear messages are easier to review, revert individually, and bisect through later when hunting for a bug. Avoid one giant “final changes” commit.
Writing good commit messages
<type>: <short summary, imperative mood, under 50 chars>
<optional longer description explaining WHY, not just what>
git commit -m "fix: correct off-by-one error in pagination"git commit -m "feat: add dark mode toggle to settings page"git commit -m "docs: update README install instructions"
Prefix
Use for
feat
a new feature
fix
a bug fix
docs
documentation only
refactor
code change that neither fixes a bug nor adds a feature
chore
tooling, dependencies, config
test
adding or correcting tests
7. Branching & Merging
git branch # list local branchesgit branch -a # list local AND remote branchesgit branch feature/login # create a new branch (doesn't switch to it)git switch feature/login # switch to an existing branch (modern syntax)git switch -c feature/login # create AND switch in one stepgit checkout feature/login # older equivalent of 'switch'git checkout -b feature/login # older equivalent of 'switch -c'git branch -d feature/login # delete a branch (safe - refuses if unmerged)git branch -D feature/login # force delete, even if unmerged
Merging
git switch maingit merge feature/login # merge feature/login INTO the current branch (main)
graph LR
A((commit A)) --> B((commit B))
B --> C((commit C - main))
B --> D((commit D - feature))
D --> E((commit E - feature))
C --> F(("merge commit
main"))
E --> F
Rebasing (Alternative to Merging)
git switch feature/logingit rebase main # replay feature/login's commits on top of the latest main
Never rebase commits that have already been pushed and shared with others
Rebasing rewrites commit history (new hashes). If anyone else has pulled the old commits, rebasing creates conflicting histories. Safe to use freely on local, unpublished branches.
Merge vs rebase, in one line
Merge preserves true history with a merge commit (safer for shared branches). Rebase creates a cleaner, linear history (better for tidying up a personal feature branch before opening a pull request).
Resolving merge conflicts
git status # shows which files have conflicts# open the file, look for conflict markers:
<<<<<<< HEAD
your current branch's version
=======
the incoming branch's version
>>>>>>> feature/login
# after manually editing to resolve:git add resolved-file.txtgit commit # completes the merge# OR, if mid-rebase:git rebase --continue
git merge --abort # bail out of a merge entirely, back to pre-merge stategit rebase --abort # same, for a rebase in progress
8. Working with Remotes
git remote -v # list remotes with URLsgit remote add origin git@github.com:me/repo.git # link a local repo to a remotegit remote set-url origin git@github.com:me/new.git # change a remote's URLgit remote remove origin # unlink a remotegit push -u origin main # push AND set upstream tracking (only needed once per branch)git push # subsequent pushes, once upstream is setgit push origin feature/login # push a specific branchgit fetch # download remote changes WITHOUT merging them into your working branchgit fetch origin # fetch from a specific remotegit pull # fetch + merge in one stepgit pull --rebase # fetch + rebase instead of merge (cleaner history)git push origin --delete feature/login # delete a branch on the remote
fetch vs pull
git fetch downloads new commits and branches but leaves your working branch untouched, letting you inspect changes (git log main..origin/main) before deciding to merge. git pull does fetch + merge immediately, which is faster to type but skips that inspection step.
9. Undoing Things
Read this section before panicking about lost work
Git rarely truly deletes anything for at least 30-90 days. git reflog (see 16 Troubleshooting) can usually recover a “lost” commit.
Discard changes in the working directory (DESTRUCTIVE)
git restore file.txt # discard uncommitted changes to a specific filegit restore . # discard ALL uncommitted changes, everywheregit checkout -- file.txt # older equivalent
Amend the last commit
git commit --amend -m "New corrected message" # fix the last commit's messagegit commit --amend --no-edit # add currently staged changes to the last commit, keep its message
Only amend commits that haven't been pushed yet
Amending rewrites the commit hash - same rule as rebasing applies.
Double-check git status before running it - there’s no undo for uncommitted changes lost this way.
Revert (undo via a NEW commit - safe for shared/pushed history)
git revert <commit-hash> # creates a new commit that undoes the specified commitgit revert HEAD # revert the most recent commit
reset vs revert
reset rewrites history (only safe locally, before pushing). revert adds a new commit that cancels out an old one, preserving full history - always safe on shared/pushed branches.
10. Stashing
Temporarily shelve uncommitted changes without committing them, e.g. to switch branches quickly.
git stash # stash all uncommitted changesgit stash -u # also stash untracked filesgit stash save "WIP: login form" # stash with a descriptive messagegit stash list # see all stashesgit stash apply # re-apply the most recent stash, KEEP it in the stash listgit stash pop # re-apply the most recent stash, REMOVE it from the listgit stash apply stash@{2} # apply a specific, older stashgit stash drop stash@{2} # delete a specific stash without applying itgit stash clear # delete ALL stashesgit stash show -p stash@{0} # view the diff of a stash without applying it
11. Tags & Releases
git tag v1.0.0 # lightweight tag on the current commitgit tag -a v1.0.0 -m "First stable release" # annotated tag (recommended - stores author, date, message)git tag # list all tagsgit tag -a v1.0.0 <commit-hash> -m "message" # tag a specific past commitgit push origin v1.0.0 # push a single taggit push origin --tags # push all tagsgit tag -d v1.0.0 # delete a local taggit push origin --delete v1.0.0 # delete a remote tag
Annotated vs lightweight tags
Use annotated tags (-a) for releases - they’re stored as full objects with metadata. Lightweight tags are just a name pointing to a commit, better suited for temporary/private bookmarks.
12. .gitignore
Create a .gitignore file in the repo root to exclude files from tracking.
GitHub maintains ready-made .gitignore templates per language/framework at github.com/github/gitignore - start from one instead of writing from scratch.
13. Useful Aliases
Add to ~/.gitconfig under [alias], or set via command line:
[alias] st = status co = checkout br = branch cm = commit -m last = log -1 HEAD unstage = restore --staged visual = log --graph --oneline --all --decorate
Now git st works exactly like git status, git visual gives a readable branch graph, etc.
14. Full Command Reference
Inspecting history
git log # full commit historygit log --oneline # condensed, one line per commitgit log --oneline --graph --all # visual branch graph across all branchesgit log -p file.txt # history of a specific file, with diffsgit log --author="Name" # filter by authorgit log --since="2 weeks ago" # filter by dategit log -n 5 # limit to the last 5 commitsgit show <commit-hash> # full details of a single commitgit blame file.txt # who last changed each line, and in which commit
Comparing changes
git diff # unstaged changes vs the last commitgit diff --staged # staged changes vs the last commitgit diff main feature/login # differences between two branchesgit diff HEAD~3 HEAD # differences over the last 3 commits
Removing / moving files
git rm file.txt # delete a file and stage the deletiongit rm --cached file.txt # stop tracking a file, but KEEP it on diskgit mv old.txt new.txt # rename/move a file, staged automatically
Cherry-picking
git cherry-pick <commit-hash> # apply a specific commit from another branch onto the current onegit cherry-pick <hash1> <hash2> # apply multiple specific commits
Bisecting (binary search for the commit that introduced a bug)
git bisect startgit bisect bad # current commit is brokengit bisect good v1.0.0 # this earlier commit was known-good# git checks out a midpoint commit - test it, then:git bisect good # orgit bisect bad# repeat until git identifies the exact breaking commitgit bisect reset # exit bisect mode, return to original HEAD
Submodules
git submodule add git@github.com:user/lib.git libs/lib # add a submodulegit submodule update --init --recursive # initialize/fetch submodules after cloninggit clone --recurse-submodules git@github.com:user/repo.git # clone a repo AND its submodules in one step
15. Common Workflows
Feature Branch Workflow
graph LR
A[main] -->|branch off| B[feature/login]
B -->|commits| B
B -->|push + open PR| C[Pull Request Review]
C -->|approved & merged| A
B -->|delete after merge| X[branch removed]
git switch maingit pull # start from the latest maingit switch -c feature/login # branch off# ... work, commit ...git push -u origin feature/login # push, open a Pull Request on GitHub/GitLab# after PR is approved and merged on the remote:git switch maingit pullgit branch -d feature/login # clean up the now-merged local branch
git revert <bad-commit-hash> # never force-push over shared historygit push
16. Troubleshooting
”Detached HEAD” state
git status# HEAD detached at <hash>
What happened
You checked out a specific commit or tag directly, rather than a branch. You CAN commit here, but those commits won’t belong to any branch and can be lost.
git switch -c new-branch-name # save your work by creating a branch from this point# OR, if you don't need to keep changes:git switch main # simply move back to a real branch
Recovering “lost” commits with reflog
git reflog # shows EVERY HEAD movement, including resets and rebasesgit checkout <hash-from-reflog> # jump back to a commit that git log no longer showsgit switch -c recovered-branch # save it permanently as a new branch
git reflog is the safety net
Even after git reset --hard or a botched rebase, the old commits usually still exist in git’s object database for a while - reflog finds them.
Accidentally committed to the wrong branch
git reset --soft HEAD~1 # undo the commit, keep changes stagedgit switch correct-branchgit commit -m "Original message" # recommit on the right branch
Merge conflict panic
git status # see which files are conflictedgit diff # see the conflict markers in context# edit files to resolve, removing <<<<<<< ======= >>>>>>> markersgit add <resolved-files>git commit # or 'git rebase --continue' if mid-rebase
Force-push safety
git push --force # DANGEROUS - can silently overwrite others' commits on the remotegit push --force-with-lease # SAFER - fails if the remote has commits you haven't seen yet
Prefer --force-with-lease over --force, always
--force-with-lease checks that no one else has pushed since your last fetch, refusing the push if so - preventing you from accidentally destroying a collaborator’s work.
Large file accidentally committed
# For a file in the LAST commit only:git rm --cached big-file.zipgit commit --amend --no-edit# For a file buried deep in history, use a dedicated tool instead of manual surgery:# pip install git-filter-repogit filter-repo --path big-file.zip --invert-paths
Reminder Git tracks everything you commit, forever recoverable via reflog. The main way to truly lose work is to never commit it in the first place - commit early, commit often.