tg update: Do not use ${:n:m} substitution (bash extension)
[topgit/greenrd.git] / tg-export.sh
blobdb491691e6b17923b78b20389a0af59414ec20a1
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # (c) Petr Baudis <pasky@suse.cz> 2008
4 # GPLv2
6 name=
7 output=
10 ## Parse options
12 while [ -n "$1" ]; do
13 arg="$1"; shift
14 case "$arg" in
15 -*)
16 echo "Usage: tg export NEWBRANCH" >&2
17 exit 1;;
19 [ -z "$output" ] || die "new branch already specified ($output)"
20 output="$arg";;
21 esac
22 done
25 name="$(git symbolic-ref HEAD | sed 's#^refs/heads/##')"
26 base_rev="$(git rev-parse --short --verify "refs/top-bases/$name" 2>/dev/null)" ||
27 die "not on a TopGit-controlled branch"
29 [ -n "$output" ] ||
30 die "no target branch specified"
32 ! git rev-parse --verify "$output" >/dev/null 2>&1 ||
33 die "target branch '$output' already exists; first run: git branch -D $output"
36 playground="$(mktemp -d)"
37 trap 'rm -rf "$playground"' EXIT
39 # Trusty Cogito code:
40 load_author()
42 if [ -z "$GIT_AUTHOR_NAME" ] && echo "$1" | grep -q '^[^< ]'; then
43 export GIT_AUTHOR_NAME="$(echo "$1" | sed 's/ *<.*//')"
45 if [ -z "$GIT_AUTHOR_EMAIL" ] && echo "$1" | grep -q '<.*>'; then
46 export GIT_AUTHOR_EMAIL="$(echo "$1" | sed 's/.*<\(.*\)>.*/\1/')"
50 # pretty_tree NAME
51 # Output tree ID of a cleaned-up tree without tg's artifacts.
52 pretty_tree()
54 (export GIT_INDEX_FILE="$playground/^index"
55 git read-tree "$1"
56 git update-index --force-remove ".topmsg" ".topdeps"
57 git write-tree)
60 # collapsed_commit NAME
61 # Produce a collapsed commit of branch NAME.
62 collapsed_commit()
64 name="$1"
66 rm -f "$playground/^pre" "$playground/^post"
67 >"$playground/^body"
69 # Get commit message and authorship information
70 git cat-file blob "$name:.topmsg" >"$playground/^msg"
71 while read line; do
72 if [ -z "$line" ]; then
73 # end of header
74 cat >"$playground/^body"
75 break
77 case "$line" in
78 From:*) load_author "${line#From: }";;
79 Subject:*) echo "${line#Subject: }" >>"$playground/^pre";;
80 *) echo "$line" >>"$playground/^post";;
81 esac
82 done <"$playground/^msg"
84 # Determine parent
85 parent="$(cut -f 1 "$playground/$name^parents")"
86 if [ "$(cat "$playground/$name^parents" | wc -l)" -gt 1 ]; then
87 # Produce a merge commit first
88 parent="$({
89 echo "TopGit-driven merge of branches:"
90 echo
91 cut -f 2 "$playground/$name^parents"
92 } | git commit-tree "$(pretty_tree "refs/top-bases/$name")" \
93 $(for p in $parent; do echo -p $p; done))"
97 if [ -s "$playground/^pre" ]; then
98 cat "$playground/^pre"
99 echo
101 cat "$playground/^body"
102 [ ! -s "$playground/^post" ] || cat "$playground/^post"
103 } | git commit-tree "$(pretty_tree "$name")" -p "$parent"
105 echo "$name" >>"$playground/^ticker"
108 # collapse_one
109 # This will collapse a single branch, using information about
110 # previously collapsed branches stored in $playground.
111 collapse_one()
113 branch_needs_update >/dev/null
114 [ "$_ret" -eq 0 ] ||
115 die "cancelling $_ret export of $_dep (-> $_name): branch not up-to-date"
117 if [ -s "$playground/$_dep" ]; then
118 # We've already seen this dep
119 commit="$(cat "$playground/$_dep")"
121 elif [ -z "$_dep_is_tgish" ]; then
122 # This dep is not for rewrite
123 commit="$(git rev-parse --verify "$_dep")"
125 else
126 # First time hitting this dep; the common case
127 commit="$(collapsed_commit "$_dep")"
129 mkdir -p "$playground/$(dirname "$_dep")"
130 echo "$commit" >"$playground/$_dep"
131 echo "Collapsed $_dep"
134 # Propagate our work through the dependency chain
135 mkdir -p "$playground/$(dirname "$_name")"
136 echo "$commit $_dep" >>"$playground/$_name^parents"
139 # Collapse all the branches - this way, collapse_one will be
140 # called in topological order.
141 recurse_deps collapse_one "$name"
142 (_ret=0; _dep="$name"; _name=""; _dep_is_tgish=1; collapse_one)
144 git update-ref "refs/heads/$output" "$(cat "$playground/$name")"
146 depcount="$(cat "$playground/^ticker" | wc -l)"
147 echo "Exported topic branch $name (total $depcount topics) to branch $output"