in the cleaned up history (corresponding basically exactly
to `tg patch` output for the topic branch).
- The command has two possible outputs now - either a Git branch
- with the collapsed history, or a quilt series in new directory.
+ The command has three possible outputs now - either a Git branch with
+ the collapsed history, a Git branch with a linearized history, or a
+ quilt series in new directory.
In case of producing collapsed history in new branch,
- You can use this collapsed structure either for providing
+ you can use this collapsed structure either for providing
a pull source for upstream, or further linearization e.g.
for creation of a quilt series using git log:
`- t/bar/good <,-------------------'/
`- t/baz ---------------------'
+ In case of using the linearize mode:
+
+ master$ tg export --linearize for-linus
+
+ you get a linear history respecting the dependencies of your patches in
+ a new branch for-linus. The result should be more or less the same as
+ using quilt mode and reimporting it into a Git branch. (More or less
+ because the topologic order can usually be extended in more than one
+ way into a complete ordering and the two methods may choose different
+ one's.) The result might be more appropriate for merging upstream as
+ it contains fewer merges.
+
+ Note that you might get conflicts during linearization because the
+ patches are reordered to get a linear history.
+
In case of the quilt mode,
master$ tg export --quilt for-linus
names get a number as prefix to allow getting the order without
consulting the series file, which eases sending out the patches.
- Usage: tg export ([--collapse] BRANCH | --quilt DIR)
+ Usage: tg export ([(--collapse | --linearize)] BRANCH | --quilt DIR)
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: -a option to export all branches
- TODO: For quilt exporting, use a temporary branch and remove it when
- done - this would allow producing conflict-less series
+ TODO: For quilt exporting, export the linearized history created in a
+ temporary branch---this would allow producing conflict-less
+ series
tg import
~~~~~~~~~
rm -f "$git_dir/top-name" "$git_dir/top-deps" "$git_dir/top-merge"
-## Create base
+## Find starting commit to create the base
-if [ -n "$merge" ]; then
+if [ -n "$merge" -a -z "$restarted" ]; then
# Unshift the first item from the to-merge list
branch="${merge%% *}"
merge="${merge#* }"
driver=quilt;;
--collapse)
driver=collapse;;
+ --linearize)
+ driver=linearize;;
-*)
- echo "Usage: tg [...] export ([--collapse] NEWBRANCH | [-b BRANCH1,BRANCH2...] --quilt DIRECTORY)" >&2
+ echo "Usage: tg [...] export ([--collapse] NEWBRANCH | [-b BRANCH1,BRANCH2...] --quilt DIRECTORY | --linearize NEWBRANCH)" >&2
exit 1;;
*)
[ -z "$output" ] || die "output already specified ($output)"
git write-tree)
}
-# collapsed_commit NAME
-# Produce a collapsed commit of branch NAME.
-collapsed_commit()
+create_tg_commit()
{
name="$1"
-
- rm -f "$playground/^pre" "$playground/^post"
- >"$playground/^body"
+ tree="$2"
+ parent="$3"
# Get commit message and authorship information
git cat-file blob "$name:.topmsg" | git mailinfo "$playground/^msg" /dev/null > "$playground/^info"
test -n "$GIT_AUTHOR_EMAIL" && export GIT_AUTHOR_EMAIL
test -n "$GIT_AUTHOR_DATE" && export GIT_AUTHOR_DATE
+ (printf '%s\n\n' "$SUBJECT"; cat "$playground/^msg") |
+ git stripspace |
+ git commit-tree "$tree" -p "$parent"
+}
+
+# collapsed_commit NAME
+# Produce a collapsed commit of branch NAME.
+collapsed_commit()
+{
+ name="$1"
+
+ rm -f "$playground/^pre" "$playground/^post"
+ >"$playground/^body"
+
# Determine parent
parent="$(cut -f 1 "$playground/$name^parents")"
if [ "$(cat "$playground/$name^parents" | wc -l)" -gt 1 ]; then
if branch_empty "$name"; then
echo "$parent";
else
- (printf '%s\n\n' "$SUBJECT"; cat "$playground/^msg") |
- git stripspace |
- git commit-tree "$(pretty_tree "$name")" -p "$parent"
+ create_tg_commit "$name" "$(pretty_tree $name)" "$parent"
fi;
echo "$name" >>"$playground/^ticker"
fi
}
+linearize()
+{
+ if test ! -f "$playground/^BASE"; then
+ head="$(git rev-parse --verify "$_dep")"
+ echo "$head" > "$playground/^BASE"
+ git checkout -q "$head"
+ return;
+ fi;
+
+ head=$(git rev-parse --verify HEAD)
+
+ if [ -z "$_dep_is_tgish" ]; then
+ # merge in $_dep unless already included
+ rev="$(git rev-parse --verify "$_dep")";
+ common="$(git merge-base --all HEAD "$_dep")";
+ if test "$rev" = "$common"; then
+ # already included, just skip
+ :;
+ else
+ git merge -s recursive "$_dep";
+ retmerge="$?";
+ if test "x$retmerge" != "x0"; then
+ echo fix up the merge, commit and then exit;
+ #todo error handling
+ sh -i
+ fi;
+ fi;
+ else
+ git merge-recursive "$(pretty_tree "refs/top-bases/$_dep")" -- HEAD "$(pretty_tree "refs/heads/$_dep")";
+ retmerge="$?";
+
+ if test "x$retmerge" != "x0"; then
+ echo "fix up the merge and update the index. Don't commit!"
+ #todo error handling
+ sh -i
+ fi
+
+ result_tree=$(git write-tree)
+ # testing branch_empty might not always give the right answer.
+ # It can happen that the patch is non-empty but still after
+ # linearizing there is no change. So compare the trees.
+ if test "x$result_tree" = "x$(git rev-parse $head^{tree})"; then
+ echo "skip empty commit $_dep";
+ else
+ newcommit=$(create_tg_commit "$_dep" "$result_tree" HEAD)
+ git update-ref HEAD $newcommit $head
+ echo "exported commit $_dep";
+ fi
+ fi
+}
## Machinery
-if [ "$driver" = "collapse" ]; then
+if [ "$driver" = "collapse" ] || [ "$driver" = "linearize" ]; then
[ -n "$output" ] ||
die "no target branch specified"
! ref_exists "$output" ||
elif [ "$driver" = "quilt" ]; then
depcount="$(cat "$output/series" | wc -l)"
echo "Exported topic branch $name (total $depcount topics) to directory $output"
+
+elif [ "$driver" = "linearize" ]; then
+ git checkout -q -b $output
+
+ echo $name
+ if test $(git rev-parse "$(pretty_tree $name)^{tree}") != $(git rev-parse "HEAD^{tree}"); then
+ echo "Warning: Exported result doesn't match";
+ echo "tg-head=$(git rev-parse "$name"), exported=$(git rev-parse "HEAD")";
+ #git diff $head HEAD;
+ fi;
+
fi
# vim:noet
git for-each-ref refs/top-bases |
while read rev type ref; do
name="${ref#refs/top-bases/}"
+ if branch_annihilated "$name"; then
+ continue;
+ fi;
+
if [ -n "$terse" ]; then
echo "$name"
continue
fi
if [ -n "$graphviz" ]; then
git cat-file blob "$name:.topdeps" | while read dep; do
- echo "\"$name\" -> \"$dep\";"
+ dep_is_tgish=true
+ ref_exists "refs/top-bases/$dep" ||
+ dep_is_tgish=false
+ if ! "$dep_is_tgish" || ! branch_annihilated $dep; then
+ echo "\"$name\" -> \"$dep\";"
+ fi
done
continue
fi
# Whether B1 is a superset of B2.
branch_contains()
{
- [ -z "$(git rev-list ^"$1" "$2" --)" ]
+ [ -z "$(git rev-list --max-count=1 ^"$1" "$2" --)" ]
}
# ref_exists REF
[ -n "$base_remote" ] && ref_exists "remotes/$base_remote/$1"
}
+branch_annihilated()
+{
+ _name="$1";
+
+ # use the merge base in case the base is ahead.
+ mb="$(git merge-base "refs/top-bases/$_name" "$_name")";
+
+ test "$(git rev-parse "$mb^{tree}")" = "$(git rev-parse "$_name^{tree}")";
+}
+
# recurse_deps CMD NAME [BRANCHPATH...]
# Recursively eval CMD on all dependencies of NAME.
# CMD can refer to $_name for queried branch name,
if has_remote "top-bases/$_name"; then
echo "refs/remotes/$base_remote/top-bases/$_name" >>"$_depsfile"
fi
- git cat-file blob "$_name:.topdeps" >>"$_depsfile"
+
+ # if the branch was annihilated, there exists no .topdeps file
+ if ! branch_annihilated "$_name"; then
+ #TODO: handle nonexisting .topdeps?
+ git cat-file blob "$_name:.topdeps" >>"$_depsfile";
+ fi;
_ret=0
while read _dep; do