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
8 deps
= # List of dependent branches
9 restarted
= # Set to 1 if we are picking up in the middle of base setup
10 merge
= # List of branches to be merged; subset of $deps
12 rname
= # Remote branch to base this one on
29 USAGE
="Usage: ${tgname:-tg} [... -r remote] create [-q] [-m <msg> | -F <file>] [--topmsg <msg> | --topmsg-file <file>] [-n] [--no-commit] [--no-deps] [<name> [<dep>...|-r [<rname>]] ]"
33 if [ "${1:-0}" != 0 ]; then
34 printf '%s\n' "$USAGE" >&2
36 printf '%s\n' "$USAGE"
43 [ -d "$git_dir/tg-create" ] ||
return 1
44 [ -s "$git_dir/tg-create/name" ] ||
return 1
45 [ -s "$git_dir/tg-create/deps" ] ||
return 1
46 [ -s "$git_dir/tg-create/merge" ] ||
return 1
47 [ -s "$git_dir/tg-create/msg" ] ||
return 1
48 [ -s "$git_dir/tg-create/topmsg" ] ||
return 1
49 [ -f "$git_dir/tg-create/nocommit" ] ||
return 1
50 [ -f "$git_dir/tg-create/noedit" ] ||
return 1
51 [ -f "$git_dir/tg-create/warntop" ] ||
return 1
52 [ -f "$git_dir/tg-create/quiet" ] ||
return 1
58 [ -n "$quiet" ] || info
"$@"
63 while [ $# -gt 0 ]; do case "$1" in
88 -m|
--message|
--message=*)
89 case "$1" in --message=*)
92 set -- --message "${x#--message=}" "$@"
95 echo "The $1 option requires an argument" >&2
101 --tm|
--tm=*|
--topmsg|
--topmsg=*)
106 set -- --tm "${x#--tm=}" "$@";;
110 set -- --topmsg "${x#--topmsg=}" "$@";;
112 if [ $# -lt 2 ]; then
113 echo "The $1 option requires an argument" >&2
120 case "$1" in --file=*)
123 set -- --file "${x#--file=}" "$@"
125 if [ $# -lt 2 ]; then
126 echo "The $1 option requires an argument" >&2
132 --tF|
--tF=*|
--topmsg-file|
--topmsg-file=*)
137 set -- --tF "${x#--tF=}" "$@";;
141 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
143 if [ $# -lt 2 ]; then
144 echo "The $1 option requires an argument" >&2
152 rname
="$1"; [ $# -eq 0 ] ||
shift
159 echo "Unknown option: $1" >&2
166 [ $# -gt 0 -o -z "$rname" ] ||
set -- "$rname"
167 [ $# -gt 0 -o -n "$remote$msg$msgfile$topmsg$topmsgfile$nocommit$nodeps" ] ||
continue=1
168 [ -z "$continue" -o "$#$remote$msg$msgfile$topmsg$topmsgfile$nocommit$nodeps" = "0" ] || usage
1
169 [ -n "$continue" -o $# -eq 0 ] ||
{ name
="$1"; shift; }
170 [ -n "$continue" -o -n "$name" ] ||
{ err
"no branch name given"; usage
1; }
171 [ -z "$remote" -o -n "$rname" ] || rname
="$name"
172 [ -z "$remote" -o -z "$msg$msgfile$topmsg$topmsgfile$nocommit$nodeps" ] ||
{ err
"-r may not be combined with other options"; usage
1; }
173 [ $# -eq 0 -o -z "$remote" ] ||
{ err
"deps not allowed with -r"; usage
1; }
174 [ $# -le 1 -o -z "$nodeps" ] ||
{ err
"--no-deps allows at most one <dep>"; usage
1; }
175 [ -z "$msg" -o -z "$msgfile" ] || die
"only one -F or -m option is allowed"
176 [ -z "$continue" ] || is_active || die
"no tg create is currently active"
177 [ "$msgfile" != "-" -o "$topmsgfile" != "-" ] ||
{ err
"--message-file and --topmsg-file may not both be '-'"; usage
1; }
179 ## Fast-track creating branches based on remote ones
181 if [ -n "$rname" ]; then
182 [ -n "$name" ] || die
"no branch name given"
183 ! ref_exists
"refs/heads/$name" || die
"branch '$name' already exists"
184 ! ref_exists
"refs/top-bases/$name" || die
"'top-bases/$name' already exists"
185 if [ -z "$base_remote" ]; then
186 die
"no remote location given. Either use -r remote argument or set topgit.remote"
188 has_remote
"$rname" || die
"no branch $rname in remote $base_remote"
190 if [ -n "$logrefupdates" ]; then
191 mkdir
-p "$git_dir/logs/refs/top-bases/$(dirname "$name")" 2>/dev
/null ||
:
192 { >>"$git_dir/logs/refs/top-bases/$name" ||
:; } 2>/dev
/null
194 msg
="tgcreate: $name -r $rname"
195 git update-ref
-m "$msg" "refs/top-bases/$name" "refs/remotes/$base_remote/top-bases/$rname" ""
196 git update-ref
-m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
197 quiet_info
"Topic branch $name based on $base_remote : $rname set up."
201 ## Auto-guess dependencies
204 if [ -z "$deps" ]; then
205 if [ -z "$name" ] && is_active
; then
206 # We are setting up the base branch now; resume merge!
207 name
="$(cat "$git_dir/tg-create
/name
")"
208 deps
="$(cat "$git_dir/tg-create
/deps
")"
209 merge
="$(cat "$git_dir/tg-create
/merge
")"
210 msg
="$(cat "$git_dir/tg-create
/msg
")"
211 topmsg
="$(cat "$git_dir/tg-create
/topmsg
")"
212 nocommit
="$(cat "$git_dir/tg-create
/nocommit
")"
213 noedit
="$(cat "$git_dir/tg-create
/noedit
")"
214 warntop
="$(cat "$git_dir/tg-create
/warntop
")"
215 quiet
="$(cat "$git_dir/tg-create
/quiet
")"
217 quiet_info
"Resuming $name setup..."
220 [ -z "$name" ] && die
"no branch name given"
221 if [ -n "$nodeps" ]; then
224 head="$(git symbolic-ref --quiet HEAD || :)"
225 [ -z "$head" ] || git rev-parse
--verify --quiet "$head" -- ||
226 die
"refusing to auto-depend on unborn branch (use --no-deps)"
227 deps
="${head#refs/heads/}"
228 [ "$deps" != "$head" ] || die
"refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
229 quiet_info
"automatically marking dependency on $deps"
235 if [ -n "$nodeps" ]; then
236 # there can be only one dep and it need only be a committish
237 # however, if it's HEAD and HEAD is an unborn branch that's okay too
238 if [ "$deps" = "HEAD" ] && unborn
="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse
--verify --quiet HEAD
-- >/dev
/null
; then
243 git rev-parse
--quiet --verify "$deps^0" -- >/dev
/null ||
244 die
"unknown committish \"$deps\""
248 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as HEAD
249 [ -n "$restarted" ] ||
[ "$deps" = "HEAD" ] ||
{
251 [ -z "$nodeps" ] || prefix
=
252 [ $# -eq 1 -a "$(git rev-parse --verify "$prefix$deps^
{tree
}" --)" = "$(git rev-parse --verify HEAD^{tree} --)" ] ||
253 (ensure_clean_tree
) ||
{
254 [ $# -ne 1 ] || info
"use \`git checkout $deps\` first and then try again"
259 [ -n "$merge" -o -n "$restarted" ] || merge
="$deps "
261 if [ -z "$nodeps" ]; then
264 for d
in $olddeps; do
265 if [ "$d" = "HEAD" ]; then
266 sr
="$(git symbolic-ref --quiet HEAD || :)"
267 [ -z "$sr" ] || git rev-parse
--verify --quiet "$sr" -- ||
268 die
"refusing to depend on unborn branch (use --no-deps)"
269 [ -n "$sr" ] || die
"cannot depend on a detached HEAD"
270 case "$sr" in refs
/heads
/*) :;; *)
271 die
"HEAD is a symref to other than refs/heads/..."
273 d
="${sr#refs/heads/}"
275 ref_exists
"refs/heads/$d" || die
"unknown branch dependency '$d'"
279 warn
"ignoring duplicate depedency $d"
282 deps
="${deps:+$deps }$d"
288 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
290 name
="${test#refs/heads/}"
293 name
="${test#refs/top-bases/}"
296 ! ref_exists
"refs/heads/$name" ||
297 die
"branch '$name' already exists"
298 ! ref_exists
"refs/top-bases/$name" ||
299 die
"'top-bases/$name' already exists"
300 [ -n "$force" ] ||
! ref_exists
"refs/tags/$name" ||
301 die
"refusing to create branch with same name as existing tag '$name' without --force"
303 # Clean up any stale stuff
304 rm -rf "$git_dir/tg-create"
309 tab
="$(printf '\t.')" && tab
="${tab%?}"
313 grep -i "^Subject[ $tab]*:" |
314 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
318 if [ -z "$restarted" ]; then
319 >"$git_dir/TG_EDITMSG"
320 if [ -n "$msgfile" ]; then
321 if [ "$msgfile" = "-" ]; then
322 git stripspace
>"$git_dir/TG_EDITMSG"
324 git stripspace
<"$msgfile" >"$git_dir/TG_EDITMSG"
326 elif [ -n "$msg" ]; then
327 printf '%s\n' "$msg" | git stripspace
>"$git_dir/TG_EDITMSG"
329 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
330 printf '%s\n' "tg create $name" | git stripspace
>"$git_dir/TG_EDITMSG"
332 msg
="$(cat "$git_dir/TG_EDITMSG
")"
333 rm -f "$git_dir/TG_EDITMSG"
335 >"$git_dir/TG_EDITMSG"
336 if [ -n "$topmsgfile" ]; then
337 if [ "$topmsgfile" = "-" ]; then
338 git stripspace
>"$git_dir/TG_EDITMSG"
340 git stripspace
<"$topmsgfile" >"$git_dir/TG_EDITMSG"
342 elif [ -n "$topmsg" ]; then
343 printf '%s\n' "$topmsg" | git stripspace
>"$git_dir/TG_EDITMSG"
345 if [ -s "$git_dir/TG_EDITMSG" ]; then
348 author
="$(git var GIT_AUTHOR_IDENT)"
349 author_addr
="${author%> *}>"
351 echo "From: $author_addr"
352 ! header
="$(git config topgit.to)" ||
echo "To: $header"
353 ! header
="$(git config topgit.cc)" ||
echo "Cc: $header"
354 ! header
="$(git config topgit.bcc)" ||
echo "Bcc: $header"
355 ! subject_prefix
="$(git config topgit.subjectprefix)" || subject_prefix
="$subject_prefix "
356 echo "Subject: [${subject_prefix}$branchtype] $name"
358 echo "<$branchdesc description>"
360 echo "Signed-off-by: $author_addr"
361 [ "$(git config --bool format.signoff)" = true
] && echo "Signed-off-by: $author_addr"
362 } | git stripspace
>"$git_dir/TG_EDITMSG"
364 if [ -z "$noedit" ]; then
365 cat <<EOT >>"$git_dir/TG_EDITMSG"
367 # Please enter the patch message for the new TopGit branch $name.
368 # It will be stored in the .topmsg file and used to create the
369 # patch header when \`tg patch\` is run on branch $name.
370 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
372 # Lines starting with '#' will be ignored, and an empty patch
373 # message aborts the \`tg create\` operation entirely.
375 # tg create ${nodeps:+--no-deps }$name $deps
377 run_editor
"$git_dir/TG_EDITMSG" || \
378 die
"there was a problem with the editor '$tg_editor'"
379 git stripspace
-s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
380 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
381 [ -s "$git_dir/TG_EDITMSG" ] || die
"nothing to do"
383 subj
="$(get_subject <"$git_dir/TG_EDITMSG
")"
384 if [ -z "$subj" ]; then
385 subj
="$(sed -n "s
/^
[ $tab][ $tab]*//; 1p
" <"$git_dir/TG_EDITMSG
")";
386 case "$subj" in "["*) :;; *) subj
="[$branchtype] $subj"; esac
387 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
388 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace
>>"$git_dir/TG_EDITMSG"+
389 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
392 topmsg
="$(cat "$git_dir/TG_EDITMSG
")"
393 rm -f "$git_dir/TG_EDITMSG"
397 ## Find starting commit to create the base
399 if [ -n "$merge" -a -z "$restarted" ]; then
400 # Unshift the first item from the to-merge list
401 branch
="${merge%% *}"
403 # We create a detached head so that we can abort this operation
405 [ -z "$nodeps" ] || prefix
=
406 if [ -n "$unborn" ]; then
407 quiet_info
"creating $name base with empty tree..."
409 quiet_info
"creating $name base from $branch..."
410 git checkout
-q "$(git rev-parse --verify "$prefix$branch^
0" --)"
415 ## Merge other dependencies into the base
417 while [ -n "$merge" ]; do
418 # Unshift the first item from the to-merge list
419 branch
="${merge%% *}"
421 quiet_info
"Merging $name base with $branch..."
423 if ! git merge
-m "tgcreate: merge $branch into top-bases/$name" "$branch^0"; then
424 info
"Please commit merge resolution and call: $tgdisplay create"
425 info
"It is also safe to abort this operation using:"
426 info
"git$gitcdopt reset --hard some_branch"
427 info
"(You are on a detached HEAD now.)"
428 mkdir
-p "$git_dir/tg-create"
429 printf '%s\n' "$name" >"$git_dir/tg-create/name"
430 printf '%s\n' "$deps" >"$git_dir/tg-create/deps"
431 printf '%s\n' "$merge" >"$git_dir/tg-create/merge"
432 printf '%s\n' "$msg" >"$git_dir/tg-create/msg"
433 printf '%s\n' "$topmsg" >"$git_dir/tg-create/topmsg"
434 printf '%s\n' "$nocommit" >"$git_dir/tg-create/nocommit"
435 printf '%s\n' "$noedit" >"$git_dir/tg-create/noedit"
436 printf '%s\n' "$warntop" >"$git_dir/tg-create/warntop"
437 printf '%s\n' "$quiet" >"$git_dir/tg-create/quiet"
443 ## Set up the topic branch
445 if [ -n "$logrefupdates" ]; then
446 mkdir
-p "$git_dir/logs/refs/top-bases/$(dirname "$name")" 2>/dev
/null ||
:
447 { >>"$git_dir/logs/refs/top-bases/$name" ||
:; } 2>/dev
/null
450 if [ -n "$unborn" ]; then
451 mttree
="$(git mktree </dev/null)"
452 emsg
="tg create empty top-bases/$name"
453 [ "refs/heads/$name" = "$unborn" ] || emsg
="Initial empty commit"
454 mtcommit
="$(git commit-tree -m "$emsg" "$mttree")" || die
"git commit-tree failed"
455 git update-ref
-m "tgcreate: create ${unborn#refs/heads/}" "HEAD" "$mtcommit" ""
456 [ "refs/heads/$name" = "$unborn" ] || warn
"branch ${unborn#refs/heads/} created with empty commit"
457 git update-ref
-m "tgcreate: set top-bases/$name" "refs/top-bases/$name" "HEAD" ""
458 [ "refs/heads/$name" = "$unborn" ] || git checkout
-b "$name"
460 git update-ref
-m "tgcreate: set top-bases/$name" "refs/top-bases/$name" "HEAD" ""
461 git checkout
-b "$name"
464 if [ -n "$nodeps" ]; then
465 >"$root_dir/.topdeps"
467 printf '%s\n' $deps >"$root_dir/.topdeps"
469 git add
-f "$root_dir/.topdeps"
470 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
471 git add
-f "$root_dir/.topmsg"
472 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
474 [ -z "$warntop" ] || warn
".topmsg content was reformatted into patch header"
475 if [ -n "$nocommit" ]; then
476 quiet_info
"Topic branch $name set up."
477 if [ -n "$noedit" ]; then
478 quiet_info
"Please fill in .topmsg now and make the initial commit."
480 quiet_info
"Please make the initial commit."
482 quiet_info
"To abort:"
483 quiet_info
" git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
487 git commit
-m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die
"git commit failed"
488 subj
="$(get_subject <"$root_dir/.topmsg
" |
489 sed "s
/^
[^
]]*]//; s
/^
[ $tab][ $tab]*//; s
/[ $tab][ $tab]*\$
//")"
490 if [ -n "$subj" ]; then
491 printf '%s\n' "$subj" ""
492 sed -e '1,/^$/d' <"$root_dir/.topmsg"
494 cat "$root_dir/.topmsg"
495 fi >"$git_dir/MERGE_MSG"
496 quiet_info
"Topic branch $name created."