#!/bin/bash #------------------------------------------------------------------------------# # vi: set sw=4 ts=4 ai: ("set modeline" in ~/.exrc) # #------------------------------------------------------------------------------# # Program : git.vi # # # # Author : Ton Kersten Groesbeek, The Netherlands # # # # Date : 23-01-2009 Time : 12:18 # # # # Description : Program to edit files and commit them to git # # # # Parameters : The files # # -m # # -e -> Do not start the Editor # # # # Pre reqs : Git should be installed, working and in your path # # # # Exit codes : 0 -> OK # # <> 0 -> !OK # #------------------------------------------------------------------------------# #------------------------------------------------------------------------------# # V e r s i o n i n f o r m a t i o n # #------------------------------------------------------------------------------# # $Id:: git.vi 56 2011-09-21 14:58:02 tonk $: # # $Revision:: 56 $: # # $Author:: Ton Kersten $: # # $Date:: 2011-09-21 14:59:20 +0200 (Wed, 21 Sep 2011) $: # # $Hash:: $: # #------------------------------------------------------------------------------# # E n d o f v e r s i o n i n f o r m a t i o n # #------------------------------------------------------------------------------# #------------------------------------------------------------------------------# # Configuration # #------------------------------------------------------------------------------# # use this maildomain if no info could be found maildomain=example.com #------------------------------------------------------------------------------# # Determine the program name and the 'running directory' # #------------------------------------------------------------------------------# IAM="${0##*/}" CRD="$( [[ "${0:0:2}" = "./" ]] && { printf "${PWD}/${0#./}" } || { printf "${0}" })" CRD="${CRD%/*}" CUR="${PWD}" #------------------------------------------------------------------------------# # Save the shell settings # #------------------------------------------------------------------------------# SETA=0; [[ ${-} = *a* ]] && SETA=1 SETE=0; [[ ${-} = *e* ]] && SETE=1 SETU=0; [[ ${-} = *u* ]] && SETU=1 SETX=0; [[ ${-} = *x* ]] && SETX=1 #------------------------------------------------------------------------------# # Set and unset the needed shell settings # #------------------------------------------------------------------------------# set +o noclobber # Overwrite existing files, if needed # set -o nounset # Dont allow uninitialized variables # set +o errexit # No returncode checking # #============================== Function "Usage" ==============================# Usage() { #+-------------------------------------------------------------------------# # Function : Usage # # # # Description : Display the syntax of this program # # # # Parameters : None # # # # Returns : Exits with RC = 1 # #-------------------------------------------------------------------------+# T=" " cat <<- @EOF vi and git combiner editor Usage: ${IAM} [-h] [-a] [-m "message"] [-e] file ... Options: ${T}-h Display this message ${T}-a All files will be committed with the same commit message. ${T} This message is the first file to be edited. ${T}-m All files will be committed with the same commit message. ${T} This message should be supplied on the command line. ${T}-e Do not edit the file(s). ${T} This is very usefull for automatic checkins This program works best when used with git filters, such as the 'git-edit' filter. See my blog on how to use this (http://tonkersten.com) ${T}Examples: ${T}${T}${IAM} -m "Changed the version number" *.cfg ${T}or ${T}${T}${IAM} -a *.cfg If both the '-m' and '-a' option are given, the '-m' has precedence. @EOF exit 1 } #========================== End of function "Usage" ===========================# #============================== Function "IsNum" ==============================# IsNum() { #+-------------------------------------------------------------------------# # Function : IsNum # # # # Description : Check if a supplied parameters is numeric # # # # Parameters : $1 -> Parameter to check # # # # Returns : RC=0: Numeric. Otherwise not numeric # #-------------------------------------------------------------------------+# case $1 in "") return 1 ;; *[!0-9]*) return 1 ;; *) return 0 ;; esac } #========================== End of function "IsNum" ===========================# #------------------------------------------------------------------------------# # No git, no glory # #------------------------------------------------------------------------------# [[ x"$(which git 2>/dev/null)" = x"" ]] && { echo "No 'git' found. Please use plain vi(m)" exit 1 } spc="$(printf "%80s" "")" #------------------------------------------------------------------------------# # Who are we # #------------------------------------------------------------------------------# who=${SUDO_USER:-${LOGNAME}} #------------------------------------------------------------------------------# # Set the TEMP dir # #------------------------------------------------------------------------------# export TMP=${TMPDIR:-/tmp} #------------------------------------------------------------------------------# # Try to get the user info from git # #------------------------------------------------------------------------------# host=$(hostname -s) if [[ -f ${HOME}/.gitconfig ]] then full=$( git config --get user.name) email=$(git config --get user.email) host=$(git config --get user.hostname) [[ x"${host}" = x"" ]] && host=$(hostname -s) fi #------------------------------------------------------------------------------# # Try to get the user info from environment variables # #------------------------------------------------------------------------------# if [[ "${GIT_AUTHOR_NAME:?}" ]] && [[ "${GIT_AUTHOR_EMAIL:?}" ]] then full="$GIT_AUTHOR_NAME" email="$GIT_AUTHOR_EMAIL" fi #------------------------------------------------------------------------------# # If no info was found, make up your own # #------------------------------------------------------------------------------# if [[ x"${full:-}" = x"" ]] then full=$(getent passwd ${who} | awk -F: '{ gsub(/,*/, ""); print $5 }') email="${who}@${maildomain}" fi author="${full} <${email}>" #------------------------------------------------------------------------------# # Get the commandline options # #------------------------------------------------------------------------------# options=":haem:" commitall=0 cmtmsg="" editit=1 while getopts ${options} opts do case "${opts}" in a) #------------------------------------------------------------------# # Commit all files with the same commit message (With editor) # #------------------------------------------------------------------# commitall=1 ;; m) #------------------------------------------------------------------# # Commit all files with the same commit message (Without editor) # #------------------------------------------------------------------# cmtmsg="${OPTARG}" ;; e) #------------------------------------------------------------------# # Edit it, or just continue # #------------------------------------------------------------------# editit=0 ;; h) #------------------------------------------------------------------# # Display help # #------------------------------------------------------------------# Usage exit ${OK} ;; *) #------------------------------------------------------------------# # Display the usage # #------------------------------------------------------------------# Usage exit 1 esac done [[ x"${cmtmsg}" != x"" ]] && commitall=0 #------------------------------------------------------------------------------# # Shift away processed options # #------------------------------------------------------------------------------# shift $(( ${OPTIND} - 1 )) #------------------------------------------------------------------------------# # Do we have files on the command line? If not, it's no use # #------------------------------------------------------------------------------# [[ x"${@:-}" = x"" ]] && Usage #------------------------------------------------------------------------------# # Set the editor # #------------------------------------------------------------------------------# if [[ x"${EDITOR:-}" = x"" ]] then EDIT="$(type -p vi 2>/dev/null)" if [[ ${?} = 0 ]] then EDITOR="${EDIT//* }" elif [[ -x /bin/vi ]] then EDITOR="/bin/vi" elif [[ -x /usr/bin/vi ]] then EDITOR="/usr/bin/vi" elif [[ -x /usr/bin/vi ]] then EDITOR="/usr/local/bin/vi" fi fi #------------------------------------------------------------------------------# # If all files should have the same commitmessage, create it now # #------------------------------------------------------------------------------# commitfile="${TMP}/${IAM}_commit.${$}" if [[ ${commitall} = 1 ]] then cat <<- @EOF > ${commitfile} # This file is the general commit file for ${IAM} # Please type the general commit text for all files. # # Comment lines starting with '#' will not be included. # # Affected files: ${@} @EOF ${EDITOR} ${commitfile} fi #------------------------------------------------------------------------------# # Do we have a umask configured. If so, set it # #------------------------------------------------------------------------------# gitowner=$(git config gitvi.owner) gitgroup=$(git config gitvi.group) gitumask=$(git config gitvi.umask) gitlang=$( git config gitvi.lang) [[ x"${gitowner}" = x"" ]] && gitowner="${USER}" [[ x"${gitgroup}" = x"" ]] && gitgroup="$(id -g)" [[ x"${gitumask}" = x"" ]] && gitumask="$(umask)" [[ x"${gitlang}" = x"" ]] && gitlang="en" umask ${gitumask} #------------------------------------------------------------------------------# # Proces all files # #------------------------------------------------------------------------------# case "${gitlang}" in en) automsg="Automatic checkin by '${IAM}'" ;; nl) automsg="Automatische checkin door '${IAM}'" ;; *) automsg="Automatic checkin by '${IAM}'" ;; esac for file in "${@}" do dir="$(dirname "${file}")" base="$(basename "${file}")" [[ ! -d "${dir}" ]] && { echo "Cannot change to a non existing directory" >&2 cd - >/dev/null 2>&1 continue } [[ -d "${file}" ]] && { echo "Cannot edit a directory" >&2 cd - >/dev/null 2>&1 continue } cd "${dir}" #--------------------------------------------------------------------------# # Find the top of the git tree # #--------------------------------------------------------------------------# GIT_TOP="$(git rev-parse --git-dir 2>/dev/null | sed 's/\.git$//')" #--------------------------------------------------------------------------# # Check if this directory is part of a git tree. If not, create one # #--------------------------------------------------------------------------# if [[ x"${GIT_TOP}" = x"" ]] then #----------------------------------------------------------------------# # Make a new one in ${PWD} # #----------------------------------------------------------------------# git init || exit 1 GIT_TOP="$(pwd)" fi #--------------------------------------------------------------------------# # Get the rights (if possible) # #--------------------------------------------------------------------------# if [[ -f "${base}" ]] then rights=$(stat -c "%a" "${base}") chmod +w "${base}" 2>/dev/null else rights=$(( 666 - ${gitumask} )) fi #--------------------------------------------------------------------------# # Create a nice filename # #--------------------------------------------------------------------------# GIT_FNAME="$(echo "$(pwd)/${base}" | sed -e "s!^${GIT_TOP}!.../!" -e "s!//!/!g")" GIT_FNAME="${host}:${GIT_FNAME}" #--------------------------------------------------------------------------# # Determine the filetype and comment length # #--------------------------------------------------------------------------# GIT_LEN=66 case ${base} in *.c) GIT_LEN=64 ;; *.h) GIT_LEN=64 ;; *.go) GIT_LEN=64 ;; *.js) GIT_LEN=64 ;; *.pas) GIT_LEN=64 ;; *.php) GIT_LEN=64 ;; *.css) GIT_LEN=64 ;; *.html) GIT_LEN=60 ;; esac export GIT_TOP GIT_LEN GIT_FNAME #--------------------------------------------------------------------------# # Edit the file # #--------------------------------------------------------------------------# if [[ ${editit} = 1 ]] then ${EDITOR} "${base}" fi #----------------------------------------------------------------------# # Did we put something usefull in it # #----------------------------------------------------------------------# [[ ! -s "${base}" ]] && exit 0 #----------------------------------------------------------------------# # Check if the file is already in git # #----------------------------------------------------------------------# initial=0 ingit=$(git ls-files ${base} | wc -l) [[ ${ingit} = 0 ]] && { git add "${base}" git commit --author "${author}" -m "${automsg}" "${base}" initial=1 } #----------------------------------------------------------------------# # If we didn't make any changes, the repo and this one are the same # #----------------------------------------------------------------------# diff=$(git diff "${base}" 2>&1) [[ x"${diff}" = x"" ]] && { if [[ ${initial} = 0 ]] then cd - >/dev/null 2>&1 continue fi } rm -f "${TMP}/${base}.bck" #----------------------------------------------------------------------# # Give the file a new version number (while we are at it) # #----------------------------------------------------------------------# if [[ ( $(grep -c "[[:space:]]*\$[I]d::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[R]evision::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[A]uthor::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[D]ate::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[F]name::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[U]RL::.*\$:" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[I]d:[^:].*\$" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[R]evision:[^:].*\$" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[A]uthor:[^:].*\$" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[D]ate:[^:].*\$" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[F]name:[^:].*\$" "${base}") != 0 ) || ( $(grep -c "[[:space:]]*\$[U]RL:[^:].*\$" "${base}") != 0 ) ]] then datim=$(stat -c"%y" "${base}" | cut -d '.' -f1) newrev="$(awk '/[[:space:]]*\$[R]evision::/ { print $3; exit }' "${base}")" if [[ ( x"${newrev}" = x"" ) || ( x"${newrev}" = x"\$:" ) ]] then # Revision tag not found. Maybe Id will help newrev="$(awk '/[[:space:]]*\$[I]d:.*\$/ { print $4; exit }' "${base}")" fi [[ x"${newrev}" = x"\$:" || x"${newrev}" = x"#" || x"${newrev}" = x"" ]] && newrev=0 IsNum "${newrev}" || newrev=0 newrev="$(( ${newrev} + 1 ))" id="${base##*/} ${newrev} ${datim} ${who}" f="${base##*/} ${newrev} ${datim} ${who} ${spc}" newrevns="${newrev}" newrev="${newrev}${spc}" nam="${author}${spc}" namns="${author}" GIT_SHORT="${GIT_FNAME}" GIT_FNAME="${GIT_FNAME}${spc}" now="$(date '+%F %T %z (%a, %d %b %Y)') ${spc}" nowns="$(date '+%F %T %z (%a, %d %b %Y)')" #------------------------------------------------------------------# # Prepare the header strings # #------------------------------------------------------------------# L0=$(( ${GIT_LEN} + 0 )) L1=$(( ${GIT_LEN} + 2 )) L2=$(( ${GIT_LEN} - 2 )) L3=$(( ${GIT_LEN} - 4 )) L4=$(( ${GIT_LEN} - 1 )) cp "${base}" "${TMP}/${base}.bck" sed -e 's!\([[:space:]]*\$[I]d::\).*\$:!\1 '"${f:0:${L1}}"'\$:!' \ -e 's!\([[:space:]]*\$[R]evision::\).*\$:!\1 '"${newrev:0:${L3}}"'\$:!' \ -e 's!\([[:space:]]*\$[A]uthor::\).*\$:!\1 '"${nam:0:${L2}}"'\$:!' \ -e 's!\([[:space:]]*\$[D]ate::\).*\$:!\1 '"${now:0:${L0}}"'\$:!' \ -e 's!\([[:space:]]*\$[F]name::\).*\$:!\1 '"${GIT_FNAME:0:${L4}}"'\$:!' \ -e 's!\([[:space:]]*\$[U]RL::\).*\$:!\1 '"${GIT_FNAME:0:${L4}}"'\$:!' \ -e 's!\([[:space:]]*\$[I]d:[^:]\).*\$!\1'"${id}"' \$!' \ -e 's!\([[:space:]]*\$[R]evision:[^:]\).*\$!\1'"${newrevns}"' \$!' \ -e 's!\([[:space:]]*\$[A]uthor:[^:]\).*\$!\1'"${namns}"' \$!' \ -e 's!\([[:space:]]*\$[D]ate:[^:]\).*\$!\1'"${nowns}"' \$!' \ -e 's!\([[:space:]]*\$[F]name:[^:]\).*\$!\1'"${GIT_SHORT//* }"' \$!' \ -e 's!\([[:space:]]*\$[U]RL:[^:]\).*\$!\1'"${GIT_SHORT//* }"' \$!' \ "${TMP}/${base}.bck" > "${base}" rm -f "${TMP}/${base}.bck" fi #----------------------------------------------------------------------# # Commit the file to git # #----------------------------------------------------------------------# if [[ ${commitall} = 1 ]] then cl=$(grep -v '^[[:space:]]*#' ${commitfile} | grep -v '^[[:space:]]*$' | wc -l) [[ ${cl} = 0 ]] && { echo "Cannot continue with an empty commit message!" >&2 cd - >/dev/null 2>&1 continue } grep -v '^[[:space:]]*#' ${commitfile} | \ git commit -F - --author "${author}" "${base}" else if [[ x"${cmtmsg}" = x"" ]] then cat <<- @EOF > ${commitfile} # Please enter the commit message for your changes. Lines starting # with '#' will be ignored, and an empty message aborts the commit. # # Changes to be committed: # (use "git rm --cached ..." to unstage) # # Affected files: ${@} # @EOF ${EDITOR} ${commitfile} cl=$(grep -v '^[[:space:]]*#' ${commitfile} | grep -v '^[[:space:]]*$' | wc -l) [[ ${cl} = 0 ]] && { echo "Cannot continue with an empty commit message!" >&2 cd - >/dev/null 2>&1 continue } grep -v '^[[:space:]]*#' ${commitfile} | \ git commit -F - --author "${author}" "${base}" else git commit --author "${author}" -m "${cmtmsg}" "${base}" fi fi #----------------------------------------------------------------------# # And check it out again to apply the smudge filters # #----------------------------------------------------------------------# mv "${base}" "${TMP}/${base}.bck" git checkout "${base}" [[ -f "${base}" ]] && rm -f "${TMP}/${base}.bck" || mv "${TMP}/${base}.bck" "${base}" chown ${gitowner} "${base}" > /dev/null 2>&1 chgrp ${gitgroup} "${base}" > /dev/null 2>&1 chmod ${rights} "${base}" > /dev/null 2>&1 cd - >/dev/null 2>&1 done [[ -f ${commitfile} ]] && rm -f ${commitfile}