Initial Commit - moved out of ubuntu_config.
This commit is contained in:
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
git.scmbrc
|
||||||
2
.travis.yml
Normal file
2
.travis.yml
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
before_script:
|
||||||
|
- sudo apt-get install zsh
|
||||||
10
Gemfile.lock
Normal file
10
Gemfile.lock
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
GEM
|
||||||
|
remote: http://rubygems.org/
|
||||||
|
specs:
|
||||||
|
rake (0.9.2)
|
||||||
|
|
||||||
|
PLATFORMS
|
||||||
|
ruby
|
||||||
|
|
||||||
|
DEPENDENCIES
|
||||||
|
rake
|
||||||
28
README.markdown
Normal file
28
README.markdown
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
# 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)**
|
||||||
|
|
||||||
|
# -------------------------------------------------------
|
||||||
|
|
||||||
|
This is a collection of shell scripts (for `bash` and `zsh`) that enhance your interaction with git.
|
||||||
|
|
||||||
|
* Numbered file shortcuts for git commands
|
||||||
|
* Repository management scripts for Git projects
|
||||||
|
* Symlink design assets
|
||||||
|
|
||||||
|
|
||||||
|
Pull requests always welcome!
|
||||||
|
|
||||||
|
|
||||||
|
### About
|
||||||
|
|
||||||
|
These scripts have been refined and incubated in my bashrc for over a year,
|
||||||
|
and I decided that they deserved their own project.
|
||||||
|
|
||||||
|
I've tried to make each section modular, so you are free to choose what parts you want to use.
|
||||||
|
|
||||||
|
I also know that we grow attached to the aliases we use every day, so I won't force you to use mine.
|
||||||
|
I've given some example aliases at `aliases.example.sh`, but please customize them however you like.
|
||||||
|
|
||||||
15
Rakefile
Normal file
15
Rakefile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
require 'rake'
|
||||||
|
|
||||||
|
desc "Run shUnit2 tests"
|
||||||
|
task :test do
|
||||||
|
Dir.glob("test/**/*_test.sh").each do |test|
|
||||||
|
["bash", "zsh"].each do |shell|
|
||||||
|
puts "== Running tests with [#{shell}]: #{test}"
|
||||||
|
@failed = !system("#{shell} #{test}") || @failed
|
||||||
|
end
|
||||||
|
end
|
||||||
|
exit @failed ? 1 : 0
|
||||||
|
end
|
||||||
|
|
||||||
|
task :default => ['test']
|
||||||
|
|
||||||
72
git.scmbrc.example
Normal file
72
git.scmbrc.example
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
#
|
||||||
|
# Git File Shortcuts Config
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# - Set your preferred prefix for env variable file shortcuts.
|
||||||
|
# (I chose 'e' because it is the easiest key to press after '$'.)
|
||||||
|
export git_env_char="e"
|
||||||
|
# - Max changes before reverting to 'git status'. git_status_shortcuts() may slow down for lots of changes.
|
||||||
|
export gs_max_changes="110"
|
||||||
|
# - Automatically use 'git rm' to remove deleted files when using the git_add_shorcuts() command?
|
||||||
|
export ga_auto_remove="yes"
|
||||||
|
|
||||||
|
|
||||||
|
# Git Repo Management Config
|
||||||
|
# --------------------------
|
||||||
|
# Repos will be automatically added from this directory.
|
||||||
|
export GIT_REPO_DIR="$HOME/src"
|
||||||
|
# Add the full paths of any extra repos to GIT_REPOS, separated with ':'
|
||||||
|
# e.g. "/opt/rails/project:/opt/rails/another project:$HOME/other/repo"
|
||||||
|
export GIT_REPOS=""
|
||||||
|
export git_status_command="git_status_shortcuts"
|
||||||
|
|
||||||
|
|
||||||
|
# Alias configuration
|
||||||
|
# ------------------------------------------------------------------------------------
|
||||||
|
git_alias="g"
|
||||||
|
|
||||||
|
# 1. 'Git Breeze' functions
|
||||||
|
git_status_shortcuts_alias="gs"
|
||||||
|
git_add_shortcuts_alias="ga"
|
||||||
|
git_show_files_alias="gsf"
|
||||||
|
exec_git_expand_args_alias="ge"
|
||||||
|
# 2. Commands that handle paths (with shortcut args expanded)
|
||||||
|
git_checkout_alias="gco"
|
||||||
|
git_commit_alias="gc"
|
||||||
|
git_reset_alias="grs"
|
||||||
|
git_rm_alias="grm"
|
||||||
|
git_blame_alias="gbl"
|
||||||
|
git_diff_alias="gd"
|
||||||
|
git_diff_cached_alias="gdc"
|
||||||
|
# 3. Standard commands
|
||||||
|
git_clone_alias="gcl"
|
||||||
|
git_fetch_alias="gf"
|
||||||
|
git_fetch_and_rebase_alias="gfr"
|
||||||
|
git_pull_alias="gpl"
|
||||||
|
git_push_alias="gps"
|
||||||
|
git_status_original_alias="gst"
|
||||||
|
git_status_short_alias="gss"
|
||||||
|
git_add_all_alias="gaa"
|
||||||
|
git_commit_all_alias="gca"
|
||||||
|
git_commit_amend_alias="gcm"
|
||||||
|
git_commit_amend_no_msg_alias="gcmh"
|
||||||
|
git_remote_alias="gr"
|
||||||
|
git_branch_alias="gb"
|
||||||
|
git_branch_all_alias="gba"
|
||||||
|
git_rebase_alias="grb"
|
||||||
|
git_merge_alias="gm"
|
||||||
|
git_cherry_pick_alias="gcp"
|
||||||
|
git_log_alias="gl"
|
||||||
|
git_log_stat_alias="gls"
|
||||||
|
git_log_graph_alias="glg"
|
||||||
|
git_show_alias="gsh"
|
||||||
|
|
||||||
|
# Git repo management
|
||||||
|
git_repo_alias="s"
|
||||||
|
|
||||||
|
|
||||||
|
# Keyboard shortcuts configuration
|
||||||
|
# ---------------------------------------------
|
||||||
|
git_status_shortcuts_keys="\C- " # CTRL+SPACE
|
||||||
|
git_commit_all_keys="\C-x " # CTRL+x SPACE
|
||||||
|
git_add_and_commit_keys="\C-xc" # CTRL+x c
|
||||||
|
|
||||||
10
install.sh
Executable file
10
install.sh
Executable file
@@ -0,0 +1,10 @@
|
|||||||
|
# This loads Git Breeze into the shell session.
|
||||||
|
exec_string='[[ -s "$HOME/.scm_breeze/scm_breeze.sh" ]] && . "$HOME/.scm_breeze/scm_breeze.sh"'
|
||||||
|
|
||||||
|
# Add line to bashrc and zshrc if not already present.
|
||||||
|
for rc in bashrc zshrc; do
|
||||||
|
if [[ -s "$HOME/.$rc" ]] && ! grep -q "$exec_string" "$HOME/.$rc"; then
|
||||||
|
echo -e "\n$exec_string" >> "$HOME/.$rc"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
8
lib/_shared.sh
Normal file
8
lib/_shared.sh
Normal 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; }
|
||||||
|
|
||||||
0
lib/bzr/BUILDME
Normal file
0
lib/bzr/BUILDME
Normal file
8
lib/git/_shared.sh
Normal file
8
lib/git/_shared.sh
Normal 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; }
|
||||||
|
|
||||||
123
lib/git/aliases_and_bindings.sh
Normal file
123
lib/git/aliases_and_bindings.sh
Normal 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
|
||||||
|
|
||||||
154
lib/git/fallback/status_shortcuts_shell.sh
Normal file
154
lib/git/fallback/status_shortcuts_shell.sh
Normal 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
229
lib/git/repo_management.sh
Normal 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
160
lib/git/status_shortcuts.rb
Normal 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
188
lib/git/status_shortcuts.sh
Normal 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
31
lib/git/tools.sh
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
0
lib/hg/BUILDME
Normal file
0
lib/hg/BUILDME
Normal file
0
lib/svn/BUILDME
Normal file
0
lib/svn/BUILDME
Normal file
24
scm_breeze.sh
Normal file
24
scm_breeze.sh
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#
|
||||||
|
# Get directory of this file (for bash and zsh).
|
||||||
|
# git_breeze.sh must not be run directly.
|
||||||
|
# It must be sourced, e.g "source ~/.git_breeze/git_breeze.sh"
|
||||||
|
# ------------------------------------------------------------
|
||||||
|
|
||||||
|
export gitbreezeDir="$(dirname ${BASH_SOURCE:-$0})"
|
||||||
|
|
||||||
|
# Load config
|
||||||
|
. "$HOME/.git.scmbrc"
|
||||||
|
|
||||||
|
. "$gitbreezeDir/lib/_shared.sh"
|
||||||
|
. "$gitbreezeDir/lib/git/aliases_and_bindings.sh"
|
||||||
|
. "$gitbreezeDir/lib/git/status_shortcuts.sh"
|
||||||
|
. "$gitbreezeDir/lib/git/repo_management.sh"
|
||||||
|
. "$gitbreezeDir/lib/git/tools.sh"
|
||||||
|
|
||||||
|
|
||||||
|
if ! type ruby > /dev/null 2>&1; then
|
||||||
|
# If Ruby is not installed, fall back to the
|
||||||
|
# slower bash/zsh implementation of 'git_status_shortcuts'
|
||||||
|
. "$gitbreezeDir/lib/git/fallback/status_shortcuts_shell.sh"
|
||||||
|
fi
|
||||||
|
|
||||||
185
test/lib/git/repo_management_test.sh
Executable file
185
test/lib/git/repo_management_test.sh
Executable file
@@ -0,0 +1,185 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Unit tests for git shell scripts
|
||||||
|
|
||||||
|
thisDir="$( cd -P "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
|
# Zsh compatibility
|
||||||
|
if [ -n "${ZSH_VERSION:-}" ]; then shell="zsh"; SHUNIT_PARENT=$0; setopt shwordsplit; fi
|
||||||
|
|
||||||
|
# Load test helpers
|
||||||
|
. "$thisDir/../../support/test_helper"
|
||||||
|
|
||||||
|
# Load functions to test
|
||||||
|
. "$thisDir/../../../lib/_shared.sh"
|
||||||
|
. "$thisDir/../../../lib/git/repo_management.sh"
|
||||||
|
|
||||||
|
|
||||||
|
# Setup and tear down
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
oneTimeSetUp() {
|
||||||
|
GIT_REPO_DIR=$(mktemp -d)
|
||||||
|
GIT_REPOS="/tmp/test_repo_1:/tmp/test_repo_11"
|
||||||
|
git_status_command="git status"
|
||||||
|
|
||||||
|
git_index_file="$GIT_REPO_DIR/.git_index"
|
||||||
|
|
||||||
|
silentGitCommands
|
||||||
|
|
||||||
|
cd $GIT_REPO_DIR
|
||||||
|
# Setup test repos in temp repo dir
|
||||||
|
for repo in github bitbucket source_forge; do
|
||||||
|
mkdir $repo; cd $repo; git init; cd - > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
# Add some nested dirs for testing resursive tab completion
|
||||||
|
mkdir -p github/videos/octocat/live_action
|
||||||
|
# Add hidden dir to test that '.git' is filtered, but other hidden dirs are available.
|
||||||
|
mkdir -p github/.im_hidden
|
||||||
|
|
||||||
|
# Setup a test repo with some submodules
|
||||||
|
# (just a dummy '.gitmodules' file and some nested .git directories)
|
||||||
|
mkdir submodules_everywhere
|
||||||
|
cd submodules_everywhere
|
||||||
|
git init
|
||||||
|
cat > .gitmodules <<EOF
|
||||||
|
[submodule "very/nested/directory/red_submodule"]
|
||||||
|
[submodule "very/nested/directory/green_submodule"]
|
||||||
|
[submodule "very/nested/directory/blue_submodule"]
|
||||||
|
EOF
|
||||||
|
mkdir -p "very/nested/directory"
|
||||||
|
cd "very/nested/directory"
|
||||||
|
for repo in red_submodule green_submodule blue_submodule; do
|
||||||
|
mkdir $repo; cd $repo; git init; cd - > /dev/null
|
||||||
|
done
|
||||||
|
|
||||||
|
# Setup some custom repos outside the main repo dir
|
||||||
|
local IFS=":"
|
||||||
|
for dir in $GIT_REPOS; do
|
||||||
|
mkdir -p $dir; cd $dir; git init;
|
||||||
|
done
|
||||||
|
unset IFS
|
||||||
|
|
||||||
|
verboseGitCommands
|
||||||
|
|
||||||
|
cd "$orig_cwd"
|
||||||
|
}
|
||||||
|
|
||||||
|
oneTimeTearDown() {
|
||||||
|
rm -rf "${GIT_REPO_DIR}"
|
||||||
|
local IFS=":"
|
||||||
|
for dir in $GIT_REPOS; do rm -rf $dir; done
|
||||||
|
}
|
||||||
|
|
||||||
|
ensureIndex() {
|
||||||
|
_check_git_repo_index
|
||||||
|
}
|
||||||
|
|
||||||
|
index_no_newlines() {
|
||||||
|
cat $git_index_file | tr "\\n" " "
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Unit tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test_repo_index_command() {
|
||||||
|
git_repo --rebuild-index > /dev/null
|
||||||
|
|
||||||
|
# Test that all repos are detected, and sorted alphabetically
|
||||||
|
assertIncludes "$(index_no_newlines)" "bitbucket.*\
|
||||||
|
blue_submodule.*\
|
||||||
|
github.*\
|
||||||
|
green_submodule.*\
|
||||||
|
red_submodule.*\
|
||||||
|
source_forge.*\
|
||||||
|
submodules_everywhere.*\
|
||||||
|
test_repo_11.*\
|
||||||
|
test_repo_1"
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
test_check_git_repo_index() {
|
||||||
|
ensureIndex
|
||||||
|
echo "should not be regenerated" >> $git_index_file
|
||||||
|
_check_git_repo_index
|
||||||
|
# Test that index is not rebuilt unless empty
|
||||||
|
assertIncludes "$(index_no_newlines)" "should not be regenerated"
|
||||||
|
rm $git_index_file
|
||||||
|
# Test the index is rebuilt
|
||||||
|
_check_git_repo_index
|
||||||
|
assertTrue "[ -f $git_index_file ]"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_git_repo_count() {
|
||||||
|
assertEquals "9" "$(_git_repo_count)"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_repo_list() {
|
||||||
|
ensureIndex
|
||||||
|
list=$(git_repo --list)
|
||||||
|
assertIncludes "$list" "bitbucket" || return
|
||||||
|
assertIncludes "$list" "blue_submodule" || return
|
||||||
|
assertIncludes "$list" "test_repo_11"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Test matching rules for changing directory
|
||||||
|
test_git_repo_changing_directory() {
|
||||||
|
ensureIndex
|
||||||
|
git_repo "github"; assertEquals "$GIT_REPO_DIR/github" "$PWD"
|
||||||
|
git_repo "github/"; assertEquals "$GIT_REPO_DIR/github" "$PWD"
|
||||||
|
git_repo "bucket"; assertEquals "$GIT_REPO_DIR/bitbucket" "$PWD"
|
||||||
|
git_repo "green_sub"; assertEquals "$GIT_REPO_DIR/submodules_everywhere/very/nested/directory/green_submodule" "$PWD"
|
||||||
|
git_repo "_submod"; assertEquals "$GIT_REPO_DIR/submodules_everywhere/very/nested/directory/blue_submodule" "$PWD"
|
||||||
|
git_repo "test_repo_1"; assertEquals "/tmp/test_repo_1" "$PWD"
|
||||||
|
git_repo "test_repo_11"; assertEquals "/tmp/test_repo_11" "$PWD"
|
||||||
|
git_repo "test_repo_"; assertEquals "/tmp/test_repo_11" "$PWD"
|
||||||
|
git_repo "github/videos/octocat/live_action"; assertEquals "$GIT_REPO_DIR/github/videos/octocat/live_action" "$PWD"
|
||||||
|
}
|
||||||
|
|
||||||
|
test_git_repo_tab_completion() {
|
||||||
|
# Only run tab completion test for bash
|
||||||
|
if [[ "$0" == *bash ]]; then
|
||||||
|
ensureIndex
|
||||||
|
COMP_CWORD=0
|
||||||
|
|
||||||
|
# Test that '--' commands have tab completion
|
||||||
|
COMP_WORDS="--"
|
||||||
|
_git_repo_tab_completion
|
||||||
|
assertEquals "Incorrect number of tab-completed '--' commands" "5" "$(tab_completions | wc -w)"
|
||||||
|
|
||||||
|
COMP_WORDS="gith"
|
||||||
|
_git_repo_tab_completion
|
||||||
|
assertIncludes "$(tab_completions)" "github/"
|
||||||
|
|
||||||
|
# Test completion for project sub-directories when project ends with '/'
|
||||||
|
COMP_WORDS="github/"
|
||||||
|
_git_repo_tab_completion
|
||||||
|
assertIncludes "$(tab_completions)" "github/videos/"
|
||||||
|
# Check that '.git/' is filtered from completion, but other hidden dirs are available
|
||||||
|
assertNotIncludes "$(tab_completions)" "github/.git/"
|
||||||
|
assertIncludes "$(tab_completions)" "github/.im_hidden/"
|
||||||
|
|
||||||
|
COMP_WORDS="github/videos/"
|
||||||
|
_git_repo_tab_completion
|
||||||
|
assertIncludes "$(tab_completions)" "github/videos/octocat/"
|
||||||
|
|
||||||
|
|
||||||
|
# Test that completion checks for other matching projects even if one matches perfectly
|
||||||
|
COMP_WORDS="test_repo_1"
|
||||||
|
_git_repo_tab_completion
|
||||||
|
assertIncludes "$(tab_completions)" "test_repo_1/ test_repo_11/"
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# load and run shUnit2
|
||||||
|
# Call this function to run tests
|
||||||
|
. "$thisDir/../../support/shunit2"
|
||||||
|
|
||||||
255
test/lib/git/status_shortcuts_test.sh
Executable file
255
test/lib/git/status_shortcuts_test.sh
Executable file
@@ -0,0 +1,255 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# 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)
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
#
|
||||||
|
# Unit tests for git shell scripts
|
||||||
|
|
||||||
|
thisDir="$( cd -P "$( dirname "$0" )" && pwd )"
|
||||||
|
|
||||||
|
# Zsh compatibility
|
||||||
|
if [ -n "${ZSH_VERSION:-}" ]; then shell="zsh"; SHUNIT_PARENT=$0; setopt shwordsplit; fi
|
||||||
|
|
||||||
|
# Load test helpers
|
||||||
|
. "$thisDir/../../support/test_helper"
|
||||||
|
|
||||||
|
# Load functions to test
|
||||||
|
. "$thisDir/../../../lib/_shared.sh"
|
||||||
|
. "$thisDir/../../../lib/git/status_shortcuts.sh"
|
||||||
|
|
||||||
|
|
||||||
|
# Setup and tear down
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
oneTimeSetUp() {
|
||||||
|
# Test Config
|
||||||
|
git_env_char="e"
|
||||||
|
gs_max_changes="20"
|
||||||
|
ga_auto_remove="yes"
|
||||||
|
|
||||||
|
testRepo=$(mktemp -d)
|
||||||
|
}
|
||||||
|
|
||||||
|
oneTimeTearDown() {
|
||||||
|
rm -rf "${testRepo}"
|
||||||
|
}
|
||||||
|
|
||||||
|
setupTestRepo() {
|
||||||
|
rm -rf "${testRepo}"
|
||||||
|
mkdir -p "$testRepo"
|
||||||
|
cd "$testRepo"
|
||||||
|
git init > /dev/null
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
# Unit tests
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
test_git_expand_args() {
|
||||||
|
local e1="one"; local e2="two"; local e3="three"; local e4="four"; local e5="five"; local e6="six"; local e7="seven"
|
||||||
|
local error="Args not expanded correctly"
|
||||||
|
assertEquals "$error" "one three seven" "$(git_expand_args 1 3 7)"
|
||||||
|
assertEquals "$error" "one two three six" "$(git_expand_args 1..3 6)"
|
||||||
|
assertEquals "$error" "seven two three four five one" "$(git_expand_args seven 2..5 1)"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_git_status_shortcuts() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
silentGitCommands
|
||||||
|
|
||||||
|
# Set up some modifications
|
||||||
|
touch deleted_file
|
||||||
|
git add deleted_file
|
||||||
|
git commit -m "Test commit"
|
||||||
|
touch new_file
|
||||||
|
touch untracked_file
|
||||||
|
git add new_file
|
||||||
|
echo "changed" > new_file
|
||||||
|
rm deleted_file
|
||||||
|
|
||||||
|
verboseGitCommands
|
||||||
|
|
||||||
|
# Test that groups can be filtered by passing a parameter
|
||||||
|
git_status1=$(git_status_shortcuts 1)
|
||||||
|
git_status3=$(git_status_shortcuts 3)
|
||||||
|
git_status4=$(git_status_shortcuts 4)
|
||||||
|
|
||||||
|
# Test for presence of expected groups
|
||||||
|
assertIncludes "$git_status1" "Changes to be committed"
|
||||||
|
assertIncludes "$git_status3" "Changes not staged for commit"
|
||||||
|
assertIncludes "$git_status4" "Untracked files"
|
||||||
|
assertNotIncludes "$git_status3" "Changes to be committed"
|
||||||
|
assertNotIncludes "$git_status4" "Changes not staged for commit"
|
||||||
|
assertNotIncludes "$git_status1" "Untracked files"
|
||||||
|
assertNotIncludes "$git_status4" "Changes to be committed"
|
||||||
|
assertNotIncludes "$git_status1" "Changes not staged for commit"
|
||||||
|
assertNotIncludes "$git_status3" "Untracked files"
|
||||||
|
|
||||||
|
# Run command in shell, load output from temp file into variable
|
||||||
|
temp_file=$(mktemp)
|
||||||
|
git_status_shortcuts > $temp_file
|
||||||
|
git_status=$(cat $temp_file | strip_colors)
|
||||||
|
|
||||||
|
assertIncludes "$git_status" "new file: *\[1\] *new_file" || return
|
||||||
|
assertIncludes "$git_status" "deleted: *\[2\] *deleted_file" || return
|
||||||
|
assertIncludes "$git_status" "modified: *\[3\] *new_file" || return
|
||||||
|
assertIncludes "$git_status" "untracked: *\[4\] *untracked_file" || return
|
||||||
|
|
||||||
|
# Test that shortcut env variables are set with full path
|
||||||
|
local error="Env variable was not set"
|
||||||
|
assertEquals "$error" "$testRepo/new_file" "$e1" || return
|
||||||
|
assertEquals "$error" "$testRepo/deleted_file" "$e2" || return
|
||||||
|
assertEquals "$error" "$testRepo/new_file" "$e3" || return
|
||||||
|
assertEquals "$error" "$testRepo/untracked_file" "$e4" || return
|
||||||
|
}
|
||||||
|
|
||||||
|
test_git_status_produces_relative_paths() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
mkdir -p dir1/sub1/subsub1
|
||||||
|
mkdir -p dir1/sub2
|
||||||
|
mkdir -p dir2
|
||||||
|
touch dir1/sub1/subsub1/testfile
|
||||||
|
touch dir1/sub2/testfile
|
||||||
|
touch dir2/testfile
|
||||||
|
git add .
|
||||||
|
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertIncludes "$git_status" "dir1/sub1/subsub1/testfile" || return
|
||||||
|
|
||||||
|
cd $testRepo/dir1
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertIncludes "$git_status" " sub1/subsub1/testfile" || return
|
||||||
|
assertIncludes "$git_status" " sub2/testfile" || return
|
||||||
|
assertIncludes "$git_status" "../dir2/testfile" || return
|
||||||
|
|
||||||
|
cd $testRepo/dir1/sub1
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertIncludes "$git_status" " subsub1/testfile" || return
|
||||||
|
assertIncludes "$git_status" " ../sub2/testfile" || return
|
||||||
|
assertIncludes "$git_status" "../../dir2/testfile" || return
|
||||||
|
|
||||||
|
cd $testRepo/dir1/sub1/subsub1
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertIncludes "$git_status" " testfile" || return
|
||||||
|
assertIncludes "$git_status" " ../../sub2/testfile" || return
|
||||||
|
assertIncludes "$git_status" "../../../dir2/testfile" || return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_git_status_shortcuts_merge_conflicts() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
silentGitCommands
|
||||||
|
|
||||||
|
# Set up every possible merge conflict
|
||||||
|
touch both_modified both_deleted deleted_by_them deleted_by_us
|
||||||
|
echo "renamed file needs some content" > renamed_file
|
||||||
|
git add both_modified both_deleted renamed_file deleted_by_them deleted_by_us
|
||||||
|
git commit -m "First commit"
|
||||||
|
|
||||||
|
git checkout -b conflict_branch
|
||||||
|
echo "added by branch" > both_added
|
||||||
|
echo "branch line" > both_modified
|
||||||
|
echo "deleted by us" > deleted_by_us
|
||||||
|
git rm deleted_by_them both_deleted
|
||||||
|
git mv renamed_file renamed_file_on_branch
|
||||||
|
git add both_added both_modified deleted_by_us
|
||||||
|
git commit -m "Branch commit"
|
||||||
|
|
||||||
|
git checkout master
|
||||||
|
echo "added by master" > both_added
|
||||||
|
echo "master line" > both_modified
|
||||||
|
echo "deleted by them" > deleted_by_them
|
||||||
|
git rm deleted_by_us both_deleted
|
||||||
|
git mv renamed_file renamed_file_on_master
|
||||||
|
git add both_added both_modified deleted_by_them
|
||||||
|
git commit -m "Master commit"
|
||||||
|
|
||||||
|
git merge conflict_branch
|
||||||
|
|
||||||
|
verboseGitCommands
|
||||||
|
|
||||||
|
# Test output without stripped color codes
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertIncludes "$git_status" "both added: *\[[0-9]*\] *both_added" || return
|
||||||
|
assertIncludes "$git_status" "both modified: *\[[0-9]*\] *both_modified" || return
|
||||||
|
assertIncludes "$git_status" "deleted by them: *\[[0-9]*\] *deleted_by_them" || return
|
||||||
|
assertIncludes "$git_status" "deleted by us: *\[[0-9]*\] *deleted_by_us" || return
|
||||||
|
assertIncludes "$git_status" "both deleted: *\[[0-9]*\] *renamed_file" || return
|
||||||
|
assertIncludes "$git_status" "added by them: *\[[0-9]*\] *renamed_file_on_branch" || return
|
||||||
|
assertIncludes "$git_status" "added by us: *\[[0-9]*\] *renamed_file_on_master" || return
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_git_status_shortcuts_max_changes() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
export gs_max_changes="5"
|
||||||
|
|
||||||
|
# Add 5 untracked files
|
||||||
|
touch a b c d e
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
for i in $(seq 1 5); do
|
||||||
|
assertIncludes "$git_status" "\[$i\]" || return
|
||||||
|
done
|
||||||
|
|
||||||
|
# 6 untracked files is more than $gs_max_changes
|
||||||
|
touch f
|
||||||
|
git_status=$(git_status_shortcuts | strip_colors)
|
||||||
|
assertNotIncludes "$git_status" "\[[0-9]*\]" || return
|
||||||
|
|
||||||
|
export gs_max_changes="20"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
test_git_add_shorcuts() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
touch a b c d e f g h i j
|
||||||
|
# Show git status, which sets up env variables
|
||||||
|
git_status_shortcuts > /dev/null
|
||||||
|
git_add_shorcuts 2..4 7 8 > /dev/null
|
||||||
|
git_status=$(git_status_shortcuts 1 | strip_colors)
|
||||||
|
|
||||||
|
for c in b c d g h; do
|
||||||
|
assertIncludes "$git_status" "\[[0-9]*\] $c" || return
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
test_git_commit_prompt() {
|
||||||
|
setupTestRepo
|
||||||
|
|
||||||
|
commit_msg="\"Nathan's git commit prompt function!\""
|
||||||
|
dbl_escaped_msg="\\\\\"Nathan's git commit prompt function\"'"'!'"'\"\\\\\""
|
||||||
|
# Create temporary history file
|
||||||
|
HISTFILE=$(mktemp)
|
||||||
|
HISTFILESIZE=1000
|
||||||
|
HISTSIZE=1000
|
||||||
|
|
||||||
|
touch a b c d
|
||||||
|
git add . > /dev/null
|
||||||
|
|
||||||
|
# Lightly test the git commit prompt, by piping a commit message
|
||||||
|
# instead of user input.
|
||||||
|
echo "$commit_msg" | git_commit_prompt > /dev/null
|
||||||
|
|
||||||
|
git_show_output=$(git show --oneline --name-only)
|
||||||
|
assertIncludes "$git_show_output" "$commit_msg"
|
||||||
|
|
||||||
|
# Test that history was appended correctly.
|
||||||
|
if [[ $shell != "zsh" ]]; then history -n; fi # Reload bash history
|
||||||
|
test_history="$(history)"
|
||||||
|
assertIncludes "$test_history" "$commit_msg"
|
||||||
|
assertIncludes "$test_history" "git commit -m \"$dbl_escaped_msg\""
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# load and run shUnit2
|
||||||
|
. "$thisDir/../../support/shunit2"
|
||||||
|
|
||||||
1048
test/support/shunit2
Executable file
1048
test/support/shunit2
Executable file
File diff suppressed because it is too large
Load Diff
38
test/support/test_helper
Normal file
38
test/support/test_helper
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
orig_cwd="$PWD"
|
||||||
|
|
||||||
|
#
|
||||||
|
# Test helpers
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# Strip color codes from a string
|
||||||
|
strip_colors() {
|
||||||
|
sed -r "s/\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]//g"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Print space separated tab completion options
|
||||||
|
tab_completions(){ echo "${COMPREPLY[@]}"; }
|
||||||
|
|
||||||
|
# Silence git commands
|
||||||
|
silentGitCommands() {
|
||||||
|
git() { /usr/bin/env git "$@" > /dev/null 2>&1; }
|
||||||
|
}
|
||||||
|
# Cancel silent git commands
|
||||||
|
verboseGitCommands() {
|
||||||
|
unset -f git
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Asserts
|
||||||
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# assert $1 contains $2
|
||||||
|
assertIncludes() {
|
||||||
|
result=1; if echo "$1" | grep -Pq "$2"; then result=0; fi
|
||||||
|
assertTrue "'$1' should have contained '$2'" $result
|
||||||
|
}
|
||||||
|
# assert $1 does not contain $2
|
||||||
|
assertNotIncludes() {
|
||||||
|
result=1; if echo "$1" | grep -Pq "$2"; then result=0; fi
|
||||||
|
assertFalse "'$1' should not have contained '$2'" $result
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user