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