▶Prefer to watch? This short video runs this page's main commands in a real lab, with on-screen captions and no voiceover. Click to play, or watch it on YouTube.
git <command> [options]
git status
Git is a distributed version control system: it records snapshots of your project so you can see what changed,
go back in time, work on ideas in branches, and collaborate through remote copies (GitHub, GitLab, your own server).
Every clone holds the full history. Most commands work offline. The mental model: your files (working tree)
> the staging area (index) > commits (history) > a remote copy. Commands move changes between those places.
Install and first-time setup
sudo apt install git # Debian/Ubuntu sudo dnf install git Fedora/RHEL family
brew install git # macOS winget install Git.Git Windows
git --version
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main # new repos start on "main"
git config --global core.editor "vim" # or nano, "code --wait"
git config --global pull.rebase false # "git pull" merges (use true to rebase instead)
git config --global push.autoSetupRemote true # first "git push" sets the upstream for you (Git 2.37+)
git config --global fetch.prune true # drop remote branches that were deleted on the server
git config --global merge.conflictstyle zdiff3 # clearer conflict markers (Git 2.35+)
git config --global rerere.enabled true # remember how you resolved a conflict and reuse it
git config --list --show-origin # see every setting and where it came from
Config levels: --system (whole machine), --global (~/.gitconfig, you), --local (this repository, the default when you leave the flag off).
Start a repository
git init # turn the current folder into a repository
git init -b main # ...with the branch named main
git clone https://github.com/user/repo.git # copy a remote repository
git clone git@github.com:user/repo.git # over SSH
git clone --depth 1 URL # shallow clone: only the latest snapshot (fast, small)
git clone --branch dev URL mydir # a specific branch, into a named folder
git clone --recurse-submodules URL # also fetch submodules
git clone --filter=blob:none URL # partial clone: fetch file contents on demand
The everyday loop
git status # what changed? git status -sb short form with the branch
git add file.txt # stage one file
git add src/ docs/ # stage folders
git add -p # stage piece by piece (review each hunk)
git add -A # stage everything (new, changed, deleted). Check status first
git commit -m "Add login form" # save a snapshot with a message
git commit # opens your editor for a longer message
git commit -am "Fix typo" # stage tracked files and commit (does not add NEW files)
git commit --amend # change the last commit (message or content). Do not amend pushed commits
git commit --amend --no-edit # add staged changes to the last commit, keep the message
git push # send commits to the remote
git pull # fetch and merge remote changes
Write good messages: a short summary (about 50 characters, imperative: "Fix crash on empty input"),
a blank line, then the why. One logical change per commit.
Look at changes and history
git diff # unstaged changes
git diff --staged # staged changes (what the next commit will contain)
git diff HEAD # everything since the last commit
git diff main..feature # differences between two branches
git diff main...feature # changes on feature since it split from main
git diff --stat # summary of files and line counts
git diff --name-only # just the file names
git log # full history
git log --oneline # one line per commit
git log --oneline --graph --decorate --all # a picture of all branches
git log -p # with the changes in each commit
git log -5 # last five commits
git log --stat # files changed per commit
git log --author="Alice" git log --since="2 weeks ago" git log --until="2026-01-01"
git log --grep="login" # search commit messages
git log -S"apiKey" # commits that added or removed this text (the "pickaxe")
git log -- path/to/file # history of one file git log --follow -- file follow renames
git log main..feature # commits on feature that main does not have
git show # the last commit git show abc123 a specific commit
git show HEAD~2:src/app.py # a file as it was two commits ago
git blame file.py # who last changed each line git blame -L 10,20 file.py lines 10 to 20
git shortlog -sn # commit counts per author
git reflog # every place HEAD has been (your safety net, see Undo)
Branches
git branch # list local branches (* marks the current one)
git branch -a # include remote branches git branch -vv show tracking and last commit
git branch feature # create a branch (does not switch to it)
git switch feature # move to a branch (older: git checkout feature)
git switch -c feature # create and switch (older: git checkout -b feature)
git switch - # jump back to the previous branch
git branch -m new-name # rename the current branch
git branch -d feature # delete a merged branch git branch -D feature force delete
git branch --merged # branches already merged into the current one
git branch --no-merged
git push -u origin feature # publish a branch and set its upstream
git push origin --delete feature # delete a remote branch
git remote prune origin # forget remote branches that no longer exist
HEAD is "where you are". A branch is just a movable label on a commit.
Merge and rebase
git merge feature # merge feature into the current branch
git merge --no-ff feature # always create a merge commit
git merge --squash feature # combine all of feature into one staged change, then commit it yourself
git merge --abort # stop a merge that has conflicts
git rebase main # replay your commits on top of main (linear history)
git rebase -i HEAD~4 # interactive: reorder, squash, reword, drop the last 4 commits
git rebase --continue # after fixing a conflict git rebase --abort give up
git rebase --onto main old-base feature # move a branch from one base to another
Golden rule of rebasing: never rebase or force-push commits other people already have.
Merge keeps the true history. Rebase gives a clean, straight line. Pick one team habit and stick to it.
Fix a merge conflict
1. git status # shows the files marked "both modified"
2. Open each file and look for the markers:
<<<<<<< HEAD your version
=======
>>>>>>> feature their version
Edit the file to what it should be and DELETE all three marker lines
3. git add path/to/fixed-file
4. git commit # (or git rebase --continue during a rebase)
Useful: git checkout --ours file / --theirs file take one side entirely (careful: in a rebase, ours and theirs swap)
git mergetool open a visual merge tool
git diff --name-only --diff-filter=U list files still in conflict
Remotes
git remote -v # list remotes
git remote add origin git@github.com:user/repo.git
git remote set-url origin NEWURL # change a remote's address
git remote rename origin upstream
git fetch # download new commits, do NOT change your files
git fetch --all --prune # every remote, and drop deleted branches
git pull # fetch + merge git pull --rebase fetch + rebase
git pull --ff-only # only fast-forward: refuse to create a merge commit
git push # send the current branch
git push -u origin main # first push: set the upstream
git push --force-with-lease # a safer force push: fails if someone else pushed in the meantime
git push origin --tags # push all tags
Forks: keep the original as "upstream": git remote add upstream URL then git fetch upstream && git merge upstream/main
Undo things (from safest to scariest)
git restore file.txt # throw away unstaged changes in a file (cannot be undone!)
git restore --staged file.txt # unstage a file, keep the changes
git restore --source=HEAD~2 file.txt # bring back a file as it was two commits ago
git commit --amend # fix the last commit (only before you push it)
git revert abc123 # make a NEW commit that undoes commit abc123 (safe for shared history)
git revert -m 1 MERGE_SHA # undo a merge commit
git reset --soft HEAD~1 # undo the last commit, keep changes STAGED
git reset HEAD~1 # undo the last commit, keep changes in your files (unstaged) (--mixed is the default)
git reset --hard HEAD~1 # undo the last commit AND delete the changes (dangerous)
git reset --hard origin/main # make your branch exactly match the remote (drops local work)
git clean -n # DRY RUN: list untracked files that would be deleted
git clean -fd # delete untracked files and folders (cannot be undone)
git stash # shelve changes to get a clean tree (see Stash)
Rescue with reflog: nearly nothing is truly lost for a while.
git reflog # find the commit you were on before the mistake, for example HEAD@{3}
git reset --hard HEAD@{3} # or: git switch -c rescue abc123 to keep it on a new branch
Rule: use revert for commits that are already shared, and reset only for local work.
Stash (a shelf for unfinished work)
git stash # save tracked changes and clean the tree
git stash push -m "wip login" # with a name
git stash -u # include untracked files
git stash list # see the shelf
git stash show -p stash@{0} # view a stash as a patch
git stash pop # re-apply the latest stash and remove it
git stash apply stash@{1} # re-apply but keep it on the shelf
git stash branch newbranch # turn a stash into a branch
git stash drop stash@{0} git stash clear # delete one / all (careful)
Tags and releases
git tag # list tags
git tag v1.0.0 # lightweight tag on the current commit
git tag -a v1.0.0 -m "Release 1.0.0" # annotated tag (recommended for releases)
git tag -a v0.9.0 abc123 # tag an older commit
git push origin v1.0.0 # push one tag git push origin --tags all tags
git tag -d v1.0.0 # delete locally git push origin --delete v1.0.0 delete on the remote
git describe --tags # nearest tag, for example v1.0.0-5-gabc1234
Find things and fix bugs
git grep "TODO" # search the files in the repository (fast)
git grep -n -i "password" -- '*.py'
git log -S"function_name" --oneline # when did this text appear or disappear?
git bisect start # binary-search for the commit that broke something
git bisect bad # the current commit is broken
git bisect good v1.0.0 # this older one worked
# Git checks out a middle commit. Test it, then run: git bisect good or git bisect bad (repeat)
git bisect run ./test.sh # automate it: the script exits 0 for good, non-zero for bad
git bisect reset # finish and return to where you were
git blame -w -C file.py # ignore whitespace, detect moved lines
git cherry-pick abc123 # copy one commit onto the current branch
git cherry-pick A..B # a range
git cherry-pick --abort
.gitignore (files Git should never track)
# .gitignore
node_modules/
__pycache__/
*.log
.env
build/
!important.log # ! re-includes a file
Patterns: *.log any log file, dir/ a folder, /todo.txt only in the repo root, **/temp anywhere.
git rm --cached file # stop tracking a file but keep it on disk (then add it to .gitignore)
git rm -r --cached . # re-apply .gitignore to everything, then git add . and commit
git check-ignore -v path/to/file # which rule ignores this file?
git status --ignored # show ignored files
Global ignore for your editor and OS files: git config --global core.excludesfile ~/.gitignore_global
Never commit secrets: .env files, private keys, tokens and passwords.
Move, rename, delete
git mv old.txt new.txt # rename and stage it
git rm file.txt # delete and stage the deletion
git rm -r folder/
git restore --staged --worktree file # get a deleted file back before committing
Worktrees and submodules
git worktree add ../hotfix hotfix-branch # a second folder with another branch checked out (no need to stash)
git worktree list git worktree remove ../hotfix
git submodule add URL path/to/sub # add another repository inside this one
git submodule update --init --recursive # fetch submodules after a clone
git submodule update --remote # move submodules to their latest commit
git submodule foreach 'git pull'
Authentication (SSH and tokens)
ssh-keygen -t ed25519 -C "you@example.com" # create a key (press Enter for the default location)
cat ~/.ssh/id_ed25519.pub # copy this into GitHub/GitLab: Settings > SSH keys
ssh -T git@github.com # test the connection
git remote set-url origin git@github.com:user/repo.git # switch a repo from HTTPS to SSH
HTTPS with a token: create a personal access token in your account and use it as the password.
git config --global credential.helper cache # remember it in memory for a while
git config --global credential.helper store # save in plain text (avoid on shared machines)
Git Credential Manager (Windows/macOS) and libsecret (Linux) store credentials securely.
GitHub CLI: gh auth login gh repo clone user/repo gh pr create gh pr list gh pr checkout 42
Aliases (shortcuts)
git config --global alias.st "status -sb"
git config --global alias.co "switch"
git config --global alias.lg "log --oneline --graph --decorate --all"
git config --global alias.unstage "restore --staged"
git config --global alias.last "log -1 HEAD --stat"
git config --global alias.amend "commit --amend --no-edit"
Then use: git st git lg git last
Hooks (run scripts automatically)
.git/hooks/pre-commit # runs before each commit (chmod +x). Exit non-zero to block the commit
.git/hooks/commit-msg # check the commit message
.git/hooks/pre-push # run tests before pushing
git config core.hooksPath .githooks # keep hooks in the repo so the team shares them
Frameworks such as pre-commit (pre-commit.com) manage hooks for linting, formatting and secret scanning.
Common team workflows
Feature branch main is always deployable. Work on a branch, open a pull/merge request, review, merge, delete the branch
git switch -c feature/login ...commits... git push -u origin feature/login (open the PR)
Trunk-based Very short-lived branches, small changes merged to main daily, behind feature flags
Git Flow main + develop + feature/release/hotfix branches. Heavier: good for versioned releases
Forking Fork the project, branch in your fork, PR back to the original ("upstream")
Keep your branch up to date: git fetch origin && git rebase origin/main (or git merge origin/main)
Before opening a PR: git status, git diff origin/main, run the tests, and rewrite messy history with git rebase -i
Commit message conventions
Conventional Commits: type(scope): summary feat(auth): add password reset fix(api): handle empty body docs: update README
Common types: feat, fix, docs, style, refactor, perf, test, build, ci, chore. Add "!" or a BREAKING CHANGE: footer for breaking changes.
Reference issues: Fixes #123 (closes the issue when the PR merges on GitHub and GitLab)
Big files and cleanup
git lfs install # Git Large File Storage: keep big binaries out of normal history
git lfs track "*.psd" # then commit .gitattributes
git count-objects -vH # repository size
git gc --aggressive --prune=now # repack and clean up (rarely needed)
git rev-list --objects --all | git cat-file --batch-check='%(objecttype) %(objectname) %(objectsize) %(rest)' | sort -k3 -n -r | head # biggest objects
GitHub rejects single files over 100 MB. Do not commit build output, videos, databases or dependencies.
Removing a secret that was committed
1. Assume it is compromised. REVOKE and ROTATE the key or password first. This matters more than any cleanup
2. Remove it from the current files and commit
3. Rewrite history to remove it from old commits, using git filter-repo (a separate tool: pip install git-filter-repo):
git filter-repo --invert-paths --path secrets.txt # or use --replace-text for a string
(BFG Repo-Cleaner is an alternative)
4. Force-push all branches and tags, and tell everyone to re-clone. Old clones and forks may still hold the secret
Rewriting history breaks other people's copies, and does not un-leak a secret that was already public.
Practical one-liners
# Undo the last commit but keep the work
git reset --soft HEAD~1
# I committed on the wrong branch (not pushed yet): move the commit to a new branch
git branch feature-x; git reset --hard HEAD~1; git switch feature-x
# Change the last commit message
git commit --amend -m "Better message"
# Discard ALL local changes and match the remote
git fetch origin && git reset --hard origin/main
# Show what you would push
git log origin/main..HEAD --oneline
# List branches sorted by most recent commit
git for-each-ref --sort=-committerdate refs/heads --format='%(committerdate:short) %(refname:short)'
# Delete all local branches already merged into main
git branch --merged main | grep -v '^\*\|main' | xargs -r git branch -d
# Squash the last 3 commits into one
git reset --soft HEAD~3 && git commit -m "One combined commit"
# See who changed a file and why
git log --follow -p -- path/to/file
# Count commits per author this year
git shortlog -sn --since="1 January 2026"
# Find the commit that deleted a file
git log --diff-filter=D --summary -- path/to/file
# Save a patch and apply it elsewhere
git format-patch -1 HEAD then git am 0001-*.patch
# Export a clean copy of the project (no .git folder)
git archive --format=zip -o project.zip HEAD
# Search every commit for a word (slow on big repos)
git grep "search term" $(git rev-list --all)
# What is on disk that git does not track?
git status --ignored --short
Common problems
"fatal: not a git repository" You are not inside a repository. cd into it, or run git init
"Please tell me who you are" Set user.name and user.email (see setup)
"detached HEAD" You checked out a commit, not a branch. Keep work with: git switch -c my-branch
"rejected ... non-fast-forward" / "fetch first" The remote has commits you do not. git pull --rebase (or git pull), then push again
"Your local changes would be overwritten" Commit or stash first, then pull or switch
"CONFLICT (content): Merge conflict in ..." Edit the file, remove the markers, git add, then commit or rebase --continue
"refusing to merge unrelated histories" Two repos with no common ancestor. If sure: git pull --allow-unrelated-histories
"fatal: Unable to create '.git/index.lock'" Another git process died. Make sure none is running, then delete .git/index.lock
"Permission denied (publickey)" SSH key not added to your account or the agent: ssh-add ~/.ssh/id_ed25519, and test ssh -T
"Authentication failed" over HTTPS Passwords no longer work on most hosts: use a personal access token or a credential manager
Whole file shows as changed (line endings) CRLF vs LF. Set core.autocrlf (true on Windows, input on Linux/macOS) or use .gitattributes
Mode changes on every file (chmod) git config core.fileMode false (on filesystems that do not keep permissions)
File is still tracked after adding to .gitignore git rm --cached file, then commit
"pathspec did not match any file(s)" Typo, wrong folder, or the file is not tracked
Pushed the wrong thing / a secret See "Removing a secret" above, and rotate the credential immediately
Lost commits after a reset or rebase git reflog, then git reset --hard HEAD@{n} or git switch -c rescue SHA
Repository is huge or slow Look for big files (see Big files), use --depth or partial clones, and git gc
Tips
Commit small and often, with clear messages. Small commits are easy to review, revert and bisect.
Run git status before every add, commit and push. Run git diff --staged before you commit.
Never work directly on main for shared projects. Use a branch and a pull request.
Use git switch and git restore (clearer than the old git checkout). Use git revert for shared history, and reset only for local work.
Use git push --force-with-lease, never plain --force, and never force-push shared branches.
Before anything risky (rebase, reset --hard, clean -fd), make a safety branch: git branch backup-before-rebase
git reflog is your undo history. Almost nothing is lost for a few weeks.
Keep secrets out of the repository with .gitignore, environment variables and secret scanning. If one leaks, rotate it first.
Pull or rebase often so your branch does not drift, and conflicts stay small.
Add a .gitignore and a README at the start of every project, and use aliases for the commands you type all day.