tg-summary.txt
tg-update
tg-update.txt
+tg-export
+tg-export.txt
tg
-# Set PREFIX to wherever you want to install TopGit
-PREFIX = $(HOME)
-bindir = $(PREFIX)/bin
-cmddir = $(PREFIX)/share/topgit
-docdir = $(PREFIX)/share/doc/topgit
+prefix = $(HOME)
+bindir = $(prefix)/bin
+cmddir = $(prefix)/libexec/topgit
+docdir = $(prefix)/share/doc/topgit
hooksdir = $(cmddir)/hooks
-commands_in = tg-create.sh tg-delete.sh tg-export.sh tg-info.sh tg-patch.sh tg-summary.sh tg-update.sh
+commands_in = $(wildcard tg-*.sh)
hooks_in = hooks/pre-commit.sh
commands_out = $(patsubst %.sh,%,$(commands_in))
tg: Updating base with t/gitweb/nifty-links changes...
tg: Updating t/whatever against new base...
+ ## Clone a TopGit-controlled repository
+ $ git clone URL repo
+ $ cd repo
+ $ tg remote --populate origin
+ ...
+ $ git fetch
+ $ tg update
+
+ ## Add a TopGit remote to a repository and push to it
+ $ git remote add foo URL
+ $ tg remote foo
+ $ git push foo
+
+ ## Update from a non-default TopGit remote
+ $ git fetch foo
+ $ tg -r foo summary
+ $ tg -r foo update
+
USAGE
-----
it will detect that you are on a topic branch base ref and
resume the topic branch creation operation.
+ In an alternative use case, if '-r BRANCH' is given instead
+ of dependency list, the topic branch is created based on
+ the given remote branch.
+
tg delete
~~~~~~~~~
Remove a TopGit-controlled topic branch of given name
TODO: tg patch -i to base at index instead of branch,
-w for working tree
+tg remote
+~~~~~~~~~
+ Register given remote as TopGit-controlled. This will create
+ the namespace for the remote branch bases and teach 'git fetch'
+ and 'git push' to operate on them.
+
+ It takes a mandatory remote name argument, and optional
+ '--populate' switch - use that for your origin-style remote,
+ it will seed the local topic branch system based on the
+ remote topic branches. '--populate' will also make 'tg remote'
+ automatically fetch the remote and 'tg update' to look at
+ branches of this remote for updates by default.
+
tg summary
~~~~~~~~~~
Show overview of all TopGit-tracked topic branches and their
- up-to-date status ('0' marks that it introduces no own changes,
+ up-to-date status ('>' marks the current topic branch,
+ '0' marks that it introduces no own changes,
+ 'l'/'r' marks that it is local-only or has remote mate,
+ 'L'/'R' marks that it is ahead/out-of-date wrt. its remote mate,
'D' marks that it is out-of-date wrt. its dependencies,
'!' marks that it has missing dependencies (even recursively),
'B' marks that it is out-of-date wrt. its base).
TODO: Make stripping of non-essential headers configurable
TODO: Make stripping of [PATCH] and other prefixes configurable
TODO: --mbox option for other mode of operation
+ TODO: -n option to prevent exporting of empty patches
+ TODO: -a option to export all branches
+ TODO: Allow branches to be exported to be passed as arguments, default
+ to the current branch if none are specified
+ TODO: For quilt exporting, use a temporary branch and remove it when
+ done - this would allow producing conflict-less series
+
+tg import
+~~~~~~~~~
+ Import commits within the given revision range into TopGit,
+ creating one topic branch per commit, the dependencies forming
+ a linear sequence starting on your current branch.
+
+ The branch names are auto-guessed from the commit messages
+ and prefixed by t/ by default; use '-p PREFIX' to specify
+ an alternative prefix (even an empty one).
tg update
~~~~~~~~~
Update the current topic branch wrt. changes in the branches
- it depends on. This is made in two phases - first,
+ it depends on and remote branches.
+ This is performed 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.
+ 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.
+ If a remote branch update brings dependencies on branches
+ not yet instantiated locally, you can either bring in all
+ the new branches from the remote using 'tg remote --populate'
+ or only pick out the missing ones using 'tg create -r'
+ ('tg summary' will point out branches with incomplete
+ dependencies by showing an '!' near to them).
+
TODO: tg update -a for updating all topic branches
-TODO: Some infrastructure for sharing topic branches between
- repositories easily
TODO: tg depend for adding/removing dependencies smoothly
+TODO: tg rename
IMPLEMENTATION
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.
+
+
+REMOTE HANDLING
+---------------
+
+There are three issues with accessing topic branches in remote repositories:
+
+ (i) Fetching/pushing accurate picture of the remote topic branch setup
+ (ii) Referring to remote topic branches from your local repository
+ (iii) Developing some of the remote topic branches locally
+
+(ii) and (iii) are fairly interconnected problems, while (i) is largely
+independent. The issue is to accurately reflect the current state of the
+quickly changing topic branches set - this can be easily done
+with the current facilities like 'git remote prune' and 'git push --mirror' -
+and to properly upload also the bases of the topic branches.
+For this, we need to modify the fetch/push refspecs to also include
+the refs/top-bases/ ref namespace; we shall provide a special 'tg remote'
+command to set up an existing remote for TopGit usage.
+
+About (ii) and (iii), there are two somewhat contradicting design
+considerations:
+
+ (a) Hacking on multiple independent TopGit remotes in a single
+ repository
+ (b) Having a self-contained topic system in local refs space
+
+To us, (a) does not appear to be very convincing, while (b) is quite desirable
+for 'git-log topic' etc. working, 'git push' automatically creating
+self-contained topic system in the remote repository, and increased conceptual
+simplicity.
+
+Thus, we choose to instantiate all the topic branches of given remote locally;
+this is performed by 'tg remote --populate'.
+'tg update' will also check if a branch can be updated from its corresponding
+remote branch. The logic is somewhat involved if we should DTRT.
+First, we update the base, handling the remote branch as if it was the first
+dependency; thus, conflict resolutions made in the remote branch will be
+carried over to our local base automagically. Then, the base is merged into
+remote branch and the result is merged to local branch - again, to carry over
+remote conflict resolutions. In the future, this order might be adjustable
+per-update in case local changes are diverging more than the remote ones.
+
+All commands by default refer to the remote that 'tg remote --populate'
+was called on the last time ('topgit.remote' configuration variable). You can
+manually run any command with a different base remote by passing '-r REMOTE'
+_before_ the subcommand name.
+topgit (0.3-1) unstable; urgency=low
+
+ * New upstream release.
+
+ -- martin f. krafft <madduck@debian.org> Wed, 10 Sep 2008 09:31:03 +0100
+
topgit (0.2-1) unstable; urgency=low
* Initial release (closes: #494710).
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=
+rname= # Remote branch to base this one on
## Parse options
while [ -n "$1" ]; do
arg="$1"; shift
case "$arg" in
+ -r)
+ rname="$1"; shift;;
-*)
- echo "Usage: tg create NAME [DEPS...]" >&2
+ echo "Usage: tg [...] create NAME [DEPS...|-r RNAME]" >&2
exit 1;;
*)
if [ -z "$name" ]; then
done
+## Fast-track creating branches based on remote ones
+
+if [ -n "$rname" ]; then
+ [ -n "$name" ] || die "no branch name given"
+ ! ref_exists "$name" || die "branch '$name' already exists"
+ has_remote "$rname" || die "no branch $rname in remote $base_remote"
+
+ git update-ref "refs/top-bases/$name" "refs/remotes/$base_remote/top-bases/$rname"
+ git update-ref "refs/heads/$name" "refs/remotes/$base_remote/$rname"
+ info "Topic branch $name based on $base_remote : $rname set up."
+ exit 0
+fi
+
+
## Auto-guess dependencies
deps="${deps# }"
if [ -z "$deps" ]; then
- head="$(git symbolic-ref HEAD)"
- bname="${head#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!
+ if [ -z "$name" -a -s "$git_dir/top-name" -a -s "$git_dir/top-deps" -a -s "$git_dir/top-merge" ]; then
+ # We are setting up the base branch now; resume merge!
+ name="$(cat "$git_dir/top-name")"
deps="$(cat "$git_dir/top-deps")"
merge="$(cat "$git_dir/top-merge")"
- name="$bname"
restarted=1
info "Resuming $name setup..."
else
# The common case
[ -z "$name" ] && die "no branch name given"
+ head="$(git symbolic-ref HEAD)"
deps="${head#refs/heads/}"
[ "$deps" != "$head" ] || die "refusing to auto-depend on non-head ref ($head)"
info "Automatically marking dependency on $deps"
[ -n "$merge" -o -n "$restarted" ] || merge="$deps "
for d in $deps; do
- git rev-parse --verify "$d" >/dev/null 2>&1 ||
+ ref_exists "$d" ||
die "unknown branch dependency '$d'"
done
-! git rev-parse --verify "$name" >/dev/null 2>&1 ||
+! ref_exists "$name" ||
die "branch '$name' already exists"
# Clean up any stale stuff
-rm -f "$git_dir/top-deps" "$git_dir/top-merge"
+rm -f "$git_dir/top-name" "$git_dir/top-deps" "$git_dir/top-merge"
## Create base
branch="${merge%% *}"
merge="${merge#* }"
info "Creating $name base from $branch..."
- switch_to_base "$name" "$branch"
+ # We create a detached head so that we can abort this operation
+ git checkout -q "$(git rev-parse "$branch")"
fi
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."
+ info "Please commit merge resolution and call: $tg create"
+ info "It is also safe to abort this operation using:"
+ info "git reset --hard some_branch"
+ info "(You are on a detached HEAD now.)"
+ echo "$name" >"$git_dir/top-name"
echo "$deps" >"$git_dir/top-deps"
echo "$merge" >"$git_dir/top-merge"
exit 2
## Set up the topic branch
+git update-ref "refs/top-bases/$name" "HEAD" ""
git checkout -b "$name"
echo "$deps" | sed 's/ /\n/g' >"$root_dir/.topdeps"
-git add "$root_dir/.topdeps"
+git add -f "$root_dir/.topdeps"
author="$(git var GIT_AUTHOR_IDENT)"
author_addr="${author%> *}>"
Signed-off-by: $author_addr
EOT
} >"$root_dir/.topmsg"
-git add "$root_dir/.topmsg"
+git add -f "$root_dir/.topmsg"
info "Topic branch $name set up. Please fill .topmsg now and make initial commit."
-info "To abort: git rm -f .top* && git checkout ${deps%% *} && tg delete $name"
+info "To abort: git rm -f .top* && git checkout ${deps%% *} && $tg delete $name"
-f)
force=1;;
-*)
- echo "Usage: tg delete [-f] NAME" >&2
+ echo "Usage: tg [...] delete [-f] NAME" >&2
exit 1;;
*)
[ -z "$name" ] || die "name already specified ($name)"
--collapse)
driver=collapse;;
-*)
- echo "Usage: tg export ([--collapse] NEWBRANCH | --quilt DIRECTORY)" >&2
+ echo "Usage: tg [...] export ([--collapse] NEWBRANCH | --quilt DIRECTORY)" >&2
exit 1;;
*)
[ -z "$output" ] || die "output already specified ($output)"
die "not on a TopGit-controlled branch"
-playground="$(mktemp -d)"
+playground="$(mktemp -d -t tg-export.XXXXXX)"
trap 'rm -rf "$playground"' EXIT
echo "Exporting $_dep"
mkdir -p "$(dirname "$filename")"
- tg patch "$_dep" >"$filename"
+ $tg patch "$_dep" >"$filename"
echo "$_dep.diff -p1" >>"$output/series"
}
if [ "$driver" = "collapse" ]; then
[ -n "$output" ] ||
die "no target branch specified"
- ! git rev-parse --verify "$output" >/dev/null 2>&1 ||
+ ! ref_exists "$output" ||
die "target branch '$output' already exists; first run: git branch -D $output"
elif [ "$driver" = "quilt" ]; then
if [ "$driver" = "collapse" ]; then
- git update-ref "refs/heads/$output" "$(cat "$playground/$name")"
+ git update-ref "refs/heads/$output" "$(cat "$playground/$name")" ""
depcount="$(cat "$playground/^ticker" | wc -l)"
echo "Exported topic branch $name (total $depcount topics) to branch $output"
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# (c) Aneesh Kumar K.V <aneesh.kumar@linux.vnet.ibm.com> 2008
+# GPLv2
+
+branch_prefix=t/
+ranges=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -p)
+ branch_prefix="$1"; shift;;
+ -*)
+ echo "Usage: tg [...] import [-p PREFIX] RANGE..." >&2
+ exit 1;;
+ *)
+ ranges="$ranges $arg";;
+ esac
+done
+
+
+get_commit_msg()
+{
+ commit="$1"
+ git log -1 --pretty=format:"From: %an <%ae>%n%n%s%n%n%b" "$commit"
+}
+
+get_branch_name()
+{
+ # nice sed script from git-format-patch.sh
+ commit="$1"
+ titleScript='
+ s/[^-a-z.A-Z_0-9]/-/g
+ s/\.\.\.*/\./g
+ s/\.*$//
+ s/--*/-/g
+ s/^-//
+ s/-$//
+ q
+'
+ git log -1 --pretty=format:"%s" "$commit" | sed -e "$titleScript"
+}
+
+process_commit()
+{
+ commit="$1"
+ branch_name=$(get_branch_name "$commit")
+ info "---- Importing $commit to $branch_prefix$branch_name"
+ tg create "$branch_prefix""$branch_name"
+ git read-tree "$commit"
+ get_commit_msg "$commit" > .topmsg
+ git add -f .topmsg .topdeps
+ git commit -C "$commit"
+ info "++++ Importing $commit finished"
+}
+
+# nice arg verification stolen from git-format-patch.sh
+for revpair in $ranges
+do
+ case "$revpair" in
+ ?*..?*)
+ rev1=`expr "z$revpair" : 'z\(.*\)\.\.'`
+ rev2=`expr "z$revpair" : 'z.*\.\.\(.*\)'`
+ ;;
+ *)
+ die "Unknow range spec $revpair"
+ ;;
+ esac
+ git rev-parse --verify "$rev1^0" >/dev/null 2>&1 ||
+ die "Not a valid rev $rev1 ($revpair)"
+ git rev-parse --verify "$rev2^0" >/dev/null 2>&1 ||
+ die "Not a valid rev $rev2 ($revpair)"
+ git cherry -v "$rev1" "$rev2" |
+ while read sign rev comment
+ do
+ case "$sign" in
+ '-')
+ info "Merged already: $comment"
+ ;;
+ *)
+ process_commit "$rev"
+ ;;
+ esac
+ done
+done
arg="$1"; shift
case "$arg" in
-*)
- echo "Usage: tg info [NAME]" >&2
+ echo "Usage: tg [...] info [NAME]" >&2
exit 1;;
*)
[ -z "$name" ] || die "name already specified ($name)"
echo "Topic Branch: $name ($measure)"
if [ "$(git rev-parse --short "$name")" = "$base_rev" ]; then
- echo "No commits."
+ echo "* No commits."
exit 0
fi
echo "Base: $base_rev"
branch_contains "$name" "$base_rev" ||
- echo "Base is newer than head! Please run \`tg update\`."
+ echo "* Base is newer than head! Please run \`$tg update\`."
+
+if has_remote "$name"; then
+ echo "Remote Mate: $base_remote/$name"
+ branch_contains "$base_rev" "refs/remotes/$base_remote/top-bases/$name" ||
+ echo "* Local base is out of date wrt. the remote base."
+ branch_contains "$name" "refs/remotes/$base_remote/$name" ||
+ echo "* Local head is out of date wrt. the remote head."
+ branch_contains "refs/remotes/$base_remote/$name" "$name" ||
+ echo "* Local head is ahead of the remote head."
+fi
git cat-file blob "$name:.topdeps" |
- sed '1{s/^/Depends: /;n}; s/^/ /;'
+ sed '1{ s/^/Depends: /; n; }; s/^/ /;'
-depcheck="$(mktemp)"
+depcheck="$(mktemp -t tg-depcheck.XXXXXX)"
missing_deps=
needs_update "$name" >"$depcheck" || :
if [ -n "$missing_deps" ]; then
arg="$1"; shift
case "$arg" in
-*)
- echo "Usage: tg patch [NAME]" >&2
+ echo "Usage: tg [...] patch [NAME]" >&2
exit 1;;
*)
[ -z "$name" ] || die "name already specified ($name)"
[ -n "$(git grep '^[-]--' "$name" -- ".topmsg")" ] || echo '---'
# Evil obnoxious hack to work around the lack of git diff --exclude
-git_is_stupid="$(mktemp)"
+git_is_stupid="$(mktemp -t tg-patch-changes.XXXXXX)"
git diff-tree --name-only "$base_rev" "$name" |
fgrep -vx ".topdeps" |
fgrep -vx ".topmsg" >"$git_is_stupid" || : # fgrep likes to fail randomly?
echo '-- '
echo "tg: ($base_rev..) $name (depends on: $(git cat-file blob "$name:.topdeps" | paste -s -d' '))"
branch_contains "$name" "$base_rev" ||
- echo "tg: The patch is out-of-date wrt. the base! Run \`tg update\`."
+ 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
+
+populate= # Set to 1 if we shall seed local branches with this
+name=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ --populate)
+ populate=1;;
+ -*)
+ echo "Usage: tg [...] remote [--populate] REMOTE" >&2
+ exit 1;;
+ *)
+ name="$arg";;
+ esac
+done
+
+git config "remote.$name.url" >/dev/null || die "unknown remote '$name'"
+
+
+## Configure the remote
+
+git config --add "remote.$name.fetch" "+refs/top-bases/*:refs/remotes/$name/top-bases/*"
+git config --add "remote.$name.push" "+refs/top-bases/*:refs/top-bases/*"
+git config --add "remote.$name.push" "+refs/heads/*:refs/heads/*"
+
+info "Remote $name can now follow TopGit topic branches."
+if [ -z "$populate" ]; then
+ info "Next, do: git fetch $name"
+ exit
+fi
+
+
+## Populate local branches
+
+info "Populating local topic branches from remote '$name'..."
+
+git fetch "$name"
+git for-each-ref "refs/remotes/$name/top-bases" |
+ while read rev type ref; do
+ branch="${ref#refs/remotes/$name/top-bases/}"
+ if git rev-parse "$branch" >/dev/null 2>&1; then
+ git rev-parse "refs/top-bases/$branch" >/dev/null 2>&1 ||
+ git update-ref "refs/top-bases/$branch" "$rev"
+ info "Skipping branch $branch: Already exists"
+ continue
+ fi
+ info "Adding branch $branch..."
+ git update-ref "refs/top-bases/$branch" "$rev"
+ git update-ref "refs/heads/$branch" "$(git rev-parse "$name/$branch")"
+ done
+
+git config "topgit.remote" "$name"
+info "The remote '$name' is now the default source of topic branches."
## Parse options
if [ -n "$1" ]; then
- echo "Usage: tg summary" >&2
+ echo "Usage: tg [...] summary" >&2
exit 1
fi
+curname="$(git symbolic-ref HEAD | sed 's#^refs/\(heads\|top-bases\)/##')"
+
## List branches
git for-each-ref refs/top-bases |
- while read rev name ref; do
+ while read rev type ref; do
name="${ref#refs/top-bases/}"
missing_deps=
+ current=' '
+ [ "$name" != "$curname" ] || current='>'
nonempty=' '
! branch_empty "$name" || nonempty='0'
+ remote=' '
+ [ -z "$base_remote" ] || remote='l'
+ ! has_remote "$name" || remote='r'
+ rem_update=' '
+ [ "$remote" != 'r' ] || ! ref_exists "refs/remotes/$base_remote/top-bases/$name" || {
+ branch_contains "refs/top-bases/$name" "refs/remotes/$base_remote/top-bases/$name" &&
+ branch_contains "$name" "refs/remotes/$base_remote/$name"
+ } || rem_update='R'
+ [ "$rem_update" = 'R' ] || branch_contains "refs/remotes/$base_remote/$name" "$name" 2>/dev/null ||
+ rem_update='L'
deps_update=' '
needs_update "$name" >/dev/null || deps_update='D'
deps_missing=' '
subject="(No commits)"
fi
- printf '%s\t%-31s\t%s\n' "$nonempty$deps_update$deps_missing$base_update" \
+ printf '%s\t%-31s\t%s\n' "$current$nonempty$remote$rem_update$deps_update$deps_missing$base_update" \
"$name" "$subject"
done
## Parse options
if [ -n "$1" ]; then
- echo "Usage: tg update" >&2
+ echo "Usage: tg [...] update" >&2
exit 1
fi
## First, take care of our base
-depcheck="$(mktemp)"
+depcheck="$(mktemp -t tg-depcheck.XXXXXX)"
missing_deps=
needs_update "$name" >"$depcheck" || :
[ -z "$missing_deps" ] || die "some dependencies are missing: $missing_deps"
dep="$(echo "$depline" | cut -c 2-)"
# 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.
+ # and base/remote out-of-date cases for $dep here,
+ # but thanks to needs_update returning : or %
+ # for the latter, we do correctly recurse here
+ # in both cases.
if [ x"$action" = x+ ]; then
info "Recursing to $dep..."
(
export TG_RECURSIVE="[$dep] $TG_RECURSIVE"
export PS1="[$dep] $PS1"
- while ! tg update; do
+ 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."
switch_to_base "$name"
fi
+ # This will be either a proper topic branch
+ # or a remote base. (branch_needs_update() is called
+ # only on the _dependencies_, not our branch itself!)
+
info "Updating base with $dep changes..."
if ! git merge "$dep"; then
if [ -z "$TG_RECURSIVE" ]; then
- resume='`tg update` again'
+ resume='`$tg update` again'
else # subshell
resume='exit'
fi
fi
rm "$depcheck"
+merge_with="refs/top-bases/$name"
+
+
+## Second, update our head with the remote branch
+
+if has_remote "$name"; then
+ rname="refs/remotes/$base_remote/$name"
+ if branch_contains "$name" "$rname"; then
+ info "The $name head is up-to-date wrt. its remote branch."
+ else
+ info "Reconciling remote branch updates with $name base..."
+ # *DETACH* our HEAD now!
+ git checkout -q "refs/top-bases/$name"
+ if ! git merge "$rname"; then
+ info "Oops, you will need to help me out here a bit."
+ info "Please commit merge resolution and call:"
+ info "git checkout $name && git merge <commitid>"
+ info "It is also safe to abort this operation using: git reset --hard $name"
+ exit 3
+ fi
+ # Go back but remember we want to merge with this, not base
+ merge_with="$(git rev-parse HEAD)"
+ git checkout -q "$name"
+ fi
+fi
+
-## Second, update our head with the base
+## Third, update our head with the base
-if branch_contains "$name" "refs/top-bases/$name"; then
+if branch_contains "$name" "$merge_with"; 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 ! git merge "$merge_with"; 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\`."
+ 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\`."
# setup_hook NAME
setup_hook()
{
- hook_call="\"\$(tg --hooks-path)\"/$1 \"\$@\""
+ hook_call="\"\$($tg --hooks-path)\"/$1 \"\$@\""
if [ -f "$git_dir/hooks/$1" ] &&
fgrep -q "$hook_call" "$git_dir/hooks/$1"; then
# Another job well done!
# Whether B1 is a superset of B2.
branch_contains()
{
- [ -z "$(git rev-list ^"$1" "$2")" ]
+ [ -z "$(git rev-list ^"$1" "$2" --)" ]
+}
+
+# ref_exists REF
+# Whether REF is a valid ref name
+ref_exists()
+{
+ git rev-parse --verify "$@" >/dev/null 2>&1
+}
+
+# has_remote BRANCH
+# Whether BRANCH has a remote equivalent (accepts top-bases/ too)
+has_remote()
+{
+ [ -n "$base_remote" ] && ref_exists "remotes/$base_remote/$1"
}
# recurse_deps CMD NAME [BRANCHPATH...]
_cmd="$1"; shift
_name="$1"; # no shift
_depchain="$*"
- _depsfile="$(mktemp)"
- git cat-file blob "$_name:.topdeps" >"$_depsfile"
+
+ _depsfile="$(mktemp -t tg-depsfile.XXXXXX)"
+ # Check also our base against remote base. Checking our head
+ # against remote head has to be done in the helper.
+ if has_remote "top-bases/$_name"; then
+ echo "refs/remotes/$base_remote/top-bases/$_name" >>"$_depsfile"
+ fi
+ git cat-file blob "$_name:.topdeps" >>"$_depsfile"
+
_ret=0
while read _dep; do
- if ! git rev-parse --verify "$_dep" >/dev/null 2>&1; then
+ if ! ref_exists "$_dep" ; then
# All hope is lost
missing_deps="$missing_deps $_dep"
continue
fi
_dep_is_tgish=1
- git rev-parse --verify "refs/top-bases/$_dep" >/dev/null 2>&1 ||
+ ref_exists "refs/top-bases/$_dep" ||
_dep_is_tgish=
# Shoo shoo, keep our environment alone!
# description for details) and set $_ret to non-zero.
branch_needs_update()
{
- _dep_base_uptodate=1
+ _dep_base_update=
if [ -n "$_dep_is_tgish" ]; then
- branch_contains "$_dep" "refs/top-bases/$_dep" || _dep_base_uptodate=
+ if has_remote "$_dep"; then
+ branch_contains "$_dep" "refs/remotes/$base_remote/$_dep" || _dep_base_update=%
+ fi
+ # This can possibly override the remote check result;
+ # we want to sync with our base first
+ branch_contains "$_dep" "refs/top-bases/$_dep" || _dep_base_update=:
fi
- if [ -z "$_dep_base_uptodate" ]; then
- # _dep needs to be synced with its base
- echo ": $_dep $_depchain"
+ if [ -n "$_dep_base_update" ]; then
+ # _dep needs to be synced with its base/remote
+ echo "$_dep_base_update $_dep $_depchain"
_ret=1
elif [ -n "$_name" ] && ! branch_contains "refs/top-bases/$_name" "$_dep"; then
# Some new commits in _dep
# 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.
+# not in sync with the base or '%' if the head is not in sync
+# with the remote (in this order of priority).
# It will also return non-zero status if NAME needs update.
# If needs_update() hits missing dependencies, it will append
# them to space-separated $missing_deps list and skip them.
sep="|"
done
- echo "TopGit v0.2 - A different patch queue manager"
- echo "Usage: tg ($cmds|help) ..."
- elif [ -r "@docdir@/tg-$1.txt" ] ; then
- cat "@docdir@/tg-$1.txt"
+ echo "TopGit v0.3 - A different patch queue manager"
+ echo "Usage: tg [-r REMOTE] ($cmds|help) ..."
+ elif [ -r "@docdir@/tg-$1.txt" ] ; then
+ cat "@docdir@/tg-$1.txt"
else
echo "`basename $0`: no help for $1" 1>&2
fi
set -e
git_dir="$(git rev-parse --git-dir)"
root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}"
+base_remote="$(git config topgit.remote 2>/dev/null)" || :
+tg="tg"
# make sure merging the .top* files will always behave sanely
setup_ours
setup_hook "pre-commit"
# this is set by hooks.
[ -z "$tg__include" ] || return 0
+if [ "$1" = "-r" ]; then
+ shift; base_remote="$1"; shift
+ tg="$tg -r $base_remote"
+fi
+
cmd="$1"
[ -n "$cmd" ] || die "He took a duck in the face at two hundred and fifty knots"
shift