mirror of
				https://github.com/ItsDrike/itsdrike.com.git
				synced 2025-11-03 19:56:36 +00:00 
			
		
		
		
	Add post about making great commits
This commit is contained in:
		
							parent
							
								
									43615f816c
								
							
						
					
					
						commit
						52fdfc5c2a
					
				
					 1 changed files with 417 additions and 0 deletions
				
			
		
							
								
								
									
										417
									
								
								content/posts/great-commits.md
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										417
									
								
								content/posts/great-commits.md
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,417 @@
 | 
			
		|||
---
 | 
			
		||||
title: Making great commits
 | 
			
		||||
date: 2023-04-17
 | 
			
		||||
tags: [programming, git]
 | 
			
		||||
sources:
 | 
			
		||||
  - <https://chris.beams.io/posts/git-commit/>
 | 
			
		||||
  - <https://dhwthompson.com/2019/my-favourite-git-commit>
 | 
			
		||||
  - <https://dev.to/samuelfaure/how-atomic-git-commits-dramatically-increased-my-productivity-and-will-increase-yours-too-4a84>
 | 
			
		||||
  - <https://thoughtbot.com/blog/5-useful-tips-for-a-better-commit-message>
 | 
			
		||||
  - <https://tbaggery.com/2008/04/19/a-note-about-git-commit-messages.html>
 | 
			
		||||
---
 | 
			
		||||
 | 
			
		||||
A well-structured git log is key to project's maintainability; it provides insight into when and why things were done,
 | 
			
		||||
for future maintainers of the project, ... and yet, so many people pay very little attention to how their commits are
 | 
			
		||||
structured.
 | 
			
		||||
 | 
			
		||||
The problem isn't necessarily that they don't even attempt to write good commit messages, it's that the commit they
 | 
			
		||||
made is not actually easy to compose a commit message for.
 | 
			
		||||
 | 
			
		||||
Another, perhaps even bigger issue is that a lot of people don't even know that there's a reason to care about their
 | 
			
		||||
git history, because they simply don't see a benefit in it. The problem with this argument is that these people have
 | 
			
		||||
simple never explored git enough, and therefore aren't even familiar with the benefits they could gain.
 | 
			
		||||
 | 
			
		||||
So then, in this post, I'll try to explain both what are the benefits that you can get, and how to make your commits
 | 
			
		||||
clean and easy to read and find in git history later on.
 | 
			
		||||
 | 
			
		||||
## Commit message
 | 
			
		||||
 | 
			
		||||
The purpose of every commit is always to simply represent some change that was made in the source code.
 | 
			
		||||
 | 
			
		||||
The commit message should then describe this change, however what many people get wrong is that they just state
 | 
			
		||||
**what** was changed, without explaining **why** it was changed. There is always a reason for why a change is made, and
 | 
			
		||||
while the contents of the commit (being the actual changes made in the code - diff) can tell you what was done, the
 | 
			
		||||
only way to figure out why it was done, is through the commit message.
 | 
			
		||||
 | 
			
		||||
Therefore, when thinking of a good commit message, you should always ask yourself not just "What does this commit
 | 
			
		||||
change?", but also, and perhaps more importantly, ask "Why is this change necessary?" and "What does this change
 | 
			
		||||
achieve?".
 | 
			
		||||
 | 
			
		||||
Knowing why something was added can then be incredibly beneficial for someone looking at `git blame`, which allows you
 | 
			
		||||
to find out the commit that was responsible for adding/modifying any particular line. In vast majority of cases, when
 | 
			
		||||
you look at git blame, you're not interested in what that single line of code is doing, but rather why it's even there.
 | 
			
		||||
 | 
			
		||||
Without having this information in the commit itself, you'd likely have to go look for the actual pull request that
 | 
			
		||||
added that commit, and read it's description, which might not even contain that reason anyway.
 | 
			
		||||
 | 
			
		||||
