Keep your commits like you would keep your functions: small and responsible for one thing.
That way you will always be able to track what changed easily.
You avoid the need to open the commit scroll tens of changes in one file, go to the next one, scroll and so on.
Just go to previous commit, look at few changed lines and see what happened. Nothing suspicious with them? Continue browsing next commit.
You also avoid problems with commit comments. It’s easier to describe in a few words changes in a few lines than in ten of them. Thus you end up with readable history like:
displaying invoices [1 file] displaying costs [1 file] add date formatting library [1 file] fix formatting date in invoices [1 file] fix formatting date in costs [1 file]
WIP displaying costs and items [5 files]
It’s also easier to find a commit with error by binary search in git history:
1. Try finding the last commit where the bug didn’t exist, in example from a month before. Checkout this commit.
2. If the bug is there try the older one.
3. If there is no bug try a commit half way newer.
4. Repeat step 3. until you have found a commit with a problem.
That’s quite a cumbersome technique but I’ve found it quite quick and useful when there are no tests or if they don’t surface a problem straightaway: it’s an issue with a library, framework or configuration.