git_map.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  1. #!/usr/bin/env python3
  2. # Copyright 2014 The Chromium Authors. All rights reserved.
  3. # Use of this source code is governed by a BSD-style license that can be
  4. # found in the LICENSE file.
  5. """
  6. usage: git map [-h] [--help] [<args>]
  7. Enhances `git log --graph` view with information on commit branches + tags that
  8. point to them. Items are colorized as follows:
  9. * Cyan - Currently checked out branch
  10. * Green - Local branch
  11. * Red - Remote branches
  12. * Magenta - Tags
  13. * White - Merge Base Markers
  14. * Blue background - The currently checked out commit
  15. """
  16. from __future__ import unicode_literals
  17. import os
  18. import sys
  19. import git_common
  20. import setup_color
  21. import subprocess2
  22. from third_party import colorama
  23. if sys.version_info.major == 2:
  24. # On Python 3, BrokenPipeError is raised instead.
  25. # pylint:disable=redefined-builtin
  26. BrokenPipeError = IOError
  27. RESET = colorama.Fore.RESET + colorama.Back.RESET + colorama.Style.RESET_ALL
  28. BRIGHT = colorama.Style.BRIGHT
  29. BLUE_BACK = colorama.Back.BLUE + BRIGHT
  30. BRIGHT_RED = colorama.Fore.RED + BRIGHT
  31. CYAN = colorama.Fore.CYAN + BRIGHT
  32. GREEN = colorama.Fore.GREEN + BRIGHT
  33. MAGENTA = colorama.Fore.MAGENTA + BRIGHT
  34. RED = colorama.Fore.RED
  35. WHITE = colorama.Fore.WHITE + BRIGHT
  36. YELLOW = colorama.Fore.YELLOW
  37. def _print_help(outbuf):
  38. names = {
  39. 'Cyan': CYAN,
  40. 'Green': GREEN,
  41. 'Magenta': MAGENTA,
  42. 'Red': RED,
  43. 'White': WHITE,
  44. 'Blue background': BLUE_BACK,
  45. }
  46. msg = ''
  47. for line in __doc__.splitlines():
  48. for name, color in names.items():
  49. if name in line:
  50. msg += line.replace('* ' + name, color + '* ' + name + RESET) + '\n'
  51. break
  52. else:
  53. msg += line + '\n'
  54. outbuf.write(msg.encode('utf-8', 'replace'))
  55. def _color_branch(branch, all_branches, all_tags, current):
  56. if branch in (current, 'HEAD -> ' + current):
  57. color = CYAN
  58. current = None
  59. elif branch in all_branches:
  60. color = GREEN
  61. all_branches.remove(branch)
  62. elif branch in all_tags:
  63. color = MAGENTA
  64. elif branch.startswith('tag: '):
  65. color = MAGENTA
  66. branch = branch[len('tag: '):]
  67. else:
  68. color = RED
  69. return color + branch + RESET
  70. def _color_branch_list(branch_list, all_branches, all_tags, current):
  71. if not branch_list:
  72. return ''
  73. colored_branches = (GREEN + ', ').join(
  74. _color_branch(branch, all_branches, all_tags, current)
  75. for branch in branch_list if branch != 'HEAD')
  76. return (GREEN + '(' + colored_branches + GREEN + ') ' + RESET)
  77. def _parse_log_line(line):
  78. graph, branch_list, commit_date, subject = (
  79. line.decode('utf-8', 'replace').strip().split('\x00'))
  80. branch_list = [] if not branch_list else branch_list.split(', ')
  81. commit = graph.split()[-1]
  82. graph = graph[:-len(commit)]
  83. return graph, commit, branch_list, commit_date, subject
  84. def main(argv, outbuf):
  85. if '-h' in argv or '--help' in argv:
  86. _print_help(outbuf)
  87. return 0
  88. map_extra = git_common.get_config_list('depot_tools.map_extra')
  89. cmd = [
  90. git_common.GIT_EXE, 'log', git_common.root(),
  91. '--graph', '--branches', '--tags', '--color=always', '--date=short',
  92. '--pretty=format:%H%x00%D%x00%cd%x00%s'
  93. ] + map_extra + argv
  94. log_proc = subprocess2.Popen(cmd, stdout=subprocess2.PIPE, shell=False)
  95. current = git_common.current_branch()
  96. all_tags = set(git_common.tags())
  97. all_branches = set(git_common.branches())
  98. if current in all_branches:
  99. all_branches.remove(current)
  100. merge_base_map = {}
  101. for branch in all_branches:
  102. merge_base = git_common.get_or_create_merge_base(branch)
  103. if merge_base:
  104. merge_base_map.setdefault(merge_base, set()).add(branch)
  105. for merge_base, branches in merge_base_map.items():
  106. merge_base_map[merge_base] = ', '.join(branches)
  107. try:
  108. for line in log_proc.stdout:
  109. if b'\x00' not in line:
  110. outbuf.write(line)
  111. continue
  112. graph, commit, branch_list, commit_date, subject = _parse_log_line(line)
  113. if 'HEAD' in branch_list:
  114. graph = graph.replace('*', BLUE_BACK + '*')
  115. line = '{graph}{commit}\t{branches}{date} ~ {subject}'.format(
  116. graph=graph,
  117. commit=BRIGHT_RED + commit[:10] + RESET,
  118. branches=_color_branch_list(
  119. branch_list, all_branches, all_tags, current),
  120. date=YELLOW + commit_date + RESET,
  121. subject=subject)
  122. if commit in merge_base_map:
  123. line += ' <({})'.format(WHITE + merge_base_map[commit] + RESET)
  124. line += os.linesep
  125. outbuf.write(line.encode('utf-8', 'replace'))
  126. except (BrokenPipeError, KeyboardInterrupt):
  127. pass
  128. return 0
  129. if __name__ == '__main__':
  130. setup_color.init()
  131. with git_common.less() as less_input:
  132. sys.exit(main(sys.argv[1:], less_input))