### Commit isn't just the first line
 | 
			
		||||
 | 
			
		||||
A huge amount of people are used to committing changes with a simple `git commit -m "My message"`, and while this is
 | 
			
		||||
enough and it's perfectly in many cases, sometimes you just need more space to describe what a change truly achieves.
 | 
			
		||||
 | 
			
		||||
Surprisingly, many people don't even know that they can make a commit that has more in it's message than just the
 | 
			
		||||
title/first line, which then leads to poorly documented changes, because single line sometimes simply isn't enough. To
 | 
			
		||||
create a commit with a bigger commit message, you can simply run `git commit` without the `-m` argument. This should
 | 
			
		||||
open your terminal text editor, allowing you to write out the message in multiple lines.
 | 
			
		||||
 | 
			
		||||
{{< notice tip >}}
 | 
			
		||||
I'd actually recommend making the simple `git commit` the default way you make new commits, since it invites you to
 | 
			
		||||
write more about it, by just seeing that you have that space available. We usually don't even know what exactly we'll
 | 
			
		||||
write in our new commit message before getting to typing it out, and knowing you have that extra space if you need it
 | 
			
		||||
will naturally lead to using it, even if you didn't know you needed it ahead of time.
 | 
			
		||||
{{< /notice >}}
 | 
			
		||||
 | 
			
		||||
That said, not every commit requires both a subject and a body, sometimes a single line is fine, especially when the
 | 
			
		||||
change is so simple that no further context is necessary, and including some would just waste the readers time. For
 | 
			
		||||
example:
 | 
			
		||||
 | 
			
		||||
```markdown
 | 
			
		||||
Fix typo in README
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
In this case, there's no need for anything extra. Some people like to include what the typo was, but if you want to know
 | 
			
		||||
that, you can use `git show` or `git diff`, or `git log --patch`, showing you the actual changes made to the code, so
 | 
			
		||||
this information isn't necessary either. So, while in some cases, having extra context can be very valuable, you also
 | 
			
		||||
shouldn't overdo it.
 | 
			
		||||
 | 
			
		||||
### Make commits searchable
 | 
			
		||||
 | 
			
		||||
It can be very beneficial to include some keywords that people could then easily find this commit by, when searching
 | 
			
		||||
for changes in the codebase. As an example, you can include the name of an exception, such as `InvalidDataStreamError`,
 | 
			
		||||
if your commit addresses a bug that causes this exception.
 | 
			
		||||
 | 
			
		||||
You can then add an explanation on why this error was getting raised, and why your change fixed that. With that, anyone
 | 
			
		||||
who found your commit by searching for this exception can immediately find out what this exception is, why was it
 | 
			
		||||
getting raised and what to do to fix it.
 | 
			
		||||
 | 
			
		||||
This is especially useful with internal API, whether it's custom exceptions, or just functions or names of classes.
 | 
			
		||||
People don't search the commit history very often, but if you do encounter a case where you think someone might perform
 | 
			
		||||
a search for at some point, it's worth it to make it as easy for them as you can.
 | 
			
		||||
 | 
			
		||||
### Make it exciting to read
 | 
			
		||||
 | 
			
		||||
I sometimes find myself going through random commit messages of a project, just to see what is the development like,
 | 
			
		||||
and explore what are the kinds of changes being introduced. Even more often, I look there to quickly see what was
 | 
			
		||||
changed, to bring myself up to date with the project.
 | 
			
		||||
 | 
			
		||||
When doing this, I'm always super thankful to people who took the time to for example include the debug process of how
 | 
			
		||||
they figured out X was an issue, or where they explain some strange behavior that you might not expect to be happening.
 | 
			
		||||
 | 
			
		||||
These kinds of commits make the history a fun place to go and read, and it allows you to teach someone something about
 | 
			
		||||
the language, the project, or programming in general, making everyone in your team a bit smarter!
 | 
			
		||||
 | 
			
		||||
### Follow the proper message structure
 | 
			
		||||
 | 
			
		||||
Git commits should be written in a very specific way. There's a few rules to follow:
 | 
			
		||||
 | 
			
		||||
- **Separate the subject/title from body with a blank line** (Especially useful when looking at `git log --oneline`,
 | 
			
		||||
  as without the blank line, lines below are considered as parts of the same paragraph, and shown together)
 | 
			
		||||
- **Limit the subject line to 50 characters** (Not a hard limit, but try not going that much longer. This limit ensures
 | 
			
		||||
  readability, and forces the author to think about the most concise way to explain what's going on. Note: If you're
 | 
			
		||||
  having trouble summarizing, you might be committing too much at once)
 | 
			
		||||
- **Capitalize the subject line**
 | 
			
		||||
- **Don't end the subject line with a period**
 | 
			
		||||
- \*Use imperative mood in subject\*\* (Imperative mood means "written as if giving a command/instruction" i.e.: "Add
 | 
			
		||||
  support for X", not "I added support for X" or "Support for X was added", as a rule of thumb, a subject message
 | 
			
		||||
  should be able to complete the sentence: "If implemented, this commit will ...")
 | 
			
		||||
- **Wrap body at 72 characters** (We usually use `git log` to print out the commits into the terminal, but it's output
 | 
			
		||||
  isn't wrapped, and going over the terminals width can cause a pretty messy output. The recommended maximum width for
 | 
			
		||||
  terminal text output is 80 characters, but git tools can often add indents, so 72 characters is a pretty sensible
 | 
			
		||||
  maximum)
 | 
			
		||||
- **Mention the "what" and the "why", but not the "how"** (A commit message shouldn't contain implementation details,
 | 
			
		||||
  if people want to see those, whey should look at the changed code diff directly)
 | 
			
		||||
 | 
			
		||||
If you want to, you can consider using markdown in your commit message, as most other programmers will understand it as
 | 
			
		||||
it's a commonly used format, and it's a great way to bring in some more style, improving readability. In fact, if you
 | 
			
		||||
view the commit from a site like GitHub, it will even render the markdown properly for you.
 | 
			
		||||
 | 
			
		||||
For example:
 | 
			
		||||
 | 
			
		||||
```markdown
 | 
			
		||||
