Thinking of commits like code
The common advice for writing good code can be applied to crafting great commits:
This comes from the Unix philosophy. Your source code should be made up of components that each perform one task. Likewise your commits should accomplish one goal. This will make it easier to follow the history of changes in your repo.
Easy to delete
Good code has distinct boundaries between it and the rest of the repository. This makes it easy to delete. Good commits should be easy to revert if a bug pops up in production.
When looking at some code or a commit, someone shouldn't say "What is this doing? Why is this here?" They should tell a clear story and have purpose. When I review a commit I don't want to have to parse through every part of the diff to figure out why the commit is needed. Like source code, make it so people can quickly get the main idea and then dive into the details if they need to.
Your team is like a concurrent program with each teammate trying to mutate the shared resource of the main branch. When writing a commit assume that the code you are changing is also being changed by someone else.
It's easy to write automated tests for well structured code. Similarly, make your commits easily testable by the reviewers. Provide before and after expected states, pictures, etc.
When I jump into a new code base I start by reading all of the commits from the first to the most recent. Hopefully this tells a story about why this code exists and the changes that it has gone through, giving me a picture of it's trajectory today. As developers, a big part of our job is storytelling. Our commits, our comments, and our tests are all part of the story. Not just your source.