chiark / gitweb /
tg update: Do not use ${:n:m} substitution (bash extension)
[topgit.git] / tg-export.sh
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # (c) Petr Baudis <pasky@suse.cz>  2008
4 # GPLv2
5
6 name=
7 output=
8
9
10 ## Parse options
11
12 while [ -n "$1" ]; do
13         arg="$1"; shift
14         case "$arg" in
15         -*)
16                 echo "Usage: tg export NEWBRANCH" >&2
17                 exit 1;;
18         *)
19                 [ -z "$output" ] || die "new branch already specified ($output)"
20                 output="$arg";;
21         esac
22 done
23
24
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"
28
29 [ -n "$output" ] ||
30         die "no target branch specified"
31
32 ! git rev-parse --verify "$output" >/dev/null 2>&1 ||
33         die "target branch '$output' already exists; first run: git branch -D $output"
34
35
36 playground="$(mktemp -d)"
37 trap 'rm -rf "$playground"' EXIT
38
39 # Trusty Cogito code:
40 load_author()
41 {
42         if [ -z "$GIT_AUTHOR_NAME" ] && echo "$1" | grep -q '^[^< ]'; then
43                 export GIT_AUTHOR_NAME="$(echo "$1" | sed 's/ *<.*//')"
44         fi
45         if [ -z "$GIT_AUTHOR_EMAIL" ] && echo "$1" | grep -q '<.*>'; then
46                 export GIT_AUTHOR_EMAIL="$(echo "$1" | sed 's/.*<\(.*\)>.*/\1/')"
47         fi
48 }
49
50 # pretty_tree NAME
51 # Output tree ID of a cleaned-up tree without tg's artifacts.
52 pretty_tree()
53 {
54         (export GIT_INDEX_FILE="$playground/^index"
55          git read-tree "$1"
56          git update-index --force-remove ".topmsg" ".topdeps"
57          git write-tree)
58 }
59
60 # collapsed_commit NAME
61 # Produce a collapsed commit of branch NAME.
62 collapsed_commit()
63 {
64         name="$1"
65
66         rm -f "$playground/^pre" "$playground/^post"
67         >"$playground/^body"
68
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
76                 fi
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"
83
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))"
94         fi
95
96         {
97                 if [ -s "$playground/^pre" ]; then
98                         cat "$playground/^pre"
99                         echo
100                 fi
101                 cat "$playground/^body"
102                 [ ! -s "$playground/^post" ] || cat "$playground/^post"
103         } | git commit-tree "$(pretty_tree "$name")" -p "$parent"
104
105         echo "$name" >>"$playground/^ticker"
106 }
107
108 # collapse_one
109 # This will collapse a single branch, using information about
110 # previously collapsed branches stored in $playground.
111 collapse_one()
112 {
113         branch_needs_update >/dev/null
114         [ "$_ret" -eq 0 ] ||
115                 die "cancelling $_ret export of $_dep (-> $_name): branch not up-to-date"
116
117         if [ -s "$playground/$_dep" ]; then
118                 # We've already seen this dep
119                 commit="$(cat "$playground/$_dep")"
120
121         elif [ -z "$_dep_is_tgish" ]; then
122                 # This dep is not for rewrite
123                 commit="$(git rev-parse --verify "$_dep")"
124
125         else
126                 # First time hitting this dep; the common case
127                 commit="$(collapsed_commit "$_dep")"
128
129                 mkdir -p "$playground/$(dirname "$_dep")"
130                 echo "$commit" >"$playground/$_dep"
131                 echo "Collapsed $_dep"
132         fi
133
134         # Propagate our work through the dependency chain
135         mkdir -p "$playground/$(dirname "$_name")"
136         echo "$commit   $_dep" >>"$playground/$_name^parents"
137 }
138
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)
143
144 git update-ref "refs/heads/$output" "$(cat "$playground/$name")"
145
146 depcount="$(cat "$playground/^ticker" | wc -l)"
147 echo "Exported topic branch $name (total $depcount topics) to branch $output"