Summarize changes in around 50 characters or less
 | 
			
		||||
 | 
			
		||||
More detailed explanatory text, if necessary. Wrap it to about 72
 | 
			
		||||
characters or so. In some contexts, the first line is treated as the
 | 
			
		||||
subject of the commit and the rest of the text as the body. The
 | 
			
		||||
blank line separating the summary from the body is critical (unless
 | 
			
		||||
you omit the body entirely); various tools like `log`, `shortlog`
 | 
			
		||||
and `rebase` can get confused if you run the two together.
 | 
			
		||||
 | 
			
		||||
Explain the problem that this commit is solving. Focus on why you
 | 
			
		||||
are making this change as opposed to how (the code explains that).
 | 
			
		||||
Are there side effects or other unintuitive consequences of this
 | 
			
		||||
change? Here's the place to explain them.
 | 
			
		||||
 | 
			
		||||
Further paragraphs come after blank lines.
 | 
			
		||||
 | 
			
		||||
- Bullet points are okay, too
 | 
			
		||||
 | 
			
		||||
- Typically a hyphen or asterisk is used for the bullet, preceded
 | 
			
		||||
  by a single space, with blank lines in between, but conventions
 | 
			
		||||
  vary here
 | 
			
		||||
 | 
			
		||||
If you use an issue tracker, put references to them at the bottom,
 | 
			
		||||
like this:
 | 
			
		||||
 | 
			
		||||
Resolves: #123
 | 
			
		||||
See also: #456, #789
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Make "atomic" commits
 | 
			
		||||
 | 
			
		||||
_Atomic: of or forming a single irreducible unit or component in a larger system._
 | 
			
		||||
 | 
			
		||||
