Skip to content
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.

Commit 54162e7

Browse files
committedSep 20, 2015
Use git status --porcelain --branch to improve performance
Based off of changes by @wkentaro to zsh-git-prompt see: olivierverdier/zsh-git-prompt#65
1 parent b46a233 commit 54162e7

File tree

2 files changed

+126
-137
lines changed

2 files changed

+126
-137
lines changed
 

‎gitstatus.py

Lines changed: 95 additions & 79 deletions
Original file line numberDiff line numberDiff line change
@@ -32,93 +32,109 @@ def Print(*args, **kwd): # 2.4, 2.5, define our own Print function
3232
w(str(a))
3333
w(kwd.get("end", "\n"))
3434

35-
3635
# change those symbols to whatever you prefer
3736
symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash':':'}
3837

39-
from subprocess import Popen, PIPE
40-
4138
import sys
42-
gitsym = Popen(['git', 'symbolic-ref', 'HEAD'], stdout=PIPE, stderr=PIPE)
43-
branch, error = gitsym.communicate()
44-
45-
error_string = error.decode('utf-8')
46-
47-
if 'fatal: Not a git repository' in error_string:
48-
sys.exit(0)
49-
50-
branch = branch.decode('utf-8').strip()[11:]
51-
52-
res, err = Popen(['git','diff','--name-status'], stdout=PIPE, stderr=PIPE).communicate()
53-
err_string = err.decode('utf-8')
54-
55-
if 'fatal' in err_string:
56-
sys.exit(0)
57-
58-
changed_files = [namestat[0] for namestat in res.splitlines()]
59-
staged_files = [namestat[0] for namestat in Popen(['git','diff', '--staged','--name-status'], stdout=PIPE).communicate()[0].splitlines()]
60-
nb_changed = len(changed_files) - changed_files.count('U')
61-
nb_U = staged_files.count('U')
62-
nb_staged = len(staged_files) - nb_U
63-
staged = str(nb_staged)
64-
conflicts = str(nb_U)
65-
changed = str(nb_changed)
66-
status_lines = Popen(['git','status','-s','-uall'],stdout=PIPE).communicate()[0].splitlines()
67-
untracked_lines = [a for a in map(lambda s: s.decode('utf-8'), status_lines) if a.startswith("??")]
68-
nb_untracked = len(untracked_lines)
69-
untracked = str(nb_untracked)
70-
stashes = Popen(['git','stash','list'],stdout=PIPE).communicate()[0].splitlines()
71-
nb_stashed = len(stashes)
72-
stashed = str(nb_stashed)
73-
74-
if not nb_changed and not nb_staged and not nb_U and not nb_untracked and not nb_stashed:
75-
clean = '1'
76-
else:
77-
clean = '0'
78-
39+
import re
40+
import shlex
41+
from subprocess import Popen, PIPE, check_output
42+
43+
44+
def get_tagname_or_hash():
45+
"""return tagname if exists else hash"""
46+
cmd = 'git log -1 --format="%h%d"'
47+
output = check_output(shlex.split(cmd)).decode('utf-8').strip()
48+
hash_, tagname = None, None
49+
# get hash
50+
m = re.search('\(.*\)$', output)
51+
if m:
52+
hash_ = output[:m.start()-1]
53+
# get tagname
54+
m = re.search('tag: .*[,\)]', output)
55+
if m:
56+
tagname = 'tags/' + output[m.start()+len('tag: '): m.end()-1]
57+
58+
if tagname:
59+
return tagname
60+
elif hash_:
61+
return hash_
62+
return None
63+
64+
def get_stash():
65+
cmd = Popen(['git', 'rev-parse', '--git-dir'], stdout=PIPE, stderr=PIPE)
66+
so, se = cmd.communicate()
67+
stashFile = '%s%s' % (so.decode('utf-8').rstrip(),'/logs/refs/stash')
68+
69+
try:
70+
with open(stashFile) as f:
71+
return sum(1 for _ in f)
72+
except IOError:
73+
return 0
74+
75+
# `git status --porcelain --branch` can collect all information
76+
# branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind
77+
po = Popen(['git', 'status', '--porcelain', '--branch'], stdout=PIPE, stderr=PIPE)
78+
stdout, sterr = po.communicate()
79+
if po.returncode != 0:
80+
sys.exit(0) # Not a git repository
81+
82+
# collect git status information
83+
untracked, staged, changed, conflicts = [], [], [], []
84+
ahead, behind = 0, 0
7985
remote = ''
80-
81-
tag, tag_error = Popen(['git', 'describe', '--exact-match'], stdout=PIPE, stderr=PIPE).communicate()
82-
83-
if not branch: # not on any branch
84-
if tag: # if we are on a tag, print the tag's name
85-
branch = tag
86-
else:
87-
branch = symbols['prehash']+ Popen(['git','rev-parse','--short','HEAD'], stdout=PIPE).communicate()[0].decode('utf-8')[:-1]
86+
status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()]
87+
for st in status:
88+
if st[0] == '#' and st[1] == '#':
89+
if re.search('Initial commit on', st[2]):
90+
branch = st[2].split(' ')[-1]
91+
elif re.search('no branch', st[2]): # detached status
92+
branch = get_tagname_or_hash()
93+
elif len(st[2].strip().split('...')) == 1:
94+
branch = st[2].strip()
95+
else:
96+
# current and remote branch info
97+
branch, rest = st[2].strip().split('...')
98+
if len(rest.split(' ')) == 1:
99+
# remote_branch = rest.split(' ')[0]
100+
pass
101+
else:
102+
# ahead or behind
103+
divergence = ' '.join(rest.split(' ')[1:])
104+
divergence = divergence.lstrip('[').rstrip(']')
105+
for div in divergence.split(', '):
106+
if 'ahead' in div:
107+
ahead = int(div[len('ahead '):].strip())
108+
remote += '%s%s' % (symbols['ahead of'], ahead)
109+
elif 'behind' in div:
110+
behind = int(div[len('behind '):].strip())
111+
remote += '%s%s' % (symbols['behind'], behind)
112+
elif st[0] == '?' and st[1] == '?':
113+
untracked.append(st)
114+
else:
115+
if st[1] == 'M':
116+
changed.append(st)
117+
if st[0] == 'U':
118+
conflicts.append(st)
119+
elif st[0] != ' ':
120+
staged.append(st)
121+
122+
if not changed and not staged and not conflicts and not untracked:
123+
clean = 1
88124
else:
89-
remote_name = Popen(['git','config','branch.%s.remote' % branch], stdout=PIPE).communicate()[0].strip()
90-
if remote_name:
91-
merge_name = Popen(['git','config','branch.%s.merge' % branch], stdout=PIPE).communicate()[0].strip()
92-
else:
93-
remote_name = "origin"
94-
merge_name = "refs/heads/%s" % branch
95-
96-
if remote_name == '.': # local
97-
remote_ref = merge_name
98-
else:
99-
remote_ref = 'refs/remotes/%s/%s' % (remote_name, merge_name[11:])
100-
revgit = Popen(['git', 'rev-list', '--left-right', '%s...HEAD' % remote_ref],stdout=PIPE, stderr=PIPE)
101-
revlist = revgit.communicate()[0]
102-
if revgit.poll(): # fallback to local
103-
revlist = Popen(['git', 'rev-list', '--left-right', '%s...HEAD' % merge_name],stdout=PIPE, stderr=PIPE).communicate()[0]
104-
behead = revlist.splitlines()
105-
ahead = len([x for x in behead if x[0]=='>'])
106-
behind = len(behead) - ahead
107-
if behind:
108-
remote += '%s%s' % (symbols['behind'], behind)
109-
if ahead:
110-
remote += '%s%s' % (symbols['ahead of'], ahead)
125+
clean = 0
111126

