#! /usr/bin/env bash # Commit and push an update to an existing 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 # @@ We could probably handle the case where extra files are added to the # note. We just need to make sure they are not changes to other notes. # if git status --porcelain | grep -q '^?? '; then error "error: repository has untracked files" fi modified="$(git status --porcelain | sed -n -e 's/^ M \(.*\)/\1/p')" mc="$(echo "$modified" | wc -w)" if [ "$mc" -eq 0 ]; then error "error: nothing modified" fi if [ "$mc" -gt 1 ]; then error "error: multiple modified files" fi # Extract the start line and count from the changeset header. # # The format is: # # @@ [+-]FROM-START,FROM-COUNT [+-]TO-START,TO-COUNT @@ # # Note that if the changeset has only one line then the count is omitted. # function changeset_start () { echo "$1" | sed -e 's/^@@ [^ ]* [+-]\([^, ]*\).*$/\1/' } function changeset_count () { local r r="$(echo "$1" | sed -n -e 's/^@@ [^ ]* [+-][^,]*,\([^ ]*\) @@$/\1/p')" if [ -z "$r" ]; then r="1" fi echo "$r" } #changeset_start "@@ -12,3 +13,11 @@" #changeset_count "@@ -12,3 +13,11 @@" #changeset_start "@@ -12 +13 @@" #changeset_count "@@ -12 +13 @@" h= if [ "$(basename "$modified")" = "list" ]; then # # This is a list of notes. The plan is to get the start line numbers for the # first and last change set. We then scan the list line by line looking for # headers until we hit the start of the first change set: the last header # that we see should be our header. We also continue scanning until we hit # the end of the last changeset: if we see any headers then we are updating # multiple notes. # # Get the diff output as a list of change set headers. # diff="$(git diff -U0 | sed -n -e '/^@@ /p')" fc="$(echo "$diff" | head -n 1)" lc="$(echo "$diff" | tail -n 1)" sl="$(changeset_start "$fc")" el="$(($(changeset_start "$lc") + $(changeset_count "$lc") - 1))" # Scan the list looking for headers. # l= c="0" while read l || [ -n "$l" ]; do # Stop if we are past the end line. # c="$(($c + 1))" if [ "$c" -gt "$el" ]; then break fi # See if this is a header. # if echo "$l" | grep -q '^[-?!] '; then # If we are past the start line then we are modifying multiple notes. # if [ "$c" -gt "$sl" ]; then error "error: multiple modified notes in $modified:${sl}-${el}" fi # This is the last header. # h="$l" fi done < <(cat "$modified") if [ -z "$h" ]; then error "error: unable to find note header for $modified:${sl}-${el}" fi else # This is a file note. All we need to do is get its header (the first line). # h="$(head -n 1 "$modified")" fi # Header should start with one of [-?!]. # sum="$(echo "$h" | sed -n -re 's/^[-?!] ([^[]*)( \[.*\])?$/\1/p')" if [ -z "$sum" ]; then error "error: unable to parse note header '$h'" fi # By convention the first label is a note type (bug, feature, etc). # type="$(echo "$h" | sed -n -re 's/^[^[]*\[([^ ]*).*\]$/\1/p')" if [ -z "$type" ]; then error "error: note header does not include note type as first label" fi # Ok, all looks good so add, commit and push the change. # git add "$modified" git ci -m "Update $type: $sum" if [ "$push" = "y" ]; then git push fi