aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorBoris Kolpackov <boris@codesynthesis.com>2016-09-15 03:29:20 +0200
committerBoris Kolpackov <boris@codesynthesis.com>2016-09-15 03:29:20 +0200
commita0ae430f01bb8ab0a98cce2203435a286dd89ff7 (patch)
tree664b1b0b326cdbd9719ba016f580c5cacb91c7b4
Initial spec version and add script
-rw-r--r--README238
-rw-r--r--README.cli282
-rwxr-xr-xadd113
3 files changed, 633 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..0ef2788
--- /dev/null
+++ b/README
@@ -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
+\
+"
diff --git a/add b/add
new file mode 100755
index 0000000..b99da32
--- /dev/null
+++ b/add
@@ -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