Skip to content
  • Home
  • How To
  • Module 0 - Foundations
  • M1
  • Agents
  • M2
  • M3
  • M4
  • M5
  • M6
  • Progress
  • Contact
  • Pricing
  • Sign in
  • Future
  1. Module 0M0
  2. ›
    S3Version Control with Git
  3. ›
    Lesson 2 of 4Reading History - Logs, Diffs, and Undoing
  4. ›
    S3›Lesson 2 of 4
  1. Module 0
  2. ›
  3. Section 3
  4. ›
  5. Lesson 2

Reading History - Logs, Diffs, and Undoing

FREE·~1.5 h·2 labs·4 questions·1 assignment


Context

A recorded history is only useful if you can read it. That means listing the snapshots with their identifiers and messages, seeing the exact lines a change added or removed, pulling a single file back to how it looked earlier, and declaring up front which files should never be recorded at all.

Reading changes line by line before recording them is how you catch the debug value you left in a configuration file. Finding the snapshot where a hyperparameter changed is how you explain why a model's numbers moved. And telling the record to ignore datasets, model checkpoints, and cache directories is not optional housekeeping, a repository that swallows a two gigabyte checkpoint carries that weight in its history permanently, for everyone who ever copies it.

Restoring the wrong thing, or restoring more than you meant to, overwrites work that was never backed up anywhere else. The previous lesson recorded snapshots and stopped there, this lesson is about inspecting what was recorded and stepping back into it deliberately.


Goal

  • Read a commit log and identify the hash, author, date, and message of any commit.
  • Inspect the exact line-level changes a commit introduced using diffs.
  • Restore a single file to an earlier snapshot without disturbing the rest of the project.
  • Construct a .gitignore file that excludes datasets, model checkpoints, and cache directories from tracking.
  • Distinguish between unstaged changes, staged changes, and committed history when reading diffs.

Prerequisites

Both are optional and the lesson assumes neither. They are here if you would rather see the picture before you build it.

  1. YouTube - "Git and GitHub for Beginners - Crash Course" (freeCodeCamp.org), only the commit history on GitHub at 10:40 and the git log at 59:20. A visual walkthrough of reading a commit timeline before you read one by hand.
  2. freeCodeCamp - "Git diff Command, How to Compare Changes in Your Code" (freeCodeCamp News, written guide). A clear, free written walkthrough of the +/- diff format.

Table of Contents

  • Commit History (git log)
  • Diff (git diff)
  • Restore / Checkout a Previous State
  • .gitignore

Theory

SETUP - DO THIS FIRST (everything below depends on it):

cd ~
mkdir history-lab && cd history-lab && git init -q
mkdir data

# the dataset
printf 'sepal,label\n5.1,0\n4.9,1\n' > data/iris.csv
cat > train.py <<'EOF'
from sklearn.linear_model import LogisticRegression

X, y = [[5.1], [4.9]], [0, 1]
model = LogisticRegression()
model.fit(X, y)
EOF
git add train.py data/iris.csv
# COMMIT 1
git commit -m "Add training script and iris dataset"

printf '{"columns": ["sepal", "label"]}\n' > data/schema.json
git add data/schema.json
git commit -m "Add dataset schema"   # COMMIT 2

echo "fake model bytes" > model.pkl  # stands in for a real saved model
git add model.pkl
git commit -m "Add trained model"    # COMMIT 3

Three commits are the minimum this lesson needs, because HEAD~2 below reaches back two steps and train.py has to exist that far back for the restore to have anything to pull.


Commit History (git log)

What. git log is the command that prints the ordered record of every commit in the repository, newest first. Each entry shows a commit's unique hash, its author, its timestamp, and its message, the full timeline of snapshots you have saved.

Why. A commit on its own is just one frozen snapshot. The log turns the pile of commits into a navigable timeline: it lets you see what happened, in what order, who did it, and which message described each change. Every act of reading history, comparing versions, or undoing a change starts by finding the right commit in the log.

When. Run git log before you change anything you are unsure about, when you need to find the commit where a bug appeared, or when you simply want to confirm that your last git commit actually landed.

# 'git log' prints the full history, newest commit at the top.
git log

