chiark / gitweb /
Merge branch 'fixes/destdir' into refs/top-bases/debian/locations
authormartin f. krafft <madduck@debian.org>
Wed, 10 Sep 2008 08:33:28 +0000 (09:33 +0100)
committermartin f. krafft <madduck@debian.org>
Wed, 10 Sep 2008 08:33:28 +0000 (09:33 +0100)
13 files changed:
.gitignore
Makefile
README
tg-create.sh
tg-delete.sh
tg-export.sh
tg-import.sh [new file with mode: 0644]
tg-info.sh
tg-patch.sh
tg-remote.sh [new file with mode: 0644]
tg-summary.sh
tg-update.sh
tg.sh

index 6f0727f9b650661ca84e30c6820a55770cb007b2..8f1d3a7d1ea4c012f90a8a62ae27265c1fb1fd16 100644 (file)
@@ -11,4 +11,6 @@ tg-summary
 tg-summary.txt
 tg-update
 tg-update.txt
+tg-export
+tg-export.txt
 tg
index bafec268d4355976a7a2dfcded9914e49050c18a..b73a1f15b9c298e0e922df1c61f6fb15df8f7821 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -1,12 +1,11 @@
-# Set PREFIX to wherever you want to install TopGit
-PREFIX = $(HOME)
-bindir = $(PREFIX)/bin
-cmddir = $(PREFIX)/libexec/topgit
-sharedir = $(PREFIX)/share/topgit
+prefix = $(HOME)
+bindir = $(prefix)/bin
+cmddir = $(prefix)/libexec/topgit
+sharedir = $(prefix)/share/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))
diff --git a/README b/README
index b58a1b43e8f7383ed53b60464886d6efaf08de1a..f45eb6cca897cda03b64354a9343c8ddb281b484 100644 (file)
--- a/README
+++ b/README
@@ -184,6 +184,24 @@ SYNOPSIS
        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
 -----
@@ -215,6 +233,10 @@ tg create
        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
@@ -250,10 +272,26 @@ tg patch
        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).
@@ -329,23 +367,48 @@ tg export
        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
@@ -394,3 +457,50 @@ 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.
+
+
+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.
index 6cce7edebc1102bc9cfe86226f96ee40bc1ab8c0..6ee3f027d23e797ac80e07c1024b22aaedc461f5 100644 (file)
@@ -7,6 +7,7 @@ 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=
+rname= # Remote branch to base this one on
 
 
 ## Parse options
@@ -14,8 +15,10 @@ name=
 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
@@ -27,22 +30,35 @@ while [ -n "$1" ]; do
 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"
@@ -52,14 +68,14 @@ fi
 [ -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
@@ -69,7 +85,8 @@ if [ -n "$merge" ]; then
        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
 
 
@@ -82,10 +99,11 @@ while [ -n "$merge" ]; do
        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
@@ -95,10 +113,11 @@ done
 
 ## 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%> *}>"
@@ -116,9 +135,9 @@ 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"
index f0114cc6b78568025116823078fc144c2e9cb05b..075d15c3ab76ea402cddce32fb72ff1b030ae8ff 100644 (file)
@@ -15,7 +15,7 @@ while [ -n "$1" ]; do
        -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)"
index 73ad2efde93508939db4e16b34cb4c9e407a127d..654b38bf50805c182f67f189ce4cf5cfecda3fc0 100644 (file)
@@ -18,7 +18,7 @@ while [ -n "$1" ]; do
        --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)"
@@ -32,7 +32,7 @@ base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)"
        die "not on a TopGit-controlled branch"
 
 
-playground="$(mktemp -d)"
+playground="$(mktemp -d -t tg-export.XXXXXX)"
 trap 'rm -rf "$playground"' EXIT
 
 
@@ -151,7 +151,7 @@ quilt()
 
        echo "Exporting $_dep"
        mkdir -p "$(dirname "$filename")"