112127
if remote == "":
113-
remote = '.'
128+
remote = '.'
114129

115130
out = '\n'.join([
116-
str(branch),
117-
str(remote),
118-
staged,
119-
conflicts,
120-
changed,
121-
untracked,
122-
stashed,
123-
clean])
131+
branch,
132+
remote.decode('utf-8'),
133+
str(len(staged)),
134+
str(len(conflicts)),
135+
str(len(changed)),
136+
str(len(untracked)),
137+
str(get_stash()),
138+
str(clean)
139+
])
124140
Print(out)

‎gitstatus.sh

Lines changed: 31 additions & 58 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,7 @@
66
# Alan K. Stebbens <aks@stebbens.org> [http://github.com/aks]
77

88
# helper functions
9-
count_lines() { echo "$1" | egrep -c "^$2" ; }
10-
all_lines() { echo "$1" | grep -v "^$" | wc -l ; }
9+
count_lines() { echo "$1" | egrep -c $3 "^$2" ; }
1110

1211
if [ -z "${__GIT_PROMPT_DIR}" ]; then
1312
SOURCE="${BASH_SOURCE[0]}"
@@ -19,92 +18,66 @@ if [ -z "${__GIT_PROMPT_DIR}" ]; then
1918
__GIT_PROMPT_DIR="$( cd -P "$( dirname "${SOURCE}" )" && pwd )"
2019
fi
2120

22-
gitsym=`git symbolic-ref HEAD`
21+
gitstatus=`git status --porcelain --branch`
2322

24-
# if "fatal: Not a git repo .., then exit
25-
case "$gitsym" in fatal*) exit 0 ;; esac
23+
# if the status is fatal, exit now
24+
[[ "$?" -ne 0 ]] && exit 0
2625

27-
# the current branch is the tail end of the symbolic reference
28-
branch="${gitsym##refs/heads/}" # get the basename after "refs/heads/"
26+
num_staged=`count_lines "$gitstatus" "(\?\?|##| )" "-v"`
27+
num_changed=`count_lines "$gitstatus" ".M"`
28+
num_conflicts=`count_lines "$gitstatus" "U"`
29+
num_untracked=`count_lines "$gitstatus" "\?\?"`
2930

