gitstatus.py
3496 bytes
1#!/usr/bin/env python3
2from __future__ import print_function
3
4import os
5import sys
6import re
7from subprocess import Popen, PIPE, check_output
8
9
10def get_tagname_or_hash():
11 """return tagname if exists else hash"""
12 # get hash
13 hash_cmd = ['git', 'rev-parse', '--short', 'HEAD']
14 hash_ = check_output(hash_cmd).decode('utf-8').strip()
15
16 # get tagname
17 tags_cmd = ['git', 'for-each-ref', '--points-at=HEAD', '--count=2', '--sort=-version:refname', '--format=%(refname:short)', 'refs/tags']
18 tags = check_output(tags_cmd).decode('utf-8').split()
19
20 if tags:
21 return tags[0] + ('+' if len(tags) > 1 else '')
22 elif hash_:
23 return hash_
24 return None
25
26# Re-use method from https://github.com/magicmonty/bash-git-prompt to get stash count
27# Use `--git-common-dir` to avoid problems with git worktrees, which don't have individual stashes
28def get_stash():
29 cmd = Popen(['git', 'rev-parse', '--git-common-dir'], stdout=PIPE, stderr=PIPE)
30 so, se = cmd.communicate()
31 stash_file = '%s%s' % (so.decode('utf-8').rstrip(), '/logs/refs/stash')
32
33 try:
34 with open(stash_file) as f:
35 return sum(1 for _ in f)
36 except IOError:
37 return 0
38
39# `git status --porcelain --branch` can collect all information
40# branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind
41po = Popen(['git', 'status', '--porcelain', '--branch'], env=dict(os.environ, LANG="C"), stdout=PIPE, stderr=PIPE)
42stdout, sterr = po.communicate()
43if po.returncode != 0:
44 sys.exit(0) # Not a git repository
45
46# collect git status information
47untracked, staged, changed, deleted, conflicts = [], [], [], [], []
48ahead, behind = 0, 0
49status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()]
50for st in status:
51 if st[0] == '#' and st[1] == '#':
52 if re.search('Initial commit on', st[2]) or re.search('No commits yet on', st[2]):
53 branch = st[2].split(' ')[-1]
54 elif re.search('no branch', st[2]): # detached status
55 branch = get_tagname_or_hash()
56 elif len(st[2].strip().split('...')) == 1:
57 branch = st[2].strip()
58 else:
59 # current and remote branch info
60 branch, rest = st[2].strip().split('...')
61 if len(rest.split(' ')) == 1:
62 # remote_branch = rest.split(' ')[0]
63 pass
64 else:
65 # ahead or behind
66 divergence = ' '.join(rest.split(' ')[1:])
67 divergence = divergence.lstrip('[').rstrip(']')
68 for div in divergence.split(', '):
69 if 'ahead' in div:
70 ahead = int(div[len('ahead '):].strip())
71 elif 'behind' in div:
72 behind = int(div[len('behind '):].strip())
73 elif st[0] == '?' and st[1] == '?':
74 untracked.append(st)
75 else:
76 if st[1] == 'M':
77 changed.append(st)
78 if st[1] == 'D':
79 deleted.append(st)
80 if st[0] == 'U':
81 conflicts.append(st)
82 elif st[0] != ' ':
83 staged.append(st)
84
85stashed = get_stash()
86if not changed and not deleted and not staged and not conflicts and not untracked:
87 clean = 1
88else:
89 clean = 0
90
91out = ' '.join([
92 branch,
93 str(ahead),
94 str(behind),
95 str(len(staged)),
96 str(len(conflicts)),
97 str(len(changed)),
98 str(len(untracked)),
99 str(stashed),
100 str(clean),
101 str(len(deleted))
102])
103print(out, end='')