cg-admin-rewritehist: More accurate --tag-name-filter description
[cogito.git] / cg-Xlib
blob2e023b5c208cde3695db99c27894af1d191cc334
1 #!/usr/bin/env bash
3 # Common code shared by the Cogito toolkit
4 # Copyright (c) Petr Baudis, 2005
6 # This file provides a library containing common code shared with all the
7 # Cogito programs.
9 _cg_cmd=${0##*/}
10 _cleanup_code=
14 #######################################################################
16 # Program lifetime and error reporting {{{1
19 warn()
21 local beep=
23 if [ "$1" = "-b" ]; then
24 beep=1
25 shift
28 echo "Warning: $@" >&2
29 [ -z "$beep" ] || echo -ne "\a" >&2
32 die()
34 echo "$_cg_cmd: $@" >&2
35 eval "$_cleanup_code"
36 exit 1
39 usage()
41 die "usage: $USAGE"
44 # Do this in case we get interrupted or prematurely die
45 cleanup_trap()
47 _cleanup_code="$*"
48 # die will execute the $_cleanup_code
49 trap "echo; die \"interrupted\"" SIGINT SIGTERM
54 #######################################################################
56 # Stubs for tools we need but aren't everywhere {{{1
59 mktemp()
61 if [ "$has_mktemp" ]; then
62 "$has_mktemp" "$@"
63 return
66 dirarg=
67 if [ x"$1" = x"-d" ]; then
68 dirarg="-d"
69 shift
71 prefix=
72 if [ x"$1" = x"-t" ]; then
73 prefix="${TMPDIR:-/tmp}/"
74 shift
77 "$(which mktemp)" $dirarg "$prefix$1"
80 stat()
82 if [ "$1" != "-c" ] || [ "$2" != "%s" -a "$2" != "%i" ]; then
83 echo "INTERNAL ERROR: Unsupported stat call $@" >&2
84 return 1
86 if [ "$has_stat" ]; then
87 "$has_stat" "$@"
88 return
91 # It's always -c '%s' now.
92 if [ "$2" = "%s" ]; then
93 ls -l "$3" | awk '{ print $5 }'
94 elif [ "$2" = "%i" ]; then
95 ls -lid "$3" | awk '{ print $1 }'
99 readlink()
101 if [ "$has_readlink" ]; then
102 "$has_readlink" "$@"
103 return
106 if [ "$1" = "-f" ]; then
107 shift
108 target="$(maynormpath "$1")"
109 target="${target%/}"
111 # -e will test the existence of the final target; therefore,
112 # it will also protect against recursive symlinks and such
113 [ -e "$target" ] || return 1
115 while true; do
116 if ! [ -L "$target" ]; then
117 echo "$target"
118 return 0
120 target2="$(readlink "$target" 2>/dev/null)" || return 1
121 [ "$target2" ] || return 1
122 target="$(maynormpath "$target2" "$target"/..)"
123 done
124 return 42
127 line="$(ls -ld "$1" 2>/dev/null)" || return 1
128 case "$line" in
129 *-\>*)
130 echo "${line#* -> }";;
132 return 1;;
133 esac
134 return 0
137 # tac is not POSIX :-(
138 tac()
140 if [ "$has_tac" ]; then
141 "$has_tac" "$@"
142 return
145 sed -n '1!G;$p;h';
148 # Usage: path_lookup COMMAND VARNAME [CMDTEST]
149 # Lookup COMMAND in $PATH and save the full path to VARNAME (optionally,
150 # only if CMDTEST on the command succeeds).
151 # This would have been type -P but we want to be bash2 compatible.
152 path_lookup()
154 local exename="$1" varname="$2" cmdtest="$3"
156 # We do our own $PATH iteration as it's faster than the fork()
157 # of $(which), and this happens many times every time we
158 # execute some cg tool.
159 # Cut'n'pasted to the 'cg' source.
160 local save_IFS dir cmd
161 save_IFS="$IFS"; IFS=:
162 for dir in $PATH; do
163 IFS="$save_IFS"
164 cmd="$dir/$exename"
165 if [ -x "$cmd" ] && { [ -z "$cmdtest" ] || eval "$cmdtest"; }; then
166 export $varname="$cmd"
167 break
169 done
170 IFS="$save_IFS"
175 #######################################################################
177 # Non-stubbish but straightforward tool wrappers {{{1
180 pager()
182 local cgless
183 # A little trick to tell the difference between unset and set-to-empty
184 # variable:
185 if [ "${CG_LESS+set}" = "set" ]; then
186 cgless="$CG_LESS"
187 else
188 cgless="R $LESS $_local_CG_LESS"
190 local line
191 # Invoke pager only if there's any actual output
192 if IFS=$'\n' read -r line; then
193 ( echo "$line"; cat; ) | LESS="$cgless" ${PAGER:-less} $PAGER_FLAGS
197 # Usage: showdate SECONDS TIMEZONE [FORMAT]
198 # Display date nicely based on how GIT stores it.
199 # Save the date to $_showdate
200 showdate()
202 local secs=$1
203 local format="$3"
205 # extract the timezone of the commit
206 local tzsign=${2%????}
207 local tmp=${2#?}
208 local tzhours=${tmp%??}
209 local tzminutes=${tmp#??}
211 # strip leading zeroes (shells don't tend to like them)
212 [ "${tzhours%?}" = 0 ] && tzhours=${tzhours#?}
213 [ "${tzminutes%?}" = 0 ] && tzminutes=${tzminutes#?}
215 secs=$(($secs $tzsign ($tzhours * 3600 + $tzminutes * 60)))
217 [ "$format" ] || format="+%a, %d %b %Y %H:%M:%S $2"
218 if [ "$has_gnudate" ]; then
219 _showdate="$(LANG=C "$has_gnudate" -ud "1970-01-01 UTC + $secs sec" "$format")"
220 else
221 _showdate="$(LANG=C date -u -r $secs "$format")"
227 #######################################################################
229 # Colorification routines {{{1
232 colorify_detect()
234 if [ -z "$1" ]; then
235 # If -c was not passed but we _are_ on a terminal,
236 # check $2.usecolor yet.
237 [ -t 1 ] || return 1
238 [ "$(git-repo-config --bool $2.usecolor)" = "true" ] || return 1
239 [ "$CG_COLORS_AUTO" ] || return 0
240 else
241 [ "$CG_COLORS_AUTO" ] || return 0
242 [ -t 1 ] || return 1
244 [ "$(tput setaf 1 2>/dev/null)" ] || return 1
245 return 0
248 # These are shared between cg-diff and cg-log:
249 colorify_diffcolors="diffhdr=1;36"
250 colorify_diffcolors="$colorify_diffcolors:diffhdradd=1;32:diffadd=32"
251 colorify_diffcolors="$colorify_diffcolors:diffhdrmod=1;35:diffmod=35"
252 colorify_diffcolors="$colorify_diffcolors:diffhdrrem=1;31:diffrem=31"
253 colorify_diffcolors="$colorify_diffcolors:diffhunk=36:diffctx=34"
254 colorify_diffcolors="$colorify_diffcolors:default=0"
256 colorify_setup()
258 local C="$1"
259 [ -z "$CG_COLORS" ] || C="$C:$CG_COLORS"
261 C=${C//=/=\'$'\e'[}
262 C=col${C//:/m\'; col}m\'
263 #coldefault=$(tput op)
264 eval "$C"
266 colorify_diffsed='
267 s/^diff --git.*/'$coldiffhdr'&'$coldefault'/
268 s/^+++.*/'$coldiffhdradd'&'$coldefault'/
269 s/^---.*/'$coldiffhdrrem'&'$coldefault'/
270 s/^[+].*/'$coldiffadd'&'$coldefault'/
271 s/^[-].*/'$coldiffrem'&'$coldefault'/
272 s/^new\( file\)\{0,1\} mode .*/'$coldiffadd'&'$coldefault'/
273 s/^\(deleted file\|old\) mode .*/'$coldiffrem'&'$coldefault'/
274 s/^rename to .*/'$coldiffadd'&'$coldefault'/
275 s/^rename from .*/'$coldiffrem'&'$coldefault'/
276 s/^\(@@ -.* +.* @@\)\(.*\)/'$coldiffhunk'\1'$coldiffctx'\2'$coldefault'/
282 #######################################################################
284 # Multi-column listing with variable column widths {{{1
287 # Usage: width="$(...single column... | column_width MINUSPREFIX MAXWIDTH)"
288 column_width()
290 local line= minusprefix="$1" maxwidth="$2"
291 [ "$maxwidth" ] || maxwidth=35
293 while read line; do
294 line=${line#$1};
295 echo ${#line}
296 done | sort -nr | head -n 1 |
298 read maxlen;
299 [ ${maxlen:-0} -le $maxwidth ] || maxlen=$maxwidth;
300 echo ${maxlen:-0}
304 # Usage: columns_print COL1 WIDTH COL2 - COL3 tWIDTH COL4 - ...
305 columns_print()
307 local fmt= cols=
308 cols=()
309 while [ $# -gt 0 ]; do
310 local col="$1"; shift
311 local width="$1"; shift
312 local tab=
313 local trim=
314 if [ x"${width:0:1}" = x"t" ]; then
315 tab=1; width="${width:1}"
317 if [ x"${width:0:1}" = x"m" ]; then
318 trim=1; width="${width:1}"
320 if [ x"$width" = x"-" ]; then
321 fmt="$fmt%s"
322 else
323 fmt="$fmt%-${width}s"
324 if [ -n "$trim" ] && [ ${#col} -gt "$width" ]; then
325 width=$((width - 3))
326 col="${col:0:$width}..."
329 cols[${#cols[@]}]="$col"
330 [ -z "$tab" ] || fmt="$fmt\t";
331 done
332 printf "$fmt\n" "${cols[@]}"
337 #######################################################################
339 # Ident-related tools {{{1
342 pick_id()
344 local lid="$1" uid="$2"
345 local pick_id_script='
346 /^'$lid' /{
347 s/'\''/'\''\\'\'\''/g
349 s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
350 s/'\''/'\''\'\'\''/g
351 s/.*/export GIT_'$uid'_NAME='\''&'\''/p
354 s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
355 s/'\''/'\''\'\'\''/g
356 s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
359 s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
360 s/'\''/'\''\'\'\''/g
361 s/.*/export GIT_'$uid'_DATE='\''&'\''/p
366 LANG=C LC_ALL=C sed -ne "$pick_id_script"
367 # Ensure non-empty id name.
368 echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
371 pick_author()
373 pick_id author AUTHOR
378 #######################################################################
380 # Path toolkit for handling path-per-line lists {{{1
383 # echo PATH | normpath
384 # Normalize the path, handling and removing any superfluous .. and .
385 # elements. Typically
386 # echo ABSPATH/RELPATH | normpath
387 # to get new absolute path.
388 normpath()
390 local inp
391 while IFS= read -r inp; do
392 local path path2
393 path=()
394 path2=()
396 while [[ "$inp" == */* ]]; do
397 path[${#path[@]}]="${inp%%/*}"
398 inp="${inp#*/}"
399 done
400 path[${#path[@]}]="$inp"
401 for (( i=0; $i < ${#path[@]}; i++ )); do
402 [ "${path[$i]}" != "." ] || continue
403 if [ "${path[$i]}" = ".." ]; then
404 [ "${#path2[@]}" -le 0 ] || unset path2[$((${#path2[@]} - 1))]
405 continue
407 path2[${#path2[@]}]="${path[$i]}"
408 done
409 for (( i=0; $i < ${#path2[@]}; i++ )); do
410 echo -n "${path2[$i]}"
411 [ $i -ge $((${#path2[@]} - 1)) ] || echo -n /
412 done
413 echo
414 done
417 # maynormpath PATH [BASE]
418 # If $PATH is relative, make it absolute wrt. $(pwd) or $BASE if specified.
419 # Basically, call this instead of normpath() if $PATH can ever be absolute.
420 maynormpath()
422 case "$1" in
424 echo "$1";;
426 base="$2"; [ "$base" ] || base="$(pwd)"
427 echo "$base/$1" | normpath
428 esac
431 # xargs with one path argument per line
432 path_xargs()
434 normpath | tr '\n' '\0' | xargs -0 "$@"
437 # Equivalent to cg-status -w -n -s '?', but the filenames are delimited
438 # by '\0' instead of '\n'.
439 # Usage: list_untracked_files DO_EXCLUDE SQUASH_DIRS [EXTRAEXCLUDE]...
440 # DO_EXCLUDE: "no", "noexclude" means not to exclude anything,
441 # otherwise the exclude rules apply
442 # SQUASH_DIRS: "squashdirs" means that if a whole directory is untracked,
443 # only the dirname/ will be listed, not all its contents
444 # EXTRAEXCLUDE: extra exclude pattern
445 list_untracked_files()
447 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: list_untracked_files() outside a working copy"
448 excludeflag="$1"; shift
449 squashflag="$1"; shift
450 EXCLUDE=()
451 if [ "$excludeflag" != "no" -a "$excludeflag" != "noexclude" ]; then
452 for excl in "$@"; do
453 EXCLUDE[${#EXCLUDE[@]}]="--exclude=$excl"
454 done
455 find_cogito_share
456 EXCLUDEFILE="${COGITO_REAL_SHARE}default-exclude"
457 if [ -f "$EXCLUDEFILE" ]; then
458 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
460 EXCLUDEFILE="$_git/info/exclude"
461 if [ -f "$EXCLUDEFILE" ]; then
462 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
464 # This is just for compatibility (2005-09-16).
465 # To be removed later.
466 EXCLUDEFILE="$_git/exclude"
467 if [ -f $EXCLUDEFILE ]; then
468 warn ".git/exclude is obsolete, use .git/info/exclude instead."
469 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
471 EXCLUDE[${#EXCLUDE[@]}]="--exclude-per-directory=.gitignore"
472 # Workaround for git < 1.2.0
473 if [ -n "$_git_relpath" ]; then
474 local dir="${_git_relpath%/}"
475 local reldir=".."
476 while [ "$dir" != "." ]; do
477 if [ "${dir%/*}" = "$dir" ]; then
478 dir="."
479 else
480 dir="${dir%/*}"
482 if [ -f "$reldir/.gitignore" ]; then
483 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$dir/.gitignore"
485 reldir="../$reldir"
486 done
489 local listdirs=
490 [ "$squashflag" != "squashdirs" ] || listdirs=--directory
491 git-ls-files -z --others $listdirs "${EXCLUDE[@]}"
496 #######################################################################
498 # Common message editor tools {{{1
501 editor_comment_start()
503 if [ -e "$_git/$1-template" ]; then
504 cat "$_git/$1-template" >>"$LOGMSG"
505 else
506 cat >>"$LOGMSG" <<EOT
507 CG: -----------------------------------------------------------------------
508 CG: Lines beginning with the CG: prefix are removed automatically.
513 # editor_comment_end [-f] ACTIONNAME
514 editor_comment_end()
516 local force= actionname=
517 if [ "$1" = "-f" ]; then
518 force=1; shift
520 actionname="$1"; shift
521 [ "$force" ] || echo "CG: If you want to abort the $actionname, just quit without saving this file." >>"$LOGMSG"
522 echo "CG: -----------------------------------------------------------------------" >>"$LOGMSG"
525 editor_msg_end()
527 echo "CG: vim: textwidth=75$*" >>"$LOGMSG"
530 editor_parse_setif()
532 if ! grep -q "^CG: $2:" "$LOGMSG2"; then
533 unset $1
534 else
535 export $1="$(sed -ne "s/^CG: $2: //p" "$LOGMSG2")"
539 editor_parse_clean()
541 grep -v ^CG: "$LOGMSG2" | git-stripspace >"$LOGMSG"
544 # editor_shalluse FORCEEDITOR
545 # This makes sure the editor is run even if input is not a tty.
546 editor_shalluse()
548 [ -n "$1" ] || tty -s
551 # editor [-f] ACTIONNAME ACTIONKEY
552 # W/o -f asks what-to-do if user didn't modify the log message.
553 # Returns $?:
554 # 0 all went fine, new log message saved in $LOGMSG2
555 # 1 aborted by user
556 _editor()
558 local force= actionname= actionkey=
559 if [ "$1" = "-f" ]; then
560 force=1; shift
562 actionname="$1"; shift
563 actionkey="$1"; shift
565 ${EDITOR:-vi} "$LOGMSG2"
566 [ -z "$force" ] || return 0
567 [ ! "$LOGMSG2" -nt "$LOGMSG" ] || return 0
569 echo "Log message unchanged or not specified" >&2
570 while true; do
571 read -p "Abort or $actionname? [a$actionkey] " choice
572 if [ "$choice" = "a" ] || [ "$choice" = "q" ]; then
573 return 1
574 elif [ "$choice" = "$actionkey" ]; then
575 return 0
577 done
579 editor()
581 if tty -s; then
582 _editor "$@"
583 else
584 _editor "$@" </dev/tty
589 #######################################################################
591 # Misc. common Git operations wrappers {{{1
594 # Usage: internal_commit IDFILE CTITLE CDESC
595 # Perform a commit for internal purposes. The commit id will be saved
596 # only to IDFILE (HEAD will not get updated), the commit title will
597 # be CTITLE (prefixed by [@internal@]) and CDESC should contain some
598 # description of what's the commit about - what created it, etc.
599 internal_commit()
601 cg-commit -w "$1" -m"[@internal@] $2" -m"$3" >/dev/null
604 # Shelve local uncommitted changes to a temporary commit
605 # Sets $curcommit to the shelved commit ID.
606 # TODO: Later, move this to cg-shelve or something and make it available for general use.
607 shelve_changes()
609 if exists_ref "refs/heads/.cg-shelve-$_git_head"; then
610 # The .cg-shelve name was deprecated as of 2006-11-19
611 rename_ref "refs/heads/.cg-shelve-$_git_head" "refs/shelves/$_git_head"
613 if already_dirty=$(get_ref "refs/shelves/$_git_head"); then
614 echo "Warning: Your current branch already has some local changes saved. Refusing to overwrite them." >&2
615 echo "This could happen if you switched away using 'cg-switch -l' but did not switch back using cg-switch." >&2
616 dirty="$_git_head-dirty"
617 if exists_ref "refs/heads/$dirty"; then
618 i=1; while exists_ref "refs/heads/$dirty$i"; do i=$((i+1)); done
619 dirty="$dirty$i"
621 rename_ref "refs/shelves/$_git_head" "refs/heads/$dirty" "$already_dirty"
622 echo "I have created branch $dirty and made the old local changes available as its last commit." >&2
624 # refs/shelves/$_git_head head does not exist and we aren't race-safe
625 # anyway, so writing directly to the file does not do any further
626 # harm here.
627 mkdir -p "$_git/refs/shelves"
628 internal_commit "$_git/refs/shelves/$_git_head" "cg-switch local changes shelve" "This commit for internal Cogito use stores uncommitted local changes at the time of cg-switch -l away from $_git_head."
629 curcommit="$(get_ref "refs/shelves/$_git_head")"
632 abort_shelve()
634 local shelvecommit="$1"
635 git-update-ref -d "refs/shelves/$_git_head" "$shelvecommit"
638 # Unshelve local changes for $_git_head
639 # (You must cache the current $_git_head commit to $dstcommit.)
640 unshelve_changes()
642 if exists_ref "refs/heads/.cg-shelve-$_git_head"; then
643 # The .cg-shelve name was deprecated as of 2006-11-19
644 if exists_ref "refs/shelves/$_git_head"; then
645 warn "shelves both under the old and new name detected, you are doing something strange; ignoring the old-style .cg-shelf-$_git_head shelve branch"
646 else
647 rename_ref "refs/heads/.cg-shelve-$_git_head" "refs/shelves/$_git_head"
650 if ! exists_ref "refs/shelves/$_git_head"; then
651 return
654 # TODO: Later, move this to cg-unshelve or something and make it available for general use.
655 # XXX: We will not properly restore merges, but that
656 # doesn't matter now since we won't let you cg-switch
657 # away from them in the first place. There are three
658 # tricky issues:
659 # * preserving the parents - not too tricky if you
660 # error out when the base branch changed in the
661 # meantime
662 # * preserving the set of files with local changes
663 # ignored by the merge. We will need to do some
664 # extra bookkeeping here, possibly in the cmomit
665 # message
666 # * when we get proper conflicts handling, we need
667 # to remember to override it when shelving and
668 # restore the list of commits after unshelving.
670 echo "Restoring local changes..."
671 local shelvecommit="$(get_ref "refs/shelves/$_git_head")"
672 tree_timewarp --no-head-update "along" "please roll" "$dstcommit" "$shelvecommit" || exit 1
673 abort_shelve "$shelvecommit"
676 # Usage: tree_timewarp [--no-head-update] DIRECTION_STR ROLLBACK_BOOL BASE BRANCH
677 # Reset the current tree from version BASE to version BRANCH, properly updating
678 # the working copy (if ROLLBACK_BOOL) and trying to keep local changes.
679 # Returns false in case of conflicts when merging local modifications (but only if ROLLBACK_ROLL).
680 tree_timewarp()
682 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: tree_timewarp() outside a working copy"
683 local no_head_update=
684 if [ "$1" = "--no-head-update" ]; then
685 no_head_update=1
686 shift
688 local dirstr="$1"; shift
689 local rollback="$1"; shift
690 local base="$1"; shift
691 local branch="$1"; shift
693 [ ! -s "$_git/cg-merge-state/merging" ] || die "merge in progress - cancel it by cg-reset first"
695 if [ -n "$rollback" ]; then
696 local localcommit=""
697 if local_changes "$base"; then
698 warn "uncommitted local changes, trying to bring them $dirstr"
699 local cidfile="$(mktemp -t gituncommit.XXXXXX)"
700 internal_commit "$cidfile" "timewarp local changes shelve" -m"This commit for internal Cogito use stores uncommitted local changes at the time of tree timewarp (any operation replaying/forwarding your tree, e.g. admin-uncommit or update), for an immediate use of three-way merging them back."
701 localcommit="$(cat "$cidfile")"
702 rm "$cidfile"
705 # We automatically do two-way or three-way merge as needed.
706 # If we do two-way merge, the index is always in sync with
707 # the working copy and HEAD; the git-read-tree logic to deal
708 # with dirty index is too crippled for us.
709 if ! git-read-tree -u -m "$base" $localcommit "$branch"; then
710 echo "cannot bring working tree to $branch, aborting" >&2
711 return 1
713 _CG_MERGING_LOCAL=1 git-merge-index -o -q "${COGITO_LIB}"cg-Xmergefile -a || :
715 [ "$no_head_update" ] || git-update-ref HEAD "$branch" || :
716 return 0
719 # Determine the most conservative merge base of two commits - keep
720 # recursing until we get only a single candidate for a merge base.
721 # The merge base is returned as $_cg_baselist. If we had to recurse,
722 # a non-zero number is stored in $_cg_base_conservative (otherwise,
723 # it's set empty).
724 conservative_merge_base()
726 local baselist safecounter
727 baselist=("$@")
728 _cg_base_conservative=
729 for (( safecounter=0; $safecounter < 1000; safecounter++ )) ; do
730 baselist=($(git-merge-base --all "${baselist[@]}")) || return 1
731 [ "${#baselist[@]}" -gt "1" ] || break
732 done
733 [ $safecounter -le 0 ] || _cg_base_conservative=$safecounter
734 _cg_baselist=("${baselist[@]}")
737 # Check whether there are any local (uncommitted) changes in the tree
738 local_changes()
740 local base="$1"
741 [ "$base" ] || base=HEAD
742 git-update-index --refresh >/dev/null || :
743 [ -n "$(git-diff-index -m -r "$base")" ]
746 # Usage: local_changes_in FILELIST
747 # Check whether there are any local (uncommitted) changes in the files
748 # listed in file FILELIST
749 local_changes_in()
751 local files="$1"
752 [ -n "$(git-diff-index -m -r HEAD | cut -f 2- | join "$files" -)" ]
755 # update_index will refresh the index and list the local modifications
756 # Note that this isn't usually safe, since some of the modifications may
757 # be recorded in the index file - modulo adds and removes also cg-restore
758 # to historical revisions. Besides, it gives confusing output for relpath.
759 # Never use it. If you do, accompany it with a comment explaining why is
760 # it safe to use it.
761 update_index()
763 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: update_index() outside a working copy"
764 git-update-index --refresh | sed 's/needs update$/locally modified/'
767 # Takes two object directories and checks if they are the same (symlinked
768 # or so).
769 is_same_repo()
771 local dir1="$1" dir2="$2" diff=1
773 # Originally, I wanted to compare readlink output, but that fails
774 # in binding setup; it isn't likely the object database directories
775 # themselves would be binded, but some trunk directories might.
776 # So we just create a file inside and see if it appears on the
777 # second side...
778 if [ ! -w "$dir1" -o ! -w "$dir2" ]; then
779 # ...except in readonly setups.
780 [ "$(readlink -f "$dir1")" != "$(readlink -f "$dir2")" ] || diff=0
781 else
782 n=$$
783 while [ -e "$dir1/.,,lnstest-$n" -o -e "$dir2/.,,lnstest-$n" ]; do
784 n=$((n+1))
785 done
786 touch "$dir1/.,,lnstest-$n"
787 [ ! -e "$dir2/.,,lnstest-$n" ] || diff=0
788 rm "$dir1/.,,lnstest-$n"
790 return $diff
793 # Determine the appropriate origin for the current branch
794 # Usage: choose_origin TESTDIR ERRORMSG
795 # TESTDIR is either branches or refs/heads, depends on if you care
796 # about the address or the ref to exist.
797 choose_origin()
799 local testdir="$1" errormsg="$2" alt_origin
801 alt_origin="$(git-repo-config --get "branch.$_git_head.merge")"
802 [ -n "$alt_origin" ] || alt_origin="refs/heads/$_git_head-origin"
804 if [ "$testdir" = "refs/heads" ]; then
805 if exists_ref "$alt_origin"; then
806 echo "${alt_origin#refs/heads/}"
807 elif exists_ref "$testdir/$_git_head-origin"; then
808 # Deprecated on 2006-11-18
809 warn "Origin default headname-origin obsolete, please use cg-switch -o to select it instead"
810 echo "$_git_head-origin"
811 elif exists_ref "$testdir/origin"; then
812 echo origin
813 else
814 die "$errormsg"
816 else
817 if [ "${alt_origin#refs/heads/}" != "$alt_origin" ]; then
818 alt_origin="$testdir/${alt_origin#refs/heads/}"
820 if [ -s "$_git/$alt_origin" ]; then
821 echo "${alt_origin#$testdir/}"
822 elif [ -s "$_git/$testdir/$_git_head-origin" ]; then
823 # Deprecated on 2006-11-18
824 warn "Origin default headname-origin obsolete, please use cg-switch -o to select it instead"
825 echo "$_git_head-origin"
826 elif [ -s "$_git/$testdir/origin" ]; then
827 echo origin
828 else
829 die "$errormsg"
834 # Does a given ref (FQRN) exist? Get its value on stdout or return error.
835 get_ref()
837 # This could be cg-object-id -c instead, but this is faster.
838 # This _should_ be git-show-ref instead, but as of git-1.4.4 it won't
839 # show hidden refs.
840 #git-show-ref --hash --verify "$1" 2>/dev/null
841 git-rev-parse --verify "$1" 2>/dev/null
844 exists_ref()
846 #git-show-ref --quiet --verify "$1"
847 get_ref "$@" >/dev/null
850 # Renames ref of a given value (will not overwrite existing ref)
851 rename_ref()
853 local from="$1" to="$2" val="$3"
854 git-update-ref "$to" "$val" "0000000000000000000000000000000000000000"
855 git-update-ref -d "$from" "$val"
860 #######################################################################
862 # Meta-tools for keeping things sticking together {{{1
865 # Setup COGITO_REAL_SHARE to COGITO_SHARE if make install'd, or to
866 # the most probable location if not.
867 find_cogito_share()
869 if [ -n "${COGITO_SHARE}" ]; then
870 COGITO_REAL_SHARE="${COGITO_SHARE}"
871 return
873 if [ "${0%/*}" != "$0" ]; then
874 COGITO_REAL_SHARE="${0%/*}/"
875 return
877 # I'm not sure if the following normally ever gets triggered.
878 # I can only do it by `sh cg-status`. --pasky
879 COGITO_REAL_SHARE="./$_git_relpath/"
884 #######################################################################
886 # Help and options parsing {{{1
889 print_help()
891 path_lookup "cg-$2" "_cg_cmd"
892 [ -n "$_cg_cmd" ] || exit 1
894 sed -n '/^USAGE=/,0s/.*"\(.*\)"/Usage: \1/p' < "$_cg_cmd"
895 if [ x"$1" = xlong ]; then
896 echo
897 # TODO: Reduce this to just one sed if possible.
898 sed -n '3,/^$/s/^# *//p' < "$_cg_cmd" | sed 's/^\(-.*\)::.*/\1::/'
899 exit
902 sed -n '3s/^# *//p' < "$_cg_cmd"
903 echo
904 echo "Options:"
905 maxlen="$(sed -n 's/^# \(-.*\)::[^A-Za-z0-9].*/\1/p' < "$_cg_cmd" | column_width)"
906 [ $maxlen -ge 11 ] || maxlen=11 # --long-help
907 _cg_fmt=" %-20s %s\n"
908 sed -n 's/# \(-.*\)::[^A-Za-z0-9]\(.*\)/\1\n\2/p' < "$_cg_cmd" | while read line; do
909 case "$line" in
911 _cg_option="$line"
914 columns_print " " - "$_cg_option" "$maxlen" " $line" -
916 esac
917 done
918 columns_print " " - "-h, --help" "$maxlen" " Print usage summary" -
919 columns_print " " - "--long-help" "$maxlen" " Print user manual" -
920 columns_print " " - "--version" "$maxlen" " Print version" -
921 exit
924 for option in "$@"; do
925 [ x"$option" != x-- ] || break
926 if [ x"$option" = x"-h" ] || [ x"$option" = x"--help" ]; then
927 print_help short "${_cg_cmd##cg-}"
928 elif [ x"$option" = x"--long-help" ]; then
929 print_help long "${_cg_cmd##cg-}"
930 elif [ x"$option" = x"--version" ]; then
931 exec "$(dirname "$0")"/cg-version
933 done
936 ARGS=("$@")
937 ARGPOS=0
938 set '' # clear positional parameters - use $ARGS[] instead
940 if [ -z "$CG_NORC" -a -t 1 -a -e "$HOME/.cgrc" ]; then
941 _cg_name="${_cg_cmd#cg-}"
942 # We hope that there are no weird (regex-sensitive) characters
943 # in Cogito command names.
944 _cg_defaults1="$(sed -n "/^$_cg_cmd/s/^$_cg_cmd //p" < "$HOME/.cgrc")"
945 _cg_defaults2="$(sed -n "/^$_cg_name/s/^$_cg_name //p" < "$HOME/.cgrc")"
946 # And here we explicitly do not quote, allowing multiple arguments
947 # to be specified - default word splitting will do its work here.
948 ARGS=($_cg_defaults1 $_cg_defaults2 "${ARGS[@]}")
951 optshift()
953 unset ARGS[$ARGPOS]
954 ARGS=("${ARGS[@]}")
955 [ -z "$1" -o -n "${ARGS[$ARGPOS]}" ] ||
956 die "option $1 requires an argument"
959 optfail()
961 die "unrecognized option ${ARGS[$ARGPOS]}"
964 optconflict()
966 die "conflicting options $CUROPT and $1"
969 optparse()
971 unset OPTARG
972 if [ -z "$1" ]; then
973 case "${ARGS[$ARGPOS]}" in
974 --) optshift; return 1 ;;
975 -*) return 0 ;;
976 *) while (( ++ARGPOS < ${#ARGS[@]} )); do
977 [[ "${ARGS[$ARGPOS]}" != -- ]] || return 1
978 [[ "${ARGS[$ARGPOS]}" != -* ]] || return 0
979 done;
980 return 1 ;;
981 esac
984 CUROPT="${ARGS[$ARGPOS]}"
985 local match="${1%=}" minmatch="${2:-1}" opt="$CUROPT" o="$CUROPT" val
986 [[ "$1" != *= ]] || val="$match"
987 case "$match" in
988 --*)
989 [ -z "$val" ] || o="${o%%=*}"
990 [ ${#o} -ge $((2 + $minmatch)) -a \
991 "${match:0:${#o}}" = "$o" ] || return 1
992 if [[ -n "$val" && "$opt" == *=?* ]]; then
993 ARGS[$ARGPOS]="${opt#*=}"
994 else
995 optshift "$val"
996 fi ;;
998 [[ "$o" == $match* ]] || return 1
999 [[ "$o" != -?-* || -n "$val" ]] || optfail
1000 ARGS[$ARGPOS]=${o#$match}
1001 if [ -n "${ARGS[$ARGPOS]}" ]; then
1002 [ -n "$val" ] || ARGS[$ARGPOS]=-"${ARGS[$ARGPOS]}";
1003 else
1004 optshift "$val"
1005 fi ;;
1007 die "optparse cannot handle $1" ;;
1008 esac
1010 if [ "$val" ]; then
1011 OPTARG="${ARGS[$ARGPOS]}"
1012 optshift
1018 #######################################################################
1020 # Common Cogito tools initialization {{{1
1024 # Optional tools detection/stubbing
1026 # check_tool_presence NAME COMMAND EXENAME...
1027 # (use $cmd in COMMAND)
1028 check_tool()
1030 cmdname="$1"; shift
1031 cmdtest="$1"; shift
1032 hasname="has_$cmdname"
1034 export $hasname=
1035 for exename in "$@"; do
1036 path_lookup "$exename" "$hasname" "$cmdtest"
1037 [ -z "$hasname" ] || break
1038 done 2>/dev/null
1041 if ! [ "$__cogito_subsequent" ]; then
1042 export __cogito_subsequent=1
1044 check_tool mktemp 'todel="$("$cmd" -t)" && rm "$todel"' mktemp
1045 check_tool stat '"$cmd" -c %s / >/dev/null' stat gnustat gstat
1046 check_tool readlink '"$cmd" -f / >/dev/null' readlink
1047 check_tool gnudate '"$cmd" -Rud "1970-01-01 UTC" >/dev/null' date gnudate gdate
1048 check_tool tac 'tac </dev/null >/dev/null' tac
1053 _git="${GIT_DIR:-.git}"
1054 if [ ! "$_git_repo_unneeded" ] && [ ! "$GIT_DIR" ] && [ ! -d "$_git" ]; then
1055 _git_abs_path="$(git-rev-parse --git-dir 2>/dev/null)"
1056 if [ -d "$_git_abs_path" ]; then
1057 _git_relpath="$(git-rev-parse --show-prefix)"
1058 cd "$_git_abs_path/.."
1061 _git_objects="${GIT_OBJECT_DIRECTORY:-$_git/objects}"
1064 # Check if we have something to work on, unless the script can do w/o it.
1065 if [ ! "$_git_repo_unneeded" ]; then
1066 # Check if we aren't _in_ the repository (perhaps it's without
1067 # a working copy).
1068 if [ ! -d "$_git" -a -d objects/ -a -d refs/ -a -s HEAD ] &&
1069 GIT_DIR=. git-symbolic-ref HEAD >/dev/null 2>&1; then
1070 _git=.
1071 export GIT_DIR=.
1073 [ "$GIT_DIR" != . ] || _git_no_wc=1
1074 if [ ! -d "$_git" ]; then
1075 echo "There is no GIT repository here ($_git not found)" >&2
1076 exit 1
1077 elif [ ! -x "$_git" ]; then
1078 echo "You do not have permission to access this GIT repository" >&2
1079 exit 1
1080 elif [ "$_git_no_wc" -a ! "$_git_wc_unneeded" ]; then
1081 echo "This command requires working copy and cannot be run inside a GIT repository" >&2
1082 exit 1
1084 _git_head=master
1085 [ ! -s "$_git/HEAD" ] || { _git_head="$(git-symbolic-ref HEAD)"; _git_head="${_git_head#refs/heads/}"; }
1086 [ ! -s "$_git/head-name" ] || _git_head="$(cat "$_git/head-name")"
1089 # Check if the script requires to be called from the workdir root.
1090 if [ "$_git_requires_root" ] && [ "$_git_relpath" ]; then
1091 echo "This command can be run only from the project root" >&2
1092 exit 1
1096 # Backward compatibility hacks:
1097 # Fortunately none as of now.