diff options
-rw-r--r-- | README | 238 | ||||
-rw-r--r-- | README.cli | 282 | ||||
-rwxr-xr-x | add | 113 |
3 files changed, 633 insertions, 0 deletions
@@ -0,0 +1,238 @@ +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): + +['-'|'!'|'?'|'+'] <summary>[ '['<label>[ <label>]...']'] + +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 issue 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 two 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. + +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. 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 <type>: <summary> + +For example: + +Add bug: Detect empty name + +If you only have a single issue added in the database then you can use the add +script to automate it. This script will commit the new issues 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. + +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 <type>: <summary> + +For example: + +Drop idea: Implement pluggable formatter diff --git a/README.cli b/README.cli new file mode 100644 index 0000000..91ae0f2 --- /dev/null +++ b/README.cli @@ -0,0 +1,282 @@ +// cli --generate-txt --txt-suffix "" README.cli + +// @@ add -n which prints the tree, asks for where to add (auto-completion) +// and starts the editor. +// +// @@ drop script? +// +// @@ impl script? Don't even need to extract anything. Or maybe should to +// make sure the commit message is the same. Could warn if doesn't match +// subject. Not going to be easy if moved. + +" +Version 1.0 + +This document describes the \i{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 +\c{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 \c{git} repository of the project +itself or, if the project consists of multiple \c{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 \c{change}. In +the latter case it is recommended to call the repository \c{change}, +potentially with a prefix denoting the overall project name, for example, +\c{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 \c{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 \c{libhello} library and the \c{hello} program. The resulting directory +structure then could be: + +\ +hello/ + +libhello/ + +change/ +| +|--hello/ +| +`--libhello/ +\ + +Continuing with this example, inside \c{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 \c{reference} is reserved for storing notes +that have been acted upon. Its usage is described in more detail below. + +A note consists of a \i{header} and an optional \i{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 \i{severity}, \i{summary}, +and optional \i{labels}. The header has the following format (literal values +are quoted): + +\ +['-'|'!'|'?'|'+'] <summary>[ '['<label>[ <label>]...']'] +\ + +For example: + +\ +! Detect empty name [bug] +- Add ability to customize greeting phrase [feature 2.0.0] +? Implement pluggable formatter [idea] +\ + +The '\c{-}' severity denotes a normal note, '\c{!}' \- critical, and '\c{?}' +\- unconfirmed or questionable, while '\c{+}' is used to denote implemented +notes in the \c{reference} directory (discussed below). + +The summary should follow the \c{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: \c{bug} (fix +something broken), \c{feature} (implement new functionality), \c{idea} (design +a new feature), \c{quality} (improve quality of implementation), \c{infra} +(work on project infrastructure). + +Further, labels can be used to group notes based on certain criteria. For +example, \c{doc} (documentation issue), \c{windows} (Windows-specific), +\c{2.0.0} (scheduled for the 2.0.0 release), \c{john} (assigned to John). The +names of subdirectories in which the issue is located are also considered its +labels. So, for example, if the above \"Detect empty name\" bug was filed in +\c{lihello/format/}, then its labels would be \c{bug}, \c{format}, and +\c{libhello}. + +The body of a note is free-form. However, for clarity, it makes sense to avoid +using '\c{-}' for lists in the body ('\c{*}' for the first level and '\c{~}' +for the second level are good options). + +Notes can be saved in two ways. Simple notes without a body or with a body +containing one or two paragraphs can be written in the \c{list} files. These +files can appear at the top level or in any subdirectory. More complex notes +can be placed in their own files. + +If a note is written in the \c{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 \c{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 \c{list} file under \c{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 \c{custom-greeting-phrase} under +\c{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 \c{list} file. Or we can move a note from \c{list} to its own +file. 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 \c{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 <type>: <summary> +\ + +For example: + +\ +Add bug: Detect empty name +\ + +If you only have a single issue added in the database then you can use the +\c{add} script to automate it. This script will commit the new issues with the +correct message and, unless the \c{-c} option is specified, push the result to +\c{origin}. This should make filing new notes a fairly burdenless process: +write a note using your favorite text editor and run the \c{add} script. + +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 \c{reference} subdirectory should recreate the same directory +structure as top-level (except for \c{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 \c{list} files in \c{reference/}). When moved, a +note should be placed into the corresponding subdirectory and its severity +changed to either '\c{+}' if it has been implemented or to '\c{?}' 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 '\c{+}' inside +\c{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 +\c{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 <type>: <summary> +\ + +For example: + +\ +Drop idea: Implement pluggable formatter +\ +" @@ -0,0 +1,113 @@ +#! /usr/bin/env bash + +# Commit and push a new note. +# +# -c +# Commit only, don't push. +# +usage="usage: $0 [-c]" + +owd=`pwd` +trap "{ cd $owd; exit 1; }" ERR +set -o errtrace # Trap in functions. + +function info () { echo "$*" 1>&2; } +function error () { info "$*"; exit 1; } + +push="y" + +while [ $# -gt 0 ]; do + case $1 in + -c) + push="n" + shift + ;; + *) + error "$usage" + ;; + esac +done + +if git status --porcelain | grep -q '^M '; then + error "error: repository already has staged changes" +fi + +untracked="$(git status --porcelain | sed -n -e 's/^?? \(.*\)/\1/p')" +modified="$(git status --porcelain | sed -n -e 's/^ M \(.*\)/\1/p')" + +uc="$(echo "$untracked" | wc -w)" +mc="$(echo "$modified" | wc -w)" + +if [ "$uc" -gt 0 -a "$mc" -gt 0 ]; then + error "error: multiple untracked/modified files" +fi + +# Add the tracked file to index with --intent-to-add so that we can diff it +# the same way as modified. The not so nice thing about this approach is that +# we have to remember to reset it if things go badly. +# +if [ "$uc" -gt 0 ]; then + git add --intent-to-add "$untracked" + modified="$untracked" +fi + +# Get the diff output skipping the header (the first four lines until the +# first changeset). +# +diff="$(git diff -U0 | sed -n -e '/^@@/,$p')" +dc="$(echo "$diff" | wc -l)" + +# Get the changeset. While we don't care for leading or trailing empty lines +# down the line, we do want them in the count. And preserving them in program +# substitution is more difficult than one would think. +# +cset="$(echo "$diff" | sed -n -e 's/^+\(.*\)/\1/p')" +cc="$(echo "$diff" | sed -n -e 's/^+\(.*\)/\1/p' | wc -l)" + +# We should only have one changeset and it should only have added lines. Note +# that this is always the case for a new file. +# +if [ "$dc" -ne "$(($cc + 1))" ]; then + info "error: multiple changesets or non-add changes" + git diff -U0 + exit 1 +fi + +# Get the first non-empty line which should be the header. +# +l= +while read l || [ -n "$l" ]; do + if [ -n "$l" ]; then + break + fi +done < <(echo "$cset") + +# Should start with one of [-?!]. +# +sum="$(echo "$l" | sed -n -re 's/^[-?!] ([^[]*)( \[.*\])?$/\1/p')" + +if [ -z "$sum" ]; then + info "error: first line does not appear to be a note header" + info "info: first line is '$l'" + exit 1 +fi + +# By convention the first label is a note type (bug, feature, etc). +# +type="$(echo "$l" | sed -n -re 's/^[^[]*\[([^ ]*).*\]$/\1/p')" + +if [ -z "$type" ]; then + error "error: note header does not include note type as first label" +fi + +#@@ Maybe check known note types and warn if not one of them? +# + +# Ok, all looks good so add, commit and push the change. +# +git add "$modified" +git ci -m "Add $type: $sum" + +if [ "$push" = "y" ]; then + git push +fi |