The term "atomic commit" means that the commit is only representing a single change, that can't be further reduced into
 | 
			
		||||
multiple commits, i.e. this commit only handles a single change. Ideally, it should be possible to sum up the changes
 | 
			
		||||
that a good commit makes in a single sentence.
 | 
			
		||||
 | 
			
		||||
That said, the irreducibility should only apply to the change itself, obviously, making a commit for every line of code
 | 
			
		||||
wouldn't be very clean. Having a commit only change a small amount of code isn't what makes it atomic. While the commit
 | 
			
		||||
certainly can be small, it can just as well be a commit that's changing thousands of lines. (That said, you should have
 | 
			
		||||
some really good justification for it if you're actually making commits that big.)
 | 
			
		||||
 | 
			
		||||
The important thing is that the commit is only responsible for addressing a single change. A counter-example would be
 | 
			
		||||
a commit that adds a new feature, but also fixes a bug you found while implementing this feature, and also improves the
 | 
			
		||||
formatting of some other function, that you encountered along the way. With atomic commits, all of these actions would
 | 
			
		||||
get their own standalone commits, as they're unrelated to each other, and describe several different changes.
 | 
			
		||||
 | 
			
		||||
But making atomic commits aren't just about splitting thins up to only represent single changes, indeed, while they
 | 
			
		||||
should only represent the smallest possible change, it should also be a "complete" change. This means that a commit
 | 
			
		||||
responsible for changing how some function works in order to improve performance should ideally also update the
 | 
			
		||||
documentation, make the necessary adjustments to unit-tests so they still pass, and update all of the references to
 | 
			
		||||
this updated function to work properly after this change.
 | 
			
		||||
 | 
			
		||||
So an atomic commit is a commit representing a single small (ideally an irreducible) change, that's fully implemented
 | 
			
		||||
and integrates well with the rest of the codebase.
 | 
			
		||||
 | 
			
		||||
### Partial adds
 | 
			
		||||
 | 
			
		||||
Many people tend to always simply use `git add -A` (or `git add .`), to stage all of the changes they made, and then
 | 
			
		||||
create a commit with it all.
 | 
			
		||||
 | 
			
		||||
In an ideal world, where you only made the changes you needed to make for this single atomic commit, this would work
 | 
			
		||||
pretty well, and while sometimes this is the case, in most cases, you will likely have say fixed some bug you found
 | 
			
		||||
alongside, or a typo you noticed, etc.
 | 
			
		||||
 | 
			
		||||
When that happens, you should know that you can instead make a partial add, and only stage the changes that belong into
 | 
			
		||||
the commit you're about to make. The simple case is when you have some unrelated changes, but they're all in different
 | 
			
		||||
files, and don't affect this commit. In that case, you can use `git add /path/to/file`, to only stage those files that
 | 
			
		||||
you need, leaving the unrelated ones alone.
 | 
			
		||||
 | 
			
		||||
But this is rarely the case, instead, you usually have a single file, that now contains both a new feature, and some
 | 
			
		||||
unrelated quick bugfix. In that case, you can use the `-p`/`--patch` flag: `git add -p /path/to/file`. This will let you
 | 
			
		||||
interactively go over every "hunk" (a chunk of code, with changes close to each other), and decide on whether to accept
 | 
			
		||||
it (hence staging it), split it into more chunks, skip it, or even modify it in your editor, allowing you to remove the
 | 
			
		||||
intertwined code for the bugfix from the code for your feature that you're committing now.
 | 
			
		||||
 | 
			
		||||
You can then make the feature commit, that only contains the changes related to it, and then create another commit, that
 | 
			
		||||
only contains the bugfix related changes.
 | 
			
		||||
 | 
			
		||||
This git feature has slowly became one of my favorite tools, and I use it almost every time I need to commit something,
 | 
			
		||||
as it also allows me to quickly review the changes I'm making, before they make it into a commit, so it can certainly be
 | 
			
		||||
