G5 · Part 1 · Foundations
The everyday commands
The loop you run hundreds of times a week, and the history-reading tools that turn "who changed this?" into a one-liner.
07On this page
This is the loop you'll run several hundred times a week. Nothing here is clever; fluency is the point. The second half of the chapter is about reading history, which is where experienced engineers get most of their value out of Git.
a normal working day
git status
git switch -c feature/checkout
git diff
git diff --staged
git add -p
git commit -m "feat: apply coupon before tax"
git fetch origin
git rebase origin/main
git push -u origin feature/checkout
Run git status constantly; it is free and it tells
you what state you are in. switch -c creates a
branch and moves onto it. The two diffs show unstaged and staged
changes. fetch downloads other people's work without
touching yours, and rebase origin/main replays your
commits on top of theirs. push -u sets the upstream
the first time, so later a bare git push and
git pull know where to go.
What each command touches
Most anxiety about Git commands comes from not knowing what they will change. This table answers that for the everyday set. "Safe" means it cannot lose work you have not committed.
| Command | Reads | Writes | Safe? |
|---|---|---|---|
git status | Files, index, HEAD | Nothing (may refresh the index's stat cache) | Yes |
git diff, log, show, grep | Files, index, objects | Nothing | Yes |
git add | Files | Objects (new blobs), index | Yes |
git commit | Index | Objects (trees, commit), current branch ref | Yes |
git switch | Target commit | HEAD, index, files | Yes: refuses if your edits would be overwritten |
git restore file | Index (or --source) | Files | No: discards edits |
git restore --staged file | HEAD | Index | Yes: files untouched |
git stash | Files, index | A stash commit; resets files and index | Yes: recoverable |
git fetch | The remote | Objects, refs/remotes/* | Yes |
git pull | The remote | Everything fetch writes, then branch, index, files | Mostly: stops on conflicts |
git push | Your branch | The remote's branch | Yes, unless forced |
Committing well
git commit with no -m opens your editor,
and git commit -v puts the full staged diff under the
message so you can read the change while you describe it. Other
habits that pay off:
-
git commit --amendreplaces the last commit with a new one (new message, or extra staged changes). Fine before you push; after that it rewrites shared history.--amend --no-editkeeps the message. -
git commit --fixup=<sha>records a small correction aimed at an older commit.git rebase -i --autosquashlater folds it in. -
git commit -astages every tracked file's changes and commits. It never adds new files, and it skips the review step, so use it only when you just read the whole diff.
Reading history like a pro
git log, actually useful
git log --oneline --graph --decorate --all
git log -p src/cart.js
git log --follow -p src/cart.js
git log -S "calculateTax"
git log --since="2 weeks" --author="Aisha"
git show a3f9c1
In order: the whole graph in your terminal, every change to one file with its diffs, the same but following the file across renames, the commits where a string appeared or vanished, a filtered list, and one commit in full. The first one is worth an alias, because it answers "where is everything?":
$ git log --oneline --graph --decorate --all
* 1b06427 (HEAD -> main) docs: add README
* eeb36f5 fix: round tax to two decimals
| * f9ea75e (feature/coupons) feat: apply coupon before tax
|/
* 9ee1c69 (tag: v1.0) feat: include tax in cart total
* e750e96 feat: add GST calculation
* 37a4c57 feat: cart total
Read it bottom to top for time order. Each * is a
commit; the lines to its left are the graph. The parentheses
show which refs point at each commit, so here
feature/coupons split from main at v1.0 and main has
moved on by two commits since.
| Filter | Shows |
|---|---|
-n 10 or -10 | Only the newest ten. |
--since="2 weeks", --until=2025-08-01 | A time window, by commit date. |
--author=Aisha | Author matches the pattern (name or email). |
--grep="coupon" -i | Commit message matches, case-insensitive. |
-- src/tax.js | Only commits that touched this path. |
--no-merges / --merges | Hide merge commits / show only them. |
--first-parent | Follow only the first parent: main as a list of what landed. |
main..feature | Commits on feature that main does not have. See the mental model. |
--stat, --name-status | Which files each commit changed, with counts or with A/M/D/R letters. |
Pretty formats
git log has built-in layouts, from terse to
exhaustive: oneline, short,
medium (the default), full,
fuller and reference.
fuller is the one to reach for when you need to see
both the author and the committer, for example after a rebase:
$ git log -1 --format=fuller
commit 1b06427a8c7d8fe438acd53c39008641a3cff92e
Author: Aisha Khan <aisha@corp.dev>
AuthorDate: Fri Aug 8 14:50:00 2025 +0530
Commit: Aisha Khan <aisha@corp.dev>
CommitDate: Fri Aug 8 14:50:00 2025 +0530
docs: add README
For anything else, build your own line with
--format (also spelled --pretty=format:)
and placeholders:
| Placeholder | Gives | Placeholder | Gives |
|---|---|---|---|
%H / %h | Full / short commit hash | %s | Subject (first line) |
%an / %ae | Author name / email | %b | Body |
%ad / %ar | Author date / relative ("3 days ago") | %d | Ref names, like (HEAD -> main) |
%cn / %cr | Committer name / relative date | %p | Short parent hashes |
%C(auto) | Git's default colouring for what follows | %n | Newline |
$ git log --format='%h %an %ad %s' --date=short -3
1b06427 Aisha Khan 2025-08-08 docs: add README
eeb36f5 Ravi Iyer 2025-08-07 fix: round tax to two decimals
9ee1c69 Aisha Khan 2025-08-06 feat: include tax in cart total
$ git config --global alias.lg "log --graph --format='%C(auto)%h%d %s %C(dim)%an, %ar'"
$ git lg -2
* 1b06427 (HEAD -> main) docs: add README Aisha Khan, 3 days ago
* eeb36f5 fix: round tax to two decimals Ravi Iyer, 4 days ago
--date controls how %ad and the default
layouts print dates: short, iso,
relative, or format:%Y-%m-%d %H:%M for
strftime. Formats are also the right way to feed Git into a
script: %H and a separator you control are far
safer to parse than the human layouts, which can change.
Git splits its commands into porcelain, meant for
people, and plumbing, meant for scripts. Porcelain
output (plain git status, git branch,
the default log layout) can change between versions
and follows your colour and locale settings. For a script, ask
for a stable format: git status --porcelain=v2 --branch
prints the branch, upstream and ahead/behind counts as
# branch.ab +1 -0 lines followed by one record per
file; git for-each-ref --format lists refs;
git log --format=%H lists commits; and
-z on most of these separates records with NUL, so
file names with spaces or newlines cannot break the parser.
git rev-parse answers "what commit is this name?",
and exits non-zero when the name does not exist.
git show: one object in full
git show prints whatever you point it at. For a
commit, the header and the diff; for a tag, the tag message and
then the commit; for a commit:path, the file as it
was at that commit, with no checkout needed.
$ git show --stat HEAD~1
commit eeb36f5fdb53fc3f8dd9e874fe93c1f7fe7733d4
Author: Ravi Iyer <ravi@corp.dev>
Date: Thu Aug 7 18:50:00 2025 +0530
fix: round tax to two decimals
src/tax.js | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
$ git show v1.0:src/cart.js > /tmp/cart-v1.js
$ git show HEAD --name-status
$ git show HEAD -- src/tax.js
Searching: grep, pickaxe, and line history
Different questions need different searches, and picking the right one is most of the skill.
git grep searches tracked files. It
is fast because it takes the file list from the index instead of
walking the disk, and it skips node_modules and build output for free
because they are not tracked. It can also search any commit
without checking it out.
$ git grep -n calculateTax
src/cart.js:1:import { calculateTax } from "./tax.js";
src/cart.js:4: return sub + calculateTax(sub);
src/tax.js:1:export function calculateTax(amount, rate = 0.18) {
$ git grep -n "TODO" v1.0
$ git grep -c "console.log" -- '*.js'
git log -S, the "pickaxe", deserves
a moment. It finds commits that changed the number of
occurrences of a string, so "when did this function get
deleted?" becomes a five-second question instead of an afternoon.
git log -G takes a regular
expression and finds commits whose diff adds or removes a
line matching it. The difference shows up when code moves
or is edited in place:
$ git log -S'0.18' --oneline
e750e96 feat: add GST calculation
$ git log -G'0\.18' --oneline
eeb36f5 fix: round tax to two decimals
e750e96 feat: add GST calculation
The fix commit rewrote the line containing 0.18, but
the file had one 0.18 before and one after. The count
did not change, so -S skips it; -G sees a
matching line removed and added, so it reports it. Use
-S to find where something was introduced or
deleted, -G to find every commit that touched it.
Add -p to either to see the diffs, and
--all to search every branch.
git log -L tracks a range of lines
through history. git log -L :calculateTax:src/tax.js
finds the function by name and shows every commit that changed
it, each with only the diff for that function. You can also give
line numbers: -L 40,60:src/cart.js.
git blame goes the other way: for
each current line, the last commit that changed it.
$ git blame -L 1,2 src/tax.js
eeb36f5f (Ravi Iyer 2025-08-07 18:50:00 +0530 1) export function calculateTax(amount, rate = 0.18) {
eeb36f5f (Ravi Iyer 2025-08-07 18:50:00 +0530 2) return Math.round(amount * rate * 100) / 100;
Blame names the last person to touch a line, which is
often a formatter or a rename, not the author of the logic.
-w ignores whitespace-only changes, -C
follows lines moved or copied from other files, and a
.git-blame-ignore-revs file listing bulk-reformat
commits (enabled with
git config blame.ignoreRevsFile .git-blame-ignore-revs)
makes blame skip them. GitHub reads the same file.
git shortlog: who did what
shortlog groups commits by author. With
-s it only counts, -n sorts by count,
and -e adds emails, which is how you spot one person
committing under two addresses.
$ git shortlog -sne --all
3 Aisha Khan <aisha@corp.dev>
2 Ravi Iyer <ravi@corp.dev>
1 Meera Das <meera@corp.dev>
$ git shortlog v1.0..HEAD
Aisha Khan (1):
docs: add README
Ravi Iyer (1):
fix: round tax to two decimals
The second form is a first draft of release notes: everything
since the last tag, grouped by person. Add
--no-merges to drop merge commits. If one person
shows up under several names or emails, a .mailmap
file at the root of the repository maps them to one identity for
shortlog, log and blame.
One caution: in a script or pipe, give shortlog a
revision such as HEAD. With no terminal attached and
no revision, it waits to read a log from standard input.
Modern Git split the overloaded git checkout into
two clearer commands: git switch for changing
branches, git restore for throwing away file
changes. checkout still works and you'll see it
everywhere, but the newer pair is much harder to misfire.
The three areas has the full mapping.
Finished reading?
0 of 18 chapters read in this series