-       tg patch "$_dep" >"$filename"
+       $tg patch "$_dep" >"$filename"
        echo "$_dep.diff -p1" >>"$output/series"
 }
 
@@ -161,7 +161,7 @@ quilt()
 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
@@ -190,7 +190,7 @@ recurse_deps driver "$name"
 
 
 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"
diff --git a/tg-import.sh b/tg-import.sh
new file mode 100644 (file)
index 0000000..6a4f79e
--- /dev/null
@@ -0,0 +1,90 @@
+#!/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
index 43589f9e635c82d908590be37f9d2ee931d6d1ba..b5b92f654116c852ddae266cbacb08765a914dfb 100644 (file)
@@ -12,7 +12,7 @@ while [ -n "$1" ]; do
        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)"
@@ -28,7 +28,7 @@ measure="$(measure_branch "$name" "$base_rev")"
 
 echo "Topic Branch: $name ($measure)"
 if [ "$(git rev-parse --short "$name")" = "$base_rev" ]; then
-       echo "No commits."
+       echo "No commits."
        exit 0
 fi
 
@@ -36,12 +36,22 @@ 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\`."
+       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
index 04023c05234d938d10acfb112a77554e7c0425f8..7a2471827d1e27db9a520f51541cecc4dd69c12a 100644 (file)
@@ -12,7 +12,7 @@ while [ -n "$1" ]; do
        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)"
@@ -29,7 +29,7 @@ echo
 [ -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?
@@ -43,4 +43,4 @@ rm "$git_is_stupid"
 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\`."
diff --git a/tg-remote.sh b/tg-remote.sh
new file mode 100644 (file)
index 0000000..4f60c73
--- /dev/null
@@ -0,0 +1,61 @@
+#!/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."
index 3905a8fab0207e9b3908e57d90e494f109b5efa1..409f45622910caf29b5f6d68a2930b3f76b6a274 100644 (file)
@@ -7,20 +7,34 @@
 ## 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=' '
@@ -35,6 +49,6 @@ git for-each-ref refs/top-bases |
                        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
index 27a8e81086f7eb71ba4c4c4d0e19aeabd6842a09..39769bead098c674ff8a8a12f7dfb48254191a7b 100644 (file)
@@ -9,7 +9,7 @@ name=
 ## Parse options
 
 if [ -n "$1" ]; then
-       echo "Usage: tg update" >&2
+       echo "Usage: tg [...] update" >&2
        exit 1
 fi
 
@@ -21,7 +21,7 @@ base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)"
 
 ## 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"
@@ -43,9 +43,10 @@ if [ -s "$depcheck" ]; then
                        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..."
@@ -53,7 +54,7 @@ if [ -s "$depcheck" ]; then
                                (
                                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."
@@ -67,10 +68,14 @@ if [ -s "$depcheck" ]; then
                                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
@@ -90,19 +95,45 @@ else
 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\`."
diff --git a/tg.sh b/tg.sh
index e5766fe8cc5b460a09a6ea4d5e9e105dc64d74a9..084dc5446e0fdf9e7cd5eaa4675ab7e4d1d3c06e 100644 (file)
--- a/tg.sh
+++ b/tg.sh
@@ -20,7 +20,7 @@ die()
 # 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!
@@ -77,7 +77,21 @@ measure_branch()
 # 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...]
@@ -95,18 +109,25 @@ recurse_deps()
        _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!
@@ -130,14 +151,19 @@ recurse_deps()
 # 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
@@ -150,7 +176,8 @@ branch_needs_update()
 # 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.
@@ -196,7 +223,7 @@ do_help()
                done
 
                echo "TopGit v0.2 - A different patch queue manager"
-               echo "Usage: tg ($cmds|help) ..."
+               echo "Usage: tg [-r REMOTE] ($cmds|help) ..."
        elif [ -r "@sharedir@/tg-$1.txt" ] ; then
                cat "@sharedir@/tg-$1.txt"
        else
@@ -210,6 +237,8 @@ do_help()
 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"
@@ -223,6 +252,11 @@ 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