Version 1.0
This document describes the change development database and process. The main
premise of the approach described here is that planning changes in code should
be handled in the same way as changing the code itself; that is, using git(1)
and our favorite text editors, rather than some external database accessible
via a web interface (which what most bug trackers are these days).
To be usable, the database format and process must not be burdensome. As a
result, there is minimum notation as well as helper tools to automate common
operations, for example, adding a new item (called a note).
The database can either be stored in the git repository of the project itself
or, if the project consists of multiple git repositories, in a repository of
its own. In the former case it is recommended to place the database in the
top-level subdirectory of a project and call it change. In the latter case it
is recommended to call the repository change, potentially with a prefix
denoting the overall project name, for example, hello-change.
The change database is a collection of notes stored in plain text files that
use a certain notation. The files are organized in subdirectories which are
used to group notes that affect a certain subproject or an area of a project.
For a database that covers multiple git repositories it is common to have
top-level subdirectories named after those repositories. As an example, let's
say we have a "Hello, World!" project that consists of two git repositories:
the libhello library and the hello program. The resulting directory structure
then could be:
hello/
libhello/
change/
|
|--hello/
|
`--libhello/
Continuing with this example, inside libhello/ we could have subdirectories for
major functionality areas:
change/
|
|--hello/
|
`--libhello/
|
|--format/
|
`--print/
It seldom makes sense to have more than two levels of subdirectories. At the
top level the subdirectory called reference is reserved for storing notes that
have been acted upon. Its usage is described in more detail below.
A note consists of a header and an optional body separated with a blank line.
All lines in a note should be no longer than 78 characters. The header is
always the first line and contains the note's severity, summary, and optional
labels. The header has the following format (literal values are quoted):
['-'|'!'|'?'|'+'] [ '['[ ]...']']
For example:
! Detect empty name [bug]
- Add ability to customize greeting phrase [feature 2.0.0]
? Implement pluggable formatter [idea]
The '-' severity denotes a normal note, '!' -- critical, and '?' -- unconfirmed
or questionable, while '+' is used to denote implemented notes in the reference
directory (discussed below).
The summary should follow the git rules for a commit message summary, that is,
it should use no articles, past/future tenses, and should ideally be no longer
than 60 characters (though this rule can sometimes be broken for clarity).
Normally, you should be able to copy the summary into the commit message when
you have implemented a note.
Labels are separated with a space (note: not a comma and space). By convention
the first label should be the note type. Commonly used types are: bug (fix
something broken), feature (implement new functionality), idea (design a new
feature), quality (improve quality of implementation), infra (work on project
infrastructure).
Further, labels can be used to group notes based on certain criteria. For
example, doc (documentation issue), windows (Windows-specific), 2.0.0
(scheduled for the 2.0.0 release), john (assigned to John). The names of
subdirectories in which the note is located are also considered its labels. So,
for example, if the above "Detect empty name" bug was filed in lihello/format/,
then its labels would be bug, format, and libhello.
The body of a note is free-form. However, for clarity, it makes sense to avoid
using '-' for lists in the body ('*' for the first level and '~' for the second
level are good options).
Notes can be saved in three ways. Simple notes without a body or with a body
containing one or two paragraphs can be written in the list files. These files
can appear at the top level or in any subdirectory. More complex notes can be
placed in their own files. Finally, notes that have addition material (what
traditional bug trackers would call "attachments") should be placed into their
own subdirectories with both the directory and the note file (inside that
subdirectory) having the same name. Other than that, such a subdirectory is
free form; it can contain other files and subdirectories.
If a note is written in the list file, then its body must be indented two
spaces to align with the start of the summary. Notes are separated with blank
lines and their order in the list files is not significant. Normally you would
add a new note at the top, for convenience. Continuing with our example, let's
file our bug and idea in the list file under libhello/format/ (since they both
only affect this functionality):
! Detect empty name [bug]
It would make sense to detect empty names and throw invalid_argument.
? Implement pluggable formatter [idea]
Some users asked for a way to provide their own formatting implementation
via some sort of a plugin mechanism.
Note that it's not clear at all this is a good idea.
If a note is written into its own file then its body need not be indented;
everything after the header and the blank like is just a normal plain text
file. When choosing a name for a file try to incorporate at least two and
preferably three keywords form the summary. This will minimize the chance of a
name conflict in the reference directory which will accumulate notes over many
years.
As an example, let's save our feature into custom-greeting-phrase under
libhello/:
- Add ability to customize greeting phrase [feature 2.0.0]
Some users asked for a way to customize the greeting phrase. For example, some
prefer less formal "Hi" to "Hello".
The way we can implement this is by adding greeting as the second argument to
say() that will default to "Hello".
Note that this change will be source but not binary compatible so we will have
to bump at least the minor version.
Note also that we can move notes freely between files. For example, we may add
a new subdirectory and move all the notes that affect this functionality from
the top-level list file. Or we can move a note from list to its own file or
from a file to a subdirectory if we need to keep some additional files with the
note. For example, if we start expanding on our "Implement pluggable formatter"
idea, then it probably makes sense to move it into its own file.
When committing (in the git sense) changes to the database, use a separate
commit for each note. When committing a newly added note, the commit message
should be in the form:
Add :
For example:
Add bug: Detect empty name
If you only have a single note added in the database then you can use the add
script to automate this. This script will commit the new note with the correct
message and, unless the -c option is specified, push the result to origin. This
should make filing new notes a fairly burdenless process: write a note using
your favorite text editor and run the add script. Note that the add script
currently cannot handle notes with extra files.
If you change an existing note (for example, add additional information), then
the commit message should have the following form:
Update :
For example:
Update idea: Implement pluggable formatter
Similar to adding, if you only have a single note updated in the database then
you can use the update script to automate this. Note that the update script
currently cannot handle updates with extra files.
Once a note is acted upon (implemented or you have decided not to do anything
about it), you can either delete it or move it to the reference. Simply
deleting a note is appropriate for simple bugs and features where all the
design information, if any, is incorporated into the code itself. For a more
elaborate note, however, it may make sense to preserve it in case it needs to
be revisited in the future.
The top-level reference subdirectory should recreate the same directory
structure as top-level (except for reference/ itself). For instance, this will
be the structure for our example:
change/
|
|--hello/
|
|--libhello/
| |
| |--format/
| |
| `--print/
|
`--reference/
|
|--hello/
|
`--libhello/
|
|--format/
|
`--print/
Only notes stored as separate files can be moved to the reference (in other
words, there should be no list files in reference/). When moved, a note should
be placed into the corresponding subdirectory and its severity changed to
either '+' if it has been implemented or to '?' if it has been dropped (in
which case it is a good idea to add an explanation as to why).
Continuing with our example, let's say we have implemented the "Customize
Greeting Phrase" feature but would like to keep the note. This is the relevant
part of our directory structure before the move:
change/
|
|--libhello/
| |
| `--custom-greeting-phrase
|
`--reference/
|
`--libhello/
And this is after the move (we also change the severity to '+' inside
custom-greeting-phrase):
change/
|
|--libhello/
|
`--reference/
|
`--libhello/
|
`--custom-greeting-phrase
For an implemented note the commit message should be the same as the one for
the implementation in the code repository and which normally should be the same
as the note subject. If the change database is part of the project's git
repository, then everything should be in the same commit.
If you have decided not to implement a note, then the commit message should
have the following form:
Drop :
For example:
Drop idea: Implement pluggable formatter