flowchart LR
subgraph wd["Working Directory"]
A["Files you<br>edit and save"]
end
subgraph sa["Staging Area"]
B["Changes selected<br>for your next commit"]
end
subgraph repo["Repository"]
C["Permanent history<br>of snapshots"]
end
A -- "git add" --> B -- "git commit" --> C
Git fundamentals
Track changes to your files with Git: initialize a repository, stage changes, commit snapshots, and review your project’s history.
Overview
Git is a system that records changes to your files over time. Instead of saving multiple copies of a file (report_v1.txt, report_v2_final.txt, report_v2_final_FINAL.txt), Git lets you save snapshots, called , that you can revisit, compare, or restore at any time. This tutorial covers the core Git workflow: initializing a repository, staging changes, committing, and reviewing history.
Adding a Git repository to your project allows you to track changes to your project at checkpoints. This will allow you to easily revert to a previous state if something goes wrong, or to compare different versions of your project as you iterate. Claude Code also has built-in support for checkpointing your project, however, it is not a substitute for Git.
Note: Git and GitHub are not the same thing. Git is the version control system that runs on your computer, while GitHub is a web-based platform for hosting Git repositories and collaborating with others (among others Codeberg, GitLab, etc). This tutorial focuses on Git itself; we’ll cover web repositories and collaboration in a future tutorial.

