Files
scm_breeze/lib/git/fallback/status_shortcuts_shell.sh
2011-11-09 20:11:24 -08:00

156 lines
6.1 KiB
Bash

# ------------------------------------------------------------------------------
# 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 <group> 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
local IFS=$'\n'
local git_status="$(git status --porcelain 2> /dev/null)"
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
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]}"
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)
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/}"
}