cogito: understand permissions written as "100755"
[cogito.git] / cg-Xlib
blobc1262bfe7b90e3eedd8b0bacd7c40ce667ee6d42
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 silent_die()
34 eval "$_cleanup_code"
35 exit 1
38 die()
40 echo "$_cg_cmd: $@" >&2
41 silent_die
44 usage()
46 echo "usage: $USAGE" >&2
47 silent_die
50 # Do this in case we get interrupted or prematurely die
51 cleanup_trap()
53 _cleanup_code="$*"
54 # die will execute the $_cleanup_code
55 trap "echo; die \"interrupted\"" SIGINT SIGTERM
60 #######################################################################
62 # Stubs for tools we need but aren't everywhere {{{1
65 mktemp()
67 if [ "$has_mktemp" ]; then
68 "$has_mktemp" "$@"
69 return
72 dirarg=
73 if [ x"$1" = x"-d" ]; then
74 dirarg="-d"
75 shift
77 prefix=
78 if [ x"$1" = x"-t" ]; then
79 prefix="${TMPDIR:-/tmp}/"
80 shift
83 "$(which mktemp)" $dirarg "$prefix$1"
86 stat()
88 if [ "$1" != "-c" ] || [ "$2" != "%s" -a "$2" != "%i" ]; then
89 echo "INTERNAL ERROR: Unsupported stat call $@" >&2
90 return 1
92 if [ "$has_stat" ]; then
93 "$has_stat" "$@"
94 return
97 # It's always -c '%s' now.
98 if [ "$2" = "%s" ]; then
99 ls -l "$3" | awk '{ print $5 }'
100 elif [ "$2" = "%i" ]; then
101 ls -lid "$3" | awk '{ print $1 }'
105 readlink()
107 if [ "$has_readlink" ]; then
108 "$has_readlink" "$@"
109 return
112 if [ "$1" = "-f" ]; then
113 shift
114 target="$(maynormpath "$1")"
115 target="${target%/}"
117 # -e will test the existence of the final target; therefore,
118 # it will also protect against recursive symlinks and such
119 [ -e "$target" ] || return 1
121 while true; do
122 if ! [ -L "$target" ]; then
123 echo "$target"
124 return 0
126 target2="$(readlink "$target" 2>/dev/null)" || return 1
127 [ "$target2" ] || return 1
128 target="$(maynormpath "$target2" "$target"/..)"
129 done
130 return 42
133 line="$(ls -ld "$1" 2>/dev/null)" || return 1
134 case "$line" in
135 *-\>*)
136 echo "${line#* -> }";;
138 return 1;;
139 esac
140 return 0
143 # tac is not POSIX :-(
144 tac()
146 if [ "$has_tac" ]; then
147 "$has_tac" "$@"
148 return
151 sed -n '1!G;$p;h';
154 # Usage: path_lookup COMMAND VARNAME [CMDTEST]
155 # Lookup COMMAND in $PATH and save the full path to VARNAME (optionally,
156 # only if CMDTEST on the command succeeds).
157 # This would have been type -P but we want to be bash2 compatible.
158 path_lookup()
160 local exename="$1" varname="$2" cmdtest="$3"
162 # We do our own $PATH iteration as it's faster than the fork()
163 # of $(which), and this happens many times every time we
164 # execute some cg tool.
165 # Cut'n'pasted to the 'cg' source.
166 local save_IFS dir cmd
167 save_IFS="$IFS"; IFS=:
168 for dir in $PATH; do
169 IFS="$save_IFS"
170 cmd="$dir/$exename"
171 if [ -x "$cmd" ] && { [ -z "$cmdtest" ] || eval "$cmdtest"; }; then
172 export $varname="$cmd"
173 break
175 done
176 IFS="$save_IFS"
181 #######################################################################
183 # Non-stubbish but straightforward tool wrappers {{{1
186 pager()
188 local cgless
189 # A little trick to tell the difference between unset and set-to-empty
190 # variable:
191 if [ "${CG_LESS+set}" = "set" ]; then
192 cgless="$CG_LESS"
193 else
194 cgless="R $LESS $_local_CG_LESS"
196 local line
197 # Invoke pager only if there's any actual output
198 if IFS=$'\n' read -r line; then
199 ( echo "$line"; cat; ) | LESS="$cgless" ${PAGER:-less} $PAGER_FLAGS
203 # Usage: showdate SECONDS TIMEZONE [FORMAT]
204 # Display date nicely based on how GIT stores it.
205 # Save the date to $_showdate
206 showdate()
208 local secs=$1
209 local format="$3"
211 # extract the timezone of the commit
212 local tzsign=${2%????}
213 local tmp=${2#?}
214 local tzhours=${tmp%??}
215 local tzminutes=${tmp#??}
217 # strip leading zeroes (shells don't tend to like them)
218 [ "${tzhours%?}" = 0 ] && tzhours=${tzhours#?}
219 [ "${tzminutes%?}" = 0 ] && tzminutes=${tzminutes#?}
221 secs=$(($secs $tzsign ($tzhours * 3600 + $tzminutes * 60)))
223 [ "$format" ] || format="+%a, %d %b %Y %H:%M:%S $2"
224 if [ "$has_gnudate" ]; then
225 _showdate="$(LANG=C "$has_gnudate" -ud "1970-01-01 UTC + $secs sec" "$format")"
226 else
227 _showdate="$(LANG=C date -u -r $secs "$format")"
233 #######################################################################
235 # Colorification routines {{{1
238 colorify_detect()
240 if [ -z "$1" ]; then
241 # If -c was not passed but we _are_ on a terminal,
242 # check $2.usecolor yet.
243 [ -t 1 ] || return 1
244 [ "$(git-repo-config --bool $2.usecolor)" = "true" ] || return 1
245 [ "$CG_COLORS_AUTO" ] || return 0
246 else
247 [ "$CG_COLORS_AUTO" ] || return 0
248 [ -t 1 ] || return 1
250 [ "$(tput setaf 1 2>/dev/null)" ] || return 1
251 return 0
254 # These are shared between cg-diff and cg-log:
255 colorify_diffcolors="diffhdr=1;36"
256 colorify_diffcolors="$colorify_diffcolors:diffhdradd=1;32:diffadd=32"
257 colorify_diffcolors="$colorify_diffcolors:diffhdrmod=1;35:diffmod=35"
258 colorify_diffcolors="$colorify_diffcolors:diffhdrrem=1;31:diffrem=31"
259 colorify_diffcolors="$colorify_diffcolors:diffhunk=36:diffctx=34"
260 colorify_diffcolors="$colorify_diffcolors:default=0"
262 colorify_setup()
264 local C="$1"
265 [ -z "$CG_COLORS" ] || C="$C:$CG_COLORS"
267 C=${C//=/=\'$'\e'[}
268 C=col${C//:/m\'; col}m\'
269 #coldefault=$(tput op)
270 eval "$C"
272 colorify_diffsed='
273 s/^diff --git.*/'$coldiffhdr'&'$coldefault'/
274 s/^+++.*/'$coldiffhdradd'&'$coldefault'/
275 s/^---.*/'$coldiffhdrrem'&'$coldefault'/
276 s/^[+].*/'$coldiffadd'&'$coldefault'/
277 s/^[-].*/'$coldiffrem'&'$coldefault'/
278 s/^new\( file\)\{0,1\} mode .*/'$coldiffadd'&'$coldefault'/
279 s/^\(deleted file\|old\) mode .*/'$coldiffrem'&'$coldefault'/
280 s/^rename to .*/'$coldiffadd'&'$coldefault'/
281 s/^rename from .*/'$coldiffrem'&'$coldefault'/
282 s/^\(@@ -.* +.* @@\)\(.*\)/'$coldiffhunk'\1'$coldiffctx'\2'$coldefault'/
288 #######################################################################
290 # Multi-column listing with variable column widths {{{1
293 # Usage: width="$(...single column... | column_width MINUSPREFIX MAXWIDTH)"
294 column_width()
296 local line= minusprefix="$1" maxwidth="$2"
297 [ "$maxwidth" ] || maxwidth=35
299 while read line; do
300 line=${line#$1};
301 echo ${#line}
302 done | sort -nr | head -n 1 |
304 read maxlen;
305 [ ${maxlen:-0} -le $maxwidth ] || maxlen=$maxwidth;
306 echo ${maxlen:-0}
310 # Usage: columns_print COL1 WIDTH COL2 - COL3 tWIDTH COL4 - ...
311 columns_print()
313 local fmt= cols=
314 cols=()
315 while [ $# -gt 0 ]; do
316 local col="$1"; shift
317 local width="$1"; shift
318 local tab=
319 local trim=
320 if [ x"${width:0:1}" = x"t" ]; then
321 tab=1; width="${width:1}"
323 if [ x"${width:0:1}" = x"m" ]; then
324 trim=1; width="${width:1}"
326 if [ x"$width" = x"-" ]; then
327 fmt="$fmt%s"
328 else
329 fmt="$fmt%-${width}s"
330 if [ -n "$trim" ] && [ ${#col} -gt "$width" ]; then
331 width=$((width - 3))
332 col="${col:0:$width}..."
335 cols[${#cols[@]}]="$col"
336 [ -z "$tab" ] || fmt="$fmt\t";
337 done
338 printf "$fmt\n" "${cols[@]}"
343 #######################################################################
345 # Ident-related tools {{{1
348 pick_id()
350 local lid="$1" uid="$2"
351 local pick_id_script='
352 /^'$lid' /{
353 s/'\''/'\''\\'\'\''/g
355 s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
356 s/'\''/'\''\'\'\''/g
357 s/.*/export GIT_'$uid'_NAME='\''&'\''/p
360 s/^'$lid' [^<]* <\([^>]*\)> .*$/\1/
361 s/'\''/'\''\'\'\''/g
362 s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
365 s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
366 s/'\''/'\''\'\'\''/g
367 s/.*/export GIT_'$uid'_DATE='\''&'\''/p
372 LANG=C LC_ALL=C sed -ne "$pick_id_script"
373 # Ensure non-empty id name.
374 echo "[ -n \"\$GIT_${uid}_NAME\" ] || export GIT_${uid}_NAME=\"\${GIT_${uid}_EMAIL%%@*}\""
377 pick_author()
379 pick_id author AUTHOR
384 #######################################################################
386 # Path toolkit for handling path-per-line lists {{{1
389 # echo PATH | normpath
390 # Normalize the path, handling and removing any superfluous .. and .
391 # elements. Typically
392 # echo ABSPATH/RELPATH | normpath
393 # to get new absolute path.
394 normpath()
396 local inp
397 while IFS= read -r inp; do
398 local path path2
399 path=()
400 path2=()
402 while [[ "$inp" == */* ]]; do
403 path[${#path[@]}]="${inp%%/*}"
404 inp="${inp#*/}"
405 done
406 path[${#path[@]}]="$inp"
407 for (( i=0; $i < ${#path[@]}; i++ )); do
408 [ "${path[$i]}" != "." ] || continue
409 if [ "${path[$i]}" = ".." ]; then
410 [ "${#path2[@]}" -le 0 ] || unset path2[$((${#path2[@]} - 1))]
411 continue
413 path2[${#path2[@]}]="${path[$i]}"
414 done
415 for (( i=0; $i < ${#path2[@]}; i++ )); do
416 echo -n "${path2[$i]}"
417 [ $i -ge $((${#path2[@]} - 1)) ] || echo -n /
418 done
419 echo
420 done
423 # maynormpath PATH [BASE]
424 # If $PATH is relative, make it absolute wrt. $(pwd) or $BASE if specified.
425 # Basically, call this instead of normpath() if $PATH can ever be absolute.
426 maynormpath()
428 case "$1" in
430 echo "$1";;
432 base="$2"; [ "$base" ] || base="$(pwd)"
433 echo "$base/$1" | normpath
434 esac
437 # xargs with one path argument per line
438 path_xargs()
440 normpath | tr '\n' '\0' | xargs -0 "$@"
443 # Equivalent to cg-status -w -n -s '?', but the filenames are delimited
444 # by '\0' instead of '\n'.
445 # Usage: list_untracked_files DO_EXCLUDE SQUASH_DIRS [EXTRAEXCLUDE]...
446 # DO_EXCLUDE: "no", "noexclude" means not to exclude anything,
447 # otherwise the exclude rules apply
448 # SQUASH_DIRS: "squashdirs" means that if a whole directory is untracked,
449 # only the dirname/ will be listed, not all its contents
450 # EXTRAEXCLUDE: extra exclude pattern
451 list_untracked_files()
453 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: list_untracked_files() outside a working copy"
454 excludeflag="$1"; shift
455 squashflag="$1"; shift
456 EXCLUDE=()
457 if [ "$excludeflag" != "no" -a "$excludeflag" != "noexclude" ]; then
458 for excl in "$@"; do
459 EXCLUDE[${#EXCLUDE[@]}]="--exclude=$excl"
460 done
461 find_cogito_share
462 EXCLUDEFILE="${COGITO_REAL_SHARE}default-exclude"
463 if [ -f "$EXCLUDEFILE" ]; then
464 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
466 EXCLUDEFILE="$_git/info/exclude"
467 if [ -f "$EXCLUDEFILE" ]; then
468 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
470 # This is just for compatibility (2005-09-16).
471 # To be removed later.
472 EXCLUDEFILE="$_git/exclude"
473 if [ -f $EXCLUDEFILE ]; then
474 warn ".git/exclude is obsolete, use .git/info/exclude instead."
475 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$EXCLUDEFILE"
477 EXCLUDE[${#EXCLUDE[@]}]="--exclude-per-directory=.gitignore"
478 # Workaround for git < 1.2.0
479 if [ -n "$_git_relpath" ]; then
480 local dir="${_git_relpath%/}"
481 local reldir=".."
482 while [ "$dir" != "." ]; do
483 if [ "${dir%/*}" = "$dir" ]; then
484 dir="."
485 else
486 dir="${dir%/*}"
488 if [ -f "$reldir/.gitignore" ]; then
489 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$dir/.gitignore"
491 reldir="../$reldir"
492 done
495 local listdirs=
496 [ "$squashflag" != "squashdirs" ] || listdirs=--directory
497 git-ls-files -z --others $listdirs "${EXCLUDE[@]}"
502 #######################################################################
504 # Common message editor tools {{{1
507 editor_comment_start()
509 if [ -e "$_git/$1-template" ]; then
510 cat "$_git/$1-template" >>"$LOGMSG"
511 else
512 cat >>"$LOGMSG" <<EOT
513 CG: -----------------------------------------------------------------------
514 CG: Lines beginning with the CG: prefix are removed automatically.
519 # editor_comment_end [-f] ACTIONNAME
520 editor_comment_end()
522 local force= actionname=
523 if [ "$1" = "-f" ]; then
524 force=1; shift
526 actionname="$1"; shift
527 [ "$force" ] || echo "CG: If you want to abort the $actionname, just quit without saving this file." >>"$LOGMSG"
528 echo "CG: -----------------------------------------------------------------------" >>"$LOGMSG"
531 editor_msg_end()
533 echo "CG: vim: textwidth=75$*" >>"$LOGMSG"
536 editor_parse_setif()
538 if ! grep -q "^CG: $2:" "$LOGMSG2"; then
539 unset $1
540 else
541 export $1="$(sed -ne "s/^CG: $2: //p" "$LOGMSG2")"
545 editor_parse_clean()
547 grep -v ^CG: "$LOGMSG2" | git-stripspace >"$LOGMSG"
550 # editor_shalluse FORCEEDITOR
551 # This makes sure the editor is run even if input is not a tty.
552 editor_shalluse()
554 [ -n "$1" ] || tty -s
557 # editor [-f] ACTIONNAME ACTIONKEY
558 # W/o -f asks what-to-do if user didn't modify the log message.
559 # Returns $?:
560 # 0 all went fine, new log message saved in $LOGMSG2
561 # 1 aborted by user
562 _editor()
564 local force= actionname= actionkey=
565 if [ "$1" = "-f" ]; then
566 force=1; shift
568 actionname="$1"; shift
569 actionkey="$1"; shift
571 ${EDITOR:-vi} "$LOGMSG2"
572 [ -z "$force" ] || return 0
573 [ ! "$LOGMSG2" -nt "$LOGMSG" ] || return 0
575 echo "Log message unchanged or not specified" >&2
576 while true; do
577 read -p "Abort or $actionname? [a$actionkey] " choice
578 if [ "$choice" = "a" ] || [ "$choice" = "q" ]; then
579 return 1
580 elif [ "$choice" = "$actionkey" ]; then
581 return 0
583 done
585 editor()
587 if tty -s; then
588 _editor "$@"
589 else
590 _editor "$@" </dev/tty
595 #######################################################################
597 # Misc. common Git operations wrappers {{{1
600 # Usage: internal_commit IDFILE CTITLE CDESC
601 # Perform a commit for internal purposes. The commit id will be saved
602 # only to IDFILE (HEAD will not get updated), the commit title will
603 # be CTITLE (prefixed by [@internal@]) and CDESC should contain some
604 # description of what's the commit about - what created it, etc.
605 internal_commit()
607 cg-commit -w "$1" -m"[@internal@] $2" -m"$3" >/dev/null
610 # Shelve local uncommitted changes to a temporary commit
611 # Sets $curcommit to the shelved commit ID.
612 # TODO: Later, move this to cg-shelve or something and make it available for general use.
613 shelve_changes()
615 if exists_ref "refs/heads/.cg-shelve-$_git_head"; then
616 # The .cg-shelve name was deprecated as of 2006-11-19
617 rename_ref "refs/heads/.cg-shelve-$_git_head" "refs/shelves/$_git_head"
619 if already_dirty=$(get_ref "refs/shelves/$_git_head"); then
620 echo "Warning: Your current branch already has some local changes saved. Refusing to overwrite them." >&2
621 echo "This could happen if you switched away using 'cg-switch -l' but did not switch back using cg-switch." >&2
622 dirty="$_git_head-dirty"
623 if exists_ref "refs/heads/$dirty"; then
624 i=1; while exists_ref "refs/heads/$dirty$i"; do i=$((i+1)); done
625 dirty="$dirty$i"
627 rename_ref "refs/shelves/$_git_head" "refs/heads/$dirty" "$already_dirty"
628 echo "I have created branch $dirty and made the old local changes available as its last commit." >&2
630 # refs/shelves/$_git_head head does not exist and we aren't race-safe
631 # anyway, so writing directly to the file does not do any further
632 # harm here.
633 mkdir -p "$_git/refs/shelves"
634 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."
635 curcommit="$(get_ref "refs/shelves/$_git_head")"
638 abort_shelve()
640 local shelvecommit="$1"
641 git-update-ref -d "refs/shelves/$_git_head" "$shelvecommit"
644 # Unshelve local changes for $_git_head
645 # (You must cache the current $_git_head commit to $dstcommit.)
646 unshelve_changes()
648 if exists_ref "refs/heads/.cg-shelve-$_git_head"; then
649 # The .cg-shelve name was deprecated as of 2006-11-19
650 if exists_ref "refs/shelves/$_git_head"; then
651 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"
652 else
653 rename_ref "refs/heads/.cg-shelve-$_git_head" "refs/shelves/$_git_head"
656 if ! exists_ref "refs/shelves/$_git_head"; then
657 return
660 # TODO: Later, move this to cg-unshelve or something and make it available for general use.
661 # XXX: We will not properly restore merges, but that
662 # doesn't matter now since we won't let you cg-switch
663 # away from them in the first place. There are three
664 # tricky issues:
665 # * preserving the parents - not too tricky if you
666 # error out when the base branch changed in the
667 # meantime
668 # * preserving the set of files with local changes
669 # ignored by the merge. We will need to do some
670 # extra bookkeeping here, possibly in the cmomit
671 # message
672 # * when we get proper conflicts handling, we need
673 # to remember to override it when shelving and
674 # restore the list of commits after unshelving.
676 echo "Restoring local changes..."
677 local shelvecommit="$(get_ref "refs/shelves/$_git_head")"
678 tree_timewarp --no-head-update "along" "please roll" "$dstcommit" "$shelvecommit" || exit 1
679 abort_shelve "$shelvecommit"
682 # Usage: tree_timewarp [--no-head-update] DIRECTION_STR ROLLBACK_BOOL BASE BRANCH
683 # Reset the current tree from version BASE to version BRANCH, properly updating
684 # the working copy (if ROLLBACK_BOOL) and trying to keep local changes.
685 # Returns false in case of conflicts when merging local modifications (but only if ROLLBACK_ROLL).
686 tree_timewarp()
688 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: tree_timewarp() outside a working copy"
689 local no_head_update=
690 if [ "$1" = "--no-head-update" ]; then
691 no_head_update=1
692 shift
694 local dirstr="$1"; shift
695 local rollback="$1"; shift
696 local base="$1"; shift
697 local branch="$1"; shift
699 [ ! -s "$_git/cg-merge-state/merging" ] || die "merge in progress - cancel it by cg-reset first"
701 if [ -n "$rollback" ]; then
702 local localcommit=""
703 if local_changes "$base"; then
704 warn "uncommitted local changes, trying to bring them $dirstr"
705 local cidfile="$(mktemp -t gituncommit.XXXXXX)"
706 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."
707 localcommit="$(cat "$cidfile")"
708 rm "$cidfile"
711 # We automatically do two-way or three-way merge as needed.
712 # If we do two-way merge, the index is always in sync with
713 # the working copy and HEAD; the git-read-tree logic to deal
714 # with dirty index is too crippled for us.
715 if ! git-read-tree -u -m "$base" $localcommit "$branch"; then
716 echo "cannot bring working tree to $branch, aborting" >&2
717 return 1
719 _CG_MERGING_LOCAL=1 git-merge-index -o -q "${COGITO_LIB}"cg-Xmergefile -a || :
721 [ "$no_head_update" ] || git-update-ref HEAD "$branch" || :
722 return 0
725 # Determine the most conservative merge base of two commits - keep
726 # recursing until we get only a single candidate for a merge base.
727 # The merge base is returned as $_cg_baselist. If we had to recurse,
728 # a non-zero number is stored in $_cg_base_conservative (otherwise,
729 # it's set empty).
730 conservative_merge_base()
732 local baselist safecounter
733 baselist=("$@")
734 _cg_base_conservative=
735 for (( safecounter=0; $safecounter < 1000; safecounter++ )) ; do
736 baselist=($(git-merge-base --all "${baselist[@]}")) || return 1
737 [ "${#baselist[@]}" -gt "1" ] || break
738 done
739 [ $safecounter -le 0 ] || _cg_base_conservative=$safecounter
740 _cg_baselist=("${baselist[@]}")
743 # Check whether there are any local (uncommitted) changes in the tree
744 local_changes()
746 local base="$1"
747 [ "$base" ] || base=HEAD
748 git-update-index --refresh >/dev/null || :
749 [ -n "$(git-diff-index -m -r "$base")" ]
752 # Usage: local_changes_in FILELIST
753 # Check whether there are any local (uncommitted) changes in the files
754 # listed in file FILELIST
755 local_changes_in()
757 local files="$1"
758 [ -n "$(git-diff-index -m -r HEAD | cut -f 2- | join "$files" -)" ]
761 # update_index will refresh the index and list the local modifications
762 # Note that this isn't usually safe, since some of the modifications may
763 # be recorded in the index file - modulo adds and removes also cg-restore
764 # to historical revisions. Besides, it gives confusing output for relpath.
765 # Never use it. If you do, accompany it with a comment explaining why is
766 # it safe to use it.
767 update_index()
769 [ -z "$_git_no_wc" ] || die "INTERNAL ERROR: update_index() outside a working copy"
770 git-update-index --refresh | sed 's/needs update$/locally modified/'
773 # Takes two object directories and checks if they are the same (symlinked
774 # or so).
775 is_same_repo()
777 local dir1="$1" dir2="$2" diff=1
779 # Originally, I wanted to compare readlink output, but that fails
780 # in binding setup; it isn't likely the object database directories
781 # themselves would be binded, but some trunk directories might.
782 # So we just create a file inside and see if it appears on the
783 # second side...
784 if [ ! -w "$dir1" -o ! -w "$dir2" ]; then
785 # ...except in readonly setups.
786 [ "$(readlink -f "$dir1")" != "$(readlink -f "$dir2")" ] || diff=0
787 else
788 n=$$
789 while [ -e "$dir1/.,,lnstest-$n" -o -e "$dir2/.,,lnstest-$n" ]; do
790 n=$((n+1))
791 done
792 touch "$dir1/.,,lnstest-$n"
793 [ ! -e "$dir2/.,,lnstest-$n" ] || diff=0
794 rm "$dir1/.,,lnstest-$n"
796 return $diff
799 # Determine the appropriate origin for the current branch
800 # Usage: choose_origin TESTDIR ERRORMSG
801 # TESTDIR is either branches or refs/heads, depends on if you care
802 # about the address or the ref to exist.
803 choose_origin()
805 local testdir="$1" errormsg="$2" alt_origin
807 alt_origin="$(git-repo-config --get "branch.$_git_head.merge")"
808 [ -n "$alt_origin" ] || alt_origin="refs/heads/$_git_head-origin"
810 if [ "$testdir" = "refs/heads" ]; then
811 if exists_ref "$alt_origin"; then
812 echo "${alt_origin#refs/heads/}"
813 elif exists_ref "$testdir/$_git_head-origin"; then
814 # Deprecated on 2006-11-18
815 warn "Origin default headname-origin obsolete, please use cg-switch -o to select it instead"
816 echo "$_git_head-origin"
817 elif exists_ref "$testdir/origin"; then
818 echo origin
819 else
820 die "$errormsg"
822 else
823 if [ "${alt_origin#refs/heads/}" != "$alt_origin" ]; then
824 alt_origin="$testdir/${alt_origin#refs/heads/}"
826 if [ -s "$_git/$alt_origin" ]; then
827 echo "${alt_origin#$testdir/}"
828 elif [ -s "$_git/$testdir/$_git_head-origin" ]; then
829 # Deprecated on 2006-11-18
830 warn "Origin default headname-origin obsolete, please use cg-switch -o to select it instead"
831 echo "$_git_head-origin"
832 elif [ -s "$_git/$testdir/origin" ]; then
833 echo origin
834 else
835 die "$errormsg"
840 # Does a given ref (FQRN) exist? Get its value on stdout or return error.
841 get_ref()
843 # This could be cg-object-id -c instead, but this is faster.
844 # This _should_ be git-show-ref instead, but as of git-1.4.4 it won't
845 # show hidden refs.
846 #git-show-ref --hash --verify "$1" 2>/dev/null
847 git-rev-parse --verify "$1" 2>/dev/null
850 exists_ref()
852 #git-show-ref --quiet --verify "$1"
853 get_ref "$@" >/dev/null
856 # Renames ref of a given value (will not overwrite existing ref)
857 rename_ref()
859 local from="$1" to="$2" val="$3"
860 git-update-ref "$to" "$val" "0000000000000000000000000000000000000000"
861 git-update-ref -d "$from" "$val"
866 #######################################################################
868 # Meta-tools for keeping things sticking together {{{1
871 # Setup COGITO_REAL_SHARE to COGITO_SHARE if make install'd, or to
872 # the most probable location if not.
873 find_cogito_share()
875 if [ -n "${COGITO_SHARE}" ]; then
876 COGITO_REAL_SHARE="${COGITO_SHARE}"
877 return
879 if [ "${0%/*}" != "$0" ]; then
880 COGITO_REAL_SHARE="${0%/*}/"
881 return
883 # I'm not sure if the following normally ever gets triggered.
884 # I can only do it by `sh cg-status`. --pasky
885 COGITO_REAL_SHARE="./$_git_relpath/"
890 #######################################################################
892 # Help and options parsing {{{1
895 print_help()
897 path_lookup "cg-$2" "_cg_cmd"
898 [ -n "$_cg_cmd" ] || exit 1
900 sed -n '/^USAGE=/,0s/.*"\(.*\)"/Usage: \1/p' < "$_cg_cmd"
901 if [ x"$1" = xlong ]; then
902 echo
903 # TODO: Reduce this to just one sed if possible.
904 sed -n '3,/^$/s/^# *//p' < "$_cg_cmd" | sed 's/^\(-.*\)::.*/\1::/'
905 exit
908 sed -n '3s/^# *//p' < "$_cg_cmd"
909 echo
910 echo "Options:"
911 maxlen="$(sed -n 's/^# \(-.*\)::[^A-Za-z0-9].*/\1/p' < "$_cg_cmd" | column_width)"
912 [ $maxlen -ge 11 ] || maxlen=11 # --long-help
913 _cg_fmt=" %-20s %s\n"
914 sed -n 's/# \(-.*\)::[^A-Za-z0-9]\(.*\)/\1\n\2/p' < "$_cg_cmd" | while read line; do
915 case "$line" in
917 _cg_option="$line"
920 columns_print " " - "$_cg_option" "$maxlen" " $line" -
922 esac
923 done
924 columns_print " " - "-h, --help" "$maxlen" " Print usage summary" -
925 columns_print " " - "--long-help" "$maxlen" " Print user manual" -
926 columns_print " " - "--version" "$maxlen" " Print version" -
927 exit
930 for option in "$@"; do
931 [ x"$option" != x-- ] || break
932 if [ x"$option" = x"-h" ] || [ x"$option" = x"--help" ]; then
933 print_help short "${_cg_cmd##cg-}"
934 elif [ x"$option" = x"--long-help" ]; then
935 print_help long "${_cg_cmd##cg-}"
936 elif [ x"$option" = x"--version" ]; then
937 exec "$(dirname "$0")"/cg-version
939 done
942 ARGS=("$@")
943 ARGPOS=0
944 set '' # clear positional parameters - use $ARGS[] instead
946 if [ -z "$CG_NORC" -a -t 1 -a -e "$HOME/.cgrc" ]; then
947 _cg_name="${_cg_cmd#cg-}"
948 # We hope that there are no weird (regex-sensitive) characters
949 # in Cogito command names.
950 _cg_defaults1="$(sed -n "/^$_cg_cmd/s/^$_cg_cmd //p" < "$HOME/.cgrc")"
951 _cg_defaults2="$(sed -n "/^$_cg_name/s/^$_cg_name //p" < "$HOME/.cgrc")"
952 # And here we explicitly do not quote, allowing multiple arguments
953 # to be specified - default word splitting will do its work here.
954 ARGS=($_cg_defaults1 $_cg_defaults2 "${ARGS[@]}")
957 optshift()
959 unset ARGS[$ARGPOS]
960 ARGS=("${ARGS[@]}")
961 [ -z "$1" -o -n "${ARGS[$ARGPOS]}" ] ||
962 die "option $1 requires an argument"
965 optfail()
967 die "unrecognized option ${ARGS[$ARGPOS]}"
970 optconflict()
972 die "conflicting options $CUROPT and $1"
975 optparse()
977 unset OPTARG
978 if [ -z "$1" ]; then
979 case "${ARGS[$ARGPOS]}" in
980 --) optshift; return 1 ;;
981 -*) return 0 ;;
982 *) while (( ++ARGPOS < ${#ARGS[@]} )); do
983 [[ "${ARGS[$ARGPOS]}" != -- ]] || return 1
984 [[ "${ARGS[$ARGPOS]}" != -* ]] || return 0
985 done;
986 return 1 ;;
987 esac
990 CUROPT="${ARGS[$ARGPOS]}"
991 local match="${1%=}" minmatch="${2:-1}" opt="$CUROPT" o="$CUROPT" val
992 [[ "$1" != *= ]] || val="$match"
993 case "$match" in
994 --*)
995 [ -z "$val" ] || o="${o%%=*}"
996 [ ${#o} -ge $((2 + $minmatch)) -a \
997 "${match:0:${#o}}" = "$o" ] || return 1
998 if [[ -n "$val" && "$opt" == *=?* ]]; then
999 ARGS[$ARGPOS]="${opt#*=}"
1000 else
1001 optshift "$val"
1002 fi ;;
1004 [[ "$o" == $match* ]] || return 1
1005 [[ "$o" != -?-* || -n "$val" ]] || optfail
1006 ARGS[$ARGPOS]=${o#$match}
1007 if [ -n "${ARGS[$ARGPOS]}" ]; then
1008 [ -n "$val" ] || ARGS[$ARGPOS]=-"${ARGS[$ARGPOS]}";
1009 else
1010 optshift "$val"
1011 fi ;;
1013 die "optparse cannot handle $1" ;;
1014 esac
1016 if [ "$val" ]; then
1017 OPTARG="${ARGS[$ARGPOS]}"
1018 optshift
1024 #######################################################################
1026 # Common Cogito tools initialization {{{1
1030 # Optional tools detection/stubbing
1032 # check_tool_presence NAME COMMAND EXENAME...
1033 # (use $cmd in COMMAND)
1034 check_tool()
1036 cmdname="$1"; shift
1037 cmdtest="$1"; shift
1038 hasname="has_$cmdname"
1040 export $hasname=
1041 for exename in "$@"; do
1042 path_lookup "$exename" "$hasname" "$cmdtest"
1043 [ -z "$hasname" ] || break
1044 done 2>/dev/null
1047 if ! [ "$__cogito_subsequent" ]; then
1048 export __cogito_subsequent=1
1050 check_tool mktemp 'todel="$("$cmd" -t)" && rm "$todel"' mktemp
1051 check_tool stat '"$cmd" -c %s / >/dev/null' stat gnustat gstat
1052 check_tool readlink '"$cmd" -f / >/dev/null' readlink
1053 check_tool gnudate '"$cmd" -Rud "1970-01-01 UTC" >/dev/null' date gnudate gdate
1054 check_tool tac 'tac </dev/null >/dev/null' tac
1059 _git="${GIT_DIR:-.git}"
1060 if [ ! "$_git_repo_unneeded" ] && [ ! "$GIT_DIR" ] && [ ! -d "$_git" ]; then
1061 _git_abs_path="$(git-rev-parse --git-dir 2>/dev/null)"
1062 if [ -d "$_git_abs_path" ]; then
1063 _git_relpath="$(git-rev-parse --show-prefix)"
1064 cd "$_git_abs_path/.."
1067 _git_objects="${GIT_OBJECT_DIRECTORY:-$_git/objects}"
1070 # Check if we have something to work on, unless the script can do w/o it.
1071 if [ ! "$_git_repo_unneeded" ]; then
1072 # Check if we aren't _in_ the repository (perhaps it's without
1073 # a working copy).
1074 if [ ! -d "$_git" -a -d objects/ -a -d refs/ -a -s HEAD ] &&
1075 GIT_DIR=. git-symbolic-ref HEAD >/dev/null 2>&1; then
1076 _git=.
1077 export GIT_DIR=.
1079 [ "$GIT_DIR" != . ] || _git_no_wc=1
1080 if [ ! -d "$_git" ]; then
1081 echo "There is no GIT repository here ($_git not found)" >&2
1082 exit 1
1083 elif [ ! -x "$_git" ]; then
1084 echo "You do not have permission to access this GIT repository" >&2
1085 exit 1
1086 elif [ "$_git_no_wc" -a ! "$_git_wc_unneeded" ]; then
1087 echo "This command requires working copy and cannot be run inside a GIT repository" >&2
1088 exit 1
1090 _git_head=master
1091 [ ! -s "$_git/HEAD" ] || { _git_head="$(git-symbolic-ref HEAD)"; _git_head="${_git_head#refs/heads/}"; }
1092 [ ! -s "$_git/head-name" ] || _git_head="$(cat "$_git/head-name")"
1095 # Check if the script requires to be called from the workdir root.
1096 if [ "$_git_requires_root" ] && [ "$_git_relpath" ]; then
1097 echo "This command can be run only from the project root" >&2
1098 exit 1
1102 # Backward compatibility hacks:
1103 # Fortunately none as of now.