Git & GitHub

The Daily Loop: Status, Add, Commit, Diff and Log

The commands you use every day, .gitignore, good commit messages and staging parts of a file with git add -p.

Lesson 019 min read#git#commits#workflow

On this page

  1. Words used in this lesson
  2. Start: a repository and git status
  3. Commit
  4. Diff: see what changed, before you commit
  5. .gitignore: files Git should never track
  6. Small commits and git add -p
  7. Good commit messages
  8. Find things in history
  9. Try it
  10. Interview questions
  11. Key takeaways
  12. What's next

In one line: your daily Git loop is small: look with status, choose with add, save with commit, and check your work with diff and log. Small commits with clear messages make every later step (review, undo, debug) much easier.

Lesson 0 showed what Git stores. This lesson is about how you work with it all day. We build a tiny project called shop, make commits, ignore files that should never be saved, and split one messy edit into two clean commits with git add -p.

You will see the same four commands again and again: git status (what is going on?), git add (pick changes), git commit (save them) and git log (read the history). Learn them well and you can do most daily work.

What I ran / checked. All commands were run in a throwaway folder with git version 2.43.0 on Linux, and the output is pasted as it came out. For repeatable output I ignored my own Git config (GIT_CONFIG_GLOBAL=/dev/null), used the identity "Ada Lovelace" and fixed the commit date, so every commit shows Thu Jan 1 10:00:00 2026. Your hashes and dates will be different. For git add -p I fed the answers through a pipe (printf 's\ny\nn\n' | git add -p), because I cannot type into a prompt here; the prompt text is real, but the answers appear on the same line. The commit message advice comes from the Pro Git book source (progit/progit2, book/05-distributed-git/sections/contributing.asc, section "Commit Guidelines"). .gitignore rules are shown from what I ran. I did not read the full gitignore manual page.

Words used in this lesson

  • Tracked file: a file Git already knows (it is in the last commit or in the staging area). Untracked file: a file in the folder that Git has never been told about.
  • Staged: added to the staging area with git add, so it will go into the next commit.
  • A diff is a list of lines removed (-) and added (+) between two versions.
  • A hunk is one block of changes inside a diff. git add -p lets you pick hunks.
  • .gitignore is a text file that lists files Git should not track.
  • A commit message is the text you write to explain a commit. The first line is the subject. The rest is the body.
  • Imperative mood means the subject reads like an order: "Add tax", not "Added tax".
  • Working tree clean means no tracked file differs from the last commit and there is nothing untracked to report.

Start: a repository and git status

Shell
git -c init.defaultBranch=main init shopcd shopgit status
TEXT
Initialized empty Git repository in .../shop/.git/On branch mainNo commits yetnothing to commit (create/copy files and use "git add" to track)

git status is the command to run when you are unsure. It tells you which branch you are on and what is different. Let me create two files and run it again:

Shell
printf 'def total(prices):\n    return sum(prices)\n' > cart.pyecho "# Shop" > README.mdgit status --short
TEXT
?? README.md?? cart.py

Both files are untracked (??). Git sees them but ignores them until you ask. Stage one:

Shell
git add README.mdgit status
TEXT
On branch mainNo commits yetChanges to be committed:  (use "git rm --cached <file>..." to unstage)	new file:   README.mdUntracked files:  (use "git add <file>..." to include in what will be committed)	cart.py

Git groups files by state. README.md is staged. cart.py is still untracked. The text in brackets is a hint for the next step, and it is worth reading, because Git tells you how to undo what you just did.

The short form git status --short is faster for daily use. It prints A README.md for a staged new file and ?? cart.py for an untracked one. A in the left column is "added to the staging area", M is modified, and the left column is the staging area while the right column is the working tree, as in lesson 0.

Commit

Shell
git commit -m "Add README"git add cart.pygit commit -m "Add total() to cart"
TEXT
[main (root-commit) ad61000] Add README 1 file changed, 1 insertion(+) create mode 100644 README.md[main 5bb83e7] Add total() to cart 1 file changed, 2 insertions(+) create mode 100644 cart.py

The part in brackets tells you the branch and the short hash of the new commit. -m gives the message on the command line. If you leave out -m, Git opens your editor so you can write a longer message. If you do not want vim, set another editor once: git config --global core.editor "nano".

Before your first commit Git needs to know who you are. Set it once for your computer:

Shell
git config --global user.name "Your Name"git config --global user.email "you@example.com"

Use the same email as your GitHub account later, so GitHub can link your commits to you.

Read the history with git log

Shell
git log
TEXT
commit 5bb83e7c0f3f898cebb27f8ecceb3872bba77f06Author: Ada Lovelace <ada@example.com>Date:   Thu Jan 1 10:00:00 2026 +0000    Add total() to cart(older commits follow)

