master
oGre 10 years ago
commit 8cd60a61da
  1. 2
      README.md
  2. 2
      git-prompt-help.sh
  3. 176
      gitstatus.py
  4. 133
      gitstatus.sh
  5. 8
      themes/TruncatedPwd_WindowTitle.bgptheme
  6. 54
      themes/TruncatedPwd_WindowTitle_NoExitState.bgptheme

@ -67,7 +67,7 @@ The symbols are as follows:
- Local Status Symbols - Local Status Symbols
- ``✔``: repository clean - ``✔``: repository clean
- ``●n``: there are ``n`` staged files - ``●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`` changed but *unstaged* files
- ``…n``: there are ``n`` untracked files - ``…n``: there are ``n`` untracked files
- ``⚑n``: there are ``n`` stash entries - ``⚑n``: there are ``n`` stash entries

@ -22,7 +22,7 @@ LOCALSTATUS is one of the following:
${GIT_PROMPT_CLEAN}${ResetColor} - repository clean ${GIT_PROMPT_CLEAN}${ResetColor} - repository clean
${GIT_PROMPT_STAGED}N${ResetColor} - N staged files ${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_CHANGED}N${ResetColor} - N changed but *unstaged* files
${GIT_PROMPT_UNTRACKED}N${ResetColor} - N untracked files ${GIT_PROMPT_UNTRACKED}N${ResetColor} - N untracked files
${GIT_PROMPT_STASHED}N${ResetColor} - N stash entries ${GIT_PROMPT_STASHED}N${ResetColor} - N stash entries

@ -32,93 +32,111 @@ except SyntaxError:
w(str(a)) w(str(a))
w(kwd.get("end", "\n")) w(kwd.get("end", "\n"))
# change those symbols to whatever you prefer # change those symbols to whatever you prefer
symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash':':'} symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash':':'}
from subprocess import Popen, PIPE
import sys import sys
gitsym = Popen(['git', 'symbolic-ref', 'HEAD'], stdout=PIPE, stderr=PIPE) import re
branch, error = gitsym.communicate() import shlex
from subprocess import Popen, PIPE, check_output
error_string = error.decode('utf-8')
if 'fatal: Not a git repository' in error_string: def get_tagname_or_hash():
sys.exit(0) """return tagname if exists else hash"""
cmd = 'git log -1 --format="%h%d"'
branch = branch.decode('utf-8').strip()[11:] output = check_output(shlex.split(cmd)).decode('utf-8').strip()
hash_, tagname = None, None
res, err = Popen(['git','diff','--name-status'], stdout=PIPE, stderr=PIPE).communicate() # get hash
err_string = err.decode('utf-8') m = re.search('\(.*\)$', output)
if m:
if 'fatal' in err_string: hash_ = output[:m.start()-1]
sys.exit(0) # get tagname
m = re.search('tag: .*[,\)]', output)
changed_files = [namestat[0] for namestat in res.splitlines()] if m:
staged_files = [namestat[0] for namestat in Popen(['git','diff', '--staged','--name-status'], stdout=PIPE).communicate()[0].splitlines()] tagname = 'tags/' + output[m.start()+len('tag: '): m.end()-1]
nb_changed = len(changed_files) - changed_files.count('U')
nb_U = staged_files.count('U') if tagname:
nb_staged = len(staged_files) - nb_U return tagname
staged = str(nb_staged) elif hash_:
conflicts = str(nb_U) return hash_
changed = str(nb_changed) return None
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("??")] def get_stash():
nb_untracked = len(untracked_lines) cmd = Popen(['git', 'rev-parse', '--git-dir'], stdout=PIPE, stderr=PIPE)
untracked = str(nb_untracked) so, se = cmd.communicate()
stashes = Popen(['git','stash','list'],stdout=PIPE).communicate()[0].splitlines() stashFile = '%s%s' % (so.decode('utf-8').rstrip(),'/logs/refs/stash')
nb_stashed = len(stashes)
stashed = str(nb_stashed) try:
with open(stashFile) as f:
if not nb_changed and not nb_staged and not nb_U and not nb_untracked and not nb_stashed: return sum(1 for _ in f)
clean = '1' except IOError:
else: return 0
clean = '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, sterr = po.communicate()
if po.returncode != 0:
sys.exit(0) # Not a git repository
# collect git status information
untracked, staged, changed, conflicts = [], [], [], []
ahead, behind = 0, 0
remote = '' remote = ''
status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()]
tag, tag_error = Popen(['git', 'describe', '--exact-match'], stdout=PIPE, stderr=PIPE).communicate() for st in status:
if st[0] == '#' and st[1] == '#':
if not branch: # not on any branch if re.search('Initial commit on', st[2]):
if tag: # if we are on a tag, print the tag's name branch = st[2].split(' ')[-1]
branch = tag elif re.search('no branch', st[2]): # detached status
else: branch = get_tagname_or_hash()
branch = symbols['prehash']+ Popen(['git','rev-parse','--short','HEAD'], stdout=PIPE).communicate()[0].decode('utf-8')[:-1] 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:
ahead = int(div[len('ahead '):].strip())
remote += '%s%s' % (symbols['ahead of'], ahead)
elif 'behind' in div:
behind = int(div[len('behind '):].strip())
remote += '%s%s' % (symbols['behind'], behind)
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: else:
remote_name = Popen(['git','config','branch.%s.remote' % branch], stdout=PIPE).communicate()[0].strip() clean = 0
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)
if remote == "": if remote == "":
remote = '.' remote = '.'
out = '\n'.join([ out = '\n'.join([
str(branch), branch,
str(remote), remote.decode('utf-8'),
staged, str(len(staged)),
conflicts, str(len(conflicts)),
changed, str(len(changed)),
untracked, str(len(untracked)),
stashed, str(stashed),
clean]) str(clean)
])
Print(out) Print(out)

