# ------------------------------------------------------------------------------ # SCM Breeze - Streamline your SCM workflow. # Copyright 2011 Nathan Broadbent (http://madebynathan.com). All Rights Reserved. # Released under the LGPL (GNU Lesser General Public License) # ------------------------------------------------------------------------------ # # bash/zsh 'git_status_shortcuts' implementation, in case Ruby is not installed. # Of course, I wrote this function first, and then rewrote it in Ruby. # # Processes 'git status --porcelain', and exports numbered # env variables that contain the path of each affected file. # Output is also more concise than standard 'git status'. # # Call with optional parameter to just show one modification state # # groups => 1: staged, 2: unmerged, 3: unstaged, 4: untracked # -------------------------------------------------------------------- git_status_shortcuts() { zsh_compat # Ensure shwordsplit is on for zsh IFS=$'\n' local git_status="$(git status --porcelain 2> /dev/null)" local i if [ -n "$git_status" ] && [[ $(echo "$git_status" | wc -l) -le $gs_max_changes ]]; then unset stat_file; unset stat_col; unset stat_msg; unset stat_grp; unset stat_x; unset stat_y # Clear numbered env variables. for (( i=1; i<=$gs_max_changes; i++ )); do unset $git_env_char$i; done # Get branch local branch=`git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/'` # Get project root if [ -d .git ]; then local project_root="$PWD" else local project_root=$(git rev-parse --git-dir 2> /dev/null | sed "s%/\.git$%%g") fi # Colors local c_rst="\e[0m" local c_branch="\e[1m" local c_header="\e[0m" local c_dark="\e[2;37m" local c_del="\e[0;31m" local c_mod="\e[0;32m" local c_new="\e[0;33m" local c_ren="\e[0;34m" local c_cpy="\e[0;33m" local c_ign="\e[0;36m" # Following colors must be prepended with modifiers e.g. '\e[1;', '\e[0;' local c_grp_1="33m"; local c_grp_2="31m"; local c_grp_3="32m"; local c_grp_4="36m" local f=1; local e=1 # Counters for number of files, and ENV variables echo -e "$c_dark#$c_rst On branch: $c_branch$branch$c_rst $c_dark| [$c_rst*$c_dark]$c_rst => \$$git_env_char*\n$c_dark#$c_rst" for line in $git_status; do if [[ $shell == *bash ]]; then x=${line:0:1}; y=${line:1:1}; file=${line:3} else x=$line[1]; y=$line[2]; file=$line[4,-1] fi # Index modification states msg="" case "$x$y" in "DD") msg=" both deleted"; col="$c_del"; grp="2";; "AU") msg=" added by us"; col="$c_new"; grp="2";; "UD") msg="deleted by them"; col="$c_del"; grp="2";; "UA") msg=" added by them"; col="$c_new"; grp="2";; "DU") msg=" deleted by us"; col="$c_del"; grp="2";; "AA") msg=" both added"; col="$c_new"; grp="2";; "UU") msg=" both modified"; col="$c_mod"; grp="2";; "M"?) msg=" modified"; col="$c_mod"; grp="1";; "A"?) msg=" new file"; col="$c_new"; grp="1";; "D"?) msg=" deleted"; col="$c_del"; grp="1";; "R"?) msg=" renamed"; col="$c_ren"; grp="1";; "C"?) msg=" copied"; col="$c_cpy"; grp="1";; "??") msg="untracked"; col="$c_ign"; grp="4";; esac if [ -n "$msg" ]; then # Store data at array index and add to group stat_file[$f]=$file; stat_msg[$f]=$msg; stat_col[$f]=$col stat_grp[$grp]="${stat_grp[$grp]} $f" let f++ fi # Work tree modification states msg="" if [[ "$y" == "M" ]]; then msg=" modified"; col="$c_mod"; grp="3"; fi # Don't show {Y} as deleted during a merge conflict. if [[ "$y" == "D" && "$x" != "D" && "$x" != "U" ]]; then msg=" deleted"; col="$c_del"; grp="3"; fi if [ -n "$msg" ]; then stat_file[$f]=$file; stat_msg[$f]=$msg; stat_col[$f]=$col stat_grp[$grp]="${stat_grp[$grp]} $f" let f++ fi done IFS=" " grp_num=1 for heading in 'Changes to be committed' 'Unmerged paths' 'Changes not staged for commit' 'Untracked files'; do # If no group specified as param, or specified group is current group if [ -z "$1" ] || [[ "$1" == "$grp_num" ]]; then local c_arrow="\e[1;$(eval echo \$c_grp_$grp_num)" local c_hash="\e[0;$(eval echo \$c_grp_$grp_num)" if [ -n "${stat_grp[$grp_num]}" ]; then echo -e "$c_arrow➤$c_header $heading\n$c_hash#$c_rst" _gs_output_file_group $grp_num fi fi let grp_num++ done else # This function will slow down if there are too many changed files, # so just use plain 'git status' git status fi IFS=$' \t\n' zsh_reset # Reset zsh environment to default } # Template function for 'git_status_shortcuts'. _gs_output_file_group() { for i in ${stat_grp[$1]}; do # Print colored hashes & files based on modification groups local c_group="\e[0;$(eval echo -e \$c_grp_$1)" # Deduce relative path based on current working directory if [ -z "$project_root" ]; then relative="${stat_file[$i]}" else dest="$project_root/${stat_file[$i]}" local pwd=$(readlink -f "$PWD") relative="$(_gs_relative_path "$pwd" "$dest" )" fi if [[ $f -gt 10 && $e -lt 10 ]]; then local pad=" "; else local pad=""; fi # (padding) echo -e "$c_hash#$c_rst ${stat_col[$i]}${stat_msg[$i]}:\ $pad$c_dark [$c_rst$e$c_dark] $c_group$relative$c_rst" # Export numbered variables in the order they are displayed. # (Exports full path, but displays relative path) # fetch first file (in the case of oldFile -> newFile) and remove quotes local filename=$(eval echo $(echo ${stat_file[$i]} | egrep -o '^"([^\\"]*(\\.[^"]*)*)"|^[^ ]+')) export $git_env_char$e="$project_root/$filename" let e++ done echo -e "$c_hash#$c_rst" } # Show relative path if current directory is not project root _gs_relative_path(){ # Credit to 'pini' for the following script. # (http://stackoverflow.com/questions/2564634/bash-convert-absolute-path-into-relative-path-given-a-current-directory) target=$2; common_part=$1; back="" while [[ "${target#$common_part}" == "${target}" ]]; do common_part="${common_part%/*}" back="../${back}" done echo "${back}${target#$common_part/}" }