diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..3c8b204
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,17 @@
+default: help
+
+help:
+ @echo make install - Install the scripts to $(HOME)/bin
+ @echo make clean - Remove the scripts
+
+clean:
+ rm -f "$(HOME)/bin/git-state"
+ rm -f "$(HOME)/bin/git-ps1.sh"
+
+install:
+ @PWD=`pwd`
+ ln -s "$(PWD)/bin/git-state" "$(HOME)/bin/git-state"
+ ln -s "$(PWD)/git-ps1.sh" "$(HOME)/bin/git-ps1.sh"
+
+.PHONY: clean install
+
diff --git a/README b/README
deleted file mode 100644
index 2dfdb88..0000000
--- a/README
+++ /dev/null
@@ -1,33 +0,0 @@
-git-ps1 - git-augmented PS1 for BASH
-
-SETUP
------
-To use, modify your PS1 variable (e.g. in ~/.bashrc) to include the script:
- PS1="$PS1\$($( cat git-ps1.sh ))"
-
-This script produces output in a number of different colors. To ensure the
-prompt operates correctly, non-printable characters must be escaped (\[\]).
-However, these characters are not recognized when output from an external
-script. Therefore, the script must be inserted directly into the PS1 string,
-where echoing the escape sequence will produce the intended result.
-
-CONFIGURATION
--------------
-All configuration is done via GITPS1_* environment variables.
-
-Indicators (set to '0' to disable)
- GITPS1_IND_STAGED - Staged changes
- GITPS1_IND_UNSTAGED - Unstaged changes
- GITPS1_IND_UNTRACKED - Untracked files
- GITPS1_IND_AHEAD - Ahead of tracking branch
- GITPS1_IND_AHEAD_COUNT - Whether to display number of commits ahead (e.g. @5)
-
-Colors:
- GITPS1_COLOR_DEFAULT - Default color, used to display brackets and branch
- GITPS1_COLOR_FASTFWD - Color used for fast-forward indicator and used to
- display brackets and hash when not on a branch
- GITPS1_COLOR_STAGED - Color used to for staged changes indicator
- GITPS1_COLOR_UNTRACKED - Color used for untracked files indicator
- GITPS1_COLOR_UNSTAGED - Color used for unstaged changes indicator
- GITPS1_COLOR_AHEAD - Color used for ahead indicator (ahead of tracking)
-
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..429654a
--- /dev/null
+++ b/README.md
@@ -0,0 +1,181 @@
+git-supp is a package of supplemental scripts and enhancements for Git. They
+are intended to solve common issues or reduce repetitiveness for common
+day-to-day Git tasks.
+
+The name "git-supp" is not the name of a project. Rather, it is simply a name
+given to the repository.
+
+
+# Git PS1
+git-ps1 is a Git-augmented PS1 for BASH. It uses colors and various identifiers
+to display information about the status of the current branch, including the
+branch name, staged and unstaged changes, untracked files, commits ahead of the
+tracking branch and more.
+
+## Setup
+To use, modify your PS1 variable (e.g. in ~/.bashrc) to include the script:
+ PS1="$PS1\$($( cat git-ps1.sh ))"
+
+This script produces output in a number of different colors. To ensure the
+prompt operates correctly, non-printable characters must be escaped (\[\]).
+However, these characters are not recognized when output from an external
+script. Therefore, the script must be inserted directly into the PS1 string,
+where echoing the escape sequence will produce the intended result.
+
+## Configuration
+All configuration is done via `GITPS1_*` environment variables.
+
+Indicators (set to '0' to disable)
+
+* `GITPS1_IND_STAGED` - Staged changes
+* `GITPS1_IND_UNSTAGED` - Unstaged changes
+* `GITPS1_IND_UNTRACKED` - Untracked files
+* `GITPS1_IND_AHEAD` - Ahead of tracking branch
+* `GITPS1_IND_AHEAD_COUNT` - Whether to display number of commits ahead (e.g. @5)
+* `GITPS1_IND_STATE` - Whether to display state string (see git-supp)
+
+Colors:
+
+* `GITPS1_COLOR_DEFAULT` - Default color, used to display brackets and branch
+* `GITPS1_COLOR_FASTFWD` - Color used for fast-forward indicator and used to
+ display brackets and hash when not on a branch
+* `GITPS1_COLOR_STAGED` - Color used to for staged changes indicator
+* `GITPS1_COLOR_UNTRACKED` - Color used for untracked files indicator
+* `GITPS1_COLOR_UNSTAGED` - Color used for unstaged changes indicator
+* `GITPS1_COLOR_AHEAD` - Color used for ahead indicator (ahead of tracking)
+* `GITPS1_COLOR_STATE` - Color used for state string (see git-supp)
+
+
+# shortmaps / BASH Completion
+The `bash_completion` file contains BASH completion for custom commands and
+"shortmaps", which provide single or double-character aliases to common Git
+commands.
+
+## Setup
+Source the `bash_completion` file (e.g. place in `.bashrc` or in
+`/etc/bash_completion.d/` on Debian systems), with the path to the provided
+`shortmaps` file as the only argument:
+
+```
+$ . bash_completion ./shortmaps
+```
+
+You may also add your own mappings to `~/.git-ps1-shortmaps`.
+
+## Usage
+By default, the following mappings are available, each with tab completion:
+
+* `a` - git add
+* `A` - git add -A
+* `B` - git bisect
+* `Bs` - git bisect start
+* `Bg` - git bisect good
+* `Bb` - git bisect bad
+* `Br` - git bisect reset
+* `c` - git commit
+* `C` - git commit -am
+* `co` - git checkout
+* `d` - git diff
+* `f` - git fetch
+* `m` - git merge
+* `p` - git push
+* `P` - git pull
+* `R` - git rebase
+* `Ri` - git rebase --interactive
+* `Ra` - git rebase --abort
+* `Rc` - git rebase --continue
+* `s` - git status
+* `S` - git stash
+* `t` - execute tig
+* `T` - git tag
+* `-` - git checkout -
+* `--` - `cd` to root dir of repository
+
+The shortmaps may only be used within a git repository. Otherwise, they will
+invoke the actual command on the system.
+
+If a command conflicts with an existing command on your system, wrap the command
+in quotes to invoke the actual command.
+
+## Configuration
+The file format is as follows:
+
+```
+KEY COMPLETION :CMD
+KEY COMPLETION |CMD
+KEY COMPLETION CMD
+```
+
+If `CMD` contains a colon (`:`) prefix, the command will be prefixed with `git`. If
+prefixed with a pipe (`|`), the command will be sent to `eval` (needed for
+certain features like subshells). Commands without either prefix will be
+executed normally.
+
+
+# git state
+Adds the concept of "states" to branches. The state, which is represented as a
+string, can be assigned to a branch and will be prepended to any commit on that
+branch. Distinct states can be assigned to separate branches.
+
+This concept is intended to aid in the following scenarios:
+
+* Branches are often used to identify a certain feature or fix. However, once
+ the branch is deleted, the only remaining identifying information is the merge
+ commit. States allow a specific string (e.g. the bug number) to be prepended
+ to each commit message automatically, which may be otherwise forgotten or
+ infrequent.
+* During large refactorings, one may need to commit during an unstable state in
+ order to prevent one massive commit. However, this complicates operations
+ like `git bisect`. One could use states to clearly mark each commit as
+ unstable until the process is complete.
+* The state can be used in conjunction with git-ps1 in order to clearly state
+ the current state of the branch.
+
+## Usage
+```sh
+$ git state foo # sets the state to "foo"
+$ git state # retrieve the current state
+foo
+$ git state --clear # clear the state
+```
+
+The previous state is stored for each branch, allowing for quick switches
+between states using `-` as the message (much like `cd -`):
+
+```sh
+$ git state foo # sets the state to "foo"
+$ git state bar # sets the state to "bar"
+$ git state - # sets the state to "foo"
+$ git state - # sets the state to "bar"
+```
+
+Remember - states are tied to the current branch:
+
+```sh
+$ git state foo
+$ git state
+foo
+$ git checkout -b newbranch
+Switched to branch 'newbranch'
+$ git state # no state
+$ git state bar
+$ git state
+bar
+$ git checkout master
+Switched to branch 'master'
+$ git state
+foo
+```
+
+## Setup
+Add the repository's `bin/` directory to your `PATH` environment variable, or
+copy the script into your `PATH`.
+
+## Configuration
+Configuration can be done via `git config`. The following options are available:
+
+* `state.delim.left` - String to be used for left portion of delimiter (default
+ '[')
+* `state.delim.right` - String to be used for right portion of delimiter
+ (default: ']')
+
diff --git a/bash_completion b/bash_completion
new file mode 100644
index 0000000..6ecab47
--- /dev/null
+++ b/bash_completion
@@ -0,0 +1,119 @@
+#!/bin/bash
+#
+# Provides short mappings for common Git commands
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+# #
+
+
+_git_state ()
+{
+ _get_comp_words_by_ref -n =: cur
+
+ case "$cur" in
+ --*)
+ __gitcomp "--clear";;
+ esac
+}
+
+# override their config function to provide our own options
+eval "$( echo '_prev_git_config ()'; declare -f _git_config | tail -n +2 )"
+_git_config ()
+{
+ _prev_git_config
+
+ _get_comp_words_by_ref -n =: cur prev
+
+ # add our configuration options
+ COMPREPLY=( ${COMPREPLY[@]-} state.delim.left state.delim.right )
+}
+
+
+__git-supp_docomplete ()
+{
+ # ignore problem commands
+ grep -q '^-' <<< "$1" && return
+
+ complete -o bashdefault -o default -o nospace -F $2 $1 2>/dev/null \
+ || complete -o default -o nospace -F $2 $1
+}
+
+__git-supp_shortmap ()
+{
+ # only perform completion when within a git dir
+ __gitdir >/dev/null || return $?
+
+ # execute the associated completion function (column two of the shortmaps
+ # file)
+ $( awk "/^$1 / { print \$2 }" <<< "$__git_supp_maps" )
+}
+
+__git-supp_register_alias ()
+{
+ # ignore invalid aliases (for which we define functions to handle them
+ # instead)
+ grep -q '^-' <<< "$1" && return
+
+ alias $1="__git-supp_shortalias $1"
+}
+
+__git-supp_shortalias ()
+{
+ shortcmd=$1
+ shift
+
+ # if we're not within a git dir, fall back to an actual command of this name
+ __gitdir >/dev/null || {
+ " $shortcmd $@"
+ return $?
+ }
+
+ # execute the command
+ cmd="$( grep "^$shortcmd " <<< "$__git_supp_maps" | cut -d' ' -f3- )"
+ if [ -z "$cmd" ]; then
+ return
+ elif [ "$( grep '^|' <<< "$cmd")" ]; then
+ eval "$( sed 's/^|//' <<< "$cmd" ) $@"
+ return $?
+ fi
+
+ $cmd "$@"
+}
+
+# functions that cannot be aliased
+- () { __git-supp_shortalias - "$@"; }
+-- () { __git-supp_shortalias -- "$@"; }
+
+# load shortmaps from cwd (or provided path) and home dir (if available)
+__git_supp_maps=$(
+ cat ${1:-./shortmaps} ~/.git-supp-shortmaps 2>/dev/null \
+ | sed 's/^\([^ ]\+ [^ ]\+\) :/\1 git /'
+)
+
+oldifs="$IFS"
+
+# register each shortmap
+IFS=$'\n'
+for line in $__git_supp_maps; do
+ IFS=$' '
+ set -- $line
+
+ [ -z "$1" ] && continue
+
+ __git-supp_docomplete "$1" __git-supp_shortmap
+ __git-supp_register_alias "$1"
+done
+
+IFS="$oldifs"
+
diff --git a/bin/git-state b/bin/git-state
new file mode 100755
index 0000000..b794c4c
--- /dev/null
+++ b/bin/git-state
@@ -0,0 +1,169 @@
+#!/bin/bash
+#
+# Adds "state" command to prepend state strings to commits
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see .
+# #
+
+gitdir=$( git rev-parse --git-dir 2>/dev/null )
+msgpath="$gitdir/COMMIT_EDITMSG_PREFIX"
+
+if [ -z "$gitdir" ]; then
+ echo "fatal: Not in a git repository" >&2
+ exit 1
+fi
+
+branch=$( git symbolic-ref HEAD 2>/dev/null )
+branch=${branch#refs/heads/}
+if [ -z "$branch" ]; then
+ echo "fatal: Not currently on any branch" >&2
+ exit 1
+fi
+
+
+##
+# Stores current state for current branch and removes any associated stale
+# states (providing one historical state for the branch)
+##
+_clearmsg()
+{
+ local bname="${branch//\//\/}"
+
+ # remove stale branch state and mark current branch state as previous (which
+ # will be removed next time the state is set for this branch again)
+ [ -f "$msgpath" ] && \
+ sed -i "/^-$bname/d;s/^$bname/-&/" "$msgpath"
+}
+
+
+##
+# Retrieve the current state message for the current branch, or an alternate
+# state as specified by an optional prefix
+_getmsg()
+{
+ local pre="$1"
+ grep "^${pre}$branch" "$msgpath" 2>/dev/null \
+ | cut -d' ' -f2-
+}
+
+
+##
+# Initialize the prepare-commit-msg hook
+#
+# If it does not exist, one will be created. Otherwise, the actions will be
+# appended to the existing hook to ensure that the process can be properly
+# terminated by any existing hook.
+##
+_hookinit()
+{
+ local hookpath="$gitdir/hooks/prepare-commit-msg"
+ local hookflag="$gitdir/hooks/.stateinit"
+
+ # do nothing if we've already initialized the hook
+ if [ -e "$hookflag" ]; then
+ return 0;
+ fi
+
+ # if the hook does not yet exist, create it
+ if [ ! -f "$hookpath" ]; then
+ echo '#!/bin/bash' > "$hookpath" \
+ && chmod +x "$hookpath" \
+ || return 1
+ fi
+
+ # append our hook code to prepend the state string to each commit (if one is
+ # set for the current branch)
+ cat - >> "$hookpath" <<'EOF'
+
+#
+# added automatically by git-state
+#
+branch=$( git symbolic-ref HEAD 2>/dev/null )
+branch=${branch#refs/heads/}
+
+msg=$( cat "$1" )
+prefix=$( git state | tr -d '\n' )
+dl=$( git config state.delim.left || echo '[' )
+dr=$( git config state.delim.right || echo ']' )
+statemsg="$dl$prefix$dr "
+
+# do not include statemsg if it's already found in the message
+grep -qoF "$statemsg" <<< "$msg" && statemsg=""
+
+if [ -n "$prefix" ]; then
+ echo "$statemsg$msg" > "$1"
+fi
+EOF
+
+ # if the append failed, we cannot continue
+ test $? -gt 0 && return 1
+
+ # prevent us from initializing again in the future
+ touch "$hookflag"
+}
+
+
+# parse options (with long option support)
+while getopts ":-:" opt; do
+ case "$opt" in
+ -)
+ # parse --long options
+ case "$OPTARG" in
+ clear)
+ _clearmsg
+ exit
+ ;;
+
+ *)
+ echo "fatal: Unknown option: $OPTARG" >&2
+ exit 64 # EX_USAGE
+ ;;
+ esac;;
+
+ ?)
+ echo "fatal: Unknown option: $OPTARG" >&2
+ exit 64 # EX_USAGE
+ ;;
+ esac
+done
+
+# attempt to initialize the hook
+_hookinit || {
+ echo "fatal: Could not initialize prepare-commit-msg hook" >&2
+ exit 1
+}
+
+# if no message was provided, simply output the current value
+if [ -z "$*" ]; then
+ statemsg=$( _getmsg )
+
+ # output state message and exit with 0 status if state message is set
+ if [ -n "$statemsg" ]; then
+ echo "$statemsg"
+ exit
+ fi
+
+ # no state message; exit with non-zero status to indicate absence
+ exit 1
+fi
+
+# the message will be, by default, the remainder of our arguments; dash will
+# indicate that the previous state should be used in place of the message
+msg="$@"
+[ "$msg" == '-' ] && msg=$( _getmsg - )
+
+# clear the previous message and set the new
+_clearmsg
+echo "$branch $msg" >> "$msgpath"
+
diff --git a/git-ps1.sh b/git-ps1.sh
old mode 100644
new mode 100755
index d805a22..4b2d407
--- a/git-ps1.sh
+++ b/git-ps1.sh
@@ -18,12 +18,12 @@
# along with this program. If not, see .
# #
-BRANCH=$(git symbolic-ref HEAD 2>/dev/null \
+branch=$(git symbolic-ref HEAD 2>/dev/null \
|| git rev-parse HEAD 2>/dev/null | cut -c1-10 \
)
# if no branch or hash was returned, then we're not in a repository
-if [ -z "$BRANCH" ]; then
+if [ -z "$branch" ]; then
exit
fi
@@ -33,69 +33,80 @@ mkcolor()
echo "\[\033[00;$1m\]"
}
-BRANCH=${BRANCH#refs/heads/}
-GIT_STATUS=$( git status 2>/dev/null )
+branch=${branch#refs/heads/}
+git_status=$( git status 2>/dev/null )
+if [ "$?" != '0' ]; then
+ exit
+fi
# colors can be overridden via the GITPS1_COLOR_* environment variables
-COLOR_DEFAULT=$( mkcolor ${GITPS1_COLOR_DEFAULT:-33} )
-COLOR_FASTFWD=$( mkcolor ${GITPS1_COLOR_FASTFWD:-31} )
-COLOR_STAGED=$( mkcolor ${GITPS1_COLOR_STAGED:-32} )
-COLOR_UNTRACKED=$( mkcolor ${GITPS1_COLOR_UNTRACKED:-31} )
-COLOR_UNSTAGED=$( mkcolor ${GITPS1_COLOR_UNSTAGED:-33} )
-COLOR_AHEAD=$( mkcolor ${GITPS1_COLOR_AHEAD:-33} )
+color_default=$( mkcolor ${GITPS1_COLOR_DEFAULT:-33} )
+color_fastfwd=$( mkcolor ${GITPS1_COLOR_FASTFWD:-31} )
+color_staged=$( mkcolor ${GITPS1_COLOR_STAGED:-32} )
+color_untracked=$( mkcolor ${GITPS1_COLOR_UNTRACKED:-31} )
+color_unstaged=$( mkcolor ${GITPS1_COLOR_UNSTAGED:-33} )
+color_ahead=$( mkcolor ${GITPS1_COLOR_AHEAD:-33} )
+color_state=$( mkcolor ${GITPS1_COLOR_STATE:-35} )
+color_clr=$( mkcolor 0 )
# indicators may be overridden via the GITPS1_IND_* environment vars; set to
# '0' to disable
-IND_STAGED=${GITPS1_IND_STAGED:-*}
-IND_UNSTAGED=${GITPS1_IND_UNSTAGED:-*}
-IND_UNTRACKED=${GITPS1_IND_UNTRACKED:-*}
-IND_AHEAD=${GITPS1_IND_AHEAD:-@}
-IND_AHEAD_COUNT=${GITPS1_IND_AHEAD_COUNT:-@}
+ind_staged=${GITPS1_IND_STAGED:-*}
+ind_unstaged=${GITPS1_IND_UNSTAGED:-*}
+ind_untracked=${GITPS1_IND_UNTRACKED:-*}
+ind_ahead=${GITPS1_IND_AHEAD:-@}
+ind_ahead_count=${GITPS1_IND_AHEAD_COUNT:-@}
+ind_state=${GITPS1_IND_STATE:-1}
-STATUS=''
-COLOR=$COLOR_DEFAULT
+statusmsg=''
+statemsg=''
+color=$color_default
# uncommited files
-if [ "$IND_UNSTAGED" != '0' ]; then
- if [ "$( echo $GIT_STATUS | grep 'Changed\|uncommitted' )" ]; then
- STATUS="${STATUS}${COLOR_UNSTAGED}${IND_UNSTAGED}"
- fi
+if [ "$ind_unstaged" != '0' ]; then
+ git diff --no-ext-diff --quiet --exit-code 2>/dev/null || \
+ statusmsg="${statusmsg}${color_unstaged}${ind_unstaged}"
fi
# not on branch/behind origin
-if [ "$( echo $GIT_STATUS | grep 'Not currently on\|is behind' )" ]; then
- COLOR=$COLOR_FASTFWD
+if [ "$( echo $git_status | grep 'Not currently on\|is behind' )" ]; then
+ color=$color_fastfwd
fi
# staged
-if [ "$IND_STAGED" != '0' ]; then
- if [ "$( echo $GIT_STATUS | grep 'to be committed' )" ]; then
- STATUS="${STATUS}${COLOR_STAGED}${IND_STAGED}"
+if [ "$ind_staged" != '0' ]; then
+ if [ "$( echo $git_status | grep 'to be committed' )" ]; then
+ statusmsg="${statusmsg}${color_staged}${ind_staged}"
fi
fi
# untracked
-if [ "$IND_UNTRACKED" != '0' ]; then
- if [ "$( echo $GIT_STATUS | grep 'Untracked' )" ]; then
- STATUS="${STATUS}${COLOR_UNTRACKED}${IND_UNTRACKED}"
+if [ "$ind_untracked" != '0' ]; then
+ if [ -n "$( git ls-files --others --exclude-standard 2>/dev/null )" ]; then
+ statusmsg="${statusmsg}${color_untracked}${ind_untracked}"
fi
fi
# ahead of tracking
-if [ "$IND_AHEAD" != '0' ]; then
- if [ "$( echo $GIT_STATUS | grep 'is ahead' )" ]; then
- STATUS="${STATUS}${COLOR_AHEAD}${IND_AHEAD}"
+if [ "$ind_ahead" != '0' ]; then
+ grep -q 'is ahead' <<< "$git_status" && {
+ statusmsg="${statusmsg}${color_ahead}${ind_ahead}"
# append count?
- if [ "$IND_AHEAD_COUNT" != '0' ]; then
- AHEAD_COUNT=$( echo $GIT_STATUS \
+ if [ "$ind_ahead_count" != '0' ]; then
+ ahead_count=$( echo $git_status \
| grep -o 'by [0-9]\+ commits\?' \
| cut -d' ' -f2 \
)
- STATUS="${STATUS}${AHEAD_COUNT}"
+ statusmsg="${statusmsg}${ahead_count}"
fi
- fi
+ }
+fi
+
+# state message
+if [ "$ind_state" != '0' ]; then
+ statemsg=$( git state 2>/dev/null ) && state=" $color_state($statemsg)"
fi
# output the status string
-echo " $COLOR[${BRANCH}${STATUS}${COLOR}]"
+echo "$color[${branch}${statusmsg}${color}]$state$color_clr "
diff --git a/shortmaps b/shortmaps
new file mode 100644
index 0000000..5e27b01
--- /dev/null
+++ b/shortmaps
@@ -0,0 +1,25 @@
+a _git_add :add
+A _git_add :add -A
+B _git_bisect :bisect
+Bs _git_bisect :bisect start
+Bg _git_bisect :bisect good
+Bb _git_bisect :bisect bad
+Br _git_bisect :bisect reset
+c _git_commit :commit
+C _git_commit :commit -am
+co _git_checkout :checkout
+d _git_diff :diff
+f _git_fetch :fetch
+m _git_merge :merge
+p _git_push :push
+P _git_pull :pull
+R __git_rebase :rebase
+Ri _git_rebase :rebase --interactive
+Ra _git_rebase :rebase --abort
+Rc _git_rebase :rebase --continue
+s : :status
+S _git_stash :stash
+t : tig
+T _git_tag :tag
+- : :checkout -
+-- : |cd "$( git rev-parse --git-dir 2>/dev/null )/../"