diff --git a/README.md b/README.md index 9be4440..278de6f 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,7 @@ # Informative git prompt for bash and fish +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/magicmonty/bash-git-prompt?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge) + This prompt is a port of the "Informative git prompt for zsh" which you can find [here](https://github.com/olivierverdier/zsh-git-prompt) @@ -65,7 +67,7 @@ The symbols are as follows: - Local Status Symbols - ``✔``: repository clean - ``●n``: there are ``n`` staged files - - ``✖n``: there are ``n`` unmerged files + - ``✖n``: there are ``n`` files with merge conflict(s) - ``✚n``: there are ``n`` changed but *unstaged* files - ``…n``: there are ``n`` untracked files - ``⚑n``: there are ``n`` stash entries @@ -198,6 +200,10 @@ function prompt_callback { } ``` +- There are two helper functions that can be used within `prompt_callback`: + - `gp_set_window_title ` - sets the window title to the given string (should work for XTerm type terminals like in OS X or Ubuntu) + - `gp_truncate_pwd` - a function that returns the current PWD truncated to fit the current terminal width + - If you want to show the git prompt only if you are in a git repository you can set ``GIT_PROMPT_ONLY_IN_REPO=1`` before sourcing the gitprompt script @@ -302,9 +308,8 @@ Please leave a comment on the issue, that you want to fix it, so others know, th Pull requests are welcome. I will check them and merge them, if I think they help the project. ## Donations -I accept tips through [Gittip][tip] and [Flattr][flattr]. +I accept tips through [Flattr][flattr]. -[![Gittip](https://img.shields.io/gittip/magicmonty.svg?style=flat)](https://www.gittip.com/magicmonty/) [![Flattr](http://api.flattr.com/button/flattr-badge-large.png)](https://flattr.com/submit/auto?user_id=magicmonty&url=https%3A%2F%2Fgithub.com%2Fmagicmonty%2Fbash-git-prompt) [blog post]: http://sebastiancelis.com/2009/nov/16/zsh-prompt-git-users/ diff --git a/git-prompt-help.sh b/git-prompt-help.sh index 40e98cd..d15db0e 100755 --- a/git-prompt-help.sh +++ b/git-prompt-help.sh @@ -22,7 +22,7 @@ LOCALSTATUS is one of the following: ${GIT_PROMPT_CLEAN}${ResetColor} - repository clean ${GIT_PROMPT_STAGED}N${ResetColor} - N staged files - ${GIT_PROMPT_CONFLICTS}N${ResetColor} - N conflicted files + ${GIT_PROMPT_CONFLICTS}N${ResetColor} - N files with merge conflicts ${GIT_PROMPT_CHANGED}N${ResetColor} - N changed but *unstaged* files ${GIT_PROMPT_UNTRACKED}N${ResetColor} - N untracked files ${GIT_PROMPT_STASHED}N${ResetColor} - N stash entries diff --git a/gitprompt.sh b/gitprompt.sh index 323fcfc..178c6d1 100755 --- a/gitprompt.sh +++ b/gitprompt.sh @@ -1,14 +1,12 @@ #!/bin/sh -function async_run() -{ +function async_run() { { eval "$@" &> /dev/null }& } -function git_prompt_dir() -{ +function git_prompt_dir() { # assume the gitstatus.sh is in the same directory as this script # code thanks to http://stackoverflow.com/questions/59895 if [ -z "$__GIT_PROMPT_DIR" ]; then @@ -23,11 +21,10 @@ function git_prompt_dir() } function echoc() { - echo -e "${1}$2${ResetColor}" | sed 's/\\\]//g' | sed 's/\\\[//g' + echo -e "${1}$2${ResetColor}" | sed 's/\\\]//g' | sed 's/\\\[//g' } -function get_theme() -{ +function get_theme() { local CUSTOM_THEME_FILE="${HOME}/.git-prompt-colors.sh" local DEFAULT_THEME_FILE="${__GIT_PROMPT_DIR}/themes/Default.bgptheme" @@ -52,9 +49,11 @@ function get_theme() local theme="" # use default theme, if theme was not found - for themefile in $(cd "$__GIT_PROMPT_DIR/themes" && echo *); do - if [[ "${themefile}" = "${GIT_PROMPT_THEME}.bgptheme" ]]; then + for themefile in "${__GIT_PROMPT_DIR}/themes/"*.bgptheme; do + local basename=${themefile##*/} + if [[ "${basename%.bgptheme}" = "${GIT_PROMPT_THEME}" ]]; then theme=$GIT_PROMPT_THEME + break fi done @@ -67,25 +66,23 @@ function get_theme() fi } -function git_prompt_load_theme() -{ +function git_prompt_load_theme() { get_theme local DEFAULT_THEME_FILE="${__GIT_PROMPT_DIR}/themes/Default.bgptheme" source "${DEFAULT_THEME_FILE}" source "${__GIT_PROMPT_THEME_FILE}" } -function git_prompt_list_themes() -{ +function git_prompt_list_themes() { local oldTheme local oldThemeFile git_prompt_dir get_theme - for themefile in `ls "$__GIT_PROMPT_DIR/themes"`; do - local theme="$(basename $themefile .bgptheme)" - + for themefile in "${__GIT_PROMPT_DIR}/themes/"*.bgptheme; do + local basename=${themefile##*/} + local theme="${basename%.bgptheme}" if [[ "${GIT_PROMPT_THEME}" = "${theme}" ]]; then echoc ${Red} "*${theme}" else @@ -117,7 +114,7 @@ function git_prompt_make_custom_theme() { if [[ "${base}" = "Custom" ]]; then echoc ${Red} "You cannot use the custom theme as base" else - echoc ${Green} "Creating new cutom theme in \"${HOME}/.git-prompt-colors.sh\"" + echoc ${Green} "Creating new custom theme in \"${HOME}/.git-prompt-colors.sh\"" echoc ${DimYellow} "Please add ${Magenta}\"GIT_PROMPT_THEME=Custom\"${DimYellow} to your .bashrc to use this theme" if [[ "${base}" == "Default" ]]; then cp "${__GIT_PROMPT_DIR}/themes/Custom.bgptemplate" "${HOME}/.git-prompt-colors.sh" @@ -160,7 +157,7 @@ function gp_set_file_var() { # return 0 (true) if any FILEPATH is readable, set ENVAR to it # return 1 (false) if not -function gp_maybe_set_envar_to_path(){ +function gp_maybe_set_envar_to_path() { local envar="$1" shift local file @@ -191,27 +188,26 @@ git_prompt_reset() { # signalled, otherwise echos the original value of RETVAL gp_format_exit_status() { - local RETVAL="$1" - local SIGNAL - # Suppress STDERR in case RETVAL is not an integer (in such cases, RETVAL - # is echoed verbatim) - if [ "${RETVAL}" -gt 128 ] 2>/dev/null; then - SIGNAL=$(( ${RETVAL} - 128 )) - kill -l "${SIGNAL}" 2>/dev/null || echo "${RETVAL}" - else - echo "${RETVAL}" - fi + local RETVAL="$1" + local SIGNAL + # Suppress STDERR in case RETVAL is not an integer (in such cases, RETVAL + # is echoed verbatim) + if [ "${RETVAL}" -gt 128 ] 2>/dev/null; then + SIGNAL=$(( ${RETVAL} - 128 )) + kill -l "${SIGNAL}" 2>/dev/null || echo "${RETVAL}" + else + echo "${RETVAL}" + fi } -function git_prompt_config() -{ +function git_prompt_config() { #Checking if root to change output _isroot=false [[ $UID -eq 0 ]] && _isroot=true # There are two files related to colors: # - # prompt-colors.sh -- sets generic color names suitable for bash `PS1` prompt + # prompt-colors.sh -- sets generic color names suitable for bash 'PS1' prompt # git-prompt-colors.sh -- sets the GIT_PROMPT color scheme, using names from prompt-colors.sh if gp_set_file_var __PROMPT_COLORS_FILE prompt-colors.sh ; then @@ -225,6 +221,12 @@ function git_prompt_config() git_prompt_load_theme + if is_function prompt_callback; then + prompt_callback="prompt_callback" + else + prompt_callback="prompt_callback_default" + fi + if [ $GIT_PROMPT_LAST_COMMAND_STATE = 0 ]; then LAST_COMMAND_INDICATOR="$GIT_PROMPT_COMMAND_OK"; else @@ -270,16 +272,7 @@ function git_prompt_config() if [[ "$GIT_PROMPT_ONLY_IN_REPO" = 1 ]]; then EMPTY_PROMPT="$OLD_GITPROMPT" else - local ps="" - if [[ -n "$VIRTUAL_ENV" ]]; then - VENV=$(basename "${VIRTUAL_ENV}") - ps="${ps}${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" - fi - if [[ -n "$CONDA_DEFAULT_ENV" ]]; then - VENV=$(basename "${CONDA_DEFAULT_ENV}") - ps="${ps}${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" - fi - ps="$ps$PROMPT_START$($prompt_callback)$PROMPT_END" + local ps="$(gp_add_virtualenv_to_prompt)$PROMPT_START$($prompt_callback)$PROMPT_END" EMPTY_PROMPT="${ps//_LAST_COMMAND_INDICATOR_/${LAST_COMMAND_INDICATOR}}" fi @@ -289,8 +282,8 @@ function git_prompt_config() fi if [[ -z "$__GIT_STATUS_CMD" ]] ; then # if GIT_STATUS_CMD not defined.. git_prompt_dir - if ! gp_maybe_set_envar_to_path __GIT_STATUS_CMD "$__GIT_PROMPT_DIR/gitstatus.sh" ; then - echo 1>&2 "Cannot find gitstatus.sh!" + if ! gp_maybe_set_envar_to_path __GIT_STATUS_CMD "$__GIT_PROMPT_DIR/$GIT_PROMPT_STATUS_COMMAND" ; then + echo 1>&2 "Cannot find $GIT_PROMPT_STATUS_COMMAND!" fi # __GIT_STATUS_CMD defined fi @@ -319,7 +312,7 @@ function update_old_git_prompt() { function setGitPrompt() { update_old_git_prompt - local repo=`git rev-parse --show-toplevel 2> /dev/null` + local repo=$(git rev-parse --show-toplevel 2> /dev/null) if [[ ! -e "$repo" ]] && [[ "$GIT_PROMPT_ONLY_IN_REPO" = 1 ]]; then # we do not permit bash-git-prompt outside git repos, so nothing to do PS1="$OLD_GITPROMPT" @@ -363,42 +356,42 @@ function setGitPrompt() { _have_find_mmin=1 function olderThanMinutes() { - local matches - local find_exit_code + local matches + local find_exit_code - if [[ -z "$_find_command" ]]; then - if command -v gfind > /dev/null; then - _find_command=gfind - else - _find_command=find - fi - fi - - if [[ "$_have_find_mmin" = 1 ]]; then - matches=`"$_find_command" "$1" -mmin +"$2" 2> /dev/null` - find_exit_code="$?" - if [[ -n "$matches" ]]; then - return 0 - else - if [[ "$find_exit_code" != 0 ]]; then - _have_find_mmin=0 - else - return 1 - fi - fi + if [[ -z "$_find_command" ]]; then + if command -v gfind > /dev/null; then + _find_command=gfind + else + _find_command=find fi + fi - # try perl, solaris ships with perl - if command -v perl > /dev/null; then - perl -e '((time - (stat("'"$1"'"))[9]) / 60) > '"$2"' && exit(0) || exit(1)' - return "$?" + if [[ "$_have_find_mmin" = 1 ]]; then + matches=$("$_find_command" "$1" -mmin +"$2" 2> /dev/null) + find_exit_code="$?" + if [[ -n "$matches" ]]; then + return 0 else - echo >&2 - echo "WARNING: neither a find that supports -mmin (such as GNU find) or perl is available, disabling remote status checking. Install GNU find as gfind or perl to enable this feature, or set GIT_PROMPT_FETCH_REMOTE_STATUS=0 to disable this warning." >&2 - echo >&2 - GIT_PROMPT_FETCH_REMOTE_STATUS=0 + if [[ "$find_exit_code" != 0 ]]; then + _have_find_mmin=0 + else return 1 + fi fi + fi + + # try perl, solaris ships with perl + if command -v perl > /dev/null; then + perl -e '((time - (stat("'"$1"'"))[9]) / 60) > '"$2"' && exit(0) || exit(1)' + return "$?" + else + echo >&2 + echo "WARNING: neither a find that supports -mmin (such as GNU find) or perl is available, disabling remote status checking. Install GNU find as gfind or perl to enable this feature, or set GIT_PROMPT_FETCH_REMOTE_STATUS=0 to disable this warning." >&2 + echo >&2 + GIT_PROMPT_FETCH_REMOTE_STATUS=0 + return 1 + fi } function checkUpstream() { @@ -418,17 +411,16 @@ function checkUpstream() { fi } -function replaceSymbols() -{ +function replaceSymbols() { if [[ -z ${GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING} ]]; then GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING=L fi - local VALUE=${1//_AHEAD_/${GIT_PROMPT_SYMBOLS_AHEAD}} - local VALUE1=${VALUE//_BEHIND_/${GIT_PROMPT_SYMBOLS_BEHIND}} + local VALUE=${1//_AHEAD_/${GIT_PROMPT_SYMBOLS_AHEAD}} + local VALUE1=${VALUE//_BEHIND_/${GIT_PROMPT_SYMBOLS_BEHIND}} local VALUE2=${VALUE1//_NO_REMOTE_TRACKING_/${GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING}} - echo ${VALUE2//_PREHASH_/${GIT_PROMPT_SYMBOLS_PREHASH}} + echo ${VALUE2//_PREHASH_/${GIT_PROMPT_SYMBOLS_PREHASH}} } function updatePrompt() { @@ -499,18 +491,7 @@ function updatePrompt() { __chk_gitvar_status 'CLEAN' '-eq 1' - __add_status "$ResetColor$GIT_PROMPT_SUFFIX" - NEW_PROMPT="" - if [[ -n "$VIRTUAL_ENV" ]]; then - VENV=$(basename "${VIRTUAL_ENV}") - NEW_PROMPT="$NEW_PROMPT${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" - fi - - if [[ -n "$CONDA_DEFAULT_ENV" ]]; then - VENV=$(basename "${CONDA_DEFAULT_ENV}") - NEW_PROMPT="$NEW_PROMPT${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" - fi - - NEW_PROMPT="$NEW_PROMPT$PROMPT_START$($prompt_callback)$STATUS$PROMPT_END" + NEW_PROMPT="$(gp_add_virtualenv_to_prompt)$PROMPT_START$($prompt_callback)$STATUS$PROMPT_END" else NEW_PROMPT="$EMPTY_PROMPT" fi @@ -518,17 +499,45 @@ function updatePrompt() { PS1="${NEW_PROMPT//_LAST_COMMAND_INDICATOR_/${LAST_COMMAND_INDICATOR}}" } +# Helper function that returns virtual env information to be set in prompt +# Honors virtualenvs own setting VIRTUAL_ENV_DISABLE_PROMPT +function gp_add_virtualenv_to_prompt { + local ACCUMULATED_VENV_PROMPT="" + local VENV="" + if [[ -n "$VIRTUAL_ENV" && -z "${VIRTUAL_ENV_DISABLE_PROMPT-}" ]]; then + VENV=$(basename "${VIRTUAL_ENV}") + ACCUMULATED_VENV_PROMPT="${ACCUMULATED_VENV_PROMPT}${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" + fi + if [[ -n "$CONDA_DEFAULT_ENV" ]]; then + VENV=$(basename "${CONDA_DEFAULT_ENV}") + ACCUMULATED_VENV_PROMPT="${ACCUMULATED_VENV_PROMPT}${GIT_PROMPT_VIRTUALENV//_VIRTUALENV_/${VENV}}" + fi + echo "$ACCUMULATED_VENV_PROMPT" +} + +# Use exit status from declare command to determine whether input argument is a +# bash function +function is_function { + declare -Ff "$1" >/dev/null; +} +#Helper function that truncates $PWD depending on window width +function gp_truncate_pwd { + local newPWD="${PWD/#$HOME/~}" + local pwdmaxlen=$((${COLUMNS:-80}/3)) + [ ${#newPWD} -gt $pwdmaxlen ] && newPWD="...${newPWD:3-$pwdmaxlen}" + echo -n "$newPWD" +} + +#Sets the window title to the given argument string +function gp_set_window_title { + echo -ne "\033]0;"$@"\007" +} + function prompt_callback_default { - return + return } function gp_install_prompt { - if [ "`type -t prompt_callback`" = 'function' ]; then - prompt_callback="prompt_callback" - else - prompt_callback="prompt_callback_default" - fi - if [ -z "$OLD_GITPROMPT" ]; then OLD_GITPROMPT=$PS1 fi diff --git a/gitstatus.py b/gitstatus.py index f350409..da175ba 100755 --- a/gitstatus.py +++ b/gitstatus.py @@ -1,124 +1,139 @@ #!/usr/bin/env python # -*- coding: UTF-8 -*- -"""This module defines a Print function to use with python 2.x or 3.x., so we can use the prompt with older versions of Python too +"""This module defines a Print function to use with python 2.x or 3.x., so we can use the prompt with older versions of +Python too It's interface is that of python 3.0's print. See http://docs.python.org/3.0/library/functions.html?highlight=print#print -Shamelessly ripped from http://www.daniweb.com/software-development/python/code/217214/a-print-function-for-different-versions-of-python +Shamelessly ripped from +http://www.daniweb.com/software-development/python/code/217214/a-print-function-for-different-versions-of-python """ - -__all__ = ["Print"] -import sys -try: - Print = eval("print") # python 3.0 case -except SyntaxError: - try: - D = dict() - exec("from __future__ import print_function\np=print", D) - Print = D["p"] # 2.6 case - del D - except SyntaxError: - del D - def Print(*args, **kwd): # 2.4, 2.5, define our own Print function - fout = kwd.get("file", sys.stdout) - w = fout.write - if args: - w(str(args[0])) - sep = kwd.get("sep", " ") - for a in args[1:]: - w(sep) - w(str(a)) - w(kwd.get("end", "\n")) - - # change those symbols to whatever you prefer -symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash':':'} +symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash': ':'} +import sys +import re from subprocess import Popen, PIPE -import sys -gitsym = Popen(['git', 'symbolic-ref', 'HEAD'], stdout=PIPE, stderr=PIPE) -branch, error = gitsym.communicate() - -error_string = error.decode('utf-8') - -if 'fatal: Not a git repository' in error_string: - sys.exit(0) - -branch = branch.decode('utf-8').strip()[11:] - -res, err = Popen(['git','diff','--name-status'], stdout=PIPE, stderr=PIPE).communicate() -err_string = err.decode('utf-8') - -if 'fatal' in err_string: - sys.exit(0) - -changed_files = [namestat[0] for namestat in res.splitlines()] -staged_files = [namestat[0] for namestat in Popen(['git','diff', '--staged','--name-status'], stdout=PIPE).communicate()[0].splitlines()] -nb_changed = len(changed_files) - changed_files.count('U') -nb_U = staged_files.count('U') -nb_staged = len(staged_files) - nb_U -staged = str(nb_staged) -conflicts = str(nb_U) -changed = str(nb_changed) -status_lines = Popen(['git','status','-s','-uall'],stdout=PIPE).communicate()[0].splitlines() -untracked_lines = [a for a in map(lambda s: s.decode('utf-8'), status_lines) if a.startswith("??")] -nb_untracked = len(untracked_lines) -untracked = str(nb_untracked) -stashes = Popen(['git','stash','list'],stdout=PIPE).communicate()[0].splitlines() -nb_stashed = len(stashes) -stashed = str(nb_stashed) - -if not nb_changed and not nb_staged and not nb_U and not nb_untracked and not nb_stashed: - clean = '1' -else: - clean = '0' +__all__ = ["Print"] +try: + Print = eval("print") # python 3.0 case +except SyntaxError: + D = dict() + try: + exec ("from __future__ import print_function\np=print", D) + Print = D["p"] # 2.6 case + except SyntaxError: + def Print(*args, **kwd): # 2.4, 2.5, define our own Print function + fout = kwd.get("file", sys.stdout) + w = fout.write + if args: + w(str(args[0])) + sep = kwd.get("sep", " ") + for a in args[1:]: + w(sep) + w(str(a)) + w(kwd.get("end", "\n")) + finally: + del D + + +def get_tag_or_hash(): + cmd = Popen(['git', 'describe', '--exact-match'], stdout=PIPE, stderr=PIPE) + so, se = cmd.communicate() + tag = '%s' % so.decode('utf-8').strip() + + if tag: + return tag + else: + cmd = Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=PIPE, stderr=PIPE) + so, se = cmd.communicate() + hash_name = '%s' % so.decode('utf-8').strip() + return ''.join([symbols['prehash'], hash_name]) + + +def get_stash(): + cmd = Popen(['git', 'rev-parse', '--git-dir'], stdout=PIPE, stderr=PIPE) + so, se = cmd.communicate() + stash_file = '%s%s' % (so.decode('utf-8').rstrip(), '/logs/refs/stash') + + try: + with open(stash_file) as f: + return sum(1 for _ in f) + except IOError: + return 0 + + +# `git status --porcelain --branch` can collect all information +# branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind +po = Popen(['git', 'status', '--porcelain', '--branch'], env={'LC_ALL': 'C'}, stdout=PIPE, stderr=PIPE) +stdout, stderr = po.communicate() +if po.returncode != 0: + sys.exit(0) # Not a git repository + +# collect git status information +untracked, staged, changed, conflicts = [], [], [], [] +num_ahead, num_behind = 0, 0 +ahead, behind = '', '' +branch = '' remote = '' - -tag, tag_error = Popen(['git', 'describe', '--exact-match'], stdout=PIPE, stderr=PIPE).communicate() - -if not branch: # not on any branch - if tag: # if we are on a tag, print the tag's name - branch = tag - else: - branch = symbols['prehash']+ Popen(['git','rev-parse','--short','HEAD'], stdout=PIPE).communicate()[0].decode('utf-8')[:-1] +status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()] +for st in status: + if st[0] == '#' and st[1] == '#': + if re.search('Initial commit on', st[2]): + branch = st[2].split(' ')[-1] + elif re.search('no branch', st[2]): # detached status + branch = get_tag_or_hash() + elif len(st[2].strip().split('...')) == 1: + branch = st[2].strip() + else: + # current and remote branch info + branch, rest = st[2].strip().split('...') + if len(rest.split(' ')) == 1: + # remote_branch = rest.split(' ')[0] + pass + else: + # ahead or behind + divergence = ' '.join(rest.split(' ')[1:]) + divergence = divergence.lstrip('[').rstrip(']') + for div in divergence.split(', '): + if 'ahead' in div: + num_ahead = int(div[len('ahead '):].strip()) + ahead = '%s%s' % (symbols['ahead of'], num_ahead) + elif 'behind' in div: + num_behind = int(div[len('behind '):].strip()) + behind = '%s%s' % (symbols['behind'], num_behind) + remote = ''.join([behind, ahead]) + elif st[0] == '?' and st[1] == '?': + untracked.append(st) + else: + if st[1] == 'M': + changed.append(st) + if st[0] == 'U': + conflicts.append(st) + elif st[0] != ' ': + staged.append(st) + +stashed = get_stash() +if not changed and not staged and not conflicts and not untracked and not stashed: + clean = 1 else: - remote_name = Popen(['git','config','branch.%s.remote' % branch], stdout=PIPE).communicate()[0].strip() - if remote_name: - merge_name = Popen(['git','config','branch.%s.merge' % branch], stdout=PIPE).communicate()[0].strip() - else: - remote_name = "origin" - merge_name = "refs/heads/%s" % branch - - if remote_name == '.': # local - remote_ref = merge_name - else: - remote_ref = 'refs/remotes/%s/%s' % (remote_name, merge_name[11:]) - revgit = Popen(['git', 'rev-list', '--left-right', '%s...HEAD' % remote_ref],stdout=PIPE, stderr=PIPE) - revlist = revgit.communicate()[0] - if revgit.poll(): # fallback to local - revlist = Popen(['git', 'rev-list', '--left-right', '%s...HEAD' % merge_name],stdout=PIPE, stderr=PIPE).communicate()[0] - behead = revlist.splitlines() - ahead = len([x for x in behead if x[0]=='>']) - behind = len(behead) - ahead - if behind: - remote += '%s%s' % (symbols['behind'], behind) - if ahead: - remote += '%s%s' % (symbols['ahead of'], ahead) + clean = 0 if remote == "": - remote = '.' + remote = '.' out = '\n'.join([ - str(branch), - str(remote), - staged, - conflicts, - changed, - untracked, - stashed, - clean]) + branch, + remote.decode('utf-8'), + str(len(staged)), + str(len(conflicts)), + str(len(changed)), + str(len(untracked)), + str(stashed), + str(clean) +]) Print(out) diff --git a/gitstatus.sh b/gitstatus.sh index 68d1a08..95d312a 100755 --- a/gitstatus.sh +++ b/gitstatus.sh @@ -5,10 +5,6 @@ # # Alan K. Stebbens [http://github.com/aks] -# helper functions -count_lines() { echo "$1" | egrep -c "^$2" ; } -all_lines() { echo "$1" | grep -v "^$" | wc -l ; } - if [ -z "${__GIT_PROMPT_DIR}" ]; then SOURCE="${BASH_SOURCE[0]}" while [ -h "${SOURCE}" ]; do @@ -19,81 +15,72 @@ if [ -z "${__GIT_PROMPT_DIR}" ]; then __GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" fi -gitsym=`git symbolic-ref HEAD` - -# if "fatal: Not a git repo .., then exit -case "$gitsym" in fatal*) exit 0 ;; esac - -# the current branch is the tail end of the symbolic reference -branch="${gitsym##refs/heads/}" # get the basename after "refs/heads/" - -gitstatus=`git diff --name-status 2>&1` - -# if the diff is fatal, exit now -case "$gitstatus" in fatal*) exit 0 ;; esac - - -staged_files=`git diff --staged --name-status` - -num_changed=$(( `all_lines "$gitstatus"` - `count_lines "$gitstatus" U` )) -num_conflicts=`count_lines "$staged_files" U` -num_staged=$(( `all_lines "$staged_files"` - num_conflicts )) -num_untracked=`git ls-files --others --exclude-standard $(git rev-parse --show-cdup) | wc -l` -if [[ "$__GIT_PROMPT_IGNORE_STASH" = "1" ]]; then - num_stashed=0 -else - num_stashed=`git stash list | wc -l` +gitstatus=$( LC_ALL=C git status --untracked-files=all --porcelain --branch ) + +# if the status is fatal, exit now +[[ "$?" -ne 0 ]] && exit 0 + +num_staged=0 +num_changed=0 +num_conflicts=0 +num_untracked=0 +while IFS='' read -r line || [[ -n "$line" ]]; do + status=${line:0:2} + case "$status" in + \#\#) branch_line="${line/\.\.\./^}" ;; + ?M) ((num_changed++)) ;; + U?) ((num_conflicts++)) ;; + \?\?) ((num_untracked++)) ;; + *) ((num_staged++)) ;; + esac +done <<< "$gitstatus" + +num_stashed=0 +if [[ "$__GIT_PROMPT_IGNORE_STASH" != "1" ]]; then + stash_file="$( git rev-parse --git-dir )/logs/refs/stash" + if [[ -e "${stash_file}" ]]; then + while IFS='' read -r wcline || [[ -n "$wcline" ]]; do + ((num_stashed++)) + done < ${stash_file} + fi fi clean=0 -if (( num_changed == 0 && num_staged == 0 && num_U == 0 && num_untracked == 0 && num_stashed == 0 )) ; then +if (( num_changed == 0 && num_staged == 0 && num_untracked == 0 && num_stashed == 0 )) ; then clean=1 fi +IFS="^" read -ra branch_fields <<< "${branch_line/\#\# }" +branch="${branch_fields[0]}" remote= -if [[ -z "$branch" ]]; then - tag=`git describe --exact-match` +if [[ "$branch" == *"Initial commit on"* ]]; then + IFS=" " read -ra fields <<< "$branch" + branch="${fields[3]}" + remote="_NO_REMOTE_TRACKING_" +elif [[ "$branch" == *"no branch"* ]]; then + tag=$( git describe --exact-match ) if [[ -n "$tag" ]]; then branch="$tag" else - branch="_PREHASH_`git rev-parse --short HEAD`" + branch="_PREHASH_$( git rev-parse --short HEAD )" fi else - remote_name=`git config branch.${branch}.remote` - - if [[ -n "$remote_name" ]]; then - merge_name=`git config branch.${branch}.merge` + if [[ "${#branch_fields[@]}" -eq 1 ]]; then + remote="_NO_REMOTE_TRACKING_" else - remote_name='origin' - merge_name="refs/heads/${branch}" - fi - - if [[ "$remote_name" == '.' ]]; then - remote_ref="$merge_name" - else - remote_ref="refs/remotes/$remote_name/${merge_name##refs/heads/}" - fi - - # detect if the local branch have a remote tracking branch - cmd_output=$(git rev-parse --abbrev-ref ${branch}@{upstream} 2>&1 >/dev/null) - - if [ `count_lines "$cmd_output" "fatal: No upstream"` == 1 ] ; then - has_remote_tracking=0 - else - has_remote_tracking=1 - fi - - # get the revision list, and count the leading "<" and ">" - revgit=`git rev-list --left-right ${remote_ref}...HEAD` - num_revs=`all_lines "$revgit"` - num_ahead=`count_lines "$revgit" "^>"` - num_behind=$(( num_revs - num_ahead )) - if (( num_behind > 0 )) ; then - remote="${remote}_BEHIND_${num_behind}" - fi - if (( num_ahead > 0 )) ; then - remote="${remote}_AHEAD_${num_ahead}" + IFS="[,]" read -ra remote_fields <<< "${branch_fields[1]}" + for remote_field in "${remote_fields[@]}"; do + if [[ "$remote_field" == *ahead* ]]; then + num_ahead=${remote_field:6} + ahead="_AHEAD_${num_ahead}" + fi + if [[ "$remote_field" == *behind* ]]; then + num_behind=${remote_field:7} + behind="_BEHIND_${num_behind# }" + fi + done + remote="${behind}${ahead}" fi fi @@ -101,12 +88,14 @@ if [[ -z "$remote" ]] ; then remote='.' fi -if [[ "$has_remote_tracking" == "0" ]] ; then - remote='_NO_REMOTE_TRACKING_' -fi - -for w in "$branch" "$remote" $num_staged $num_conflicts $num_changed $num_untracked $num_stashed $clean ; do - echo "$w" -done +printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n" \ + "$branch" \ + "$remote" \ + $num_staged \ + $num_conflicts \ + $num_changed \ + $num_untracked \ + $num_stashed \ + $clean exit diff --git a/gitstatus_pre-1.7.10.sh b/gitstatus_pre-1.7.10.sh new file mode 100755 index 0000000..f613709 --- /dev/null +++ b/gitstatus_pre-1.7.10.sh @@ -0,0 +1,123 @@ +#!/bin/bash +# -*- coding: UTF-8 -*- +# gitstatus.sh -- produce the current git repo status on STDOUT +# Functionally equivalent to 'gitstatus.py', but written in bash (not python). +# +# Alan K. Stebbens [http://github.com/aks] + +# helper functions +count_lines() { echo "$1" | egrep -c "^$2" ; } +all_lines() { echo "$1" | grep -v "^$" | wc -l ; } + +if [ -z "${__GIT_PROMPT_DIR}" ]; then + SOURCE="${BASH_SOURCE[0]}" + while [ -h "${SOURCE}" ]; do + DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" + SOURCE="$(readlink "${SOURCE}")" + [[ $SOURCE != /* ]] && SOURCE="${DIR}/${SOURCE}" + done + __GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" +fi + +gitsym=`git symbolic-ref HEAD` + +# if "fatal: Not a git repo .., then exit +case "$gitsym" in fatal*) exit 0 ;; esac + +# the current branch is the tail end of the symbolic reference +branch="${gitsym##refs/heads/}" # get the basename after "refs/heads/" + +gitstatus=`git diff --name-status 2>&1` + +# if the diff is fatal, exit now +case "$gitstatus" in fatal*) exit 0 ;; esac + + +staged_files=`git diff --staged --name-status` + +num_changed=$(( `all_lines "$gitstatus"` - `count_lines "$gitstatus" U` )) +num_conflicts=`count_lines "$staged_files" U` +num_staged=$(( `all_lines "$staged_files"` - num_conflicts )) +num_untracked=`git ls-files --others --exclude-standard $(git rev-parse --show-cdup) | wc -l` + +num_stashed=0 +if [[ "$__GIT_PROMPT_IGNORE_STASH" != "1" ]]; then + stash_file="$( git rev-parse --git-dir )/logs/refs/stash" + if [[ -e "${stash_file}" ]]; then + while IFS='' read -r wcline || [[ -n "$wcline" ]]; do + ((num_stashed++)) + done < ${stash_file} + fi +fi + +clean=0 +if (( num_changed == 0 && num_staged == 0 && num_U == 0 && num_untracked == 0 && num_stashed == 0 )) ; then + clean=1 +fi + +remote= + +if [[ -z "$branch" ]]; then + tag=`git describe --exact-match` + if [[ -n "$tag" ]]; then + branch="$tag" + else + branch="_PREHASH_`git rev-parse --short HEAD`" + fi +else + remote_name=`git config branch.${branch}.remote` + + if [[ -n "$remote_name" ]]; then + merge_name=`git config branch.${branch}.merge` + else + remote_name='origin' + merge_name="refs/heads/${branch}" + fi + + if [[ "$remote_name" == '.' ]]; then + remote_ref="$merge_name" + else + remote_ref="refs/remotes/$remote_name/${merge_name##refs/heads/}" + fi + + # detect if the local branch have a remote tracking branch + cmd_output=$(git rev-parse --abbrev-ref ${branch}@{upstream} 2>&1 >/dev/null) + + if [[ $? == 0 ]]; then + has_remote_tracking=1 + else + has_remote_tracking=0 + fi + + # get the revision list, and count the leading "<" and ">" + revgit=`git rev-list --left-right ${remote_ref}...HEAD` + num_revs=`all_lines "$revgit"` + num_ahead=`count_lines "$revgit" "^>"` + num_behind=$(( num_revs - num_ahead )) + if (( num_behind > 0 )) ; then + remote="${remote}_BEHIND_${num_behind}" + fi + if (( num_ahead > 0 )) ; then + remote="${remote}_AHEAD_${num_ahead}" + fi +fi + +if [[ -z "$remote" ]] ; then + remote='.' +fi + +if [[ "$has_remote_tracking" == "0" ]] ; then + remote='_NO_REMOTE_TRACKING_' +fi + +printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n" \ + "$branch" \ + "$remote" \ + $num_staged \ + $num_conflicts \ + $num_changed \ + $num_untracked \ + $num_stashed \ + $clean + +exit diff --git a/themes/Chmike.bgptheme b/themes/Chmike.bgptheme new file mode 100644 index 0000000..33f9655 --- /dev/null +++ b/themes/Chmike.bgptheme @@ -0,0 +1,31 @@ +# This theme for gitprompt.sh is designed for dark color schemes +# it is clone of oh-my-zsh crunch theme style with exit status + +override_git_prompt_colors() { + if [ -e ~/.rvm/bin/rvm-prompt ]; then + RUBY_PROMPT='{$(~/.rvm/bin/rvm-prompt i v)}' + else + if command -v rbenv > /dev/null; then + RUBY_PROMPT='{$(rbenv version | sed -e "s/ (set.*$//")}' + fi + fi + Time12a="\$(date +%H:%M)" + + + GIT_PROMPT_THEME_NAME="Chmike" + GIT_PROMPT_BRANCH="${Green}" + GIT_PROMPT_REMOTE=" " + GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING="⭐" + + GIT_PROMPT_SEPARATOR="|" + GIT_PROMPT_CHANGED="${Cyan}❗" + GIT_PROMPT_STAGED="${Yellow}▸" + GIT_PROMPT_UNTRACKED="${Blue}…" + GIT_PROMPT_CONFLICTS="${BoldRed}❓" + GIT_PROMPT_STASHED="${Magenta}>" + GIT_PROMPT_CLEAN="${Green}✔ " + GIT_PROMPT_COMMAND_OK="${Green}✔" + GIT_PROMPT_COMMAND_FAIL="${Red}✘" +} + +reload_git_prompt_colors "Crunch" diff --git a/themes/Default.bgptheme b/themes/Default.bgptheme index b37b0e3..fd823cb 100644 --- a/themes/Default.bgptheme +++ b/themes/Default.bgptheme @@ -16,6 +16,7 @@ unset_git_prompt_colors() { unset GIT_PROMPT_CLEAN unset GIT_PROMPT_COMMAND_OK unset GIT_PROMPT_COMMAND_FAIL + unset GIT_PROMPT_STATUS_COMMAND unset GIT_PROMPT_VIRTUALENV unset GIT_PROMPT_START_USER unset GIT_PROMPT_START_ROOT @@ -51,7 +52,7 @@ define_undefined_git_prompt_colors() { if [[ -z ${GIT_PROMPT_STASHED} ]]; then GIT_PROMPT_STASHED="${BoldBlue}⚑ "; fi # the number of stashed files/dir if [[ -z ${GIT_PROMPT_CLEAN} ]]; then GIT_PROMPT_CLEAN="${BoldGreen}✔"; fi # a colored flag indicating a "clean" repo - # For the command indicator, the placeholder _LAST_COMMAND_STATE_ + # For the command indicator, the placeholder _LAST_COMMAND_STATE_ # will be replaced with the exit code of the last command # e.g. # GIT_PROMPT_COMMAND_OK="${Green}✔-_LAST_COMMAND_STATE_ " # indicator if the last command returned with an exit code of 0 @@ -60,11 +61,18 @@ define_undefined_git_prompt_colors() { if [[ -z ${GIT_PROMPT_COMMAND_OK} ]]; then GIT_PROMPT_COMMAND_OK="${Green}✔"; fi # indicator if the last command returned with an exit code of 0 if [[ -z ${GIT_PROMPT_COMMAND_FAIL} ]]; then GIT_PROMPT_COMMAND_FAIL="${Red}✘-_LAST_COMMAND_STATE_"; fi # indicator if the last command returned with an exit code of other than 0 + # Possible to change which command is used to create git status information + # There are three options: + # 1) gitstatus.sh (uses git status --branch --porcelain - fast, requires git > 1.7.10) + # 2) gitstatus_pre-1.7.10.sh (Uses a variety of git commands and pipes - slower, works with older git clients) + # 3) gitstatus.py (Unsupported, lack features found in the bash versions) + if [[ -z ${GIT_PROMPT_STATUS_COMMAND} ]]; then GIT_PROMPT_STATUS_COMMAND="gitstatus.sh"; fi # Point out the command to get the git status from + # template for displaying the current virtual environment - # use the placeholder _VIRTUALENV_ will be replaced with + # use the placeholder _VIRTUALENV_ will be replaced with # the name of the current virtual environment (currently CONDA and VIRTUAL_ENV) if [[ -z ${GIT_PROMPT_VIRTUALENV} ]]; then GIT_PROMPT_VIRTUALENV="(${Blue}_VIRTUALENV_${ResetColor}) "; fi - + # _LAST_COMMAND_INDICATOR_ will be replaced by the appropriate GIT_PROMPT_COMMAND_OK OR GIT_PROMPT_COMMAND_FAIL if [[ -z ${GIT_PROMPT_START_USER} ]]; then GIT_PROMPT_START_USER="_LAST_COMMAND_INDICATOR_ ${Yellow}${PathShort}${ResetColor}"; fi if [[ -z ${GIT_PROMPT_START_ROOT} ]]; then GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}"; fi @@ -75,7 +83,7 @@ define_undefined_git_prompt_colors() { if [[ -z ${GIT_PROMPT_SYMBOLS_AHEAD} ]]; then GIT_PROMPT_SYMBOLS_AHEAD="↑·"; fi # The symbol for "n versions ahead of origin" if [[ -z ${GIT_PROMPT_SYMBOLS_BEHIND} ]]; then GIT_PROMPT_SYMBOLS_BEHIND="↓·"; fi # The symbol for "n versions behind of origin" if [[ -z ${GIT_PROMPT_SYMBOLS_PREHASH} ]]; then GIT_PROMPT_SYMBOLS_PREHASH=":"; fi # Written before hash of commit, if no name could be found - if [[ -z ${GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING} ]]; then GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING="L"; fi # This symbol is written after the branch, if the branch is not tracked + if [[ -z ${GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING} ]]; then GIT_PROMPT_SYMBOLS_NO_REMOTE_TRACKING="L"; fi # This symbol is written after the branch, if the branch is not tracked } # call only from theme file diff --git a/themes/TruncatedPwd_WindowTitle.bgptheme b/themes/TruncatedPwd_WindowTitle.bgptheme new file mode 100644 index 0000000..b7f94e2 --- /dev/null +++ b/themes/TruncatedPwd_WindowTitle.bgptheme @@ -0,0 +1,41 @@ +############################################################################## +# Changes the prompt to a Debian-style one that truncates pwd to a max length +# depending on the terminal column width. Also uses the prompt_callback +# function of bash-git-prompt to set the window title to almost the same +# Debian-style. +# +# The prompt will use a Debian-style on the form +# +# [user@host: ] [bash-git-prompt-info] +# HH:MM $ +# +# The window title will have the form +# user@host: +# +# Example usage: +# if [ -f ~/.bash-git-prompt/gitprompt.sh ]; then +# GIT_PROMPT_THEME=TruncatedPwd_WindowTitle +# source ~/.bash-git-prompt/gitprompt.sh +# fi +# +# oGre [https://github.com/ogr3] +############################################################################## +override_git_prompt_colors() { + GIT_PROMPT_THEME_NAME="TruncatedPwd_WindowTitle" + + #Overrides the prompt_callback function used by bash-git-prompt + function prompt_callback { + local PS1="\u@\h: $(gp_truncate_pwd)" + gp_set_window_title "$PS1" + echo -n "[${PS1}]${ResetColor}" + } + + Time12a="\$(date +%H:%M)" + + GIT_PROMPT_START_USER="${Yellow}" + GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}" + GIT_PROMPT_END_USER=" _LAST_COMMAND_INDICATOR_\n${White}${Time12a}${ResetColor} $ " + GIT_PROMPT_END_ROOT=" _LAST_COMMAND_INDICATOR_\n${White}${Time12a}${ResetColor} # " +} + +reload_git_prompt_colors "TruncatedPwd_WindowTitle" diff --git a/themes/TruncatedPwd_WindowTitle_NoExitState.bgptheme b/themes/TruncatedPwd_WindowTitle_NoExitState.bgptheme new file mode 100644 index 0000000..6e63676 --- /dev/null +++ b/themes/TruncatedPwd_WindowTitle_NoExitState.bgptheme @@ -0,0 +1,41 @@ +############################################################################## +# Changes the prompt to a Debian-style one that truncates pwd to a max length +# depending on the terminal column width. Also uses the prompt_callback +# function of bash-git-prompt to set the window title to almost the same +# Debian-style. +# +# The prompt will use a Debian-style on the form +# +# [user@host: ] [bash-git-prompt-info] +# HH:MM $ +# +# The window title will have the form +# user@host: +# +# Example usage: +# if [ -f ~/.bash-git-prompt/gitprompt.sh ]; then +# GIT_PROMPT_THEME=TruncatedPwd_WindowTitle_NoExitState +# source ~/.bash-git-prompt/gitprompt.sh +# fi +# +# oGre [https://github.com/ogr3] +############################################################################## +override_git_prompt_colors() { + GIT_PROMPT_THEME_NAME="TruncatedPwd_WindowTitle_NoExitState" + + #Overrides the prompt_callback function used by bash-git-prompt + function prompt_callback { + local PS1="\u@\h: $(gp_truncate_pwd)" + gp_set_window_title "$PS1" + echo -n "[${PS1}]${ResetColor}" + } + + Time12a="\$(date +%H:%M)" + + GIT_PROMPT_START_USER="${Yellow}" + GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}" + GIT_PROMPT_END_USER="\n${White}${Time12a}${ResetColor} $ " + GIT_PROMPT_END_ROOT="\n${White}${Time12a}${ResetColor} # " +} + +reload_git_prompt_colors "TruncatedPwd_WindowTitle_NoExitState"