Track Changes with Git
Introduction
This lesson covers the core of course 3 of the Meta Front-End Developer pathway ("Version Control"): using Git to record the history of a project, work on ideas in isolation with branches, and share work through remotes such as GitHub. Git is the standard version-control system in front-end development. Every React project you build in the next chapters will live in a Git repository, and almost every team will ask you to use it from your first day.
Version control answers questions that every developer eventually asks: "What changed since yesterday?", "Who changed this line and why?", "Can I go back to the version that worked?", and "How can two people edit the same project without overwriting each other?" This lesson teaches the day-to-day commands and, more importantly, the mental model that makes them predictable.
The practice scenario is original: you will put the Harbor Workshops project under version control, make a series of commits, create a feature branch for a seat-warning feature, and connect the repository to a remote.
Learning objectives
- Explain what a repository, commit, and branch are, and what makes Git distributed.
- Describe the three areas of Git: working directory, staging area, and repository.
- Configure Git and create a repository with
git initorgit clone. - Use
git status,git add,git commit,git diff, andgit logfluently. - Write useful commit messages and use
.gitignorecorrectly. - Create, switch, merge, and delete branches.
- Undo mistakes safely with
git restore,git revert, andgit reset, and choose the right one. - Use
git remote,git push,git fetch, andgit pull, and explain the difference between them.
1. The mental model
Git records snapshots of your project. Each snapshot is a commit: it stores the state of all tracked files at a moment, a message, an author, a timestamp, and a pointer to its parent commit (or parents, for merges). Following the parent pointers gives you the history.
Git is distributed: every clone holds the full history, so you can commit, view the log, and create branches offline. A remote such as GitHub is just another copy that the team agrees to treat as the shared one.
The three areas
Files move through three areas, and nearly every beginner mistake comes from forgetting which area a change is in.
| Area | What it is | Commands that move changes |
|---|---|---|
| Working directory | The files you edit on disk | You edit here |
| Staging area (index) | A draft of the next commit | git add moves changes in, git restore --staged moves them out |
| Repository | The saved history of commits | git commit records the staged draft |
The staging area lets you build a commit deliberately. If you fixed a bug and also reformatted a file, you can stage and commit them separately so each commit has one purpose.
Each tracked file is always in one of these states: untracked (new, unknown to Git), modified (changed since the last commit), staged (marked for the next commit), or committed (safely in history).
Git is not GitHub
Git is the version-control tool installed on your machine. GitHub (like GitLab and Bitbucket) is a hosting service for Git repositories with collaboration features such as pull requests and issues. You can use Git without GitHub, but not GitHub without Git.
2. First-time setup
Tell Git who you are. This information is written into each commit.
git config --global user.name "Amira Haddad"
git config --global user.email "amira@example.com"
git config --global init.defaultBranch main
git config --list
init.defaultBranch main makes new repositories start with a branch named main, the current convention on GitHub. Without it, older Git versions may name the first branch master. The name is only a convention; the history is identical.
Do not use the global config for credentials. Authentication to GitHub uses a Personal Access Token over HTTPS, or an SSH key, or a credential helper.
3. Creating a repository and the basic cycle
cd ~/projects/harbor-workshops
git init
# Initialized empty Git repository in .../harbor-workshops/.git/
git status
git init creates the hidden .git folder, which holds the entire history. Never edit it by hand, and never delete it unless you intend to destroy the history.
git status is the command you will use most. Read it before and after every other command.
git status
# On branch main
# No commits yet
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
# README.md
# src/
Stage and commit
git add README.md # stage one file
git add src/ # stage a folder
git add . # stage everything in this folder (check status first!)
git status
# Changes to be committed:
# new file: README.md
# new file: src/App.jsx
git commit -m "Add project README and App skeleton"
# [main (root-commit) 3f2a1c9] Add project README and App skeleton
git add . is convenient but dangerous when you have not checked what it will include: it will stage anything not ignored, including accidental large files or secrets. Run git status first, or use git add -p to review changes piece by piece.
Inspect changes
git diff # working directory vs staging area (unstaged changes)
git diff --staged # staging area vs last commit (what will be committed)
git log # full history
git log --oneline # one line per commit
git log --oneline --graph --all # branch structure as a text graph
git show 3f2a1c9 # details of one commit
git blame src/App.jsx # who last changed each line
A commit is identified by a hash (for example 3f2a1c9). You can use the first 7 or so characters as long as they are unique.
Good commit messages
A commit message is a note to your future self and your team.
- Start with a short summary (about 50 characters), in the imperative mood: "Add seat warning", "Fix double booking".
- Describe what and why, not a transcript of keystrokes. Put detail after a blank line when needed.
- Make each commit one logical change that leaves the project working.
| Weak message | Better message |
|---|---|
update |
Fix seat count when two bookings arrive together |
changes |
Add SeatWarning component for low capacity |
final version real |
Extract date formatting into formatDate helper |
Commit often, in small steps. Small commits are easy to review, easy to revert, and make git blame and git bisect useful.
.gitignore
A .gitignore file lists patterns that Git should not track:
node_modules
dist
.env
*.log
.DS_Store
Ignoring only prevents untracked files from being added. If a file such as .env was already committed, adding it to .gitignore does not remove it from history. You must stop tracking it with git rm --cached .env and, if it contained a secret, rotate the secret, because it remains in the history.
4. Branches
A branch is a movable pointer to a commit. Creating one is nearly free because Git only creates a new pointer. HEAD is the pointer that says which branch (or commit) you are currently on.
git branch # list local branches (* marks the current one)
git branch seat-warning # create a branch
git switch seat-warning # move to it
git switch -c seat-warning # create and switch in one step
git checkout -b seat-warning # older equivalent
git switch and git restore were introduced to split the overloaded git checkout into clearer commands. Both styles work, and you will meet checkout in tutorials and older projects.
Now you commit on the branch without disturbing main:
git switch -c seat-warning
# edit src/components/WorkshopCard.jsx
git add src/components/WorkshopCard.jsx
git commit -m "Show warning when fewer than 3 seats remain"
git log --oneline --graph --all
# * 8d41b7e (HEAD -> seat-warning) Show warning when fewer than 3 seats remain
# * 3f2a1c9 (main) Add project README and App skeleton
Merging
When the feature is ready, bring it into main.
git switch main
git merge seat-warning
There are two outcomes.
- Fast-forward:
mainhas not moved since you branched, so Git just moves themainpointer forward. No new commit is created. - Merge commit: both branches have new commits, so Git creates a commit with two parents that combines them.
git branch -d seat-warning # delete the merged branch (safe: refuses if unmerged)
git branch -D seat-warning # force-delete (use only when you mean it)
If both branches changed the same lines, Git stops with a conflict. Resolving conflicts is the subject of the next lesson.
Typical branch workflow
- Make sure
mainis up to date. - Create a short-lived branch named for the work (
seat-warning,fix/date-parse). - Commit in small steps.
- Merge back, usually through a pull request.
- Delete the branch.
Short branches reduce conflicts, because less time passes for the code to diverge.
5. Undoing things safely
Choosing the right undo depends on where the mistake is and whether it has been shared.
| Situation | Command | Effect |
|---|---|---|
| Discard unstaged edits in a file | git restore src/App.jsx |
Replaces the file with the staged/last committed version. Edits are lost. |
| Unstage a file but keep edits | git restore --staged src/App.jsx |
Removes it from the next commit |
| Fix the last commit message or add a forgotten file (not pushed) | git commit --amend |
Replaces the last commit |
| Undo a commit already shared | git revert <hash> |
Adds a new commit that reverses the old one; history is preserved |
| Move the branch back, local only | git reset --soft/--mixed/--hard <hash> |
Rewrites where the branch points |
git reset has three modes:
--soft: moves the branch pointer, keeps changes staged.--mixed(default): moves the pointer, keeps changes in the working directory but unstaged.--hard: moves the pointer and discards changes in the working directory. This is the dangerous one.
The golden rule: do not rewrite history that others already have. revert is safe for shared branches because it only adds. reset and amend change history and are appropriate for commits that exist only on your machine. Even a lost commit is often recoverable with git reflog, which records where HEAD has been in the last weeks, but do not rely on it as a plan.
git stash saves uncommitted work temporarily when you need to switch tasks:
git stash # shelve changes
git switch main
git stash pop # bring them back
6. Remotes: sharing work
A remote is another copy of the repository, identified by a URL and a short name. By convention the main one is called origin.
git clone https://github.com/example/harbor-workshops.git
# creates the folder, downloads full history, sets origin, checks out the default branch
# or connect an existing local project to a new empty remote:
git remote add origin https://github.com/example/harbor-workshops.git
git remote -v
git push -u origin main # publish main and remember the upstream
The -u flag sets the upstream, so later you can run plain git push and git pull on that branch.
fetch, pull, and push
| Command | What it does | Changes your files? |
|---|---|---|
git fetch |
Downloads new commits and updates remote-tracking branches such as origin/main |
No |
git pull |
git fetch followed by merging (or rebasing) into your current branch |
Yes |
git push |
Uploads your local commits to the remote branch | Changes the remote |
git fetch is the safe way to look first: after it, git log main..origin/main shows what is new, and git diff main origin/main shows how it differs. git pull is a convenience when you are ready to integrate.
A push is rejected when the remote has commits you do not have:
! [rejected] main -> main (fetch first)
The fix is to integrate first: git pull (or git fetch and merge), resolve conflicts if any, then push again. Using git push --force to override is almost always wrong on a shared branch, because it deletes other people's commits from the remote.
Branches on the remote
git push -u origin seat-warning # publish your branch
git branch -r # list remote-tracking branches
git switch seat-warning # after fetch, creates a local branch tracking origin's
7. Worked example: ship a seat-warning feature
Task. The main branch has the Harbor Workshops skeleton. Add a low-seats warning on a branch, fix a mistake in your last commit, and publish the branch.
Step 1: Start from an up-to-date main.
git switch main
git pull
Step 2: Create the branch.
git switch -c seat-warning
Step 3: Make the change and review it.
Edit WorkshopCard.jsx to add const lowSeats = seatsLeft < 3;.
git status
# modified: src/components/WorkshopCard.jsx
git diff
git diff shows the exact lines. You notice a leftover console.log you do not want.
Step 4: Remove the leftover, stage, and commit.
# delete the console.log line in the editor
git add src/components/WorkshopCard.jsx
git commit -m "Show warning when fewer than 3 seats remain"
Step 5: You forgot to update the README, and the commit is not pushed.
# edit README.md
git add README.md
git commit --amend --no-edit
--amend is acceptable here because the commit has not been shared. Result: one clean commit instead of two.
Step 6: Publish.
git push -u origin seat-warning
Step 7: Later you discover the commit shipped to main has a bug. Because main is shared, you use revert, not reset.
git switch main
git revert 8d41b7e
git push
Why these choices and not others. Working on a branch kept main stable while the feature was unfinished. amend was safe only because no one else had the commit; using it after the push would force you to override remote history. revert on main adds a visible, reversible commit; reset --hard followed by push --force would delete history that teammates may have already pulled, breaking their local branches.
8. Pitfalls and exam tips
- Pitfall: forgetting to stage.
git commitrecords only staged changes. If the commit looks empty, rungit status. The shortcutgit commit -am "message"stages changes to tracked files, but it never adds new untracked files. - Pitfall:
git add .too early. It can capture.env, build output, or large binaries. Checkgit statusfirst and keep.gitignorecurrent. - Pitfall: committing secrets. Deleting the file in a later commit does not remove it from history. Rotate the key.
- Pitfall: working on the wrong branch. Look at
git status("On branch ...") before editing and before committing. - Pitfall:
reset --hardwith uncommitted work. It cannot be undone for files that were never committed. - Pitfall: confusing
fetchandpull.fetchnever changes your working files. - Pitfall: force-pushing shared branches.
- Pitfall: huge commits that mix unrelated changes, which are hard to review or revert.
Exam tip. Many questions give a goal and ask for the command: "stage only one file" (git add file), "see staged changes" (git diff --staged), "undo a pushed commit safely" (git revert), "download without changing files" (git fetch), "create and switch" (git switch -c). Identify whether the change is unstaged, staged, committed locally, or pushed, and then choose the least destructive tool that works.
Exam tip. If a push is rejected with "fetch first", the answer is to integrate the remote changes, not to force the push.
Mini case study: the leaked key and the broken main
Situation. Karim commits a .env file containing VITE_MAPS_KEY and pushes to main. A teammate notices the next day. Karim's first idea is to delete .env, commit "remove env", and carry on. Separately, a commit pushed to main last hour introduced a layout bug and Karim proposes git reset --hard HEAD~1 followed by git push --force.
Questions.
- Is deleting
.envin a new commit enough? - What should Karim do to prevent the file from being tracked again?
- What is the safer way to remove the buggy commit from
main?
Solution.
- No. The secret still exists in earlier commits in history, and anyone who cloned has it. The key must be revoked and replaced (rotated). Removing it from history is a separate, optional step that requires special tools and cannot erase copies others already have.
- Run
git rm --cached .env, add.envto.gitignore, and commit. Provide a.env.examplewith placeholder values so teammates know what variables are needed. - Use
git revert <hash>and a normalgit push. This adds a commit that undoes the change without rewriting shared history. The reset-and-force approach would delete the remote commit and could discard work that teammates built on top of it.
Key terms
- Repository: a project folder plus its full history in
.git. - Commit: a snapshot with a message, author, and parent pointer.
- Staging area (index): the draft of the next commit.
- Branch: a movable pointer to a commit.
- HEAD: the pointer to the commit or branch you are on.
- Merge: combining the histories of two branches.
- Fast-forward: a merge that only moves a pointer.
- Remote / origin: another copy of the repository, by convention named
origin. - Upstream: the remote branch a local branch tracks.
- Revert: a new commit that undoes an earlier one.
Summary checklist
- I can explain the working directory, staging area, and repository.
- I configure
user.name,user.email, and the default branch. - I use
git status,git diff, andgit log --onelinebefore and after changes. - I write small commits with imperative, meaningful messages.
- I keep
.env,node_modules, and build output out of the repository. - I create and switch branches with
git switch -cand merge them. - I choose
restore,amend,revert, orresetbased on whether the change is shared. - I know
fetchdownloads andpullalso integrates, and thatpush -usets the upstream. - I never force-push a shared branch.