@ -5,10 +5,6 @@
# #
# Alan K. Stebbens <aks@stebbens.org> [http://github.com/aks] # Alan K. Stebbens <aks@stebbens.org> [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 if [ -z "${__GIT_PROMPT_DIR}" ]; then
SOURCE="${BASH_SOURCE[0]}" SOURCE="${BASH_SOURCE[0]}"
while [ -h "${SOURCE}" ]; do while [ -h "${SOURCE}" ]; do
@ -19,81 +15,72 @@ if [ -z "${__GIT_PROMPT_DIR}" ]; then
__GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )" __GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
fi fi
gitsym=`git symbolic-ref HEAD` gitstatus=$( LC_ALL=C git status --porcelain --branch )
# if "fatal: Not a git repo .., then exit # if the status is fatal, exit now
case "$gitsym" in fatal*) exit 0 ;; esac [[ "$?" -ne 0 ]] && exit 0
# the current branch is the tail end of the symbolic reference num_staged=0
branch="${gitsym##refs/heads/}" # get the basename after "refs/heads/" num_changed=0
num_conflicts=0
gitstatus=`git diff --name-status 2>&1` num_untracked=0
while IFS='' read -r line || [[ -n "$line" ]]; do
# if the diff is fatal, exit now status=${line:0:2}
case "$gitstatus" in fatal*) exit 0 ;; esac case "$status" in
\#\#) branch_line="$line" ;;
?M) ((num_changed++)) ;;
staged_files=`git diff --staged --name-status` U?) ((num_conflicts++)) ;;
\?\?) ((num_untracked++)) ;;
num_changed=$(( `all_lines "$gitstatus"` - `count_lines "$gitstatus" U` )) *) ((num_staged++)) ;;
num_conflicts=`count_lines "$staged_files" U` esac
num_staged=$(( `all_lines "$staged_files"` - num_conflicts )) done <<< "$gitstatus"
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
num_stashed=0 if [[ "$__GIT_PROMPT_IGNORE_STASH" != "1" ]]; then
else stash_file="$( git rev-parse --git-dir )/logs/refs/stash"
num_stashed=`git stash list | wc -l` if [[ -e "${stash_file}" ]]; then
while IFS='' read -r wcline || [[ -n "$wcline" ]]; do
((num_stashed++))
done < ${stash_file}
fi
fi fi
clean=0 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 clean=1
fi fi
IFS="." read -ra line <<< "${branch_line/\#\# }"
branch="${line[0]}"
remote= remote=
if [[ -z "$branch" ]]; then if [[ "$branch" == *"Initial commit on"* ]]; then
tag=`git describe --exact-match` IFS=" " read -ra branch_line <<< "$branch"
branch="${branch_line[3]}"
remote="_NO_REMOTE_TRACKING_"
elif [[ "$branch" == *"no branch"* ]]; then
tag=$( git describe --exact-match )
if [[ -n "$tag" ]]; then if [[ -n "$tag" ]]; then
branch="$tag" branch="$tag"
else else
branch="_PREHASH_`git rev-parse --short HEAD`" branch="_PREHASH_$( git rev-parse --short HEAD )"
fi fi
else else
remote_name=`git config branch.${branch}.remote` if [[ "${#line[@]}" -eq 1 ]]; then
remote="_NO_REMOTE_TRACKING_"
if [[ -n "$remote_name" ]]; then
merge_name=`git config branch.${branch}.merge`
else else
remote_name='origin' IFS="[,]" read -ra remote_line <<< "${line[3]}"
merge_name="refs/heads/${branch}" for rline in "${remote_line[@]}"; do
fi if [[ "$rline" == *ahead* ]]; then
num_ahead=${rline:6}
if [[ "$remote_name" == '.' ]]; then ahead="_AHEAD_${num_ahead}"
remote_ref="$merge_name" fi
else if [[ "$rline" == *behind* ]]; then
remote_ref="refs/remotes/$remote_name/${merge_name##refs/heads/}" num_behind=${rline:7}
fi behind="_BEHIND_${num_behind# }"
fi
# detect if the local branch have a remote tracking branch done
cmd_output=$(git rev-parse --abbrev-ref ${branch}@{upstream} 2>&1 >/dev/null) remote="${behind}${ahead}"
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}"
fi fi
fi fi
@ -101,12 +88,14 @@ if [[ -z "$remote" ]] ; then
remote='.' remote='.'
fi fi
if [[ "$has_remote_tracking" == "0" ]] ; then printf "%s\n%s\n%s\n%s\n%s\n%s\n%s\n%s\n" \
remote='_NO_REMOTE_TRACKING_' "$branch" \
fi "$remote" \
$num_staged \
for w in "$branch" "$remote" $num_staged $num_conflicts $num_changed $num_untracked $num_stashed $clean ; do $num_conflicts \
echo "$w" $num_changed \
done $num_untracked \
$num_stashed \
$clean
exit exit