The newest commit is on top. This is long, so most people use --oneline:

Shell
git log --oneline
TEXT
5bb83e7 Add total() to cartad61000 Add README

Diff: see what changed, before you commit

Edit cart.py and add a function:

Shell
printf 'def total(prices):\n    return sum(prices)\n\n\ndef discount(amount, percent):\n    return amount * (100 - percent) / 100\n' > cart.pygit status --shortgit diff
TEXT
 M cart.pydiff --git a/cart.py b/cart.pyindex aa11c87..6a59ccc 100644--- a/cart.py+++ b/cart.py@@ -1,2 +1,6 @@ def total(prices):     return sum(prices)+++def discount(amount, percent):+    return amount * (100 - percent) / 100

How to read a diff:

  • --- a/cart.py is the old version and +++ b/cart.py is the new version.
  • @@ -1,2 +1,6 @@ says: the hunk covers lines 1-2 in the old file and lines 1-6 in the new file.
  • Lines starting with + are added. Lines starting with - are removed. Lines with a space are context, shown to help you find the place.

The same file now shows with a space in the left column and M in the right: M. That means "changed in the working tree, not staged". Stage it, and look at both diffs:

Shell
git add cart.pygit diff            # prints nothing: nothing differs between disk and the staging areagit diff --staged   # shows what the next commit will contain

git diff printed nothing, and git diff --staged printed the same diff as above. This is the classic trap from lesson 0: after git add, plain git diff is empty. Use git diff --staged (the older name --cached means the same).

Make it a habit: before every commit run git diff --staged and read it. It is the cheapest code review you will ever get. Read it for things like leftover debug prints, a file you did not mean to include, or a password.

Shell
git commit -m "Add discount() to cart"
TEXT
[main ddb3943] Add discount() to cart 1 file changed, 4 insertions(+)

Other forms: git diff HEAD (everything since the last commit), git diff main feature (two branches), git diff --stat (only a summary) and git diff --check (warns about whitespace errors; the Pro Git book recommends it before committing).

.gitignore: files Git should never track

Some files do not belong in history: dependencies (node_modules/), build output, log files and, most important, secrets such as .env. I created four of them:

Shell
mkdir -p node_modules/x buildecho junk > node_modules/x/i.js; echo out > build/app.jsecho "SECRET=1" > .env; echo log > debug.loggit status --short
TEXT
?? .env?? build/?? debug.log?? node_modules/

Git offers all four. Now I write a .gitignore file with one pattern per line:

Shell
printf 'node_modules/\nbuild/\n.env\n*.log\n' > .gitignoregit status --short
TEXT
?? .gitignore

The four are hidden. Only .gitignore itself shows up, and that is correct: you commit .gitignore so the whole team shares the rules. If you are not sure why a file is ignored, ask Git:

Shell
git check-ignore -v .env debug.log
TEXT
.gitignore:3:.env	.env.gitignore:4:*.log	debug.log

The output means: file .gitignore, line 3, pattern .env matched the path .env.

Shell
git add .gitignoregit commit -m "Add .gitignore"

Common patterns: name/ ignores a folder, *.log ignores by extension, !keep.log makes an exception, and /build (with a leading slash) only matches in the top folder.

.gitignore does not remove files already tracked. If you committed .env yesterday, adding it to .gitignore today changes nothing for that file: Git keeps tracking it. You must run git rm --cached .env and commit. And worse: the secret is already in history. Removing it from the latest commit does not remove it from old commits. If a real secret was ever committed, treat it as leaked and replace it (rotate it) first, then clean up.

Small commits and git add -p

A good commit does one thing. Reasons:

  • A reviewer can understand it in a minute.
  • If it causes a bug, you can undo exactly that one thing (lesson 4: revert).
  • git bisect can point at the guilty commit (lesson 4).
  • The history reads like a story.

But real work is messy. You often change two things in one file. git add -p ("patch") lets you stage parts of a file. I changed cart.py in two places: a docstring for total() and a new tax() function.

Shell
git diff
TEXT
@@ -1,6 +1,11 @@ def total(prices):+    """Add up prices."""     return sum(prices)   def discount(amount, percent):     return amount * (100 - percent) / 100+++def tax(amount, rate):+    return amount * (1 + rate)

Git shows this as one hunk, because the changes are close together. I answer the prompt with s (split into smaller hunks), then y (stage the first), then n (do not stage the second):

