--- /dev/null
+#!/bin/sh
+set -eu
+
+. ${0%/*}/tg-datastore.inc
+
+info() { printf "I: $@\n" >&2; }
+
+case "$1" in
+ add)
+ # tg-datastore add var=val var2=val2 ...
+ # only add to HEAD
+ shift
+ info "returns non-zero if there is already a datastore on HEAD."
+ info "adding the following data to the datastore of HEAD:"
+ for pair in "$@"; do
+ info " ${pair%%=*}: ${pair#*=}\n"
+ printf "${pair%%=*}: ${pair#*=}\n"
+ done | tg_ds_add_data || die "E: HEAD already has a datastore"
+ ;;
+ remove)
+ # removes from HEAD
+ info "always returns zero, even if there was nothing to remove."
+ tg_ds_remove_data
+ ;;
+ list)
+ # $2 is the commit
+ info "returns non-zero if no datastore found at given commit."
+ info "prints contents of datastore otherwise."
+ tg_ds_list_data "${2:-HEAD}"
+ ;;
+ get)
+ # get parameter $2 from $3 (default HEAD)
+ info "prints the value of the parameter stored in the given commit."
+ info "prints nothing if the commit has a datastore but without the parameter."
+ info "returns non-zero if there is no datastore."
+ tg_ds_get_value "$2" ${3:-HEAD}
+ ;;
+ find)
+ # search for parameter $2 by backtracking from $3 (default HEAD)
+ info "prints the value of the parameter, or empty if parameter is not found."
+ info "returns non-zero if no datastore was found."
+ tg_ds_find_value "$2" ${3:-HEAD}
+ ;;
+ *)
+ echo "Usage: ${0##*/} [ add var=val... | remove | list | get var [commit] | find var [commit] ]"
+ ;;
+esac
--- /dev/null
+_get_empty_treeref()
+{
+ git mktree </dev/null
+}
+
+_make_pcommit()
+{
+ local timestamp; timestamp=$(date +'%s %z')
+ git hash-object -t commit -w --stdin <<-_commit_data
+ tree $(_get_empty_treeref)
+ author TopGit <> $timestamp
+ committer "$author"
+ x-topgit-data-node yes
+
+ TopGit data node
+
+ This commit is used internally by TopGit. Please just ignore it.
+
+ TopGit data follow:
+ $(cat)
+ _commit_data
+}
+
+_append_parentref_to_HEAD()
+{
+ local parent; parent="$1"
+ local ref; ref=$(git cat-file commit HEAD |
+ sed -e "/^parent/aparent ${parent}" |
+ git hash-object -t commit -w --stdin)
+ git update-ref HEAD $ref
+}
+
+_remove_parentref_from_HEAD()
+{
+ local parent; parent="$1"
+ local ref; ref=$(git cat-file commit HEAD |
+ grep -v "^parent ${parent}" |
+ git hash-object -t commit -w --stdin)
+ git update-ref HEAD $ref
+}
+
+# returns 0 if $1 is a valid TopGit pcommit
+_is_pcommit()
+{
+ local pcommit; pcommit="$1"
+ git cat-file commit $pcommit | grep -q '^x-topgit-data-node yes$'
+}
+
+# returns the ref of the pcommit attached to $1
+# returns 1 when none is found
+_find_pcommit()
+{
+ local commit; commit="$1"
+ local parentnr; parentnr=1
+
+ while :; do
+ ref=$(tg_ds_resolve_commit "${commit}^${parentnr}") || break
+ _is_pcommit "$ref" && printf "$ref\n" && return 0
+ parentnr=$(($parentnr + 1))
+ done
+ return 1
+}
+
+# expects data on stdin and adds to HEAD
+tg_ds_add_data()
+{
+ local author; author=$(git cat-file commit HEAD | sed -rne 's,^author ,,p')
+ _append_parentref_to_HEAD $(_make_pcommit "$author")
+}
+
+# removes data from HEAD
+tg_ds_remove_data()
+{
+ local pcommit; pcommit=$(_find_pcommit HEAD) || return # no error
+ _remove_parentref_from_HEAD $pcommit
+}
+
+# lists data on a given commit
+tg_ds_list_data()
+{
+ local commit; commit="${1:-HEAD}"
+ ref=$(tg_ds_resolve_commit $commit) || die "tg_ds_list_data: invalid commit: $commit"
+ local pcommit; pcommit=$(_find_pcommit $commit) || return 1
+ git cat-file commit $pcommit | sed -e '0,/^TopGit data follow:$/d'
+}
--- /dev/null
+die()
+{
+ printf "E: $@\n" >&2
+ exit 1
+}
+
+tg_ds_resolve_commit()
+{
+ git rev-parse --verify $1 2>/dev/null
+}
+
+. ${0%/*}/tg-datastore-pcommits.inc
+
+tg_ds_get_value()
+{
+ local name; name="${1:-}"
+ local commit; commit="${2:-HEAD}"
+
+ [ -z "$1" ] && die 'tg_ds_get_value: missing first argument (name)'
+ tg_ds_resolve_commit $commit >/dev/null || die "tg_ds_get_value: invalid commit: $commit"
+
+ local data; data=$(tg_ds_list_data "$commit") || return 1
+ printf "$data\n" | sed -rne "s,^${name}: *,,p"
+}
+
+tg_ds_find_value()
+{
+ local name; name="${1:-}"
+ local commit; commit="${2:-HEAD}"
+
+ [ -z "$1" ] && die 'tg_ds_find_value: missing first argument (name)'
+ ref=$(tg_ds_resolve_commit $commit) || die "tg_ds_find_value: invalid commit: $commit"
+
+ while :; do
+ if [ "$name" = commitref ]; then
+ tg_ds_list_data "$commit" >/dev/null && printf "$ref\n" && return 0
+ else
+ tg_ds_get_value "$name" $commit && return 0
+ fi
+ commit="${commit}^"
+ ref=$(tg_ds_resolve_commit "${commit}") || break
+ done
+
+ return 1
+}