Skip to content

Lesson: Your first repo

Before the commands: a ten-minute exercise

Section titled “Before the commands: a ten-minute exercise”

Pick any project of your own. A document, a code file, a spreadsheet, anything that is going to change over time.

Now imagine you have to track its history without git. Here is the manual-tracking procedure:

  1. Make a folder called project-history somewhere safe
  2. Copy the current state of your file into that folder, name it project-v1.txt (or whatever the file extension is)
  3. Open the original file, make a meaningful change. Save it.
  4. Copy the new state into the history folder, name it project-v2.txt
  5. Make another change. Save it.
  6. Copy that state in too, name it project-v3.txt
  7. Make a fourth change. Save it.
  8. Now stop, and answer three questions:

Question one: what did the file look like after version 2, before version 3? (You should be able to answer; the file is in your history folder.)

Question two: what is different between version 2 and version 3? (You can answer, but it requires opening both files side by side and reading carefully.)

Question three: what changed between version 1 and version 3? (Same answer: open both, read carefully, hold the difference in your head.)

Now imagine doing this for thirty versions. Now imagine doing it for a project with twenty files. Now imagine a teammate joining the project and asking “wait, which version was the one I last looked at?” Now imagine a teammate ALSO using manual tracking with their own version-numbered files in their own folder.

You are now feeling the cost of manual tracking. The folder grows fast, the answers to “what changed when” require manual reading, the answer to “which version was approved” requires guessing, and the moment a second person joins, the system collapses.

Git is the answer to all of this. The commands you are about to learn replace the manual procedure with something faster, more accurate, and that scales gracefully to twenty files, thirty versions, and many teammates.

Recall the snapshot mental model from L1: git stores snapshots of project state over time. Every command in this lesson is a way to make those snapshots happen automatically and meaningfully.

Specifically, the manual procedure you just felt the pain of becomes this:

  • Step 1 (make a folder for history) becomes git init. One command. You only do it once per project.
  • Steps 2, 4, 6 (copy each state into history) become git commit. You do this whenever you want to save a checkpoint.
  • Questions 1, 2, 3 (recover and compare past states) become git log, git show, and git diff. We will meet these in this lesson and the next.

The headline: what took ten minutes of manual procedure and produced a fragile pile of numbered files becomes a few seconds of typing and produces a complete, queryable history.

A git repository (called a repo for short) is the home for the history of one project. Creating a repo means telling git “track this folder as a project.”

Pick a folder for a real project of yours, or create a new empty folder if you want to experiment safely. Open a terminal there. The command to create the repo is:

Terminal window
git init

That is it. Git prints a short confirmation message and adds a hidden folder named .git to your project. The .git folder is where git stores every snapshot and the entire history. You will never need to look inside it by hand; git’s commands manage it for you.

A few practical notes:

  • You only run git init once per project. Running it a second time in the same folder is harmless but unnecessary.
  • The folder itself is your project. Everything in this folder (and subfolders) is what git is potentially tracking. Files outside the folder are not part of this repo.
  • You can initialize in a folder with existing files. Git does not delete or modify your files when you initialize. It only adds the .git folder.

The three areas: working directory, staging area, repository

Section titled “The three areas: working directory, staging area, repository”

Here is the mental model that makes every git command after this make sense. Git divides your project state into three areas:

  1. The working directory is what you see when you open the folder in a file manager or text editor. The actual files as they exist on your disk right now.

  2. The staging area (sometimes called the index) is a holding space where you decide which changes to include in the next snapshot. Think of it as a draft of your next commit.

  3. The repository is the permanent history. Every snapshot you save is stored here, forever recoverable.

Changes flow through these three areas in one direction:

+------------------+ +------------------+ +------------------+
| | | | | |
| Working | | Staging | | Repository |
| Directory | ----> | Area | ----> | (history) |
| | git | | git | |
| Your edits | add | Next snapshot | commit | Permanent |
| in real-time | | draft | | snapshots |
| | | | | |
+------------------+ +------------------+ +------------------+

