--- /dev/null
+hooks/pre-commit
+tg-create
+tg-delete
+tg-info
+tg-patch
+tg-summary
+tg-update
+tg
--- /dev/null
+# Set PREFIX to wherever you want to install TopGit
+PREFIX = $(HOME)
+bindir = $(PREFIX)/bin
+cmddir = $(PREFIX)/libexec/topgit
+hooksdir = $(cmddir)/hooks
+
+
+commands_in = tg-create.sh tg-delete.sh tg-info.sh tg-patch.sh tg-summary.sh tg-update.sh
+hooks_in = hooks/pre-commit.sh
+
+commands_out = $(patsubst %.sh,%,$(commands_in))
+hooks_out = $(patsubst %.sh,%,$(hooks_in))
+
+all:: tg $(commands_out) $(hooks_out)
+
+tg $(commands_out) $(hooks_out): % : %.sh
+ @echo "[SED] $@"
+ @sed -e 's#@cmddir@#$(cmddir)#g;' \
+ -e 's#@hooksdir@#$(hooksdir)#g' \
+ -e 's#@bindir@#$(bindir)#g' \
+ $@.sh >$@+ && \
+ chmod +x $@+ && \
+ mv $@+ $@
+
+
+install:: all
+ install tg "$(bindir)"
+ install -d -m 755 "$(cmddir)"
+ install $(commands_out) "$(cmddir)"
+ install -d -m 755 "$(hooksdir)"
+ install $(hooks_out) "$(hooksdir)"
+
+clean::
+ rm -f tg $(commands_out) $(hooks_out)
--- /dev/null
+TopGit - A different patch queue manager
+
+
+DESCRIPTION
+-----------
+
+TopGit aims to make handling of large amount of interdependent topic
+branches easier. In fact, it is designed especially for the case
+when you maintain a queue of third-party patches on top of another
+(perhaps Git-controlled) project and want to easily organize, maintain
+and submit them - TopGit achieves that by keeping a separate topic
+branch for each patch and providing few tools to maintain the branches.
+
+
+RATIONALE
+---------
+
+Why not use something like StGIT or Guilt or rebase -i for that?
+The advantage of these tools is their simplicity; they work with patch
+_series_ and defer to the reflog facility for version control of patches
+(reordering of patches is not version-controlled at all). But there are
+several disadvantages - for one, these tools (especially StGIT) do not
+actually fit well with plain Git at all - it is basically impossible
+to take advantage of index efectively when using StGIT. But more
+importantly, these tools horribly fail in the face of distributed
+environment.
+
+TopGit has been designed around three main tenents:
+
+ (i) TopGit is as thin layer on top of Git as possible.
+You still maintain your index and commit using Git, TopGit will
+only automate few indispensable tasks.
+
+ (ii) TopGit is anxious about _keeping_ your history. It will
+never rewrite your history and all metadata are also tracked by Git,
+smoothly and non-obnoxiously. It is useful if there is a _single_
+point when the history is cleaned up, and that is at the point of
+inclusion in the upstream project; locally, you can see how your
+patch has evolved and easily return to older versions.
+
+ (iii) TopGit is specifically designed to work in distributed
+environment. You can have several instances of TopGit-aware repositories
+and smoothly keep them all up-to-date and transfer your changes between
+them.
+
+As mentioned above, the main intended use-case for TopGit is tracking
+third-party patches, where each patch is effectively a single topic
+branch. In order to flexibly accomodate even complex scenarios when
+you track many patches where many are independent but some depend
+on others, TopGit ignores the ancient Quilt heritage of patch series
+and instead allows the patches to freely form graphs (DAGs just like
+Git history itself, only "one lever higher"). For now, you have
+to manually specify which patches does the current one depend
+on, but TopGit might help you with that in the future in a darcs-like
+fashion.
+
+A glossary plug: The union (i.e. merge) of patch dependencies is
+called a _base_ of the patch (topic branch).
+
+Of course, TopGit is perhaps not the right tool for you:
+
+ (i) TopGit is not complicated, but StGIT et al. are somewhat
+simpler, conceptually. If you just want to make a linear purely-local
+patch queue, deferring to StGIT instead might make more sense.
+
+ (ii) While keeping your history anxiously, in some extreme
+cases the TopGit-generated history graph will perhaps be a little
+too complex. ;-)
+
+
+SYNOPSIS
+--------
+
+ ## Create and evolve a topic branch
+ $ tg create t/gitweb/pathinfo-action
+ tg: Automatically marking dependency on master
+ tg: Creating t/gitweb/pathinfo-action base from master...
+ $ ..hack..
+ $ git commit
+ $ ..fix a mistake..
+ $ git commit
+
+ ## Create another topic branch on top of the former one
+ $ tg create t/gitweb/nifty-links
+ tg: Automatically marking dependency on t/gitweb/pathinfo-action
+ tg: Creating t/gitweb/nifty-links base from t/gitweb/pathinfo-action...
+ $ ..hack..
+ $ git commit
+
+ ## Create another topic branch on top of specified one and submit
+ ## the resulting patch upstream
+ $ tg create -d master t/revlist/author-fixed
+ tg: Creating t/revlist/author-fixed base from master...
+ $ ..hack..
+ $ git commit
+ $ tg patch -m
+ tg: Sent t/revlist/author-fixed
+ From: pasky@suse.cz
+ To: git@vger.kernel.org
+ Cc: gitster@pobox.com
+ Subject: [PATCH] Fix broken revlist --author when --fixed-string
+
+ ## Create another topic branch depending on two others non-trivially
+ $ tg create -d t/revlist/author-fixed,t/gitweb/nifty-links t/whatever
+ tg: Creating t/whatever base from t/revlist/author-fixed...
+ tg: Merging t/whatever base with t/gitweb/nifty-links...
+ Merge failed!
+ tg: Please commit merge resolution and call: tg create
+ tg: It is also safe to abort this operation using `git reset --hard`
+ tg: but please remember you are on the base branch now;
+ tg: you will want to switch to a different branch.
+ $ ..resolve..
+ $ git commit
+ tg: Resuming t/whatever setup...
+ $ tg create t/whatever
+ $ ..hack..
+ $ git commit
+
+ ## Update a single topic branch and propagate the changes to
+ ## a different one
+ $ git checkout t/gitweb/nifty-links
+ $ ..hack..
+ $ git commit
+ $ git checkout t/whatever
+ $ tg info
+ Topic Branch: t/whatever (1 commit)
+ Subject: [PATCH] Whatever patch
+ Base: 3f47ebc1
+ Depends: t/revlist/author-fixed t/gitweb/nifty-links
+ Needs update from:
+ t/gitweb/nifty-links (1 commit)
+ $ tg update
+ tg: Updating base with t/gitweb/nifty-links changes...
+ Merge failed!
+ tg: Please commit merge resolution and call `tg update` again.
+ tg: It is also safe to abort this operation using `git reset --hard`,
+ tg: but please remember you are on the base branch now;
+ tg: you will want to switch to a different branch.
+ $ ..resolve..
+ $ git commit
+ $ tg update
+ tg: Updating t/whatever against new base...
+ Merge failed!
+ tg: Please resolve the merge and commit. No need to do anything else.
+ tg: You can abort this operation using `git reset --hard` now
+ tg: and retry this merge later using `tg update`.
+ $ ..resolve..
+ $ git commit
+
+ ## Update a single topic branch and propagate the changes
+ ## further through the dependency chain
+ $ git checkout t/gitweb/pathinfo-action
+ $ ..hack..
+ $ git commit
+ $ git checkout t/whatever
+ $ tg info
+ Topic Branch: t/whatever (1/2 commits)
+ Subject: [PATCH] Whatever patch
+ Base: 0ab2c9b3
+ Depends: t/revlist/author-fixed t/gitweb/nifty-links
+ Needs update from:
+ t/gitweb/pathinfo-action (<= t/gitweb/nifty-links) (1 commit)
+ $ tg update
+ tg: Recursing to t/gitweb/nifty-links...
+ [t/gitweb/nifty-links] tg: Updating base with t/gitweb/pathinfo-action changes...
+ Merge failed!
+ [t/gitweb/nifty-links] tg: Please commit merge resolution and call `tg update` again.
+ [t/gitweb/nifty-links] tg: It is also safe to abort this operation using `git reset --hard`,
+ [t/gitweb/nifty-links] tg: but please remember you are on the base branch now;
+ [t/gitweb/nifty-links] tg: you will want to switch to a different branch.
+ [t/gitweb/nifty-links] tg: You are in a subshell. If you abort the merge,
+ [t/gitweb/nifty-links] tg: use `exit` to abort the recursive update altogether.
+ [t/gitweb/nifty-links] $ ..resolve..
+ [t/gitweb/nifty-links] $ git commit
+ [t/gitweb/nifty-links] $ tg update
+ [t/gitweb/nifty-links] tg: Updating t/gitweb/nifty-links against new base...
+ Merge failed!
+ [t/gitweb/nifty-links] tg: Please resolve the merge and commit.
+ [t/gitweb/nifty-links] tg: You can abort this operation using `git reset --hard`.
+ [t/gitweb/nifty-links] tg: You are in a subshell. After you either commit or abort
+ [t/gitweb/nifty-links] tg: your merge, use `exit` to proceed with the recursive update.
+ [t/gitweb/nifty-links] $ ..resolve..
+ [t/gitweb/nifty-links] $ git commit
+ [t/gitweb/nifty-links] $ exit
+ tg: Updating base with t/gitweb/nifty-links changes...
+ tg: Updating t/whatever against new base...
+
+
+USAGE
+-----
+
+The 'tg' tool of TopGit has several subcommands:
+
+tg help
+~~~~~~~
+ Our sophisticated integrated help facility. Doesn't do
+ a whole lot for now.
+
+tg create
+~~~~~~~~~
+ Create a new TopGit-controlled topic branch of a given name
+ (required argument) and switch to it. If no dependencies
+ are specified using the '-d' paremeter, the current branch
+ is assumed to be the only dependency.
+
+ After `tg create`, you should insert the patch description
+ to the '.topmsg' file.
+
+ The main task of `tg create` is to set up the topic branch
+ base from the dependencies. This may fail due to merge conflicts.
+ In that case, after you commit the conflicts resolution,
+ you should call `tg create` again (without any arguments);
+ it will detect that you are on a topic branch base ref and
+ resume the topic branch creation operation.
+
+ '-d':
+ Manually specified dependencies. A comma- or
+ space-separated list of branch names.
+
+tg delete
+~~~~~~~~~
+ Remove a TopGit-controlled topic branch of given name
+ (required argument). Normally, this command will remove
+ only empty branch (base == head); use '-f' to remove
+ non-empty branch.
+
+ Currently, this command will _NOT_ remove the branch from
+ the dependency list in other branches. You need to take
+ care of this _manually_. This is even more complicated
+ in combination with '-f', in that case you need to manually
+ unmerge the removed branch's changes from the branches
+ depending on it.
+
+ TODO: '-a' to delete all empty branches, depfix, revert
+
+tg info
+~~~~~~~
+ Show a summary information about the current or specified
+ topic branch.
+
+tg patch
+~~~~~~~~
+ Generate a patch from the current or specified topic branch.
+ This means that the diff between the topic branch base and
+ head (latest commit) is shown, appended to the description
+ found in the .topmsg file.
+
+ The patch is by default simply dumped to stdout. In the future,
+ tg patch will be able to automatically send the patches by mail
+ or save them to files.
+
+ TODO: tg patch -i to base at index instead of branch,
+ -w for working tree
+
+tg summary
+~~~~~~~~~~
+ Show overview of all TopGit-tracked topic branches and their
+ up-to-date status ('D' marks that it is out-of-date wrt. its
+ dependencies, 'B' marks that it is out-of-date wrt. its base).
+
+tg update
+~~~~~~~~~
+ Update the current topic branch wrt. changes in the branches
+ it depends on. This is made in two phases - first,
+ changes within the dependencies are merged to the base,
+ then the base is merged into the topic branch. The output
+ will guide you in case of conflicts.
+
+ In case your dependencies are not up-to-date, tg update
+ will first recurse into them and update these.
+
+ TODO: tg update -a for updating all topic branches
+
+TODO: Some infrastructure for sharing topic branches between
+ repositories easily
+
+
+IMPLEMENTATION
+--------------
+
+TopGit stores all the topic branches in the regular refs/heads/
+namespace, (we recommend to mark them with the 't/' prefix).
+Except that, TopGit also maintains a set of auxiliary refs in
+refs/top-*. Currently, only refs/top-bases/ is used, containing
+the current _base_ of the given topic branch - this is basically
+a merge of all the branches the topic branch depends on; it is
+updated during `tg update` and then merged to the topic branch,
+and it is the base of a patch generated from the topic branch by
+`tg patch`.
+
+All the metadata is tracked within the source tree and history
+of the topic branch itself, in .top* files; these files are kept
+isolated within the topic branches during TopGit-controlled merges
+and are of course omitted during `tg patch`. The state of these
+files in base commits is undefined; look at them only in the topic
+branches themselves. Currently, two files are defined:
+
+ .topmsg: Contains the description of the topic branch
+in a mail-like format, plus the author information,
+whatever Cc headers you choose or the post-three-dashes message.
+When mailing out your patch, basically only few extra headers
+mail headers are inserted and the patch itself is appended.
+Thus, as your patches evolve, you can record nuances like whether
+the paricular patch should have To-list/Cc-maintainer or vice
+versa and similar nuances, if your project is into that.
+
+ .topdeps: Contains the one-per-line list of branches
+your patch depends on, pre-seeded with `tg create`. (Continuously
+updated) merge of these branches will be the "base" of your topic
+branch.
+
+TopGit also automagically installs a bunch of custom commit-related
+hooks that will verify if you are committing the .top* files in sane
+state. It will add the hooks to separate files within the hooks/
+subdirectory and merely insert calls of them to the appropriate hooks
+and make them executable (but make sure the original hooks code
+is not called if the hook was not executable beforehand).
+
+Another automagically installed piece is .git/info/attributes specifier
+for an 'ours' merge strategy for the files .topmsg and .topdeps, and
+the (intuitive) 'ours' merge strategy definition in .git/config.
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+
+## Set up all the tg machinery
+
+set -e
+tg__include=1
+tg_util() {
+ . "@bindir@"/tg
+}
+tg_util
+
+
+## Generally have fun
+
+# Don't do anything on non-topgit branch
+git rev-parse --verify "$(git symbolic-ref HEAD | sed 's/heads/top-bases/')" >/dev/null 2>&1 ||
+ exit 0
+
+[ -s "$root_dir/.topdeps" ] ||
+ die ".topdeps is missing"
+[ -s "$root_dir/.topmsg" ] ||
+ die ".topmsg is missing"
+
+# TODO: Verify .topdeps for valid branch names and against cycles
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+deps= # List of dependent branches
+restarted= # Set to 1 if we are picking up in the middle of base setup
+merge= # List of branches to be merged; subset of $deps
+name=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -d)
+ deps="$(echo "$1" | sed 's/,/ /g')"; shift;;
+ -*)
+ echo "Usage: tg create [-d DEPS...] NAME" >&2
+ exit 1;;
+ *)
+ [ -z "$name" ] || die "name already specified ($name)"
+ name="$arg";;
+ esac
+done
+
+
+## Auto-guess dependencies
+
+if [ -z "$deps" ]; then
+ head="$(git symbolic-ref HEAD)"
+ bname="${heads#refs/top-bases/}"
+ if [ "$bname" != "$head" -a -s "$git_dir/top-deps" -a -s "$git_dir/top-merge" ]; then
+ # We are on a base branch now; resume merge!
+ deps="$(cat "$git_dir/top-deps")"
+ merge="$(cat "$git_dir/top-merge") "
+ name="$base"
+ restarted=1
+ info "Resuming $name setup..."
+ else
+ # The common case
+ [ -z "$name" ] && die "no branch name given"
+ deps="${head#refs/heads/}"
+ [ "$deps" != "$head" ] || die "refusing to auto-depend on non-head ref ($head)"
+ info "Automatically marking dependency on $deps"
+ fi
+fi
+
+[ -n "$merge" -o -n "$restarted" ] || merge="$deps "
+
+for d in $deps; do
+ git rev-parse --verify "$d" >/dev/null 2>&1 ||
+ die "unknown branch dependency '$d'"
+done
+! git rev-parse --verify "$name" >/dev/null 2>&1 ||
+ die "branch '$name' already exists"
+
+# Clean up any stale stuff
+rm -f "$git_dir/top-deps" "$git_dir/top-merge"
+
+
+## Create base
+
+if [ -n "$merge" ]; then
+ # Unshift the first item from the to-merge list
+ branch="${merge%% *}"
+ merge="${merge#* }"
+ info "Creating $name base from $branch..."
+ switch_to_base "$name" "$branch"
+fi
+
+
+## Merge other dependencies into the base
+
+while [ -n "$merge" ]; do
+ # Unshift the first item from the to-merge list
+ branch="${merge%% *}"
+ merge="${merge#* }"
+ info "Merging $name base with $branch..."
+
+ if ! git merge "$branch"; then
+ info "Please commit merge resolution and call: tg create"
+ info "It is also safe to abort this operation using \`git reset --hard\`"
+ info "but please remember you are on the base branch now;"
+ info "you will want to switch to a different branch."
+ echo "$deps" >"$git_dir/top-deps"
+ echo "$merge" >"$git_dir/top-merge"
+ exit 2
+ fi
+done
+
+
+## Set up the topic branch
+
+git checkout -b "$name"
+
+echo "$deps" | sed 's/ /\n/g' >"$root_dir/.topdeps"
+git add "$root_dir/.topdeps"
+
+author="$(git var GIT_AUTHOR_IDENT)"
+author_addr="${author%> *}>"
+{
+ echo "From: $author_addr"
+ echo "Subject: [PATCH] $1"
+ echo
+ cat <<EOT
+<patch description>
+
+Signed-off-by: $author_addr
+EOT
+} >"$root_dir/.topmsg"
+git add "$root_dir/.topmsg"
+
+
+
+info "Topic branch $name successfully set up. Please fill .topmsg now."
+info "You MUST do an initial commit. To abort: git rm -f .top* && git checkout ${deps%% *} && tg delete $name"
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+force= # Whether to delete non-empty branch
+name=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -f)
+ force=1;;
+ -*)
+ echo "Usage: tg delete [-f] NAME" >&2
+ exit 1;;
+ *)
+ [ -z "$name" ] || die "name already specified ($name)"
+ name="$arg";;
+ esac
+done
+
+
+## Sanity checks
+
+[ -n "$name" ] || die "no branch name specified"
+branchrev="$(git rev-parse --verify "$name" 2>/dev/null)" ||
+ die "invalid branch name: $name"
+baserev="$(git rev-parse --verify "refs/top-bases/$name" 2>/dev/null)" ||
+ die "not a TopGit topic branch: $name"
+[ "$(git symbolic-ref HEAD)" != "refs/heads/$name" ] ||
+ die "cannot delete your current branch"
+
+nonempty=
+[ -z "$(git diff-tree "refs/top-bases/$name" "$name" | fgrep -v " .top")" ] || nonempty=1
+
+[ -z "$nonempty" ] || [ -n "$force" ] || die "branch is non-empty: $name"
+
+
+## Wipe out
+
+git update-ref -d "refs/top-bases/$name" "$baserev"
+git update-ref -d "refs/heads/$name" "$branchrev"
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+name=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -*)
+ echo "Usage: tg info [NAME]" >&2
+ exit 1;;
+ *)
+ [ -z "$name" ] || die "name already specified ($name)"
+ name="$arg";;
+ esac
+done
+
+[ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')"
+base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" ||
+ die "not a TopGit-controlled branch"
+
+measure="$(measure_branch "$name" "$base_rev")"
+
+echo "Topic Branch: $name ($measure)"
+if [ "$(git rev-parse --short "$name")" = "$base_rev" ]; then
+ echo "No commits."
+ exit 0
+fi
+
+git cat-file blob "$name:.topmsg" | grep ^Subject:
+
+echo "Base: $base_rev"
+branch_contains "$name" "$base_rev" ||
+ echo "Base is newer than head! Please run \`tg update\`."
+
+deps="$(git cat-file blob "$name:.topdeps")"
+echo "Depends: $deps"
+
+depcheck="$(mktemp)"
+needs_update "$name" >"$depcheck"
+if [ -s "$depcheck" ]; then
+ echo "Needs update from:"
+ cat "$depcheck" |
+ sed 's/ [^ ]* *$//' | # last is $name
+ sed 's/^: //' | # don't distinguish base updates
+ while read dep chain; do
+ echo -n "$dep "
+ [ -n "$chain" ] && echo -n "(<= $(echo "$chain" | sed 's/ / <= /')) "
+ dep_parent="${chain%% *}"
+ echo -n "($(measure_branch "$dep" "${dep2:-$name}"))"
+ echo
+ done | sed 's/^/\t/'
+else
+ echo "Up-to-date."
+fi
+rm "$depcheck"
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+name=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -*)
+ echo "Usage: tg patch [NAME]" >&2
+ exit 1;;
+ *)
+ [ -z "$name" ] || die "name already specified ($name)"
+ name="$arg";;
+ esac
+done
+
+[ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')"
+base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" ||
+ die "not a TopGit-controlled branch"
+
+git cat-file blob "$name:.topmsg"
+echo
+[ -n "$(git grep '^[-]--' "$name" -- ".topmsg")" ] || echo '---'
+
+# Evil obnoxious hack to work around the lack of git diff --exclude
+git_is_stupid="$(mktemp)"
+git diff-tree --name-only "$base_rev" "$name" |
+ fgrep -vx ".topdeps" |
+ fgrep -vx ".topmsg" >"$git_is_stupid" || : # fgrep likes to fail randomly?
+if [ -s "$git_is_stupid" ]; then
+ cat "$git_is_stupid" | xargs git diff --patch-with-stat "$base_rev" "$name" --
+else
+ echo "No changes."
+fi
+rm "$git_is_stupid"
+
+echo '-- '
+echo "tg: ($base_rev..) $name (depends on $(git cat-file blob "$name:.topdeps"))"
+branch_contains "$name" "$base_rev" ||
+ echo "tg: The patch is out-of-date wrt. the base! Run \`tg update\`."
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+
+## Parse options
+
+if [ -n "$1" ]; then
+ echo "Usage: tg summary" >&2
+ exit 1
+fi
+
+
+## List branches
+
+git for-each-ref | cut -f 2 |
+ while read ref; do
+ name="${ref#refs/heads/}"
+ [ "$name" != "$ref" ] ||
+ continue # eew, not a branch
+ git rev-parse --verify "refs/top-bases/$name" >/dev/null 2>&1 ||
+ continue # not a TopGit branch
+
+ deps_update=' '
+ [ -z "$(needs_update "$name")" ] || deps_update='D'
+ base_update=' '
+ branch_contains "$name" "refs/top-bases/$name" || base_update='B'
+
+ if [ "$(git rev-parse "$name")" != "$(git rev-parse "refs/top-bases/$name")" ]; then
+ subject="$(git cat-file blob "$name:.topmsg" | sed -n 's/^Subject: //p')"
+ else
+ # No commits yet
+ subject="(No commits)"
+ fi
+
+ printf '%s%s\t%-31s\t%s\n' "$deps_update" "$base_update" "$name" "$subject"
+ done
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+name=
+
+
+## Parse options
+
+if [ -n "$1" ]; then
+ echo "Usage: tg update" >&2
+ exit 1
+fi
+
+
+name="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')"
+base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" ||
+ die "not a TopGit-controlled branch"
+
+
+## First, take care of our base
+
+depcheck="$(mktemp)"
+needs_update "$name" >"$depcheck"
+if [ -s "$depcheck" ]; then
+ # We need to switch to the base branch
+ # ...but only if we aren't there yet (from failed previous merge)
+ HEAD="$(git symbolic-ref HEAD)"
+ if [ "$HEAD" = "${HEAD#refs/top-bases/}" ]; then
+ switch_to_base "$name"
+ fi
+
+ cat "$depcheck" |
+ sed 's/ [^ ]* *$//' | # last is $name
+ sed 's/.* \([^ ]*\)$/+\1/' | # only immediate dependencies
+ sed 's/^\([^+]\)/-\1/' | # now each line is +branch or -branch (+ == recurse)
+ uniq -s 1 | # fold branch lines; + always comes before - and thus wins within uniq
+ while read depline; do
+ action="${depline:0:1}"
+ dep="${depline:1}"
+
+ # We do not distinguish between dependencies out-of-date
+ # and base out-of-date cases for $dep here, but thanks
+ # to needs_update returning : for the latter, we do
+ # correctly recurse here in both cases.
+
+ if [ x"$action" = x+ ]; then
+ info "Recursing to $dep..."
+ git checkout -q "$dep"
+ (
+ export TG_RECURSIVE="[$dep] $TG_RECURSIVE"
+ export PS1="[$dep] $PS1"
+ while ! tg update; do
+ # The merge got stuck! Let the user fix it up.
+ info "You are in a subshell. If you abort the merge,"
+ info "use \`exit 1\` to abort the recursive update altogether."
+ if ! sh -i; then
+ info "Ok, you aborated the merge. Now, you just need to"
+ info "switch back to some sane branch using \`git checkout\`."
+ exit 3
+ fi
+ done
+ )
+ switch_to_base "$name"
+ fi
+
+ info "Updating base with $dep changes..."
+ if ! git merge "$dep"; then
+ if [ -z "$TG_RECURSIVE" ]; then
+ resume='`tg update` again'
+ else # subshell
+ resume='exit'
+ fi
+ info "Please commit merge resolution and call $resume."
+ info "It is also safe to abort this operation using \`git reset --hard\`,"
+ info "but please remember that you are on the base branch now;"
+ info "you will want to switch to some normal branch afterwards."
+ rm "$depcheck"
+ exit 2
+ fi
+ done
+
+ # Home, sweet home...
+ git checkout -q "$name"
+else
+ info "The base is up-to-date."
+fi
+rm "$depcheck"
+
+
+## Second, update our head with the base
+
+if branch_contains "$name" "refs/top-bases/$name"; then
+ info "The $name head is up-to-date wrt. the base."
+ exit 0
+fi
+info "Updating $name against new base..."
+if ! git merge "refs/top-bases/$name"; then
+ if [ -z "$TG_RECURSIVE" ]; then
+ info "Please commit merge resolution. No need to do anything else"
+ info "You can abort this operation using \`git reset --hard\` now"
+ info "and retry this merge later using \`tg update\`."
+ else # subshell
+ info "Please commit merge resolution and call exit."
+ info "You can abort this operation using \`git reset --hard\`."
+ fi
+fi
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+
+## Auxiliary functions
+
+info()
+{
+ echo "${TG_RECURSIVE}tg: $*"
+}
+
+die()
+{
+ info "fatal: $*"
+ exit 1
+}
+
+# setup_hook NAME
+setup_hook()
+{
+ hook_call="\"\$(tg --hooks-path)\"/$1 \"\$@\""
+ if fgrep -q "$hook_call" "$git_dir/hooks/$1"; then
+ # Another job well done!
+ return
+ fi
+ # Prepare incanation
+ if [ -x "$git_dir/hooks/$1" ]; then
+ hook_call="$hook_call"' || exit $?'
+ else
+ hook_call="exec $hook_call"
+ fi
+ # Insert call into the hook
+ {
+ echo "#!/bin/sh"
+ echo "$hook_call"
+ cat "$git_dir/hooks/$1"
+ } >"$git_dir/hooks/$1+"
+ chmod a+x "$git_dir/hooks/$1+"
+ mv "$git_dir/hooks/$1+" "$git_dir/hooks/$1"
+}
+
+# setup_ours (no arguments)
+setup_ours()
+{
+ if [ ! -s "$git_dir/info/gitattributes" ] || ! grep -q topmsg "$git_dir/info/gitattributes"; then
+ {
+ echo -e ".topmsg\tmerge=ours"
+ echo -e ".topdeps\tmerge=ours"
+ } >>"$git_dir/info/gitattributes"
+ fi
+ if ! git config merge.ours.driver >/dev/null; then
+ git config merge.ours.name '"always keep ours" merge driver'
+ git config merge.ours.driver 'touch %A'
+ fi
+}
+
+# measure_branch NAME [BASE]
+measure_branch()
+{
+ _name="$1"; _base="$2"
+ [ -n "$_base" ] || _base="refs/top-bases/$_name"
+ # The caller should've verified $name is valid
+ _commits="$(git rev-list "$_name" ^"$_base" | wc -l)"
+ _nmcommits="$(git rev-list --no-merges "$_name" ^"$_base" | wc -l)"
+ if [ $_commits -gt 1 ]; then
+ _suffix="commits"
+ else
+ _suffix="commit"
+ fi
+ echo "$_commits/$_nmcommits $_suffix"
+}
+
+# branch_contains B1 B2
+# Whether B1 is a superset of B2.
+branch_contains()
+{
+ [ "$(git rev-list ^"$1" "$2" | wc -l)" -eq 0 ]
+}
+
+# needs_update NAME [BRANCHPATH...]
+# This function is recursive; it outputs reverse path from NAME
+# to the branch (e.g. B_DIRTY B1 B2 NAME), one path per line,
+# inner paths first. Innermost name can be ':' if the head is
+# not in sync with the base.
+needs_update()
+{
+ {
+ git cat-file blob "$1:.topdeps" 2>/dev/null |
+ while read _dep; do
+ _dep_is_tgish=1
+ git rev-parse --verify "refs/top-bases/$_dep" >/dev/null 2>&1 ||
+ _dep_is_tgish=
+
+ # Shoo shoo, keep our environment alone!
+ [ -z "$_dep_is_tgish" ] || (needs_update "$_dep" "$@")
+
+ _dep_base_uptodate=1
+ if [ -n "$_dep_is_tgish" ]; then
+ branch_contains "$_dep" "refs/top-bases/$_dep" || _dep_base_uptodate=
+ fi
+
+ if [ -z "$_dep_base_uptodate" ]; then
+ # _dep needs to be synced with its base
+ echo ": $_dep $*"
+ elif ! branch_contains "refs/top-bases/$1" "$_dep"; then
+ # Some new commits in _dep
+ echo "$_dep $*"
+ fi
+ done
+ } || : # $1 is not tracked by TopGit anymore
+}
+
+# switch_to_base NAME [SEED]
+switch_to_base()
+{
+ _base="refs/top-bases/$1"; _seed="$2"
+ # We have to do all the hard work ourselves :/
+ # This is like git checkout -b "$_base" "$_seed"
+ # (or just git checkout "$_base"),
+ # but does not create a detached HEAD.
+ git read-tree -u -m HEAD "${_seed:-$_base}"
+ [ -z "$_seed" ] || git update-ref "$_base" "$_seed"
+ git symbolic-ref HEAD "$_base"
+}
+
+
+## Initial setup
+
+set -e
+git_dir="$(git rev-parse --git-dir)"
+root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}"
+# make sure merging the .top* files will always behave sanely
+setup_ours
+setup_hook "pre-commit"
+
+
+## Dispatch
+
+# We were sourced from another script for our utility functions;
+# this is set by hooks.
+[ -z "$tg__include" ] || return 0
+
+cmd="$1"
+[ -n "$cmd" ] || die "He took a duck in the face at two hundred and fifty knots"
+shift
+
+case "$cmd" in
+help)
+ echo "TopGit - A different patch queue manager"
+ echo "Usage: tg (create|delete|info|patch|summary|update|help) ..."
+ exit 1;;
+create|delete|info|patch|summary|update)
+ . "@cmddir@"/tg-$cmd;;
+--hooks-path)
+ # Internal command
+ echo "@hooksdir@";;
+*)
+ echo "Unknown subcommand: $cmd" >&2
+ exit 1;;
+esac