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
14 #######################################################################
16 # Program lifetime and error reporting {{{1
23 if [ "$1" = "-b" ]; then
28 echo "Warning: $@" >&2
29 [ -z "$beep" ] ||
echo -ne "\a" >&2
34 echo "$_cg_cmd: $@" >&2
44 # Do this in case we get interrupted or prematurely die
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
61 if [ "$has_mktemp" ]; then
67 if [ x
"$1" = x
"-d" ]; then
72 if [ x
"$1" = x
"-t" ]; then
73 prefix
="${TMPDIR:-/tmp}/"
77 "$(which mktemp)" $dirarg "$prefix$1"
82 if [ "$1" != "-c" ] ||
[ "$2" != "%s" -a "$2" != "%i" ]; then
83 echo "INTERNAL ERROR: Unsupported stat call $@" >&2
86 if [ "$has_stat" ]; then
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 }'
101 if [ "$has_readlink" ]; then
106 if [ "$1" = "-f" ]; then
108 target
="$(maynormpath "$1")"
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
116 if ! [ -L "$target" ]; then
120 target2
="$(readlink "$target" 2>/dev/null)" ||
return 1
121 [ "$target2" ] ||
return 1
122 target
="$(maynormpath "$target2" "$target"/..)"
127 line
="$(ls -ld "$1" 2>/dev/null)" ||
return 1
130 echo "${line#* -> }";;
137 # tac is not POSIX :-(
140 if [ "$has_tac" ]; then
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.
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
=:
165 if [ -x "$cmd" ] && { [ -z "$cmdtest" ] ||
eval "$cmdtest"; }; then
166 export $varname="$cmd"
175 #######################################################################
177 # Non-stubbish but straightforward tool wrappers {{{1
183 # A little trick to tell the difference between unset and set-to-empty
185 if [ "${CG_LESS+set}" = "set" ]; then
188 cgless
="R $LESS $_local_CG_LESS"
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
205 # extract the timezone of the commit
206 local tzsign
=${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")"
221 _showdate
="$(LANG=C date -u -r $secs "$format")"
227 #######################################################################
229 # Colorification routines {{{1
235 # If -c was not passed but we _are_ on a terminal,
236 # check $2.usecolor yet.
238 [ "$(git-repo-config --bool $2.usecolor)" = "true" ] ||
return 1
239 [ "$CG_COLORS_AUTO" ] ||
return 0
241 [ "$CG_COLORS_AUTO" ] ||
return 0
244 [ "$(tput setaf 1 2>/dev/null)" ] ||
return 1
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"
259 [ -z "$CG_COLORS" ] || C
="$C:$CG_COLORS"
262 C
=col${C//:/m\'; col}m
\'
263 #coldefault=$(tput op)
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)"
290 local line
= minusprefix
="$1" maxwidth
="$2"
291 [ "$maxwidth" ] || maxwidth
=35
296 done |
sort -nr |
head -n 1 |
299 [ ${maxlen:-0} -le $maxwidth ] || maxlen
=$maxwidth;
304 # Usage: columns_print COL1 WIDTH COL2 - COL3 tWIDTH COL4 - ...
309 while [ $# -gt 0 ]; do
310 local col="$1"; shift
311 local width
="$1"; shift
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
323 fmt="$fmt%-${width}s"
324 if [ -n "$trim" ] && [ ${#col} -gt "$width" ]; then
326 col="${col:0:$width}..."
329 cols
[${#cols[@]}]="$col"
330 [ -z "$tab" ] ||
fmt="$fmt\t";
332 printf "$fmt\n" "${cols[@]}"
337 #######################################################################
339 # Ident-related tools {{{1
344 local lid
="$1" uid
="$2"
345 local pick_id_script
='
347 s/'\''/'\''\\'\'\''/g
349 s/^'$lid' \([^<]*\) <[^>]*> .*$/\1/
351 s
/.
*/export GIT_
'$uid'_NAME
='\''&'\''/p
354 s
/^
'$lid' [^
<]* <\
([^
>]*\
)> .
*$
/\
1/
356 s/.*/export GIT_'$uid'_EMAIL='\''&'\''/p
359 s/^'$lid' [^<]* <[^>]*> \(.*\)$/\1/
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%%@*}\""
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.
391 while IFS= read -r inp; do
396 while [[ "$inp" == */* ]]; do
397 path[${#path[@]}]="${inp%%/*}"
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))]
407 path2[${#path2[@]}]="${path[$i]}"
409 for (( i=0; $i < ${#path2[@]}; i++ )); do
410 echo -n "${path2[$i]}"
411 [ $i -ge $((${#path2[@]} - 1)) ] || echo -n /
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.
426 base="$2"; [ "$base" ] || base="$(pwd)"
427 echo "$base/$1" | normpath
431 # xargs with one path argument per line
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
451 if [ "$excludeflag" != "no" -a "$excludeflag" != "noexclude" ]; then
453 EXCLUDE[${#EXCLUDE[@]}]="--exclude=$excl"
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%/}"
476 while [ "$dir" != "." ]; do
477 if [ "${dir%/*}" = "$dir" ]; then
482 if [ -f "$reldir/.gitignore" ]; then
483 EXCLUDE[${#EXCLUDE[@]}]="--exclude-from=$dir/.gitignore"
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"
506 cat >>"$LOGMSG" <<EOT
507 CG: -----------------------------------------------------------------------
508 CG: Lines beginning with the CG: prefix are removed automatically.
513 # editor_comment_end [-f] ACTIONNAME
516 local force= actionname=
517 if [ "$1" = "-f" ]; then
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"
527 echo "CG: vim: textwidth=75$*" >>"$LOGMSG"
532 if ! grep -q "^CG: $2:" "$LOGMSG2"; then
535 export $1="$(sed -ne "s/^CG: $2: //p" "$LOGMSG2")"
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.
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.
554 # 0 all went fine, new log message saved in $LOGMSG2
558 local force
= actionname
= actionkey
=
559 if [ "$1" = "-f" ]; then
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
571 read -p "Abort or $actionname? [a$actionkey] " choice
572 if [ "$choice" = "a" ] ||
[ "$choice" = "q" ]; then
574 elif [ "$choice" = "$actionkey" ]; then
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.
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.
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
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
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")"
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.)
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"
647 rename_ref
"refs/heads/.cg-shelve-$_git_head" "refs/shelves/$_git_head"
650 if ! exists_ref
"refs/shelves/$_git_head"; then
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
659 # * preserving the parents - not too tricky if you
660 # error out when the base branch changed in the
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
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).
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
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
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")"
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
713 _CG_MERGING_LOCAL
=1 git-merge-index
-o -q "${COGITO_LIB}"cg-Xmergefile
-a ||
:
715 [ "$no_head_update" ] || git-update-ref HEAD
"$branch" ||
:
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,
724 conservative_merge_base
()
726 local baselist safecounter
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
733 [ $safecounter -le 0 ] || _cg_base_conservative
=$safecounter
734 _cg_baselist
=("${baselist[@]}")
737 # Check whether there are any local (uncommitted) changes in the tree
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
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
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
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
778 if [ ! -w "$dir1" -o ! -w "$dir2" ]; then
779 # ...except in readonly setups.
780 [ "$(readlink -f "$dir1")" != "$(readlink -f "$dir2")" ] ||
diff=0
783 while [ -e "$dir1/.,,lnstest-$n" -o -e "$dir2/.,,lnstest-$n" ]; do
786 touch "$dir1/.,,lnstest-$n"
787 [ ! -e "$dir2/.,,lnstest-$n" ] ||
diff=0
788 rm "$dir1/.,,lnstest-$n"
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.
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
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
834 # Does a given ref (FQRN) exist? Get its value on stdout or return error.
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
840 #git-show-ref --hash --verify "$1" 2>/dev/null
841 git-rev-parse
--verify "$1" 2>/dev
/null
846 #git-show-ref --quiet --verify "$1"
847 get_ref
"$@" >/dev
/null
850 # Renames ref of a given value (will not overwrite existing 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.
869 if [ -n "${COGITO_SHARE}" ]; then
870 COGITO_REAL_SHARE
="${COGITO_SHARE}"
873 if [ "${0%/*}" != "$0" ]; then
874 COGITO_REAL_SHARE
="${0%/*}/"
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
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
897 # TODO: Reduce this to just one sed if possible.
898 sed -n '3,/^$/s/^# *//p' < "$_cg_cmd" |
sed 's/^\(-.*\)::.*/\1::/'
902 sed -n '3s/^# *//p' < "$_cg_cmd"
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
914 columns_print
" " - "$_cg_option" "$maxlen" " $line" -
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" -
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
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[@]}")
955 [ -z "$1" -o -n "${ARGS[$ARGPOS]}" ] ||
956 die
"option $1 requires an argument"
961 die
"unrecognized option ${ARGS[$ARGPOS]}"
966 die
"conflicting options $CUROPT and $1"
973 case "${ARGS[$ARGPOS]}" in
974 --) optshift
; return 1 ;;
976 *) while (( ++ARGPOS
< ${#ARGS[@]} )); do
977 [[ "${ARGS[$ARGPOS]}" != -- ]] ||
return 1
978 [[ "${ARGS[$ARGPOS]}" != -* ]] ||
return 0
984 CUROPT
="${ARGS[$ARGPOS]}"
985 local match
="${1%=}" minmatch
="${2:-1}" opt
="$CUROPT" o
="$CUROPT" val
986 [[ "$1" != *= ]] || val
="$match"
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#*=}"
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]}";
1007 die
"optparse cannot handle $1" ;;
1011 OPTARG
="${ARGS[$ARGPOS]}"
1018 #######################################################################
1020 # Common Cogito tools initialization {{{1
1024 # Optional tools detection/stubbing
1026 # check_tool_presence NAME COMMAND EXENAME...
1027 # (use $cmd in COMMAND)
1032 hasname
="has_$cmdname"
1035 for exename
in "$@"; do
1036 path_lookup
"$exename" "$hasname" "$cmdtest"
1037 [ -z "$hasname" ] ||
break
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
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
1073 [ "$GIT_DIR" != .
] || _git_no_wc
=1
1074 if [ ! -d "$_git" ]; then
1075 echo "There is no GIT repository here ($_git not found)" >&2
1077 elif [ ! -x "$_git" ]; then
1078 echo "You do not have permission to access this GIT repository" >&2
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
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
1096 # Backward compatibility hacks:
1097 # Fortunately none as of now.