tg-create.sh: with --no-deps (aka --base) allow any committish
[topgit/pro.git] / tg-create.sh
bloba2424ae96cc77d0782c48eddbac0d4f1a7fa7c98
1 #!/bin/sh
2 # TopGit - A different patch queue manager
3 # Copyright (C) Petr Baudis <pasky@suse.cz> 2008
4 # Copyright (C) Kyle J. McKay <mackyle@gmail.com> 2015,2016,2017
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 nodeps=
21 topmsg=
22 warntop=
23 quiet=
24 branchtype=PATCH
25 branchdesc=patch
27 USAGE="Usage: ${tgname:-tg} [... -r remote] create [-q] [-m <msg> | -F <file>] [--topmsg <msg> | --topmsg-file <file>] [-n] [--no-commit] [--base] [<name> [<dep>...|-r [<rname>]] ]"
29 usage()
31 if [ "${1:-0}" != 0 ]; then
32 printf '%s\n' "$USAGE" >&2
33 else
34 printf '%s\n' "$USAGE"
36 exit ${1:-0}
39 quiet_info()
41 [ -n "$quiet" ] || info "$@"
44 ## Parse options
46 while [ $# -gt 0 ]; do case "$1" in
47 -h|--help)
48 usage
50 --quiet|-q)
51 quiet=1
53 --force|-f)
54 force=1
56 --no-commit)
57 nocommit=1
59 -n|--no-edit)
60 noedit=1
61 nocommit=1
63 --no-deps|--base)
64 nodeps=1
65 branchtype=BASE
66 branchdesc=base
68 -m|--message|--message=*)
69 case "$1" in --message=*)
70 x="$1"
71 shift
72 set -- --message "${x#--message=}" "$@"
73 esac
74 if [ $# -lt 2 ]; then
75 echo "The $1 option requires an argument" >&2
76 usage 1
78 shift
79 msg="$1"
81 --tm|--tm=*|--topmsg|--topmsg=*)
82 case "$1" in
83 --tm=*)
84 x="$1"
85 shift
86 set -- --tm "${x#--tm=}" "$@";;
87 --topmsg=*)
88 x="$1"
89 shift
90 set -- --topmsg "${x#--topmsg=}" "$@";;
91 esac
92 if [ $# -lt 2 ]; then
93 echo "The $1 option requires an argument" >&2
94 usage 1
96 shift
97 topmsg="$1"
99 -F|--file|--file=*)
100 case "$1" in --file=*)
101 x="$1"
102 shift
103 set -- --file "${x#--file=}" "$@"
104 esac
105 if [ $# -lt 2 ]; then
106 echo "The $1 option requires an argument" >&2
107 usage 1
109 shift
110 msgfile="$1"
112 --tF|--tF=*|--topmsg-file|--topmsg-file=*)
113 case "$1" in
114 --tF=*)
115 x="$1"
116 shift
117 set -- --tF "${x#--tF=}" "$@";;
118 --topmsg-file=*)
119 x="$1"
120 shift
121 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
122 esac
123 if [ $# -lt 2 ]; then
124 echo "The $1 option requires an argument" >&2
125 usage 1
127 shift
128 topmsgfile="$1"
131 remote=1
132 rname="$2"; [ $# -eq 0 ] || shift
135 shift
136 break
138 -?*)
139 echo "Unknown option: $1" >&2
140 usage 1
143 break
145 esac; shift; done
146 ensure_work_tree
147 [ $# -gt 0 -o -z "$rname" ] || set -- "$rname"
148 if [ $# -gt 0 ]; then
149 name="$1"
150 shift
151 if [ -z "$remote" -a "$1" = "-r" ]; then
152 remote=1
153 shift;
154 rname="$1"
155 [ $# -eq 0 ] || shift
158 [ -n "$name" ] || { err "no branch name given"; usage 1; }
159 [ -z "$remote" -o -n "$rname" ] || rname="$name"
160 [ -z "$remote" -o -z "$msg$msgfile$topmsg$topmsgfile$nocommit$nodeps" ] || { err "-r may not be combined with other options"; usage 1; }
161 [ $# -eq 0 -o -z "$remote" ] || { err "deps not allowed with -r"; usage 1; }
162 [ $# -le 1 -o -z "$nodeps" ] || { err "--base (aka --no-deps) allows at most one <dep>"; usage 1; }
163 [ -z "$msg" -o -z "$msgfile" ] || die "only one -F or -m option is allowed"
164 [ "$msgfile" != "-" -o "$topmsgfile" != "-" ] || { err "--message-file and --topmsg-file may not both be '-'"; usage 1; }
166 ## Fast-track creating branches based on remote ones
168 if [ -n "$rname" ]; then
169 [ -n "$name" ] || die "no branch name given"
170 ! ref_exists "refs/heads/$name" || die "branch '$name' already exists"
171 ! ref_exists "refs/$topbases/$name" || die "'$topbases/$name' already exists"
172 if [ -z "$base_remote" ]; then
173 die "no remote location given. Either use -r remote argument or set topgit.remote"
175 has_remote "$rname" || die "no branch $rname in remote $base_remote"
176 init_reflog "refs/$topases/$name"
177 msg="tgcreate: $name -r $rname"
178 tbrv="$(ref_exists_rev "refs/remotes/$base_remote/${topbases#heads/}/$rname")" ||
179 tbrv="$(ref_exists_rev "refs/remotes/$base_remote/${oldbases#heads/}/$rname")" ||
180 die "no branch $rname in remote $base_remote"
181 git update-ref -m "$msg" "refs/$topbases/$name" "$tbrv" ""
182 git update-ref -m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
183 quiet_info "Topic branch $name based on $base_remote : $rname set up."
184 exit 0
187 ## Auto-guess dependencies
189 [ "$name" != "@" ] || name="HEAD"
190 if [ -z "$*" ]; then
191 # The common case
192 [ -n "$name" ] || die "no branch name given"
193 if [ -n "$nodeps" ]; then
194 deps="HEAD"
195 else
196 head="$(git symbolic-ref --quiet HEAD)" || :
197 [ -z "$head" ] || git rev-parse --verify --quiet "$head" -- >/dev/null ||
198 die "refusing to auto-depend on unborn branch (use --base aka --no-deps)"
199 deps="${head#refs/heads/}"
200 [ "$deps" != "$head" ] || die "refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
201 quiet_info "automatically marking dependency on $deps"
203 elif [ -n "$nodeps" ]; then
204 deps="$1"
205 else
206 # verify each dep is valid and expand "@" to "HEAD" and "HEAD" to it's symref (unless detached)
207 deps=
208 head="$(git symbolic-ref --quiet HEAD)" || :
209 for d in "$@"; do
210 [ "$d" != "@" ] || d="HEAD"
211 [ "$d" != "HEAD" ] || [ -z "$head" ] || d="$head"
212 case "$d" in
213 HEAD)
214 die "cannot depend on detached HEAD"
216 refs/heads/?*)
217 d="${d#refs/heads/}"
219 refs/*)
220 die "cannot depend on non-branch ref '$d'"
222 esac
223 ref_exists "refs/heads/$d" || {
225 case "refs/$d" in refs/heads/?*)
226 d="${d#heads/}"
227 ! ref_exists "refs/heads/$d" || ok=1
229 esac
230 [ -n "$ok" ] || die "unknown branch dependency '$d'"
232 deps="${deps:+$deps }$d"
233 done
236 unborn=
237 if [ -n "$nodeps" ]; then
238 # there can be only one dep and it need only be a committish
239 # however, if it's HEAD and HEAD is an unborn branch that's okay too
240 if [ "$deps" = "HEAD" ] && unborn="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse --verify --quiet HEAD -- >/dev/null; then
241 branchtype=ROOT
242 branchdesc=root
243 else
244 unborn=
245 git rev-parse --quiet --verify "$deps^0" -- >/dev/null ||
246 die "unknown committish \"$deps\""
250 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as a not unborn HEAD
251 # Also the .topdeps and .topmsg files, if they exist, may not be overwriten unless they are "clean"
253 prefix=refs/heads/
254 [ -z "$nodeps" ] || prefix=
255 ensure_cmd=ensure_clean_tree
256 if [ -n "$unborn" ]; then
257 ensure_cmd=:
258 elif [ $# -eq 1 ] && { [ "$deps" = "HEAD" ] ||
259 [ "$(git rev-parse --quiet --verify "$prefix$deps^{tree}" --)" = "$(git rev-parse --quiet --verify HEAD^{tree} --)" ]; }; then
260 ensure_cmd=:
262 ($ensure_cmd && ensure_clean_topfiles ${unborn:+-u}) || {
263 [ $# -ne 1 ] || [ "$deps" = "HEAD" ] || info "use \`git checkout $deps\` first and then try again"
264 exit 1
267 [ -n "$merge" ] || merge="$deps "
269 if [ -z "$nodeps" ]; then
270 olddeps="$deps"
271 deps=
272 while read d && [ -n "$d" ]; do
273 if [ "$d" = "HEAD" ]; then
274 sr="$(git symbolic-ref --quiet HEAD)" || :
275 [ -z "$sr" ] || git rev-parse --verify --quiet "$sr" -- >/dev/null ||
276 die "refusing to depend on unborn branch (use --base aka --no-deps)"
277 [ -n "$sr" ] || die "cannot depend on a detached HEAD"
278 case "$sr" in refs/heads/*);;*)
279 die "HEAD is a symref to other than refs/heads/..."
280 esac
281 d="${sr#refs/heads/}"
282 else
283 ref_exists "refs/heads/$d" || die "unknown branch dependency '$d'"
285 case " $deps " in
286 *" $d "*)
287 warn "ignoring duplicate depedency $d"
290 deps="${deps:+$deps }$d"
292 esac
293 done <<-EOT
294 $(sed 'y/ /\n/' <<-LIST
295 $olddeps
296 LIST
299 unset olddeps
301 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
302 refs/"$topbases"/*)
303 name="${test#refs/$topbases/}"
304 break;;
305 refs/heads/*)
306 name="${test#refs/heads/}"
307 break;;
308 esac; fi
309 ! ref_exists "refs/heads/$name" ||
310 die "branch '$name' already exists"
311 ! ref_exists "refs/$topbases/$name" ||
312 die "'$topbases/$name' already exists"
313 [ -n "$force" ] || ! ref_exists "refs/tags/$name" ||
314 die "refusing to create branch with same name as existing tag '$name' without --force"
316 # Barf now rather than later if missing ident
317 ensure_ident_available
319 if [ -n "$merge" ] && [ -z "$unborn" ]; then
320 # make sure the checkout won't fail
321 branch="${merge%% *}"
322 prefix=refs/heads/
323 [ -z "$nodeps" ] || prefix=
324 git rev-parse --quiet --verify "$prefix$branch^0" >/dev/null ||
325 die "invalid dependency: $branch"
326 git read-tree -n -u -m "$prefix$branch^0" ||
327 die "git checkout \"$branch\" would fail"
330 # Get messages
332 tab="$(printf '\t.')" && tab="${tab%?}"
333 get_subject()
335 sed -n '1,/^$/p' |
336 grep -i "^Subject[ $tab]*:" |
337 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
341 >"$git_dir/TG_EDITMSG"
342 if [ -n "$msgfile" ]; then
343 if [ "$msgfile" = "-" ]; then
344 git stripspace >"$git_dir/TG_EDITMSG"
345 else
346 git stripspace <"$msgfile" >"$git_dir/TG_EDITMSG"
348 elif [ -n "$msg" ]; then
349 printf '%s\n' "$msg" | git stripspace >"$git_dir/TG_EDITMSG"
351 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
352 printf '%s\n' "tg create $name" | git stripspace >"$git_dir/TG_EDITMSG"
354 msg="$(cat "$git_dir/TG_EDITMSG")"
355 rm -f "$git_dir/TG_EDITMSG"
357 >"$git_dir/TG_EDITMSG"
358 if [ -n "$topmsgfile" ]; then
359 if [ "$topmsgfile" = "-" ]; then
360 git stripspace >"$git_dir/TG_EDITMSG"
361 else
362 git stripspace <"$topmsgfile" >"$git_dir/TG_EDITMSG"
364 elif [ -n "$topmsg" ]; then
365 printf '%s\n' "$topmsg" | git stripspace >"$git_dir/TG_EDITMSG"
367 if [ -s "$git_dir/TG_EDITMSG" ]; then
368 noedit=1
369 else
370 author="$(git var GIT_AUTHOR_IDENT)"
371 author_addr="${author%> *}>"
373 echo "From: $author_addr"
374 ! header="$(git config topgit.to)" || echo "To: $header"
375 ! header="$(git config topgit.cc)" || echo "Cc: $header"
376 ! header="$(git config topgit.bcc)" || echo "Bcc: $header"
377 ! subject_prefix="$(git config topgit.subjectprefix)" || subject_prefix="$subject_prefix "
378 echo "Subject: [${subject_prefix}$branchtype] $name"
379 echo
380 echo "#$branchdesc description"
381 echo
382 sobpfx='#'
383 [ z"$(git config --bool format.signoff 2>/dev/null)" != z"true" ] || sobpfx=
384 echo "${sobpfx}Signed-off-by: $author_addr"
385 } | git stripspace >"$git_dir/TG_EDITMSG"
387 if [ -z "$noedit" ]; then
388 cat <<EOT >>"$git_dir/TG_EDITMSG"
390 # Please enter the patch message for the new TopGit branch $name.
391 # It will be stored in the .topmsg file and used to create the
392 # patch header when \`tg patch\` is run on branch $name.
393 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
395 # Lines starting with '#' will be ignored, and an empty patch
396 # message aborts the \`tg create\` operation entirely.
398 # tg create ${nodeps:+--base }$name $deps
400 run_editor "$git_dir/TG_EDITMSG" ||
401 die "there was a problem with the editor '$tg_editor'"
402 git stripspace -s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
403 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
404 [ -s "$git_dir/TG_EDITMSG" ] || die "nothing to do"
406 subj="$(get_subject <"$git_dir/TG_EDITMSG")"
407 if [ -z "$subj" ]; then
408 subj="$(sed -n "s/^[ $tab][ $tab]*//; 1p" <"$git_dir/TG_EDITMSG")";
409 case "$subj" in "["*);;*) subj="[$branchtype] $subj"; esac
410 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
411 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace >>"$git_dir/TG_EDITMSG"+
412 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
413 warntop=1
415 topmsg="$(cat "$git_dir/TG_EDITMSG")"
416 rm -f "$git_dir/TG_EDITMSG"
418 ## Find starting commit to create the base
420 if [ -n "$merge" ]; then
421 # Unshift the first item from the to-merge list
422 branch="${merge%% *}"
423 merge="${merge#* }"
424 # We create a detached head so that we can abort this operation
425 prefix=refs/heads/
426 [ -z "$nodeps" ] || prefix=
427 if [ -n "$unborn" ]; then
428 quiet_info "creating $name base with empty tree..."
429 else
430 quiet_info "creating $name base from $branch..."
431 git checkout -q $iowopt "$(git rev-parse --verify "$prefix$branch^0" --)"
435 ## Set up the topic branch
437 git update-index --index-info <<EOT || die "git update-index failed"
438 0 $nullsha$tab.topdeps
439 0 $nullsha$tab.topmsg
441 rm -rf "$root_dir/.topdeps" "$root_dir/.topmsg"
442 init_reflog "refs/$topbases/$name"
443 if [ -n "$unborn" ]; then
444 mttree="$(git mktree </dev/null)"
445 emsg="tg create empty $name base"
446 [ "refs/heads/$name" = "$unborn" ] || emsg="Initial empty commit"
447 mtcommit="$(git commit-tree -m "$emsg" "$mttree")" || die "git commit-tree failed"
448 git update-ref -m "tgcreate: create ${unborn#refs/heads/}" "HEAD" "$mtcommit" ""
449 [ "refs/heads/$name" = "$unborn" ] || warn "branch ${unborn#refs/heads/} created with empty commit"
450 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "HEAD" ""
451 [ "refs/heads/$name" = "$unborn" ] || git checkout $iowopt -b "$name"
452 else
453 basetree="$(git rev-parse --verify "HEAD^{tree}" --)" && [ -n "$basetree" ] || die "HEAD disappeared"
454 baseptree="$(pretty_tree "HEAD" -r)" || die "pretty_tree HEAD -r (via git mktree) failed"
455 if [ "$basetree" != "$baseptree" ]; then
456 bmsg="tg create $name base"
457 basecommit="$(git commit-tree -p "HEAD" -m "$bmsg" "$baseptree")" || die "git commit-tree failed"
458 else
459 basecommit="HEAD"
461 git update-ref -m "tgcreate: set $name base" "refs/$topbases/$name" "$basecommit" ""
462 [ "$basecommit" = "HEAD" ] || git update-ref -m "tgcreate: set $name base" "HEAD" "$basecommit"
463 git checkout $iowopt -b "$name"
466 if [ -n "$nodeps" ] || [ -z "$deps" ]; then
467 >"$root_dir/.topdeps"
468 else
469 sed 'y/ /\n/' <<-EOT >"$root_dir/.topdeps"
470 $deps
473 git add -f "$root_dir/.topdeps"
474 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
475 git add -f "$root_dir/.topmsg"
476 rm -f "$git_dir/TGMERGE_MSG"
478 [ -z "$warntop" ] || warn ".topmsg content was reformatted into patch header"
479 if [ -n "$nocommit" ]; then
480 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
481 quiet_info "Topic branch $name set up."
482 if [ -n "$noedit" ]; then
483 quiet_info "Please fill in .topmsg now and make the initial commit."
484 else
485 quiet_info "Please make the initial commit."
487 quiet_info "Remember to run $tgdisplay update afterwards."
488 quiet_info "To abort:"
489 quiet_info " git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
490 exit 0
493 git commit -m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die "git commit failed"
494 rawsubj="$(get_subject <"$root_dir/.topmsg")"
495 nommsg=1
496 case "$rawsubj" in *"["[Pp][Aa][Tt][Cc][Hh]"]"*)
497 nommsg=
498 subj="$(sed "s/^[^]]*]//; s/^[ $tab][ $tab]*//; s/[ $tab][ $tab]*\$//" <<-EOT
499 $rawsubj
503 [ -z "$subj" ] || printf '%s\n' "$subj" ""
504 sed -e '1,/^$/d' <"$root_dir/.topmsg"
505 } >"$git_dir/MERGE_MSG"
506 esac
507 quiet_info "Topic branch $name created."
508 [ -n "$merge" ] || exit 0
509 ## Merge other dependencies into the base
510 quiet_info "Running $tgname update to merge in dependencies."
511 [ -n "$nommsg" ] || ! [ -f "$git_dir/MERGE_MSG" ] || mv -f "$git_dir/MERGE_MSG" "$git_dir/TGMERGE_MSG" || :
512 set -- "$name"
513 . "$TG_INST_CMDDIR"/tg-update