Git Basics
- What Git is
- Basic concepts
- Configure Git
- Clone a repo
- Check state
- Get latest main
- Create a branch
- Add and commit
- Push branch
- Compare changes
- Merge / rebase
- Undo mistakes
- Stash
- Merge conflicts
- git fetch
- .gitignore
- git reflog
- Modern: git switch / restore
- Remotes and tracking
- Tags
- cherry-pick and interactive rebase
What Git is
Git is version control. It tracks file changes over time so you can:
- Make changes safely without breaking what works
- Go back to earlier versions
- Work in isolated branches
- Merge work from multiple people
Basic concepts
- repository — the project, its files, and its full change history
- commit — a saved snapshot of the repo at a point in time
- branch — a separate, independent line of work
- remote — the server copy of the repo (e.g. on GitLab)
- origin — the default name given to the main remote when you clone
- main — the common default main branch name
Configure Git
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch main
Sets your name and email for commits. These appear in the commit history. Do this once on each machine.
Clone a repo
git clone git@host:group/repo.git
cd repo
Copies the remote repository to your machine, including the full history. The git@ prefix uses SSH auth.
Check state
git status
git status
Shows what branch you are on, changed files, staged files, and untracked files. This is the single most important Git command. Run it often.
git branch / git log
git branch
git log --oneline --graph --decorate -n 20
git branch shows local branches. git log shows recent commit history. The flags give a compact graph view with branch names.
Get latest main
git checkout main
git pull origin main
git checkout main switches to the main branch. git pull fetches and merges the latest remote changes. Always do this before starting new work.
Create a new branch
git checkout -b feature/my-change # classic (still works everywhere)
git switch -c feature/my-change # modern equivalent
Creates and immediately switches to a new branch. The modern git switch -c is clearer in intent — it cannot accidentally be used to restore files (that is what git restore is for). Use branches to isolate your work from main. Name branches clearly — feature/, fix/, chore/ prefixes help.
Add and commit changes
git add
git add file
git add .
git add file stages a specific file for the next commit. git add . stages all changed files. Be careful with . — do not include accidental changes.
git commit
git commit -m "Add useful change"
Creates a commit with a message describing the change. Write commit messages in the imperative mood: "Add feature", "Fix bug", "Update config".
Push your branch
git push -u origin feature/my-change
Uploads your branch to the remote repo. -u sets the upstream tracking branch — after this you can just run git push on subsequent pushes.
Compare changes
git diff
git diff --staged
git show
git diff shows unstaged changes. git diff --staged shows what you have added but not yet committed. git show shows the most recent commit.
Merge and rebase
Merge main into your branch
git checkout main
git pull origin main
git checkout feature/my-change
git merge main
Rebase your branch onto main
git checkout main
git pull origin main
git checkout feature/my-change
git rebase main
Undo mistakes
Unstage a file
git restore --staged file
Removes a file from staging without losing your edits.
Discard local file changes
git restore file
Throws away unstaged changes in a file. This is permanent — the changes are gone.
Undo last commit, keep changes
git reset --soft HEAD~1
Moves back one commit but keeps your changes staged and available. Useful if your last commit message or contents were wrong.
Revert a commit safely
git revert <commit>
Creates a new commit that undoes an earlier one. This is the safe way to undo changes that have already been pushed — it does not rewrite history.
git reset --hard discards changes permanently. Prefer --soft or git revert on shared branches.
Stash
git stash
git stash pop
git stash list
git stash temporarily hides your current changes. git stash pop restores the most recent stash. Use it when you need to quickly switch branches but your working tree is not clean.
Merge conflicts
git status
# edit the conflicting files — look for <<<<<<< ======= >>>>>>> markers
git add file
git commit
When git cannot automatically merge two versions of the same file, it marks the conflicts inline. Edit the file to resolve them, then stage and commit the resolution.
git fetch
git fetch origin
git fetch --all
git fetch downloads commits and branch info from the remote but does not change your working directory or local branches. Use it to see what has changed on the server before deciding whether to merge.
After fetching you can inspect without merging:
git log HEAD..origin/main # see what's on remote that you don't have yet
git diff HEAD origin/main # see the diff
git pull = git fetch + git merge. Using fetch first gives you a chance to review before merging.
.gitignore
A .gitignore file in your repo root tells Git to never track certain files or directories. Use it to exclude generated files, local configs, and secrets.
# dependencies
node_modules/
__pycache__/
# build output
dist/
*.pyc
# env and secrets
.env
*.key
# editor noise
.DS_Store
.vscode/
Each line is a pattern. Git silently ignores all matching files — they will never appear in git status and can never be accidentally committed.
.gitignore only works for files not already tracked by Git. If you accidentally committed a file first, you need to untrack it: git rm --cached filename
git reflog
git reflog
Shows a local history of every place HEAD has pointed — including commits that are no longer on any branch. This is your recovery tool when something goes wrong.
Common recovery scenarios:
# restore a branch accidentally deleted
git reflog # find the commit hash
git checkout -b recovered abc1234 # recreate branch from that commit
# recover from a hard reset
git reflog # find the commit before the reset
git reset --hard abc1234 # go back to it
git reflog first. Commits are not truly deleted until Git's garbage collection runs (usually after 90 days).
Modern: git switch / git restore
Git 2.23 split git checkout into two purpose-specific commands. Both still work but the new ones are harder to misuse.
# Switch to an existing branch
git switch main
git checkout main # old equivalent
# Create and switch to a new branch
git switch -c feature/new
git checkout -b feature/new # old equivalent
# Discard unstaged changes to a file (restore from working tree)
git restore file.txt
git checkout -- file.txt # old equivalent
# Unstage a file (restore from index)
git restore --staged file.txt
git reset HEAD file.txt # old equivalent
You will see git checkout in most docs and scripts — it still works. Use git switch / git restore in your own work because they are less ambiguous and the error messages are more helpful.
Remotes and tracking branches
# List all remotes
git remote -v
# See which remote branch each local branch tracks
git branch -vv
# Example output:
# feature/login abc123 [origin/feature/login] Add login form
# main def456 [origin/main] Update README
# orphan 789abc No tracking — not connected to a remote
# Set (or fix) the upstream tracking branch
git branch --set-upstream-to=origin/main main
# Push and set upstream in one step (for a new branch)
git push -u origin feature/login
# Fetch all remotes and prune deleted remote branches
git fetch --all --prune
When git status says "Your branch is ahead/behind by N commits", that comparison is against the tracking branch. If git branch -vv shows no tracking branch, git push will not know where to push — run git push -u origin branchname to fix it.
Tags
Tags mark specific commits — typically releases. Unlike branches, tags do not move when you commit.
# Lightweight tag (just a pointer)
git tag v1.0.0
# Annotated tag (has a message, tagger, and date — use these for releases)
git tag -a v1.0.0 -m "Release version 1.0.0"
# Tag a specific commit
git tag -a v0.9.0 abc1234 -m "Previous release"
# Push tags to remote (they are NOT pushed by default)
git push origin v1.0.0 # push one tag
git push --tags # push all tags
# List tags
git tag -l
# Delete a tag (local and remote)
git tag -d v1.0.0
git push origin --delete v1.0.0
CI/CD pipelines often trigger deployment jobs on tags (e.g. rules: if: $CI_COMMIT_TAG in GitLab). Annotated tags are preferred because they carry metadata and are listed by git describe.
cherry-pick and interactive rebase
git cherry-pick
Applies a specific commit from one branch onto the current branch — without merging the whole branch.
# Apply a single commit to current branch
git cherry-pick abc1234
# Apply multiple commits
git cherry-pick abc1234 def5678
# Cherry-pick without committing (stage only, let you edit)
git cherry-pick -n abc1234
Common use: a bug was fixed on main and you need it on a long-lived release/1.x branch without merging all of main.
Interactive rebase (squash, fixup, reorder)
Rewrite your local commit history before pushing — squash "WIP" commits, fix typos in messages, or reorder changes.
# Rewrite the last 3 commits interactively
git rebase -i HEAD~3
An editor opens showing your commits. Change the word pick:
pick abc123 Add user model
squash def456 Fix typo # squash into the previous commit
fixup 789ghi Another typo fix # like squash but discard this message
reword xyz123 Update docs # keep commit, but edit its message