worth using, even if you know you want to commit the entire file.
 | 
			
		||||
 | 
			
		||||
## Stop making fixing commits
 | 
			
		||||
 | 
			
		||||
A very common occurrence I see in a ton of different projects is people making sequences of commits that go like:
 | 
			
		||||
 | 
			
		||||
- Fix bug X
 | 
			
		||||
- Actually fix bug X
 | 
			
		||||
- Fix typo in variable name
 | 
			
		||||
- Sort imports
 | 
			
		||||
- Follow lint rules
 | 
			
		||||
- Run auto-formatter
 | 
			
		||||
 | 
			
		||||
While people can obviously mess up sometimes, and just not get something right on the first try, a fixing commit like
 | 
			
		||||
this is actually not the only way to solve this happening.
 | 
			
		||||
 | 
			
		||||
Instead of making a new commit, you can actually just amend the original. To do this, we can use the `git commit
 | 
			
		||||
--amned`, which will add your staged changes into the previous commit, even allowing you to change the message of that
 | 
			
		||||
old commit.
 | 
			
		||||
 | 
			
		||||
Not only that, if you've already made another commit, but now found something that needs changing in the commit before
 | 
			
		||||
that, you can use interactive rebase with `git rebase -i HEAD~3`, allowing you to change the last 3 commits, or even
 | 
			
		||||
completely remove some of those commits.
 | 
			
		||||
 | 
			
		||||
For more on history rewriting, I'd recommend checking the [official
 | 
			
		||||
