tg-export.sh: support --quilt --force and tidy up usage
[topgit/pro.git] / tg-export.sh
blob922e211df790566c0d1d4b59412453f315018b92
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) 2008 Petr Baudis <pasky@suse.cz>
4 # Copyright (C) 2015-2017 Kyle J. McKay <mackyle@gmail.com>
5 # All rights reserved
6 # GPLv2
8 USAGE="\
9 Usage: ${tgname:-tg} [...] export [--collapse] [--force] <newbranch>
10 Or: ${tgname:-tg} [...] export --linearize [--force] <newbranch>
11 Or: ${tgname:-tg} [...] export --quilt [--force] [-a | --all | -b <branch>...]
12 [--binary] [--flatten] [--numbered] [--strip[=N]] <directory>"
14 usage()
16 if [ "${1:-0}" != 0 ]; then
17 printf '%s\n' "$USAGE" >&2
18 else
19 printf '%s\n' "$USAGE"
21 exit ${1:-0}
24 name=
25 branches=
26 forceoutput=
27 checkout_opt=-b
28 output=
29 driver=collapse
30 flatten=false
31 numbered=false
32 strip=false
33 stripval=0
34 allbranches=false
35 binary=
36 pl=
38 ## Parse options
40 while [ -n "$1" ]; do
41 arg="$1"; shift
42 case "$arg" in
43 -h|--help)
44 usage;;
45 -a|--all)
46 allbranches=true;;
47 -b)
48 branches="${branches:+$branches }$1"; shift;;
49 --force)
50 forceoutput=1;;
51 --flatten)
52 flatten=true;;
53 --binary)
54 binary=1;;
55 --numbered)
56 flatten=true
57 numbered=true;;
58 --strip*)
59 val=${arg#*=}
60 if [ "$val" = "--strip" ]; then
61 strip=true
62 stripval=9999
63 elif [ -n "$val" -a "x$(echol "$val" | sed -e 's/[0-9]//g')" = "x" ]; then
64 strip=true
65 stripval=$val
66 else
67 die "invalid parameter $arg"
68 fi;;
69 --quilt)
70 driver=quilt;;
71 --collapse)
72 driver=collapse;;
73 --linearize)
74 driver=linearize;;
75 -*)
76 usage 1;;
78 [ -z "$output" ] || die "output already specified ($output)"
79 output="$arg";;
80 esac
81 done
83 [ -z "$branches" ] || [ "$driver" = "quilt" ] ||
84 die "-b works only with the quilt driver"
86 [ "$driver" = "quilt" ] || ! "$numbered" ||
87 die "--numbered works only with the quilt driver"
89 [ "$driver" = "quilt" ] || ! "$flatten" ||
90 die "--flatten works only with the quilt driver"
92 [ "$driver" = "quilt" ] || [ -z "$binary" ] ||
93 die "--binary works only with the quilt driver"
95 [ "$driver" = "quilt" ] || ! "$strip" ||
96 die "--strip works only with the quilt driver"
98 [ "$driver" = "quilt" ] || ! "$allbranches" ||
99 die "--all works only with the quilt driver"
101 [ -z "$branches" ] || ! "$allbranches" ||
102 die "-b conflicts with the --all option"
104 if [ "$driver" = "linearize" ]; then
105 setup_git_dir_is_bare
106 if [ -n "$git_dir_is_bare" ]; then
107 fatal 'export --linearize does not work on a bare repository...yet!'
108 ensure_work_tree
112 if [ -z "$branches" ] && ! "$allbranches"; then
113 # this check is only needed when no branches have been passed
114 name="$(verify_topgit_branch HEAD)"
117 if [ -n "$branches" ]; then
118 oldbranches="$branches"
119 branches=
120 while read bname && [ -n "$bname" ]; do
121 branches="${branches:+$branches }$(verify_topgit_branch "$bname")"
122 done <<-EOT
123 $(sed 'y/ /\n/' <<-LIST
124 $oldbranches
125 LIST
128 unset oldbranches bname
131 read -r nowsecs nowtzoff <<EOT
132 $(date '+%s %z')
134 playground="$(get_temp tg-export -d)"
137 ## Collapse driver
139 bump_timestamp()
141 nowsecs=$(( $nowsecs + 1 ))
144 create_tg_commit()
146 name="$1"
147 tree="$2"
148 parent="$3"
150 # Get commit message and authorship information
151 git cat-file blob "$name:.topmsg" 2>/dev/null |
152 git mailinfo "$playground/^msg" /dev/null > "$playground/^info"
154 unset GIT_AUTHOR_NAME
155 unset GIT_AUTHOR_EMAIL
157 GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$playground/^info")"
158 GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$playground/^info")"
159 GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$playground/^info")"
160 SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$playground/^info")"
162 test -n "$GIT_AUTHOR_NAME" && export GIT_AUTHOR_NAME
163 test -n "$GIT_AUTHOR_EMAIL" && export GIT_AUTHOR_EMAIL
165 GIT_COMMITTER_DATE="$nowsecs $nowtzoff"
166 : ${GIT_AUTHOR_DATE:=$GIT_COMMITTER_DATE}
167 export GIT_AUTHOR_DATE
168 export GIT_COMMITTER_DATE
170 (printf '%s\n\n' "$SUBJECT"; cat "$playground/^msg") |
171 git stripspace |
172 git commit-tree "$tree" -p "$parent"
174 unset GIT_AUTHOR_NAME
175 unset GIT_AUTHOR_EMAIL
176 unset GIT_AUTHOR_DATE
177 unset GIT_COMMITTER_DATE
180 # collapsed_commit NAME
181 # Produce a collapsed commit of branch NAME.
182 collapsed_commit()
184 name="$1"
186 rm -f "$playground/^pre" "$playground/^post"
187 >"$playground/^body"
189 # Determine parent
190 [ -s "$playground/$name^parents" ] || git rev-parse --verify "refs/$topbases/$name^0" -- >> "$playground/$name^parents"
191 parent="$(cut -f 1 "$playground/$name^parents" 2> /dev/null |
192 while read -r p; do git rev-parse --quiet --verify "$p^0" -- || :; done)"
193 if [ $(( $(cat "$playground/$name^parents" 2>/dev/null | wc -l) )) -gt 1 ]; then
194 # Produce a merge commit first
195 parent="$({
196 echo "TopGit-driven merge of branches:"
197 echo
198 cut -f 2 "$playground/$name^parents"
199 } | GIT_AUTHOR_DATE="$nowsecs $nowtzoff" \
200 GIT_COMMITTER_DATE="$nowsecs $nowtzoff" \
201 git commit-tree "$(pretty_tree "$name" -b)" \
202 $(for p in $parent; do echo "-p $p"; done))"
205 if branch_empty "$name"; then
206 echol "$parent"
207 else
208 create_tg_commit "$name" "$(pretty_tree "$name")" "$parent"
211 echol "$name" >>"$playground/^ticker"
214 # collapse
215 # This will collapse a single branch, using information about
216 # previously collapsed branches stored in $playground.
217 collapse()
219 if [ -s "$playground/$_dep^commit" ]; then
220 # We've already seen this dep
221 commit="$(cat "$playground/$_dep^commit")"
223 elif [ -z "$_dep_is_tgish" ]; then
224 # This dep is not for rewrite
225 commit="$(git rev-parse --verify "refs/heads/$_dep^0" --)"
227 else
228 # First time hitting this dep; the common case
229 echo "Collapsing $_dep"
230 test -d "$playground/${_dep%/*}" || mkdir -p "$playground/${_dep%/*}"
231 commit="$(collapsed_commit "$_dep")"
232 bump_timestamp
233 echol "$commit" >"$playground/$_dep^commit"
236 # Propagate our work through the dependency chain
237 test -d "$playground/${_name%/*}" || mkdir -p "$playground/${_name%/*}"
238 echo "$commit $_dep" >>"$playground/$_name^parents"
242 ## Quilt driver
244 quilt()
246 if [ -z "$_dep_is_tgish" ]; then
247 # This dep is not for rewrite
248 return
251 _dep_tmp=$_dep
253 if "$strip"; then
254 i=$stripval
255 while [ "$i" -gt 0 ]; do
256 [ "$_dep_tmp" = "${_dep_tmp#*/}" ] && break
257 _dep_tmp=${_dep_tmp#*/}
258 i=$((i - 1))
259 done
262 dn="${_dep_tmp%/}.diff"
263 case "$dn" in */*);;*) dn="./$dn"; esac
264 bn="${dn##*/}"
265 dn="${dn%/*}/"
266 [ "x$dn" = "x./" ] && dn=""
268 if "$flatten" && [ "$dn" ]; then
269 bn="$(echo "$_dep_tmp.diff" | sed -e 's#_#__#g' -e 's#/#_#g')"
270 dn=""
273 unset _dep_tmp
275 if [ -e "$playground/$_dep^commit" ]; then
276 # We've already seen this dep
277 return
280 test -d "$playground/${_dep%/*}" || mkdir -p "$playground/${_dep%/*}"
281 >>"$playground/$_dep^commit"
283 if branch_empty "$_dep"; then
284 echo "Skip empty patch $_dep"
285 else
286 if "$numbered"; then
287 number="$(echo $(($(cat "$playground/^number" 2>/dev/null) + 1)))"
288 bn="$(printf "%04u-$bn" $number)"
289 echo "$number" >"$playground/^number"
292 echo "Exporting $_dep"
293 mkdir -p "$output/$dn"
294 tg patch ${binary:+--binary} "$_dep" >"$output/$dn$bn"
295 echol "$dn$bn -p1" >>"$output/series"
299 linearize()
301 if test ! -f "$playground/^BASE"; then
302 if [ -n "$_dep_is_tgish" ]; then
303 head="$(git rev-parse --verify "refs/$topbases/$_dep^0" --)"
304 else
305 head="$(git rev-parse --verify "refs/heads/$_dep^0" --)"
307 echol "$head" > "$playground/^BASE"
308 git checkout -q $iowopt "$head"
309 [ -n "$_dep_is_tgish" ] || return 0
312 head=$(git rev-parse --verify HEAD --)
314 if [ -z "$_dep_is_tgish" ]; then
315 # merge in $_dep unless already included
316 rev="$(git rev-parse --verify "refs/heads/$_dep^0" --)"
317 common="$(git merge-base --all HEAD "$rev")" || :
318 if test "$rev" = "$common"; then
319 # already included, just skip
321 else
322 retmerge=0
324 git merge $auhopt -m "tgexport: merge $_dep into base" -s recursive "refs/heads/$_dep^0" || retmerge="$?"
325 if test "x$retmerge" != "x0"; then
326 echo "fix up the merge, commit and then exit."
327 #todo error handling
328 "${SHELL:-@SHELL_PATH@}" -i </dev/tty
331 else
332 retmerge=0
334 git merge-recursive "$(pretty_tree "$_dep" -b)" -- HEAD "$(pretty_tree "$_dep")" || retmerge="$?"
336 if test "x$retmerge" != "x0"; then
337 git rerere
338 echo "fix up the merge, update the index and then exit. Don't commit!"
339 #todo error handling
340 "${SHELL:-@SHELL_PATH@}" -i </dev/tty
341 git rerere
344 result_tree=$(git write-tree)
345 # testing branch_empty might not always give the right answer.
346 # It can happen that the patch is non-empty but still after
347 # linearizing there is no change. So compare the trees.
348 if test "x$result_tree" = "x$(git rev-parse --verify $head^{tree} --)"; then
349 echo "skip empty commit $_dep"
350 else
351 newcommit=$(create_tg_commit "$_dep" "$result_tree" HEAD)
352 bump_timestamp
353 git update-ref HEAD $newcommit $head
354 echo "exported commit $_dep"
359 ## Machinery
361 if [ "$driver" = "collapse" ] || [ "$driver" = "linearize" ]; then
362 [ -n "$output" ] ||
363 die "no target branch specified"
364 if ! ref_exists "refs/heads/$output"; then
366 elif [ -z "$forceoutput" ]; then
367 die "target branch '$output' already exists; first run: git$gitcdopt branch -D $output, or run $tgdisplay export with --force"
368 else
369 checkout_opt=-B
371 ensure_ident_available
373 elif [ "$driver" = "quilt" ]; then
374 [ -n "$output" ] ||
375 die "no target directory specified"
376 [ -n "$forceoutput" ] || [ ! -e "$output" ] ||
377 die "target directory already exists (use --force to override): $output"
379 mkdir -p "$output"
383 driver()
385 # FIXME should we abort on missing dependency?
386 [ -z "$_dep_missing" ] || return 0
388 [ -z "$_dep_is_tgish" ] || [ -z "$_dep_annihilated" ] || return 0
390 case $_dep in ":"*) return; esac
391 branch_needs_update >/dev/null
392 [ "$_ret" -eq 0 ] ||
393 die "cancelling export of $_dep (-> $_name): branch not up-to-date"
395 $driver
398 # Call driver on all the branches - this will happen
399 # in topological order.
400 if "$allbranches" ; then
401 _dep_is_tgish=1
402 non_annihilated_branches |
403 while read _dep; do
404 driver
405 done
406 test $? -eq 0
407 elif [ -z "$branches" ]; then
408 recurse_deps driver "$name"
409 (_ret=0; _dep="$name"; _name=; _dep_is_tgish=1; _dep_missing=; driver)
410 test $? -eq 0
411 else
412 while read _dep && [ -n "$_dep" ]; do
413 _dep_is_tgish=1
414 $driver
415 done <<-EOT
416 $(sed 'y/ /\n/' <<-LIST
417 $branches
418 LIST
421 name="$branches"
422 case "$branches" in *" "*) pl="es"; esac
426 if [ "$driver" = "collapse" ]; then
427 cmd='git update-ref "refs/heads/$output" "$(cat "$playground/$name^commit")"'
428 [ -n "$forceoutput" ] || cmd="$cmd \"\""
429 eval "$cmd"
431 depcount=$(( $(cat "$playground/^ticker" | wc -l) ))
432 echo "Exported topic branch $name (total $depcount topics) to branch $output"
434 elif [ "$driver" = "quilt" ]; then
435 depcount=$(( $(cat "$output/series" | wc -l) ))
436 echo "Exported topic branch$pl $name (total $depcount topics) to directory $output"
438 elif [ "$driver" = "linearize" ]; then
439 git checkout -q $iowopt $checkout_opt $output
441 echol "$name"
442 if test $(git rev-parse --verify "$(pretty_tree "$name")^{tree}" --) != $(git rev-parse --verify "HEAD^{tree}" --); then
443 echo "Warning: Exported result doesn't match"
444 echo "tg-head=$(git rev-parse --verify "refs/heads/$name" --), exported=$(git rev-parse --verify "HEAD" --)"
445 #git diff $head HEAD