You make edits in the working directory by opening files and typing. When you are ready to save a checkpoint, you tell git which of those changes belong in the snapshot using git add. When the staging area looks correct, you save the snapshot using git commit.

Why the staging area exists. A naive system might just snapshot whatever the working directory looks like at the moment you commit. Git’s design separates “the things I am ready to commit” from “the things I am still working on” so that you can keep editing files A and B in the working directory while you commit a finished version of file C. This becomes load-bearing later when you fix multiple things at once but want to commit them as separate logical changes; we will come back to this in L3.

For now, the rule of thumb: edit files in the working directory, choose what to include with git add, save the snapshot with git commit.

The single most useful command in git is also the most boring:

Terminal window
git status

It does not change anything. It just tells you what state the three areas are in right now. Run it any time you want to know what git thinks is going on.

A typical output, just after you initialized an empty repo:

On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)

After you create a file called notes.txt in the folder:

On 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 is helpful. It tells you what state things are in AND what command you can run next.

The untracked label is git saying “I see this file exists, but you have not told me to start tracking it.” The next step is to add it.

Discipline tip: run git status before you commit, after you commit, after you switch branches, and any time you are confused about what state your repo is in. It is cheap, it is informative, and it prevents mistakes.

The first commit happens in three steps:

Step 1: tell git which files to include in the next snapshot. This is git add:

Terminal window
git add notes.txt

If you have many files and want to add all of them, you can use a shortcut, git add followed by a single dot:

Terminal window
git add .

The trailing dot means “everything in the current directory.” Use this carefully; it can pick up files you did not intend to track. (We will address this with a gitignore file shortly.)

Always check what got staged

Adding everything with a dot will add files you may have forgotten about, like a massive temp.log, a secrets.env, or a .DS_Store clutter file from macOS. Get in the habit of running git status IMMEDIATELY after you stage everything, to double-check what was actually staged. Two seconds of verification saves hours of “wait, why did I commit my AWS keys” remediation.

Run git status again. You will see your file moved from “Untracked” to “Changes to be committed”:

On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: notes.txt

This is the staging area. Git is saying “you have queued up notes.txt for the next snapshot.”

Step 2: take the snapshot. This is git commit:

Terminal window
git commit -m "Add initial notes"

The message flag (typed as dash m) is short for “message.” The text in quotes is your commit message: a short description of what this snapshot contains. Git will reject an empty commit message; every snapshot must have at least a label.

(We will spend an entire lesson on commit hygiene and what makes a good message in L3. For now, anything descriptive works.)

Git responds with something like:

[main (root-commit) abc1234] Add initial notes
1 file changed, 5 insertions(+)
create mode 100644 notes.txt

That short hash at the front is the commit’s identifier. Every commit gets a unique fingerprint based on its contents. You will rarely need to remember the hash; git keeps track of it for you.

Step 3: verify the commit landed. Run git status:

On branch main
nothing to commit, working tree clean

“Working tree clean” means the working directory matches the most recent commit. There are no uncommitted changes. This is the calm state.

Congratulations. You just made your first commit. The snapshot is stored in the repository. If you delete the file, or modify it badly, or your laptop crashes (assuming you have the .git folder), the snapshot is recoverable.

The pattern you just walked through is the daily commit cycle. Read the four-line summary; it will become muscle memory within a week of regular use:

1. Edit files in the working directory.
2. git add <file> (stage the changes you want in the next snapshot)
3. git commit -m "message" (take the snapshot)
4. git status (confirm clean state)

Repeat any time you have a meaningful chunk of work you want to checkpoint. Once per day is a fine starting cadence; once per logical change is better (we will get there in L3).

If you have used git before, you may be tempted to skip git status because you already know what state the repo is in. Resist the temptation. The cost is roughly zero, and it catches “wait, I had unstaged changes I forgot about” surprises before they become commits with the wrong contents.

