OLD | NEW |
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 """Provides a short mapping of all the branches in your local repo, organized | 6 """Provides a short mapping of all the branches in your local repo, organized |
7 by their upstream ('tracking branch') layout. | 7 by their upstream ('tracking branch') layout. |
8 | 8 |
9 Example: | 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 * Blue - a 'branch-heads' branch |
22 * Magenta - a tag | 23 * Magenta - a tag |
23 * Magenta '{NO UPSTREAM}' - If you have local branches which do not track any | 24 * Magenta '{NO UPSTREAM}' - If you have local branches which do not track any |
24 upstream, then you will see this. | 25 upstream, then you will see this. |
25 """ | 26 """ |
26 | 27 |
27 import argparse | 28 import argparse |
28 import collections | 29 import collections |
29 import sys | 30 import sys |
| 31 import subprocess2 |
30 | 32 |
31 from third_party import colorama | 33 from third_party import colorama |
32 from third_party.colorama import Fore, Style | 34 from third_party.colorama import Fore, Style |
33 | 35 |
34 from git_common import current_branch, upstream, tags, get_branches_info | 36 from git_common import current_branch, upstream, tags, get_branches_info |
35 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION, hash_one | 37 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION, hash_one |
36 | 38 |
37 DEFAULT_SEPARATOR = ' ' * 4 | 39 DEFAULT_SEPARATOR = ' ' * 4 |
38 | 40 |
39 | 41 |
(...skipping 79 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
119 self.__branches_info = get_branches_info( | 121 self.__branches_info = get_branches_info( |
120 include_tracking_status=self.verbosity >= 1) | 122 include_tracking_status=self.verbosity >= 1) |
121 roots = set() | 123 roots = set() |
122 | 124 |
123 # A map of parents to a list of their children. | 125 # A map of parents to a list of their children. |
124 for branch, branch_info in self.__branches_info.iteritems(): | 126 for branch, branch_info in self.__branches_info.iteritems(): |
125 if not branch_info: | 127 if not branch_info: |
126 continue | 128 continue |
127 | 129 |
128 parent = branch_info.upstream | 130 parent = branch_info.upstream |
129 if parent and not self.__branches_info[parent]: | 131 if not self.__branches_info[parent]: |
130 branch_upstream = upstream(branch) | 132 branch_upstream = upstream(branch) |
131 # If git can't find the upstream, mark the upstream as gone. | 133 # If git can't find the upstream, mark the upstream as gone. |
132 if branch_upstream: | 134 if branch_upstream: |
133 parent = branch_upstream | 135 parent = branch_upstream |
134 else: | 136 else: |
135 self.__gone_branches.add(parent) | 137 self.__gone_branches.add(parent) |
136 # A parent that isn't in the branches info is a root. | 138 # A parent that isn't in the branches info is a root. |
137 roots.add(parent) | 139 roots.add(parent) |
138 | 140 |
139 self.__parent_map[parent].append(branch) | 141 self.__parent_map[parent].append(branch) |
140 | 142 |
141 self.__current_branch = current_branch() | 143 self.__current_branch = current_branch() |
142 self.__current_hash = hash_one('HEAD', short=True) | 144 self.__current_hash = hash_one('HEAD', short=True) |
143 self.__tag_set = tags() | 145 self.__tag_set = tags() |
144 | 146 |
145 if roots: | 147 if roots: |
146 for root in sorted(roots): | 148 for root in sorted(roots): |
147 self.__append_branch(root) | 149 self.__append_branch(root) |
148 else: | 150 else: |
149 no_branches = OutputLine() | 151 no_branches = OutputLine() |
150 no_branches.append('No User Branches') | 152 no_branches.append('No User Branches') |
151 self.output.append(no_branches) | 153 self.output.append(no_branches) |
152 | 154 |
153 def __is_invalid_parent(self, parent): | 155 def __is_invalid_parent(self, parent): |
154 return not parent or parent in self.__gone_branches | 156 return not parent or parent in self.__gone_branches |
155 | 157 |
156 def __color_for_branch(self, branch, branch_hash): | 158 def __color_for_branch(self, branch, branch_hash): |
157 if branch.startswith('origin'): | 159 if branch.startswith('origin'): |
158 color = Fore.RED | 160 color = Fore.RED |
| 161 elif branch.startswith('branch-heads'): |
| 162 color = Fore.BLUE |
159 elif self.__is_invalid_parent(branch) or branch in self.__tag_set: | 163 elif self.__is_invalid_parent(branch) or branch in self.__tag_set: |
160 color = Fore.MAGENTA | 164 color = Fore.MAGENTA |
161 elif self.__current_hash.startswith(branch_hash): | 165 elif self.__current_hash.startswith(branch_hash): |
162 color = Fore.CYAN | 166 color = Fore.CYAN |
163 else: | 167 else: |
164 color = Fore.GREEN | 168 color = Fore.GREEN |
165 | 169 |
166 if self.__current_hash.startswith(branch_hash): | 170 if branch_hash and self.__current_hash.startswith(branch_hash): |
167 color += Style.BRIGHT | 171 color += Style.BRIGHT |
168 else: | 172 else: |
169 color += Style.NORMAL | 173 color += Style.NORMAL |
170 | 174 |
171 return color | 175 return color |
172 | 176 |
173 def __append_branch(self, branch, depth=0): | 177 def __append_branch(self, branch, depth=0): |
174 """Recurses through the tree structure and appends an OutputLine to the | 178 """Recurses through the tree structure and appends an OutputLine to the |
175 OutputManager for each branch.""" | 179 OutputManager for each branch.""" |
176 branch_info = self.__branches_info[branch] | 180 branch_info = self.__branches_info[branch] |
177 if branch_info: | 181 if branch_info: |
178 branch_hash = branch_info.hash | 182 branch_hash = branch_info.hash |
179 else: | 183 else: |
180 branch_hash = hash_one(branch, short=True) | 184 try: |
| 185 branch_hash = hash_one(branch, short=True) |
| 186 except subprocess2.CalledProcessError: |
| 187 branch_hash = None |
181 | 188 |
182 line = OutputLine() | 189 line = OutputLine() |
183 | 190 |
184 # The branch name with appropriate indentation. | 191 # The branch name with appropriate indentation. |
185 suffix = '' | 192 suffix = '' |
186 if branch == self.__current_branch or ( | 193 if branch == self.__current_branch or ( |
187 self.__current_branch == 'HEAD' and branch == self.__current_hash): | 194 self.__current_branch == 'HEAD' and branch == self.__current_hash): |
188 suffix = ' *' | 195 suffix = ' *' |
189 branch_string = branch | 196 branch_string = branch |
190 if branch in self.__gone_branches: | 197 if branch in self.__gone_branches: |
(...skipping 35 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
226 line.append(front_separator, separator=' ') | 233 line.append(front_separator, separator=' ') |
227 line.append(ahead_string, separator=' ', color=Fore.MAGENTA) | 234 line.append(ahead_string, separator=' ', color=Fore.MAGENTA) |
228 line.append(center_separator, separator=' ') | 235 line.append(center_separator, separator=' ') |
229 line.append(behind_string, separator=' ', color=Fore.MAGENTA) | 236 line.append(behind_string, separator=' ', color=Fore.MAGENTA) |
230 line.append(back_separator) | 237 line.append(back_separator) |
231 | 238 |
232 # The Rietveld issue associated with the branch. | 239 # The Rietveld issue associated with the branch. |
233 if self.verbosity >= 2: | 240 if self.verbosity >= 2: |
234 import git_cl # avoid heavy import cost unless we need it | 241 import git_cl # avoid heavy import cost unless we need it |
235 none_text = '' if self.__is_invalid_parent(branch) else 'None' | 242 none_text = '' if self.__is_invalid_parent(branch) else 'None' |
236 url = git_cl.Changelist(branchref=branch).GetIssueURL() | 243 url = git_cl.Changelist( |
| 244 branchref=branch).GetIssueURL() if branch_hash else None |
237 line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE) | 245 line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE) |
238 | 246 |
239 self.output.append(line) | 247 self.output.append(line) |
240 | 248 |
241 for child in sorted(self.__parent_map.pop(branch, ())): | 249 for child in sorted(self.__parent_map.pop(branch, ())): |
242 self.__append_branch(child, depth=depth + 1) | 250 self.__append_branch(child, depth=depth + 1) |
243 | 251 |
244 | 252 |
245 def main(argv): | 253 def main(argv): |
246 colorama.init() | 254 colorama.init() |
(...skipping 14 matching lines...) Expand all Loading... |
261 opts = parser.parse_args(argv[1:]) | 269 opts = parser.parse_args(argv[1:]) |
262 | 270 |
263 mapper = BranchMapper() | 271 mapper = BranchMapper() |
264 mapper.verbosity = opts.v | 272 mapper.verbosity = opts.v |
265 mapper.output.nocolor = opts.nocolor | 273 mapper.output.nocolor = opts.nocolor |
266 mapper.start() | 274 mapper.start() |
267 print mapper.output.as_formatted_string() | 275 print mapper.output.as_formatted_string() |
268 | 276 |
269 if __name__ == '__main__': | 277 if __name__ == '__main__': |
270 sys.exit(main(sys.argv)) | 278 sys.exit(main(sys.argv)) |
OLD | NEW |