Initial Commit - moved out of ubuntu_config.

This commit is contained in:
Nathan Broadbent
2011-10-18 00:18:17 +08:00
commit e634d0c4b5
24 changed files with 2592 additions and 0 deletions

8
lib/git/_shared.sh Normal file
View File

@@ -0,0 +1,8 @@
# Detect shell
if [ -n "${ZSH_VERSION:-}" ]; then shell="zsh"; else shell="bash"; fi
# Detect whether zsh 'shwordsplit' option is on by default.
if [[ $shell == "zsh" ]]; then zsh_shwordsplit=$((setopt | grep -q shwordsplit) && echo "true"); fi
# Switch on/off shwordsplit for functions that require it.
zsh_compat(){ if [[ $shell == "zsh" && -z $zsh_shwordsplit ]]; then setopt shwordsplit; fi; }
zsh_reset(){ if [[ $shell == "zsh" && -z $zsh_shwordsplit ]]; then unsetopt shwordsplit; fi; }

View File

@@ -0,0 +1,123 @@
#
# Set up configured aliases & keyboard shortcuts
# --------------------------------------------------------------------
# Git Breeze functions
alias $git_status_shortcuts_alias="git_status_shortcuts"
alias $git_add_shortcuts_alias="git_add_shorcuts"
alias $exec_git_expand_args_alias="exec_git_expand_args"
alias $git_show_files_alias="git_show_affected_files"
alias $git_commit_all_alias='git_commit_all'
# Expand numbers and ranges for commands that deal with paths
_exp="exec_git_expand_args"
alias $git_checkout_alias="$_exp git checkout"
alias $git_commit_alias="$_exp git commit"
alias $git_reset_alias="$_exp git reset"
alias $git_rm_alias="$_exp git rm"
alias $git_blame_alias="$_exp git blame"
alias $git_diff_alias="$_exp git diff"
alias $git_diff_cached_alias="$_exp git diff --cached"
# Standard commands
alias $git_clone_alias='git clone'
alias $git_fetch_alias='git fetch'
alias $git_fetch_and_rebase_alias='git fetch && git rebase'
alias $git_pull_alias='git pull'
alias $git_push_alias='git push'
alias $git_status_original_alias='git status' # (Standard git status)
alias $git_status_short_alias='git status -s'
alias $git_remote_alias='git remote -v'
alias $git_branch_alias='git branch'
alias $git_branch_all_alias='git branch -a'
alias $git_rebase_alias='git rebase'
alias $git_merge_alias='git merge'
alias $git_cherry_pick_alias='git cherry-pick'
alias $git_log_alias='git log'
alias $git_log_stat_alias='git log --stat --max-count=5'
alias $git_log_graph_alias='git log --graph --max-count=5'
alias $git_show_alias='git show'
alias $git_add_all_alias='git add -A'
alias $git_commit_amend_alias='git commit --amend'
# Add staged changes to latest commit without prompting for message
alias $git_commit_amend_no_msg_alias='git commit --amend -C HEAD'
# Git repo management alias
alias $git_repo_alias="git_repo" # The 's' stands for 'switch' or 'sourcecode'
# Tab completion for aliases
if [[ $shell == "zsh" ]]; then
# Turn on support for bash completion
autoload bashcompinit
bashcompinit
# -- zsh
compdef $git_alias=git
compdef _git $git_pull_alias=git-pull
compdef _git $git_push_alias=git-push
compdef _git $git_fetch_alias=git-fetch
compdef _git $git_fetch_and_rebase_alias=git-fetch
compdef _git $git_diff_alias=git-diff
compdef _git $git_commit_alias=git-commit
compdef _git $git_commit_all_alias=git-commit
compdef _git $git_checkout_alias=git-checkout
compdef _git $git_branch_alias=git-branch
compdef _git $git_branch_all_alias=git-branch
compdef _git $git_log_alias=git-log
compdef _git $git_log_stat_alias=git-log
compdef _git $git_log_graph_alias=git-log
compdef _git $git_add_shortcuts_alias=git-add
compdef _git $git_merge_alias=git-merge
else
# -- bash
complete -o default -o nospace -F _git $git_alias
complete -o default -o nospace -F _git_pull $git_pull_alias
complete -o default -o nospace -F _git_push $git_push_alias
complete -o default -o nospace -F _git_fetch $git_fetch_alias
complete -o default -o nospace -F _git_branch $git_branch_alias
complete -o default -o nospace -F _git_rebase $git_rebase_alias
complete -o default -o nospace -F _git_merge $git_merge_alias
complete -o default -o nospace -F _git_log $git_log_alias
complete -o default -o nospace -F _git_diff $git_diff_alias
complete -o default -o nospace -F _git_checkout $git_checkout_alias
complete -o default -o nospace -F _git_remote $git_remote_alias
complete -o default -o nospace -F _git_show $git_show_alias
fi
# Git repo management & aliases.
# If you know how to rewrite _git_repo_tab_completion() for zsh, please send me a pull request!
complete -o nospace -o filenames -F _git_repo_tab_completion git_repo
complete -o nospace -o filenames -F _git_repo_tab_completion $git_repo_alias
# Keyboard Bindings
# -----------------------------------------------------------
# 'git_commit_all' and 'git_add_and_commit' give commit message prompts.
# See [here](http://qntm.org/bash#sec1) for info about why I wanted a prompt.
# Cross-shell key bindings
_bind(){
if [[ $shell == "zsh" ]]; then
bindkey -s "$1" "$2" # zsh
else
bind "\"$1\": \"$2\"" # bash
fi
}
case "$TERM" in
xterm*|rxvt*)
# CTRL-SPACE => $ git_status_shortcuts {ENTER}
_bind "$git_status_shortcuts_keys" " git_status_shortcuts\n"
# CTRL-x-SPACE => $ git_commit_all {ENTER}
_bind "$git_commit_all_keys" " git_commit_all\n"
# CTRL-x-c => $ git_add_and_commit {ENTER}
# 1 3 CTRL-x-c => $ git_add_and_commit 1 3 {ENTER}
_bind "$git_add_and_commit_keys" "\e[1~ git_add_and_commit \n"
# Commands are prepended with a space so that they won't be added to history.
# Make sure this is turned on with:
# zsh: setopt histignorespace histignoredups
# bash: HISTCONTROL=ignorespace:ignoredups
esac

