This commit notation allows developers to convey 2 critical pieces of metadata about each commit:
- How risky is it? What has the original author done to mitigate risk?
- What was the intention? When the original author changed the code, what was s/he attempting to accomplish?
This information is conveyed in the first 3 characters of the commit summary line. That way a receiving developer can quickly scan the commit log in order to determine risk and intent for any incoming change set.
This is particularly useful when:
- Deciding whether to approve a pull request.
- Reading
main
— just the pull request commit summaries to understand the history of changes for a release.
Risk Level | Code | Example | Meaning |
---|---|---|---|
Known safe | lowercase letter | r Extract method Applesauce |
Addresses all known and unknown risks. |
Validated | uppercase letter | R Extract method Applesauce |
Addresses all known risks. |
Risky | uppercase followed by 2 bangs | R!! Extract method Applesauce |
Known risks remain unverified. |
(Probably) Broken | uppercase followed by 2 stars | R** Start extracting method with no name |
No risk attestation. |
- Known safe: Developer performed the task in a way that prevents the potential risks, even for situations that developer is not aware of.
- Validated: Developer performed the task in some way that includes validation for all risks the developer thought of. The most common technique is developer-written automated tests.
- Risky: Developer is aware of risks and attempted to mitigate them as much as possible, but there is no formal verification. Commonly this includes a manual change that the developer could not fully verify.
- Broken: Either known to be broken, or developer couldn't even check to see if it works. May not compile. Used when the developer cannot see the results of the work without checking in, or as a savepoint when the developer is about to switch tasks or direction.
These developer intentions exist on every project. They are always allowed in commits that use this notation.
Each intention can appear at any of the 4 risk levels. Each intention's full details section includes the potential risks inherent in that kind of change, as well as common approaches to attain each risk level.
Prefix | Name | Intention |
---|---|---|
F | Feature | Change or extend one aspect of program behavior without altering others. |
B | Bugfix | Repair one existing, undesirable program behavior without altering any others. |
R | Refactoring | Change implementation without changing program behavior. |
D | Documentation | Change something which communicates to team members and does not impact program behavior. |
Known Risks
- May alter unrelated feature (spooky action at a distance).
- May alter a piece of this feature that you intended to remain unchanged.
- May implement the intended change in a way different than intended.
Code | Known Approaches |
---|---|
f |
None known |
F |
Meets all of:
|
F!! |
Change includes unit tests for new behavior. |
F** |
No automatic tests, or unfinished implementation. |
A bugfix is a lot like a feature. However, the intention is to change an undesired — and usually unintentional — behavior of the current system. The risk profile is similar but the intention is different, so there are often more operational risks.
Known Risks
- Intended change may have unintended consequences in the market. For example, customers may be depending on the bug.
- May alter unrelated feature (spooky action at a distance).
- May alter a piece of this feature that you intended to remain unchanged.
- May implement the intended change in a way different than intended.
Code | Known Approaches |
---|---|
b |
None known |
B |
Meets all of:
|
B!! |
Change includes unit tests for new behavior. |
B** |
No automatic tests, or unfinished implementation. |
A Refactoring or Remodeling intends to alter the program in some way without changing any behavior. The risk levels indicate the probability of the commit living up to that intention, based on how the code change was executed.
Known Risks
- May cause a bug.
- May fix a bug.
- May change a behavior in a way that doesn't impact a user.
- May force a test update.
Code | Known Approaches |
---|---|
r |
One of: |
R |
Test-supported Procedural Refactoring3 |
R!! |
Identified single, named refactoring, but executed by editing code or without whole-project test coverage. |
R** |
Remodeled by editing code, even in small chunks. |
Changes that don't impact the code, but do change documentation around the code. Note that this does not include end-user documentation1.
Known Risks
- May mislead future developers.
- May mislead other stakeholders.
- May alter team processes in ways that have unintended consequences.
Code | Known Approaches |
---|---|
d |
Developer-visible documentation, not in a source file, or verified to generate byte-identical compilation. |
D |
Dev-impacting only, but changes compilation or process. E.g., changing text on a dev-only screen, or changes code-review checklist. |
D!! |
Alters an important process. |
D** |
Trying out a process change that is intended to gain info, not to work. |
Each project can define a set of extension intentions. Each project should define which extension codes it uses. It is up to each project to define the approaches for each of the 4 risk levels.
These are some common intentions, each used in several projects. Each also lists alternatives used in projects that don't use the code.
Prefix | Name | Intention | Alternatives |
---|---|---|---|
M | Merge | Merge branches | Use F , B , or R , based on the main intention of the branch, with risk level based on maximum for any individual commit in the branch. Optionally leave blank for merge from main to a feature branch. |
T | Test-only | Alter automated tests without altering functionality. May include code-generating code that just throws a NotImplementedException or similar approaches. |
Use f or b , depending on which kind of work this test is going to validate. Use r if this is a refactoring purely within test code. It is a lower-case letter unless you also change product code. |
E | Environment | Environment (non-code) changes that affect development setup, and other tooling changes that don't affect program behavior (e.g. linting) | Consider the environment to be a product where the users are team members, and code it accordingly. |
A | Auto | Automatic formatting, code generation, or similar tasks. | Use the intention that matches the reason you are performing the action, almost-certainly as a lower-case level of risk. For example, code cleanup would be r , and generating code to make a test for a new feature compile would be t or f . |
C | Comment | Changes comments only. Does not include comments that are visible to doc-generation tools. | Use D . |
P | Process | Changes some team process or working agreement. | Any of:
|
S | Spec | Changes the spec or design. Used when team does formal specs or design reviews and keeps all such documents in the main product source, perhaps in the product code itself. | Any of:
|
* | Unknown / multiple | Made a bunch of changes and are just getting it checked in. No real way to validate safety, and may not even compile. Usually used at the highest risk level (*** ). |
Don't allow this. Require each commit to do exactly one intention and document itself accordingly. |
If you can get a series of commits that is all lowercase commits, you can deploy without the need for Regression Testing, or lengthy conversations about accepting the pull request to trunk.
A provable refactoring requires a burden of proof. The main methods of proof are
- automated refactoring via tool, with knowledge of tool bugs.
- Scripted manual refactoring, using the compiler to verify each step. Recipes Here
With discipline these can prove bug-for-bug compatibility. They demonstrate safety for unknown bugs, even guaranteeing that you do not accidentally fix a bug you don't know exists (but your customers may be depending on).
All of these recipes use static analysis to demonstrate safety. As such, they work equally well on code that lacks tests. They can be a good way to make code testable. Their downside is that they are language-specific.
These are refactorings with a lower standard of proof:
- Commit contains only a single refactoring.
- Refactoring is named and published (e.g., in Fowler's refactoring catalog).
- Either: a) Your entire product is very highly tested, or b) you are working on new code that is not yet called.
- You followed the published recipe, including running full-suite test runs when indicated.
Note that this can not prove bug-for-bug compatibility. It can only demonstrate that you didn't cause any problems that have been thought of before; it does not demonstrate safety for novel bugs.
Requirement 3 is there because many refactorings can have non-local effects. It is not sufficient to have great tests on the code you are changing. You also need great tests on the code that you are not intending to change, to demonstrate that you didn't. Therefore, until your entire codebase is very highly tested, you will only be able to use the R
commit designation on new code that is uncalled by your product.
End user documentation is a feature, bugfix, or refactoring, depending on its nature. Use those codes (including levels of risk) accordingly.
Features and bug fixes intentionally change behavior. This makes them much riskier than refactorings. It is not possible to prove that they have only the intended effect. However, small changes are much lower risk for three reasons:
- It's only possible when the code is well-organized already.
- It's easy to see the possible side effects of small chunks of code.
- It's easy to code review, so you are likely to get good reviews.
Therefore, we treat any feature or bug fix as high risk if it changes more than 8 lines of code in one commit. This includes test changes.
One good approach to enable small features is to refactor until the feature change is easy, then add it. Then add the feature one piece at a time, with a test for each.
We invite you to submit pull requests to help evolve this notation and methodology.