Shell
printf 's\ny\nn\n' | git add -p cart.py
TEXT
(1/1) Stage this hunk [y,n,q,a,d,s,e,?]? Split into 2 hunks.@@ -1,6 +1,7 @@ def total(prices):+    """Add up prices."""     return sum(prices)   def discount(amount, percent):     return amount * (100 - percent) / 100(1/2) Stage this hunk [y,n,q,a,d,j,J,g,/,e,?]? @@ -2,5 +3,9 @@     return sum(prices)   def discount(amount, percent):     return amount * (100 - percent) / 100+++def tax(amount, rate):+    return amount * (1 + rate)(2/2) Stage this hunk [y,n,q,a,d,K,g,/,e,?]? 

(The prompts and my answers are mixed together because the answers came from a pipe. In a real terminal you type one letter at each prompt.) The letters: y stage, n skip, s split, q quit, a stage this and all later hunks, d skip this and all later hunks, e edit the hunk by hand, ? help. Now:

Shell
git status --short
TEXT
MM cart.py

MM again: the same file has a staged part and an unstaged part. Check each part:

Shell
git diff --staged     # only the docstringgit commit -m "Document total()"git diff              # only tax() is leftgit commit -a -m "Add tax()"
TEXT
diff --git a/cart.py b/cart.pyindex 6a59ccc..edc0250 100644--- a/cart.py+++ b/cart.py@@ -1,4 +1,5 @@ def total(prices):+    """Add up prices."""     return sum(prices)  diff --git a/cart.py b/cart.pyindex edc0250..4543ffd 100644--- a/cart.py+++ b/cart.py@@ -5,3 +5,7 @@ def total(prices):  def discount(amount, percent):     return amount * (100 - percent) / 100+++def tax(amount, rate):+    return amount * (1 + rate)

The first diff is the staged docstring. The second diff is what was left, only tax(). (I left out the one-line output of the two commits.) git commit -a stages all changes to tracked files and commits them in one step. It skips untracked files. It is handy, but it removes your chance to choose. Use it only when you really want everything.

Shell
git log --oneline
TEXT
6b5f2bc Add tax()53138b1 Document total()282c4b2 Add .gitignoreddb3943 Add discount() to cart5bb83e7 Add total() to cartad61000 Add README

One messy edit became two clean commits.

Good commit messages

The Pro Git book (and the Git project itself) suggest this shape:

TEXT
Capitalized, short (50 chars or less) summaryMore detailed explanatory text, if necessary. Wrap it to about 72characters. The blank line between the summary and the body matters.

Rules that work well in teams:

  1. Subject: about 50 characters, imperative mood. "Fix crash on empty cart", not "fixed stuff". The book says to write it as "Fix bug" and not "Fixed bug" or "Fixes bug", so it matches the messages Git makes for you, such as Merge branch ... and Revert ....
  2. Blank line, then a body when the reason is not obvious. Explain why you made the change, and what it replaces. The code already shows what changed.
  3. One idea per commit, so the subject does not need the word "and".
  4. A link to the ticket (for example Refs #123) if your team uses them.

Compare fix stuff with Fix rounding in discount() for 0 percent. Only the second one helps the next reader. Avoid WIP and updates in anything you push (squash them away, lesson 5).

A longer example with a body (this one is a sample message, not from a run):

TEXT
Round discount to two decimalsFloating point gave results like 89.99999999999999 on the order page.Use round() on the final amount only, so rates still add up exactly.

Find things in history

Shell
git log --oneline --stat -2        # which files changed, last two commits (output below)git log -S"tax" --oneline          # commits that added or removed the text "tax"git log --oneline -- README.md     # commits that touched this filegit log --oneline --author=Ada -3  # by author, newest three
TEXT
6b5f2bc Add tax() cart.py | 4 ++++ 1 file changed, 4 insertions(+)53138b1 Document total() cart.py | 1 + 1 file changed, 1 insertion(+)

The next three commands printed (in order) 6b5f2bc Add tax() for -S"tax", ad61000 Add README for -- README.md, and the newest three commits 6b5f2bc, 53138b1, 282c4b2 for --author=Ada -3. Two more useful ones: git show <commit> prints one commit with its diff, and git diff HEAD~2 HEAD --stat summarises the last two commits.

-S is called the "pickaxe". It is one of the best ways to answer "when did this word first appear in the code?"

Try it

Make a new folder and run git -c init.defaultBranch=main init.

  1. Create notes.txt, commit it with the message Add notes. Then change the file and run git status --short, git diff, and git diff --staged. Which of the last two shows the change?
  2. Run git add notes.txt and repeat both diff commands. What changed, and why?
  3. Create a file secret.env and a file app.log. Write a .gitignore that ignores both, and prove with git check-ignore -v that it works.
  4. Put five lines in a file, commit it. Change line 1 and line 5. Use git add -p so that line 1 goes into one commit and line 5 into another. (If Git shows one hunk, try s, or edit with e.)
  5. Look at git log --oneline. Rewrite this bad message into a good one: fixed login thing and changed css.
  6. Find the commit that first added the word tax to a file, with a single command.

Answers

  1. git diff shows the change (it is unstaged). git diff --staged shows nothing.
  2. Now git diff shows nothing and git diff --staged shows the change, because the staging area now matches the working tree and differs from the last commit.
  3. .gitignore containing secret.env and *.log (or app.log). git check-ignore -v secret.env app.log prints the rule that matched each path.
  4. If both edits are in one hunk (they are only 5 lines apart, so the context overlaps), press s to split. If Git cannot split, press e and delete the lines you do not want to stage. Commit, then git add -p again for the second hunk.
  5. Two commits with two messages, for example Fix login redirect after sign-in and Increase button contrast. The word "and" in a subject is a hint to split.
  6. git log -S"tax" --oneline. The oldest commit in the result is the one that introduced it.

Interview questions

What is the difference between git add and git commit? (Must know)

git add copies the current content of files into the staging area. It marks what will be in the next commit. git commit saves the staging area as a new commit in the history. So add chooses and commit records. If I change a file after git add, the new change is not in the commit unless I add it again.

git diff shows nothing but I changed a file. Why? (Must know)

Most likely the change is already staged. Plain git diff compares the working tree with the staging area, so after git add it is empty. I run git diff --staged to see the staged change, or git diff HEAD to see everything since the last commit. A second reason is that the file is untracked (diff does not show new untracked files) or ignored.

How do you stop Git from tracking a file? (Must know)

For a file that is not tracked yet, I add it to .gitignore. For a file that is already tracked, .gitignore does not help, so I run git rm --cached file and commit; this removes it from the repository but keeps it on disk. If the file had a secret, the secret stays in old commits, so I must rotate the secret too.

What makes a good commit message? (Must know)

A subject of about 50 characters in the imperative mood ("Fix crash on empty cart"), a blank line, and a body that explains why the change was needed when it is not obvious. One idea per commit. The reader of git log or git blame in a year should understand the reason without asking me.

Follow-up: "Do you follow Conventional Commits?" It is a popular convention (feat:, fix:) that helps tools make release notes. I follow whatever the team has agreed. The most important parts are still the clear subject and the explanation of why.

Why do you prefer small commits? (Senior signal)

They are easier to review, and each one can be reverted alone. git bisect works better, because it ends at a small commit instead of a big one with 40 files. The history also explains how the feature grew. A practical limit: every commit should leave the project working (build and tests pass), so that bisect does not land on a broken in-between state.

When do you use git add -p? (Senior signal)

When one file, or one work session, has several unrelated changes. -p goes through the hunks and I choose y or n for each; s splits a big hunk and e lets me edit it by hand. After staging I run git diff --staged to check, then commit. It also gives me a last look at my own change before it goes in.

What does git commit -a do, and when is it a bad idea? (Trap)

It stages all modified and deleted tracked files and commits them. It does not include new untracked files. It is a bad idea when I have unrelated edits in the working tree, because everything lands in one commit, and when I have not reviewed the diff. I prefer git add -p or specific file names for anything that will be reviewed.

You committed a password. What now? (Senior signal)

First, I treat the password as leaked and change it at the source. This is the urgent step, and it cannot be skipped. Then I remove it from the code and move it to an environment variable or secret store, and add the file to .gitignore. Cleaning old history is possible with history rewriting tools, but if the repository was ever pushed, others may already have copies. So rotating the secret is the real fix, and rewriting history is a tidy-up.

How do you find which commit introduced a string or changed a file? (Senior signal)

git log -S"text" finds commits where the number of occurrences of the text changed. git log -- path lists commits that touched a file, and git log -p -- path adds the diffs. git blame path shows the last commit for each line. For a bug that is not a string, I use git bisect (lesson 4).

Key takeaways

  • The loop is git status, git add, git diff --staged, git commit, git log --oneline.
  • Plain git diff shows unstaged changes only. Use git diff --staged before each commit.
  • Put dependencies, build output, logs and secrets in .gitignore. Ignoring does not remove files that are already tracked, and a committed secret must be replaced.
  • Make small commits with one idea each. Use git add -p to split a messy edit.
  • Write the subject in the imperative, about 50 characters, and use the body to explain why.
  • git log -S and git log -- file help you find when something changed.

What's next

Next: Branches, merging and conflicts, Lesson 2. You will create branches, see the difference between a fast-forward and a merge commit, and resolve a real conflict step by step. Practise now: make five small commits in a practice repository, then read them with git log --oneline --stat.

Published by Jainik · Last updated · How these lessons are made

Keep learning

Related lessons

Part of the study guide

Related series