Java Guild course · Lesson 0007

Lesson 0007 · the narrative

The Commit Log Is Part Of The Submission

An assessor with forty submissions to mark will read your history before they read your code. Write it for them.

The brief asks for “a brief description of the approach taken”. Most candidates write a paragraph. You can do better: hand over a commit log where the approach is demonstrated, commit by commit, with the reasoning attached to the change it justifies.

The reframe

A commit message is not a note to yourself. It is a message to a reviewer who was not there, explaining why the diff below it exists. The diff already says what changed; the message exists to say why.

The seven rules, and the two that matter here

Chris Beams’ seven rules are the standard reference. All seven are worth following, but two do the heavy lifting:

RuleWhy it earns marks here
Subject line in the imperative mood, under 50 chars, no full stop“Replace IOException with domain exceptions” completes the sentence “if applied, this commit will…”. Consistency signals someone who has worked on a team.
Body explains what and why, not howThis is where the marks are. The how is the diff. The why is the only thing you can contribute that the code cannot.

The same change, three ways

fixed bug

Tells the reviewer nothing. They now have to read the diff and guess your intent.

Changed borrowBook to throw AlreadyBorrowedException instead of
IOException and removed the boolean return type

Better, but it is only the diff in English. A reviewer can already see that.

Replace IOException with the supplied domain exceptions

borrowBook and returnBook signalled every failure with IOException,
although neither performs any I/O, and collapsed two distinct cases
into one branch: borrowing an already-borrowed book reported
"Book not found" for a book the library was holding.

Wires up AlreadyBorrowedException, BookNotFoundException and
NotBorrowedException, which were present in the repository but
unreferenced.

Now the reviewer knows the defect, the severity, and that you noticed the unused classes were a hint. That last sentence is worth more than the diff.

Conventional Commits: adopt it or don’t, but decide

Conventional Commits prefixes each subject with a type: feat:, fix:, refactor:, test:, docs:.

Argument
ForThe type instantly tells a reviewer whether to expect behaviour change. refactor: is a promise the tests did not change — a promise they can check.
AgainstIt is a convention for repos with automated changelogs and semantic-version releases. This has neither. It can read as ceremony.
The recommendation

Use it, consistently, and say so in one line of your README. The value here is not tooling — it is that refactor: versus fix: makes your make-it-work-then-make-it-right discipline visible at a glance down the log. And “I followed Conventional Commits” is a two-word answer to “how do you work in a team?”

What you must not do is use it for half the commits. Inconsistency is worse than either choice.

The commit script

Here is the whole submission as a sequence. Each line is one commit, at green. Full messages are on the commit script reference card.

#SubjectWhy it is its own commit
1chore: add starter project as suppliedThe most important commit in the repo. Everything after it is a visible diff against what Solirius handed over.
2build: compile with release 11 and set a real groupIdBuild hygiene, separate from any code change.
3test: pin existing behaviour with a golden masterThe safety net, before anything it protects. Adds the ApprovalTests dependency in the same commit.
4test: add unit tests for Book and LibraryThe Graduate requirement, discharged and identifiable.
5fix: replace IOException with the supplied domain exceptionsThe headline change. Include the re-approved golden master here.
6refactor: return Optional from searchBookBehaviour-preserving; keep it out of commit 5.
7fix: stop the saved format losing and corrupting dataThree related defects in one subsystem — one commit is right, three would fragment the story.
8refactor: extract LibraryRepository from LibraryThe DIP move. Separate, so the reviewer can check no assertion changed.
9refactor: extract LibraryConsole from MainPure extraction. Claim “golden master unchanged”.
10fix: handle menu input that is not a numberBehaviour change, so not folded into commit 9.
11feat: search by author as well as titleBonus. Clearly marked as beyond the required tier.
12feat: sort the book list by title or authorBonus.
13docs: document the approach, decisions and deviationsThe README. Last, so it describes what actually exists.
Rules for this log

Do this now

Task · 15 minutes · tidy the history before you package

The git init and baseline commit are at the end of Lesson 0001, and each lesson since has ended with its own commit block. So by now you have a history. This task is about reading it as an assessor would, and fixing what is wrong with it.

