tg-delete.txt
tg-info
tg-info.txt
+tg-mail
+tg-mail.txt
tg-patch
tg-patch.txt
tg-summary
tg-update.txt
tg-export
tg-export.txt
+tg-import
+tg-import.txt
+tg-remote
+tg-remote.txt
tg
+.*.swp
all:: tg $(commands_out) $(hooks_out) $(help_out)
-tg $(commands_out) $(hooks_out): % : %.sh
+tg $(commands_out) $(hooks_out): % : %.sh Makefile
@echo "[SED] $@"
@sed -e 's#@cmddir@#$(cmddir)#g;' \
-e 's#@hooksdir@#$(hooksdir)#g' \
TODO: '-a' to delete all empty branches, depfix, revert
+tg depend
+~~~~~~~~~
+ Change dependencies of a TopGit-controlled topic branch.
+ This should have several subcommands, but only 'add' is
+ supported right now.
+
+ The 'add' subcommand takes an argument of a topic branch
+ to be added, adds it to '.topdeps', performs a commit and
+ then updates your topic branch accordingly. If you want to
+ do other things related to the dependency addition, like
+ adjusting '.topmsg', prepare them in the index before
+ calling 'tg depend add'.
+
tg info
~~~~~~~
Show a summary information about the current or specified
TODO: tg patch -i to base at index instead of branch,
-w for working tree
+tg mail
+~~~~~~~
+ Send a patch from the current or specified topic branch as
+ email.
+
+ Takes the patch given on the command line and emails it out.
+ Destination addresses such as To, Cc and Bcc are taken from the
+ patch header.
+
+ Since it actually boils down to `git send-email` please refer to
+ its documentation for details on how to setup email for git.
+ You can pass arbitrary options to this command through the
+ '-s' parameter, but you must double-quote everything.
+
+ TODO: 'tg mail patchfile' to mail an already exported patch
+ TODO: mailing patch series
+ TODO: specifying additional options and addresses on command
+ line
+
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.
+ and 'git push' to operate on them. (Do NOT use 'git push --all'
+ for your pushes - plain 'git push' will do the right thing.)
It takes a mandatory remote name argument, and optional
'--populate' switch - use that for your origin-style remote,
and an argument specifying the directory
where the quilt series should be saved.
+ With '--quilt', you can also pass '-b' parameter followed by
+ a comma-separated explicit list of branches to export. This
+ mode of operation is currently not supported with collapse.
+
Usage: tg export ([--collapse] BRANCH | --quilt DIR)
TODO: Make stripping of non-essential headers 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
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" ] ||
+! git symbolic-ref HEAD >/dev/null || [ "$(git symbolic-ref HEAD)" != "refs/heads/$name" ] ||
die "cannot delete your current branch"
nonempty=
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# (c) Petr Baudis <pasky@suse.cz> 2008
+# GPLv2
+
+name=
+
+
+## Parse options
+
+subcmd="$1"; shift
+[ "$subcmd" = "add" ] || die "unknown subcommand ($subcmd)"
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -*)
+ echo "Usage: tg [...] depend add 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"
+
+
+## Record new dependency
+
+echo "$name" >>.topdeps
+git add .topdeps
+git commit -m"New TopGit dependency: $name"
+$tg update
# GPLv2
name=
+branches=
output=
driver=collapse
while [ -n "$1" ]; do
arg="$1"; shift
case "$arg" in
+ -b)
+ branches="$1"; shift;;
--quilt)
driver=quilt;;
--collapse)
driver=collapse;;
-*)
- echo "Usage: tg [...] export ([--collapse] NEWBRANCH | --quilt DIRECTORY)" >&2
+ echo "Usage: tg [...] export [-b BRANCH1,BRANCH2...] ([--collapse] NEWBRANCH | --quilt DIRECTORY)" >&2
exit 1;;
*)
[ -z "$output" ] || die "output already specified ($output)"
base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" ||
die "not on a TopGit-controlled branch"
+[ -z "$branches" -o "$driver" = "quilt" ] ||
+ die "-b works only with the quilt driver"
+
playground="$(mktemp -d -t tg-export.XXXXXX)"
trap 'rm -rf "$playground"' EXIT
## Collapse driver
-# Trusty Cogito code:
-load_author()
-{
- if [ -z "$GIT_AUTHOR_NAME" ] && echo "$1" | grep -q '^[^< ]'; then
- export GIT_AUTHOR_NAME="$(echo "$1" | sed 's/ *<.*//')"
- fi
- if [ -z "$GIT_AUTHOR_EMAIL" ] && echo "$1" | grep -q '<.*>'; then
- export GIT_AUTHOR_EMAIL="$(echo "$1" | sed 's/.*<\(.*\)>.*/\1/')"
- fi
-}
-
# pretty_tree NAME
# Output tree ID of a cleaned-up tree without tg's artifacts.
pretty_tree()
>"$playground/^body"
# Get commit message and authorship information
- git cat-file blob "$name:.topmsg" >"$playground/^msg"
- while read line; do
- if [ -z "$line" ]; then
- # end of header
- cat >"$playground/^body"
- break
- fi
- case "$line" in
- From:*) load_author "${line#From: }";;
- Subject:*) echo "${line#Subject: }" >>"$playground/^pre";;
- *) echo "$line" >>"$playground/^post";;
- esac
- done <"$playground/^msg"
+ git cat-file blob "$name:.topmsg" | git mailinfo "$playground/^msg" /dev/null > "$playground/^info"
+
+ GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$playground/^info")"
+ GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$playground/^info")"
+ GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$playground/^info")"
+ SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$playground/^info")"
+
+ test -n "$GIT_AUTHOR_NAME" && export GIT_AUTHOR_NAME
+ test -n "$GIT_AUTHOR_EMAIL" && export GIT_AUTHOR_EMAIL
+ test -n "$GIT_AUTHOR_DATE" && export GIT_AUTHOR_DATE
# Determine parent
parent="$(cut -f 1 "$playground/$name^parents")"
$(for p in $parent; do echo -p $p; done))"
fi
- {
- if [ -s "$playground/^pre" ]; then
- cat "$playground/^pre"
- echo
- fi
- cat "$playground/^body"
- [ ! -s "$playground/^post" ] || cat "$playground/^post"
- } | git commit-tree "$(pretty_tree "$name")" -p "$parent"
+ (printf '%s\n\n' "$SUBJECT"; cat "$playground/^msg") |
+ git stripspace |
+ git commit-tree "$(pretty_tree "$name")" -p "$parent"
echo "$name" >>"$playground/^ticker"
}
driver()
{
+ case $_dep in refs/remotes/*) return;; esac
branch_needs_update >/dev/null
[ "$_ret" -eq 0 ] ||
die "cancelling export of $_dep (-> $_name): branch not up-to-date"
# Call driver on all the branches - this will happen
# in topological order.
-recurse_deps driver "$name"
-(_ret=0; _dep="$name"; _name=""; _dep_is_tgish=1; driver)
+if [ -z "$branches" ]; then
+ recurse_deps driver "$name"
+ (_ret=0; _dep="$name"; _name=""; _dep_is_tgish=1; driver)
+else
+ echo "$branches" | tr ',' '\n' | while read _dep; do
+ _dep_is_tgish=1
+ $driver
+ done
+ name="$(echo "$branches" | sed 's/.*,//')"
+fi
if [ "$driver" = "collapse" ]; then
done
+## Make sure our tree is clean
+
+git update-index --ignore-submodules --refresh || exit
+[ -z "$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)" ] ||
+ die "the index is not clean"
+
+
+## Perform import
+
get_commit_msg()
{
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"
+ git cherry-pick --no-commit "$commit"
get_commit_msg "$commit" > .topmsg
git add -f .topmsg .topdeps
git commit -C "$commit"
esac
done
-[ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')"
+[ -n "$name" ] || 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"
exit 0
fi
-git cat-file blob "$name:.topmsg" | grep ^Subject:
+git cat-file blob "$name:.topmsg" | grep ^Subject: || :
echo "Base: $base_rev"
branch_contains "$name" "$base_rev" ||
--- /dev/null
+#!/bin/sh
+# TopGit - A different patch queue manager
+# GPLv2
+
+name=
+send_email_args=
+
+
+## Parse options
+
+while [ -n "$1" ]; do
+ arg="$1"; shift
+ case "$arg" in
+ -s)
+ send_email_args="$1"; shift;;
+ -*)
+ echo "Usage: tg [...] mail [-s SEND_EMAIL_ARGS] [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"
+
+
+patchfile="$(mktemp -t tg-mail.XXXXXX)"
+
+$tg patch $name >"$patchfile"
+
+hlines=$(grep -n -m 1 '^---' "$patchfile" | sed 's/:---//')
+header=$(head -n $(($hlines - 1)) "$patchfile")
+
+
+
+from="$(echo "$header" | grep '^From:' | sed 's/From:\s*//')"
+to="$(echo "$header" | grep '^To:' | sed 's/To:\s*//')"
+
+
+# XXX: I can't get quoting right without arrays
+people=()
+[ -n "$from" ] && people=("${people[@]}" --from "$from")
+# FIXME: there could be multimple To
+[ -n "$to" ] && people=("${people[@]}" --to "$to")
+
+
+# NOTE: git-send-email handles cc itself
+git send-email $send_email_args "${people[@]}" "$patchfile"
+
+rm "$patchfile"
esac
done
-[ -n "$name" ] || name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')"
+[ -n "$name" ] || 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"
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
# Another job well done!
return
fi
- # Prepare incanation
+ # Prepare incantation
if [ -x "$git_dir/hooks/$1" ]; then
hook_call="$hook_call"' || exit $?'
else
sep="|"
done
- echo "TopGit v0.3 - A different patch queue manager"
+ echo "TopGit v0.4 - A different patch queue manager"
echo "Usage: tg [-r REMOTE] ($cmds|help) ..."
- elif [ -r "@sharedir@/tg-$1.txt" ] ; then
- cat "@sharedir@/tg-$1.txt"
+ elif [ -r "@cmddir@"/tg-$1 ] ; then
+ @cmddir@/tg-$1 -h || :
+ echo
+ if [ -r "@sharedir@/tg-$1.txt" ] ; then
+ cat "@sharedir@/tg-$1.txt"
+ fi
else
echo "`basename $0`: no help for $1" 1>&2
fi