Skip to content

Terminal, Git, and Collaboration

Step 11 of 45

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

  1. Explain what a repository, commit, and branch are, and what makes Git distributed.
  2. Describe the three areas of Git: working directory, staging area, and repository.
  3. Configure Git and create a repository with git init or git clone.
  4. Use git status, git add, git commit, git diff, and git log fluently.
  5. Write useful commit messages and use .gitignore correctly.
  6. Create, switch, merge, and delete branches.
  7. Undo mistakes safely with git restore, git revert, and git reset, and choose the right one.
  8. Use git remote, git push, git fetch, and git 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: main has not moved since you branched, so Git just moves the main pointer 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

  1. Make sure main is up to date.
  2. Create a short-lived branch named for the work (seat-warning, fix/date-parse).
  3. Commit in small steps.
  4. Merge back, usually through a pull request.
  5. 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 commit records only staged changes. If the commit looks empty, run git status. The shortcut git 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. Check git status first and keep .gitignore current.
  • 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 --hard with uncommitted work. It cannot be undone for files that were never committed.
  • Pitfall: confusing fetch and pull. fetch never 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.

  1. Is deleting .env in a new commit enough?
  2. What should Karim do to prevent the file from being tracked again?
  3. What is the safer way to remove the buggy commit from main?

Solution.

  1. 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.
  2. Run git rm --cached .env, add .env to .gitignore, and commit. Provide a .env.example with placeholder values so teammates know what variables are needed.
  3. Use git revert <hash> and a normal git 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, and git log --oneline before 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 -c and merge them.
  • I choose restore, amend, revert, or reset based on whether the change is shared.
  • I know fetch downloads and pull also integrates, and that push -u sets the upstream.
  • I never force-push a shared branch.

Official sources