#!/bin/bash # YummyYummySourceControl - Simple and convenient Git wrapper # # Copyright (C) 2007 Tim Janik # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # If you have not received a copy of the GNU General Public License # along with this program, see: http://www.gnu.org/licenses/ #set -e # exit on errors #set -x # show commands VERSION=YummyYummySourceControl-0.9 # setup SETBOLD=true ; SETNORM=true ; COLOR= ; OUT=cat ; YYHELP= # perform wildcard string matches function match() { pattern="$1" searchtext="$2" case "$searchtext" in $pattern) return 0 ;; # $pattern must be unquoted to allow *?[] esac return 1 } function exit_err() { ecode=127 test -n "$1" && { ecode="$1"; shift; } test -n "$*" && { echo -ne "$*\n" >&2 ; } exit $ecode } # configure for terminals with ANSI color escapes TAB=$'\t' # joe(1) syntax highlighting is broken for $'' test -t 1 && match $'*\e[31m*' "`tput setaf 1 2>/dev/null`" && { SETBOLD=echo\ -ne\ '\e[1m' ; SETNORM=echo\ -ne\ '\e[0m' BOLD=$'\e[1m'; GREEN=$'\e[32m'; TURK=$'\e[36m'; NORM=$'\e[0m'; COLOR=--color ; OUT=less\ -R #' # work around syntax highlighting in old joe(1) versions wrg $'' # we hardcode ANSI escapes directly because less -R can only deal with \e[*m } # find repository match '*/yyhelp*' "/$0" && YYHELP=1 # no repo required gitdir="$(git-rev-parse --git-dir 2>/dev/null)" if test -f "$gitdir/HEAD" ; then githead="$gitdir/`git-symbolic-ref -q HEAD || echo HEAD`"; test -e "$githead" || githead= gitprefix="$(git-rev-parse --show-prefix)" ; test -z "$gitprefix" && gitprefix=. # no subdir support if GIT_DIR is set elif ! test "$YYHELP" ; then echo "`basename \"$0\"`: No repository" >&2 ; exit 9 fi gitsvn=false SVNURL="`git-config --get svn-remote.svn.url`"; GITURL="`git-config --get remote.origin.url`"; test -z "$GITURL" && GITURL="`cat $gitdir/remotes/origin 2>/dev/null | sed -n '/^URL: /{ s/^URL: //; p; q; }'`" test -z "$GITURL" -a -r "$gitdir/branches/origin" && GITURL="`cat $gitdir/branches/origin`" # cogito location test -n "$SVNURL" -a -z "$GITURL" && gitsvn=true # use git-svn for local git repos with svn url test -z "$GITURL" && GITURL="`cd \"$gitdir\" && pwd`" # no url, must be local # functions function list_committable() { # [FILES...] test -s "$gitdir/commit-ignore" && commitignore="$gitdir/commit-ignore" || commitignore= test -z "$*" && commitdir=. || commitdir= stripprefix="$gitprefix" test -n "$stripprefix" -a "${stripprefix: -1}" != / && stripprefix="$stripprefix/" # force trailing / git-diff-index --name-status --no-renames HEAD -- $commitdir "$@" | while IFS="$TAB" read -r mode file ; do case "$mode" in D) test -n "$(git-diff-files -- "$file")" && mode=! ;; M) test -n "$commitignore" && fgrep -qx "$file" "$commitignore" && mode=n ;; esac echo "$mode ${file#$stripprefix}" done } # GIT commands case "/$0" in */yyadd) exec git-update-index --add -- "$@" ;; */yyblame) test -z "$*" && exec yyhelp git-blame -- "$@" | { test -n "$COLOR" && sed "/^[^()]\+(Not Committed Yet\b/{ s/^\([^()]\+\)(\([^()]\+\))\(.*\)/$GREEN\1$TURK(\2)$GREEN\3$NORM/; p; d; }; s/^\([^()]\+\)(\([^()]\+\))/$BOLD\1$NORM$TURK(\2)$NORM/" \ || cat ; } | $OUT ;; */yybranch) FORCE=; test "x$1" = "x-f" && { FORCE=-f; shift ; } test "$#" = 1 && BRANCH="$1" || exec yyhelp exec git-branch $FORCE "$BRANCH" ;; */yybranchdel) DEL=-d; test "x$1" = "x-f" && { DEL=-D; shift ; } test "$#" = 1 && BRANCH="$1" || exec yyhelp exec git-branch $DEL "$BRANCH" ;; */yyChangeLog) FIRSTSVN= HASHFORMAT=" # %H (%cn)" test "x$1" = "x-s" && { shift; FIRSTSVN=$(git-rev-list HEAD --max-count=1 --grep='^git-svn-id:.*@[0-9].*-[a-f0-9]\{12\}$'); HASHFORMAT= # skip commit SHA1 } test -n "$*" && exec yyhelp git-log HEAD ${FIRSTSVN:+^$FIRSTSVN} \ --pretty="format:%ad %an$HASHFORMAT%n%n%s%n%n%b" | sed -e 's/^/ /;s/^ //;/^[ ]*$/d' \ -e 's/^[ ]*$//' | $OUT ;; */yycommit) # standard boilerplate USAGE='[FILES...]' ; SUBDIRECTORY_OK=Yes ; . git-sh-setup ; require_work_tree verify_msg=true # check and set AUTHOR and COMMITTER git-var GIT_AUTHOR_IDENT 2>/dev/null | grep -q '\b[0-9]\{8,\}[ ]\+[+-][0-9]\+ *$' || exit_err 9 "$0: missing commit author information" git-var GIT_COMMITTER_IDENT 2>/dev/null | grep -q '\b[0-9]\{8,\}[ ]\+[+-][0-9]\+ *$' || exit_err 9 "$0: missing committer information" AIDENT=$(git-var GIT_AUTHOR_IDENT | sed 's/\b\([0-9]\{8,\}[ ]\+[+-][0-9]\+ *\)$/\n\1/' | { read author; read time; echo -n " $author" ; echo $time | gawk '{ print strftime (" %F %T %z", $1) }' }) CIDENT=$(git-var GIT_COMMITTER_IDENT | sed 's/\b\([0-9]\{8,\}[ ]\+[+-][0-9]\+ *\)$/\n\1/' | { read author; read time; echo -n " $author" ; echo $time | gawk '{ print strftime (" %F %T %z", $1) }' }) # construct commit message echo > "$GIT_DIR/commit-editmsg.txt" || exit $? # abort if not writable cat >> "$GIT_DIR/commit-editmsg.txt" <<-_EOF_HERE #YY: ---------------------------------------------------------------------- #YY: Files to be committed (detected by "#YY:F"): #YY: _EOF_HERE list_committable "$@" | sed 's/^/#YY:F /' >> "$GIT_DIR/commit-editmsg.txt" cat >> "$GIT_DIR/commit-editmsg.txt" <<-_EOF_HERE #YY: #YY: Author: #YY: $AIDENT #YY: Committer: #YY: $CIDENT #YY: _EOF_HERE yyinfo | sed 's/^/#YY: /' >> "$GIT_DIR/commit-editmsg.txt" cat >> "$GIT_DIR/commit-editmsg.txt" <<-_EOF_HERE #YY: #YY: (The "#YY:" prefixed lines are ignored for commit messages) _EOF_HERE # edit commit message grep -q '^#YY:F ' "$GIT_DIR/commit-editmsg.txt" || exit_err 0 "** Nothing to commit." ${VISUAL:-${EDITOR:-vi}} "$GIT_DIR/commit-editmsg.txt" || exit_err 3 "** Aborting commit, editing commit message failed: $GIT_DIR/commit-editmsg.txt" # verify user's commit message grep -v -i '^\(Signed-off-by\|#YY\):' < "$GIT_DIR/commit-editmsg.txt" > "$GIT_DIR/commit-msg.txt" || exit $? $verify_msg && test -x "$GIT_DIR/hooks/commit-msg" && { "$GIT_DIR/hooks/commit-msg" "$GIT_DIR/commit-msg.txt" || exit $? ; } mlines=`git-stripspace < "$GIT_DIR/commit-msg.txt" | wc -l` test 0 -lt "$mlines" || exit_err 3 "** Aborting commit, missing commit message..." # save current index around commit (since GIT_INDEX_FILE=tmpindex git-commit is buggy) TMPINDEX=`mktemp "$GIT_DIR/.precommittindex$$.XXXXXX"` && cp -p "$GIT_DIR/index" "$TMPINDEX" || exit_err 9 "$0: failed to create temporary file" trap 'mv -f "$TMPINDEX" "$GIT_DIR/index"' 0 HUP INT QUIT TRAP USR1 PIPE TERM # unstage everything in index git-read-tree HEAD # stage files from commit message grep "^#YY:F " "$GIT_DIR/commit-editmsg.txt" | # filter #YY:F sed -e "s/^#YY:F \+//" | { # extract file names while IFS=" " read -r mode file ; do case "$mode" in D) git-update-index --force-remove -- "$file" || exit_err 5 "** Aborting commit, removing failed: $file" ;; A|M) git-update-index --add -- "$file" || exit_err 6 "** Aborting commit, adding failed: $file" ;; n) ;; # commitignore '!') ;; # missing file *) exit_err 7 "** Aborting commit, unknown file mode: $mode $file" ;; esac done } rm -f "$GIT_DIR/commit-editmsg.txt" # abort for empty committs git-diff-index --cached HEAD | grep -q '.' || exit_err 0 "** Nothing to commit." # actually commit changes git-commit -F "$GIT_DIR/commit-msg.txt" ; ccode=$? # cleanup, restore old index and force update on comitted files test 0 = $ccode && rm -f "$GIT_DIR/commit-msg.txt" "$GIT_DIR/commit-editmsg.txt" mv -f "$TMPINDEX" "$GIT_DIR/index" trap - 0 HUP INT QUIT TRAP USR1 PIPE TERM git-update-index --refresh --again > /dev/null # update stat info # handle auto pushing test 0 = $ccode -a "true" = "`git-config --bool yyhelp.auto-push-commits`" && yypushpull exit $ccode ;; */yydiff) args=`getopt -n "$0" -o r: -- "$@"`; [ $? = 0 ] || exec yyhelp; eval set -- "$args" rev=HEAD while :; do case "$1" in -r) rev="$2"; shift 2; git-rev-parse --verify "$rev" >/dev/null 2>&1 || exit_err 3 "$0: unknown revision: $rev" ;; --) shift; break ;; esac; done git-update-index --refresh > /dev/null # 'git-diff-index -m' shows uncommittable files git-diff-index -r -C -p $COLOR "$rev" -- "$@" | $OUT ;; */yygc) test -n "$*" && exec yyhelp exec git-gc --prune ;; */yyHistoryGrep) REV= ; test "x$1" = "x-r" && { shift; REV=--reverse ; } test "$#" = 1 || exec yyhelp # create temporary file TMAP=`mktemp -t yyTMAP.$$XXXXXX` && touch $TMAP || exit_err 9 "$0: failed to create temporary file" trap "rm -f $TMAP" 0 HUP INT QUIT TRAP USR1 PIPE TERM # create sed mapping from tree hashes to commit hashes git-rev-list --all --pretty=format:'/^%t/s/^%T:/%H:/' | grep -v ^commit >$TMAP # grep trees in chronological order and convert hashes on the fly git-rev-list --all $REV --pretty=format:%T | grep -v ^commit | xargs git-grep -E -e $1 | sed -f $TMAP | $OUT ;; */yyinfo) test -n "$*" && exec yyhelp REPO="`cd \"$gitdir/..\" && basename \"$(pwd)\" `" REPODIR="`git-rev-parse --show-cdup`" ; test -z "$REPODIR" && REPODIR=. OC="`git-rev-parse --verify origin 2>/dev/null`"; test -n "$OC" && OC="`date -d \"$(git-log -n1 --pretty=format:%cD $OC)\" '+%F %T %z'` # $OC" HC="`git-rev-parse --verify HEAD 2>/dev/null`"; test -n "$HC" && HC="`date -d \"$(git-log -n1 --pretty=format:%cD $HC)\" '+%F %T %z'` # $HC" test -n "$SVNURL" && SVNREV=`git-cat-file commit HEAD | tail -n1 | sed -n '/^git-svn-id:.*/ { s/^[^@]\+@\([0-9]\+\).*/\1/; p; }'` true && echo "GIT-Repo: $REPO" true && echo "URL: $GITURL" true && echo "Path: $REPODIR" $gitsvn && echo "Method: git-svn commands are used for push and pull" $gitsvn || echo "Method: git-push and git-pull are used for updates" test -n "$SVNURL" && echo "SVN-URL: $SVNURL" test -n "$SVNREV" && echo "SVN-Rev: $SVNREV (HEAD)" test -n "$HC" && echo "HEAD: $HC" test -n "$OC" && echo "Origin: $OC" ;; */yylsbranches) test -n "$*" && exec yyhelp exec git-branch $COLOR -a ;; */yylstags) test -n "$*" && exec yyhelp exec git-tag -l '.*' ;; */yypull) test -n "$*" && exec yyhelp $gitsvn && exec git-svn rebase || exec git-pull ;; */yypushpull) test -n "$*" && exec yyhelp $gitsvn && exec git-svn dcommit git-push && git-pull || exit $? ;; */yyremove) exec git-update-index --force-remove -- "$@" ;; */yyreset) test -n "$*" && exec yyhelp exec git-checkout -f HEAD ;; */yyrestore) git-ls-tree --full-name -r HEAD -- "$@" | git-update-index --index-info # resurrect deleted exec git-checkout-index -f -u -- "$@" ;; */yystatus) test -z "$*" || match '-[tuc]' "$*" || exec yyhelp git-update-index --refresh > /dev/null # update stat info ! match '*-[tc]*' "$*" && # list unknown files git-ls-files --others --directory --exclude-from=$gitdir/info/exclude --exclude-per-directory=.gitignore | { test " $*" != " -u" && sed 's,^,? ,' || sed 's,/$,,' ; } test " $*" != " -u" && { # list non-unknown files if test -z "$githead" ; then # empty history, list new files git-ls-files | sed 's/^/A /' else # list known files, path-relative list_committable fi } | { test " $*" != " -c" && cat || sed 's/^[^ ] \([^ ].*\)/* \1:/' ; } ;; */yytag) (set -e && test -n "$*" && test -z "$3" && ! match "* -*" " $*" ) || exec yyhelp test -n "$2" && exec git-tag "$1" "$2" exec git-tag "$1" ;; */yytagdel) test -z "$*" && exec yyhelp match "* -*" " $*" && exec yyhelp exec git-tag -d "$1" ;; */yyuncommit) test -n "$*" && exec yyhelp exec git-reset --soft HEAD~1 ;; */yyview) test -n "$*" && exec yyhelp exec gitk --all -d & ;; */yywarp) test -z "$*" && exec yyhelp git-rev-parse --verify "$1" >/dev/null 2>&1 || exit_err 3 "$0: unknown revision: $1" exec git-checkout "$1" ;; *) test "$YYHELP" || { echo "`basename \"$0\"`: No such command" >&2 ; exit 9 ; } ;; esac test "$YYHELP" || exit 0 # successful command execution YYHELP_ALIASES="yyadd yyblame yybranch yybranchdel yyChangeLog yycommit yydiff yygc yyHistoryGrep yyinfo yylsbranches yylstags yypull yypushpull yyremove yyreset yyrestore yystatus yytag yytagdel yyuncommit yyview yywarp" test "x$1" = "x--install-aliases" && { git --version | egrep '^git version (1\.[5-9]\.|[2-9]\.)' -q || { echo "$0: failed to install: missing git >= 1.5.0" >&2 ; exit 2 ; } test -x ./yyhelp || { echo "$0: failed to install: missing ./yyhelp" >&2 ; exit 2 ; } test -x /bin/bash || { echo "$0: failed to install: missing /bin/bash" >&2 ; exit 2 ; } gawk --version 2>/dev/null | fgrep -q "GNU Awk" || { echo "$0: failed to install: missing GNU awk (/usr/bin/gawk)" >&2 ; exit 2 ; } set -e for i in $YYHELP_ALIASES ; do test -L $i || ln -vs yyhelp $i done exit 0 } test "x$1" = "x--uninstall-aliases" && { set -e for i in $YYHELP_ALIASES ; do test -L $i && rm -vf $i done exit 0 } { # yyhelp echo -e 'YummyYummySourceControl YummyYummySourceControl' echo # 0\t911234567892123456789312345678941234567895123456789612345678971234567898 $SETBOLD echo -e 'NAME' $SETNORM echo -e '\tYummyYummySourceControl - Simple and convenient Git wrapper' echo $SETBOLD echo -e 'SYNOPSIS' $SETNORM echo -e '\tyyadd [FILES...] - add files to git repository' echo -e '\tyyblame [FILES...] - annotate file source code' echo -e '\tyybranch [-f] - add branch (forcable)' echo -e '\tyybranchdel [-f] - delete branch (forcable)' echo -e '\tyyChangeLog [-s] - show git log in ChangeLog style' echo -e '\t -s: skip committed SVN revisions' echo -e '\tyycommit [FILES...] - commit current working tree' echo -e '\tyydiff [-rREV] [FILES...] - show committable differences in' echo -e '\t working tree; given revision REV' echo -e '\tyygc - repack and prune repository' echo -e '\tyyhelp - display yy* help information' echo -e '\tyyHistoryGrep [-r] - grep commit history for POSIX extended' echo -e '\t regular expression pattern PAT' echo -e '\t -r: reverse order, search chronologically' echo -e '\tyyinfo - display repository information' echo -e '\tyylsbranches - list branches' echo -e '\tyylstags - list tags' echo -e '\tyypull - pull upstream sources' echo -e '\tyypushpull - push & pull upstream sources' echo -e '\tyyremove [FILES...] - remove files from git repository' echo -e '\tyyreset - reset (revert to HEAD) all files in the tree' echo -e '\tyyrestore [FILES...] - forcefully recheckout specific files' echo -e '\tyystatus [-t|-u|-c] - display working tree status' echo -e '\t -t: trim unknown; -u: show unknown;' echo -e '\t -c: trim and use commit message style' echo -e '\tyytag [revision] - add tag' echo -e '\tyytagdel - delete tag' echo -e '\tyyuncommit - undo the last commit (must be unpushed)' echo -e '\tyyview - start view to browse & navigate the history' echo -e '\tyywarp - checkout new branch' echo $SETBOLD echo -e 'DESCRIPTION' $SETNORM echo -e '\tYummyYummySourceControl is a shallow wrapper around the myriads of' echo -e '\tcommands and options offered by the Git(7) revision control system.' echo -e '\tThe scope of this wrapper is confined to a farily basic set of actions' echo -e '\taround source control management, such as adding/removing/modifying' echo -e '\tfiles and the simplest forms of tag and branch handling.' echo -e '\tIf git-svn(1) is used to upgrade the repository, the git-svn commands' echo -e '\trebase and dcommit will be used to push and pull respectively.' echo -e '\tThe yyinfo command indicates git-svn repositories.' echo $SETBOLD echo -e 'CONFIGURATION' $SETNORM echo -e "\tThe yycommit command can be configured to automatically issue" echo -e "\tyypushpull after successful commits with the following option:" echo -e "\t\tgit-config yyhelp.auto-push-commits true" echo $SETBOLD echo -e 'INSTALLATION' $SETNORM echo -e '\tTo install YummyYummySourceControl, copy yyhelp into a bin/ directory' echo -e '\tfrom $PATH and invoke: ./yyhelp --install-aliases' echo $SETBOLD echo -e 'HISTORY' $SETNORM echo -e "\tYummyYummySourceControl was created as a very shallow porcelain script" echo -e "\taround git(7) tool option variants, to simplify common use cases." echo -e "\tDepending on programming habits, YummyYummySourceControl may or may" echo -e "\tnot suit a developers daily needs. It is in no case meant as a full" echo -e "\treplacement for the git interface (there is e.g. no yyclone)." echo -e "\tThe prefix 'yy' was choosen to allow conflict free shell completion." echo VERSIONSTRING___________FIXED=`printf '%-30s' "$VERSION"` echo -e "$VERSIONSTRING___________FIXED YummyYummySourceControl" } | $OUT exit 0