tg-update.sh: improve .topdeps and .topmsg merge handling
[topgit/pro.git] / tg.sh
blobc3980dd440745d7a78bd2627f39c9a1fef94f547
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> 2014,2015,2016
5 # All rights reserved.
6 # GPLv2
8 TG_VERSION=0.19.7
10 # Update in Makefile if you add any code that requires a newer version of git
11 GIT_MINIMUM_VERSION="@mingitver@"
13 ## SHA-1 pattern
15 octet='[0-9a-f][0-9a-f]'
16 octet4="$octet$octet$octet$octet"
17 octet19="$octet4$octet4$octet4$octet4$octet$octet$octet"
18 octet20="$octet4$octet4$octet4$octet4$octet4"
19 nullsha="0000000000000000000000000000000000000000"
20 tab=' '
21 lf='
24 ## Auxiliary functions
26 # Preserves current $? value while triggering a non-zero set -e exit if active
27 # This works even for shells that sometimes fail to correctly trigger a -e exit
28 check_exit_code()
30 return $?
33 # This is the POSIX equivalent of which
34 cmd_path()
36 { "unset" -f command unset unalias "$1"; } >/dev/null 2>&1 || :
37 { "unalias" -a; } >/dev/null 2>&1 || :
38 command -v "$1"
41 # Output arguments without any possible interpretation
42 # (Avoid misinterpretation of '\' characters or leading "-n", "-E" or "-e")
43 echol()
45 printf '%s\n' "$*"
48 info()
50 echol "${TG_RECURSIVE}${tgname:-tg}: $*"
53 warn()
55 info "warning: $*" >&2
58 err()
60 info "error: $*" >&2
63 die()
65 info "fatal: $*" >&2
66 exit 1
69 # shift off first arg then return "$*" properly quoted in single-quotes
70 # if $1 was '' output goes to stdout otherwise it's assigned to $1
71 # the final \n, if any, is omitted from the result but any others are included
72 v_quotearg()
74 _quotearg_v="$1"
75 shift
76 set -- "$_quotearg_v" \
77 "sed \"s/'/'\\\\\\''/g;1s/^/'/;\\\$s/\\\$/'/;s/'''/'/g;1s/^''\\(.\\)/\\1/\"" "$*"
78 unset _quotearg_v
79 if [ -z "$3" ]; then
80 if [ -z "$1" ]; then
81 echo "''"
82 else
83 eval "$1=\"''\""
85 else
86 if [ -z "$1" ]; then
87 printf "%s$4" "$3" | eval "$2"
88 else
89 eval "$1="'"$(printf "%s$4" "$3" | eval "$2")"'
94 # same as v_quotearg except there's no extra $1 so output always goes to stdout
95 quotearg()
97 v_quotearg '' "$@"
100 vcmp()
102 # Compare $1 to $3 each of which must match ^[^0-9]*\d*(\.\d*)*.*$
103 # where only the "\d*" parts in the regex participate in the comparison
104 # Since EVERY string matches that regex this function is easy to use
105 # An empty string ('') for $1 or $3 or any "\d*" part is treated as 0
106 # $2 is a compare op '<', '<=', '=', '==', '!=', '>=', '>'
107 # Return code is 0 for true, 1 for false (or unknown compare op)
108 # There is NO difference in behavior between '=' and '=='
109 # Note that "vcmp 1.8 == 1.8.0.0.0.0" correctly returns 0
110 set -- "$1" "$2" "$3" "${1%%[0-9]*}" "${3%%[0-9]*}"
111 set -- "${1#"$4"}" "$2" "${3#"$5"}"
112 set -- "${1%%[!0-9.]*}" "$2" "${3%%[!0-9.]*}"
113 while
114 vcmp_a_="${1%%.*}"
115 vcmp_b_="${3%%.*}"
116 [ "z$vcmp_a_" != "z" -o "z$vcmp_b_" != "z" ]
118 if [ "${vcmp_a_:-0}" -lt "${vcmp_b_:-0}" ]; then
119 unset vcmp_a_ vcmp_b_
120 case "$2" in "<"|"<="|"!=") return 0; esac
121 return 1
122 elif [ "${vcmp_a_:-0}" -gt "${vcmp_b_:-0}" ]; then
123 unset vcmp_a_ vcmp_b_
124 case "$2" in ">"|">="|"!=") return 0; esac
125 return 1;
127 vcmp_a_="${1#$vcmp_a_}"
128 vcmp_b_="${3#$vcmp_b_}"
129 set -- "${vcmp_a_#.}" "$2" "${vcmp_b_#.}"
130 done
131 unset vcmp_a_ vcmp_b_
132 case "$2" in "="|"=="|"<="|">=") return 0; esac
133 return 1
136 precheck() {
137 if ! git_version="$(git version)"; then
138 die "'git version' failed"
140 case "$git_version" in [Gg]"it version "*);;*)
141 die "'git version' output does not start with 'git version '"
142 esac
144 vcmp "$git_version" '>=' "$GIT_MINIMUM_VERSION" ||
145 die "git version >= $GIT_MINIMUM_VERSION required but found $git_version instead"
148 case "$1" in version|--version|-V)
149 echo "TopGit version $TG_VERSION"
150 exit 0
151 esac
153 precheck
154 [ "$1" = "precheck" ] && exit 0
157 cat_depsmsg_internal()
159 _rev="$(ref_exists_rev "refs/heads/$1")" || return 0
160 if [ -s "$tg_cache_dir/$1/.$2" ]; then
161 if read _rev_match && [ "$_rev" = "$_rev_match" ]; then
162 _line=
163 while IFS= read -r _line || [ -n "$_line" ]; do
164 printf '%s\n' "$_line"
165 done
166 return 0
167 fi <"$tg_cache_dir/$1/.$2"
169 [ -d "$tg_cache_dir/$1" ] || mkdir -p "$tg_cache_dir/$1" 2>/dev/null || :
170 if [ -d "$tg_cache_dir/$1" ]; then
171 printf '%s\n' "$_rev" >"$tg_cache_dir/$1/.$2"
172 _line=
173 git cat-file blob "$_rev:.$2" 2>/dev/null |
174 while IFS= read -r _line || [ -n "$_line" ]; do
175 printf '%s\n' "$_line" >&3
176 printf '%s\n' "$_line"
177 done 3>>"$tg_cache_dir/$1/.$2"
178 else
179 git cat-file blob "$_rev:.$2" 2>/dev/null
183 # cat_deps BRANCHNAME
184 # Caches result
185 cat_deps()
187 cat_depsmsg_internal "$1" topdeps
190 # cat_msg BRANCHNAME
191 # Caches result
192 cat_msg()
194 cat_depsmsg_internal "$1" topmsg
197 # cat_file TOPIC:PATH [FROM]
198 # cat the file PATH from branch TOPIC when FROM is empty.
199 # FROM can be -i or -w, than the file will be from the index or worktree,
200 # respectively. The caller should than ensure that HEAD is TOPIC, to make sense.
201 cat_file()
203 path="$1"
204 case "$2" in
206 cat "$root_dir/${path#*:}"
209 # ':file' means cat from index
210 git cat-file blob ":${path#*:}" 2>/dev/null
213 case "$path" in
214 refs/heads/*:.topdeps)
215 _temp="${path%:.topdeps}"
216 cat_deps "${_temp#refs/heads/}"
218 refs/heads/*:.topmsg)
219 _temp="${path%:.topmsg}"
220 cat_msg "${_temp#refs/heads/}"
223 git cat-file blob "$path" 2>/dev/null
225 esac
228 die "Wrong argument to cat_file: '$2'"
230 esac
233 # if use_alt_temp_odb and tg_use_alt_odb are true try to write the object(s)
234 # into the temporary alt odb area instead of the usual location
235 git_temp_alt_odb_cmd()
237 if [ -n "$use_alt_temp_odb" ] && [ -n "$tg_use_alt_odb" ] &&
238 [ -n "$TG_OBJECT_DIRECTORY" ] &&
239 [ -f "$TG_OBJECT_DIRECTORY/info/alternates" ]; then
241 GIT_ALTERNATE_OBJECT_DIRECTORIES="$TG_PRESERVED_ALTERNATES"
242 GIT_OBJECT_DIRECTORY="$TG_OBJECT_DIRECTORY"
243 unset TG_OBJECT_DIRECTORY TG_PRESERVED_ALTERNATES
244 export GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_OBJECT_DIRECTORY
245 git "$@"
247 else
248 git "$@"
252 git_write_tree() { git_temp_alt_odb_cmd write-tree "$@"; }
253 git_mktree() { git_temp_alt_odb_cmd mktree "$@"; }
255 # get tree for the committed topic
256 get_tree_()
258 echo "refs/heads/$1"
261 # get tree for the base
262 get_tree_b()
264 echo "refs/$topbases/$1"
267 # get tree for the index
268 get_tree_i()
270 git_write_tree
273 # get tree for the worktree
274 get_tree_w()
276 i_tree=$(git_write_tree)
278 # the file for --index-output needs to sit next to the
279 # current index file
280 cd "$root_dir"
281 : ${GIT_INDEX_FILE:="$git_dir/index"}
282 TMP_INDEX="$(mktemp "${GIT_INDEX_FILE}-tg.XXXXXX")"
283 git read-tree -m $i_tree --index-output="$TMP_INDEX" &&
284 GIT_INDEX_FILE="$TMP_INDEX" &&
285 export GIT_INDEX_FILE &&
286 git diff --name-only -z HEAD |
287 git update-index -z --add --remove --stdin &&
288 git_write_tree &&
289 rm -f "$TMP_INDEX"
293 # get tree for arbitrary ref
294 get_tree_r()
296 echo "$1"
299 # strip_ref "$(git symbolic-ref HEAD)"
300 # Output will have a leading refs/heads/ or refs/$topbases/ stripped if present
301 strip_ref()
303 case "$1" in
304 refs/"$topbases"/*)
305 echol "${1#refs/$topbases/}"
307 refs/heads/*)
308 echol "${1#refs/heads/}"
311 echol "$1"
312 esac
315 # pretty_tree [-t] NAME [-b | -i | -w | -r]
316 # Output tree ID of a cleaned-up tree without tg's artifacts.
317 # NAME will be ignored for -i and -w, but needs to be present
318 # With -r NAME must be a full ref name to a treeish (it's used as-is)
319 # If -t is used the tree is written into the alternate temporary objects area
320 pretty_tree()
322 use_alt_temp_odb=
323 [ "$1" != "-t" ] || { shift; use_alt_temp_odb=1; }
324 name="$1"
325 source="${2#?}"
326 git ls-tree --full-tree "$(get_tree_$source "$name")" |
327 LC_ALL=C sed -ne '/ \.top.*$/!p' |
328 git_mktree
331 # return an empty-tree root commit -- date is either passed in or current
332 # If passed in "$*" must be epochsecs followed by optional hhmm offset (+0000 default)
333 # An invalid secs causes the current date to be used, an invalid zone offset
334 # causes +0000 to be used
335 make_empty_commit()
337 # the empty tree is guaranteed to always be there even in a repo with
338 # zero objects, but for completeness we force it to exist as a real object
339 SECS=
340 read -r SECS ZONE JUNK <<-EOT || :
343 case "$SECS" in *[!0-9]*) SECS=; esac
344 if [ -z "$SECS" ]; then
345 MTDATE="$(date '+%s %z')"
346 else
347 case "$ZONE" in
348 -[01][0-9][0-5][0-9]|+[01][0-9][0-5][0-9])
350 [01][0-9][0-5][0-9])
351 ZONE="+$ZONE"
354 ZONE="+0000"
355 esac
356 MTDATE="$SECS $ZONE"
358 EMPTYID="- <-> $MTDATE"
359 EMPTYTREE="$(git hash-object -t tree -w --stdin < /dev/null)"
360 printf '%s\n' "tree $EMPTYTREE" "author $EMPTYID" "committer $EMPTYID" '' |
361 git hash-object -t commit -w --stdin
364 # setup_hook NAME
365 setup_hook()
367 tgname="${0##*/}"
368 hook_call="\"\$(\"$tgname\" --hooks-path)\"/$1 \"\$@\""
369 if [ -f "$git_hooks_dir/$1" ] && LC_ALL=C grep -Fq "$hook_call" "$git_hooks_dir/$1"; then
370 # Another job well done!
371 return
373 # Prepare incantation
374 hook_chain=
375 if [ -s "$git_hooks_dir/$1" -a -x "$git_hooks_dir/$1" ]; then
376 hook_call="$hook_call"' || exit $?'
377 if [ -L "$git_hooks_dir/$1" ] || ! LC_ALL=C sed -n 1p <"$git_hooks_dir/$1" | LC_ALL=C grep -Fqx "#!@SHELL_PATH@"; then
378 chain_num=
379 while [ -e "$git_hooks_dir/$1-chain$chain_num" ]; do
380 chain_num=$(( $chain_num + 1 ))
381 done
382 mv -f "$git_hooks_dir/$1" "$git_hooks_dir/$1-chain$chain_num"
383 hook_chain=1
385 else
386 hook_call="exec $hook_call"
387 [ -d "$git_hooks_dir" ] || mkdir -p "$git_hooks_dir" || :
389 # Don't call hook if tg is not installed
390 hook_call="if command -v \"$tgname\" >/dev/null 2>&1; then $hook_call; fi"
391 # Insert call into the hook
393 echol "#!@SHELL_PATH@"
394 echol "$hook_call"
395 if [ -n "$hook_chain" ]; then
396 echol "exec \"\$0-chain$chain_num\" \"\$@\""
397 else
398 [ ! -s "$git_hooks_dir/$1" ] || cat "$git_hooks_dir/$1"
400 } >"$git_hooks_dir/$1+"
401 chmod a+x "$git_hooks_dir/$1+"
402 mv "$git_hooks_dir/$1+" "$git_hooks_dir/$1"
405 # setup_ours (no arguments)
406 setup_ours()
408 if [ ! -s "$git_common_dir/info/attributes" ] || ! grep -q topmsg "$git_common_dir/info/attributes"; then
409 [ -d "$git_common_dir/info" ] || mkdir "$git_common_dir/info"
411 echo ".topmsg merge=ours"
412 echo ".topdeps merge=ours"
413 } >>"$git_common_dir/info/attributes"
415 if ! git config merge.ours.driver >/dev/null; then
416 git config merge.ours.name '"always keep ours" merge driver'
417 git config merge.ours.driver 'touch %A'
421 # measure_branch NAME [BASE] [EXTRAHEAD...]
422 measure_branch()
424 _bname="$1"; _base="$2"
425 shift; shift
426 [ -n "$_base" ] || _base="refs/$topbases/$(strip_ref "$_bname")"
427 # The caller should've verified $name is valid
428 _commits="$(git rev-list --count "$_bname" "$@" ^"$_base" --)"
429 _nmcommits="$(git rev-list --count --no-merges "$_bname" "$@" ^"$_base" --)"
430 if [ $_commits -ne 1 ]; then
431 _suffix="commits"
432 else
433 _suffix="commit"
435 echo "$_commits/$_nmcommits $_suffix"
438 # true if $1 is contained by (or the same as) $2
439 # this is never slower than merge-base --is-ancestor and is often slightly faster
440 contained_by()
442 [ "$(git rev-list --count --max-count=1 "$1" --not "$2" --)" = "0" ]
445 # branch_contains B1 B2
446 # Whether B1 is a superset of B2.
447 branch_contains()
449 _revb1="$(ref_exists_rev "$1")" || return 0
450 _revb2="$(ref_exists_rev "$2")" || return 0
451 if [ -s "$tg_cache_dir/$1/.bc/$2/.d" ]; then
452 if read _result _rev_matchb1 _rev_matchb2 &&
453 [ "$_revb1" = "$_rev_matchb1" -a "$_revb2" = "$_rev_matchb2" ]; then
454 return $_result
455 fi <"$tg_cache_dir/$1/.bc/$2/.d"
457 [ -d "$tg_cache_dir/$1/.bc/$2" ] || mkdir -p "$tg_cache_dir/$1/.bc/$2" 2>/dev/null || :
458 _result=0
459 contained_by "$_revb2" "$_revb1" || _result=1
460 if [ -d "$tg_cache_dir/$1/.bc/$2" ]; then
461 echo "$_result" "$_revb1" "$_revb2" >"$tg_cache_dir/$1/.bc/$2/.d"
463 return $_result
466 create_ref_dirs()
468 [ ! -s "$tg_tmp_dir/tg~ref-dirs-created" -a -s "$tg_ref_cache" ] || return 0
469 LC_ALL=C awk -v p="$tg_tmp_dir/cached/" '{print p $1}' <"$tg_ref_cache" | LC_ALL=C tr '\n' '\0' | xargs -0 mkdir -p
470 echo 1 >"$tg_tmp_dir/tg~ref-dirs-created"
473 # If the first argument is non-empty, stores "1" there if this call created the cache
474 v_create_ref_cache()
476 [ -n "$tg_ref_cache" -a ! -s "$tg_ref_cache" ] || return 0
477 _remotespec=
478 [ -z "$base_remote" ] || _remotespec="refs/remotes/$base_remote"
479 [ -z "$1" ] || eval "$1=1"
480 git for-each-ref --format='%(refname) %(objectname)' \
481 refs/heads "refs/$topbases" $_remotespec >"$tg_ref_cache"
482 create_ref_dirs
485 remove_ref_cache()
487 [ -n "$tg_ref_cache" -a -s "$tg_ref_cache" ] || return 0
488 >"$tg_ref_cache"
491 # setting tg_ref_cache_only to non-empty will force non-$tg_ref_cache lookups to fail
492 rev_parse()
494 if [ -n "$tg_ref_cache" -a -s "$tg_ref_cache" ]; then
495 LC_ALL=C awk -v r="$1" 'BEGIN {e=1}; $1 == r {print $2; e=0; exit}; END {exit e}' <"$tg_ref_cache"
496 else
497 [ -z "$tg_ref_cache_only" ] || return 1
498 git rev-parse --quiet --verify "$1^0" -- 2>/dev/null
502 # ref_exists_rev REF
503 # Whether REF is a valid ref name
504 # REF must be fully qualified and start with refs/heads/, refs/$topbases/
505 # or, if $base_remote is set, refs/remotes/$base_remote/
506 # Caches result if $tg_read_only and outputs HASH on success
507 ref_exists_rev()
509 case "$1" in
510 refs/*)
512 $octet20)
513 printf '%s' "$1"
514 return;;
516 die "ref_exists_rev requires fully-qualified ref name"
517 esac
518 [ -n "$tg_read_only" ] || { git rev-parse --quiet --verify "$1^0" -- 2>/dev/null; return; }
519 _result=
520 _result_rev=
521 { read -r _result _result_rev <"$tg_tmp_dir/cached/$1/.ref"; } 2>/dev/null || :
522 [ -z "$_result" ] || { printf '%s' "$_result_rev"; return $_result; }
523 _result_rev="$(rev_parse "$1")"
524 _result=$?
525 [ -d "$tg_tmp_dir/cached/$1" ] || mkdir -p "$tg_tmp_dir/cached/$1" 2>/dev/null
526 [ ! -d "$tg_tmp_dir/cached/$1" ] ||
527 echo $_result $_result_rev >"$tg_tmp_dir/cached/$1/.ref" 2>/dev/null || :
528 printf '%s' "$_result_rev"
529 return $_result
532 # Same as ref_exists_rev but output is abbreviated hash
533 # Optional second argument defaults to --short but may be any --short=.../--no-short option
534 ref_exists_rev_short()
536 case "$1" in
537 refs/*)
539 $octet20)
542 die "ref_exists_rev_short requires fully-qualified ref name"
543 esac
544 [ -n "$tg_read_only" ] || { git rev-parse --quiet --verify ${2:---short} "$1^0" -- 2>/dev/null; return; }
545 _result=
546 _result_rev=
547 { read -r _result _result_rev <"$tg_tmp_dir/cached/$1/.rfs"; } 2>/dev/null || :
548 [ -z "$_result" ] || { printf '%s' "$_result_rev"; return $_result; }
549 _result_rev="$(rev_parse "$1")"
550 _result=$?
551 if [ $_result -eq 0 ]; then
552 _result_rev="$(git rev-parse --verify ${2:---short} --quiet "$_result_rev" --)"
553 _result=$?
555 [ -d "$tg_tmp_dir/cached/$1" ] || mkdir -p "$tg_tmp_dir/cached/$1" 2>/dev/null
556 [ ! -d "$tg_tmp_dir/cached/$1" ] ||
557 echo $_result $_result_rev >"$tg_tmp_dir/cached/$1/.rfs" 2>/dev/null || :
558 printf '%s' "$_result_rev"
559 return $_result
562 # ref_exists REF
563 # Whether REF is a valid ref name
564 # REF must be fully qualified and start with refs/heads/, refs/$topbases/
565 # or, if $base_remote is set, refs/remotes/$base_remote/
566 # Caches result
567 ref_exists()
569 ref_exists_rev "$1" >/dev/null
572 # rev_parse_tree REF
573 # Runs git rev-parse REF^{tree}
574 # Caches result if $tg_read_only
575 rev_parse_tree()
577 [ -n "$tg_read_only" ] || { git rev-parse --verify "$1^{tree}" -- 2>/dev/null; return; }
578 if [ -f "$tg_tmp_dir/cached/$1/.rpt" ]; then
579 if IFS= read -r _result <"$tg_tmp_dir/cached/$1/.rpt"; then
580 printf '%s\n' "$_result"
581 return 0
583 return 1
585 [ -d "$tg_tmp_dir/cached/$1" ] || mkdir -p "$tg_tmp_dir/cached/$1" 2>/dev/null || :
586 if [ -d "$tg_tmp_dir/cached/$1" ]; then
587 git rev-parse --verify "$1^{tree}" -- >"$tg_tmp_dir/cached/$1/.rpt" 2>/dev/null || :
588 if IFS= read -r _result <"$tg_tmp_dir/cached/$1/.rpt"; then
589 printf '%s\n' "$_result"
590 return 0
592 return 1
594 git rev-parse --verify "$1^{tree}" -- 2>/dev/null
597 # has_remote BRANCH
598 # Whether BRANCH has a remote equivalent (accepts ${topbases#heads/}/ too)
599 has_remote()
601 [ -n "$base_remote" ] && ref_exists "refs/remotes/$base_remote/$1"
604 # Return the verified TopGit branch name for "$2" in "$1" or die with an error.
605 # If -z "$1" still set return code but do not return result
606 # As a convenience, if HEAD or @ is given and HEAD is a symbolic ref to
607 # refs/heads/... then ... will be verified instead.
608 # if "$3" = "-f" (for fail) then return an error rather than dying.
609 v_verify_topgit_branch()
611 if [ "$2" = "HEAD" ] || [ "$2" = "@" ]; then
612 _verifyname="$(git symbolic-ref HEAD 2>/dev/null)" || :
613 [ -n "$_verifyname" -o "$3" = "-f" ] || die "HEAD is not a symbolic ref"
614 case "$_verifyname" in refs/"$topbases"/*|refs/heads/*);;*)
615 [ "$3" != "-f" ] || return 1
616 die "HEAD is not a symbolic ref to the refs/heads namespace"
617 esac
618 set -- "$1" "$_verifyname" "$3"
620 case "$2" in
621 refs/"$topbases"/*)
622 _verifyname="${2#refs/$topbases/}"
624 refs/heads/*)
625 _verifyname="${2#refs/heads/}"
628 _verifyname="$2"
630 esac
631 if ! ref_exists "refs/heads/$_verifyname"; then
632 [ "$3" != "-f" ] || return 1
633 die "no such branch: $_verifyname"
635 if ! ref_exists "refs/$topbases/$_verifyname"; then
636 [ "$3" != "-f" ] || return 1
637 die "not a TopGit-controlled branch: $_verifyname"
639 [ -z "$1" ] || eval "$1="'"$_verifyname"'
642 # Return the verified TopGit branch name or die with an error.
643 # As a convenience, if HEAD or @ is given and HEAD is a symbolic ref to
644 # refs/heads/... then ... will be verified instead.
645 # if "$2" = "-f" (for fail) then return an error rather than dying.
646 verify_topgit_branch()
648 v_verify_topgit_branch _verifyname "$@" || return
649 printf '%s' "$_verifyname"
652 # Caches result
653 # $1 = branch name (i.e. "t/foo/bar")
654 # $2 = optional result of rev-parse "refs/heads/$1"
655 # $3 = optional result of rev-parse "refs/$topbases/$1"
656 branch_annihilated()
658 _branch_name="$1"
659 _rev="${2:-$(ref_exists_rev "refs/heads/$_branch_name")}"
660 _rev_base="${3:-$(ref_exists_rev "refs/$topbases/$_branch_name")}"
662 _result=
663 _result_rev=
664 _result_rev_base=
665 { read -r _result _result_rev _result_rev_base <"$tg_cache_dir/$_branch_name/.ann"; } 2>/dev/null || :
666 [ -z "$_result" -o "$_result_rev" != "$_rev" -o "$_result_rev_base" != "$_rev_base" ] || return $_result
668 # use the merge base in case the base is ahead.
669 mb="$(git merge-base "$_rev_base" "$_rev" 2>/dev/null)"
671 test -z "$mb" || test "$(rev_parse_tree "$mb")" = "$(rev_parse_tree "$_rev")"
672 _result=$?
673 [ -d "$tg_cache_dir/$_branch_name" ] || mkdir -p "$tg_cache_dir/$_branch_name" 2>/dev/null
674 [ ! -d "$tg_cache_dir/$_branch_name" ] ||
675 echo $_result $_rev $_rev_base >"$tg_cache_dir/$_branch_name/.ann" 2>/dev/null || :
676 return $_result
679 non_annihilated_branches()
681 [ $# -gt 0 ] || set -- "refs/$topbases"
682 git for-each-ref --format='%(objectname) %(refname)' "$@" |
683 while read rev ref; do
684 name="${ref#refs/$topbases/}"
685 if branch_annihilated "$name" "" "$rev"; then
686 continue
688 echol "$name"
689 done
692 # Make sure our tree is clean
693 # if optional "$1" given also verify that a checkout to "$1" would succeed
694 ensure_clean_tree()
696 check_status
697 [ -z "$tg_state$git_state" ] || { do_status; exit 1; }
698 git update-index --ignore-submodules --refresh ||
699 die "the working directory has uncommitted changes (see above) - first commit or reset them"
700 [ -z "$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)" ] ||
701 die "the index has uncommited changes"
702 [ -z "$1" ] || git read-tree -n -u -m "$1" ||
703 die "git checkout \"$1\" would fail"
706 # is_sha1 REF
707 # Whether REF is a SHA1 (compared to a symbolic name).
708 is_sha1()
710 case "$1" in $octet20) return 0;; esac
711 return 1
714 # recurse_deps_internal NAME [BRANCHPATH...]
715 # get recursive list of dependencies with leading 0 if branch exists 1 if missing
716 # followed by a 1 if the branch is "tgish" (2 if it also has a remote); 0 if not
717 # followed by a 0 for a non-leaf, 1 for a leaf or 2 for annihilated tgish
718 # but missing and remotes are always "0"
719 # then the branch name followed by its depedency chain (which might be empty)
720 # An output line might look like this:
721 # 0 1 1 t/foo/leaf t/foo/int t/stage
722 # If no_remotes is non-empty, exclude remotes
723 # If recurse_preorder is non-empty, do a preorder rather than postorder traversal
724 # but the leaf info will always be 0 or 2 in that case
725 # If with_top_level is non-empty, include the top-level that's normally omitted
726 # any branch names in the space-separated recurse_deps_exclude variable
727 # are skipped (along with their dependencies)
728 recurse_deps_internal()
730 case " $recurse_deps_exclude " in *" $1 "*) return 0; esac
731 _ref_hash=
732 if ! _ref_hash="$(ref_exists_rev "refs/heads/$1")"; then
733 [ -z "$2" ] || echo "1 0 0 $*"
734 return 0
737 _is_tgish=0
738 _ref_hash_base=
739 _is_leaf=0
740 ! _ref_hash_base="$(ref_exists_rev "refs/$topbases/$1")" || _is_tgish=1
741 [ "$_is_tgish" = "0" ] || [ -n "$no_remotes" ] || ! has_remote "${topbases#heads/}/$1" || _is_tgish=2
742 [ "$_is_tgish" = "0" ] || ! branch_annihilated "$1" "$_ref_hash" "$_ref_hash_base" || _is_leaf=2
743 [ -z "$recurse_preorder" -o -z "${2:-$with_top_level}" ] || echo "0 $_is_tgish $_is_leaf $*"
745 # If no_remotes is unset also check our base against remote base.
746 # Checking our head against remote head has to be done in the helper.
747 if [ "$_is_tgish" = "2" ]; then
748 echo "0 0 0 refs/remotes/$base_remote/${topbases#heads/}/$1 $*"
751 # if the branch was annihilated, it is considered to have no dependencies
752 [ "$_is_leaf" = "2" ] || _is_leaf=1
753 if [ "$_is_tgish" != "0" ] && [ "$_is_leaf" = "1" ]; then
754 #TODO: handle nonexisting .topdeps?
755 while read _dname && [ -n "$_dname" ]; do
756 # Avoid depedency loops
757 case " $* " in *" $_dname "*)
758 warn "dependency loop detected in branch $_dname"
759 _is_leaf=0
760 continue
761 esac
762 # Shoo shoo, leave our environment alone!
763 _dep_is_leaf=0
764 (recurse_deps_internal "$_dname" "$@") || _dep_is_leaf=$?
765 [ "$_dep_is_leaf" = "2" ] || _is_leaf=0
766 done <<-EOT
767 $(cat_deps "$1")
771 [ -n "$recurse_preorder" -o -z "${2:-$with_top_level}" ] || echo "0 $_is_tgish $_is_leaf $*"
772 return ${_is_leaf:-0}
775 # do_eval CMD
776 # helper for recurse_deps so that a return statement executed inside CMD
777 # does not return from recurse_deps. This shouldn't be necessary, but it
778 # seems that it actually is.
779 do_eval()
781 eval "$@"
784 # becomes read-only for caching purposes
785 # assigns new value to tg_read_only
786 # become_cacheable/undo_become_cacheable calls may be nested
787 become_cacheable()
789 _old_tg_read_only="$tg_read_only"
790 if [ -z "$tg_read_only" ]; then
791 ! [ -e "$tg_tmp_dir/cached" ] && ! [ -e "$tg_tmp_dir/tg~ref-dirs-created" ] ||
792 rm -rf "$tg_tmp_dir/cached" "$tg_tmp_dir/tg~ref-dirs-created"
793 tg_read_only=1
795 _my_ref_cache=
796 v_create_ref_cache _my_ref_cache
797 _my_ref_cache="${_my_ref_cache:+1}"
798 tg_read_only="undo${_my_ref_cache:-0}-$_old_tg_read_only"
801 # restores tg_read_only and ref_cache to state before become_cacheable call
802 # become_cacheable/undo_bocome_cacheable calls may be nested
803 undo_become_cacheable()
805 case "$tg_read_only" in
806 "undo"[01]"-"*)
807 _suffix="${tg_read_only#undo?-}"
808 [ "${tg_read_only%$_suffix}" = "undo0-" ] || remove_ref_cache
809 tg_read_only="$_suffix"
810 esac
813 # just call this, no undo, sets tg_read_only= and removes ref cache and cached results
814 become_non_cacheable()
816 remove_ref_cache
817 tg_read_only=
818 ! [ -e "$tg_tmp_dir/cached" ] && ! [ -e "$tg_tmp_dir/tg~ref-dirs-created" ] ||
819 rm -rf "$tg_tmp_dir/cached" "$tg_tmp_dir/tg~ref-dirs-created"
822 # call this to make sure Git will not complain about a missing user/email
823 # result is cached in TG_IDENT_CHECKED and a non-empty value suppresses the check
824 ensure_ident_available()
826 [ -z "$TG_IDENT_CHECKED" ] || return 0
827 git var GIT_AUTHOR_IDENT >/dev/null &&
828 git var GIT_COMMITTER_IDENT >/dev/null || exit
829 TG_IDENT_CHECKED=1
830 export TG_IDENT_CHECKED
831 return 0
834 # recurse_deps CMD NAME [BRANCHPATH...]
835 # Recursively eval CMD on all dependencies of NAME.
836 # Dependencies are visited in topological order.
837 # CMD can refer to the following variables:
839 # _ret starts as 0; CMD can change; will be final return result
840 # _dep bare branch name or "refs/remotes/..." for a remote base
841 # _name has $_dep in its .topdeps ("" for top and $with_top_level)
842 # _depchain 0+ space-separated branch names forming a path to top
843 # _dep_missing boolean "1" if no such $_dep ref; "" if ref present
844 # _dep_is_leaf boolean "1" if leaf; "" if not
845 # _dep_is_tgish boolean "1" if tgish; "" if not (which implies no remote)
846 # _dep_has_remote boolean "1" if $_dep has_remote; "" if not
847 # _dep_annihilated boolean "1" if $_dep annihilated; "" if not
849 # CMD may use a "return" statement without issue; its return value is ignored,
850 # but if CMD sets _ret to a negative value, e.g. "-0" or "-1" the enumeration
851 # will stop immediately and the value with the leading "-" stripped off will
852 # be the final result code
854 # CMD can refer to $_name for queried branch name,
855 # $_dep for dependency name,
856 # $_depchain for space-seperated branch backtrace,
857 # $_dep_missing boolean to check whether $_dep is present
858 # and the $_dep_is_tgish and $_dep_annihilated booleans.
859 # If recurse_preorder is NOT set then the $_dep_is_leaf boolean is also valid.
860 # It can modify $_ret to affect the return value
861 # of the whole function.
862 # If recurse_deps() hits missing dependencies, it will append
863 # them to space-separated $missing_deps list and skip them
864 # after calling CMD with _dep_missing set.
865 # remote dependencies are processed if no_remotes is unset.
866 # any branch names in the space-separated recurse_deps_exclude variable
867 # are skipped (along with their dependencies)
868 recurse_deps()
870 _cmd="$1"; shift
872 become_cacheable
873 _depsfile="$(get_temp tg-depsfile)"
874 recurse_deps_internal "$@" >>"$_depsfile" || :
875 undo_become_cacheable
877 _ret=0
878 while read _ismissing _istgish _isleaf _dep _name _deppath; do
879 _depchain="$_name${_deppath:+ $_deppath}"
880 _dep_is_tgish=
881 [ "$_istgish" = "0" ] || _dep_is_tgish=1
882 _dep_has_remote=
883 [ "$_istgish" != "2" ] || _dep_has_remote=1
884 _dep_missing=
885 if [ "$_ismissing" != "0" ]; then
886 _dep_missing=1
887 case " $missing_deps " in *" $_dep "*);;*)
888 missing_deps="${missing_deps:+$missing_deps }$_dep"
889 esac
891 _dep_annihilated=
892 _dep_is_leaf=
893 if [ "$_isleaf" = "1" ]; then
894 _dep_is_leaf=1
895 elif [ "$_isleaf" = "2" ]; then
896 _dep_annihilated=1
898 do_eval "$_cmd" || :
899 if [ "${_ret#-}" != "$_ret" ]; then
900 _ret="${_ret#-}"
901 break
903 done <"$_depsfile"
904 rm -f "$_depsfile"
905 return ${_ret:-0}
908 find_leaves_internal()
910 if [ -n "$_dep_is_leaf" ] && [ -z "$_dep_annihilated" ] && [ -z "$_dep_missing" ]; then
911 if [ -n "$_dep_is_tgish" ]; then
912 fulldep="refs/$topbases/$_dep"
913 else
914 fulldep="refs/heads/$_dep"
916 case " $seen_leaf_refs " in *" $fulldep "*);;*)
917 seen_leaf_refs="${seen_leaf_refs:+$seen_leaf_refs }$fulldep"
918 if fullrev="$(ref_exists_rev "$fulldep")"; then
919 case " $seen_leaf_revs " in *" $fullrev "*);;*)
920 seen_leaf_revs="${seen_leaf_revs:+$seen_leaf_revs }$fullrev"
921 # See if Git knows it by another name
922 if tagname="$(git describe --exact-match "$fullrev" 2>/dev/null)" && [ -n "$tagname" ]; then
923 echo "refs/tags/$tagname"
924 else
925 echo "$fulldep"
927 esac
929 esac
933 # find_leaves NAME
934 # output (one per line) the unique leaves of NAME
935 # a leaf is either
936 # 1) a non-tgish dependency
937 # 2) the base of a tgish dependency with no non-annihilated dependencies
938 # duplicates are suppressed (by commit rev) and remotes are always ignored
939 # if a leaf has an exact tag match that will be output
940 # note that recurse_deps_exclude IS honored for this operation
941 find_leaves()
943 no_remotes=1
944 with_top_level=1
945 recurse_preorder=
946 seen_leaf_refs=
947 seen_leaf_revs=
948 recurse_deps find_leaves_internal "$1"
949 with_top_level=
952 # branch_needs_update
953 # This is a helper function for determining whether given branch
954 # is up-to-date wrt. its dependencies. It expects input as if it
955 # is called as a recurse_deps() helper.
956 # In case the branch does need update, it will echo it together
957 # with the branch backtrace on the output (see needs_update()
958 # description for details) and set $_ret to non-zero.
959 branch_needs_update()
961 if [ -n "$_dep_missing" ]; then
962 echo "! $_dep $_depchain"
963 return 0
966 if [ -n "$_dep_is_tgish" ]; then
967 branch_annihilated "$_dep" && return 0
969 if has_remote "$_dep"; then
970 branch_contains "refs/heads/$_dep" "refs/remotes/$base_remote/$_dep" ||
971 echo "refs/remotes/$base_remote/$_dep $_dep $_depchain"
973 # We want to sync with our base first and should output this before
974 # the remote branch, but the order does not actually matter to tg-update
975 # as it just recurses regardless, but it does matter for tg-info (which
976 # treats out-of-date bases as though they were already merged in) so
977 # we output the remote before the base.
978 branch_contains "refs/heads/$_dep" "refs/$topbases/$_dep" || {
979 echo ": $_dep $_depchain"
980 _ret=1
981 return
985 if [ -n "$_name" ]; then
986 case "$_dep" in refs/*) _fulldep="$_dep";; *) _fulldep="refs/heads/$_dep";; esac
987 if ! branch_contains "refs/$topbases/$_name" "$_fulldep"; then
988 # Some new commits in _dep
989 echo "$_dep $_depchain"
990 _ret=1
995 # needs_update NAME
996 # This function is recursive; it outputs reverse path from NAME
997 # to the branch (e.g. B_DIRTY B1 B2 NAME), one path per line,
998 # inner paths first. Innermost name can be refs/remotes/<remote>/<name>
999 # if the head is not in sync with the <remote> branch <name>, ':' if
1000 # the head is not in sync with the base (in this order of priority)
1001 # or '!' if dependency is missing. Note that the remote branch, base
1002 # order is reversed from the order they will actually be updated in
1003 # order to accomodate tg info which treats out-of-date items that are
1004 # only in the base as already being in the head for status purposes.
1005 # It will also return non-zero status if NAME needs update.
1006 # If needs_update() hits missing dependencies, it will append
1007 # them to space-separated $missing_deps list and skip them.
1008 needs_update()
1010 recurse_deps branch_needs_update "$1"
1013 # branch_empty NAME [-i | -w]
1014 branch_empty()
1016 if [ -z "$2" ]; then
1017 _rev="$(ref_exists_rev "refs/heads/$1")" || return 0
1018 _result=
1019 _result_rev=
1020 { read -r _result _result_rev <"$tg_cache_dir/$1/.mt"; } 2>/dev/null || :
1021 [ -z "$_result" -o "$_result_rev" != "$_rev" ] || return $_result
1022 _result=0
1023 [ "$(pretty_tree -t "$1" -b)" = "$(pretty_tree -t "$1" $2)" ] || _result=$?
1024 [ -d "$tg_cache_dir/$1" ] || mkdir -p "$tg_cache_dir/$1" 2>/dev/null
1025 [ ! -d "$tg_cache_dir/$1" ] || echo $_result $_rev >"$tg_cache_dir/$1/.mt"
1026 return $_result
1027 else
1028 [ "$(pretty_tree -t "$1" -b)" = "$(pretty_tree -t "$1" $2)" ]
1032 # list_deps [-i | -w] [BRANCH]
1033 # -i/-w apply only to HEAD
1034 list_deps()
1036 head_from=
1037 [ "$1" != "-i" -a "$1" != "-w" ] || { head_from="$1"; shift; }
1038 head="$(git symbolic-ref -q HEAD)" ||
1039 head="..detached.."
1041 git for-each-ref --format='%(objectname) %(refname)' "refs/$topbases${1:+/$1}" |
1042 while read rev ref; do
1043 name="${ref#refs/$topbases/}"
1044 if branch_annihilated "$name" "" "$rev"; then
1045 continue
1048 from=$head_from
1049 [ "refs/heads/$name" = "$head" ] ||
1050 from=
1051 cat_file "refs/heads/$name:.topdeps" $from | while read dep; do
1052 dep_is_tgish=true
1053 ref_exists "refs/$topbases/$dep" ||
1054 dep_is_tgish=false
1055 if ! "$dep_is_tgish" || ! branch_annihilated $dep; then
1056 echo "$name $dep"
1058 done
1059 done
1062 # checkout_symref_full [-f] FULLREF [SEED]
1063 # Just like git checkout $iowopt -b FULLREF [SEED] except that FULLREF MUST start with
1064 # refs/ and HEAD is ALWAYS set to a symref to it and [SEED] (default is FULLREF)
1065 # MUST be a committish which if present will be used instead of current FULLREF
1066 # (and FULLREF will be updated to it as well in that case)
1067 # With -f it's like git checkout $iowopt -f -b FULLREF (uses read-tree --reset instead of -m)
1068 # As an extension, FULLREF may also be a full hash to create a detached HEAD instead
1069 checkout_symref_full()
1071 _mode=-m
1072 if [ "$1" = "-f" ]; then
1073 mode="--reset"
1074 shift
1076 _ishash=
1077 case "$1" in
1078 refs/?*)
1080 $octet20)
1081 _ishash=1
1082 [ -z "$2" ] || [ "$1" = "$2" ] ||
1083 die "programmer error: invalid checkout_symref_full \"$1\" \"$2\""
1084 set -- HEAD "$1"
1087 die "programmer error: invalid checkout_symref_full \"$1\""
1089 esac
1090 _seedrev="$(git rev-parse --quiet --verify "${2:-$1}^0" --)" ||
1091 die "invalid committish: \"${2:-$1}\""
1092 # We have to do all the hard work ourselves :/
1093 # This is like git checkout -b "$1" "$2"
1094 # (or just git checkout "$1"),
1095 # but never creates a detached HEAD (unless $1 is a hash)
1096 git read-tree -u $_mode HEAD "$_seedrev" &&
1098 [ -z "$2" ] && [ "$(git cat-file -t "$1")" = "commit" ] ||
1099 git update-ref "$1" "$_seedrev"
1100 } && {
1101 [ -n "$_ishash" ] || git symbolic-ref HEAD "$1"
1105 # switch_to_base NAME [SEED]
1106 switch_to_base()
1108 checkout_symref_full "refs/$topbases/$1" "$2"
1111 # run editor with arguments
1112 # the editor setting will be cached in $tg_editor (which is eval'd)
1113 # result non-zero if editor fails or GIT_EDITOR cannot be determined
1114 run_editor()
1116 tg_editor="$GIT_EDITOR"
1117 [ -n "$tg_editor" ] || tg_editor="$(git var GIT_EDITOR)" || return $?
1118 eval "$tg_editor" '"$@"'
1121 # Show the help messages.
1122 do_help()
1124 _www=
1125 if [ "$1" = "-w" ]; then
1126 _www=1
1127 shift
1129 if [ -z "$1" ] ; then
1130 # This is currently invoked in all kinds of circumstances,
1131 # including when the user made a usage error. Should we end up
1132 # providing more than a short help message, then we should
1133 # differentiate.
1134 # Petr's comment: http://marc.info/?l=git&m=122718711327376&w=2
1136 ## Build available commands list for help output
1138 cmds=
1139 sep=
1140 for cmd in "$TG_INST_CMDDIR"/tg-[!-]*; do
1141 ! [ -r "$cmd" ] && continue
1142 # strip directory part and "tg-" prefix
1143 cmd="${cmd##*/}"
1144 cmd="${cmd#tg-}"
1145 [ "$cmd" != "migrate-bases" ] || continue
1146 [ "$cmd" != "summary" ] || cmd="st[atus]|$cmd"
1147 cmds="$cmds$sep$cmd"
1148 sep="|"
1149 done
1151 echo "TopGit version $TG_VERSION - A different patch queue manager"
1152 echo "Usage: $tgname [-C <dir>] [-r <remote> | -u] [-c <name>=<val>] ($cmds) ..."
1153 echo " Or: $tgname help [-w] [<command>]"
1154 echo "Use \"$tgdisplaydir$tgname help tg\" for overview of TopGit"
1155 elif [ -r "$TG_INST_CMDDIR"/tg-$1 -o -r "$TG_INST_SHAREDIR/tg-$1.txt" ] ; then
1156 if [ -n "$_www" ]; then
1157 nohtml=
1158 if ! [ -r "$TG_INST_SHAREDIR/topgit.html" ]; then
1159 echo "${0##*/}: missing html help file:" \
1160 "$TG_INST_SHAREDIR/topgit.html" 1>&2
1161 nohtml=1
1163 if ! [ -r "$TG_INST_SHAREDIR/tg-$1.html" ]; then
1164 echo "${0##*/}: missing html help file:" \
1165 "$TG_INST_SHAREDIR/tg-$1.html" 1>&2
1166 nohtml=1
1168 if [ -n "$nohtml" ]; then
1169 echo "${0##*/}: use" \
1170 "\"${0##*/} help $1\" instead" 1>&2
1171 exit 1
1173 git web--browse -c help.browser "$TG_INST_SHAREDIR/tg-$1.html"
1174 exit
1176 output()
1178 if [ -r "$TG_INST_CMDDIR"/tg-$1 ] ; then
1179 "$TG_INST_CMDDIR"/tg-$1 -h 2>&1 || :
1180 echo
1181 elif [ "$1" = "help" ]; then
1182 echo "Usage: ${tgname:-tg} help [-w] [<command>]"
1183 echo
1184 elif [ "$1" = "status" ] || [ "$1" = "st" ]; then
1185 echo "Usage: ${tgname:-tg} @tgsthelpusage@"
1186 echo
1188 if [ -r "$TG_INST_SHAREDIR/tg-$1.txt" ] ; then
1189 cat "$TG_INST_SHAREDIR/tg-$1.txt"
1192 page output "$1"
1193 else
1194 echo "${0##*/}: no help for $1" 1>&2
1195 do_help
1196 exit 1
1200 check_status()
1202 git_state=
1203 git_remove=
1204 if [ -e "$git_dir/MERGE_HEAD" ]; then
1205 git_state="merge"
1206 elif [ -e "$git_dir/rebase-apply/applying" ]; then
1207 git_state="am"
1208 git_remove="$git_dir/rebase-apply"
1209 elif [ -e "$git_dir/rebase-apply" ]; then
1210 git_state="rebase"
1211 git_remove="$git_dir/rebase-apply"
1212 elif [ -e "$git_dir/rebase-merge" ]; then
1213 git_state="rebase"
1214 git_remove="$git_dir/rebase-merge"
1215 elif [ -e "$git_dir/CHERRY_PICK_HEAD" ]; then
1216 git_state="cherry-pick"
1217 elif [ -e "$git_dir/BISECT_LOG" ]; then
1218 git_state="bisect"
1219 elif [ -e "$git_dir/REVERT_HEAD" ]; then
1220 git_state="revert"
1222 git_remove="${git_remove#./}"
1224 tg_state=
1225 tg_remove=
1226 if [ -e "$git_dir/tg-update" ]; then
1227 tg_state="update"
1228 tg_remove="$git_dir/tg-update"
1230 tg_remove="${tg_remove#./}"
1233 # Show status information
1234 do_status()
1236 do_status_result=0
1237 do_status_verbose=
1238 do_status_help=
1239 abbrev=refs
1240 pfx=
1241 while [ $# -gt 0 ] && case "$1" in
1242 --help|-h)
1243 do_status_help=1
1244 break;;
1245 -vv)
1246 # kludge in this common bundling option
1247 abbrev=
1248 do_status_verbose=1
1249 pfx="## "
1251 --verbose|-v)
1252 [ -z "$do_status_verbose" ] || abbrev=
1253 do_status_verbose=1
1254 pfx="## "
1256 --exit-code)
1257 do_status_result=2
1260 die "unknown status argument: $1"
1262 esac; do shift; done
1263 if [ -n "$do_status_help" ]; then
1264 echo "Usage: ${tgname:-tg} @tgsthelpusage@"
1265 return
1267 check_status
1268 symref="$(git symbolic-ref --quiet HEAD)" || :
1269 headrv="$(git rev-parse --quiet --verify ${abbrev:+--short} HEAD --)" || :
1270 if [ -n "$symref" ]; then
1271 uprefpart=
1272 if [ -n "$headrv" ]; then
1273 upref="$(git rev-parse --symbolic-full-name @{upstream} 2>/dev/null)" || :
1274 if [ -n "$upref" ]; then
1275 uprefpart=" ... ${upref#$abbrev/remotes/}"
1276 mbase="$(git merge-base HEAD "$upref")" || :
1277 ahead="$(git rev-list --count HEAD ${mbase:+--not $mbase})" || ahead=0
1278 behind="$(git rev-list --count "$upref" ${mbase:+--not $mbase})" || behind=0
1279 [ "$ahead$behind" = "00" ] || uprefpart="$uprefpart ["
1280 [ "$ahead" = "0" ] || uprefpart="${uprefpart}ahead $ahead"
1281 [ "$ahead" = "0" ] || [ "$behind" = "0" ] || uprefpart="$uprefpart, "
1282 [ "$behind" = "0" ] || uprefpart="${uprefpart}behind $behind"
1283 [ "$ahead$behind" = "00" ] || uprefpart="$uprefpart]"
1286 echol "${pfx}HEAD -> ${symref#$abbrev/heads/} [${headrv:-unborn}]$uprefpart"
1287 else
1288 echol "${pfx}HEAD -> ${headrv:-?}"
1290 if [ -n "$tg_state" ]; then
1291 extra=
1292 if [ "$tg_state" = "update" ]; then
1293 IFS= read -r uname <"$git_dir/tg-update/name" || :
1294 [ -z "$uname" ] ||
1295 extra="; currently updating branch '$uname'"
1297 echol "${pfx}tg $tg_state in progress$extra"
1298 if [ -s "$git_dir/tg-update/fullcmd" ] && [ -s "$git_dir/tg-update/names" ]; then
1299 printf "${pfx}You are currently updating as a result of:\n${pfx} "
1300 cat "$git_dir/tg-update/fullcmd"
1301 bcnt="$(( $(wc -w < "$git_dir/tg-update/names") ))"
1302 if [ $bcnt -gt 1 ]; then
1303 pcnt=0
1304 ! [ -s "$git_dir/tg-update/processed" ] ||
1305 pcnt="$(( $(wc -w < "$git_dir/tg-update/processed") ))"
1306 echo "${pfx}$pcnt of $bcnt branches updated so far"
1309 if [ "$tg_state" = "update" ]; then
1310 echol "${pfx}"' (use "tg update --continue" to continue)'
1311 echol "${pfx}"' (use "tg update --skip" to skip this branch and continue)'
1312 echol "${pfx}"' (use "tg update --stop" to stop and retain updates so far)'
1313 echol "${pfx}"' (use "tg update --abort" to restore pre-update state)'
1316 [ -z "$git_state" ] || echo "${pfx}git $git_state in progress"
1317 if [ "$git_state" = "merge" ]; then
1318 ucnt="$(( $(git ls-files --unmerged --full-name --abbrev :/ | wc -l) ))"
1319 if [ $ucnt -gt 0 ]; then
1320 echo "${pfx}"'fix conflicts and then "git commit" the result'
1321 else
1322 echo "${pfx}"'all conflicts fixed; run "git commit" to record result'
1325 if [ -z "$git_state" ]; then
1326 gsp="$(git status --porcelain 2>/dev/null)" || return 0 # bare repository
1327 gspcnt=0
1328 [ -z "$gsp" ] ||
1329 gspcnt="$(( $(printf '%s\n' "$gsp" | LC_ALL=C sed -n '/^??/!p' | wc -l) ))"
1330 untr=
1331 if [ "$gspcnt" -eq 0 ]; then
1332 [ -z "$gsp" ] || untr="; non-ignored, untracked files present"
1333 echo "${pfx}working directory is clean$untr"
1334 [ -n "$tg_state" ] || do_status_result=0
1335 else
1336 echo "${pfx}working directory is DIRTY"
1337 [ -z "$do_status_verbose" ] || git status --short --untracked-files=no
1342 ## Pager stuff
1344 # isatty FD
1345 isatty()
1347 test -t $1
1350 # pass "diff" to get pager.diff
1351 # if pager.$1 is a boolean false returns cat
1352 # if set to true or unset fails
1353 # otherwise succeeds and returns the value
1354 get_pager()
1356 if _x="$(git config --bool "pager.$1" 2>/dev/null)"; then
1357 [ "$_x" != "true" ] || return 1
1358 echo "cat"
1359 return 0
1361 if _x="$(git config "pager.$1" 2>/dev/null)"; then
1362 echol "$_x"
1363 return 0
1365 return 1
1368 # setup_pager
1369 # Set TG_PAGER to a valid executable
1370 # After calling, code to be paged should be surrounded with {...} | eval "$TG_PAGER"
1371 # See also the following "page" function for ease of use
1372 # emptypager will be set to 1 (otherwise empty) if TG_PAGER was set to "cat" to not be empty
1373 # Preference is (same as Git):
1374 # 1. GIT_PAGER
1375 # 2. pager.$USE_PAGER_TYPE (but only if USE_PAGER_TYPE is set and so is pager.$USE_PAGER_TYPE)
1376 # 3. core.pager (only if set)
1377 # 4. PAGER
1378 # 5. git var GIT_PAGER
1379 # 6. less
1380 setup_pager()
1382 isatty 1 || { emptypager=1; TG_PAGER=cat; return 0; }
1384 emptypager=
1385 if [ -z "$TG_PAGER_IN_USE" ]; then
1386 # TG_PAGER = GIT_PAGER | PAGER | less
1387 # NOTE: GIT_PAGER='' is significant
1388 if [ -n "${GIT_PAGER+set}" ]; then
1389 TG_PAGER="$GIT_PAGER"
1390 elif [ -n "$USE_PAGER_TYPE" ] && _dp="$(get_pager "$USE_PAGER_TYPE")"; then
1391 TG_PAGER="$_dp"
1392 elif _cp="$(git config core.pager 2>/dev/null)"; then
1393 TG_PAGER="$_cp"
1394 elif [ -n "${PAGER+set}" ]; then
1395 TG_PAGER="$PAGER"
1396 else
1397 _gp="$(git var GIT_PAGER 2>/dev/null)" || :
1398 [ "$_gp" != ":" ] || _gp=
1399 TG_PAGER="${_gp:-less}"
1401 if [ -z "$TG_PAGER" ]; then
1402 emptypager=1
1403 TG_PAGER=cat
1405 else
1406 emptypager=1
1407 TG_PAGER=cat
1410 # Set pager default environment variables
1411 # see pager.c:setup_pager
1412 if [ -z "${LESS+set}" ]; then
1413 LESS="-FRX"
1414 export LESS
1416 if [ -z "${LV+set}" ]; then
1417 LV="-c"
1418 export LV
1421 # this is needed so e.g. $(git diff) will still colorize it's output if
1422 # requested in ~/.gitconfig with color.diff=auto
1423 GIT_PAGER_IN_USE=1
1424 export GIT_PAGER_IN_USE
1426 # this is needed so we don't get nested pagers
1427 TG_PAGER_IN_USE=1
1428 export TG_PAGER_IN_USE
1431 # page eval_arg [arg ...]
1433 # Calls setup_pager then evals the first argument passing it all the rest
1434 # where the output is piped through eval "$TG_PAGER" unless emptypager is set
1435 # by setup_pager (in which case the output is left as-is).
1437 # To handle arbitrary paging duties, collect lines to be paged into a
1438 # function and then call page with the function name or perhaps func_name "$@".
1440 # If no arguments at all are passed in do nothing (return with success).
1441 page()
1443 [ $# -gt 0 ] || return 0
1444 setup_pager
1445 _evalarg="$1"; shift
1446 if [ -n "$emptypager" ]; then
1447 eval "$_evalarg" '"$@"'
1448 else
1449 eval "$_evalarg" '"$@"' | eval "$TG_PAGER"
1453 # get_temp NAME [-d]
1454 # creates a new temporary file (or directory with -d) in the global
1455 # temporary directory $tg_tmp_dir with pattern prefix NAME
1456 get_temp()
1458 mktemp $2 "$tg_tmp_dir/$1.XXXXXX"
1461 # automatically called by strftime
1462 # does nothing if already setup
1463 # may be called explicitly if the first call would otherwise be in a subshell
1464 # so that the setup is only done once before subshells start being spawned
1465 setup_strftime()
1467 [ -z "$strftime_is_setup" ] || return 0
1469 # date option to format raw epoch seconds values
1470 daterawopt=
1471 _testes='951807788'
1472 _testdt='2000-02-29 07:03:08 UTC'
1473 _testfm='%Y-%m-%d %H:%M:%S %Z'
1474 if [ "$(TZ=UTC date "-d@$_testes" "+$_testfm" 2>/dev/null)" = "$_testdt" ]; then
1475 daterawopt='-d@'
1476 elif [ "$(TZ=UTC date "-r$_testes" "+$_testfm" 2>/dev/null)" = "$_testdt" ]; then
1477 daterawopt='-r'
1479 strftime_is_setup=1
1482 # $1 => strftime format string to use
1483 # $2 => raw timestamp as seconds since epoch
1484 # $3 => optional time zone string (empty/absent for local time zone)
1485 strftime()
1487 setup_strftime
1488 if [ -n "$daterawopt" ]; then
1489 if [ -n "$3" ]; then
1490 TZ="$3" date "$daterawopt$2" "+$1"
1491 else
1492 date "$daterawopt$2" "+$1"
1494 else
1495 if [ -n "$3" ]; then
1496 TZ="$3" perl -MPOSIX=strftime -le 'print strftime($ARGV[0],localtime($ARGV[1]))' "$1" "$2"
1497 else
1498 perl -MPOSIX=strftime -le 'print strftime($ARGV[0],localtime($ARGV[1]))' "$1" "$2"
1503 setup_git_dirs()
1505 [ -n "$git_dir" ] || git_dir="$(git rev-parse --git-dir)"
1506 if [ -n "$git_dir" ] && [ -d "$git_dir" ]; then
1507 git_dir="$(cd "$git_dir" && pwd)"
1509 if [ -z "$git_common_dir" ]; then
1510 if vcmp "$git_version" '>=' "2.5"; then
1511 # rev-parse --git-common-dir is broken and may give
1512 # an incorrect result unless the current directory is
1513 # already set to the top level directory
1514 git_common_dir="$(cd "./$(git rev-parse --show-cdup)" && cd "$(git rev-parse --git-common-dir)" && pwd)"
1515 else
1516 git_common_dir="$git_dir"
1519 [ -n "$git_dir" ] && [ -n "$git_common_dir" ] &&
1520 [ -d "$git_dir" ] && [ -d "$git_common_dir" ] || die "Not a git repository"
1521 git_hooks_dir="$git_common_dir/hooks"
1522 if vcmp "$git_version" '>=' "2.9" && gchp="$(git config --path --get core.hooksPath 2>/dev/null)" && [ -n "$gchp" ]; then
1523 case "$gchp" in
1524 /[!/]*)
1525 git_hooks_dir="$gchp"
1528 [ -n "$1" ] || warn "ignoring non-absolute core.hooksPath: $gchp"
1530 esac
1531 unset gchp
1535 basic_setup()
1537 setup_git_dirs $1
1538 [ -n "$base_remote" ] || base_remote="$(git config topgit.remote 2>/dev/null)" || :
1539 tgsequester="$(git config --bool topgit.sequester 2>/dev/null)" || :
1540 tgnosequester=
1541 [ "$tgsequester" != "false" ] || tgnosequester=1
1542 unset tgsequester
1544 # catch errors if topbases is used without being set
1545 unset tg_topbases_set
1546 topbases="programmer*:error"
1547 topbasesrx="programmer*:error}"
1548 oldbases="$topbases"
1551 ## Initial setup
1552 initial_setup()
1554 # suppress the merge log editor feature since git 1.7.10
1556 GIT_MERGE_AUTOEDIT=no
1557 export GIT_MERGE_AUTOEDIT
1559 basic_setup $1
1560 iowopt=
1561 ! vcmp "$git_version" '>=' "2.5" || iowopt="--ignore-other-worktrees"
1562 auhopt=
1563 ! vcmp "$git_version" '>=' "2.9" || auhopt="--allow-unrelated-histories"
1564 root_dir="$(git rev-parse --show-cdup)"; root_dir="${root_dir:-.}"
1565 logrefupdates="$(git config --bool core.logallrefupdates 2>/dev/null)" || :
1566 [ "$logrefupdates" = "true" ] || logrefupdates=
1568 # make sure root_dir doesn't end with a trailing slash.
1570 root_dir="${root_dir%/}"
1572 # create global temporary directories, inside GIT_DIR
1574 tg_tmp_dir=
1575 trap 'rm -rf "$tg_tmp_dir"' EXIT
1576 trap 'exit 129' HUP
1577 trap 'exit 130' INT
1578 trap 'exit 131' QUIT
1579 trap 'exit 134' ABRT
1580 trap 'exit 143' TERM
1581 tg_tmp_dir="$(mktemp -d "$git_dir/tg-tmp.XXXXXX" 2>/dev/null)" || tg_tmp_dir=
1582 [ -n "$tg_tmp_dir" ] || tg_tmp_dir="$(mktemp -d "${TMPDIR:-/tmp}/tg-tmp.XXXXXX" 2>/dev/null)" || tg_tmp_dir=
1583 [ -n "$tg_tmp_dir" ] || [ -z "$TMPDIR" ] || tg_tmp_dir="$(mktemp -d "/tmp/tg-tmp.XXXXXX" 2>/dev/null)" || tg_tmp_dir=
1584 tg_ref_cache="$tg_tmp_dir/tg~ref-cache"
1585 [ -n "$tg_tmp_dir" ] && [ -w "$tg_tmp_dir" ] && { >"$tg_ref_cache"; } >/dev/null 2>&1 ||
1586 die "could not create a writable temporary directory"
1588 # make sure global cache directory exists inside GIT_DIR or $tg_tmp_dir
1590 user_id_no="$(id -u)" || :
1591 : "${user_id_no:=_99_}"
1592 tg_cache_dir="$git_common_dir/tg-cache"
1593 [ -d "$tg_cache_dir" ] || mkdir "$tg_cache_dir" >/dev/null 2>&1 || tg_cache_dir=
1594 [ -z "$tg_cache_dir" ] || tg_cache_dir="$tg_cache_dir/$user_id_no"
1595 [ -z "$tg_cache_dir" ] || [ -d "$tg_cache_dir" ] || mkdir "$tg_cache_dir" >/dev/null 2>&1 || tg_cache_dir=
1596 [ -z "$tg_cache_dir" ] || { >"$tg_cache_dir/.tgcache"; } >/dev/null 2>&1 || tg_cache_dir=
1597 if [ -z "$tg_cache_dir" ]; then
1598 tg_cache_dir="$tg_tmp_dir/tg-cache"
1599 [ -d "$tg_cache_dir" ] || mkdir "$tg_cache_dir" >/dev/null 2>&1 || tg_cache_dir=
1600 [ -z "$tg_cache_dir" ] || { >"$tg_cache_dir/.tgcache"; } >/dev/null 2>&1 || tg_cache_dir=
1602 [ -n "$tg_cache_dir" ] ||
1603 die "could not create a writable tg-cache directory (even a temporary one)"
1605 # GIT_ALTERNATE_OBJECT_DIRECTORIES can contain double-quoted entries
1606 # since Git v2.11.1; however, it's only necessary for : (or perhaps ;)
1607 # so we avoid it if possible and require v2.11.1 to do it at all
1608 # otherwise just don't make an alternates temporary store in that case;
1609 # it's okay to not have one; everything will still work; the nicety of
1610 # making the temporary tree objects vanish when tg exits just won't
1611 # happen in that case but nothing will break also be sure to reuse
1612 # the parent's if we've been recursively invoked and it's for the
1613 # same repository we were invoked on
1615 tg_use_alt_odb=1
1616 _odbdir="${GIT_OBJECT_DIRECTORY:-$git_common_dir/objects}"
1617 [ -n "$_odbdir" ] && [ -d "$_odbdir" ] || tg_use_alt_odb=
1618 _fulltmpdir=
1619 [ -z "$tg_use_alt_odb" ] || _fulltmpdir="$(cd "$tg_tmp_dir" && pwd -P)"
1620 case "$_fulltmpdir" in *[";:"]*) vcmp "$git_version" '>=' "2.11.1" || tg_use_alt_odb=; esac
1621 _fullodbdir=
1622 [ -z "$tg_use_alt_odb" ] || _fullodbdir="$(cd "$_odbdir" && pwd -P)"
1623 if [ -n "$tg_use_alt_odb" ] && [ -n "$TG_OBJECT_DIRECTORY" ] && [ -d "$TG_OBJECT_DIRECTORY/info" ] &&
1624 [ -f "$TG_OBJECT_DIRECTORY/info/alternates" ] && [ -r "$TG_OBJECT_DIRECTORY/info/alternates" ]; then
1625 if IFS= read -r _otherodbdir <"$TG_OBJECT_DIRECTORY/info/alternates" &&
1626 [ -n "$_otherodbdir" ] && [ "$_otherodbdir" = "$_fullodbdir" ]; then
1627 tg_use_alt_odb=2
1630 if [ "$tg_use_alt_odb" = "1" ]; then
1631 # create an alternate objects database to keep the ephemeral objects in
1632 mkdir -p "$tg_tmp_dir/objects/info"
1633 echol "$_fullodbdir" >"$tg_tmp_dir/objects/info/alternates"
1634 TG_OBJECT_DIRECTORY="$_fulltmpdir/objects"
1635 case "$TG_OBJECT_DIRECTORY" in
1636 *[";:"]*)
1637 # surround in "..." and backslash-escape internal '"' and '\\'
1638 _altodbdq="\"$(printf '%s\n' "$TG_OBJECT_DIRECTORY" |
1639 LC_ALL=C sed 's/\([""\\]\)/\\\1/g')\""
1642 _altodbdq="$TG_OBJECT_DIRECTORY"
1644 esac
1645 TG_PRESERVED_ALTERNATES="$GIT_ALTERNATE_OBJECT_DIRECTORIES"
1646 if [ -n "$GIT_ALTERNATE_OBJECT_DIRECTORIES" ]; then
1647 GIT_ALTERNATE_OBJECT_DIRECTORIES="$_altodbdq:$GIT_ALTERNATE_OBJECT_DIRECTORIES"
1648 else
1649 GIT_ALTERNATE_OBJECT_DIRECTORIES="$_altodbdq"
1651 export TG_PRESERVED_ALTERNATES TG_OBJECT_DIRECTORY GIT_ALTERNATE_OBJECT_DIRECTORIES GIT_OBJECT_DIRECTORY
1655 set_topbases()
1657 # refer to "top-bases" in a refname with $topbases
1659 [ -z "$tg_topbases_set" ] || return 0
1661 topbases_implicit_default=1
1662 # See if topgit.top-bases is set to heads or refs
1663 tgtb="$(git config "topgit.top-bases" 2>/dev/null)" || :
1664 if [ -n "$tgtb" ] && [ "$tgtb" != "heads" ] && [ "$tgtb" != "refs" ]; then
1665 if [ -n "$1" ]; then
1666 # never die on the hook script
1667 unset tgtb
1668 else
1669 die "invalid \"topgit.top-bases\" setting (must be \"heads\" or \"refs\")"
1672 if [ -n "$tgtb" ]; then
1673 case "$tgtb" in
1674 heads)
1675 topbases="heads/{top-bases}"
1676 topbasesrx="heads/[{]top-bases[}]"
1677 oldbases="top-bases";;
1678 refs)
1679 topbases="top-bases"
1680 topbasesrx="top-bases"
1681 oldbases="heads/{top-bases}";;
1682 esac
1683 # MUST NOT be exported
1684 unset tgtb tg_topbases_set topbases_implicit_default
1685 tg_topbases_set=1
1686 return 0
1688 unset tgtb
1690 # check heads and top-bases and see what state the current
1691 # repository is in. remotes are ignored.
1693 hblist=" "
1694 topbases=
1695 both=
1696 newtb="heads/{top-bases}"
1697 while read -r rn && [ -n "$rn" ]; do case "$rn" in
1698 "refs/heads/{top-bases}"/*)
1699 case "$hblist" in *" ${rn#refs/$newtb/} "*)
1700 if [ "$topbases" != "heads/{top-bases}" ] && [ -n "$topbases" ]; then
1701 both=1
1702 break;
1703 else
1704 topbases="heads/{top-bases}"
1705 topbasesrx="heads/[{]top-bases[}]"
1706 oldbases="top-bases"
1708 esac;;
1709 "refs/top-bases"/*)
1710 case "$hblist" in *" ${rn#refs/top-bases/} "*)
1711 if [ "$topbases" != "top-bases" ] && [ -n "$topbases" ]; then
1712 both=1
1713 break;
1714 else
1715 topbases="top-bases"
1716 topbasesrx="top-bases"
1717 oldbases="heads/{top-bases}"
1719 esac;;
1720 "refs/heads"/*)
1721 hblist="$hblist${rn#refs/heads/} ";;
1722 esac; done <<-EOT
1723 $(git for-each-ref --format='%(refname)' "refs/heads" "refs/top-bases" 2>/dev/null)
1725 if [ -n "$both" ]; then
1726 if [ -n "$1" ]; then
1727 # hook script always prefers newer without complaint
1728 topbases="heads/{top-bases}"
1729 topbasesrx="heads/[{]top-bases[}]"
1730 oldbases="top-bases"
1731 else
1732 # Complain and die
1733 err "repository contains existing TopGit branches"
1734 err "but some use refs/top-bases/... for the base"
1735 err "and some use refs/heads/{top-bases}/... for the base"
1736 err "with the latter being the new, preferred location"
1737 err "set \"topgit.top-bases\" to either \"heads\" to use"
1738 err "the new heads/{top-bases} location or \"refs\" to use"
1739 err "the old top-bases location."
1740 err "(the tg migrate-bases command can also resolve this issue)"
1741 die "schizophrenic repository requires topgit.top-bases setting"
1743 elif [ -n "$topbases" ]; then
1744 unset topbases_implicit_default
1747 [ -n "$topbases" ] || {
1748 # default is still top-bases for now
1749 topbases="top-bases"
1750 topbasesrx="top-bases"
1751 oldbases="heads/{top-bases}"
1753 # MUST NOT be exported
1754 unset hblist both newtb rn tg_topases_set
1755 tg_topbases_set=1
1756 return 0
1759 # init_reflog "ref"
1760 # if "$logrefupdates" is set and ref is not under refs/heads/ then force
1761 # an empty log file to exist so that ref changes will be logged
1762 # "$1" must be a fully-qualified refname (i.e. start with "refs/")
1763 # However, if "$1" is "refs/tgstash" then always make the reflog
1764 # The only ref not under refs/ that Git will write a reflog for is HEAD;
1765 # no matter what, it will NOT update a reflog for any other bare refs so
1766 # just quietly succeed when passed TG_STASH without doing anything.
1767 init_reflog()
1769 [ -n "$1" ] && [ "$1" != "TG_STASH" ] || return 0
1770 [ -n "$logrefupdates" ] || [ "$1" = "refs/tgstash" ] || return 0
1771 case "$1" in refs/heads/*|HEAD) return 0;; refs/*[!/]);; *) return 1; esac
1772 mkdir -p "$git_common_dir/logs/${1%/*}" 2>/dev/null || :
1773 { >>"$git_common_dir/logs/$1" || :; } 2>/dev/null
1776 # store the "realpath" for "$2" in "$1" except the leaf is not resolved if it's
1777 # a symbolic link. The directory part must exist, but the basename need not.
1778 v_get_abs_path()
1780 [ -n "$1" ] && [ -n "$2" ] || return 1
1781 set -- "$1" "$2" "${2%/}"
1782 case "$3" in
1783 */*) set -- "$1" "$2" "${3%/*}";;
1784 * ) set -- "$1" "$2" ".";;
1785 esac
1786 [ -d "$3" ] || return 1
1787 eval "$1="'"$(cd "$3" && pwd -P)/${2##*/}"'
1790 ## Startup
1792 : "${TG_INST_CMDDIR:=@cmddir@}"
1793 : "${TG_INST_SHAREDIR:=@sharedir@}"
1794 : "${TG_INST_HOOKSDIR:=@hooksdir@}"
1796 [ -d "$TG_INST_CMDDIR" ] ||
1797 die "No command directory: '$TG_INST_CMDDIR'"
1799 if [ -n "$tg__include" ]; then
1801 # We were sourced from another script for our utility functions;
1802 # this is set by hooks. Skip the rest of the file. A simple return doesn't
1803 # work as expected in every shell. See http://bugs.debian.org/516188
1805 # ensure setup happens
1807 initial_setup 1
1808 set_topbases 1
1810 else
1812 set -e
1814 tg="$0"
1815 tgdir="${tg%/}"
1816 case "$tgdir" in */*);;*) tgdir="./$tgdir"; esac
1817 tgdir="${tgdir%/*}/"
1818 tgname="${tg##*/}"
1819 [ "$0" != "$tgname" ] || tgdir=""
1821 # If tg contains a '/' but does not start with one then replace it with an absolute path
1823 case "$0" in /*) ;; */*)
1824 tgdir="$(cd "${0%/*}" && pwd -P)/"
1825 tg="$tgdir$tgname"
1826 esac
1828 # If the tg in the PATH is the same as "$tg" just display the basename
1829 # tgdisplay will include any explicit -C <dir> option whereas tg will not
1831 tgdisplaydir="$tgdir"
1832 tgdisplay="$tg"
1834 v_get_abs_path _tgabs "$tg" &&
1835 v_get_abs_path _tgnameabs "$(cmd_path "$tgname")" &&
1836 [ "$_tgabs" = "$_tgnameabs" ]
1837 then
1838 tgdisplaydir=""
1839 tgdisplay="$tgname"
1841 unset _tgabs _tgnameabs
1843 explicit_remote=
1844 explicit_dir=
1845 gitcdopt=
1846 noremote=
1848 cmd=
1849 while :; do case "$1" in
1851 help|--help|-h)
1852 cmd=help
1853 shift
1854 break;;
1856 status|--status)
1857 cmd=status
1858 shift
1859 break;;
1861 --hooks-path)
1862 cmd=hooks-path
1863 shift
1864 break;;
1866 --top-bases)
1867 cmd=top-bases
1868 shift
1869 break;;
1872 shift
1873 if [ -z "$1" ]; then
1874 echo "Option -r requires an argument." >&2
1875 do_help
1876 exit 1
1878 unset noremote
1879 base_remote="$1"
1880 explicit_remote="$base_remote"
1881 tg="$tgdir$tgname -r $explicit_remote"
1882 tgdisplay="$tgdisplaydir$tgname"
1883 [ -z "$explicit_dir" ] || tgdisplay="$tgdisplay -C \"$explicit_dir\""
1884 tgdisplay="$tgdisplay -r $explicit_remote"
1885 shift;;
1888 unset base_remote explicit_remote
1889 noremote=1
1890 tg="$tgdir$tgname -u"
1891 tgdisplay="$tgdisplaydir$tgname"
1892 [ -z "$explicit_dir" ] || tgdisplay="$tgdisplay -C \"$explicit_dir\""
1893 tgdisplay="$tgdisplay -u"
1894 shift;;
1897 shift
1898 if [ -z "$1" ]; then
1899 echo "Option -C requires an argument." >&2
1900 do_help
1901 exit 1
1903 cd "$1"
1904 unset GIT_DIR GIT_COMMON_DIR
1905 explicit_dir="$1"
1906 gitcdopt=" -C \"$explicit_dir\""
1907 tg="$tgdir$tgname"
1908 tgdisplay="$tgdisplaydir$tgname -C \"$explicit_dir\""
1909 [ -z "$explicit_remote" ] || tg="$tg -r $explicit_remote"
1910 [ -z "$explicit_remote" ] || tgdisplay="$tgdisplay -r $explicit_remote"
1911 [ -z "$noremote" ] || tg="$tg -u"
1912 [ -z "$noremote" ] || tg="$tgdisplay -u"
1913 shift;;
1916 shift
1917 if [ -z "$1" ]; then
1918 echo "Option -c requires an argument." >&2
1919 do_help
1920 exit 1
1922 param="'$(printf '%s\n' "$1" | sed "s/[']/'\\\\''/g")'"
1923 GIT_CONFIG_PARAMETERS="${GIT_CONFIG_PARAMETERS:+$GIT_CONFIG_PARAMETERS }$param"
1924 export GIT_CONFIG_PARAMETERS
1925 shift;;
1928 shift
1929 break;;
1932 echo "Invalid option $1 (subcommand options must appear AFTER the subcommand)." >&2
1933 do_help
1934 exit 1;;
1937 break;;
1939 esac; done
1941 [ -n "$cmd" -o $# -lt 1 ] || { cmd="$1"; shift; }
1943 ## Dispatch
1945 [ -n "$cmd" ] || { do_help; exit 1; }
1947 case "$cmd" in
1949 help)
1950 do_help "$@"
1951 exit 0;;
1953 status|st)
1954 unset base_remote
1955 basic_setup
1956 set_topbases
1957 do_status "$@"
1958 exit ${do_status_result:-0};;
1960 hooks-path)
1961 # Internal command
1962 echol "$TG_INST_HOOKSDIR";;
1964 top-bases)
1965 # Maintenance command
1966 ! git rev-parse --git-dir >/dev/null 2>&1 || setup_git_dirs
1967 set_topbases
1968 echol "refs/$topbases";;
1971 isutil=
1972 case "$cmd" in index-merge-one-file)
1973 isutil="-"
1974 esac
1975 [ -r "$TG_INST_CMDDIR"/tg-$isutil$cmd ] || {
1976 looplevel="$TG_ALIAS_DEPTH"
1977 [ "${looplevel#[1-9]}" != "$looplevel" ] &&
1978 [ "${looplevel%%[!0-9]*}" = "$looplevel" ] ||
1979 looplevel=0
1980 tgalias="$(git config "topgit.alias.$cmd" 2>/dev/null)" || :
1981 [ -n "$tgalias" ] || {
1982 echo "Unknown subcommand: $cmd" >&2
1983 do_help
1984 exit 1
1986 looplevel=$(( $looplevel + 1 ))
1987 [ $looplevel -le 10 ] || die "topgit.alias nesting level 10 exceeded"
1988 TG_ALIAS_DEPTH="$looplevel"
1989 export TG_ALIAS_DEPTH
1990 if [ "!${tgalias#?}" = "$tgalias" ]; then
1991 unset GIT_PREFIX
1992 if pfx="$(git rev-parse --show-prefix 2>/dev/null)"; then
1993 GIT_PREFIX="$pfx"
1994 export GIT_PREFIX
1996 cd "./$(git rev-parse --show-cdup 2>/dev/null)"
1997 exec @SHELL_PATH@ -c "${tgalias#?} \"\$@\"" @SHELL_PATH@ "$@"
1998 else
1999 eval 'exec "$tgbin"' "$tgalias" '"$@"'
2001 die "alias execution failed for: $tgalias"
2003 unset TG_ALIAS_DEPTH
2005 showing_help=
2006 if [ "$*" = "-h" ] || [ "$*" = "--help" ]; then
2007 showing_help=1
2010 [ -n "$showing_help" ] || initial_setup
2011 [ -z "$noremote" ] || unset base_remote
2013 nomergesetup="$showing_help"
2014 case "$cmd" in base|contains|info|log|rebase|revert|summary|tag)
2015 # avoid merge setup where not necessary
2017 nomergesetup=1
2018 esac
2020 if [ -z "$nomergesetup" ]; then
2021 # make sure merging the .top* files will always behave sanely
2023 setup_ours
2024 setup_hook "pre-commit"
2027 # everything but rebase needs topbases set
2028 carefully="$showing_help"
2029 [ "$cmd" != "migrate-bases" ] || carefully=1
2030 [ "$cmd" = "rebase" ] || set_topbases $carefully
2032 _use_ref_cache=
2033 tg_read_only=1
2034 case "$cmd$showing_help" in
2035 contains|export|info|summary|tag)
2036 _use_ref_cache=1;;
2037 annihilate|create|delete|depend|import|update)
2038 tg_use_alt_odb=
2039 tg_read_only=;;
2040 esac
2041 [ -z "$_use_ref_cache" ] || v_create_ref_cache
2043 fullcmd="${tgname:-tg} $cmd $*"
2044 . "$TG_INST_CMDDIR"/tg-$isutil$cmd;;
2045 esac