30-
gitstatus=`git diff --name-status 2>&1`
31-
32-
# if the diff is fatal, exit now
33-
case "$gitstatus" in fatal*) exit 0 ;; esac
34-
35-
36-
staged_files=`git diff --staged --name-status`
37-
38-
num_changed=$(( `all_lines "$gitstatus"` - `count_lines "$gitstatus" U` ))
39-
num_conflicts=`count_lines "$staged_files" U`
40-
num_staged=$(( `all_lines "$staged_files"` - num_conflicts ))
41-
num_untracked=`git ls-files --others --exclude-standard $(git rev-parse --show-cdup) | wc -l`
4231
if [[ "$__GIT_PROMPT_IGNORE_STASH" = "1" ]]; then
4332
num_stashed=0
44-
else
45-
num_stashed=`git stash list | wc -l`
33+
else
34+
stash_file="`git rev-parse --git-dir`/logs/refs/stash"
35+
if [[ -e "${stash_file}" ]]; then
36+
num_stashed=`wc -l "${stash_file}" | cut -d' ' -f 1`
37+
else
38+
num_stashed=0
39+
fi
4640
fi
4741

4842
clean=0
49-
if (( num_changed == 0 && num_staged == 0 && num_U == 0 && num_untracked == 0 && num_stashed == 0 )) ; then
43+
if (( num_changed == 0 && num_staged == 0 && num_untracked == 0 && num_stashed == 0 )) ; then
5044
clean=1
5145
fi
5246

5347
remote=
5448

49+
branch_line=`echo $gitstatus | grep "^##"`
50+
IFS="." read -ra line <<< "${branch_line/\#\# }"
51+
branch="${line[0]}"
52+
5553
if [[ -z "$branch" ]]; then
5654
tag=`git describe --exact-match`
5755
if [[ -n "$tag" ]]; then
5856
branch="$tag"
5957
else
6058
branch="_PREHASH_`git rev-parse --short HEAD`"
6159
fi
60+
elif [[ "$branch" == *"Initial commit on"* ]]; then
61+
IFS=" " read -ra branch_line <<< "$branch"
62+
branch=${branch_line[-1]}
63+
elif [[ "$branch" == *"no branch"* ]]; then
64+
branch="_PREHASH_`git rev-parse --short HEAD`"
6265
else
63-
remote_name=`git config branch.${branch}.remote`
64-
65-
if [[ -n "$remote_name" ]]; then
66-
merge_name=`git config branch.${branch}.merge`
67-
else
68-
remote_name='origin'
69-
merge_name="refs/heads/${branch}"
70-
fi
71-
72-
if [[ "$remote_name" == '.' ]]; then
73-
remote_ref="$merge_name"
74-
else
75-
remote_ref="refs/remotes/$remote_name/${merge_name##refs/heads/}"
76-
fi
77-
78-
# detect if the local branch have a remote tracking branch
79-
cmd_output=$(git rev-parse --abbrev-ref ${branch}@{upstream} 2>&1 >/dev/null)
80-
81-
if [ `count_lines "$cmd_output" "fatal: no upstream"` == 1 ] ; then
82-
has_remote_tracking=0
83-
else
84-
has_remote_tracking=1
66+
IFS="[]" read -ra remote_line <<< "${line[3]}"
67+
if [[ "${remote_line[1]}" == *ahead* ]]; then
68+
num_ahead=`echo "${remote_line[1]}" | cut -c 7-`
69+
remote="${remote}_AHEAD_${num_ahead}"
8570
fi
86-
87-
# get the revision list, and count the leading "<" and ">"
88-
revgit=`git rev-list --left-right ${remote_ref}...HEAD`
89-
num_revs=`all_lines "$revgit"`
90-
num_ahead=`count_lines "$revgit" "^>"`
91-
num_behind=$(( num_revs - num_ahead ))
92-
if (( num_behind > 0 )) ; then
71+
if [[ "${remote_line[1]}" == *behind* ]]; then
72+
num_behind=`echo "${remote_line[1]}" | cut -c 9-`
9373
remote="${remote}_BEHIND_${num_behind}"
9474
fi
95-
if (( num_ahead > 0 )) ; then
96-
remote="${remote}_AHEAD_${num_ahead}"
97-
fi
9875
fi
9976

10077
if [[ -z "$remote" ]] ; then
10178
remote='.'
10279
fi
10380

104-
if [[ "$has_remote_tracking" == "0" ]] ; then
105-
remote='_NO_REMOTE_TRACKING_'
106-
fi
107-
10881
for w in "$branch" "$remote" $num_staged $num_conflicts $num_changed $num_untracked $num_stashed $clean ; do
10982
echo "$w"
11083
done

0 commit comments

Comments
 (0)
Please sign in to comment.