Prerequisites: Shell configuration
What is version control?
As alluded to in the overview, there is an inherent problem when working with projects in which files and directories are changing over time; how do you keep track of those changes? One common approach is to save multiple versions of a file with different names. However, these, as you know, leads to all sorts of confusion, for yourself and even more so for collaborators.
Furthermore, when working with a tool, such as Claude Code, that allows you to iterate on a project by making changes to files and directories across your project, it can be difficult to keep track of those changes –even if you wanted to– without a version control system.
A version control system, like Git, allows you to address both issues: 1) it provides a structured way to track changes to your files over time (without renaming them), and 2) it allows you to easily revert to a previous state of your project (or parts of it) if something goes wrong, or to compare different versions of your project as you iterate.
There’s some overhead to learning Git, but the benefits are well worth it. Once you get the hang of it, you’ll wonder how you ever managed without it.
Installing Git
Many modern operating systems come with Git pre-installed. Check with git --version. However, these versions can be outdated. To get the latest version, use Homebrew1:
brew install gitIf you ran the workgroup setup script, Git is already configured. Check with git config --list.
Configuring Git
Before you start using Git, you need to configure it with your name and email, at least. This information is used to identify the author of your commits. While we are at it, we can also set the default branch name to main, which is the modern convention (do not worry about branches for now, this is a more advanced topic).
From the terminal, run the following commands, replacing the name and email with your own:
git config --global user.name "Your Name"
git config --global user.email "you@example.com"
git config --global init.defaultBranch mainGood news, you only need to do this once for a given machine! Git will save this information in a configuration file on your computer, so you won’t have to set it again for future projects. You can verify that your configuration is set up correctly with:
git config --global --listVerify that your name, email, and default branch name are listed in the output.
Creating a repository
Now we are ready to create Git repositories for our projects. A Git is a special directory that contains all the information about the history of your project, including the commits, branches, and configuration. You can create a Git repository in any directory by running git init in that directory.
cd path/to/your/project
git initThis command creates a hidden directory called .git/ in your project directory. This is where Git stores all the information about your repository, including the commits, branches, and project-specific configuration. You can see this directory with ls -a, but you should not modify anything inside it directly.
Once you have initialized a Git repository, Git will start tracking changes to your files. However, it won’t automatically log those changes as commits. That’s our next step: staging and committing changes.
The staging area
Git uses a three-zone model to manage changes to your files:
- : This is where you make changes to your files. It’s the actual files and directories you see in your project.
- (index): This is an intermediate area where you can stage changes that you want to include in your next commit. You can think of it as a “to-do list” for your next commit.
- : This is where Git stores the history of your commits. When you commit changes, Git takes a snapshot of the staged changes and saves it in the repository.
Here is the flow:
The core workflow
In Figure 1, we see the overview of the core Git workflow: you make changes to your files in the working directory, then you stage the changes you want to include in your next commit, and finally, you commit those staged changes to the repository. Let’s go through each step in more detail.
To do so we will use the following example:
flowchart LR
subgraph wd["Working Directory"]
direction TB
w1["notes.txt<br><i>modified</i>"]
w2["report.qmd<br><i>modified</i>"]
w3["scratch.txt<br><i>modified</i>"]
w4["data.csv<br><i>untracked</i>"]
w5["results.csv<br><i>unmodified</i>"]
end
style w5 stroke-dasharray: 5 5
subgraph sa["Staging Area"]
direction TB
s1["notes.txt<br><i>staged</i>"]
s2["report.qmd<br><i>staged</i>"]
s3["scratch.txt<br><i>not staged</i>"]
s4["data.csv<br><i>staged</i>"]
end
subgraph repo["Repository"]
C["Commit:<br><i>'Update notes and report'</i>"]
end
w1 -- "git add" --> s1
w2 -- "git add" --> s2
w3 --> s3
w4 -- "git add" --> s4
s1 --> repo
s2 -- "git commit" --> repo
s4 --> repo
Checking status (git status)
The git status command shows you the current state of your and . It tells you which files have been modified, which changes are staged and ready to commit, and which files Git doesn’t know about yet. You can run it at any point without changing anything — it’s purely informational.
Let’s say you just initialized a new repository and created a file called notes.txt. Running git status gives you:
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.txt
nothing added to commit but untracked files present (use "git add" to track)
Git sees notes.txt but isn’t tracking it yet — it’s untracked. Once you stage the file with git add notes.txt and run git status again:
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: notes.txt
Now notes.txt appears under “Changes to be committed” — it’s staged. After you commit, git status reports a clean working tree:
On branch main
nothing to commit, working tree clean
This means every tracked file matches the latest . No changes are pending.
Run git status early and often. It’s the safest way to understand what Git sees before you stage or commit anything.
Adding files to staging (git add)
The git add command moves changes from the into the . You can stage individual files by name:
git add notes.txt
git add report.qmdOr stage multiple files in one command:
git add notes.txt report.qmd data.csvReferring back to Figure 2, notice that scratch.txt is not staged even though it was modified. This is the power of the staging area — you choose exactly which changes go into each commit. Maybe scratch.txt contains temporary work you don’t want to record yet.
You may see tutorials that use git add . (with a dot) to stage everything in the current directory at once. This works, but it can bite you.
git add . stages every change in the current directory, including files you may not intend to commit — temporary files, data files, API keys, or editor backups. Prefer staging files by name so you know exactly what goes into each commit.
Committing a snapshot (git commit)
Once your changes are staged, git commit saves a snapshot of those changes as a in the . Each commit gets a unique identifier (a long hash like a1b2c3d), records who made the change and when, and stores the message you provide.
Use the -m flag to write your commit message inline:
git commit -m "Add project notes and initial report"[main 4f3a1b2] Add project notes and initial report
2 files changed, 24 insertions(+)
create mode 100644 notes.txt
create mode 100644 report.qmd
The output confirms the branch (main), an abbreviated commit hash (4f3a1b2), and a summary of what changed. If you omit the -m flag, Git opens a text editor for you to write the message.
A good commit message is short (under ~50 characters for the subject line), uses the imperative mood (“Add report” not “Added report”), and describes why the change matters rather than just what changed. Think of it as a label for a snapshot: if you browse your history later, the messages should tell a clear story.
Bad: updated stuff Good: Add draft methodology section to report
If Git opens an unfamiliar editor (often Vim), you’ll see a screen that doesn’t respond to typing as you’d expect. To escape Vim: press Esc, then type :q! and press Enter. To avoid this in the future, always use the -m flag, or set a friendlier default editor:
git config --global core.editor "nano"Viewing history (git log)
The git log command shows the history of commits in your repository, starting with the most recent:
git logcommit 4f3a1b2e8d9c0f1a2b3c4d5e6f7a8b9c0d1e2f3a
Author: Your Name <you@example.com>
Date: Thu Feb 6 10:30:00 2026 -0500
Add project notes and initial report
commit 1a2b3c4d5e6f7a8b9c0d1e2f3a4b5c6d7e8f9a0b
Author: Your Name <you@example.com>
Date: Thu Feb 6 10:15:00 2026 -0500
Initialize project with README
Each entry shows the full commit hash, the author, the date, and the message. For a more compact view, use the --oneline flag:
git log --oneline4f3a1b2 Add project notes and initial report
1a2b3c4 Initialize project with README
This is often all you need when scanning your project’s history. You can also add --graph to visualize branch structure — not useful yet with a single branch, but handy once you start working with branches:
git log --oneline --graphComparing changes (git diff)
Before you stage or commit, it’s often useful to see exactly what changed. The git diff command compares versions of your files.
Unstaged changes (working directory vs. staging area):
git diffThis shows changes you’ve made but haven’t staged yet. For example, say you edit notes.txt and add a new line. Running git diff produces output like:
diff --git a/notes.txt b/notes.txt
index 3b18e51..f3c1a2d 100644
--- a/notes.txt
+++ b/notes.txt
@@ -1,2 +1,3 @@
Meeting notes from Feb 6
- Discussed project timeline
+- Assigned tasks to team membersLines starting with + were added; lines starting with - were removed. The @@ line tells you which part of the file changed.
Staged changes (staging area vs. last commit):
git diff --stagedThis shows the changes that are already staged and will go into your next commit. After you run git add notes.txt, running git diff produces no output (there are no unstaged changes left), but git diff --staged shows the same diff as before.
Undoing mistakes
One of Git’s biggest benefits is that mistakes are rarely permanent. This section covers three levels of “undo,” from lightest to heaviest.
Unstaging a file (git restore --staged)
If you staged a file by accident (say you ran git add scratch.txt and didn’t mean to), you can remove it from the staging area without losing the changes in your working directory:
git restore --staged scratch.txtVerify with git status:
On branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: scratch.txt
no changes added to commit (use "git add" to track)
The file is back to modified but unstaged. Your edits are still in the file — Git just removed it from the “to be committed” list.
Discarding changes (git restore)
If you made edits to a file and want to throw them away entirely — reverting the file to its state at the last commit — use git restore without the --staged flag:
git restore report.qmdAfter running this, git status will no longer show report.qmd as modified. The file on disk has been reverted to match the most recent commit.
git restore discards uncommitted changes permanently. There is no way to get those edits back. Make sure you truly want to lose them before running this command.
Reverting a commit (git revert)
What if the mistake is already committed? You could rewrite history, but that gets complicated and can cause problems if others are working with the same repository. The safer approach is git revert, which creates a new commit that undoes the changes from a previous one.
For example, say your most recent commit introduced a mistake. You can undo it with:
git revert HEADHEAD is a shorthand for “the latest commit.” Git will create a new commit whose changes are the exact opposite of the reverted commit. Your log will look something like:
a3b4c5d Revert "Add broken config file"
9f8e7d6 Add broken config file
4f3a1b2 Add project notes and initial report
The original commit (9f8e7d6) stays in history, and the revert commit (a3b4c5d) cleanly undoes its effects. This is the safe approach — the history tells the full story of what happened and how it was fixed.
Ignoring files (.gitignore)
Not every file in your project belongs in the . Operating system files (.DS_Store on macOS), editor temp files, build outputs, large data files, and especially files containing secrets (API keys, passwords) should stay out of your commit history.
Git uses a file called .gitignore in the root of your project to define patterns for files and directories it should ignore entirely. Create the file and add one pattern per line:
nano .gitignoreA typical .gitignore might look like:
# .gitignore file --------
# Operating system files
.DS_Store
Thumbs.db
# Editor temporary files
*.swp
*~
.vscode/
# Build artifacts
_site/
_freeze/
*.html
# Data files (too large for version control)
data/*.csv
# Secrets — never commit these
.env
credentials.json
Lines starting with # are comments. The * wildcard matches any sequence of characters, so *.swp ignores all files ending in .swp. A trailing / means “this directory and everything inside it.”
Once you save .gitignore, Git stops showing matched files in git status output. You should add and commit the .gitignore file itself — it’s part of your project configuration and others working with the repository need it too.
git add .gitignore
git commit -m "Add .gitignore with common patterns"Add a .gitignore early in your project, ideally before your first commit. It’s much easier to keep unwanted files out of the repository from the start than to remove them later.
Practice exercise
This exercise walks through the full workflow covered in this tutorial. You’ll create a small project, make commits, inspect history, and undo a change. Each step shows the command and what to expect.
1. Create a project directory and initialize Git:
mkdir git-practice
cd git-practice
git initInitialized empty Git repository in /Users/you/git-practice/.git/
2. Create two files with some content:
echo "Meeting notes from Feb 6" > notes.txt
echo "# Quarterly Report" > report.qmd3. Check the status:
git statusOn branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
notes.txt
report.qmd
nothing added to commit but untracked files present (use "git add" to track)
Both files are untracked — Git sees them but isn’t recording their changes yet.
4. Stage and commit both files:
git add notes.txt report.qmd
git commit -m "Add initial notes and report"[main (root-commit) 1a2b3c4] Add initial notes and report
2 files changed, 2 insertions(+)
create mode 100644 notes.txt
create mode 100644 report.qmd
5. Create a new file and modify an existing one:
echo "id,value" > data.csv
echo "- Discussed project timeline" >> notes.txt6. Check the status again:
git statusOn branch main
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: notes.txt
Untracked files:
(use "git add <file>..." to include in what will be committed)
data.csv
no changes added to commit (use "git add" to track)
Git shows notes.txt as modified and data.csv as untracked.
7. Stage both files selectively and commit:
git add notes.txt data.csv
git commit -m "Add meeting discussion notes and data file"8. View the log:
git log --oneline7e8f9a0 Add meeting discussion notes and data file
1a2b3c4 Add initial notes and report
Two commits, listed newest first — the timeline of your project so far.
9. Make an unwanted edit, then undo it:
echo "MISTAKE - delete this line" >> report.qmd
git diffdiff --git a/report.qmd b/report.qmd
index 3b18e51..a4c2d8e 100644
--- a/report.qmd
+++ b/report.qmd
@@ -1 +1,2 @@
# Quarterly Report
+MISTAKE - delete this lineYou can see the unwanted addition. Discard it:
git restore report.qmd10. Verify everything is clean:
git statusOn branch main
nothing to commit, working tree clean
git log --oneline7e8f9a0 Add meeting discussion notes and data file
1a2b3c4 Add initial notes and report
Your history is intact, and the unwanted edit is gone. You’ve used the full Git workflow: init, status, add, commit, log, diff, and restore.
Summary
Here’s a reference table of the core Git commands covered in this tutorial:
| Command | What it does | Example |
|---|---|---|
git init |
Create a new repository in the current directory | git init |
git status |
Show the state of your working directory and staging area | git status |
git add |
Stage changes for the next commit | git add notes.txt |
git commit |
Save a snapshot of staged changes | git commit -m "Add notes" |
git log |
View commit history | git log --oneline |
git diff |
Show unstaged changes | git diff |
git diff --staged |
Show staged changes | git diff --staged |
git restore --staged |
Remove a file from the staging area | git restore --staged file.txt |
git restore |
Discard uncommitted changes to a file | git restore file.txt |
git revert |
Create a new commit that undoes a previous commit | git revert HEAD |
All of these commands operate on your local machine. In the next tutorial, we’ll connect your local repository to GitHub and learn how to share your work and collaborate with others.
Next tutorial: GitHub collaboration
Footnotes
If you don’t have Homebrew installed, see the CLI fundamentals for instructions.↩︎