Git Basics

Page 03 — What Git is, how branching works, and how to undo mistakes safely.

What Git is

Git is version control. It tracks file changes over time so you can:

Basic concepts

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
Rebase vs merge: Rebase replays your commits on top of the latest main, giving a clean linear history. Merge creates a merge commit but preserves the full branch history. Follow your team's convention.

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.

Warning: 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
fetch vs pull: 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.

Warning: .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
Tip: If you think you lost a commit, check 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
Never rebase commits that have already been pushed to a shared branch. Rewriting history that others have pulled causes conflicts for everyone. Interactive rebase is safe only on your own private branch (commits not yet pushed, or pushed to your own fork).