gitstatus.py 3.2 KB

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