testlib: suppress superfluous messages from rm
[topgit/pro.git] / tg-create.sh
blob4c275e1e4cfa5fb7eaac8fde4642ef25d1a4aa22
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) 2008 Petr Baudis <pasky@suse.cz>
4 # Copyright (C) 2015,2016,2017 Kyle J. McKay <mackyle@gmail.com>
5 # All rights reserved
6 # GPLv2
8 deps= # List of dependent branches
9 merge= # List of branches to be merged; subset of $deps
10 name=
11 rname= # Remote branch to base this one on
12 remote=
13 force=
14 msg=
15 msgfile=
16 topmsg=
17 topmsgfile=
18 noedit=
19 nocommit=
20 noupdate=
21 nodeps=
22 topmsg=
23 warntop=
24 quiet=
25 branchtype=PATCH
26 branchdesc=patch
28 USAGE="Usage: ${tgname:-tg} [... -r remote] create [-q] [-m <msg> | -F <file>] [--topmsg <msg> | --topmsg-file <file>] [--no-edit] [--no-commit | --no-update] [--base] [<name> [<dep>...|-r [<rname>]] ]"
30 usage()
32 if [ "${1:-0}" != 0 ]; then
33 printf '%s\n' "$USAGE" >&2
34 else
35 printf '%s\n' "$USAGE"
37 exit ${1:-0}
40 quiet_info()
42 [ -n "$quiet" ] || info "$@"
45 ## Parse options
47 while [ $# -gt 0 ]; do case "$1" in
48 -h|--help)
49 usage
51 --quiet|-q)
52 quiet=1
54 --force|-f)
55 force=1
57 -n|--no-commit)
58 nocommit=1
60 --no-update)
61 noupdate=1
63 --no-edit)
64 noedit=1
66 --no-deps|--base)
67 nodeps=1
68 branchtype=BASE
69 branchdesc=base
71 -m|--message|--message=*)
72 case "$1" in --message=*)
73 x="$1"
74 shift
75 set -- --message "${x#--message=}" "$@"
76 esac
77 if [ $# -lt 2 ]; then
78 echo "The $1 option requires an argument" >&2
79 usage 1
81 shift
82 msg="$1"
84 --tm|--tm=*|--topmsg|--topmsg=*)
85 case "$1" in
86 --tm=*)
87 x="$1"
88 shift
89 set -- --tm "${x#--tm=}" "$@";;
90 --topmsg=*)
91 x="$1"
92 shift
93 set -- --topmsg "${x#--topmsg=}" "$@";;
94 esac
95 if [ $# -lt 2 ]; then
96 echo "The $1 option requires an argument" >&2
97 usage 1
99 shift
100 topmsg="$1"
102 -F|--file|--file=*)
103 case "$1" in --file=*)
104 x="$1"
105 shift
106 set -- --file "${x#--file=}" "$@"
107 esac
108 if [ $# -lt 2 ]; then
109 echo "The $1 option requires an argument" >&2
110 usage 1
112 shift
113 msgfile="$1"
115 --tF|--tF=*|--topmsg-file|--topmsg-file=*)
116 case "$1" in
117 --tF=*)
118 x="$1"
119 shift
120 set -- --tF "${x#--tF=}" "$@";;
121 --topmsg-file=*)
122 x="$1"
123 shift
124 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
125 esac
126 if [ $# -lt 2 ]; then
127 echo "The $1 option requires an argument" >&2
128 usage 1
130 shift
131 topmsgfile="$1"
134 remote=1
135 rname="$2"; [ $# -eq 0 ] || shift
138 shift
139 break
141 -?*)
142 echo "Unknown option: $1" >&2
143 usage 1
146 break
148 esac; shift; done
149 ensure_work_tree
150 [ $# -gt 0 -o -z "$rname" ] || set -- "$rname"
151 if [ $# -gt 0 ]; then
152 name="$1"
153 shift
154 if [ -z "$remote" -a "$1" = "-r" ]; then
155 remote=1
156 shift;
157 rname="$1"
158 [ $# -eq 0 ] || shift
161 [ -n "$name" ] || { err "no branch name given"; usage 1; }
162 [ -z "$remote" -o -n "$rname" ] || rname="$name"
163 [ -z "$remote" -o -z "$msg$msgfile$topmsg$topmsgfile$nocommit$noupdate$nodeps" ] || { err "-r may not be combined with other options"; usage 1; }
164 [ $# -eq 0 -o -z "$remote" ] || { err "deps not allowed with -r"; usage 1; }
165 [ $# -le 1 -o -z "$nodeps" ] || { err "--base (aka --no-deps) allows at most one <dep>"; usage 1; }
166 [ "$nocommit$noupdate" != "11" ] || die "--no-commit and --no-update are mutually exclusive options"
167 [ -z "$msg" -o -z "$msgfile" ] || die "only one -F or -m option is allowed"
168 [ "$msgfile" != "-" -o "$topmsgfile" != "-" ] || { err "--message-file and --topmsg-file may not both be '-'"; usage 1; }
170 ## Fast-track creating branches based on remote ones
172 if [ -n "$rname" ]; then
173 [ -n "$name" ] || die "no branch name given"
174 ! ref_exists "refs/heads/$name" || die "branch '$name' already exists"
175 ! ref_exists "refs/$topbases/$name" || die "'$topbases/$name' already exists"
176 if [ -z "$base_remote" ]; then
177 die "no remote location given. Either use -r remote argument or set topgit.remote"
179 has_remote "$rname" || die "no branch $rname in remote $base_remote"
180 init_reflog "refs/$topases/$name"
181 msg="tgcreate: $name -r $rname"
182 tbrv="$(ref_exists_rev "refs/remotes/$base_remote/${topbases#heads/}/$rname")" ||
183 tbrv="$(ref_exists_rev "refs/remotes/$base_remote/${oldbases#heads/}/$rname")" ||
184 die "no branch $rname in remote $base_remote"
185 git update-ref -m "$msg" "refs/$topbases/$name" "$tbrv" ""
186 git update-ref -m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
187 quiet_info "Topic branch $name based on $base_remote : $rname set up."
188 exit 0
191 ## Auto-guess dependencies
193 [ "$name" != "@" ] || name="HEAD"
194 if [ -z "$*" ]; then
195 # The common case
196 [ -n "$name" ] || die "no branch name given"
197 if [ -n "$nodeps" ]; then
198 deps="HEAD"
199 else
200 head="$(git symbolic-ref --quiet HEAD)" || :
201 [ -z "$head" ] || git rev-parse --verify --quiet "$head" -- >/dev/null ||
202 die "refusing to auto-depend on unborn branch (use --base aka --no-deps)"
203 deps="${head#refs/heads/}"
204 [ "$deps" != "$head" ] || die "refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
205 quiet_info "automatically marking dependency on $deps"
207 elif [ -n "$nodeps" ]; then
208 deps="$1"
209 else
210 # verify each dep is valid and expand "@" to "HEAD" and "HEAD" to it's symref (unless detached)
211 deps=
212 head="$(git symbolic-ref --quiet HEAD)" || :
213 for d in "$@"; do
214 [ "$d" != "@" ] || d="HEAD"
215 [ "$d" != "HEAD" ] || [ -z "$head" ] || d="$head"
216 case "$d" in
217 HEAD)
218 die "cannot depend on detached HEAD"
220 refs/heads/?*)
221 d="${d#refs/heads/}"
223 refs/*)
224 die "cannot depend on non-branch ref '$d'"
226 esac
227 ref_exists "refs/heads/$d" || {
229 case "refs/$d" in refs/heads/?*)
230 d="${d#heads/}"
231 ! ref_exists "refs/heads/$d" || ok=1
233 esac
234 [ -n "$ok" ] || die "unknown branch dependency '$d'"
236 deps="${deps:+$deps }$d"
237 done
240 unborn=
241 if [ -n "$nodeps" ]; then
242 # there can be only one dep and it need only be a committish
243 # however, if it's HEAD and HEAD is an unborn branch that's okay too
244 if [ "$deps" = "HEAD" ] && unborn="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse --verify --quiet HEAD -- >/dev/null; then
245 branchtype=ROOT
246 branchdesc=root
247 else
248 unborn=
249 git rev-parse --quiet --verify "$deps^0" -- >/dev/null ||
250 die "unknown committish \"$deps\""
254 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as a not unborn HEAD
255 # Also the .topdeps and .topmsg files, if they exist, may not be overwriten unless they are "clean"
257 prefix=refs/heads/
258 [ -z "$nodeps" ] || prefix=
259 ensure_cmd=ensure_clean_tree
260 if [ -n "$unborn" ]; then
261 ensure_cmd=:
262 elif [ $# -eq 1 ] && { [ "$deps" = "HEAD" ] ||
263 [ "$(git rev-parse --quiet --verify "$prefix$deps^{tree}" --)" = "$(git rev-parse --quiet --verify HEAD^{tree} --)" ]; }; then
264 ensure_cmd=:
266 ($ensure_cmd && ensure_clean_topfiles ${unborn:+-u}) || {
267 [ $# -ne 1 ] || [ "$deps" = "HEAD" ] || info "use \`git checkout $deps\` first and then try again"
268 exit 1
271 [ -n "$merge" ] || merge="$deps "
273 if [ -z "$nodeps" ]; then
274 olddeps="$deps"
275 deps=
276 while read d && [ -n "$d" ]; do
277 if [ "$d" = "HEAD" ]; then
278 sr="$(git symbolic-ref --quiet HEAD)" || :
279 [ -z "$sr" ] || git rev-parse --verify --quiet "$sr" -- >/dev/null ||
280 die "refusing to depend on unborn branch (use --base aka --no-deps)"
281 [ -n "$sr" ] || die "cannot depend on a detached HEAD"
282 case "$sr" in refs/heads/*);;*)
283 die "HEAD is a symref to other than refs/heads/..."
284 esac
285 d="${sr#refs/heads/}"
286 else
287 ref_exists "refs/heads/$d" || die "unknown branch dependency '$d'"
289 case " $deps " in
290 *" $d "*)
291 warn "ignoring duplicate depedency $d"
294 deps="${deps:+$deps }$d"
296 esac
297 done <<-EOT
298 $(sed 'y/ /\n/' <<-LIST
299 $olddeps
300 LIST
303 unset olddeps
305 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
306 refs/"$topbases"/*)
307 name="${test#refs/$topbases/}"
308 break;;
309 refs/heads/*)
310 name="${test#refs/heads/}"
311 break;;
312 esac; fi
313 ! ref_exists "refs/heads/$name" ||
314 die "branch '$name' already exists"
315 ! ref_exists "refs/$topbases/$name" ||
316 die "'$topbases/$name' already exists"
317 [ -n "$force" ] || ! ref_exists "refs/tags/$name" ||
318 die "refusing to create branch with same name as existing tag '$name' without --force"
320 # Barf now rather than later if missing ident
321 ensure_ident_available
323 if [ -n "$merge" ] && [ -z "$unborn" ]; then
324 # make sure the checkout won't fail
325 branch="${merge%% *}"
326 prefix=refs/heads/
327 [ -z "$nodeps" ] || prefix=
328 git rev-parse --quiet --verify "$prefix$branch^0" >/dev/null ||
329 die "invalid dependency: $branch"
330 git read-tree -n -u -m "$prefix$branch^0" ||
331 die "git checkout \"$branch\" would fail"
334 # Get messages
336 tab="$(printf '\t.')" && tab="${tab%?}"
337 get_subject()
339 sed -n '1,/^$/p' |
340 grep -i "^Subject[ $tab]*:" |
341 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
345 >"$git_dir/TG_EDITMSG"
346 if [ -n "$msgfile" ]; then
347 if [ "$msgfile" = "-" ]; then
348 git stripspace >"$git_dir/TG_EDITMSG"
349 else
350 git stripspace <"$msgfile" >"$git_dir/TG_EDITMSG"
352 elif [ -n "$msg" ]; then
353 printf '%s\n' "$msg" | git stripspace >"$git_dir/TG_EDITMSG"
355 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
356 printf '%s\n' "tg create $name" | git stripspace >"$git_dir/TG_EDITMSG"
358 msg="$(cat "$git_dir/TG_EDITMSG")"
359 rm -f "$git_dir/TG_EDITMSG"
361 >"$git_dir/TG_EDITMSG"
362 if [ -n "$topmsgfile" ]; then
363 if [ "$topmsgfile" = "-" ]; then
364 git stripspace >"$git_dir/TG_EDITMSG"
365 else
366 git stripspace <"$topmsgfile" >"$git_dir/TG_EDITMSG"
368 elif [ -n "$topmsg" ]; then
369 printf '%s\n' "$topmsg" | git stripspace | sed "1s/^[ $tab][ $tab]*//" >"$git_dir/TG_EDITMSG"
371 if [ -s "$git_dir/TG_EDITMSG" ]; then
372 noedit=1
373 else
374 author="$(git var GIT_AUTHOR_IDENT)"
375 author_addr="${author%> *}>"
377 echo "From: $author_addr"
378 ! header="$(git config topgit.to)" || echo "To: $header"
379 ! header="$(git config topgit.cc)" || echo "Cc: $header"
380 ! header="$(git config topgit.bcc)" || echo "Bcc: $header"
381 ! subject_prefix="$(git config topgit.subjectprefix)" || subject_prefix="$subject_prefix "
382 echo "Subject: [${subject_prefix}$branchtype] $name"
383 echo
384 echo "#$branchdesc description"
385 echo
386 sobpfx='#'
387 [ z"$(git config --bool format.signoff 2>/dev/null)" != z"true" ] || sobpfx=
388 echo "${sobpfx}Signed-off-by: $author_addr"
389 } | git stripspace ${noedit:+-s} >"$git_dir/TG_EDITMSG"
391 if [ -z "$noedit" ]; then
392 cat <<EOT >>"$git_dir/TG_EDITMSG"
394 # Please enter the patch message for the new TopGit branch $name.
395 # It will be stored in the .topmsg file and used to create the
396 # patch header when \`tg patch\` is run on branch $name.
397 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
399 # Lines starting with '#' will be ignored, and an empty patch
400 # message aborts the \`tg create\` operation entirely.
402 # tg create ${nodeps:+--base }$name $deps
404 run_editor "$git_dir/TG_EDITMSG" ||
405 die "there was a problem with the editor '$tg_editor'"
406 git stripspace -s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
407 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
408 [ -s "$git_dir/TG_EDITMSG" ] || die "nothing to do"
410 subj="$(get_subject <"$git_dir/TG_EDITMSG")"
411 if [ -z "$subj" ]; then
412 subj="$(sed -n "s/^[ $tab][ $tab]*//; 1p" <"$git_dir/TG_EDITMSG")";
413 case "$subj" in "["*);;*) subj="[$branchtype] $subj"; esac
414 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
415 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace >>"$git_dir/TG_EDITMSG"+
416 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
417 warntop=1
419 topmsg="$(cat "$git_dir/TG_EDITMSG")"
420 rm -f "$git_dir/TG_EDITMSG"
422 ## Find starting commit to create the base
424 if [ -n "$merge" ]; then
425 # Unshift the first item from the to-merge list
426 branch="${merge%% *}"
427 merge="${merge#* }"
428 # We create a detached head so that we can abort this operation
429 prefix=refs/heads/
430 [ -z "$nodeps" ] || prefix=
431 if [ -n "$unborn" ]; then
432 quiet_info "creating $name base with empty tree..."
433 else
434 quiet_info "creating $name base from $branch..."
435 git checkout -q $iowopt "$(git rev-parse --verify "$prefix$branch^0" --)"
439 ## Set up the topic branch
441 git update-index --index-info <<EOT || die "git update-index failed"
442 0 $nullsha$tab.topdeps
443 0 $nullsha$tab.topmsg
445 rm -rf "$root_dir/.topdeps" "$root_dir/.topmsg"
446 init_reflog "refs/$topbases/$name"
447 if [ -n "$unborn" ]; then
448 mttree="$(git mktree </dev/null)"
449 emsg="tg create empty $name base"
450 [ "refs/heads/$name" = "$unborn" ] || emsg="Initial empty commit"
451 mtcommit="$(git commit-tree -m "$emsg" "$mttree")" || die "git commit-tree failed"
452 git update-ref -m "tgcreate: create ${unborn#refs/heads/}" "HEAD" "$mtcommit" ""
453 [ "refs/heads/$name" = "$unborn" ] || warn "branch ${unborn#refs/heads/} created with empty commit"
454 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "HEAD" ""
455 [ "refs/heads/$name" = "$unborn" ] || git checkout $iowopt -b "$name"
456 else
457 basetree="$(git rev-parse --verify "HEAD^{tree}" --)" && [ -n "$basetree" ] || die "HEAD disappeared"
458 baseptree="$(pretty_tree "HEAD" -r)" || die "pretty_tree HEAD -r (via git mktree) failed"
459 if [ "$basetree" != "$baseptree" ]; then
460 bmsg="tg create $name base"
461 basecommit="$(git commit-tree -p "HEAD" -m "$bmsg" "$baseptree")" || die "git commit-tree failed"
462 else
463 basecommit="HEAD"
465 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "$basecommit" ""
466 [ "$basecommit" = "HEAD" ] || git update-ref -m "tgcreate: set $name base" "HEAD" "$basecommit"
467 git checkout $iowopt -b "$name"
470 if [ -n "$nodeps" ] || [ -z "$deps" ]; then
471 >"$root_dir/.topdeps"
472 else
473 sed 'y/ /\n/' <<-EOT >"$root_dir/.topdeps"
474 $deps
477 git add -f "$root_dir/.topdeps"
478 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
479 git add -f "$root_dir/.topmsg"
480 rm -f "$git_dir/TGMERGE_MSG"
482 [ -z "$warntop" ] || warn ".topmsg content was reformatted into patch header"
483 if [ -n "$nocommit" ]; then
484 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
485 quiet_info "Topic branch $name set up."
486 if [ -n "$noedit" ]; then
487 quiet_info "Please fill in .topmsg now and make the initial commit."
488 else
489 quiet_info "Please make the initial commit."
491 quiet_info "Remember to run $tgdisplay update afterwards."
492 quiet_info "To abort:"
493 quiet_info " git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
494 exit 0
497 git commit -m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die "git commit failed"
498 rawsubj="$(get_subject <"$root_dir/.topmsg")"
499 nommsg=1
500 case "$rawsubj" in *"["[Pp][Aa][Tt][Cc][Hh]"]"*)
501 nommsg=
502 subj="$(sed "s/^[^]]*]//; s/^[ $tab][ $tab]*//; s/[ $tab][ $tab]*\$//" <<-EOT
503 $rawsubj
507 [ -z "$subj" ] || printf '%s\n' "$subj" ""
508 sed -e '1,/^$/d' <"$root_dir/.topmsg"
509 } >"$git_dir/MERGE_MSG"
510 esac
511 quiet_info "Topic branch $name created."
512 [ -n "$merge" ] || exit 0
513 ## Merge other dependencies into the base
514 if [ -n "$noupdate" ]; then
515 quiet_info "Remember to run $tgdisplay update to merge in dependencies."
516 exit 0
518 quiet_info "Running $tgname update to merge in dependencies."
519 [ -n "$nommsg" ] || ! [ -f "$git_dir/MERGE_MSG" ] || mv -f "$git_dir/MERGE_MSG" "$git_dir/TGMERGE_MSG" || :
520 set -- "$name"
521 . "$TG_INST_CMDDIR"/tg-update