View File

@@ -0,0 +1,154 @@
# ------------------------------------------------------------------------------
# Git Breeze - Streamline your git 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)
export $git_env_char$e="$project_root/${stat_file[$i]}"
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/}"
}

229
lib/git/repo_management.sh Normal file
View File

@@ -0,0 +1,229 @@
# -------------------------------------------------------
# Git Breeze - Streamline your git workflow.
# Copyright 2011 Nathan Broadbent (http://madebynathan.com). All Rights Reserved.
# Released under the LGPL (GNU Lesser General Public License)
# -------------------------------------------------------
# -------------------------------------------------------
# Repository management scripts for Git projects
# -------------------------------------------------------
# * The `git_repo` function makes it easy to list & switch between
# git projects in $GIT_REPO_DIR (default = ~/src)
#
# * Change directory to any of your git repos or submodules, with recursive tab completion.
#
# * A repository index will be created at $GIT_REPO_DIR/.git_index
# (Scanning for git projects and submodules can take a few seconds.)
#
# * Cache can be rebuilt by running:
# $ git_repo --rebuild-index
# ('--' commands have tab completion too.)
#
# * Ignores projects within an 'archive' folder.
#
# * Allows you to run batch commands across all your repositories:
#
# - Update every repo from their remote: 'git_repo --update-all'
# - Produce a count of repos for each host: 'git_repo --count-by-host'
# - Run a custom command for each repo: 'git_repo --batch-cmd <command>'
#
# Examples:
#
# $ git_repo --list
# # => Lists all git projects
#
# $ git_repo ub[TAB]
# # => Provides tab completion for all project folders that begin with 'ub'
#
# $ git_repo ubuntu_config
# # => Changes directory to ubuntu_config, and auto-updates code from git remote.
#
# $ git_repo buntu_conf
# # => Same result as `git_repo ubuntu_config`
#
# $ git_repo
# # => cd $GIT_REPO_DIR
function git_repo() {
local IFS=$'\n'
if [ -z "$1" ]; then
# Just change to $GIT_REPO_DIR if no params given.
cd $GIT_REPO_DIR
else
if [ "$1" = "--rebuild-index" ]; then
_rebuild_git_repo_index
elif [ "$1" = "--update-all" ]; then
_git_repo_git_update_all
elif [ "$1" = "--batch-cmd" ]; then
_git_repo_git_batch_cmd "${@:2:$(($#-1))}" # Pass all args except $1
elif [ "$1" = "--list" ] || [ "$1" = "-l" ]; then
echo -e "$_bld_col$(_git_repo_count)$_txt_col Git repositories in $_bld_col$GIT_REPO_DIR$_txt_col:\n"
for repo in $(_git_repo_dirs_without_home); do
echo $(basename $repo) : $repo
done | sort | column -t -s ':'
elif [ "$1" = "--count-by-host" ]; then
echo -e "=== Producing a report of the number of repos per host...\n"
_git_repo_batch_cmd git remote -v | grep "origin.*(fetch)" |
sed -e "s/origin\s*//" -e "s/(fetch)//" |
sed -e "s/\(\([^/]*\/\/\)\?\([^@]*@\)\?\([^:/]*\)\).*/\1/" |
sort | uniq -c
echo
else
_check_git_repo_index
# Figure out which directory we need to change to.
local project=$(echo $1 | cut -d "/" -f1)
# Find base path of project
local base_path="$(grep "/$project$" "$GIT_REPO_DIR/.git_index")"
if [ -n "$base_path" ]; then
sub_path=$(echo $1 | sed "s:^$project::")
# Append subdirectories to base path
base_path="$base_path$sub_path"
fi
# Try partial matches
# - string at beginning of project
if [ -z "$base_path" ]; then base_path=$(_git_repo_dirs_without_home | grep -m1 "/$project"); fi
# - string anywhere in project
if [ -z "$base_path" ]; then base_path=$(_git_repo_dirs_without_home | grep -m1 "$project"); fi
# --------------------
# Go to our base path
if [ -n "$base_path" ]; then
unset IFS
eval cd "$base_path" # eval turns ~ into $HOME
# Run git callback (either update or show changes), if we are in the root directory
if [ -z "${sub_path%/}" ]; then _git_repo_pull_or_status; fi
else
echo -e "$_wrn_col'$1' did not match any git repos in $GIT_REPO_DIR$_txt_col"
fi
fi
fi
}
_git_repo_dirs_without_home() {
sed -e "s/--.*//" -e "s%$HOME%~%" $GIT_REPO_DIR/.git_index
}
# Recursively searches for git repos in $GIT_REPO_DIR
function _find_git_repos() {
# Find all unarchived projects
local IFS=$'\n'
for repo in $(find "$GIT_REPO_DIR" -maxdepth 4 -name ".git" -type d \! -wholename '*/archive/*'); do
echo ${repo%/.git} # Return project folder, with trailing ':'
_find_git_submodules $repo # Detect any submodules
done
}
# List all submodules for a git repo, if any.
function _find_git_submodules() {
if [ -e "$1/../.gitmodules" ]; then
grep "\[submodule" "$1/../.gitmodules" | sed "s%\[submodule \"%${1%/.git}/%g" | sed "s/\"]//g"
fi
}
# Rebuilds index of git repos in $GIT_REPO_DIR.
function _rebuild_git_repo_index() {
if [ "$1" != "--silent" ]; then echo -e "== Scanning $GIT_REPO_DIR for git repos & submodules..."; fi
# Get repos from src dir and custom dirs, then sort by basename
local IFS=$'\n'
for repo in $(echo -e "$(_find_git_repos)\n$(echo $GIT_REPOS | sed "s/:/\n/g")"); do
echo $(basename $repo | sed "s/ /_/g") $repo
done | sort | cut -d " " -f2- > "$GIT_REPO_DIR/.git_index"
if [ "$1" != "--silent" ]; then
echo -e "===== Cached $_bld_col$(_git_repo_count)$_txt_col repos in $GIT_REPO_DIR/.git_index"
fi
}
# Build index if empty
function _check_git_repo_index() {
if [ ! -f "$GIT_REPO_DIR/.git_index" ]; then
_rebuild_git_repo_index --silent
fi
}
# Produces a count of repos in the tab completion index (excluding commands)
function _git_repo_count() {
echo $(sed -e "s/--.*//" "$GIT_REPO_DIR/.git_index" | grep . | wc -l)
}
# If the working directory is clean, update the git repository. Otherwise, show changes.
function _git_repo_pull_or_status() {
if ! [ `git status --porcelain | wc -l` -eq 0 ]; then
# Fall back to 'git status' if git status alias isn't configured
if type $git_status_command 2>&1 | grep -qv "not found"; then
eval $git_status_command
else
git status
fi
else
# Check that a local 'origin' remote exists.
if (git remote -v | grep -q origin); then
branch=`parse_git_branch`
# Only update the git repo if it hasn't been touched for at least 6 hours.
if $(find ".git" -maxdepth 0 -type d -mmin +360 | grep -q "\.git"); then
# If we aren't on any branch, checkout master.
if [ "$branch" = "(no branch)" ]; then
echo -e "=== Checking out$_git_col master$_txt_col branch."
git checkout master
branch="master"
fi
echo -e "=== Updating '$branch' branch in $_bld_col$base_path$_txt_col from$_git_col origin$_txt_col... (Press Ctrl+C to cancel)"
# Pull the latest code from the server
git pull origin $branch
fi
fi
fi
}
# Updates all git repositories with clean working directories.
function _git_repo_update_all() {
echo -e "== Updating code in $_bld_col$(_git_repo_count)$_txt_col repos...\n"
for base_path in $(sed -e "s/--.*//" "$GIT_REPO_DIR/.git_index" | grep . | sort); do
echo -e "===== Updating code in \e[1;32m$base_path\e[0m...\n"
cd "$base_path"
_git_repo_pull_or_status
done
}
# Runs a command for all git repos
function _git_repo_batch_cmd() {
if [ -n "$1" ]; then
echo -e "== Running command for $_bld_col$(_git_repo_count)$_txt_col repos...\n"
for base_path in $(sed -e "s/--.*//" "$GIT_REPO_DIR/.git_index" | grep . | sort); do
cd "$base_path"
$@
done
else
echo "Please give a command to run for all repos. (It may be useful to write your command as a function or script.)"
fi
}
# Bash tab completion function for git_repo()
function _git_repo_tab_completion() {
_check_git_repo_index
local curw
local IFS=$'\n'
COMPREPLY=()
curw=${COMP_WORDS[COMP_CWORD]}
# If the first part of $curw matches a high-level directory,
# then match on sub-directories for that project
local project=$(echo "$curw" | cut -d "/" -f1)
local base_path=$(grep "/$project$" "$GIT_REPO_DIR/.git_index" | sed 's/ /\\ /g')
# If matching path was found and curr string contains a /, then complete project sub-directories
if [[ -n "$base_path" && $curw == */* ]]; then
local search_path=$(echo "$curw" | sed "s:^${project/\\/\\\\\\}::")
COMPREPLY=($(compgen -d "$base_path$search_path" | grep -v "/.git" | sed -e "s:$base_path:$project:" -e "s:$:/:" ))
# Else, tab complete all the entries in .git_index, plus '--' commands
else
local commands="--list\n--rebuild-index\n--update-all\n--batch-cmd\n--count-by-host"
COMPREPLY=($(compgen -W '$(sed -e "s:.*/::" -e "s:$:/:" "$GIT_REPO_DIR/.git_index" | sort)$(echo -e "\n"$commands)' -- $curw))
fi
return 0
}

160
lib/git/status_shortcuts.rb Normal file
View File

@@ -0,0 +1,160 @@
#!/usr/bin/env ruby
# encoding: UTF-8
# ------------------------------------------------------------------------------
# Git Breeze - Streamline your git workflow.
# Copyright 2011 Nathan Broadbent (http://madebynathan.com). All Rights Reserved.
# Released under the LGPL (GNU Lesser General Public License)
# ------------------------------------------------------------------------------
#
# A much faster implementation of git_status_shortcuts() in ruby
# (bash: 0m0.549s, ruby: 0m0.045s)
#
# Last line of output contains the ordered absolute file paths,
# which need to be extracted by the shell and exported as numbered env variables.
#
# 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
# --------------------------------------------------------------------
@project_root = File.exist?(".git") ? Dir.pwd : `git rev-parse --git-dir 2> /dev/null`.sub(/\/\.git$/, '').strip
@git_status = `git status --porcelain 2> /dev/null`
# Exit if no changes
exit if @git_status == ""
git_branch = `git branch -v 2> /dev/null`
@branch = git_branch[/^\* ([^ ]*)/, 1]
@ahead = git_branch[/^\* [^ ]* *[^ ]* *\[ahead ?(\d+)\]/, 1]
@changes = @git_status.split("\n")
# Exit if too many changes
exit if @changes.size > ENV["gs_max_changes"].to_i
# Colors
@c = {
:rst => "\e[0m",
:del => "\e[0;31m",
:mod => "\e[0;32m",
:new => "\e[0;33m",
:ren => "\e[0;34m",
:cpy => "\e[0;33m",
:unt => "\e[0;36m",
:dark => "\e[2;37m",
:branch => "\e[1m",
:header => "\e[0m"
}
# Following colors must be prepended with modifiers e.g. '\e[1;', '\e[0;'
@group_c = {
:staged => "33m",
:unmerged => "31m",
:unstaged => "32m",
:untracked => "36m"
}
@stat_hash = {
:staged => [],
:unmerged => [],
:unstaged => [],
:untracked => []
}
@output_files = []
# Counter for env variables
@e = 0
# Heading
ahead = @ahead ? " #{@c[:dark]}| #{@c[:new]}+#{@ahead}#{@c[:rst]}" : ""
puts "%s#%s On branch: %s#{@branch}#{ahead} %s| [%s*%s]%s => $#{ENV["git_env_char"]}*\n%s#%s" % [
@c[:dark], @c[:rst], @c[:branch], @c[:dark], @c[:rst], @c[:dark], @c[:rst], @c[:dark], @c[:rst]
]
# Index modification states
@changes.each do |change|
x, y, file = change[0, 1], change[1, 1], change[3..-1]
msg, col, group = case change[0..1]
when "DD"; [" both deleted", :del, :unmerged]
when "AU"; [" added by us", :new, :unmerged]
when "UD"; ["deleted by them", :del, :unmerged]
when "UA"; [" added by them", :new, :unmerged]
when "DU"; [" deleted by us", :del, :unmerged]
when "AA"; [" both added", :new, :unmerged]
when "UU"; [" both modified", :mod, :unmerged]
when /M./; [" modified", :mod, :staged]
when /A./; [" new file", :new, :staged]
when /D./; [" deleted", :del, :staged]
when /R./; [" renamed", :ren, :staged]
when /C./; [" copied", :cpy, :staged]
when "??"; ["untracked", :unt, :untracked]
end
# Store data
@stat_hash[group] << {:msg => msg, :col => col, :file => file} if msg
# Work tree modification states
if y == "M"
@stat_hash[:unstaged] << {:msg => " modified", :col => :mod, :file => file}
elsif y == "D" && x != "D" && x != "U"
# Don't show deleted 'y' during a merge conflict.
@stat_hash[:unstaged] << {:msg => " deleted", :col => :del, :file => file}
end
end
def relative_path(base, target)
back = ""
while target.sub(base,'') == target
base = base.sub(/\/[^\/]*$/, '')
back = "../#{back}"
end
"#{back}#{target.sub("#{base}/",'')}"
end
# Output files
def output_file_group(group)
# Print colored hashes & files based on modification groups
c_group = "\e[0;#{@group_c[group]}"
@stat_hash[group].each do |h|
@e += 1
padding = (@e < 10 && @changes.size >= 10) ? " " : ""
rel_file = relative_path(Dir.pwd, File.join(@project_root, h[:file]))
puts "#{c_group}##{@c[:rst]} #{@c[h[:col]]}#{h[:msg]}:\
#{padding}#{@c[:dark]} [#{@c[:rst]}#{@e}#{@c[:dark]}] #{c_group}#{rel_file}#{@c[:rst]}"
# Save the ordered list of output files
@output_files << h[:file]
end
puts "#{c_group}##{@c[:rst]}" # Extra '#'
end
[[:staged, 'Changes to be committed'],
[:unmerged, 'Unmerged paths'],
[:unstaged, 'Changes not staged for commit'],
[:untracked, 'Untracked files']
].each_with_index do |data, i|
group, heading = *data
# Allow filtering by specific group (by string or integer)
if !ARGV[0] || ARGV[0] == group.to_s || ARGV[0] == (i+1).to_s; then
if !@stat_hash[group].empty?
c_arrow="\e[1;#{@group_c[group]}"
c_hash="\e[0;#{@group_c[group]}"
puts "#{c_arrow}#{@c[:header]} #{heading}\n#{c_hash}##{@c[:rst]}"
output_file_group(group)
end
end
end
puts @output_files.map{|f| File.join(@project_root, f) }.join("|")

188
lib/git/status_shortcuts.sh Normal file
View File

@@ -0,0 +1,188 @@
# ------------------------------------------------------------------------------
# Git Breeze - Streamline your git workflow.
# Copyright 2011 Nathan Broadbent (http://madebynathan.com). All Rights Reserved.
# Released under the LGPL (GNU Lesser General Public License)
# ------------------------------------------------------------------------------
# ------------------------------------------------------------------------------
# Numbered file shortcuts for git commands
# ------------------------------------------------------------------------------
# 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 filter by modification state:
# 1 || staged, 2 || unmerged, 3 || unstaged, 4 || untracked
# --------------------------------------------------------------------
git_status_shortcuts() {
# Run ruby script, store output
cmd_output=$(/usr/bin/env ruby "$gitbreezeDir/lib/git/status_shortcuts.rb" $@)
if [[ -z "$cmd_output" ]]; then
# Just show regular git status if ruby script returns nothing.
git status; return 1
fi
git_clear_vars
# Fetch list of files from last line of script output
files="$(echo "$cmd_output" | tail -n 1)"
# Export numbered env variables for each file
local IFS="|"
e=1; for file in $files; do export $git_env_char$e="$file"; let e++; done
# Print status
echo "$cmd_output" | head -n -1
}
# 'git add' & 'git rm' wrapper
# This shortcut means 'stage the change to the file'
# i.e. It will add new and changed files, and remove deleted files.
# Should be used in conjunction with the git_status_shortcuts() function for 'git status'.
# - 'auto git rm' behaviour can be turned off
# -------------------------------------------------------------------------------
git_add_shorcuts() {
if [ -z "$1" ]; then
echo "Usage: ga <file> => git add <file>"
echo " ga 1 => git add \$e1"
echo " ga 2..4 => git add \$e2 \$e3 \$e4"
echo " ga 2 5..7 => git add \$e2 \$e5 \$e6 \$e7"
if [[ $ga_auto_remove == "yes" ]]; then
echo -e "\nNote: Deleted files will also be staged using this shortcut."
echo " To turn off this behaviour, change the 'auto_remove' option."
fi
else
git_silent_add_shorcuts "$@"
# Makes sense to run 'git status' after this command.
git_status_shortcuts
fi
}
# Does nothing if no args are given.
git_silent_add_shorcuts() {
if [ -n "$1" ]; then
# Expand args and process resulting set of files.
for file in $(git_expand_args "$@"); do
# Use 'git rm' if file doesn't exist and 'ga_auto_remove' is enabled.
if [[ $ga_auto_remove == "yes" ]] && ! [ -e $file ]; then
echo -n "# "
git rm $file
else
git add $file
echo -e "# add '$file'"
fi
done
echo "#"
fi
}
# Prints a list of all files affected by a given SHA1,
# and exports numbered environment variables for each file.
git_show_affected_files(){
f=0 # File count
# Show colored revision and commit message
echo -n "# "; script -q -c "git show --oneline --name-only $@" /dev/null | sed "s/\r//g" | head -n 1; echo "# "
for file in $(git show --pretty="format:" --name-only $@ | grep -v '^$'); do
let f++
export $git_env_char$f=$file # Export numbered variable.
echo -e "# \e[2;37m[\e[0m$f\e[2;37m]\e[0m $file"
done; echo "# "
}
# Allows expansion of numbered shortcuts, ranges of shortcuts, or standard paths.
# Numbered shortcut variables are produced by various commands, such as:
# * git_status_shortcuts() - git status implementation
# * git_show_affected_files() - shows files affected by a given SHA1, etc.
git_expand_args() {
files=""
for arg in "$@"; do
if [[ "$arg" =~ ^[0-9]+$ ]] ; then # Substitute $e{*} variables for any integers
files="$files $(eval echo \$$git_env_char$arg)"
elif [[ $arg =~ ^[0-9]+\.\.[0-9]+$ ]]; then # Expand ranges into $e{*} variables
for i in $(seq $(echo $arg | tr ".." " ")); do
files="$files $(eval echo \$$git_env_char$i)"
done
else # Otherwise, treat $arg as a normal string.
files="$files $arg"
fi
done
echo $files
}
# Execute a command with expanded args, e.g. Delete files 6 to 12: $ ge rm 6..12
exec_git_expand_args() { $(git_expand_args "$@"); }
# Clear numbered env variables
git_clear_vars() {
for (( i=1; i<=$gs_max_changes; i++ )); do
# Stop clearing after first empty var
if [[ -z "$(eval echo "\$$git_env_char$i")" ]]; then break; fi
unset $git_env_char$i
done
}
# Shortcuts for resolving merge conflicts.
ours(){ local files=$(git_expand_args "$@"); git checkout --ours $files; git add $files; }
theirs(){ local files=$(git_expand_args "$@"); git checkout --theirs $files; git add $files; }
# Git commit prompts
# ------------------------------------------------------------------------------
# * Prompt for commit message
# * Execute prerequisite commands if message given, abort if not
# * Pipe commit message to 'git commit'
# * Add escaped commit command and unescaped message to bash history.
git_commit_prompt() {
local commit_msg
if [[ $shell == "zsh" ]]; then
# zsh 'read' is weak. If you know how to make this better, please send a pull request.
# (Bash 'read' supports prompt, arrow keys, home/end, up through bash history, etc.)
echo -n "Commit Message: "; read commit_msg
else
read -r -e -p "Commit Message: " commit_msg
fi
if [ -n "$commit_msg" ]; then
eval $@ # run any prequisite commands
echo $commit_msg | git commit -F - | tail -n +2
else
echo -e "\e[0;31mAborting commit due to empty commit message.\e[0m"
fi
escaped=$(echo "$commit_msg" | sed -e 's/"/\\"/g' -e 's/!/"'"'"'!'"'"'"/g')
if [[ $shell == "zsh" ]]; then
print -s "git commit -m \"${escaped//\\/\\\\}\"" # zsh's print needs double escaping
print -s "$commit_msg"
else
echo "git commit -m \"$escaped\"" >> $HISTFILE
# Also add unescaped commit message, for git prompt
echo "$commit_msg" >> $HISTFILE
fi
}
# Prompt for commit message, then commit all modified and untracked files.
git_commit_all() {
changes=$(git status --porcelain | wc -l)
if [ "$changes" -gt 0 ]; then
echo -e "\e[0;33mCommitting all files (\e[0;31m$changes\e[0;33m)\e[0m"
git_commit_prompt "git add -A"
else
echo "# No changed files to commit."
fi
}
# Add paths or expanded args if any given, then commit all staged changes.
git_add_and_commit() {
git_silent_add_shorcuts "$@"
changes=$(git diff --cached --numstat | wc -l)
if [ "$changes" -gt 0 ]; then
git_status_shortcuts 1 # only show staged changes
git_commit_prompt
else
echo "# No staged changes to commit."
fi
}

31
lib/git/tools.sh Normal file
View File

@@ -0,0 +1,31 @@
# -------------------------------------------------------
# Git Breeze - Streamline your git workflow.
# Copyright 2011 Nathan Broadbent (http://madebynathan.com). All Rights Reserved.
# Released under the LGPL (GNU Lesser General Public License)
# -------------------------------------------------------
# -----------------------------------------------------------------
# Misc Git Tools
# - Please feel free to add your own git scripts, and send me a pull request
# at https://github.com/ndbroadbent/scm_breeze
# -----------------------------------------------------------------
# Remove files/folders from git history
# -------------------------------------------------------------------
# To use it, cd to your repository's root and then run the function
# with a list of paths you want to delete. e.g. git_remove_history path1 path2
# Original Author: David Underhill
git_remove_history() {
# Make sure we're at the root of a git repo
if [ ! -d .git ]; then
echo "Error: must run this script from the root of a git repository"
return
fi
# Remove all paths passed as arguments from the history of the repo
files=$@
git filter-branch --index-filter "git rm -rf --cached --ignore-unmatch $files" HEAD
# Remove the temporary history git-filter-branch otherwise leaves behind for a long time
rm -rf .git/refs/original/ && git reflog expire --all && git gc --aggressive --prune
}