Create a repository
A repository (often shortened to repo) is a project folder whose history Git keeps. Any folder can become one: git init turns the current folder into a repository by creating a hidden folder called .git inside it. Everything Git knows about the project’s history lives in .git, while your own files stay where they are. You never need to open .git or change what is inside it, and you should not.
The command you will use more than any other is git status. It tells you which branch you are on, what Git sees in the folder and, often, which command to try next. Whenever you are unsure what state your work is in, run git status.
The rest of this series builds a small collection of family recipes. Start it now:
mkdir recipes
cd recipes
git init
Initialized empty Git repository in /home/alex/recipes/.git/
ls -a
. .. .git
git status
On branch main
No commits yet
nothing to commit (create/copy files and use "git add" to track)
git init reports where it created the repository: a .git folder inside recipes. A plain ls would print nothing, because the folder is empty and .git is hidden; ls -a (for all) shows hidden entries as well, including . (this folder) and .. (the folder above). git status confirms that you are on a branch called main and that there are no commits yet, and the hint in brackets says what to do next: create files and use git add.
Run git init inside your project’s folder, never in your home folder: that would put everything you own under version control. If you do it by mistake, delete the .git folder it created there, and only that folder.
In PowerShell, ls -a does not work: type ls -Force to see hidden items such as .git. In Git Bash, ls -a works as shown.
The three areas
Git keeps your work in three places, and most of its commands move changes between them:
- The working tree is the project’s files as you see and edit them, in the folder.
- The staging area (also called the index) holds what will go into your next commit. You put changes there with
git add. - The repository holds the commits: every snapshot you have saved, stored in
.git.git committurns the contents of the staging area into a new commit.
Changes travel from the working tree to the staging area with git add, and from the staging area into the repository with git commit.
Why not simply commit everything at once? The staging area lets you choose exactly what goes into each commit, and look at it before you commit. If you fixed a typo and also started a new recipe, you can commit the two separately, so that each commit tells one story. Section 4 does exactly that.
As you work, each file is in one of a few states. A new file is untracked: Git has never stored it. After git add, it is staged. After git commit, it is committed, and while it matches the last commit Git says nothing about it. Once you change it again, it is modified, and the cycle starts over.
Staging is like packing a parcel, and committing is like sealing it and writing the label. You can keep adding to the parcel, or take things out, until you seal it. Once it is sealed, its contents are fixed.
Your first commit
Time for the first commit. Use your editor to create a file called README.md in the recipes folder, with the contents shown in the session. Then run the commands one at a time, and read what git status says after each step: the file starts as untracked, becomes a change to be committed, and ends up committed, leaving a clean working tree.
On Windows, git add may print warning: in the working copy of 'README.md', LF will be replaced by CRLF the next time Git touches it. This is harmless: Git for Windows converts between the line endings Windows editors use and the ones stored in the repository. You can ignore the warning.
README.md so that it contains:# Family recipes
Recipes we cook again and again.
git status
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
README.md
nothing added to commit but untracked files present (use "git add" to track)
git add README.md
git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
git commit -m "Add a README"
[main (root-commit) 2cf38b1] Add a README
1 file changed, 3 insertions(+)
create mode 100644 README.md
git status
On branch main
nothing to commit, working tree clean
git log
commit 2cf38b1297fde0d36ab8bfe11266e27a305d7567
Author: Alex Smith <alex@example.com>
Date: Mon Jan 5 09:07:00 2026 +0000
Add a README
The output of git commit packs in a lot. [main (root-commit) 2cf38b1] names the branch the commit was made on, says that this is the root commit (the first in the repository, with no commit before it), and gives the start of the commit’s hash, 2cf38b1. The next lines summarise the change: one file, three lines added (the heading, the blank line and the sentence), and a new file created.
git log lists the history, newest commit first. For each commit it shows the full hash, a 40-character name that identifies the commit, then the author, the date and the message. Git usually needs only the first seven characters to tell commits apart, which is why short hashes appear elsewhere.
Your hash will be different from 2cf38b1. Git calculates it from the commit’s content, its author and its time, so two commits share a hash only if they are identical in every respect.
Stage what you mean to commit
Suppose that while adding a pancake recipe, you also reworded the README. These are two unrelated changes, so they belong in two commits: one that adds the recipe, and one that explains the README change. With the staging area, you can commit them separately even though you made them at the same time. Create or edit both files as the session shows, then stage only the recipe first.
pancakes.md so that it contains:# Pancakes
- 200 g flour
- 2 eggs
- 300 ml milk
README.md so that it contains:# Family recipes
Recipes we cook again and again, with notes on what worked.
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: README.md
Untracked files:
(use "git add <file>..." to include in what will be committed)
pancakes.md
no changes added to commit (use "git add" and/or "git commit -a")
git add pancakes.md
git status
On branch main
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: pancakes.md
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: README.md
git commit -m "Add a pancake recipe"
[main 3f65e55] Add a pancake recipe
1 file changed, 5 insertions(+)
create mode 100644 pancakes.md
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: README.md
no changes added to commit (use "git add" and/or "git commit -a")
git add README.md
git commit -m "Say what the notes are for"
[main 40bc459] Say what the notes are for
1 file changed, 1 insertion(+), 1 deletion(-)
git log --oneline
40bc459 Say what the notes are for
3f65e55 Add a pancake recipe
907a979 Add a README
* 40bc459 (HEAD -> main) Say what the notes are for * 3f65e55 Add a pancake recipe * 907a979 Add a README
The first git status lists two kinds of change: README.md is modified but not staged, and pancakes.md is untracked. After git add pancakes.md, the recipe moves to the “Changes to be committed” section, while README.md stays under “Changes not staged for commit”. The commit therefore contains only the recipe, and the next git status confirms that the README change is still waiting. Adding and committing it makes the second commit.
The lines in brackets are Git’s hints about what you might do next. git restore, which they mention, undoes changes; Module 3 covers it.
git log --oneline shows each commit on a single line, newest first: the short hash and the message. The graph below the session draws the same three commits as a straight line. The label HEAD → main on the newest one says that you are on the branch main, and that your next commit will go there. What branches are, and what HEAD means, is Module 4’s subject.
Good commit messages
A commit message tells your future self, and anyone you work with, why a change was made. The change itself shows what changed; the message is the only place where the reason is recorded. A few habits make messages useful:
- Write a short summary line, of about 50 characters or fewer.
- Use the imperative mood, as if giving an instruction: Add a pancake recipe, not Added or Adds. A good test is that the summary completes the sentence “If applied, this commit will…”.
- Make each commit one change. If the summary needs the word “and”, it probably describes two commits.
- When the reason is not obvious, add more detail. Run
git commitwithout-m, and Git opens your editor: write the summary on the first line, leave a blank line, then explain. Save and close the file to finish the commit.
| Weak message | Better message |
|---|---|
stuff |
Add a pancake recipe |
fixed it |
Fix the oven temperature in the bread recipe |
changes |
Say what the notes are for |
Fixed typo and added soup and updated README |
three commits, one for each change |
Good messages cost a few seconds when you commit, and save minutes or hours whenever someone tries to understand the history.
Ignoring files
Some files should never be in the history: temporary files and backups that editors create, files your operating system adds (.DS_Store on macOS, Thumbs.db on Windows), large or generated files that can be recreated, and above all secrets such as passwords and access keys.
A file called .gitignore tells Git which names to ignore, one pattern per line. It is committed like any other file, so everyone who works on the project shares the same rules. In the session, a shopping list saved as shopping.tmp should stay out of the repository.
shopping.tmp so that it contains:eggs, milk, flour
git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
shopping.tmp
nothing added to commit but untracked files present (use "git add" to track)
.gitignore so that it contains:# Temporary files from my editor
*.tmp
git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
nothing added to commit but untracked files present (use "git add" to track)
git add .gitignore
git commit -m "Ignore temporary files"
[main 2a3bd8d] Ignore temporary files
1 file changed, 2 insertions(+)
create mode 100644 .gitignore
git status
On branch main
nothing to commit, working tree clean
The first git status lists shopping.tmp as untracked. Once .gitignore contains the pattern *.tmp, the second git status no longer mentions it: only .gitignore itself is left to commit. The file is still on your disk; Git simply ignores it.
A few pattern rules cover most needs. * matches any characters, so *.tmp matches every name ending in .tmp. A pattern ending in /, such as photos/, matches a folder and everything in it. Lines starting with # are comments. GitHub keeps ready-made .gitignore files for many languages and tools at https://github.com/github/gitignore.
.gitignore affects only files that are not yet tracked. A file that is already committed stays tracked even if a pattern matches it; Module 3 shows how to stop tracking one.
Never commit a password or a key. Once a secret is in the history, treat it as leaked and change it at once, even if you delete the file later.
Exercises
Which area? For each step, say where the newest version of pancakes.md is: the working tree, the staging area or the repository.
- You save
pancakes.mdin your editor. - You run
git add pancakes.md. - You run
git commit -m "Add a pancake recipe". - You change the recipe again and save it.
Show solution
- Working tree only: Git has not been told about the file.
- Staging area (and still the working tree): the next commit will include this version.
- Repository: the commit has saved it as part of a snapshot; the working tree and staging area now match it.
- Working tree: the file is modified; the repository still holds the version from step 3 until you add and commit again.
Read the status. Here is part of the output of git status:
Changes to be committed:
(use "git restore --staged <file>..." to unstage)
new file: pancakes.md
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: README.md
Which file will the next git commit include? Which file is modified but not staged, and which command would stage it?
Show solution
The next commit will include pancakes.md only, because it is under “Changes to be committed”. README.md is modified but not staged; git add README.md would stage it. This is the situation in the middle of the session in Section 4.
Two files, two commits. Make a new folder called menu, next to your recipes folder rather than inside it, and turn it into a repository. Then create soup.md and bread.md, each with a heading. Commit them separately, each with a clear message, then list the history in one line per commit.
Show solution
Make the folder and its repository first, then stage and commit one file at a time:
mkdir menu
cd menu
git init
Initialized empty Git repository in /home/alex/menu/.git/
soup.md so that it contains:# Tomato soup
bread.md so that it contains:# Soda bread
git add soup.md
git commit -m "Add a tomato soup recipe"
[main (root-commit) 78fc921] Add a tomato soup recipe
1 file changed, 1 insertion(+)
create mode 100644 soup.md
git add bread.md
git commit -m "Add a soda bread recipe"
[main 0bbe433] Add a soda bread recipe
1 file changed, 1 insertion(+)
create mode 100644 bread.md
git log --oneline
0bbe433 Add a soda bread recipe
78fc921 Add a tomato soup recipe
git log --oneline lists the newest commit first. Your hashes will differ from these.
Improve these messages. Rewrite each commit message, splitting the commit where that would help: update, fixed typo and added new recipe and changed readme, WIP.
Show solution
updatesays nothing; name the change, for exampleUpdate the bread recipe for a smaller tin.fixed typo and added new recipe and changed readmedescribes three changes. Make three commits:Fix a typo in the soup recipe,Add a lemon cake recipeandSay what the notes are for.WIP(“work in progress”) gives no reason to keep the commit. Commit when a piece of work is complete, and describe it, for exampleAdd the first half of the cake recipe.
Ignore a whole folder. Your recipes folder has a photos folder full of large pictures. Keep the folder out of the repository and commit the rule.
Show solution
A pattern ending in / matches a folder and everything in it:
.gitignore so that it contains:# Photos are too large to keep in Git
photos/
git status
On branch main
Untracked files:
(use "git add <file>..." to include in what will be committed)
.gitignore
nothing added to commit but untracked files present (use "git add" to track)
git add .gitignore
git commit -m "Keep photos out of the repository"
[main 52e90e9] Keep photos out of the repository
1 file changed, 2 insertions(+)
create mode 100644 .gitignore
git status
On branch main
nothing to commit, working tree clean
git status lists only .gitignore: the photos folder no longer appears as untracked.
Too late to ignore? Yesterday you committed passwords.txt by mistake. Today you add passwords.txt to .gitignore. Is the file safe now? What should you do?
Show solution
No. .gitignore affects only untracked files. passwords.txt is already tracked, so Git keeps tracking it, and yesterday’s commit keeps the passwords in the history.
Change every password in the file at once, and treat the old ones as leaked. Then stop tracking the file (git rm --cached, in Module 3) so that .gitignore applies from now on. Removing it from old commits means rewriting history (Module 8), which is no help once the repository has been shared. The best protection is never to commit secrets at all.
Self-check quiz
git init create?git init turns a folder into a repository by creating .git; the first commit comes later.git add pancakes.md, where is that version of the file recorded?git add copies the file’s current content into the staging area, ready for the next commit.git status says “nothing to commit, working tree clean”. What does that mean?(root-commit). Why?.gitignore?.gitignore itself is committed so that everyone shares the rules.Further reading
- Scott Chacon and Ben Straub, Pro Git, section 2.2: Recording Changes to the Repository.
- The reference pages for git status, git add and git commit.
- The reference page for gitignore, with every pattern rule, and GitHub’s collection of .gitignore templates.
- Chris Beams, How to write a Git commit message.