Git Fundamentals
Advanced · 18 min read · ▶ live playground · ✦ checkpoint
Git saves the history of a project as named snapshots, so you can change code without treating every edit like a one-way door. In this lesson you will learn the Git model beginners actually need first: the working tree, the staging area, commits, status, add, commit, log, .gitignore, and commit messages that make future work easier.
Git stores snapshots, not vibes
When people say "put this project in Git," they mean: create a repository, a project folder with a hidden .git database inside it. Your source files stay visible in the folder. Git's private history lives in .git.
Here is the start of a small project called weather-widget:
$ cd ~/projects/weather-widget
$ git init -b main
Initialized empty Git repository in /Users/ada/projects/weather-widget/.git/That command does not upload your code anywhere. It does not make a backup in the cloud. It just gives this folder a Git history database.
Git history is made of commits. A commit is a snapshot of tracked files at a moment in time, plus a message explaining why that snapshot exists. Each commit gets a hash, a long identifier like a13f2c4.... In daily work you usually see the short version.
The important mental shift is this: Git is not "save as final-final-v3." Git is a timeline of deliberate checkpoints.
The three areas
Git asks you to think in three areas:
- Working tree: the files on disk, where you edit normally.
- Staging area: the next snapshot you are preparing.
- Repository: the committed history stored in
.git.
Most beginner confusion comes from expecting Git to jump straight from "I edited a file" to "it is committed." Git has a middle step on purpose. The staging area lets you choose exactly what belongs in the next commit.
Start by asking Git what it sees:
$ git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
src/renderWeather.js
nothing added to commit but untracked files present (use "git add" to track)Untracked means Git can see the files, but they are not part of history yet. They are just ordinary files in the working tree.
Now choose what belongs in the first snapshot:
$ git add README.md src/renderWeather.js
$ git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
new file: src/renderWeather.jsgit add does not mean "upload." It means "stage this version of this file for the next commit." If you edit src/renderWeather.js again after staging it, Git will notice that the staged version and the working-tree version are no longer the same.
When the staged snapshot is coherent, commit it:
$ git commit -m "Add weather widget starter"
[main (root-commit) a13f2c4] Add weather widget starter
2 files changed, 42 insertions(+)
create mode 100644 README.md
create mode 100644 src/renderWeather.jsThe message is part of the history. A teammate can now understand that the first checkpoint added the starter project, not some random mix of unrelated edits.
Reading status like a dashboard
git status is the command you run before almost every Git decision. The full output is friendly, but the short form becomes useful once you know the symbols:
$ git status --short
M src/renderWeather.js
?? debug-output.logRead that as:
M src/renderWeather.js: Git tracks this file, and your working tree has unstaged changes.?? debug-output.log: Git has never tracked this file.
If the renderWeather.js change is part of your next checkpoint, stage it:
$ git add src/renderWeather.js
$ git status --short
M src/renderWeather.js
?? debug-output.logNow the M moved into the staged column. The debug-output.log file is still untracked, so it will not be committed unless you add it too.
That separation is the craft. You might want to commit the real code change while leaving a messy local log out of history.
$ git commit -m "Show fallback text when weather data is missing"
[main f7b4d91] Show fallback text when weather data is missing
1 file changed, 8 insertions(+), 2 deletions(-)The code change is committed. The local log is still just noise in the folder, and the next section handles files like that without putting them in history.
.gitignore keeps noise out of history
Some files belong in your project folder but should not belong in Git history. The usual reasons are:
- They are generated again from source, like
dist/. - They are dependency folders, like
node_modules/. - They are local machine files, like logs or editor scratch files.
- They contain secrets, like a real
.envfile.
Those rules live in a plain text file named .gitignore:
node_modules/
dist/
.env
*.logThis says:
- ignore the
node_modules/folder - ignore the built
dist/folder - ignore a real
.envfile - ignore any file ending in
.log
You usually commit .gitignore itself because it is part of the project agreement:
$ git add .gitignore
$ git commit -m "Ignore local and generated files"
[main c9e8a10] Ignore local and generated files
1 file changed, 4 insertions(+)
create mode 100644 .gitignoreNow the *.log rule keeps debug-output.log out of status and future commits:
$ git status
On branch main
nothing to commit, working tree clean"Clean" does not mean the project is correct. It means Git has no uncommitted changes to report. Tests, linters, and your own judgment still decide whether the code is good.
One beginner surprise: .gitignore only keeps untracked files out. If a file is already committed, adding a matching ignore rule later does not erase it from old commits or make Git forget it automatically. This matters most for secrets. A real .env file should be ignored before it ever reaches history; a safe .env.example file can be tracked because it shows the required variable names without real secret values.
log is the project story
Commits are useful only if you can read them later. git log shows the history:
$ git log --oneline
c9e8a10 Ignore local and generated files
f7b4d91 Show fallback text when weather data is missing
a13f2c4 Add weather widget starterNewest commits appear first. Each line has a short hash and the subject line from the commit message.
That tiny list is already a story:
- The project started.
- Missing weather data got a better user-facing fallback.
- Local and generated files were kept out of history.
Good history is not about writing perfect prose. It is about leaving enough signal that another developer can inspect a change, review it, undo it, or build on it without guessing.
Commit messages your future team will thank you for
A useful commit message answers one question: what changed in this snapshot?
Prefer messages that name the user-visible behavior, project rule, or technical move:
Good:
Add weather widget starter
Show fallback text when weather data is missing
Ignore local and generated files
Hard to use later:
changes
fix stuff
final final
more updatesThree habits make commits easier to review:
- Keep one idea per commit. Formatting changes, feature work, and dependency cleanup rarely belong in the same checkpoint.
- Write the subject as a short command:
Add,Fix,Rename,Remove,Refactor,Document. - Check
git statusbefore committing so you do not accidentally include scratch files.
You do not need a ceremony for every tiny edit while you are exploring. But before you share work, Git history should explain the shape of the work, not merely prove that typing happened.
Next you will put this snapshot model to work with branches and pull requests: the team workflow that lets several people change the same project without stepping on each other.
Checkpoint
Answer all three to mark this lesson complete