Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(68)

Side by Side Diff: git_map_branches.py

Issue 509843002: Give git map-branches extra information behind -v and -vv flags. (Closed) Base URL: https://chromium.googlesource.com/chromium/tools/depot_tools.git@master
Patch Set: add # pragma: no cover to compensate for git < 1.9 Created 6 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « git_common.py ('k') | tests/git_common_test.py » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 #!/usr/bin/env python 1 #!/usr/bin/env python
2 # Copyright 2014 The Chromium Authors. All rights reserved. 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 3 # Use of this source code is governed by a BSD-style license that can be
4 # found in the LICENSE file. 4 # found in the LICENSE file.
5 5
6 """ 6 """Provides a short mapping of all the branches in your local repo, organized
7 Provides a short mapping of all the branches in your local repo, organized by 7 by their upstream ('tracking branch') layout.
8 their upstream ('tracking branch') layout. Example:
9 8
9 Example:
10 origin/master 10 origin/master
11 cool_feature 11 cool_feature
12 dependent_feature 12 dependent_feature
13 other_dependent_feature 13 other_dependent_feature
14 other_feature 14 other_feature
15 15
16 Branches are colorized as follows: 16 Branches are colorized as follows:
17 * Red - a remote branch (usually the root of all local branches) 17 * Red - a remote branch (usually the root of all local branches)
18 * Cyan - a local branch which is the same as HEAD 18 * Cyan - a local branch which is the same as HEAD
19 * Note that multiple branches may be Cyan, if they are all on the same 19 * Note that multiple branches may be Cyan, if they are all on the same
20 commit, and you have that commit checked out. 20 commit, and you have that commit checked out.
21 * Green - a local branch 21 * Green - a local branch
22 * Magenta - a tag 22 * Magenta - a tag
23 * Magenta '{NO UPSTREAM}' - If you have local branches which do not track any 23 * Magenta '{NO UPSTREAM}' - If you have local branches which do not track any
24 upstream, then you will see this. 24 upstream, then you will see this.
25 """ 25 """
26 26
27 import argparse
27 import collections 28 import collections
28 import sys 29 import sys
29 30
30 from third_party import colorama 31 from third_party import colorama
31 from third_party.colorama import Fore, Style 32 from third_party.colorama import Fore, Style
32 33
33 from git_common import current_branch, branches, upstream, hash_one, hash_multi 34 from git_common import current_branch, upstream, tags, get_all_tracking_info
34 from git_common import tags 35 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION
35 36
36 NO_UPSTREAM = '{NO UPSTREAM}' 37 import git_cl
37 38
38 def color_for_branch(branch, branch_hash, cur_hash, tag_set): 39 DEFAULT_SEPARATOR = ' ' * 4
39 if branch.startswith('origin'): 40
40 color = Fore.RED 41
41 elif branch == NO_UPSTREAM or branch in tag_set: 42 class OutputManager(object):
42 color = Fore.MAGENTA 43 """Manages a number of OutputLines and formats them into aligned columns."""
43 elif branch_hash == cur_hash: 44
44 color = Fore.CYAN 45 def __init__(self):
45 else: 46 self.lines = []
46 color = Fore.GREEN 47 self.nocolor = False
47 48 self.max_column_lengths = []
48 if branch_hash == cur_hash: 49 self.num_columns = None
49 color += Style.BRIGHT 50
50 else: 51 def append(self, line):
51 color += Style.NORMAL 52 # All lines must have the same number of columns.
52 53 if not self.num_columns:
53 return color 54 self.num_columns = len(line.columns)
54 55 self.max_column_lengths = [0] * self.num_columns
55 56 assert self.num_columns == len(line.columns)
56 def print_branch(cur, cur_hash, branch, branch_hashes, par_map, branch_map, 57
57 tag_set, depth=0): 58 if self.nocolor:
58 branch_hash = branch_hashes[branch] 59 line.colors = [''] * self.num_columns
59 60
60 color = color_for_branch(branch, branch_hash, cur_hash, tag_set) 61 self.lines.append(line)
61 62
62 suffix = '' 63 # Update maximum column lengths.
63 if cur == 'HEAD': 64 for i, col in enumerate(line.columns):
64 if branch_hash == cur_hash: 65 self.max_column_lengths[i] = max(self.max_column_lengths[i], len(col))
66
67 def as_formatted_string(self):
68 return '\n'.join(
69 l.as_padded_string(self.max_column_lengths) for l in self.lines)
70
71
72 class OutputLine(object):
73 """A single line of data.
74
75 This consists of an equal number of columns, colors and separators."""
76
77 def __init__(self):
78 self.columns = []
79 self.separators = []
80 self.colors = []
81
82 def append(self, data, separator=DEFAULT_SEPARATOR, color=Fore.WHITE):
83 self.columns.append(data)
84 self.separators.append(separator)
85 self.colors.append(color)
86
87 def as_padded_string(self, max_column_lengths):
88 """"Returns the data as a string with each column padded to
89 |max_column_lengths|."""
90 output_string = ''
91 for i, (color, data, separator) in enumerate(
92 zip(self.colors, self.columns, self.separators)):
93 if max_column_lengths[i] == 0:
94 continue
95
96 padding = (max_column_lengths[i] - len(data)) * ' '
97 output_string += color + data + padding + separator
98
99 return output_string.rstrip()
100
101
102 class BranchMapper(object):
103 """A class which constructs output representing the tree's branch structure.
104
105 Attributes:
106 __tracking_info: a map of branches to their TrackingInfo objects which
107 consist of the branch hash, upstream and ahead/behind status.
108 __gone_branches: a set of upstreams which are not fetchable by git"""
109
110 def __init__(self):
111 self.verbosity = 0
112 self.output = OutputManager()
113 self.__tracking_info = get_all_tracking_info()
114 self.__gone_branches = set()
115 self.__roots = set()
116
117 # A map of parents to a list of their children.
118 self.parent_map = collections.defaultdict(list)
119 for branch, branch_info in self.__tracking_info.iteritems():
120 if not branch_info:
121 continue
122
123 parent = branch_info.upstream
124 if parent and not self.__tracking_info[parent]:
125 branch_upstream = upstream(branch)
126 # If git can't find the upstream, mark the upstream as gone.
127 if branch_upstream:
128 parent = branch_upstream
129 else:
130 self.__gone_branches.add(parent)
131 # A parent that isn't in the tracking info is a root.
132 self.__roots.add(parent)
133
134 self.parent_map[parent].append(branch)
135
136 self.__current_branch = current_branch()
137 self.__current_hash = self.__tracking_info[self.__current_branch].hash
138 self.__tag_set = tags()
139
140 def start(self):
141 for root in sorted(self.__roots):
142 self.__append_branch(root)
143
144 def __is_invalid_parent(self, parent):
145 return not parent or parent in self.__gone_branches
146
147 def __color_for_branch(self, branch, branch_hash):
148 if branch.startswith('origin'):
149 color = Fore.RED
150 elif self.__is_invalid_parent(branch) or branch in self.__tag_set:
151 color = Fore.MAGENTA
152 elif branch_hash == self.__current_hash:
153 color = Fore.CYAN
154 else:
155 color = Fore.GREEN
156
157 if branch_hash == self.__current_hash:
158 color += Style.BRIGHT
159 else:
160 color += Style.NORMAL
161
162 return color
163
164 def __append_branch(self, branch, depth=0):
165 """Recurses through the tree structure and appends an OutputLine to the
166 OutputManager for each branch."""
167 branch_info = self.__tracking_info[branch]
168 branch_hash = branch_info.hash if branch_info else None
169
170 line = OutputLine()
171
172 # The branch name with appropriate indentation.
173 suffix = ''
174 if branch == self.__current_branch or (
175 self.__current_branch == 'HEAD' and branch == self.__current_hash):
65 suffix = ' *' 176 suffix = ' *'
66 elif branch == cur: 177 branch_string = branch
67 suffix = ' *' 178 if branch in self.__gone_branches:
68 179 branch_string = '{%s:GONE}' % branch
69 print color + " "*depth + branch + suffix 180 if not branch:
70 for child in par_map.pop(branch, ()): 181 branch_string = '{NO_UPSTREAM}'
71 print_branch(cur, cur_hash, child, branch_hashes, par_map, branch_map, 182 main_string = ' ' * depth + branch_string + suffix
72 tag_set, depth=depth+1) 183 line.append(
184 main_string,
185 color=self.__color_for_branch(branch, branch_hash))
186
187 # The branch hash.
188 if self.verbosity >= 2:
189 line.append(branch_hash or '', separator=' ', color=Fore.RED)
190
191 # The branch tracking status.
192 if self.verbosity >= 1:
193 ahead_string = ''
194 behind_string = ''
195 front_separator = ''
196 center_separator = ''
197 back_separator = ''
198 if branch_info and not self.__is_invalid_parent(branch_info.upstream):
199 ahead = branch_info.ahead
200 behind = branch_info.behind
201
202 if ahead:
203 ahead_string = 'ahead %d' % ahead
204 if behind:
205 behind_string = 'behind %d' % behind
206
207 if ahead or behind:
208 front_separator = '['
209 back_separator = ']'
210
211 if ahead and behind:
212 center_separator = '|'
213
214 line.append(front_separator, separator=' ')
215 line.append(ahead_string, separator=' ', color=Fore.MAGENTA)
216 line.append(center_separator, separator=' ')
217 line.append(behind_string, separator=' ', color=Fore.MAGENTA)
218 line.append(back_separator)
219
220 # The Rietveld issue associated with the branch.
221 if self.verbosity >= 2:
222 none_text = '' if self.__is_invalid_parent(branch) else 'None'
223 url = git_cl.Changelist(branchref=branch).GetIssueURL()
224 line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE)
225
226 self.output.append(line)
227
228 for child in sorted(self.parent_map.pop(branch, ())):
229 self.__append_branch(child, depth=depth + 1)
73 230
74 231
75 def main(argv): 232 def main(argv):
76 colorama.init() 233 colorama.init()
77 assert len(argv) == 1, "No arguments expected" 234 if get_git_version() < MIN_UPSTREAM_TRACK_GIT_VERSION:
78 branch_map = {} 235 print >> sys.stderr, (
79 par_map = collections.defaultdict(list) 236 'This tool will not show all tracking information for git version '
80 for branch in branches(): 237 'earlier than ' +
81 par = upstream(branch) or NO_UPSTREAM 238 '.'.join(str(x) for x in MIN_UPSTREAM_TRACK_GIT_VERSION) +
82 branch_map[branch] = par 239 '. Please consider upgrading.')
83 par_map[par].append(branch) 240
84 241 parser = argparse.ArgumentParser(
85 current = current_branch() 242 description='Print a a tree of all branches parented by their upstreams')
86 hashes = hash_multi(current, *branch_map.keys()) 243 parser.add_argument('-v', action='count',
87 current_hash = hashes[0] 244 help='Display branch hash and Rietveld URL')
88 par_hashes = {k: hashes[i+1] for i, k in enumerate(branch_map.iterkeys())} 245 parser.add_argument('--no-color', action='store_true', dest='nocolor',
89 par_hashes[NO_UPSTREAM] = 0 246 help='Turn off colors.')
90 tag_set = tags() 247
91 while par_map: 248 opts = parser.parse_args(argv[1:])
92 for parent in par_map: 249
93 if parent not in branch_map: 250 mapper = BranchMapper()
94 if parent not in par_hashes: 251 mapper.verbosity = opts.v
95 par_hashes[parent] = hash_one(parent) 252 mapper.output.nocolor = opts.nocolor
96 print_branch(current, current_hash, parent, par_hashes, par_map, 253 mapper.start()
97 branch_map, tag_set) 254 print mapper.output.as_formatted_string()
98 break
99
100 255
101 if __name__ == '__main__': 256 if __name__ == '__main__':
102 sys.exit(main(sys.argv)) 257 sys.exit(main(sys.argv))
103
OLDNEW
« no previous file with comments | « git_common.py ('k') | tests/git_common_test.py » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698