gitstatus.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155
  1. #!/usr/bin/env python
  2. # -*- coding: UTF-8 -*-
  3. """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
  4. Python too
  5. It's interface is that of python 3.0's print. See
  6. http://docs.python.org/3.0/library/functions.html?highlight=print#print
  7. Shamelessly ripped from
  8. http://www.daniweb.com/software-development/python/code/217214/a-print-function-for-different-versions-of-python
  9. """
  10. # change those symbols to whatever you prefer
  11. symbols = {'ahead of': '↑·', 'behind': '↓·', 'prehash': ':'}
  12. import sys
  13. import re
  14. from subprocess import Popen, PIPE
  15. __all__ = ["Print"]
  16. try:
  17. Print = eval("print") # python 3.0 case
  18. python_version = 3
  19. to_str = str
  20. except SyntaxError as e:
  21. python_version = 2
  22. to_str = unicode
  23. D = dict()
  24. try:
  25. exec ("from __future__ import print_function\np=print", D)
  26. Print = D["p"] # 2.6 case
  27. except SyntaxError:
  28. def Print(*args, **kwd): # 2.4, 2.5, define our own Print function
  29. fout = kwd.get("file", sys.stdout)
  30. w = fout.write
  31. if args:
  32. w(str(args[0]))
  33. sep = kwd.get("sep", " ")
  34. for a in args[1:]:
  35. w(sep)
  36. w(str(a))
  37. w(kwd.get("end", "\n"))
  38. finally:
  39. del D
  40. def get_tag_or_hash():
  41. cmd = Popen(['git', 'describe', '--exact-match'], stdout=PIPE, stderr=PIPE)
  42. so, se = cmd.communicate()
  43. tag = '%s' % so.decode('utf-8').strip()
  44. if tag:
  45. return tag
  46. else:
  47. cmd = Popen(['git', 'rev-parse', '--short', 'HEAD'], stdout=PIPE, stderr=PIPE)
  48. so, se = cmd.communicate()
  49. hash_name = '%s' % so.decode('utf-8').strip()
  50. return ''.join([symbols['prehash'], hash_name])
  51. def get_stash():
  52. cmd = Popen(['git', 'rev-parse', '--git-dir'], stdout=PIPE, stderr=PIPE)
  53. so, se = cmd.communicate()
  54. stash_file = '%s%s' % (so.decode('utf-8').rstrip(), '/logs/refs/stash')
  55. try:
  56. with open(stash_file) as f:
  57. return sum(1 for _ in f)
  58. except IOError:
  59. return 0
  60. # `git status --porcelain --branch` can collect all information
  61. # branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind
  62. po = Popen(['git', 'status', '--porcelain', '--branch'], env={'LC_ALL': 'C'}, stdout=PIPE, stderr=PIPE)
  63. stdout, stderr = po.communicate()
  64. if po.returncode != 0:
  65. sys.exit(0) # Not a git repository
  66. # collect git status information
  67. untracked, staged, changed, conflicts = [], [], [], []
  68. num_ahead, num_behind = 0, 0
  69. ahead, behind = '', ''
  70. branch = ''
  71. remote = ''
  72. status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()]
  73. for st in status:
  74. if st[0] == '#' and st[1] == '#':
  75. if re.search('Initial commit on', st[2]):
  76. branch = st[2].split(' ')[-1]
  77. elif re.search('No commits yet on', st[2]):
  78. branch = st[2].split(' ')[-1]
  79. elif re.search('no branch', st[2]): # detached status
  80. branch = get_tag_or_hash()
  81. elif len(st[2].strip().split('...')) == 1:
  82. branch = st[2].strip()
  83. else:
  84. # current and remote branch info
  85. branch, rest = st[2].strip().split('...')
  86. if len(rest.split(' ')) == 1:
  87. # remote_branch = rest.split(' ')[0]
  88. pass
  89. else:
  90. # ahead or behind
  91. divergence = ' '.join(rest.split(' ')[1:])
  92. divergence = divergence.lstrip('[').rstrip(']')
  93. for div in divergence.split(', '):
  94. if 'ahead' in div:
  95. num_ahead = int(div[len('ahead '):].strip())
  96. ahead = '%s%s' % (symbols['ahead of'], num_ahead)
  97. elif 'behind' in div:
  98. num_behind = int(div[len('behind '):].strip())
  99. behind = '%s%s' % (symbols['behind'], num_behind)
  100. remote = ''.join([behind, ahead])
  101. elif st[0] == '?' and st[1] == '?':
  102. untracked.append(st)
  103. else:
  104. if st[1] == 'M':
  105. changed.append(st)
  106. if st[0] == 'U':
  107. conflicts.append(st)
  108. elif st[0] != ' ':
  109. staged.append(st)
  110. stashed = get_stash()
  111. if not changed and not staged and not conflicts and not untracked and not stashed:
  112. clean = 1
  113. else:
  114. clean = 0
  115. if remote == "":
  116. remote = '.'
  117. if python_version == 2:
  118. remote = remote.decode('utf-8')
  119. out = '\n'.join([
  120. branch,
  121. remote,
  122. to_str(len(staged)),
  123. to_str(len(conflicts)),
  124. to_str(len(changed)),
  125. to_str(len(untracked)),
  126. to_str(stashed),
  127. to_str(clean),
  128. to_str(python_version),
  129. ])
  130. if python_version == 2:
  131. Print(out.encode('utf-8'))
  132. else:
  133. Print(out)