@ -1,13 +1,13 @@
############################################################################## ##############################################################################
# Changes the prompt to a Debian-style one that truncates pwd to a max length # 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 # 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 # function of bash-git-prompt to set the window title to almost the same
# Debian-style. # Debian-style.
# #
# The prompt will use a Debian-style on the form # The prompt will use a Debian-style on the form
# #
# [user@host: <truncated PWD>] [bash-git-prompt-info] <exit status> # [user@host: <truncated PWD>] [bash-git-prompt-info] <exit status>
# HH:MM $ # HH:MM $
# #
# The window title will have the form # The window title will have the form
# user@host: <truncated PWD> # user@host: <truncated PWD>
@ -44,7 +44,7 @@ override_git_prompt_colors() {
} }
Time12a="\$(date +%H:%M)" Time12a="\$(date +%H:%M)"
GIT_PROMPT_START_USER="${Yellow}" GIT_PROMPT_START_USER="${Yellow}"
GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}" GIT_PROMPT_START_ROOT="${GIT_PROMPT_START_USER}"
GIT_PROMPT_END_USER=" _LAST_COMMAND_INDICATOR_\n${White}${Time12a}${ResetColor} $ " GIT_PROMPT_END_USER=" _LAST_COMMAND_INDICATOR_\n${White}${Time12a}${ResetColor} $ "

@ -0,0 +1,54 @@
##############################################################################
# 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: <truncated PWD>] [bash-git-prompt-info]
# HH:MM $
#
# The window title will have the form
# user@host: <truncated PWD>
#
# Example usage:
# if [ -f ~/.bash-git-prompt/gitprompt.sh ]; then
# GIT_PROMPT_THEME=TruncatedPwd_WindowTitle_NoExitState
# source ~/.bash-git-prompt/gitprompt.sh
# fi
#
# oGre <oGre@muppfarmen.se> [https://github.com/ogr3]
##############################################################################
override_git_prompt_colors() {
GIT_PROMPT_THEME_NAME="TruncatedPwd_WindowTitle_NoExitState"
#Sets the window title to the given argument string
function gp_set_title {
echo -ne "\033]0;"$@"\007"
}
#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"
}
#Overrides the prompt_callback function used by bash-git-prompt
function prompt_callback {
local PS1="\u@\h: $(gp_truncate_pwd)"
gp_set_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"
Loading…
Cancel
Save