The other observation worth landing: if you came from Subversion, the working-staging-repo three-area model may feel like overhead. (SVN has working dir plus repo, no staging.) The staging area pays for itself the first time you want to commit two unrelated changes as separate commits without manually undoing one of them. We will cover this in L3.

The files you do NOT want to track: a gitignore file

Section titled “The files you do NOT want to track: a gitignore file”

Some files do not belong in version control. Examples:

  • Compiled output (a dist or build folder)
  • Dependencies installed by a package manager (a node-modules folder)
  • Temporary editor files (swap files from vim, lock files from LibreOffice)
  • Secrets (an environment file with API keys, credentials)
  • Personal preferences (editor folders like dot-idea or dot-vscode)

If you stage everything with a dot in a project with any of these, you commit them. That is bad: secrets become visible in history, compiled output bloats the repository, and dependencies are hundreds of megabytes that everyone re-downloads when they clone.

Git solves this with a file called .gitignore. Create a file with that exact name in the root of your repo. Each line is a pattern git should ignore.

A minimal gitignore file for a typical project might look like:

# Build output
dist/
build/
# Dependencies
node_modules/
# Editor files
.swp
.~lock*
.idea/
.vscode/
# Secrets
.env
.env.local
# OS artifacts
.DS_Store
Thumbs.db

Lines starting with a hash mark are comments. Other lines are patterns. A trailing slash means “match a directory and everything in it.” Without a trailing slash, the pattern matches both files and directories with that name.

Once the gitignore file is in place, staging everything will silently skip the ignored patterns. You can verify by running git status; ignored files will not appear in the output.

Why the gitignore file matters in snapshot terms: every commit is a snapshot of every tracked file. Ignored files are not tracked, so they never appear in any snapshot. By ignoring compiled output, dependencies, editor files, and secrets, your snapshots remain focused on YOUR contributions, the project history stays clean, fast (smaller clones), and secure (no committed credentials). The discipline pays off compoundingly over the project’s lifetime.

Commit your gitignore file. It is part of your project; collaborators need it. The same files everyone wants ignored will be ignored on every clone.

A useful frame for managers and technical product managers

Section titled “A useful frame for managers and technical product managers”

If you are a non-engineering reader, the value of this lesson is in three observations:

First, the three-area model (working directory, staging, repository) explains why your engineering team sometimes says “I have uncommitted changes” or “I need to stage these first.” They are referring to specific intermediate states between editing and committing. Knowing the model makes those conversations legible.

Second, the gitignore mechanism is your team’s defense against committing secrets to public repositories. When a team accidentally publishes an API key (which has happened to companies large and small), the cause is almost always either a missing gitignore entry or someone using a “commit everything” shortcut without inspecting first. Asking “what is in your gitignore file?” during a security review is a reasonable, specific question.

Third, a commit is a unit of intent. Every commit has a message, a timestamp, and an author. That history becomes the project’s institutional memory. When a junior engineer asks “why did we do it this way?”, the answer often lives in an old commit message from three years ago. The discipline of meaningful commit messages (covered in L3) is the discipline of maintaining institutional memory.

By the end of this lesson you can:

  • Initialize a repository in any project folder
  • See the state of your repository with git status
  • Stage changes with git add
  • Save snapshots with git commit
  • Tell git which files to ignore with a gitignore file

You have a workflow. Open any project, run git init, start the edit-add-commit cycle, and you have a recoverable history. The procedure that took ten minutes of manual copy-paste-rename has become a few seconds of typing.

L3 covers commit hygiene: what makes a good commit message, why atomic commits matter, and how to make your history readable in a year. The skills land on top of the muscle memory you just built.

L4 covers undoing things: how to recover when you commit the wrong thing, when to use reset versus revert versus restore, and why the reflog is your safety net.

After L4 you have confident solo workflow. Phase 2 adds collaboration.

Git stores snapshots. Every other command is just navigating those snapshots.

You just navigated to the snapshot-creating commands. git add says “include this in the next snapshot.” git commit says “take the snapshot now.” git status says “tell me what state I am in.” Every command is a verb against the snapshot mental model.