2 # TopGit - A different patch queue manager
3 # (C) Petr Baudis <pasky@suse.cz> 2008
4 # (C) Kyle J. McKay <mackyle@gmail.com> 2014,2015
9 # Update if you add any code that requires a newer version of git
10 GIT_MINIMUM_VERSION
=1.7.7.2
12 ## Auxiliary functions
16 echo "${TG_RECURSIVE}${tgname:-tg}: $*"
33 echo "$3" |
tr "${separator}" '\n' |
(for l
in $
(echo "$2"|
tr "${separator}" ' '); do
35 [ $l -ge $r ] ||
return 1
36 [ $l -gt $r ] && return 0
41 git_ver
="$(git version | sed -e 's/^[^0-9][^0-9]*//')"
42 compare_versions .
"${git_ver%%[!0-9.]*}" "${GIT_MINIMUM_VERSION}" \
43 || die
"git version >= ${GIT_MINIMUM_VERSION} required"
46 case "$1" in version|
--version|
-V)
47 echo "TopGit version $TG_VERSION"
52 [ "$1" = "precheck" ] && exit 0
54 # cat_file TOPIC:PATH [FROM]
55 # cat the file PATH from branch TOPIC when FROM is empty.
56 # FROM can be -i or -w, than the file will be from the index or worktree,
57 # respectively. The caller should than ensure that HEAD is TOPIC, to make sense.
63 cat "$root_dir/${path#*:}"
66 # ':file' means cat from index
67 git cat-file blob
":${path#*:}"
70 git cat-file blob
"$path"
73 die
"Wrong argument to cat_file: '$2'"
78 # get tree for the committed topic
84 # get tree for the base
87 echo "refs/top-bases/$1"
90 # get tree for the index
96 # get tree for the worktree
99 i_tree
=$
(git write-tree
)
101 # the file for --index-output needs to sit next to the
104 : ${GIT_INDEX_FILE:="$git_dir/index"}
105 TMP_INDEX
="$(mktemp "${GIT_INDEX_FILE}-tg.XXXXXX
")"
106 git read-tree
-m $i_tree --index-output="$TMP_INDEX" &&
107 GIT_INDEX_FILE
="$TMP_INDEX" &&
108 export GIT_INDEX_FILE
&&
109 git
diff --name-only -z HEAD |
110 git update-index
-z --add --remove --stdin &&
116 # strip_ref "$(git symbolic-ref HEAD)"
117 # Output will have a leading refs/heads/ or refs/top-bases/ stripped if present
122 echo "${1#refs/heads/}"
125 echo "${1#refs/top-bases/}"
132 # pretty_tree NAME [-b | -i | -w]
133 # Output tree ID of a cleaned-up tree without tg's artifacts.
134 # NAME will be ignored for -i and -w, but needs to be present
139 git ls-tree
--full-tree "$(get_tree_$source "$name")" |
140 awk -F ' ' '$2 !~ /^.top/' |
147 tgname
="$(basename "$0")"
148 hook_call
="\"\$(\"$tgname\" --hooks-path)\"/$1 \"\$@\""
149 if [ -f "$git_dir/hooks/$1" ] && fgrep
-q "$hook_call" "$git_dir/hooks/$1"; then
150 # Another job well done!
153 # Prepare incantation
154 if [ -x "$git_dir/hooks/$1" ]; then
155 hook_call
="$hook_call"' || exit $?'
157 hook_call
="exec $hook_call"
159 # Don't call hook if tg is not installed
160 hook_call
="if which \"$tgname\" > /dev/null; then $hook_call; fi"
161 # Insert call into the hook
165 [ ! -s "$git_dir/hooks/$1" ] ||
cat "$git_dir/hooks/$1"
166 } >"$git_dir/hooks/$1+"
167 chmod a
+x
"$git_dir/hooks/$1+"
168 mv "$git_dir/hooks/$1+" "$git_dir/hooks/$1"
171 # setup_ours (no arguments)
174 if [ ! -s "$git_dir/info/attributes" ] ||
! grep -q topmsg
"$git_dir/info/attributes"; then
175 [ -d "$git_dir/info" ] || mkdir
"$git_dir/info"
177 echo ".topmsg merge=ours"
178 echo ".topdeps merge=ours"
179 } >>"$git_dir/info/attributes"
181 if ! git config merge.ours.driver
>/dev
/null
; then
182 git config merge.ours.name
'"always keep ours" merge driver'
183 git config merge.ours.driver
'touch %A'
187 # measure_branch NAME [BASE]
190 _bname
="$1"; _base
="$2"
191 [ -n "$_base" ] || _base
="refs/top-bases/$_bname"
192 # The caller should've verified $name is valid
193 _commits
="$(git rev-list "$_bname" ^"$_base" -- | wc_l)"
194 _nmcommits
="$(git rev-list --no-merges "$_bname" ^"$_base" -- | wc_l)"
195 if [ $_commits -ne 1 ]; then
200 echo "$_commits/$_nmcommits $_suffix"
203 # branch_contains B1 B2
204 # Whether B1 is a superset of B2.
207 [ -z "$(git rev-list --max-count=1 ^"$1" "$2" --)" ]
211 # Whether REF is a valid ref name
214 git rev-parse
--verify "$@" >/dev
/null
2>&1
218 # Whether BRANCH has a remote equivalent (accepts top-bases/ too)
221 [ -n "$base_remote" ] && ref_exists
"remotes/$base_remote/$1"
224 # Return the verified TopGit branch name or die with an error.
225 # As a convenience, if HEAD is given and HEAD is a symbolic ref to
226 # refs/heads/... then ... will be verified instead.
227 # if "$2" = "-f" then return an error rather than dying.
228 verify_topgit_branch
()
232 _verifyname
="${1#refs/heads/}"
235 _verifyname
="${1#refs/top-bases/}"
238 _verifyname
="$(git symbolic-ref HEAD 2>/dev/null || :)"
239 [ -n "$_verifyname" ] || die
"HEAD is not a symbolic ref"
240 case "$_verifyname" in refs
/heads
/*) :;; *)
241 [ "$2" != "-f" ] ||
return 1
242 die
"HEAD is not a symbolic ref to the refs/heads namespace"
244 _verifyname
="${_verifyname#refs/heads/}"
250 if ! git rev-parse
--short --verify "refs/heads/$_verifyname" >/dev
/null
2>&1; then
251 [ "$2" != "-f" ] ||
return 1
254 if ! git rev-parse
--short --verify "refs/top-bases/$_verifyname" >/dev
/null
2>&1; then
255 [ "$2" != "-f" ] ||
return 1
256 die
"not a TopGit-controlled branch"
258 printf '%s' "$_verifyname"
265 # use the merge base in case the base is ahead.
266 mb
="$(git merge-base "refs
/top-bases
/$_branch_name" "$_branch_name" 2> /dev/null)";
268 test -z "$mb" ||
test "$(git rev-parse "$mb^
{tree
}")" = "$(git rev-parse "$_branch_name^
{tree
}")";
271 non_annihilated_branches
()
274 git for-each-ref
${_pattern:-refs/top-bases} |
275 while read rev type ref
; do
276 name
="${ref#refs/top-bases/}"
277 if branch_annihilated
"$name"; then
284 # Make sure our tree is clean
287 git update-index
--ignore-submodules --refresh ||
288 die
"the working directory has uncommitted changes (see above) - first commit or reset them"
289 [ -z "$(git diff-index --cached --name-status -r --ignore-submodules HEAD --)" ] ||
290 die
"the index has uncommited changes"
294 # Whether REF is a SHA1 (compared to a symbolic name).
297 [ "$(git rev-parse "$1")" = "$1" ]
300 # recurse_deps_internal NAME [BRANCHPATH...]
301 # get recursive list of dependencies with leading 0 if branch exists 1 if missing
302 # followed by a 1 if the branch is "tgish" or a 0 if not
303 # then the branch name followed by its depedency chain (which might be empty)
304 # An output line might look like this:
305 # 0 1 t/foo/leaf t/foo/int t/stage
306 # If no_remotes is non-empty, exclude remotes
307 # If recurse_preorder is non-empty, do a preorder rather than postorder traversal
308 recurse_deps_internal
()
310 if ! ref_exists
"$1"; then
311 [ -z "$2" ] ||
echo "1 0 $*"
315 # If no_remotes is unset also check our base against remote base.
316 # Checking our head against remote head has to be done in the helper.
317 if test -z "$no_remotes" && has_remote
"top-bases/$1"; then
318 echo "0 0 refs/remotes/$base_remote/top-bases/$1 $*"
322 if ref_exists
"refs/top-bases/$1"; then
324 [ -z "$recurse_preorder" -o -z "$2" ] ||
echo "0 $_is_tgish $*"
326 # if the branch was annihilated, it is considered to have no dependencies
327 if ! branch_annihilated
"$1"; then
328 #TODO: handle nonexisting .topdeps?
329 git cat-file blob
"$1:.topdeps" 2>/dev
/null |
330 while read _dname
; do
331 # Shoo shoo, leave our environment alone!
332 (recurse_deps_internal
"$_dname" "$@")
337 [ -n "$recurse_preorder" -o -z "$2" ] ||
echo "0 $_is_tgish $*"
341 # helper for recurse_deps so that a return statement executed inside CMD
342 # does not return from recurse_deps. This shouldn't be necessary, but it
343 # seems that it actually is.
349 # recurse_deps CMD NAME [BRANCHPATH...]
350 # Recursively eval CMD on all dependencies of NAME.
351 # Dependencies are visited in topological order.
352 # CMD can refer to $_name for queried branch name,
353 # $_dep for dependency name,
354 # $_depchain for space-seperated branch backtrace,
355 # $_dep_missing boolean to check whether $_dep is present
356 # and the $_dep_is_tgish boolean.
357 # It can modify $_ret to affect the return value
358 # of the whole function.
359 # If recurse_deps() hits missing dependencies, it will append
360 # them to space-separated $missing_deps list and skip them
361 # after calling CMD with _dep_missing set.
362 # remote dependencies are processed if no_remotes is unset.
367 _depsfile
="$(get_temp tg-depsfile)"
368 recurse_deps_internal
"$@" >>"$_depsfile"
371 while read _ismissing _istgish _dep _name _deppath
; do
372 _depchain
="$_name${_deppath:+ $_deppath}"
374 [ "$_istgish" = "0" ] || _dep_is_tgish
=1
376 if [ "$_ismissing" != "0" ]; then
378 case " $missing_deps " in *" $_dep "*) :;; *)
379 missing_deps
="${missing_deps:+$missing_deps }$_dep"
388 # branch_needs_update
389 # This is a helper function for determining whether given branch
390 # is up-to-date wrt. its dependencies. It expects input as if it
391 # is called as a recurse_deps() helper.
392 # In case the branch does need update, it will echo it together
393 # with the branch backtrace on the output (see needs_update()
394 # description for details) and set $_ret to non-zero.
395 branch_needs_update
()
397 if [ -n "$_dep_missing" ]; then
398 echo "! $_dep $_depchain"
403 if [ -n "$_dep_is_tgish" ]; then
404 branch_annihilated
"$_dep" && return 0
406 if has_remote
"$_dep"; then
407 branch_contains
"$_dep" "refs/remotes/$base_remote/$_dep" || _dep_base_update
=%
409 # This can possibly override the remote check result;
410 # we want to sync with our base first
411 branch_contains
"$_dep" "refs/top-bases/$_dep" || _dep_base_update
=:
414 if [ -n "$_dep_base_update" ]; then
415 # _dep needs to be synced with its base/remote
416 echo "$_dep_base_update $_dep $_depchain"
418 elif [ -n "$_name" ] && ! branch_contains
"refs/top-bases/$_name" "$_dep"; then
419 # Some new commits in _dep
420 echo "$_dep $_depchain"
426 # This function is recursive; it outputs reverse path from NAME
427 # to the branch (e.g. B_DIRTY B1 B2 NAME), one path per line,
428 # inner paths first. Innermost name can be ':' if the head is
429 # not in sync with the base, '%' if the head is not in sync
430 # with the remote (in this order of priority) or '!' if depednecy
432 # It will also return non-zero status if NAME needs update.
433 # If needs_update() hits missing dependencies, it will append
434 # them to space-separated $missing_deps list and skip them.
437 recurse_deps branch_needs_update
"$@"
440 # branch_empty NAME [-i | -w]
443 [ "$(pretty_tree "$1" -b)" = "$(pretty_tree "$1" $2)" ]
446 # list_deps [-i | -w]
447 # -i/-w apply only to HEAD
451 head="$(git symbolic-ref -q HEAD)" ||
454 git for-each-ref refs
/top-bases |
455 while read rev type ref
; do
456 name
="${ref#refs/top-bases/}"
457 if branch_annihilated
"$name"; then
462 [ "refs/heads/$name" = "$head" ] ||
464 cat_file
"$name:.topdeps" $from |
while read dep
; do
466 ref_exists
"refs/top-bases/$dep" ||
468 if ! "$dep_is_tgish" ||
! branch_annihilated
$dep; then
475 # switch_to_base NAME [SEED]
478 _base
="refs/top-bases/$1"; _seed
="$2"
479 # We have to do all the hard work ourselves :/
480 # This is like git checkout -b "$_base" "$_seed"
481 # (or just git checkout "$_base"),
482 # but does not create a detached HEAD.
483 git read-tree
-u -m HEAD
"${_seed:-$_base}"
484 [ -z "$_seed" ] || git update-ref
"$_base" "$_seed"
485 git symbolic-ref HEAD
"$_base"
488 # Show the help messages.
492 if [ "$1" = "-w" ]; then
496 if [ -z "$1" ] ; then
497 # This is currently invoked in all kinds of circumstances,
498 # including when the user made a usage error. Should we end up
499 # providing more than a short help message, then we should
501 # Petr's comment: http://marc.info/?l=git&m=122718711327376&w=2
503 ## Build available commands list for help output
507 for cmd
in "@cmddir@"/tg-
*; do
508 ! [ -r "$cmd" ] && continue
509 # strip directory part and "tg-" prefix
510 cmd
="$(basename "$cmd")"
516 echo "TopGit version $TG_VERSION - A different patch queue manager"
517 echo "Usage: $tgname ( help [-w] [<command>] | [-C <dir>] [-r <remote>] ($cmds) ...)"
518 echo "Use \"$tgdisplaydir$tgname help tg\" for overview of TopGit"
519 elif [ -r "@cmddir@"/tg-
$1 -o -r "@sharedir@/tg-$1.txt" ] ; then
520 if [ -n "$_www" ]; then
522 if ! [ -r "@sharedir@/topgit.html" ]; then
523 echo "`basename $0`: missing html help file:" \
524 "@sharedir@/topgit.html" 1>&2
527 if ! [ -r "@sharedir@/tg-$1.html" ]; then
528 echo "`basename $0`: missing html help file:" \
529 "@sharedir@/tg-$1.html" 1>&2
532 if [ -n "$nohtml" ]; then
533 echo "`basename $0`: use" \
534 "\"`basename $0` help $1\" instead" 1>&2
537 git web--browse
-c help.browser
"@sharedir@/tg-$1.html"
542 if [ -r "@cmddir@"/tg-
$1 ] ; then
543 "@cmddir@"/tg-
$1 -h 2>&1 ||
:
546 if [ -r "@sharedir@/tg-$1.txt" ] ; then
547 cat "@sharedir@/tg-$1.txt"
551 echo "`basename $0`: no help for $1" 1>&2
565 # pass "diff" to get pager.diff
566 # if pager.$1 is a boolean false returns cat
567 # if set to true or unset fails
568 # otherwise succeeds and returns the value
571 if _x
="$(git config --bool "pager.
$1" 2>/dev/null)"; then
572 [ "$_x" != "true" ] ||
return 1
576 if _x
="$(git config "pager.
$1" 2>/dev/null)"; then
584 # Set TG_PAGER to a valid executable
585 # After calling, code to be paged should be surrounded with {...} | eval "$TG_PAGER"
586 # Preference is (same as Git):
588 # 2. pager.$USE_PAGER_TYPE (but only if USE_PAGER_TYPE is set and so is pager.$USE_PAGER_TYPE)
589 # 3. core.pager (only if set)
594 isatty
1 ||
{ TG_PAGER
=cat; return 0; }
596 if [ -z "$TG_PAGER_IN_USE" ]; then
597 # TG_PAGER = GIT_PAGER | PAGER | less
598 # NOTE: GIT_PAGER='' is significant
599 if [ -n "${GIT_PAGER+set}" ]; then
600 TG_PAGER
="$GIT_PAGER"
601 elif [ -n "$USE_PAGER_TYPE" ] && _dp
="$(get_pager "$USE_PAGER_TYPE")"; then
603 elif _cp
="$(git config core.pager 2>/dev/null)"; then
605 elif [ -n "${PAGER+set}" ]; then
615 # Set pager default environment variables
616 # see pager.c:setup_pager
617 if [ -z "${LESS+set}" ]; then
620 if [ -z "${LV+set}" ]; then
624 # this is needed so e.g. `git diff` will still colorize it's output if
625 # requested in ~/.gitconfig with color.diff=auto
626 export GIT_PAGER_IN_USE
=1
628 # this is needed so we don't get nested pagers
629 export TG_PAGER_IN_USE
=1
633 # creates a new temporary file (or directory with -d) in the global
634 # temporary directory $tg_tmp_dir with pattern prefix NAME
637 mktemp
$2 "$tg_tmp_dir/$1.XXXXXX"
643 # suppress the merge log editor feature since git 1.7.10
645 export GIT_MERGE_AUTOEDIT
=no
646 git_dir
="$(git rev-parse --git-dir)"
647 root_dir
="$(git rev-parse --show-cdup)"; root_dir
="${root_dir:-.}"
648 logrefupdates
="$(git config --bool core.logallrefupdates 2>/dev/null || :)"
649 [ "$logrefupdates" = "true" ] || logrefupdates
=
651 # Make sure root_dir doesn't end with a trailing slash.
653 root_dir
="${root_dir%/}"
654 [ -n "$base_remote" ] || base_remote
="$(git config topgit.remote 2>/dev/null)" ||
:
656 # create global temporary directories, inside GIT_DIR
658 tg_tmp_dir
="$(mktemp -d "$git_dir/tg-tmp.XXXXXX
")"
659 trap "rm -rf \"$tg_tmp_dir\"" EXIT
662 # return the "realpath" for the item except the leaf is not resolved if it's
663 # a symbolic link. The directory part must exist, but the basename need not.
666 [ -n "$1" -a -d "$(dirname -- "$1")" ] ||
return 1
667 printf '%s' "$(cd -- "$
(dirname -- "$1")" && pwd -P)/$(basename -- "$1")"
673 die
"No command directory: '@cmddir@'"
675 if [ -n "$tg__include" ]; then
677 # We were sourced from another script for our utility functions;
678 # this is set by hooks. Skip the rest of the file. A simple return doesn't
679 # work as expected in every shell. See http://bugs.debian.org/516188
681 # ensure setup happens
690 tgdir
="$(dirname -- "$tg")/"
691 tgname
="$(basename -- "$tg")"
692 [ "$0" != "$tgname" ] || tgdir
=""
694 # If tg contains a '/' but does not start with one then replace it with an absolute path
696 case "$0" in /*) :;; */*)
697 tgdir
="$(cd "$
(dirname -- "$0")" && pwd -P)/"
701 # If the tg in the PATH is the same as "$tg" just display the basename
702 # tgdisplay will include any explicit -C <dir> option whereas tg will not
704 tgdisplaydir
="$tgdir"
706 if [ "$(get_abs_path "$tg")" = "$(get_abs_path "$
(which "$tgname" ||
:)" || :)" ]; then
717 while :; do case "$1" in
732 echo "Option -r requires an argument." >&2
738 explicit_remote
="$base_remote"
739 tg
="$tgdir$tgname -r $explicit_remote"
740 tgdisplay
="$tgdisplaydir$tgname"
741 [ -z "$explicit_dir" ] || tgdisplay
="$tgdisplay -C \"$explicit_dir\""
742 tgdisplay
="$tgdisplay -r $explicit_remote"
746 unset base_remote explicit_remote
748 tg
="$tgdir$tgname -u"
749 tgdisplay
="$tgdisplaydir$tgname"
750 [ -z "$explicit_dir" ] || tgdisplay
="$tgdisplay -C \"$explicit_dir\""
751 tgdisplay
="$tgdisplay -u"
757 echo "Option -C requires an argument." >&2
763 gitcdopt
=" -C \"$explicit_dir\""
765 tgdisplay
="$tgdisplaydir$tgname -C \"$explicit_dir\""
766 [ -z "$explicit_remote" ] || tg
="$tg -r $explicit_remote"
767 [ -z "$explicit_remote" ] || tgdisplay
="$tgdisplay -r $explicit_remote"
768 [ -z "$noremote" ] || tg
="$tg -u"
769 [ -z "$noremote" ] || tg
="$tgdisplay -u"
777 echo "Invalid option $1 (subcommand options must appear AFTER the subcommand)." >&2
786 [ -n "$cmd" ] ||
{ cmd
="$1"; shift ||
:; }
790 [ -n "$cmd" ] ||
{ do_help
; exit 1; }
803 [ -r "@cmddir@"/tg-
$cmd ] ||
{
804 echo "Unknown subcommand: $cmd" >&2
810 [ -z "$noremote" ] ||
unset base_remote
812 # make sure merging the .top* files will always behave sanely
815 setup_hook
"pre-commit"
817 .
"@cmddir@"/tg-
$cmd;;