From 074eb3f1b432d95880fcc2c5b388bebac75046e9 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 10 Aug 2008 22:30:15 +0200 Subject: [PATCH 1/1] tg-export: New command for cleaning up history --- Makefile | 2 +- README | 44 +++++++++++++++ tg-export.sh | 147 +++++++++++++++++++++++++++++++++++++++++++++++++++ tg.sh | 2 +- 4 files changed, 193 insertions(+), 2 deletions(-) create mode 100644 tg-export.sh diff --git a/Makefile b/Makefile index cf842d6..6eade1e 100644 --- a/Makefile +++ b/Makefile @@ -6,7 +6,7 @@ sharedir = $(PREFIX)/share/topgit hooksdir = $(cmddir)/hooks -commands_in = tg-create.sh tg-delete.sh tg-info.sh tg-patch.sh tg-summary.sh tg-update.sh +commands_in = tg-create.sh tg-delete.sh tg-export.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)) diff --git a/README b/README index a4839f4..275043b 100644 --- a/README +++ b/README @@ -261,6 +261,50 @@ tg summary TODO: Speed up by an order of magnitude TODO: Graph view +tg export +~~~~~~~~~ + Create a new branch containing tidied-up history + of the current topic branch and its dependencies, + suitable for pull by upstream - each topic branch + corresponds to a single commit in the cleaned up history + (corresponding basically exactly to `tg patch` output + for the topic branch). + + 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: + + git log --pretty=email -p --topo-order origin..exported + + To better understand the function of `tg export`, + consider this dependency structure of topic branches: + + origin/master - t/foo/blue - t/foo/red - master + `- t/bar/good <,----------' + `- t/baz ------------' + + (Where each of the branches may have hefty history.) Then + + master$ tg export for-linus + + will create this commit structure on branch for-linus: + + origin/master - t/foo/blue -. merge - t/foo/red -.. merge - master + `- t/bar/good <,-------------------'/ + `- t/baz ---------------------' + + The command works on the current topic branch + and requires one mandatory argument: the name of the branch + where the exported result shall be stored. + The branch will be silently overwritten if it exists already! + Use git reflog to recover in case of mistake. + + Usage: tg export BRANCH + + TODO: Make stripping of non-essential headers configurable + TODO: Make stripping of [PATCH] and other prefixes configurable + TODO: --quilt and --mbox options for other modes of operation + tg update ~~~~~~~~~ Update the current topic branch wrt. changes in the branches diff --git a/tg-export.sh b/tg-export.sh new file mode 100644 index 0000000..9f19300 --- /dev/null +++ b/tg-export.sh @@ -0,0 +1,147 @@ +#!/bin/sh +# TopGit - A different patch queue manager +# (c) Petr Baudis 2008 +# GPLv2 + +name= +output= + + +## Parse options + +while [ -n "$1" ]; do + arg="$1"; shift + case "$arg" in + -*) + echo "Usage: tg export NEWBRANCH" >&2 + exit 1;; + *) + [ -z "$output" ] || die "new branch already specified ($output)" + output="$arg";; + esac +done + + +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 on a TopGit-controlled branch" + +[ -n "$output" ] || + die "no target branch specified" + +! git rev-parse --verify "$output" >/dev/null 2>&1 || + die "target branch '$output' already exists; first run: git branch -D $output" + + +playground="$(mktemp -d)" +trap 'rm -rf "$playground"' EXIT + +# 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() +{ + (export GIT_INDEX_FILE="$playground/^index" + git read-tree "$1" + git update-index --force-remove ".top*" + git write-tree) +} + +# collapsed_commit NAME +# Produce a collapsed commit of branch NAME. +collapsed_commit() +{ + name="$1" + + rm -f "$playground/^pre" "$playground/^post" + >"$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" + + # Determine parent + parent="$(cut -f 1 "$playground/$name^parents")" + if [ "$(cat "$playground/$name^parents" | wc -l)" -gt 1 ]; then + # Produce a merge commit first + parent="$({ + echo "TopGit-driven merge of branches:" + echo + cut -f 2 "$playground/$name^parents" + } | git commit-tree "$(pretty_tree "refs/top-bases/$name")" \ + $(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" + + echo "$name" >>"$playground/^ticker" +} + +# collapse_one +# This will collapse a single branch, using information about +# previously collapsed branches stored in $playground. +collapse_one() +{ + branch_needs_update >/dev/null + [ "$_ret" -eq 0 ] || + die "cancelling $_ret export of $_dep (-> $_name): branch not up-to-date" + + if [ -s "$playground/$_dep" ]; then + # We've already seen this dep + commit="$(cat "$playground/$_dep")" + + elif [ -z "$_dep_is_tgish" ]; then + # This dep is not for rewrite + commit="$(git rev-parse --verify "$_dep")" + + else + # First time hitting this dep; the common case + commit="$(collapsed_commit "$_dep")" + + mkdir -p "$playground/$(dirname "$_dep")" + echo "$commit" >"$playground/$_dep" + echo "Collapsed $_dep" + fi + + # Propagate our work through the dependency chain + mkdir -p "$playground/$(dirname "$_name")" + echo "$commit $_dep" >>"$playground/$_name^parents" +} + +# Collapse all the branches - this way, collapse_one will be +# called in topological order. +recurse_deps collapse_one "$name" +(_ret=0; _dep="$name"; _name=""; _dep_is_tgish=1; collapse_one) + +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.sh b/tg.sh index 4fef779..a844b5e 100644 --- a/tg.sh +++ b/tg.sh @@ -139,7 +139,7 @@ branch_needs_update() # _dep needs to be synced with its base echo ": $_dep $_depchain" _ret=1 - elif ! branch_contains "refs/top-bases/$_name" "$_dep"; then + elif [ -n "$_name" ] && ! branch_contains "refs/top-bases/$_name" "$_dep"; then # Some new commits in _dep echo "$_dep $_depchain" _ret=1 -- 2.30.2