tg-revert.sh: convert any top-bases in TOPGIT REFS
[topgit/pro.git] / tg-create.sh
bloba7484c4e4307cd7d11e02c772dcaedea42757969
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
5 # All rights reserved.
6 # GPLv2
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
11 name=
12 rname= # Remote branch to base this one on
13 remote=
14 force=
15 msg=
16 msgfile=
17 topmsg=
18 topmsgfile=
19 noedit=
20 nocommit=
21 nodeps=
22 continue=
23 topmsg=
24 warntop=
25 quiet=
26 branchtype=PATCH
27 branchdesc=patch
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>]] ]"
31 usage()
33 if [ "${1:-0}" != 0 ]; then
34 printf '%s\n' "$USAGE" >&2
35 else
36 printf '%s\n' "$USAGE"
38 exit ${1:-0}
41 is_active()
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
53 return 0
56 quiet_info()
58 [ -n "$quiet" ] || info "$@"
61 ## Parse options
63 while [ $# -gt 0 ]; do case "$1" in
64 -h|--help)
65 usage
67 --quiet|-q)
68 quiet=1
70 --force|-f)
71 force=1
73 --no-commit)
74 nocommit=1
76 -n|--no-edit)
77 noedit=1
78 nocommit=1
80 --no-deps)
81 nodeps=1
82 branchtype=BASE
83 branchdesc=base
85 --continue)
86 continue=1
88 -m|--message|--message=*)
89 case "$1" in --message=*)
90 x="$1"
91 shift
92 set -- --message "${x#--message=}" "$@"
93 esac
94 if [ $# -lt 2 ]; then
95 echo "The $1 option requires an argument" >&2
96 usage 1
98 shift
99 msg="$1"
101 --tm|--tm=*|--topmsg|--topmsg=*)
102 case "$1" in
103 --tm=*)
104 x="$1"
105 shift
106 set -- --tm "${x#--tm=}" "$@";;
107 --topmsg=*)
108 x="$1"
109 shift
110 set -- --topmsg "${x#--topmsg=}" "$@";;
111 esac
112 if [ $# -lt 2 ]; then
113 echo "The $1 option requires an argument" >&2
114 usage 1
116 shift
117 topmsg="$1"
119 -F|--file|--file=*)
120 case "$1" in --file=*)
121 x="$1"
122 shift
123 set -- --file "${x#--file=}" "$@"
124 esac
125 if [ $# -lt 2 ]; then
126 echo "The $1 option requires an argument" >&2
127 usage 1
129 shift
130 msgfile="$1"
132 --tF|--tF=*|--topmsg-file|--topmsg-file=*)
133 case "$1" in
134 --tF=*)
135 x="$1"
136 shift
137 set -- --tF "${x#--tF=}" "$@";;
138 --topmsg-file=*)
139 x="$1"
140 shift
141 set -- --topmsg-file "${x#--topmsg-file=}" "$@";;
142 esac
143 if [ $# -lt 2 ]; then
144 echo "The $1 option requires an argument" >&2
145 usage 1
147 shift
148 topmsgfile="$1"
151 remote=1
152 rname="$2"; [ $# -eq 0 ] || shift
155 shift
156 break
158 -?*)
159 echo "Unknown option: $1" >&2
160 usage 1
163 break
165 esac; shift; done
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 if [ -z "$continue" -a $# -gt 0 ]; then
170 name="$1"
171 shift
172 if [ -z "$remote" -a "$1" = "-r" ]; then
173 remote=1
174 shift;
175 rname="$1"
176 [ $# -eq 0 ] || shift
179 [ -n "$continue" -o -n "$name" ] || { err "no branch name given"; usage 1; }
180 [ -z "$remote" -o -n "$rname" ] || rname="$name"
181 [ -z "$remote" -o -z "$msg$msgfile$topmsg$topmsgfile$nocommit$nodeps" ] || { err "-r may not be combined with other options"; usage 1; }
182 [ $# -eq 0 -o -z "$remote" ] || { err "deps not allowed with -r"; usage 1; }
183 [ $# -le 1 -o -z "$nodeps" ] || { err "--no-deps allows at most one <dep>"; usage 1; }
184 [ -z "$msg" -o -z "$msgfile" ] || die "only one -F or -m option is allowed"
185 [ -z "$continue" ] || is_active || die "no tg create is currently active"
186 [ "$msgfile" != "-" -o "$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"
199 if [ -n "$logrefupdates" ]; then
200 mkdir -p "$git_dir/logs/refs/$topbases/$(dirname "$name")" 2>/dev/null || :
201 { >>"$git_dir/logs/refs/$topbases/$name" || :; } 2>/dev/null
203 msg="tgcreate: $name -r $rname"
204 git update-ref -m "$msg" "refs/$topbases/$name" "refs/remotes/$base_remote/$topbases/$rname" ""
205 git update-ref -m "$msg" "refs/heads/$name" "refs/remotes/$base_remote/$rname" ""
206 quiet_info "Topic branch $name based on $base_remote : $rname set up."
207 exit 0
210 ## Auto-guess dependencies
212 deps="$*"
213 if [ -z "$deps" ]; then
214 if [ -z "$name" ] && is_active; then
215 # We are setting up the base branch now; resume merge!
216 name="$(cat "$git_dir/tg-create/name")"
217 deps="$(cat "$git_dir/tg-create/deps")"
218 merge="$(cat "$git_dir/tg-create/merge")"
219 msg="$(cat "$git_dir/tg-create/msg")"
220 topmsg="$(cat "$git_dir/tg-create/topmsg")"
221 nocommit="$(cat "$git_dir/tg-create/nocommit")"
222 noedit="$(cat "$git_dir/tg-create/noedit")"
223 warntop="$(cat "$git_dir/tg-create/warntop")"
224 quiet="$(cat "$git_dir/tg-create/quiet")"
225 restarted=1
226 quiet_info "Resuming $name setup..."
227 else
228 # The common case
229 [ -z "$name" ] && die "no branch name given"
230 if [ -n "$nodeps" ]; then
231 deps="HEAD"
232 else
233 head="$(git symbolic-ref --quiet HEAD || :)"
234 [ -z "$head" ] || git rev-parse --verify --quiet "$head" -- ||
235 die "refusing to auto-depend on unborn branch (use --no-deps)"
236 deps="${head#refs/heads/}"
237 [ "$deps" != "$head" ] || die "refusing to auto-depend on non-branch ref (${head:-detached HEAD})"
238 quiet_info "automatically marking dependency on $deps"
243 unborn=
244 if [ -n "$nodeps" ]; then
245 # there can be only one dep and it need only be a committish
246 # however, if it's HEAD and HEAD is an unborn branch that's okay too
247 if [ "$deps" = "HEAD" ] && unborn="$(git symbolic-ref --quiet HEAD --)" && ! git rev-parse --verify --quiet HEAD -- >/dev/null; then
248 branchtype=ROOT
249 branchdesc=root
250 else
251 unborn=
252 git rev-parse --quiet --verify "$deps^0" -- >/dev/null ||
253 die "unknown committish \"$deps\""
257 # Non-remote branch set up requires a clean tree unless the single dep is the same tree as HEAD
258 [ -n "$restarted" ] || [ "$deps" = "HEAD" ] || {
259 prefix=refs/heads/
260 [ -z "$nodeps" ] || prefix=
261 [ $# -eq 1 -a "$(git rev-parse --quiet --verify "$prefix$deps^{tree}" --)" = "$(git rev-parse --quiet --verify HEAD^{tree} --)" ] ||
262 (ensure_clean_tree) || {
263 [ $# -ne 1 ] || info "use \`git checkout $deps\` first and then try again"
264 exit 1
268 [ -n "$merge" -o -n "$restarted" ] || merge="$deps "
270 if [ -z "$nodeps" ]; then
271 olddeps="$deps"
272 deps=
273 while read d && [ -n "$d" ]; do
274 if [ "$d" = "HEAD" ]; then
275 sr="$(git symbolic-ref --quiet HEAD || :)"
276 [ -z "$sr" ] || git rev-parse --verify --quiet "$sr" -- ||
277 die "refusing to depend on unborn branch (use --no-deps)"
278 [ -n "$sr" ] || die "cannot depend on a detached HEAD"
279 case "$sr" in refs/heads/*);;*)
280 die "HEAD is a symref to other than refs/heads/..."
281 esac
282 d="${sr#refs/heads/}"
283 else
284 ref_exists "refs/heads/$d" || die "unknown branch dependency '$d'"
286 case " $deps " in
287 *" $d "*)
288 warn "ignoring duplicate depedency $d"
291 deps="${deps:+$deps }$d"
293 esac
294 done <<-EOT
295 $(sed 'y/ /\n/' <<-LIST
296 $olddeps
297 LIST
300 unset olddeps
302 if test="$(git symbolic-ref --quiet "$name" --)"; then case "$test" in
303 refs/heads/*)
304 name="${test#refs/heads/}"
305 break;;
306 refs/"$topbases"/*)
307 name="${test#refs/$topbases/}"
308 break;;
309 esac; fi
310 ! ref_exists "refs/heads/$name" ||
311 die "branch '$name' already exists"
312 ! ref_exists "refs/$topbases/$name" ||
313 die "'$topbases/$name' already exists"
314 [ -n "$force" ] || ! ref_exists "refs/tags/$name" ||
315 die "refusing to create branch with same name as existing tag '$name' without --force"
317 # Clean up any stale stuff
318 rm -rf "$git_dir/tg-create"
320 # Barf now rather than later if missing ident
321 ensure_ident_available
323 # Get messages
325 tab="$(printf '\t.')" && tab="${tab%?}"
326 get_subject()
328 sed -n '1,/^$/p' |
329 grep -i "^Subject[ $tab]*:" |
330 sed -n "s/^[^: $tab][^: $tab]*[ $tab]*:[ $tab]*//; s/[ $tab][ $tab]*\$//; 1p" ||
334 if [ -z "$restarted" ]; then
335 >"$git_dir/TG_EDITMSG"
336 if [ -n "$msgfile" ]; then
337 if [ "$msgfile" = "-" ]; then
338 git stripspace >"$git_dir/TG_EDITMSG"
339 else
340 git stripspace <"$msgfile" >"$git_dir/TG_EDITMSG"
342 elif [ -n "$msg" ]; then
343 printf '%s\n' "$msg" | git stripspace >"$git_dir/TG_EDITMSG"
345 if [ ! -s "$git_dir/TG_EDITMSG" ]; then
346 printf '%s\n' "tg create $name" | git stripspace >"$git_dir/TG_EDITMSG"
348 msg="$(cat "$git_dir/TG_EDITMSG")"
349 rm -f "$git_dir/TG_EDITMSG"
351 >"$git_dir/TG_EDITMSG"
352 if [ -n "$topmsgfile" ]; then
353 if [ "$topmsgfile" = "-" ]; then
354 git stripspace >"$git_dir/TG_EDITMSG"
355 else
356 git stripspace <"$topmsgfile" >"$git_dir/TG_EDITMSG"
358 elif [ -n "$topmsg" ]; then
359 printf '%s\n' "$topmsg" | git stripspace >"$git_dir/TG_EDITMSG"
361 if [ -s "$git_dir/TG_EDITMSG" ]; then
362 noedit=1
363 else
364 author="$(git var GIT_AUTHOR_IDENT)"
365 author_addr="${author%> *}>"
367 echo "From: $author_addr"
368 ! header="$(git config topgit.to)" || echo "To: $header"
369 ! header="$(git config topgit.cc)" || echo "Cc: $header"
370 ! header="$(git config topgit.bcc)" || echo "Bcc: $header"
371 ! subject_prefix="$(git config topgit.subjectprefix)" || subject_prefix="$subject_prefix "
372 echo "Subject: [${subject_prefix}$branchtype] $name"
373 echo
374 echo "<$branchdesc description>"
375 echo
376 echo "Signed-off-by: $author_addr"
377 [ "$(git config --bool format.signoff)" = true ] && echo "Signed-off-by: $author_addr"
378 } | git stripspace >"$git_dir/TG_EDITMSG"
380 if [ -z "$noedit" ]; then
381 cat <<EOT >>"$git_dir/TG_EDITMSG"
383 # Please enter the patch message for the new TopGit branch $name.
384 # It will be stored in the .topmsg file and used to create the
385 # patch header when \`tg patch\` is run on branch $name.
386 # The "Subject:" line will appear in \`tg summary\` and \`tg info\` output.
388 # Lines starting with '#' will be ignored, and an empty patch
389 # message aborts the \`tg create\` operation entirely.
391 # tg create ${nodeps:+--no-deps }$name $deps
393 run_editor "$git_dir/TG_EDITMSG" ||
394 die "there was a problem with the editor '$tg_editor'"
395 git stripspace -s <"$git_dir/TG_EDITMSG" >"$git_dir/TG_EDITMSG"+
396 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
397 [ -s "$git_dir/TG_EDITMSG" ] || die "nothing to do"
399 subj="$(get_subject <"$git_dir/TG_EDITMSG")"
400 if [ -z "$subj" ]; then
401 subj="$(sed -n "s/^[ $tab][ $tab]*//; 1p" <"$git_dir/TG_EDITMSG")";
402 case "$subj" in "["*);;*) subj="[$branchtype] $subj"; esac
403 printf '%s\n' "Subject: $subj" "" >"$git_dir/TG_EDITMSG"+
404 sed -n '2,$p' <"$git_dir/TG_EDITMSG" | git stripspace >>"$git_dir/TG_EDITMSG"+
405 mv -f "$git_dir/TG_EDITMSG"+ "$git_dir/TG_EDITMSG"
406 warntop=1
408 topmsg="$(cat "$git_dir/TG_EDITMSG")"
409 rm -f "$git_dir/TG_EDITMSG"
413 ## Find starting commit to create the base
415 if [ -n "$merge" -a -z "$restarted" ]; then
416 # Unshift the first item from the to-merge list
417 branch="${merge%% *}"
418 merge="${merge#* }"
419 # We create a detached head so that we can abort this operation
420 prefix=refs/heads/
421 [ -z "$nodeps" ] || prefix=
422 if [ -n "$unborn" ]; then
423 quiet_info "creating $name base with empty tree..."
424 else
425 quiet_info "creating $name base from $branch..."
426 git checkout -q "$(git rev-parse --verify "$prefix$branch^0" --)"
431 ## Merge other dependencies into the base
433 while [ -n "$merge" ]; do
434 # Unshift the first item from the to-merge list
435 branch="${merge%% *}"
436 merge="${merge#* }"
437 quiet_info "Merging $name base with $branch..."
439 if ! git merge $auhopt -m "tgcreate: merge $branch into $topbases/$name" "$branch^0"; then
440 info "Please commit merge resolution and call: $tgdisplay create"
441 info "It is also safe to abort this operation using:"
442 info "git$gitcdopt reset --hard some_branch"
443 info "(You are on a detached HEAD now.)"
444 mkdir -p "$git_dir/tg-create"
445 printf '%s\n' "$name" >"$git_dir/tg-create/name"
446 printf '%s\n' "$deps" >"$git_dir/tg-create/deps"
447 printf '%s\n' "$merge" >"$git_dir/tg-create/merge"
448 printf '%s\n' "$msg" >"$git_dir/tg-create/msg"
449 printf '%s\n' "$topmsg" >"$git_dir/tg-create/topmsg"
450 printf '%s\n' "$nocommit" >"$git_dir/tg-create/nocommit"
451 printf '%s\n' "$noedit" >"$git_dir/tg-create/noedit"
452 printf '%s\n' "$warntop" >"$git_dir/tg-create/warntop"
453 printf '%s\n' "$quiet" >"$git_dir/tg-create/quiet"
454 exit 2
456 done
459 ## Set up the topic branch
461 if [ -n "$logrefupdates" ]; then
462 mkdir -p "$git_dir/logs/refs/$topbases/$(dirname "$name")" 2>/dev/null || :
463 { >>"$git_dir/logs/refs/$topbases/$name" || :; } 2>/dev/null
466 if [ -n "$unborn" ]; then
467 mttree="$(git mktree </dev/null)"
468 emsg="tg create empty $topbases/$name"
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 $topbases/$name" "refs/$topbases/$name" "HEAD" ""
474 [ "refs/heads/$name" = "$unborn" ] || git checkout -b "$name"
475 else
476 git update-ref -m "tgcreate: set $topbases/$name" "refs/$topbases/$name" "HEAD" ""
477 git checkout -b "$name"
480 if [ -n "$nodeps" ] || [ -z "$deps" ]; then
481 >"$root_dir/.topdeps"
482 else
483 sed 'y/ /\n/' <<-EOT >"$root_dir/.topdeps"
484 $deps
487 git add -f "$root_dir/.topdeps"
488 printf '%s\n' "$topmsg" >"$root_dir/.topmsg"
489 git add -f "$root_dir/.topmsg"
490 printf '%s\n' "$msg" >"$git_dir/MERGE_MSG"
492 [ -z "$warntop" ] || warn ".topmsg content was reformatted into patch header"
493 if [ -n "$nocommit" ]; then
494 quiet_info "Topic branch $name set up."
495 if [ -n "$noedit" ]; then
496 quiet_info "Please fill in .topmsg now and make the initial commit."
497 else
498 quiet_info "Please make the initial commit."
500 quiet_info "To abort:"
501 quiet_info " git$gitcdopt rm -f .top* && git$gitcdopt checkout ${deps%% *} && $tgdisplay delete $name"
502 exit 0
505 git commit -m "$msg" "$root_dir/.topdeps" "$root_dir/.topmsg" || die "git commit failed"
506 subj="$(get_subject <"$root_dir/.topmsg" |
507 sed "s/^[^]]*]//; s/^[ $tab][ $tab]*//; s/[ $tab][ $tab]*\$//")"
508 if [ -n "$subj" ]; then
509 printf '%s\n' "$subj" ""
510 sed -e '1,/^$/d' <"$root_dir/.topmsg"
511 else
512 cat "$root_dir/.topmsg"
513 fi >"$git_dir/MERGE_MSG"
514 quiet_info "Topic branch $name created."
515 exit 0
517 # vim:noet