gitstatus.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103
  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 stash count
  21. # Use `--git-common-dir` to avoid problems with git worktrees, which don't have individual stashes
  22. def get_stash():
  23. cmd = Popen(['git', 'rev-parse', '--git-common-dir'], stdout=PIPE, stderr=PIPE)
  24. so, se = cmd.communicate()
  25. stash_file = '%s%s' % (so.decode('utf-8').rstrip(), '/logs/refs/stash')
  26. try:
  27. with open(stash_file) as f:
  28. return sum(1 for _ in f)
  29. except IOError:
  30. return 0
  31. # `git status --porcelain --branch` can collect all information
  32. # branch, remote_branch, untracked, staged, changed, conflicts, ahead, behind
  33. po = Popen(['git', 'status', '--porcelain', '--branch'], env=dict(os.environ, LANG="C"), stdout=PIPE, stderr=PIPE)
  34. stdout, sterr = po.communicate()
  35. if po.returncode != 0:
  36. sys.exit(0) # Not a git repository
  37. # collect git status information
  38. untracked, staged, changed, deleted, conflicts = [], [], [], [], []
  39. ahead, behind = 0, 0
  40. status = [(line[0], line[1], line[2:]) for line in stdout.decode('utf-8').splitlines()]
  41. for st in status:
  42. if st[0] == '#' and st[1] == '#':
  43. if re.search('Initial commit on', st[2]) or re.search('No commits yet on', st[2]):
  44. branch = st[2].split(' ')[-1]
  45. elif re.search('no branch', st[2]): # detached status
  46. branch = get_tagname_or_hash()
  47. elif len(st[2].strip().split('...')) == 1:
  48. branch = st[2].strip()
  49. else:
  50. # current and remote branch info
  51. branch, rest = st[2].strip().split('...')
  52. if len(rest.split(' ')) == 1:
  53. # remote_branch = rest.split(' ')[0]
  54. pass
  55. else:
  56. # ahead or behind
  57. divergence = ' '.join(rest.split(' ')[1:])
  58. divergence = divergence.lstrip('[').rstrip(']')
  59. for div in divergence.split(', '):
  60. if 'ahead' in div:
  61. ahead = int(div[len('ahead '):].strip())
  62. elif 'behind' in div:
  63. behind = int(div[len('behind '):].strip())
  64. elif st[0] == '?' and st[1] == '?':
  65. untracked.append(st)
  66. else:
  67. if st[1] == 'M':
  68. changed.append(st)
  69. if st[1] == 'D':
  70. deleted.append(st)
  71. if st[0] == 'U':
  72. conflicts.append(st)
  73. elif st[0] != ' ':
  74. staged.append(st)
  75. stashed = get_stash()
  76. if not changed and not deleted and not staged and not conflicts and not untracked:
  77. clean = 1
  78. else:
  79. clean = 0
  80. out = ' '.join([
  81. branch,
  82. str(ahead),
  83. str(behind),
  84. str(len(staged)),
  85. str(len(conflicts)),
  86. str(len(changed)),
  87. str(len(untracked)),
  88. str(stashed),
  89. str(clean),
  90. str(len(deleted))
  91. ])
  92. print(out, end='')