This prints one block per commit. Each block shows a long 40-character hash (the commit's unique ID), the Author, the Date, and the commit message indented below. The most recent commit is always at the very top.

# '--oneline' compresses each commit to: short-hash + message, one per line.
git log --oneline

The --oneline flag is the form you will use most. It shrinks each commit to a short 7-character hash and its message on a single line, so each commit takes one line of output. The short hash is enough to refer to a commit in later commands.

# '-n 3' limits the output to the 3 most recent commits.
git log --oneline -n 3
# '--stat' adds, per commit, which files changed and how many lines.
git log --oneline --stat

You can of course combine them both. git log --oneline -n 3 shows only the three newest commits, which is useful in a long history when you only care about recent work. git log --oneline --stat adds a per-commit summary of which files changed and the count of inserted/deleted lines, so you can see the shape of each change without reading the actual diff yet.

# '--graph' draws branch lines on the left as ASCII art.
git log --oneline --graph

--graph draws an ASCII diagram of how commits connect. With a single line of work it is just a straight column of * characters, but it becomes essential later when branches split and merge.

DON'T:

  • DON'T confuse the long 40-character hash with the short 7-character hash, they point to the same commit, Git accepts either, and the short one is just the first characters of the long one.
  • DON'T read the error git log gives in a brand-new repository as something broken, a repository with zero commits has no history to show, and fatal: your current branch 'main' does not have any commits yet is the expected answer.
  • DON'T try to scroll past the end with arrow keys when the log opens in a pager, press q to quit the pager and return to your prompt.

Diff (git diff)

What. A diff is the exact, line-level difference between two states of your files. git diff computes and prints that difference: which lines were removed (-) and which were added (+) to get from one snapshot to another.

Why. The log tells you that a file changed, the diff tells you exactly what changed inside it. Before you stage or commit, a diff is your last chance to read precisely what you are about to record. After committing, a diff between two commits shows you the exact edit a past change introduced, line by line, with no guessing.

When. Run a diff before git add to review unstaged edits, before git commit to review what is staged, and between any two commits when you need to see what a historical change actually did.

# 'git diff' shows changes in your working files that are NOT yet staged.
git diff

With no arguments, git diff compares your working files against the last staged or committed version and shows everything you have edited but not yet git add-ed. If you have made no unstaged edits, it prints nothing.

# '--staged' shows changes that ARE staged but not yet committed.
git diff --staged

git diff --staged (also written --cached) shows the changes you have already added with git add and are about to commit. This is the diff that answers "what exactly will my next commit contain?".

A diff is read like this:

diff --git a/train.py b/train.py
index 7c4e842..0ac6347 100644
--- a/train.py
+++ b/train.py
@@ -2,4 +2,5 @@ from sklearn.linear_model import LogisticRegression
 
 X, y = [[5.1], [4.9]], [0, 1]
 model = LogisticRegression()
-model.fit(X, y)
+model.fit(X_train, y_train)
+print("training done")

The index line names the old and new versions of the file by short ids Git computes from their contents, then its mode, 100644 meaning an ordinary, non-executable file. The --- line is the old version, the +++ line is the new version. The @@ -2,4 +2,5 @@ line is a hunk header: the hunk covers old lines 2-5, four lines from line 2, and new lines 2-6, five lines from line 2. After the second @@, Git prints the nearest line above the hunk that starts with a letter or _, a landmark that in a longer script is usually the def line of the function the change sits in. Lines starting with - were removed, lines starting with + were added, and lines with a leading space are unchanged context shown so you can locate the change.

a1b2c3d and e4f5a6b below stand in for two ids, so type two that your own git log --oneline printed, since your commits' ids differ from these.

# Compare two commits directly using their short hashes.
git diff a1b2c3d e4f5a6b
# Compare the latest commit (HEAD) against the one before it.
git diff HEAD~1 HEAD

git diff <old> <new> shows what changed between any two commits. HEAD is a name that always means "the commit I am currently on", and HEAD~1 means "one commit before HEAD", so git diff HEAD~1 HEAD is a quick way to see what your most recent commit changed.

DON'T:

  • DON'T forget --staged, plain git diff shows nothing once everything is staged, which fools people into thinking they have no changes when the changes are simply already added.
  • DON'T read - as "deleted forever", in a commit-to-commit diff, - only means "this line was present in the older state and absent in the newer one", not that data was destroyed.
  • DON'T write the two commits in the wrong order, git diff NEW OLD reverses every + and -, which inverts the meaning of the whole diff.

Restore / Checkout a Previous State

What. Restoring means returning one or more files to how they looked in an earlier snapshot. The modern command is git restore, the older command that does the same job is git checkout. Both rewrite the file on disk to match a chosen version.

Why. Mistakes happen. You delete the wrong lines, break train.py, or stage a file you did not mean to. Because every commit is a saved snapshot, you can always pull a clean copy of a file back out of history. Restore is the "undo" that the whole point of committing makes possible.

When. Use restore when you want to throw away unstaged edits to a file, when you want to pull a file as it existed in a specific past commit, or when you accidentally staged a file and want to unstage it.

# Discard ALL unstaged edits to 'train.py' and set it back to its staged version,
# which is the last commit's when nothing is staged.
git restore train.py

This sets train.py on disk back to its staged version, which is the last commit's when nothing is staged, throwing away every edit you have not staged. Use it when your working changes are wrong and you want a clean slate for that file.

# '--staged' unstages a file: it leaves your edits but removes them from staging.
git restore --staged train.py

git restore --staged is the opposite of git add. It takes a file out of the staging area without touching the actual edits in the file, handy when you ran git add too eagerly and want to commit it separately later.

As with git diff, type an id your own git log --oneline printed where the block below shows a1b2c3d.

# '--source' pulls 'train.py' as it existed in a SPECIFIC past commit.
git restore --source=HEAD~2 train.py
# You can also name an exact commit by its short hash.
git restore --source=a1b2c3d train.py

--source lets you choose which snapshot to restore from. HEAD~2 means "two commits ago", so this pulls train.py as it was at that point and drops it into your working folder, ready to be reviewed and committed as a new change.

DON'T:

  • DON'T run git restore <file> expecting an "are you sure?" prompt, it discards your unstaged edits immediately and silently, and Git cannot recover edits that were never committed.
  • DON'T confuse git restore --staged (unstage, keep edits) with git restore (discard edits), they sound similar but one is harmless and one throws work away.

.gitignore

What. .gitignore is a plain text file, placed in your repository, that lists patterns of paths Git should never track. An untracked file matching a pattern drops out of git status and is skipped by git add ., and naming it outright, as in git add best_model.pkl for a new checkpoint, gets a refusal that names the file.

Why. Not everything in a project folder belongs in history. ML projects generate large datasets, saved model files, virtual environments, and __pycache__ caches, committing them bloats the repository, leaks data, and creates noise. .gitignore keeps history focused on the source code that actually matters. It is a hidden file (its name starts with a dot) and it uses glob patterns, both of which you have already met.

When. Create a .gitignore as one of the first things in any new repository, and add to it whenever a tool starts generating files you do not want under version control.

# Create the file. The leading dot makes it a hidden file.
touch .gitignore

touch .gitignore creates the empty hidden file (on Windows use WSL so this same command works). The dot prefix means it will not show in a plain folder listing, exactly like the .git directory itself. Open it with nano .gitignore, paste the lines below into it, each one a pattern, then save and close.

Inside .gitignore, each line is one pattern:

# Lines starting with '#' are comments, ignored by Git.

# Ignore one specific file by exact name.
model.pkl

# '*' is a glob wildcard: ignore EVERY file ending in '.pkl'.
*.pkl

# A trailing '/' means ignore an entire directory and its contents.
data/

# Ignore Python's bytecode cache folders anywhere in the project.
__pycache__/

# Ignore the virtual environment folder.
.venv/

# '!' negates: track this file even though '*.pkl' ignored it.
!important_baseline.pkl

Each line is matched against your paths. model.pkl ignores that one file, *.pkl uses the glob * to ignore all .pkl files, data/ ignores a whole folder, and !important_baseline.pkl is a negation that re-includes one file that an earlier pattern excluded. Order matters for negation, the ! line must come after the pattern it overrides.

# Stage and commit the .gitignore itself so the rules are shared.
git add .gitignore
git commit -m "Add gitignore for data and model artifacts"

The .gitignore file is meant to be committed and shared. Once committed, everyone who clones the repo gets the same ignore rules. Note the irony: .gitignore is one file you do want tracked.

There is one critical limitation. .gitignore only stops Git from tracking files it is not already tracking. If you committed model.pkl last week and add *.pkl today, Git keeps tracking the file you already committed. To fix that, you must untell Git about it explicitly (covered in the assignment).

DON'T:

  • DON'T expect .gitignore to untrack files Git is already tracking, patterns only apply to files Git has not yet started following.
  • DON'T write data when you mean the folder, without the trailing / the pattern matches a file or folder named data, which is usually not what you intend, write data/ to mean "the directory".
  • DON'T forget to commit .gitignore, an uncommitted ignore file only protects your own machine and helps no one who clones the project.

Lab Exercises

Lab Exercise 1: Read the Timeline

You are inside history-lab, where the setup block's three commits and the .gitignore commit make four. Print the full history, then print a compact one-line history limited to the two most recent commits, then show which files changed in each of those commits. Finally, capture the short hash of the oldest commit and the newest commit on paper.

Lab Exercise 2: See and Undo a Change

Append one line to train.py, echo "epochs = 20" >> train.py, and do not stage it. (1) Show your unstaged changes as a diff. (2) Stage the file and show the staged diff. (3) Compare the newest commit against the commit before it. (4) Decide your edit was wrong and restore train.py back to the last committed version, then confirm with git status that the working tree is clean.


Solution 1: Read the Timeline
# Print the complete history with full hashes, authors, dates, messages.
git log
# Compact view, only the 2 newest commits, one line each.
git log --oneline -n 2
# Same 2 commits but also list which files changed and line counts.
git log --oneline -n 2 --stat

git log gives the full record, --oneline -n 2 trims it to the two most recent commits in single-line form, adding --stat appends the per-commit file/line summary. To capture the oldest and newest short hashes, read the top line (newest) and bottom line (oldest) of git log --oneline and write down the 7-character codes, for example 9f3a1c2 and 2b7d0e4.

DON'T:

  • DON'T mix up top and bottom of the log: newest is at the top, oldest at the bottom, the reverse of how you might read a list of events chronologically.
Solution 2: See and Undo a Change
# (1) Show edits made but not yet staged.
git diff
# (2) Stage the file, then show what is now staged.
git add train.py
git diff --staged
# (3) Compare the newest commit to the one immediately before it.
git diff HEAD~1 HEAD
# (4) Throw away the working edits and confirm a clean tree.
git restore --staged train.py  # first unstage it
git restore train.py           # then discard the edits
git status                     # should say nothing to commit, working tree clean

Step 1's git diff shows unstaged edits, after git add, those edits move to staging so plain git diff goes empty and git diff --staged shows them instead. Step 3 compares the last two commits via the HEAD~1/HEAD shorthand. In step 4 you must git restore --staged first (because the file is staged) and then git restore to discard the actual edits, git status then confirms working tree clean.

DON'T:

  • DON'T skip the unstage step, running git restore train.py while the file is still staged changes nothing you can see, because the edit is still staged, leaving you confused about why git status is not clean.

Theory Assessment

Conceptual reinforcement, not a coding section. All four questions with their hints first, then all four answers.

Q1: Reading the timeline

In git log --oneline, what two pieces of information appear on each line, and why is the hash shorter than in plain git log? Then describe where the newest and oldest commits sit in the output, and explain what HEAD~1 refers to relative to your current position.

Hint: One of the two pieces is what you wrote and one is what git computed. For the ordering, do not assume a log reads like a book, top to bottom in time.

Q2: A diff that shows nothing, and a restore that shows too much

A student edits train.py, runs git add train.py, then runs git diff and sees no output. They panic that the edit is gone. Explain what actually happened and which command shows the change. Then explain the difference in outcome between git restore train.py and git restore --staged train.py, and state clearly which of the two can destroy work.

Hint: git diff compares two specific things, so ask which two, and where the edit went. For the second half, one command changes the file on disk and the other changes only what is selected.

Q3: One character that changes a pattern

In a .gitignore, what is the difference between the pattern logs and the pattern logs/?

Hint: One of the two can match more kinds of thing than the other.

Q4: The rule that arrived too late, and the history you thought was gone

A teammate adds *.csv to .gitignore, yet after a cleaning script edits data/iris.csv, committed last week, git status still lists modified: data/iris.csv. Explain why .gitignore does not hide it, what category of file .gitignore actually governs, and what you must do instead. Then: your branch has been moved back one commit, and the commit it used to point at no longer appears in git log. git reflog prints every position HEAD has recently been at, and that commit's hash is still in its list. Explain why git reflog finds it when git log cannot.

Hint: Ask which files .gitignore is consulted about at all, because it is not every file in the folder. For the second half, git log walks the history reachable from where you are now, and that is not the same set as everywhere you have been.


Answer 1:

git log --oneline shows the short commit hash, the first 7 characters, followed by the commit message. The hash is abbreviated because --oneline is built for scanning, and 7 characters are almost always unique enough for git to identify the commit when you use it in another command. It is the same commit either way, just written shorter.

The newest commit is at the top and the oldest at the bottom, most-recent-first rather than chronological. HEAD labels the commit you are currently on, HEAD~1 means one commit before it, HEAD~2 two before, counting backward through the parent chain from wherever HEAD sits.

DON'T treat the short hash as a different commit. DON'T read the log bottom-to-top expecting chronological order.

Answer 2:

The edit is not gone. git add moved it from the working tree into the staging area, and plain git diff compares the working tree against the staging area. Since those now match, there is genuinely nothing to report. git diff --staged compares the staging area against the last commit, which is exactly where the edit now lives, so it shows the change.

git restore train.py sets the file on disk back to its staged version, which is the last commit's when nothing is staged, discarding unstaged edits permanently. git restore --staged train.py only removes the file from the staging area and leaves your edits in the file untouched. The first destroys content, the second is a safe undo of git add, and the difference is one flag.

DON'T read "no output from git diff" as "no changes", check git diff --staged too. DON'T run git restore <file> casually on valuable unstaged edits, there is no undo and no confirmation.

Answer 3:

logs matches any path named logs, whether it is a file or a directory. logs/ with the trailing slash matches only a directory of that name, and everything inside it. The slash narrows the pattern, so a stray file called logs would still be tracked.

DON'T drop the trailing slash when you specifically mean a folder, be explicit so a same-named file is not ignored by accident.

Answer 4:

.gitignore governs only files git is not already tracking. data/iris.csv was committed last week, so git tracks it, and the new *.csv rule is never consulted for it. Ignore rules decide whether new, untracked paths get picked up, and they have no authority over a file Git already tracks. To fix it you must untrack the file explicitly with git rm --cached data/iris.csv (git rm untracks a file and deletes it from disk, and --cached keeps it on disk), and commit that removal to record it. The pattern applies from the moment the file is untracked.

git log shows only the history reachable from where you are now: it starts at HEAD and follows each commit's parent backward, and the commit your branch moved back from comes after HEAD, so a walk that only goes backward never reaches it, although the commit still exists in the repository. git reflog reads a different record, the local log of every position HEAD has recently occupied, every commit, switch, merge and reset, so that commit's hash is still in it. It is a record of your movements rather than of the history's shape, which is why it finds what log will not.

Both halves are the same misconception in different clothes: assuming a command reports on everything, when each one reports on a specific, narrower set. .gitignore is asked about untracked paths only. git log walks reachable history only. Neither is broken, and neither will tell you it is answering a smaller question than you asked.

DON'T expect editing .gitignore alone to fix an already-committed file. DON'T conclude a commit is gone because git log stopped showing it.


Assignment

Assignment: Clean Up a Polluted Repo

Your repo accidentally committed model.pkl and the whole data/ folder weeks ago, and they keep showing up in every diff. (1) Write a .gitignore that excludes all .pkl files and the data/ directory but keeps tracking one specific file data/schema.json. (2) Stop Git from tracking the already-committed model.pkl and data/ contents without deleting them from disk. (3) Commit the cleanup. (4) Overwrite model.pkl, as retraining would, and confirm it no longer appears in git status.

Hints:

  • A leading ! line re-includes a file that a broader pattern excluded, but it must come after that broader pattern, and it cannot re-include a file inside a fully ignored directory unless you unignore the directory carefully.
  • git rm --cached -r <folder> untracks a folder's contents while leaving the files on disk, the -r makes it recurse into the directory.

Solution:
# Ignore every model file.
*.pkl
# Ignore the dataset directory's contents...
data/*
# ...but keep tracking this one schema file.
!data/schema.json
# Untrack the already-committed model file (keep it on disk).
git rm --cached model.pkl
# Untrack the already-committed data folder contents (keep them on disk).
git rm --cached -r data/
# The line above untracked 'data/schema.json' too, so re-add it.
git add data/schema.json
# Record the ignore rules and the untracking.
git add .gitignore
git commit -m "Stop tracking models and data, keep schema"
# Overwrite the model as a retrain would, then confirm status no longer lists it.
echo "retrained model bytes" > model.pkl
git status

The .gitignore uses data/* (not data/) so that the !data/schema.json negation can still re-include one file, a fully ignored data/ directory would block any negation inside it. git rm --cached removes model.pkl from tracking but leaves the file in your folder, and git rm --cached -r data/ does the same recursively for the dataset folder. You then re-add data/schema.json and commit everything. Before the cleanup, that overwrite would have put modified: model.pkl in git status, and after it git status reports nothing to commit, working tree clean.

DON'T use plain git rm model.pkl (without --cached) here, that deletes the file from disk as well as untracking it, which is not what the assignment asked for.


Additional Resources

Optional, and nothing later depends on either. Worth a look only if this lesson left you curious.

  1. Atlassian - "Undoing Changes" (Atlassian Git Tutorial). A clear, free, illustrated walkthrough of the checkout, revert, reset and clean undo flows, going beyond what this lesson covered.
  2. YouTube - "How to use a .gitignore file" (Pluralsight, Teach Me Tech). Building ignore patterns from scratch for a real project.

Summary

This lesson turned a pile of saved snapshots into something you can actually read and steer. The commit log is the spine: it takes everything you have committed and lays it out as an ordered timeline, newest first, each entry stamped with a hash, an author, a date, and a message. Once you can read that timeline, every other skill hangs off it, you point at a commit, and from there you can ask what it changed, or ask to have a file the way it used to be.

The diff is how you move from "something changed" to "exactly these lines changed". Reading the + and - lines, the hunk headers, and the difference between unstaged, staged, and committed states is the daily literacy of version control. Most confusion beginners feel, "where did my edit go after git add?", dissolves the moment the three states become concrete in your head. Restore then gives that confidence teeth: because every commit is a real snapshot, no edit is ever truly trapped, and you can pull a clean file back out of history or quietly undo a git add without ceremony.

Finally, .gitignore is the discipline that keeps history worth reading. An ML project is a magnet for noise, datasets, checkpoints, caches, environments, and a repository that tracks all of it becomes slow, heavy, and impossible to review. By writing a few glob patterns into one hidden file, you decide what counts as project history and what is just generated clutter. Together these four ideas are the difference between blindly making commits and genuinely working with your history: looking back, comparing, recovering, and curating. That is the real shift this lesson asks of you, and everything in later version-control work assumes you now have it.


Cheat Sheet

Part A - Reference Table

Task Command Notes When to Avoid
Full history git log Newest at top, q to quit pager When you want a quick scan
Compact history git log --oneline Short hash + message per line When you need full author/date
Limit count git log --oneline -n 3 Last N commits only When you need everything
Files per commit git log --stat Adds changed files + line counts When you want the actual diff
Unstaged changes git diff Working tree vs. staging After you've staged everything
Staged changes git diff --staged Staging vs. last commit For unstaged edits (use git diff)
Two commits git diff <old> <new> Order matters: old then new Reversing the order
Last commit's change git diff HEAD~1 HEAD HEAD~n counts backward First commit (no parent)
Discard edits git restore <file> Permanent, no prompt When edits are valuable & unstaged
Unstage git restore --staged <file> Keeps edits, undoes git add When you want to discard content
Restore from past git restore --source=HEAD~2 <file> Pulls an older snapshot When current version is correct
Ignore paths edit .gitignore Globs, trailing / = dir Already-tracked files
Untrack a file git rm --cached <file> Keeps file on disk When you want it deleted too

Part B - Compact Code Block

git log --oneline -n 5 --stat  # recent history + changed files
git diff                       # unstaged edits
git diff --staged              # staged edits, pre-commit
git diff HEAD~1 HEAD           # what the last commit changed
git restore train.py           # discard unstaged edits (permanent)
git restore --staged train.py  # unstage, keep the edits
git restore --source=HEAD~2 f  # pull file from 2 commits ago
git rm --cached -r data/       # untrack folder, keep on disk
# .gitignore patterns
# ignore all model files
*.pkl
# ignore everything inside a directory
data/*
# ignore caches anywhere
__pycache__/
# but keep this one (needs data/* above, not data/)
!data/schema.json
←Previous: Lesson 1
Next: Lesson 3→

Questions about this lesson: #module-0 on Discord

Found an error in this lesson? Report it

Your progress
Current streak
🔥 0
Lessons marked today
0 / 3
Longest streak
0

Sign in

The bootcamp

  • Curriculum
  • How To
  • Pricing
  • Changelog
  • Future

The community

  • Discord
  • Reviews
  • Contact

Follow

  • Instagram
  • X
  • TikTok
  • Reddit

Legal

  • Terms
  • Privacy
  • Refunds

Coming soon on November 16, 2026

Theme