README_DOCS.rst: wordsmith some of the commentary
[topgit/pro.git] / tg-create.sh
blobcfae9bad74264ae34272e5ff7f6881fbc4456795
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,2021 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="\
29 Usage: ${tgname:-tg} [...] create [<option>...] [<name> [<dep>...]]
30 Or: ${tgname:-tg} [...] create [<option>...] --base <name> [<committish>]
31 Or: ${tgname:-tg} [-r <remote>] create [<option>...] <name> -r [<rbranch>]
32 Options:
33 --no-deps alternate spelling of '--base'
34 --quiet / -q suppress most informational messages
35 --message <msg> replace default commit message
36 -m <msg> (default message is \"tg create <name>\")
37 --file <file> replace default commit message
38 -F <file> with contents of <file>
39 --topmsg <msg> use <msg> as .topmsg and skip editor
40 --tm <msg> (<msg> may be reformatted with a warning)
41 --topmsg-file <f> use contents of file <f> as --topmsg
42 --tF <file> alias for --topmsg-file <file>
43 --force / -f ignore tag with same name as new branch
44 --no-edit do not run the editor on default .topmsg
45 --no-commit / -n stop before actually making the commit
46 --no-update do not run 'tg update' (implied by -n)"
48 usage()
50 if [ "${1:-0}" != 0 ]; then
51 printf '%s\n' "$USAGE" >&2
52 else
53 printf '%s\n' "$USAGE"
55 exit ${1:-0}
58 quiet_info()
60 [ -n "$quiet" ] || info "$@"
63 ## Parse options
65 while [ $# -gt 0 ]; do case "$1" in
66 -h|--help)
67 usage
69 --quiet|-q)
70 quiet=1
72 --force|-f)
73 force=1
75 -n|--no-commit)
76 nocommit=1
78 --no-update)
79 noupdate=1
81 --no-edit)
82 noedit=1
84 --no-deps|--base)
85 nodeps=1
86 branchtype=BASE
87 branchdesc=base
89 -m|--message|--message=*)
90 case "$1" in --message=*)
91 x="$1"
92 shift
93 set -- --message "${x#--message=}" "$@"
94 esac
95 if [ $# -lt 2 ]; then
96 echo "The $1 option requires an argument" >&2
97 usage 1
99 shift
100 msg="$1"
102 --tm|--tm=*|--topmsg|--topmsg=*)
103 case "$1" in
104 --tm=*)
105 x="$1"
106 shift
107 set -- --tm "${x#--tm=}" "$@";;
108 --topmsg=*)
109 x="$1"
110 shift
111 set -- --topmsg "${x#--topmsg=}" "$@";;
112 esac
113 if [ $# -lt 2 ]; then
114 echo "The $1 option requires an argument" >&2
115 usage 1
117 shift
118 topmsg="$1"
120 -F|--file|--file=*)
121 case "$1" in --file=*)
122 x="$1"
123 shift
124 set -- --file "${x#--file=}" "$@"
125 esac
126 if [ $# -lt 2 ]; then
127 echo "The $1 option requires an argument" >&2
128 usage 1
130 shift
131 msgfile="$1"
133 --tF|--tF=*|--topmsg-file|--topmsg-file=*)
134 case "$1" in
135 --tF=*)
136 x="$1"
137 shift
138 set -- --tF "${x#--tF=}" "$@";;
139 --topmsg-file=*)
140 x="$1"
141 shift
142 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
143 esac
144 if [ $# -lt 2 ]; then
145 echo "The $1 option requires an argument" >&2
146 usage 1
148 shift
149 topmsgfile="$1"
152 remote=1
153 rname="$2"; [ $# -eq 0 ] || shift
156 shift
157 break
159 -?*)
160 echo "Unknown option: $1" >&2
161 usage 1
164 break
166 esac; shift; done
167 ensure_work_tree
168 [ $# -gt 0 ] || [ -z "$rname" ] || set -- "$rname"
169 if [ $# -gt 0 ]; then
170 name="$1"
171 shift
172 if [ -z "$remote" ] && [ "$1" = "-r" ]; then
173 remote=1
174 shift;
175 rname="$1"
176 [ $# -eq 0 ] || shift
179 [ -n "$name" ] || { err "no branch name given"; usage 1; }
180 [ -z "$remote" ] || [ -n "$rname" ] || rname="$name"
181 [ -z "$remote" ] || [ -z "$msg$msgfile$topmsg$topmsgfile$nocommit$noupdate$nodeps" ] || { err "-r may not be combined with other options"; usage 1; }
182 [ $# -eq 0 ] || [ -z "$remote" ] || { err "deps not allowed with -r"; usage 1; }
183 [ $# -le 1 ] || [ -z "$nodeps" ] || { err "--base (aka --no-deps) allows at most one <dep>"; usage 1; }
184 [ "$nocommit$noupdate" != "11" ] || die "--no-commit and --no-update are mutually exclusive options"
185 [ -z "$msg" ] || [ -z "$msgfile" ] || die "only one -F or -m option is allowed"
186 [ "$msgfile" != "-" ] || [ "$topmsgfile" != "-" ] || { err "--message-file and --topmsg-file may not both be '-'"; usage 1; }
188 ## Fast-track creating branches based on remote ones
190 if [ -n "$rname" ]; then
191 [ -n "$name" ] || die "no branch name given"
192 ! ref_exists "refs/heads/$name" || die "branch '$name' already exists"
193 ! ref_exists "refs/$topbases/$name" || die "'$topbases/$name' already exists"
194 if [ -z "$base_remote" ]; then
195 die "no remote location given. Either use -r remote argument or set topgit.remote"
197 has_remote "$rname" || die "no branch $rname in remote $base_remote"
198 init_reflog "refs/$topases/$name"
199 msg="tgcreate: $name -r $rname"
200 v_ref_exists_rev tbrv "refs/remotes/$base_remote/${topbases#heads/}/$rname" ||
201 v_ref_exists_rev tbrv "refs/remotes/$base_remote/${oldbases#heads/}/$rname" ||
202 die "no branch $rname in remote $base_remote"
203 git update-ref -m "$msg" "refs/$topbases/$name" "$tbrv" ""
204 git update-ref -m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
205 quiet_info "Topic branch $name based on $base_remote : $rname set up."
206 exit 0
209 ## Auto-guess dependencies
211 [ "$name" != "@" ] || name="HEAD"
212 [ -z "$nodeps" ] || [ $# -ne 1 ] || [ "$1" != "@" ] || set -- "HEAD"
213 if [ -z "$*" ]; then
214 # The common case
215 [ -n "$name" ] || die "no branch name given"
216 if [ -n "$nodeps" ]; then
217 deps="HEAD"
218 else
219 head="$(git symbolic-ref --quiet HEAD)" || :
220 [ -z "$head" ] || git rev-parse --verify --quiet "$head" -- >/dev/null ||
221 die "refusing to auto-depend on unborn branch (use --base aka --no-deps)"
222 deps="${head#refs/heads/}"
223 [ "$deps" != "$head" ] || die "refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
224 quiet_info "automatically marking dependency on $deps"
226 elif [ -n "$nodeps" ]; then
227 deps="$1"
228 else
229 # verify each dep is valid and expand "@" to "HEAD" and "HEAD" to it's symref (unless detached)
230 deps=
231 head="$(git symbolic-ref --quiet HEAD)" || :
232 for d in "$@"; do
233 [ "$d" != "@" ] || d="HEAD"
234 [ "$d" != "HEAD" ] || [ -z "$head" ] || d="$head"
235 case "$d" in
236 HEAD)
237 die "cannot depend on detached HEAD"
239 refs/heads/?*)
240 d="${d#refs/heads/}"
242 refs/*)
243 die "cannot depend on non-branch ref '$d'"
245 esac
246 ref_exists "refs/heads/$d" || {
248 case "refs/$d" in refs/heads/?*)
249 d="${d#heads/}"
250 ! ref_exists "refs/heads/$d" || ok=1
252 esac
253 [ -n "$ok" ] || die "unknown branch dependency '$d'"
255 deps="${deps:+$deps }$d"
256 done
259 unborn=
260 if [ -n "$nodeps" ]; then
261 # there can be only one dep and it need only be a committish
262 # however, if it's HEAD and HEAD is an unborn branch that's okay too
263 if [ "$deps" = "HEAD" ] && unborn="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse --verify --quiet HEAD -- >/dev/null; then
264 branchtype=ROOT
265 branchdesc=root
266 else
267 unborn=
268 git rev-parse --quiet --verify "$deps^0" -- >/dev/null ||
269 die "unknown committish \"$deps\""
273 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as a not unborn HEAD
274 # Also the .topdeps and .topmsg files, if they exist, may not be overwriten unless they are "clean"
276 prefix=refs/heads/
277 [ -z "$nodeps" ] || prefix=
278 ensure_cmd=ensure_clean_tree
279 if [ -n "$unborn" ]; then
280 ensure_cmd=:
281 elif [ $# -eq 1 ] && { [ "$deps" = "HEAD" ] ||
282 [ "$(git rev-parse --quiet --verify "$prefix$deps^{tree}" --)" = "$(git rev-parse --quiet --verify HEAD^{tree} --)" ]; }; then
283 ensure_cmd=:
285 ($ensure_cmd && ensure_clean_topfiles ${unborn:+-u}) || {
286 [ $# -ne 1 ] || [ "$deps" = "HEAD" ] || info "use \`git checkout $deps\` first and then try again"
287 exit 1
290 [ -n "$merge" ] || merge="$deps "
292 if [ -z "$nodeps" ]; then
293 olddeps="$deps"
294 deps=
295 while read d && [ -n "$d" ]; do
296 if [ "$d" = "HEAD" ]; then
297 sr="$(git symbolic-ref --quiet HEAD)" || :
298 [ -z "$sr" ] || git rev-parse --verify --quiet "$sr" -- >/dev/null ||
299 die "refusing to depend on unborn branch (use --base aka --no-deps)"
300 [ -n "$sr" ] || die "cannot depend on a detached HEAD"
301 case "$sr" in refs/heads/*);;*)
302 die "HEAD is a symref to other than refs/heads/..."
303 esac
304 d="${sr#refs/heads/}"
305 else
306 ref_exists "refs/heads/$d" || die "unknown branch dependency '$d'"
308 case " $deps " in
309 *" $d "*)
310 warn "ignoring duplicate depedency $d"
313 deps="${deps:+$deps }$d"
315 esac
316 done <<-EOT
317 $(sed 'y/ /\n/' <<-LIST
318 $olddeps
319 LIST
322 unset olddeps
324 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
325 refs/"$topbases"/*)
326 name="${test#refs/$topbases/}"
327 break;;
328 refs/heads/*)
329 name="${test#refs/heads/}"
330 break;;
331 esac; fi
332 ! ref_exists "refs/heads/$name" ||
333 die "branch '$name' already exists"
334 ! ref_exists "refs/$topbases/$name" ||
335 die "'$topbases/$name' already exists"
336 [ -n "$force" ] || ! ref_exists "refs/tags/$name" ||
337 die "refusing to create branch with same name as existing tag '$name' without --force"
339 # Barf now rather than later if missing ident
340 ensure_ident_available
342 if [ -n "$merge" ] && [ -z "$unborn" ]; then
343 # make sure the checkout won't fail
344 branch="${merge%% *}"
345 prefix=refs/heads/
346 [ -z "$nodeps" ] || prefix=
347 git rev-parse --quiet --verify "$prefix$branch^0" >/dev/null ||
348 die "invalid dependency: $branch"
349 git read-tree -n -u -m "$prefix$branch^0" ||
350 die "git checkout \"$branch\" would fail"
353 # Get messages
355 tab="$(printf '\t.')" && tab="${tab%?}"
356 get_subject()
358 sed -n '1,/^$/p' |
359 grep -i "^Subject[ $tab]*:" |
360 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
364 >"$git_dir/TG_EDITMSG"
365 if [ -n "$msgfile" ]; then
366 if [ "$msgfile" = "-" ]; then
367 git stripspace >"$git_dir/TG_EDITMSG"
368 else
369 git stripspace <"$msgfile" >"$git_dir/TG_EDITMSG"
371 elif [ -n "$msg" ]; then
372 printf '%s\n' "$msg" | git stripspace >"$git_dir/TG_EDITMSG"
374 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
375 printf '%s\n' "tg create $name" | git stripspace >"$git_dir/TG_EDITMSG"
377 msg="$(cat "$git_dir/TG_EDITMSG")"
378 rm -f "$git_dir/TG_EDITMSG"
380 >"$git_dir/TG_EDITMSG"
381 if [ -n "$topmsgfile" ]; then
382 if [ "$topmsgfile" = "-" ]; then
383 git stripspace >"$git_dir/TG_EDITMSG"
384 else
385 git stripspace <"$topmsgfile" >"$git_dir/TG_EDITMSG"
387 elif [ -n "$topmsg" ]; then
388 printf '%s\n' "$topmsg" | git stripspace | sed "1s/^[ $tab][ $tab]*//" >"$git_dir/TG_EDITMSG"
390 if [ -s "$git_dir/TG_EDITMSG" ]; then
391 noedit=1
392 else
393 author="$(git var GIT_AUTHOR_IDENT)"
394 author_addr="${author%> *}>"
396 echo "From: $author_addr"
397 ! header="$(git config topgit.to)" || echo "To: $header"
398 ! header="$(git config topgit.cc)" || echo "Cc: $header"
399 ! header="$(git config topgit.bcc)" || echo "Bcc: $header"
400 ! subject_prefix="$(git config topgit.subjectprefix)" || subject_prefix="$subject_prefix "
401 echo "Subject: [${subject_prefix}$branchtype] $name"
402 echo
403 echo "#$branchdesc description"
404 echo
405 sobpfx='#'
406 [ z"$(git config --bool format.signoff 2>/dev/null)" != z"true" ] || sobpfx=
407 echo "${sobpfx}Signed-off-by: $author_addr"
408 } | git -c core.commentchar='#' stripspace ${noedit:+-s} >"$git_dir/TG_EDITMSG"
410 if [ -z "$noedit" ]; then
411 cat <<EOT >>"$git_dir/TG_EDITMSG"
413 # Please enter the patch message for the new TopGit branch $name.
414 # It will be stored in the .topmsg file and used to create the
415 # patch header when \`tg patch\` is run on branch $name.
416 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
418 # Lines starting with '#' will be ignored, and an empty patch
419 # message aborts the \`tg create\` operation entirely.
421 # tg create ${nodeps:+--base }$name $deps
423 run_editor "$git_dir/TG_EDITMSG" ||
424 die "there was a problem with the editor '$tg_editor'"
425 git -c core.commentchar='#' stripspace -s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
426 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
427 [ -s "$git_dir/TG_EDITMSG" ] || die "nothing to do"
429 subj="$(get_subject <"$git_dir/TG_EDITMSG")"
430 if [ -z "$subj" ]; then
431 subj="$(sed -n "s/^[ $tab][ $tab]*//; 1p" <"$git_dir/TG_EDITMSG")";
432 case "$subj" in "["*);;*) subj="[$branchtype] $subj"; esac
433 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
434 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace >>"$git_dir/TG_EDITMSG"+
435 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
436 warntop=1
438 topmsg="$(cat "$git_dir/TG_EDITMSG")"
439 rm -f "$git_dir/TG_EDITMSG"
441 ## Find starting commit to create the base
443 if [ -n "$merge" ]; then
444 # Unshift the first item from the to-merge list
445 branch="${merge%% *}"
446 merge="${merge#* }"
447 # We create a detached head so that we can abort this operation
448 prefix=refs/heads/
449 [ -z "$nodeps" ] || prefix=
450 if [ -n "$unborn" ]; then
451 quiet_info "creating $name base with empty tree..."
452 else
453 quiet_info "creating $name base from $branch..."
454 git checkout -q $iowopt "$(git rev-parse --verify "$prefix$branch^0" --)"
458 ## Set up the topic branch
460 git update-index --index-info <<EOT || die "git update-index failed"
461 0 $nullsha$tab.topdeps
462 0 $nullsha$tab.topmsg
464 rm -rf "$root_dir/.topdeps" "$root_dir/.topmsg"
465 init_reflog "refs/$topbases/$name"
466 if [ -n "$unborn" ]; then
467 mttree="$(git mktree </dev/null)"
468 emsg="tg create empty $name base"
469 [ "refs/heads/$name" = "$unborn" ] || emsg="Initial empty commit"
470 mtcommit="$(git commit-tree -m "$emsg" "$mttree")" || die "git commit-tree failed"
471 git update-ref -m "tgcreate: create ${unborn#refs/heads/}" "HEAD" "$mtcommit" ""
472 [ "refs/heads/$name" = "$unborn" ] || warn "branch ${unborn#refs/heads/} created with empty commit"
473 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "HEAD" ""
474 [ "refs/heads/$name" = "$unborn" ] || git checkout $iowopt -b "$name"
475 else
476 basetree="$(git rev-parse --verify "HEAD^{tree}" --)" && [ -n "$basetree" ] || die "HEAD disappeared"
477 v_pretty_tree baseptree "HEAD" -r || die "v_pretty_tree ... HEAD -r (via git mktree) failed"
478 if [ "$basetree" != "$baseptree" ]; then
479 bmsg="tg create $name base"
480 basecommit="$(git commit-tree -p "HEAD" -m "$bmsg" "$baseptree")" || die "git commit-tree failed"
481 else
482 basecommit="HEAD"
484 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "$basecommit" ""
485 [ "$basecommit" = "HEAD" ] || git update-ref -m "tgcreate: set $name base" "HEAD" "$basecommit"
486 git checkout $iowopt -b "$name"
489 if [ -n "$nodeps" ] || [ -z "$deps" ]; then
490 >"$root_dir/.topdeps"
491 else
492 sed 'y/ /\n/' <<-EOT >"$root_dir/.topdeps"
493 $deps
496 git add -f "$root_dir/.topdeps"
497 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
498 git add -f "$root_dir/.topmsg"
499 rm -f "$git_dir/TGMERGE_MSG"
501 [ -z "$warntop" ] || warn ".topmsg content was reformatted into patch header"
502 if [ -n "$nocommit" ]; then
503 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
504 quiet_info "Topic branch $name set up."
505 if [ -n "$noedit" ]; then
506 quiet_info "Please fill in .topmsg now and make the initial commit."
507 else
508 quiet_info "Please make the initial commit."
510 quiet_info "Remember to run $tgdisplay update afterwards."
511 quiet_info "To abort:"
512 quiet_info " git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
513 exit 0
516 git commit -m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die "git commit failed"
517 rawsubj="$(get_subject <"$root_dir/.topmsg")"
518 nommsg=1
519 case "$rawsubj" in *"["[Pp][Aa][Tt][Cc][Hh]"]"*)
520 nommsg=
521 subj="$(sed "s/^[^]]*]//; s/^[ $tab][ $tab]*//; s/[ $tab][ $tab]*\$//" <<-EOT
522 $rawsubj
526 [ -z "$subj" ] || printf '%s\n' "$subj" ""
527 sed -e '1,/^$/d' <"$root_dir/.topmsg"
528 } >"$git_dir/MERGE_MSG"
529 esac
530 quiet_info "Topic branch $name created."
531 [ -n "$merge" ] || exit 0
532 ## Merge other dependencies into the base
533 if [ -n "$noupdate" ]; then
534 quiet_info "Remember to run $tgdisplay update to merge in dependencies."
535 exit 0
537 quiet_info "Running $tgname update to merge in dependencies."
538 [ -n "$nommsg" ] || ! [ -f "$git_dir/MERGE_MSG" ] || mv -f "$git_dir/MERGE_MSG" "$git_dir/TGMERGE_MSG" || :
539 set -- "$name"
540 . "$TG_INST_CMDDIR"/tg-update