Git Basics: Working Tree, Staging Area and Your First Commits
DevOpsLesson 1 of 46 min read
What you'll learn
- Explain the working tree, the index and the commit history, and what moves between them
- Read the two-letter codes in git status -s
- Stage part of your work and commit only that part
- Ignore scratch files and write a useful commit message
On this page
Stage a change, edit the same file again, then run git status -s. Instead of one status letter you get two: MM. That small oddity is the best door into Git, because it only makes sense once you know that Git keeps your work in three different places at the same time. This lesson follows a bakery's price list through those three places, one command at a time, and every transcript below is real output from a throwaway repository.
Three areas, not two
Most beginners picture a file as either saved or not saved. Git has a middle step. The working tree is the folder you edit. The index, also called the staging area, is a draft of your next commit. The repository holds the permanent history of commits. You choose, change by change, what moves from one area to the next.
working tree --git add--> index --git commit--> history
(your files) (next commit) (commits)
^ |
+------ git restore -----+| Command | What it moves | Typical use |
|---|---|---|
git add <file> | Working tree to index | Pick what goes into the next commit |
git commit | Index to history | Record the draft as a permanent snapshot |
git restore <file> | Index to working tree | Throw away edits you have not staged |
git restore --staged <file> | History to index | Unstage a file but keep your edits |
git diff | Compares working tree with index | See what you have not staged yet |
git diff --staged | Compares index with last commit | See exactly what the next commit will contain |
Tell Git who you are, once
Every commit records an author. Without a name and email Git refuses to commit and prints Author identity unknown followed by *** Please tell me who you are. Set both once per machine, and choose the default branch name for new repositories while you are at it. The values below are the practice identity used for this lesson's transcripts.
git config --global user.name "Asha Rao"
git config --global user.email "asha@example.test"
git config --global init.defaultBranch mainYour first repository and first commit
Create a folder called bakery, open a terminal in it, and follow along. Shown paths are shortened and your commit hashes will differ, since a hash depends on content, author and time. The -s flag asks for the short status format: two columns, where the left column compares the index to the last commit and the right column compares the working tree to the index.
$ git init
Initialized empty Git repository in /home/asha/bakery/.git/
$ printf 'Sourdough loaf, 6.50\nCroissant, 3.20\n' > prices.txt
$ git status -s
?? prices.txt
$ git add prices.txt
$ git status -s
A prices.txt
$ git commit -m "Add first price list"
[main (root-commit) c2b541a] Add first price list
1 file changed, 2 insertions(+)
create mode 100644 prices.txtRead the codes: ?? means Git sees the file but is not tracking it. A (an A, then a space) means added to the index. After the commit, the status is empty because all three areas agree. A message such as [main (root-commit) ...] marks the very first commit, which has no parent.
The same file in two states at once
Now the puzzle from the top. We stage the rye bun, then append a to-do line without staging it. The file exists in three versions: the last commit, the index and the working tree.
$ echo 'Rye bun, 2.10' >> prices.txt
$ git status -s
M prices.txt
$ git add prices.txt
$ echo 'TODO: ask about cardamom buns' >> prices.txt
$ git status -s
MM prices.txt
$ git diff
diff --git a/prices.txt b/prices.txt
index 7201cc5..dcd4a8b 100644
--- a/prices.txt
+++ b/prices.txt
@@ -1,3 +1,4 @@
Sourdough loaf, 6.50
Croissant, 3.20
Rye bun, 2.10
+TODO: ask about cardamom buns
$ git diff --staged
diff --git a/prices.txt b/prices.txt
index 775a966..7201cc5 100644
--- a/prices.txt
+++ b/prices.txt
@@ -1,2 +1,3 @@
Sourdough loaf, 6.50
Croissant, 3.20
+Rye bun, 2.10
$ git commit -m "Add rye bun"
[main 374b16b] Add rye bun
1 file changed, 1 insertion(+)
$ git status -s
M prices.txt
$ git log --oneline
374b16b Add rye bun
c2b541a Add first price listMM reads left to right: the index differs from the last commit, and the working tree differs from the index. The two diffs prove it. git diff shows only the TODO line, while git diff --staged shows only the rye bun. The commit took what was in the index, so after it the status is M: the to-do is still an unstaged edit. This is the habit that makes Git worth learning. You can keep a scratch note in a file and commit only the part that is ready.
Keeping scratch files out, and what a commit really is
A file named in .gitignore is invisible to git status and git add . as long as it is untracked. Below, the scratch note is ignored while the new .gitignore file itself is committed so teammates share the rule. The last command prints the raw commit object.
$ echo 'scratch.tmp' > .gitignore
$ echo 'half-written supplier note' > scratch.tmp
$ git status -s
M prices.txt
?? .gitignore
$ git add .gitignore prices.txt
$ git commit -m "Add cardamom TODO and ignore scratch files"
[main 7ab492e] Add cardamom TODO and ignore scratch files
2 files changed, 2 insertions(+)
create mode 100644 .gitignore
$ git status -s
$ git ls-files
.gitignore
prices.txt
$ git cat-file -p HEAD
tree 8b80563a71569f75c2b8c24d5a5875c8a100ad6d
parent 374b16b1e0ec1324351851e4d785da67056e4685
author Asha Rao <asha@example.test> 1791170752 +0530
committer Asha Rao <asha@example.test> 1791170752 +0530
Add cardamom TODO and ignore scratch filesA commit is not a diff. It points to a tree, a full snapshot of every tracked file, and to its parent commit, plus the author, committer, timestamp and message. The hash of all that is the commit's name. Because each commit names its parent, history is a chain, and changing any old commit would change every hash after it. That is why rewriting shared history is a big deal, as the last lesson shows. Git stores snapshots efficiently, so a file that did not change is not copied again.
Commit messages that help
- Start with a short summary in the imperative mood, about 50 characters:
Add rye bun to price list, notadded stuff. - Describe one logical change per commit. If you need the word and, consider two commits.
- Use the body, after a blank line, to explain why, because the diff already shows what.
- Do not commit half-working states to a shared branch just to save progress; use a branch for that.
Common mistakes
- Committing before setting your identity. Symptom:
Author identity unknownandfatal: unable to auto-detect email address; run the twogit configcommands above and repeat the commit. - Editing after `git add`, then committing. Symptom: the commit lacks your last edit and
git statusstill showsMon the file. Stage again before committing. - Using `git add .` blindly. Symptom:
.envfiles, build output or a huge binary slip into a commit; if it is pushed, a host may reject files over its size limit (GitHub refuses files above 100 MB) or, worse, publish a secret. Checkgit statusfirst and add a.gitignoreearly. - Expecting `.gitignore` to hide an already-tracked file. Symptom: the file keeps appearing as modified. Ignore rules only affect untracked files; run
git rm --cached <file>to stop tracking it.
How this shows up in projects and interviews
On a team, your commit history is read by reviewers and by the person debugging at midnight in six months, so small commits with clear messages are a real skill. Interviewers often ask what the difference is between git add and git commit, or between the working tree and the staging area; the three-area picture answers both in a sentence. If you are building a portfolio for job applications, a repository with readable history tells a reviewer more about you than a single giant first commit.
Try it yourself
Create a new folder with a file called menu.txt holding three lines about a food truck. Make two commits: first the file, then an added fourth line. Then add a fifth line without staging it and a sixth line you do stage, and use git status -s, git diff and git diff --staged to explain what the next commit will contain before you run it.
Show a hint
Stage the sixth line by editing, running git add, and then editing the file again to add the fifth. You should see MM in the status.
Key takeaways
- Git has three areas: working tree, index (staging area) and committed history.
- git add copies to the index and git commit records the index, so a file can be both staged and modified.
- git diff compares working tree with index; git diff --staged compares index with the last commit.
- A commit is a snapshot with a parent pointer, which is what makes history a chain.
- Set your identity once, add a .gitignore early and keep commits small and described.
Test yourself
A trick from this lesson's ride: Status has two columns: left is staged, right is not-yet-staged. Four more tricks like it wait along the route, each followed by a question.
Frequently asked questions
What is the difference between git add and git commit?
git add copies the current state of a file into the index, which is a draft of your next commit. git commit then records whatever is in the index as a permanent snapshot in history. Edits you make after git add are not included unless you add them again.
What is the staging area in Git?
The staging area, also called the index, sits between your working folder and your commit history. It holds exactly what will go into the next commit, so you can commit part of your changes instead of everything. git diff --staged shows its contents compared with the last commit.
How do I see what I changed before committing in Git?
Use git status -s for a short list of changed files and git diff to see unstaged changes. After git add, use git diff --staged to see exactly what the next commit will contain. Running both before each commit catches stray edits and debug lines.