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>
8 deps
= # List of dependent branches
9 merge
= # List of branches to be merged; subset of $deps
11 rname
= # Remote branch to base this one on
28 USAGE
="Usage: ${tgname:-tg} [... -r remote] create [-q] [-m <msg> | -F <file>] [--topmsg <msg> | --topmsg-file <file>] [-n] [--no-commit | --no-update] [--base] [<name> [<dep>...|-r [<rname>]] ]"
32 if [ "${1:-0}" != 0 ]; then
33 printf '%s\n' "$USAGE" >&2
35 printf '%s\n' "$USAGE"
42 [ -n "$quiet" ] || info
"$@"
47 while [ $# -gt 0 ]; do case "$1" in
72 -m|
--message|
--message=*)
73 case "$1" in --message=*)
76 set -- --message "${x#--message=}" "$@"
79 echo "The $1 option requires an argument" >&2
85 --tm|
--tm=*|
--topmsg|
--topmsg=*)
90 set -- --tm "${x#--tm=}" "$@";;
94 set -- --topmsg "${x#--topmsg=}" "$@";;
97 echo "The $1 option requires an argument" >&2
104 case "$1" in --file=*)
107 set -- --file "${x#--file=}" "$@"
109 if [ $# -lt 2 ]; then
110 echo "The $1 option requires an argument" >&2
116 --tF|
--tF=*|
--topmsg-file|
--topmsg-file=*)
121 set -- --tF "${x#--tF=}" "$@";;
125 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
127 if [ $# -lt 2 ]; then
128 echo "The $1 option requires an argument" >&2
136 rname
="$2"; [ $# -eq 0 ] ||
shift
143 echo "Unknown option: $1" >&2
151 [ $# -gt 0 -o -z "$rname" ] ||
set -- "$rname"
152 if [ $# -gt 0 ]; then
155 if [ -z "$remote" -a "$1" = "-r" ]; then
159 [ $# -eq 0 ] ||
shift
162 [ -n "$name" ] ||
{ err
"no branch name given"; usage
1; }
163 [ -z "$remote" -o -n "$rname" ] || rname
="$name"
164 [ -z "$remote" -o -z "$msg$msgfile$topmsg$topmsgfile$nocommit$noupdate$nodeps" ] ||
{ err
"-r may not be combined with other options"; usage
1; }
165 [ $# -eq 0 -o -z "$remote" ] ||
{ err
"deps not allowed with -r"; usage
1; }
166 [ $# -le 1 -o -z "$nodeps" ] ||
{ err
"--base (aka --no-deps) allows at most one <dep>"; usage
1; }
167 [ "$nocommit$noupdate" != "11" ] || die
"--no-commit and --no-update are mutually exclusive options"
168 [ -z "$msg" -o -z "$msgfile" ] || die
"only one -F or -m option is allowed"
169 [ "$msgfile" != "-" -o "$topmsgfile" != "-" ] ||
{ err
"--message-file and --topmsg-file may not both be '-'"; usage
1; }
171 ## Fast-track creating branches based on remote ones
173 if [ -n "$rname" ]; then
174 [ -n "$name" ] || die
"no branch name given"
175 ! ref_exists
"refs/heads/$name" || die
"branch '$name' already exists"
176 ! ref_exists
"refs/$topbases/$name" || die
"'$topbases/$name' already exists"
177 if [ -z "$base_remote" ]; then
178 die
"no remote location given. Either use -r remote argument or set topgit.remote"
180 has_remote
"$rname" || die
"no branch $rname in remote $base_remote"
181 init_reflog
"refs/$topases/$name"
182 msg
="tgcreate: $name -r $rname"
183 tbrv
="$(ref_exists_rev "refs
/remotes
/$base_remote/${topbases#heads/}/$rname")" ||
184 tbrv
="$(ref_exists_rev "refs
/remotes
/$base_remote/${oldbases#heads/}/$rname")" ||
185 die
"no branch $rname in remote $base_remote"
186 git update-ref
-m "$msg" "refs/$topbases/$name" "$tbrv" ""
187 git update-ref
-m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
188 quiet_info
"Topic branch $name based on $base_remote : $rname set up."
192 ## Auto-guess dependencies
194 [ "$name" != "@" ] || name
="HEAD"
197 [ -n "$name" ] || die
"no branch name given"
198 if [ -n "$nodeps" ]; then
201 head="$(git symbolic-ref --quiet HEAD)" ||
:
202 [ -z "$head" ] || git rev-parse
--verify --quiet "$head" -- >/dev
/null ||
203 die
"refusing to auto-depend on unborn branch (use --base aka --no-deps)"
204 deps
="${head#refs/heads/}"
205 [ "$deps" != "$head" ] || die
"refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
206 quiet_info
"automatically marking dependency on $deps"
208 elif [ -n "$nodeps" ]; then
211 # verify each dep is valid and expand "@" to "HEAD" and "HEAD" to it's symref (unless detached)
213 head="$(git symbolic-ref --quiet HEAD)" ||
:
215 [ "$d" != "@" ] || d
="HEAD"
216 [ "$d" != "HEAD" ] ||
[ -z "$head" ] || d
="$head"
219 die
"cannot depend on detached HEAD"
225 die
"cannot depend on non-branch ref '$d'"
228 ref_exists
"refs/heads/$d" ||
{
230 case "refs/$d" in refs
/heads
/?
*)
232 ! ref_exists
"refs/heads/$d" || ok
=1
235 [ -n "$ok" ] || die
"unknown branch dependency '$d'"
237 deps
="${deps:+$deps }$d"
242 if [ -n "$nodeps" ]; then
243 # there can be only one dep and it need only be a committish
244 # however, if it's HEAD and HEAD is an unborn branch that's okay too
245 if [ "$deps" = "HEAD" ] && unborn
="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse
--verify --quiet HEAD
-- >/dev
/null
; then
250 git rev-parse
--quiet --verify "$deps^0" -- >/dev
/null ||
251 die
"unknown committish \"$deps\""
255 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as a not unborn HEAD
256 # Also the .topdeps and .topmsg files, if they exist, may not be overwriten unless they are "clean"
259 [ -z "$nodeps" ] || prefix
=
260 ensure_cmd
=ensure_clean_tree
261 if [ -n "$unborn" ]; then
263 elif [ $# -eq 1 ] && { [ "$deps" = "HEAD" ] ||
264 [ "$(git rev-parse --quiet --verify "$prefix$deps^
{tree
}" --)" = "$(git rev-parse --quiet --verify HEAD^{tree} --)" ]; }; then
267 ($ensure_cmd && ensure_clean_topfiles
${unborn:+-u}) ||
{
268 [ $# -ne 1 ] ||
[ "$deps" = "HEAD" ] || info
"use \`git checkout $deps\` first and then try again"
272 [ -n "$merge" ] || merge
="$deps "
274 if [ -z "$nodeps" ]; then
277 while read d
&& [ -n "$d" ]; do
278 if [ "$d" = "HEAD" ]; then
279 sr
="$(git symbolic-ref --quiet HEAD)" ||
:
280 [ -z "$sr" ] || git rev-parse
--verify --quiet "$sr" -- >/dev
/null ||
281 die
"refusing to depend on unborn branch (use --base aka --no-deps)"
282 [ -n "$sr" ] || die
"cannot depend on a detached HEAD"
283 case "$sr" in refs
/heads
/*);;*)
284 die
"HEAD is a symref to other than refs/heads/..."
286 d
="${sr#refs/heads/}"
288 ref_exists
"refs/heads/$d" || die
"unknown branch dependency '$d'"
292 warn
"ignoring duplicate depedency $d"
295 deps
="${deps:+$deps }$d"
299 $(sed 'y/ /\n/' <<-LIST
306 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
308 name
="${test#refs/$topbases/}"
311 name
="${test#refs/heads/}"
314 ! ref_exists
"refs/heads/$name" ||
315 die
"branch '$name' already exists"
316 ! ref_exists
"refs/$topbases/$name" ||
317 die
"'$topbases/$name' already exists"
318 [ -n "$force" ] ||
! ref_exists
"refs/tags/$name" ||
319 die
"refusing to create branch with same name as existing tag '$name' without --force"
321 # Barf now rather than later if missing ident
322 ensure_ident_available
324 if [ -n "$merge" ] && [ -z "$unborn" ]; then
325 # make sure the checkout won't fail
326 branch
="${merge%% *}"
328 [ -z "$nodeps" ] || prefix
=
329 git rev-parse
--quiet --verify "$prefix$branch^0" >/dev
/null ||
330 die
"invalid dependency: $branch"
331 git read-tree
-n -u -m "$prefix$branch^0" ||
332 die
"git checkout \"$branch\" would fail"
337 tab
="$(printf '\t.')" && tab
="${tab%?}"
341 grep -i "^Subject[ $tab]*:" |
342 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
346 >"$git_dir/TG_EDITMSG"
347 if [ -n "$msgfile" ]; then
348 if [ "$msgfile" = "-" ]; then
349 git stripspace
>"$git_dir/TG_EDITMSG"
351 git stripspace
<"$msgfile" >"$git_dir/TG_EDITMSG"
353 elif [ -n "$msg" ]; then
354 printf '%s\n' "$msg" | git stripspace
>"$git_dir/TG_EDITMSG"
356 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
357 printf '%s\n' "tg create $name" | git stripspace
>"$git_dir/TG_EDITMSG"
359 msg
="$(cat "$git_dir/TG_EDITMSG
")"
360 rm -f "$git_dir/TG_EDITMSG"
362 >"$git_dir/TG_EDITMSG"
363 if [ -n "$topmsgfile" ]; then
364 if [ "$topmsgfile" = "-" ]; then
365 git stripspace
>"$git_dir/TG_EDITMSG"
367 git stripspace
<"$topmsgfile" >"$git_dir/TG_EDITMSG"
369 elif [ -n "$topmsg" ]; then
370 printf '%s\n' "$topmsg" | git stripspace |
sed "1s/^[ $tab][ $tab]*//" >"$git_dir/TG_EDITMSG"
372 if [ -s "$git_dir/TG_EDITMSG" ]; then
375 author
="$(git var GIT_AUTHOR_IDENT)"
376 author_addr
="${author%> *}>"
378 echo "From: $author_addr"
379 ! header
="$(git config topgit.to)" ||
echo "To: $header"
380 ! header
="$(git config topgit.cc)" ||
echo "Cc: $header"
381 ! header
="$(git config topgit.bcc)" ||
echo "Bcc: $header"
382 ! subject_prefix
="$(git config topgit.subjectprefix)" || subject_prefix
="$subject_prefix "
383 echo "Subject: [${subject_prefix}$branchtype] $name"
385 echo "#$branchdesc description"
388 [ z
"$(git config --bool format.signoff 2>/dev/null)" != z
"true" ] || sobpfx
=
389 echo "${sobpfx}Signed-off-by: $author_addr"
390 } | git stripspace
>"$git_dir/TG_EDITMSG"
392 if [ -z "$noedit" ]; then
393 cat <<EOT >>"$git_dir/TG_EDITMSG"
395 # Please enter the patch message for the new TopGit branch $name.
396 # It will be stored in the .topmsg file and used to create the
397 # patch header when \`tg patch\` is run on branch $name.
398 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
400 # Lines starting with '#' will be ignored, and an empty patch
401 # message aborts the \`tg create\` operation entirely.
403 # tg create ${nodeps:+--base }$name $deps
405 run_editor
"$git_dir/TG_EDITMSG" ||
406 die
"there was a problem with the editor '$tg_editor'"
407 git stripspace
-s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
408 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
409 [ -s "$git_dir/TG_EDITMSG" ] || die
"nothing to do"
411 subj
="$(get_subject <"$git_dir/TG_EDITMSG
")"
412 if [ -z "$subj" ]; then
413 subj
="$(sed -n "s
/^
[ $tab][ $tab]*//; 1p
" <"$git_dir/TG_EDITMSG
")";
414 case "$subj" in "["*);;*) subj
="[$branchtype] $subj"; esac
415 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
416 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace
>>"$git_dir/TG_EDITMSG"+
417 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
420 topmsg
="$(cat "$git_dir/TG_EDITMSG
")"
421 rm -f "$git_dir/TG_EDITMSG"
423 ## Find starting commit to create the base
425 if [ -n "$merge" ]; then
426 # Unshift the first item from the to-merge list
427 branch
="${merge%% *}"
429 # We create a detached head so that we can abort this operation
431 [ -z "$nodeps" ] || prefix
=
432 if [ -n "$unborn" ]; then
433 quiet_info
"creating $name base with empty tree..."
435 quiet_info
"creating $name base from $branch..."
436 git checkout
-q $iowopt "$(git rev-parse --verify "$prefix$branch^
0" --)"
440 ## Set up the topic branch
442 git update-index
--index-info <<EOT || die "git update-index failed"
443 0 $nullsha$tab.topdeps
444 0 $nullsha$tab.topmsg
446 rm -rf "$root_dir/.topdeps" "$root_dir/.topmsg"
447 init_reflog
"refs/$topbases/$name"
448 if [ -n "$unborn" ]; then
449 mttree
="$(git mktree </dev/null)"
450 emsg
="tg create empty $name base"
451 [ "refs/heads/$name" = "$unborn" ] || emsg
="Initial empty commit"
452 mtcommit
="$(git commit-tree -m "$emsg" "$mttree")" || die
"git commit-tree failed"
453 git update-ref
-m "tgcreate: create ${unborn#refs/heads/}" "HEAD" "$mtcommit" ""
454 [ "refs/heads/$name" = "$unborn" ] || warn
"branch ${unborn#refs/heads/} created with empty commit"
455 git update-ref
-m "tgcreate: set $name base" "refs/$topbases/$name" "HEAD" ""
456 [ "refs/heads/$name" = "$unborn" ] || git checkout
$iowopt -b "$name"
458 basetree
="$(git rev-parse --verify "HEAD^
{tree
}" --)" && [ -n "$basetree" ] || die
"HEAD disappeared"
459 baseptree
="$(pretty_tree "HEAD
" -r)" || die
"pretty_tree HEAD -r (via git mktree) failed"
460 if [ "$basetree" != "$baseptree" ]; then
461 bmsg
="tg create $name base"
462 basecommit
="$(git commit-tree -p "HEAD
" -m "$bmsg" "$baseptree")" || die
"git commit-tree failed"
466 git update-ref
-m "tgcreate: set $name base" "refs/$topbases/$name" "$basecommit" ""
467 [ "$basecommit" = "HEAD" ] || git update-ref
-m "tgcreate: set $name base" "HEAD" "$basecommit"
468 git checkout
$iowopt -b "$name"
471 if [ -n "$nodeps" ] ||
[ -z "$deps" ]; then
472 >"$root_dir/.topdeps"
474 sed 'y/ /\n/' <<-EOT >"$root_dir/.topdeps"
478 git add
-f "$root_dir/.topdeps"
479 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
480 git add
-f "$root_dir/.topmsg"
481 rm -f "$git_dir/TGMERGE_MSG"
483 [ -z "$warntop" ] || warn
".topmsg content was reformatted into patch header"
484 if [ -n "$nocommit" ]; then
485 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
486 quiet_info
"Topic branch $name set up."
487 if [ -n "$noedit" ]; then
488 quiet_info
"Please fill in .topmsg now and make the initial commit."
490 quiet_info
"Please make the initial commit."
492 quiet_info
"Remember to run $tgdisplay update afterwards."
493 quiet_info
"To abort:"
494 quiet_info
" git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
498 git commit
-m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die
"git commit failed"
499 rawsubj
="$(get_subject <"$root_dir/.topmsg
")"
501 case "$rawsubj" in *"["[Pp
][Aa
][Tt
][Cc
][Hh
]"]"*)
503 subj
="$(sed "s
/^
[^
]]*]//; s
/^
[ $tab][ $tab]*//; s
/[ $tab][ $tab]*\$
//" <<-EOT
508 [ -z "$subj" ] ||
printf '%s\n' "$subj" ""
509 sed -e '1,/^$/d' <"$root_dir/.topmsg"
510 } >"$git_dir/MERGE_MSG"
512 quiet_info
"Topic branch $name created."
513 [ -n "$merge" ] ||
exit 0
514 ## Merge other dependencies into the base
515 if [ -n "$noupdate" ]; then
516 quiet_info
"Remember to run $tgdisplay update to merge in dependencies."
519 quiet_info
"Running $tgname update to merge in dependencies."
520 [ -n "$nommsg" ] ||
! [ -f "$git_dir/MERGE_MSG" ] ||
mv -f "$git_dir/MERGE_MSG" "$git_dir/TGMERGE_MSG" ||
:
522 .
"$TG_INST_CMDDIR"/tg-update