--- /dev/null
--- /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
--- /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
--- /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
++}