| 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 |