Git Add, Commit and Diff: Track and Undo Changes
The everyday Git loop: git status, git diff, git add (and add -p), git commit, git log, renaming files, undoing changes with restore and amend, and tags.
Most of your time with Git is one short loop: change files, look at what changed, stage the changes that belong together, commit them with a clear message, repeat. This part covers that loop in detail, plus the commands that fix the usual slips: a file staged by mistake, an edit you want to throw away, a commit message with a typo. At the end you tag the first release of the project.
Prerequisites
- The
status-pagerepository from Part 2, with two commits. Any repository of your own works too; the commands are the same.
Check the State with git status
git status is the command you will run most. Run it before and after anything you are not sure about:
Now make three changes. Add a docs service at the end of services.txt:
docs https://docs.example.com/
In index.html, add the team name to the <title>, and add a footer below the list:
<title>Service status - Example Team</title>
...
<ul id="services"></ul>
<footer>Built by scripts/build.sh</footer>
In the short form, the two columns are the staging area and the working tree. M means modified in the working tree, not staged yet. M means staged, MM means staged and then changed again, A added, D deleted, R renamed and ?? untracked.
See What Changed with git diff
git diff shows the changes in the working tree that are not staged yet, line by line:
Lines starting with - were removed and lines starting with + were added. Each @@ -2,11 +2,12 @@ header is a hunk: here, 11 lines starting at line 2 in the old version became 12 lines starting at line 2 in the new one. The other lines are unchanged context.
There are three diffs you will use all the time:
| Command | Compares |
|---|---|
git diff |
Working tree with the staging area (unstaged changes) |
git diff --staged |
Staging area with the last commit (what git commit would store) |
git diff HEAD |
Working tree with the last commit (everything) |
Stage Changes with git add
The services.txt change and the index.html changes are unrelated, so they should be separate commits. Stage only services.txt:
git add copies the file's content at that moment. If you edit the file again after git add, the new edits are not staged until you add it again (git status then shows MM).
| Command | Stages |
|---|---|
git add <file> |
That file (or every file in a directory) |
git add . |
All changes, new files and deletions under the current directory |
git add -A |
All changes in the whole repository, from any directory |
git add -u |
Changes and deletions of tracked files only, no new files |
git add -p |
Chosen parts of files, interactively |
The difference between . and -A only shows when you run them from a subdirectory (the details).
Stage part of a file with git add -p
index.html has two changes, the title and the footer, and only the title belongs in the next commit. git add -p (patch mode) shows each hunk and asks what to do with it. Git put both changes in one hunk because they are close together; s splits it, y stages a hunk and n skips it:
The most useful answers at the prompt are y (stage), n (skip), s (split into smaller hunks), e (edit the hunk by hand), q (quit) and ? (help). Patch mode is how you keep commits small even when you made several changes at once. How to commit only part of a file has more examples.
Make Commits
The staging area now holds services.txt and the title change. Commit them as two commits. With a path, git commit commits only that path (its current content) and leaves everything else staged for the next commit:
The footer change is still in the working tree, unstaged. git commit -a stages every change to a tracked file and commits it in one step. It never adds untracked files:
Write a message that helps the next reader
-m is fine for a one-line message. For anything that needs an explanation, run git commit without -m: Git opens your editor, and you write a short subject line, a blank line, and a body that says why the change was made. You can also pass several -m options; each one becomes a paragraph:
git commit -m "Check that service URLs use HTTPS" \
-m "The page links to every service. A plain http:// URL would send
readers to an unencrypted page, so the check now rejects it."
Part 9 covers message conventions, including Conventional Commits and trailers.
View the History with git log
git log --onelineshows one line per commit, newest first.--statadds the files each commit changed;-2limits the output to two commits;-pshows the full diff.git show <commit>shows one commit: its metadata and its diff.
You can name a commit in several ways:
| Name | Means |
|---|---|
46b8da1 |
A hash, or any unique prefix of at least 4 characters |
HEAD |
The commit you have checked out |
HEAD~1, HEAD~2 |
One or two commits before HEAD (following the first parent) |
HEAD^ |
The first parent of HEAD (HEAD^2 is a merge's second parent) |
main, v0.1.0 |
The commit a branch or tag points to |
When the output is longer than your screen, Git shows it in a pager (less): scroll with the arrow keys or space, and quit with q. Part 8 shows how to search the history by message, author, date, file or content.
Remove and Rename Files
index.html is really a template: scripts/build.sh turns it into build/index.html. A clearer name is template.html. git mv renames the file and stages the rename in one step; the build script needs the new name too:
Git does not store renames. It stores the new snapshot, and when it shows history it notices that a deleted file and an added file have the same (or very similar) content, and reports a rename. git mv old new is the same as mv old new followed by git add of both paths. To see a renamed file's history across the rename, use git log --follow -- template.html.
To delete a file, use git rm: it deletes the file and stages the deletion. To stop tracking a file but keep it on disk, use git rm --cached. Suppose you kept personal notes in the project folder and a git add . committed them:
The file is still on disk, no longer tracked, and ignored through .git/info/exclude (personal, so not in the shared .gitignore). It is still in the history, in the "Add notes" commit. That is harmless for notes and serious for secrets: a committed password stays in every clone until you rewrite the history (Part 8), and you must change the password anyway.
Undo Things
Most mistakes before a push are easy to undo. Pick the row that matches what you want:
| You want to | Run |
|---|---|
| Throw away unstaged changes to a file | git restore <file> |
| Unstage a file, but keep the changes | git restore --staged <file> |
| Change the last commit's message | git commit --amend -m "new message" |
| Add a forgotten change to the last commit | git add <file>, then git commit --amend --no-edit |
| Undo the last commit, keep its changes staged | git reset --soft HEAD~1 |
| Undo a commit that is already pushed | git revert <commit> (Part 8) |
--amend does not edit the old commit. It makes a new commit with a new hash and moves the branch to it; the old one is no longer on any branch. That is why you should only amend commits you have not pushed yet: anyone who already has the old commit now has a different history from yours.
git restore <file> (like the older git checkout -- <file>) overwrites your unstaged changes to that file. Changes you never staged or committed were never stored by Git, so Git cannot bring them back. Your editor's local history is the only rescue.
You will see older equivalents in many answers online. They still work:
| Newer command (Git 2.23+) | Older equivalent |
|---|---|
git restore <file> |
git checkout -- <file> |
git restore --staged <file> |
git reset HEAD <file> |
git switch <branch> |
git checkout <branch> |
git switch and git restore were marked experimental when they arrived; Git 2.51 removed that label. This guide uses them because each does one job.
Tag a Release
A tag is a fixed name for a commit, usually a release. Unlike a branch, it does not move. Git has two kinds:
- An annotated tag is an object of its own with a tagger, a date and a message, and it can be signed. Use it for releases.
- A lightweight tag is only a name for a commit, like a branch that never moves. Use it for private bookmarks.
git describe prints the nearest annotated tag that the current commit contains, the number of commits since that tag, and g plus the short hash of the current commit (on the tagged commit itself, it prints only the tag name). Build scripts often use it as a version string. Tags are not pushed by default; Part 6 shows how.
Common Mistakes
git add .without looking. Rungit statusfirst, or usegit add -p. It is the main way secrets and junk files get committed.- One commit for unrelated changes. It makes review,
git revertandgit bisectharder. Stage by file or by hunk. - Amending a pushed commit. It rewrites history that others have. Make a new commit instead.
- Expecting
git commit -ato add new files. It only stages files Git already tracks. git rmwhen you meantgit rm --cached. Plaingit rmdeletes the file from disk too (it refuses if the file has uncommitted changes).
Summary
git statusandgit diffshow what changed;git diff --stagedshows what the next commit contains.git addstages content,git add -pstages parts of files, andgit commitstores the staging area.git log,git showand names such asHEAD~1let you read the history.git mv,git rmandgit rm --cachedrename, delete and untrack files.git restore,git restore --stagedandgit commit --amendundo the usual mistakes before you push.- Annotated tags (
git tag -a) mark releases.
Next: Branching, where you work on a change without touching main.
Keep Learning
Tags
Found an issue?