1. Read it end to end, subjects only:

cd ~/Desktop/java-guild-candidate-test-intermediate-main
git log --oneline

2. Check each commit is one logical change:

git log --stat | head -80

A commit touching Library.java, Main.java and a test for one defect is fine. One touching eight unrelated files is two commits wearing a trenchcoat.

3. Squash any slips. If commit 6 fixes a typo from commit 5, fold it in. Nobody has ever pulled this repository, so rewriting it is proofreading, not vandalism:

git rebase -i --root

In the editor that opens, change pick to fixup on the line for the typo-fix commit, save and close. It disappears into the commit above it. Then confirm nothing broke:

~/.local/maven/bin/mvn clean test
git log --oneline

4. Spot-check that older commits still build. An assessor may check out any of them:

git stash list                      # should be empty
git checkout HEAD~4
~/.local/maven/bin/mvn clean test   # expect BUILD SUCCESS
git checkout main

5. Confirm nothing unwanted is tracked:

git ls-files | grep -E 'target/|\.idea/|libraryState|received' && \
  echo "^^ PROBLEM - remove these" || echo "clean"

If anything shows up, remove it from tracking but keep it on disk:

git rm -r --cached target .idea 2>/dev/null || true
git commit -m "chore: stop tracking build output and IDE files"

Drill

Why commit the unmodified starter before making any changes?

  1. Git refuses to compute a diff for files added in the initial repo commit
  2. It makes every later change legible as a diff from what you were handed
  3. It preserves the original authorship metadata for the code Solirius wrote
  4. It provides a commit to revert to should the later refactoring go wrong

Git diffs the root commit fine, against the empty tree. Not a constraint.

This is the whole reason. Without it, the assessor sees one commit containing both their code and yours and cannot tell them apart. With it, git diff HEAD~12 is precisely and only your work — which is what they are marking.

Authorship is recorded as you, since you are making the commit. It preserves the code, not the credit — and your README should say the starter was supplied.

A genuine benefit, but a minor one. You would have that from any commit; the value here is specifically about the boundary between their work and yours.

Which belongs in a commit message body?

  1. A list naming each file that this particular commit has modified or added
  2. A summary of the algorithm the new code uses, written out step by step
  3. The problem that existed beforehand and why this approach was chosen
  4. A note of how long the change took and which parts proved most difficult

git show --stat already prints that, accurately and for free. Restating it by hand just creates something that can go stale.

The diff is the algorithm, and it cannot drift out of date. Prose describing how the code works belongs in the code, as a comment or a javadoc.

This is the content only you can supply. The code shows what it does now; nothing in the repository records what was wrong before or which alternatives you rejected. That reasoning is exactly what “the approach taken” means.

Interesting to you, irrelevant to a reviewer, and it invites a judgement about your speed that you do not want to raise.

You realise commit 5 contained a typo you fixed in commit 6. What should you do before submitting?

  1. Rebase interactively to squash the fix into the commit that introduced it
  2. Leave both, since rewriting history is bad practice on any git repository
  3. Add a further commit explaining that commit 6 corrects a slip in commit 5
  4. Reset back to commit 4 and redo the remaining work as a single clean commit

Right. The taboo on rewriting history applies to published branches other people have pulled. Nobody has ever seen this repository. Tidying it before it is read is the same courtesy as proofreading — and git rebase -i with fixup is exactly the tool.

The rule is “don’t rewrite shared history”, not “never rewrite history”. Reciting the shorter version here leaves noise in the artifact being marked.

Now three commits are involved in one small mistake, and you have drawn attention to it. Worse than either alternative.

This throws away the story. The commit-by-commit narrative is a large part of the value; collapsing it to protect one typo trades the asset for the blemish.

Read this

Primary source

Chris Beams — “How to Write a Git Commit Message”. Fifteen minutes, and the highest ratio of value to length on the whole reading list. Read the worked examples at the end, not just the seven rules.

Carry on

Paste me your git log before you package and I will read it the way an assessor will — top to bottom, without opening a single diff, and tell you whether the story holds together.