documentation](https://git-scm.com/book/en/v2/Git-Tools-Rewriting-History)
 | 
			
		||||
 | 
			
		||||
### Force pushing
 | 
			
		||||
 | 
			
		||||
{{< notice warning >}}
 | 
			
		||||
Changing history is a great tool to clean up after yourself, it works best with local changes, i.e. with changes you
 | 
			
		||||
haven't yet pushed.
 | 
			
		||||
 | 
			
		||||
Even though changing already pushed history is possible, it requires a "force push" (`git push --force`). These kinds of
 | 
			
		||||
pushes are something you need to be very careful about, as someone might have already pulled the changes, which you then
 | 
			
		||||
overwritten with your force push. Now, they might've done some work from the point at which they pulled, but then they
 | 
			
		||||
find out that this point is actually gone from the history, and they can't push their changes back. So now, they'll need
 | 
			
		||||
to undo their changes, pull the force pushed branch, and carry the work over, which can be very annoying.
 | 
			
		||||
{{< /notice >}}
 | 
			
		||||
 | 
			
		||||
My recommendation to avoid force pushing is to reduce the amount of (regular) pushes you do completely. If your changes
 | 
			
		||||
are only local, rewriting history is easy, and won't break anyone else's workflow, but the moment you push, the changes
 | 
			
		||||
are public, and anyone might've pulled them already.
 | 
			
		||||
 | 
			
		||||
This especially applies when you're pushing directly to master/main branch, or other shared branch which multiple people
 | 
			
		||||
are working with. If this is your personal branch (like a feature branch you're responsible for), force-pushing there is
 | 
			
		||||
generally ok, though you might still have people using your branch since they wanted to try out a feature early, or
 | 
			
		||||
review the changes from their editor. So even with personal branches, it's not always safe to force-push.
 | 
			
		||||
 | 
			
		||||
My rule of thumb is to avoid pushing until the feature is fully complete, as that allows you to change anything during
 | 
			
		||||
the development. Perhaps some change you made no longer makes sense, because you realized you won't actually be using it
 | 
			
		||||
in the way you anticipated, or you found a bug with it later on. You can now simply rewrite your local history, and
 | 
			
		||||
rather than making a fixing commit, it'd be as if the bug was never there.
 | 
			
		||||
 | 
			
		||||
Once you do finally decide to push, it's a good practice to run any auto-formatters and linters, and perhaps even
 | 
			
		||||
unit-tests. You can also take a quick peek at `git log`, to make sure you didn't make any typos. Then, only if all of
 | 
			
		||||
those local toolings passed should you actually push your version.
 | 
			
		||||
 | 
			
		||||
{{< notice tip >}}
 | 
			
		||||
If you do need to force-push, try to at least do it as quickly as possible. The more time that has passed since your
 | 
			
		||||
normal push, the more likely it is that someone have already clonned/pulled those changes. If you force-push within just
 | 
			
		||||
a few seconds after pushing, it's not very likely that someone has pulled already, and so you won't break anyone's
 | 
			
		||||
version.
 | 
			
		||||
{{< /notice >}}
 | 
			
		||||
 | 
			
		||||
## Benefits
 | 
			
		||||
 | 
			
		||||
Alright, now that we've seen some of the best practices for making new commits, let's explore the benefits that we can
 | 
			
		||||
actually gain by following these.
 | 
			
		||||
 | 
			
		||||
### A generally improved development workflow
 | 
			
		||||
 | 
			
		||||
I can confidently say, that in my experience, learning to make good git commits made me a much better programmer
 | 
			
		||||
overall. That might sound surprising, but it's really true.
 | 
			
		||||
 | 
			
		||||
The reason for this is that making good commits, that only tackle one issue at a time naturally helps you to think about
 | 
			
		||||
how to split your problem up into several smaller "atomic" problems, and make commits addressing that single part, after
 | 
			
		||||
which you move to another. This is actually one of very well known approaches to problem-solving, called "divide and
 | 
			
		||||
conquer" method, because you divide your problem into really small, trivially simple chunks, which you solve one by one.
 | 
			
		||||
 | 
			
		||||
Learning and getting used to doing this just makes you better at problem solving in general, and while git commits
 | 
			
		||||
certainly aren't the only way to get yourself to think like this, it's honestly one of the simplest ones, and you become
 | 
			
		||||
good at git while at it!
 | 
			
		||||
 | 
			
		||||
### Finding a tricky bug
 | 
			
		||||
 | 
			
		||||
Imagine you've just came up with a new feature that you're really eager to implement for your project. So, the moment
 | 
			
		||||
you think of how to do it, you start working on it. Then, a good bit of work, you're finally done, entirely. You now
 | 
			
		||||
make a commit, with all of the changes.
 | 
			
		||||
 | 
			
		||||
However, you now realize that as you pushed your commit to your repo, the automated CI workflows start to fail on some
 | 
			
		||||
unit-tests. Turns out you didn't think of some edge-case, some part of your solution is suddenly affecting something
 | 
			
		||||
completely unrelated. As you attempt to fix it, more and more other issues arise, and you don't really even know where
 | 
			
		||||
to start. You have this big single diff for the entire feature, but you have no idea where in that is the bug.
 | 
			
		||||
 | 
			
		||||
Figuring it out takes at best a lot of mental effort, analyzing and keeping track with all of the changes at once, or at
 | 
			
		||||
worst, you'll spend a lot of time doing this, but you'll just keep getting lost in your own code, until you finally just
 | 
			
		||||
give up, and start over. This time, only doing small changes at a time, and running the unit-tests
 | 
			
		||||
for each one as you go.
 | 
			
		||||
 | 
			
		||||
#### Same scenario, but with atomic commits
 | 
			
		||||
 | 
			
		||||
Now, let's consider the same scenario, but this time, you're following the best git principles, and so you're splitting
 | 
			
		||||
the problem up and making atomic commits for each of necessary changes, that will together make up the feature.
 | 
			
		||||
 | 
			
		||||
Once you're done, you decide to push all of those commits, and see the CI fail. However this time, you have a much
 | 
			
		||||
eaiser time finding where that pesky bug hides. Why? Because this time, you can just checkout one of those commits you
 | 
			
		||||
divided your bigger task into, and run the tests there. If it fails, you can run the tests in the commit before that.
 | 
			
		||||
You can just repeat this until you find the exact commit that caused these failures.
 | 
			
		||||
 | 
			
		||||
At this point, you know exactly which change caused this, because the commit you discovered was pretty small, it only
 | 
			
		||||
changed a few dozen lines and introduced a very specific behavior, in which after looking at it for a while, you find
 | 
			
		||||
that there's indeed a completely unexpected fault, which you only found out because you knew exactly where to look.
 | 
			
		||||
 | 
			
		||||
#### Git bisect
 | 
			
		||||
 | 
			
		||||
This scenario is actually very common and can come up a lot while developing, because of that, git actually has an
 | 
			
		||||
amazing tool that can make this process even easier! This tool is called `git bisect`.
 | 
			
		||||
 | 
			
		||||
Essentially, you can give git bisect a specific start commit, where you know everything worked as it should've, and an
 | 
			
		||||
end commit, where you know the fault exists somewhere. Git will automatically check out the commits in between in the
 | 
			
		||||
most optimal way (binary search), and all you have to do is then check whether the issue exists in the checked out
 | 
			
		||||
commit, or not. If it does, you tell bisect that this commit is still faulty, or if not, you say it's good.
 | 
			
		||||
 | 
			
		||||
Since bisect is essentially a binary search, it won't take too many attempts to figure out exactly which commit is the
 | 
			
		||||
faulty one, essentially automating the process above. Better yet, if the task of finding the bug can be uncovered by
 | 
			
		||||
simply running some script/command (perhaps the unit tests suite), you can actually just specify that command when using
 | 
			
		||||
git bisect, and it'll do all of the work for you, running that command on each of those check outs, and depending on
 | 
			
		||||
it's exit code, if the command passed, marking the commit as good, or if not, marking it as faulty.
 | 
			
		||||
 | 
			
		||||
So, even if the test suite takes a while, you can actually just have git find the bug for you, while you take a break
 | 
			
		||||
and make a nice cup of coffee.
 | 
			
		||||
 | 
			
		||||
### Git blame
 | 
			
		||||
 | 
			
		||||
Git blame is a tool that allows you to look at a file, and see exactly which lines were committed by who, and in which
 | 
			
		||||
commit. This can be very useful if you just want to check what that line was added there for. If it's a part of a larger
 | 
			
		||||
spanning commit, you can then check the diff of that commit, to see why that line was relevant, with the context of the
 | 
			
		||||
rest of the changes done.
 | 
			
		||||
 | 
			
		||||
Having good commit history and using atomic commits makes doing this a great and easy experience, as you're not very
 | 
			
		||||
likely to find that commit to be addressing 10 different issues at once, without providing any real description in the
 | 
			
		||||
commit message, as to why, and perhaps not even as to what it's doing. With commits like those, git blame becomes almost
 | 
			
		||||
useless, but if you do follow these best practices, it can be a great tool for understanding why anything in the code is
 | 
			
		||||
where it is, without needing to check the documentation, if there even is any.
 | 
			
		||||
 | 
			
		||||
### Cherry picking
 | 
			
		||||
 | 
			
		||||
Cherry picking is the process of taking a commit (or multiple commits), and carrying them over (essentially
 | 
			
		||||
copying/transferring them) to another branch, or just another point. So for example, you might have a feature branch, in
 | 
			
		||||
which you fixed a bug that also affects the current release. Instead of checking out the release branch, and re-doing
 | 
			
		||||
the changes there, you can actually use cherry-picking to carry the commit from the feature branch into the release
 | 
			
		||||
branch. This will mean any changes made in that commit will be applied, fixing the bug in release branch and allowing
 | 
			
		||||
you to make a release.
 | 
			
		||||
 | 
			
		||||
However, if the commit that fixed this issue wasn't atomic, and it also contained fixes for tons of other things, or
 | 
			
		||||
worse off, includes logic for additional features, you can't just carry it over like this, as you'd be introducing other
 | 
			
		||||
things into the release branch which aren't supposed to be there (yet). So instead, you'd have to make the changes in
 | 
			
		||||
the branch yourself, and create another commit, which is simply slower.
 | 
			
		||||
 | 
			
		||||
### Pull request reviews
 | 
			
		||||
 | 
			
		||||
When someone else is reviewing your pull request, having clean commits can be incredibly helpful to the reviewer, as
 | 
			
		||||
they can go through the individual commits instead of reviewing all of the changes at once by looking at the full diff
 | 
			
		||||
compared to the branch you're merging to. This alone can greatly reduce the mental overhead of having to keep track of
 | 
			
		||||
all of the added/changed code, and knowing how it interacts with the rest of the changes.
 | 
			
		||||
 | 
			
		||||
Atomic commits then allow for the reviewer to understand each and every atomic change you made, one by one, which is
 | 
			
		||||
much easier to grasp. So even if when put together, the code is pretty complex, in these atomic chunks, it's actually
 | 
			
		||||
pretty easy to see what's going on, and why. This is especially the case if these commits include great descriptions of
 | 
			
		||||
what it is they're addressing exactly.
 | 
			
		||||
 | 
			
		||||
This then doesn't just apply for pull-requests, this kind of workflow can actually be useful to anyone looking over some
 | 
			
		||||
code in a file. You could use git blame to find out the commit, and follow the parent commits up, allowing you to see
 | 
			
		||||
the individual changes as they were done one by one, which again, is then easier to understand, and allows you to then
 | 
			
		||||
realize what the whole file is about much quicker.
 | 
			
		||||
 | 
			
		||||
### Easy reverts
 | 
			
		||||
 | 
			
		||||
Sometimes, we might realize that a change that we made a while ago should not actually have been made, but the change
 | 
			
		||||
was already pushed and there's a lot of commits after it. That means at this point, we can't simply rewrite the history,
 | 
			
		||||
and we will need to push a commit that undoes that change.
 | 
			
		||||
 | 
			
		||||
The great advantage of atomic commits is that they should include the entire change, along with documentation it
 | 
			
		||||
introduces, tests, etc. in a single piece, a single commit. Because of that, assuming there weren't any commits that
 | 
			
		||||
built upon this change later on, we can use git's amazing `git revert` command.
 | 
			
		||||
 | 
			
		||||
This will create a new commit that undoes everything another specified commit did, making it very easy to revert some
 | 
			
		||||
specific change, while leaving everything else alone. This is much faster and easier than having to look at what the
 | 
			
		||||
original commit changed line by line, and change it back ourselves, and while this isn't something you'll use all that
 | 
			
		||||
often, when you do get a chance to use it, it's really nice and can be a good time saver.
 | 
			
		||||
 | 
			
		||||
## Conclusion
 | 
			
		||||
 | 
			
		||||
Git is something programmers use every day, learning how to do so properly is invaluable. There's a lot of rules I
 | 
			
		||||
mentioned here, and of course, you probably won't be able to just start doing all of them at once. But I would encourage
 | 
			
		||||
you to at least stop for a while before every commit you're about to make, and think of whether you really need to stage
 | 
			
		||||
all of the files, or if you should do a partial add, and make multiple commits instead, and also take a while to think
 | 
			
		||||
of a good commit message.
 | 
			
		||||
 | 
			
		||||
For motivation, here's a quick recap of the most important benefits a good git workflow gives you:
 | 
			
		||||
 | 
			
		||||
- Your development workflow becomes easier by allowing you to find issues a lot quicker
 | 
			
		||||
- You can also help your team or whoever ends up reading your commits understand what's going on and bring them up to date with the project
 | 
			
		||||
- You will be able to quickly find out who committed something and why
 | 
			
		||||
- Your overall programming skills will improve, because you'll get used to dividing up your problems naturally
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue