Skip to main content

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-page repository 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 --oneline shows one line per commit, newest first.
  • --stat adds the files each commit changed; -2 limits the output to two commits; -p shows 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.

Warning

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. Run git status first, or use git add -p. It is the main way secrets and junk files get committed.
  • One commit for unrelated changes. It makes review, git revert and git bisect harder. Stage by file or by hunk.
  • Amending a pushed commit. It rewrites history that others have. Make a new commit instead.
  • Expecting git commit -a to add new files. It only stages files Git already tracks.
  • git rm when you meant git rm --cached. Plain git rm deletes the file from disk too (it refuses if the file has uncommitted changes).

Summary

  • git status and git diff show what changed; git diff --staged shows what the next commit contains.
  • git add stages content, git add -p stages parts of files, and git commit stores the staging area.
  • git log, git show and names such as HEAD~1 let you read the history.
  • git mv, git rm and git rm --cached rename, delete and untrack files.
  • git restore, git restore --staged and git commit --amend undo 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

Updated:

Tags

GitVersion ControlGitHubBranchingCollaborationDevOps

Found an issue?