Git Conventions

Doom has adopted conventional commits (with a few minor tweaks). I summarize it here, temporarily, until our new documentation is up.

[SUBJECT]

[BODY]

[FILE-LOG]

[FOOTER]

Where [SUBJECT] is:

TYPE[!][(SCOPE)]: SUMMARY

As of 074b9eb0, use doom ci lint-commits HEAD~1 to lint your last commit locally.

TODO TL;DR examples

Subject

Types

The commit type indicates, at a glance, what kind of change was made. Here are the types our projects recognize:

  • All projects:

    • dev: for work on project infrastructure: build scripts, CI/CD config files (e.g. .github/*), ignore files, etc.

    • docs: changes to user-facing documentation such as module docs, manuals, function docstrings, and output from CLI commands like doom help or doom doctor. Also for core file templates, like init.example.el and core/templates/* files.

    • feat: code introducing a new feature or extends an existing one.

    • fix: Resolves a bug or misbehavior. Also applicable when fixing stale code that was missed during a recent bump.

    • nit: cosmetic changes to comments, formatting, or spelling/grammar with no effect on any behavior whatsoever.

    • perf: A refactor intent on improving startup/runtime performance.

    • refactor: code changes with little effect on user-facing behavior (unless paired with !–to indicate a breaking change–in which case it indicates the wholesale removal of code/features).

    • revert: Undoes a previous change. Must be followed by:

      • Doom modules or categories (reverting bumps)
      • Packages (reverting bumps)
      • The full subject of the reverted commit (and add Revert HASH to FOOTER).

      If you are reverting another commit in a same PR, please rebase the earlier commit out instead.

    • test: Changes to tests (but not testing infrastructure)

    • tweak: Code changes that changes defaults and user-facing behavior, but not drastically.

    • Types reserved for contributors:

      • release: TODO

      • merge: TODO

  • doomemacs/doomemacs:

    • bump: Package updates or code/doc changes intended solely to reflect changes upstream. Use revert: when downgrading packages. Must include bump commit diffs:

      bump: [:CATEGORY [MODULE...]|PACKAGE [PACKAGE...]|*]
      
      user/repo@a1b2c3da1b2c -> user/repo@z9y8x7wz9y8x
      user/repo@a1b2c3da1b2c -> user/repo@z9y8x7wz9y8x
      user/repo@a1b2c3da1b2c -> user/repo@z9y8x7wz9y8x
      
      [BODY]
      
      [FILE-LOG]
      
      [FOOTER]
      
    • module: Changes to our module list: adding, removing, moving, or deprecating modules.

      • Not for changes /within/ modules.

      • Place scope after colon.

        module: add :lang zig
        
        module: move :feature evil to :editor evil
        
        module: remove :ui fill-column
        
        With Emacs 26.x support dropped and display-fill-column-indicator-mode
        introduced in Emacs 27.1, this module is reduced to a single line, and so has
        become too trivial to warrant its own module.
        
  • doomemacs/themes:

    • theme: TODO
  • doomemacs/snippets:

    • snippet: TODO

Scopes

Scopes may be one or more of the following:

  • All projects:
    • ci – affects continuous integration/delivery infrastucture.
  • For doomemacs/doomemacs:
    • A module’s name without the category: nit(python):, test(doom-dashboard):, dev(popup):, docs(everywhere):
    • A category without the module, implying it affects all or many modules inside: fix(:lang):, feat(:editor):, …
    • One of these special scopes:
      • cli: for anything concerning Doom’s CLI: core-cli.el, core/cli/**, or bin/**.
      • For docs commits:
        • The basename of the target org file in docs/*: docs(migrate):, docs(contributing):, docs(index): (omit me entirely for project README, LICENSE, or major changes across many docs)
        • install for changes to our installation guide(s). This gets its own because it’s expected to change especially often.
  • For doomemacs/themes:
    • base – affects the base theme (or all themes)
    • The name of any of its themes, minus the doom- prefix and -theme suffix. E.g. feat(one): ...) for doom-one, or fix(gruvbox-light): ... for doom-gruvbox-light.
    • The name of any of any doom-themes-ext-X.el extension, minus doom-themes- and .el. E.g. feat(ext-org): ...
  • For doomemacs/snippets:
    • Any major mode, minus the -mode suffix. E.g. fix(org): ... for a bugfix in an org-mode snippet.

The scope may be omitted if:

  • The change affects many parts.
  • The change affects “core” code.
  • Using a scopeless type, like bump, revert, release, or merge (as well as theme for doomemacs/themes and module for doomemacs/doomemacs). For these, the scope belongs in their SUBJECT.

:warning: Does your change touch multiple Doom modules? Only mention the target scope(s). For example, a fix or update for :tools lsp that affects LSP implementations in various :lang modules only needs to specify lsp as its scope.

:warning: No issue/PR references in SUBJECT. See Footer below.

Summary

  • Should be present tense and imperative mood.

    • feat: add X instead of feat: added Y
    • fix: replace A with B instead of fix: replaced A with B
    • When TYPE is a verb, it may be used as part of the SUBJECT:
      • Bad: fix: fix TAB breaking when focus is lost
      • Good: fix: TAB breaking when focus is lost
  • The first word in SUBJECT should not be capitalized – unless it’s a code reference, e.g.

    • Good: fix: TAB breaks when focus is lost
    • Good: fix: remove buggy code
    • Bad: feat: Add new shenanigans
  • Do not quote symbol references and inline code: tweak: gc-cons-threshold = (* 128 1024 1024)

  • Only quote key sequences if they are ambiguous in their context:

    • Good: feat: add C-; keybind
    • Good: refactor!: remove C-c C-; keybind
    • Bad: fix: make SPC s m a reality
    • Good: fix: make 'SPC s m' a reality
    • Bad: fix: bind 'SPC '' to ivy-resume
    • Good: fix: bind "SPC '" to ivy-resume

    A single quote is preferred. Switch to double quote if ' is present in sequence, otherwise backquote. If all three characters are present, know that the ancient ones don’t take kindly to your summoning methods

Body

BODY is optional and should explain the commit’s changes in greater detail. It has no restrictions on tense, but should follow these conventions:

  • Line width must not exceed 72 characters, except for:

    • Machine generated lines, like bump diffs: user/repo@HASH -> user/repo@HASH
    • Long URLs
    • Long symbol references (e.g. a long function name)
  • Code blocks should be indented by 2 spaces from the previous paragraph, and separated from them with a blank line (before and after).

    fix(foo): bar
    
    Before:
    
      (+ 1 2)
    
    After:
    
      (+ 2 1)
    
    Ref: #1234
    
  • Inline code should be surrounded by backquotes, ala sub-shell syntax:

    fix(foo): bar
    
    `foo` calls the juxtapose of `(+ 1 2)`, i.e. `(+ 2 1)`.
    

File Log

Every commit ought to represent a single, central change, but that change might unavoidably span multiple files and unrelated scopes. Explain that central change in BODY, but use a per-file changelog to explain auxiliary changes, after BODY. For example:

refactor!: rename foo to bar

BREAKING CHANGE: This changes the semantics of the `foo` function to do
X and Y, instead of Z. Any uses of this function must be updated to
reflect this.

* some/file.el (func-that-used-bar): remove `bar` use as it's no longer
  needed.
* another/file.el (bar-hook, other-bar-hook): update call
* foo.el (maybe-calls-bar): update call
* a/big/file.el:
  - (maybe-calls-bar): update in case of Z instead of X
  - (variablename): change default to accommodate Z instead of Y

Format

The precise format of a file log entry is either:

* FILENAME[ (SUBSCOPE[, SUBSCOPE]...)]: SUMMARY

Or, for multiple changes in one file:

* FILENAME:
  - (SUBSCOPE[, SUBSCOPE]): SUMMARY
  - (SUBSCOPE[, SUBSCOPE]): SUMMARY
  - ...

Strive to document all non-trivial changes this way, so that readers (and parsers) can detect where a symbol was changed.

Notes

  • A file log entry isn’t necessary for:
    • The core change itself (e.g. where foo was changed to bar).
    • Changes that are trivial and have no impact on program correctness or behavior (e.g. reformatting, grammar/spelling, etc) unless it’s directly relevant to the core change of the commit.
  • SUBSCOPE can be any arbitrary identifier, but 99% of the time should refer to the local symbol(s) affected.
  • If a change is too broad, the SUBSCOPEs may be omitted, but this is discouraged. A long list of SUBSCOPEs is preferred over few/no scopes, for posterity.
  • If a change affects a file’s top-level, and doesn’t affect any particular symbol, then the scope may be omitted.
  • Please sort the file list lexicographically (i.e. make it match the order they’re displayed in, in git status or the magit diff).
  • No blank link in between log entries.

Footer

FOOTER is where commit trailers go. Each line should be comprised of:

  • A keyword followed by a colon:
    • Fix: REF|HASH|URL – This commit resolves a Github issue. Use Ref if it isn’t certain that this commit will fix the issue.
    • Ref: REF|HASH|URL – Consult this resource for more information about this commit.
    • Close: REF – This commit supersedes a Github PR or closes a Github issue (but does not resolve it; e.g. if a commit makes the issue obsolete or indicates a decision not to address the issue).
    • Revert: REF|HASH – For commits that completely revert an earlier commit or un-bumps packages (repins them to earlier commits). For half-reverts use Amend.
    • Amend: REF|HASH – This commit addresses an issue introduced within the same release (or PR). This merges or hides the commit from the changelog generator (E.g. to avoid announcing a breaking change to behavior that was introduced before it was released).
    • One of Github’s supported trailers, e.g. Co-authored-by: or Signed-off-by:
  • A single reference:
    • An issue or PR REFerence: #123, user/repo#123, https://git.forge.fake/repo#123
    • A 12-character commit HASH: 3bedae38dd9f, user/repo@3bedae38dd9f, https://git.forge.fake/repo@3bedae38dd9f
    • An URL to an external website: e.g. a mailing list, reddit discussion, etc.

Examples:

Amend: #666
Amend: 3bedae38dd9f
Close: #55
Close: user/repo#952
Fix: #1
Fix: #25
Fix: https://git.forge.fake/repo#123
Fix: user/repo#84
Ref: #4
Ref: #5
Ref: 3bedae38dd9f
Ref: https://en.wikipedia.org/wiki/Git
Ref: https://git.forge.fake/repo#123
Ref: user/repo#25
Ref: user/repo@3bedae38dd9f
Revert: 3bedae38dd9f
Co-authored-by: John Doe <john@doe.com>
Signed-off-by: Jane Doe <jane.com>

:warning: Order of trailers don’t matter, but please group like-keywords together.

Breaking changes

  • Append a ! to the TYPE to indicate a breaking change.
  • Prepend BREAKING CHANGE: to BODY, followed by an explanation of what is broken (and ideally, how to get around it).

Examples:

  • refactor!: remove X functionality
    
    BREAKING CHANGE: Without X, A and B will not work. Enable Y to get 
    similar behavior.
    
  • fix!(zig): remove lsp support
    
    BREAKING CHANGE: removing LSP support reduces how much Microsoft you
    must ingest to write Zig, but it means no more LSP. Only workaround 
    is to meme on the internet, especially in obscure git convention guides
    that only a handful of people will ever read.
    
6 Likes