| 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 |
| (...skipping 13 matching lines...) Expand all Loading... |
| 24 upstream, then you will see this. | 24 upstream, then you will see this. |
| 25 """ | 25 """ |
| 26 | 26 |
| 27 import argparse | 27 import argparse |
| 28 import collections | 28 import collections |
| 29 import sys | 29 import sys |
| 30 | 30 |
| 31 from third_party import colorama | 31 from third_party import colorama |
| 32 from third_party.colorama import Fore, Style | 32 from third_party.colorama import Fore, Style |
| 33 | 33 |
| 34 from git_common import current_branch, upstream, tags, get_all_tracking_info | 34 from git_common import current_branch, upstream, tags, get_branches_info |
| 35 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION | 35 from git_common import get_git_version, MIN_UPSTREAM_TRACK_GIT_VERSION |
| 36 | 36 |
| 37 import git_cl | 37 import git_cl |
| 38 | 38 |
| 39 DEFAULT_SEPARATOR = ' ' * 4 | 39 DEFAULT_SEPARATOR = ' ' * 4 |
| 40 | 40 |
| 41 | 41 |
| 42 class OutputManager(object): | 42 class OutputManager(object): |
| 43 """Manages a number of OutputLines and formats them into aligned columns.""" | 43 """Manages a number of OutputLines and formats them into aligned columns.""" |
| 44 | 44 |
| (...skipping 51 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 96 padding = (max_column_lengths[i] - len(data)) * ' ' | 96 padding = (max_column_lengths[i] - len(data)) * ' ' |
| 97 output_string += color + data + padding + separator | 97 output_string += color + data + padding + separator |
| 98 | 98 |
| 99 return output_string.rstrip() | 99 return output_string.rstrip() |
| 100 | 100 |
| 101 | 101 |
| 102 class BranchMapper(object): | 102 class BranchMapper(object): |
| 103 """A class which constructs output representing the tree's branch structure. | 103 """A class which constructs output representing the tree's branch structure. |
| 104 | 104 |
| 105 Attributes: | 105 Attributes: |
| 106 __tracking_info: a map of branches to their TrackingInfo objects which | 106 __branches_info: a map of branches to their BranchesInfo objects which |
| 107 consist of the branch hash, upstream and ahead/behind status. | 107 consist of the branch hash, upstream and ahead/behind status. |
| 108 __gone_branches: a set of upstreams which are not fetchable by git""" | 108 __gone_branches: a set of upstreams which are not fetchable by git""" |
| 109 | 109 |
| 110 def __init__(self): | 110 def __init__(self): |
| 111 self.verbosity = 0 | 111 self.verbosity = 0 |
| 112 self.output = OutputManager() | 112 self.output = OutputManager() |
| 113 self.__tracking_info = get_all_tracking_info() | |
| 114 self.__gone_branches = set() | 113 self.__gone_branches = set() |
| 115 self.__roots = set() | 114 self.__branches_info = None |
| 115 self.__parent_map = collections.defaultdict(list) |
| 116 self.__current_branch = None |
| 117 self.__current_hash = None |
| 118 self.__tag_set = None |
| 119 |
| 120 def start(self): |
| 121 self.__branches_info = get_branches_info( |
| 122 include_tracking_status=self.verbosity >= 1) |
| 123 roots = set() |
| 116 | 124 |
| 117 # A map of parents to a list of their children. | 125 # A map of parents to a list of their children. |
| 118 self.parent_map = collections.defaultdict(list) | 126 for branch, branch_info in self.__branches_info.iteritems(): |
| 119 for branch, branch_info in self.__tracking_info.iteritems(): | |
| 120 if not branch_info: | 127 if not branch_info: |
| 121 continue | 128 continue |
| 122 | 129 |
| 123 parent = branch_info.upstream | 130 parent = branch_info.upstream |
| 124 if parent and not self.__tracking_info[parent]: | 131 if parent and not self.__branches_info[parent]: |
| 125 branch_upstream = upstream(branch) | 132 branch_upstream = upstream(branch) |
| 126 # 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. |
| 127 if branch_upstream: | 134 if branch_upstream: |
| 128 parent = branch_upstream | 135 parent = branch_upstream |
| 129 else: | 136 else: |
| 130 self.__gone_branches.add(parent) | 137 self.__gone_branches.add(parent) |
| 131 # A parent that isn't in the tracking info is a root. | 138 # A parent that isn't in the branches info is a root. |
| 132 self.__roots.add(parent) | 139 roots.add(parent) |
| 133 | 140 |
| 134 self.parent_map[parent].append(branch) | 141 self.__parent_map[parent].append(branch) |
| 135 | 142 |
| 136 self.__current_branch = current_branch() | 143 self.__current_branch = current_branch() |
| 137 self.__current_hash = self.__tracking_info[self.__current_branch].hash | 144 self.__current_hash = self.__branches_info[self.__current_branch].hash |
| 138 self.__tag_set = tags() | 145 self.__tag_set = tags() |
| 139 | 146 |
| 140 def start(self): | 147 for root in sorted(roots): |
| 141 for root in sorted(self.__roots): | |
| 142 self.__append_branch(root) | 148 self.__append_branch(root) |
| 143 | 149 |
| 144 def __is_invalid_parent(self, parent): | 150 def __is_invalid_parent(self, parent): |
| 145 return not parent or parent in self.__gone_branches | 151 return not parent or parent in self.__gone_branches |
| 146 | 152 |
| 147 def __color_for_branch(self, branch, branch_hash): | 153 def __color_for_branch(self, branch, branch_hash): |
| 148 if branch.startswith('origin'): | 154 if branch.startswith('origin'): |
| 149 color = Fore.RED | 155 color = Fore.RED |
| 150 elif self.__is_invalid_parent(branch) or branch in self.__tag_set: | 156 elif self.__is_invalid_parent(branch) or branch in self.__tag_set: |
| 151 color = Fore.MAGENTA | 157 color = Fore.MAGENTA |
| 152 elif branch_hash == self.__current_hash: | 158 elif branch_hash == self.__current_hash: |
| 153 color = Fore.CYAN | 159 color = Fore.CYAN |
| 154 else: | 160 else: |
| 155 color = Fore.GREEN | 161 color = Fore.GREEN |
| 156 | 162 |
| 157 if branch_hash == self.__current_hash: | 163 if branch_hash == self.__current_hash: |
| 158 color += Style.BRIGHT | 164 color += Style.BRIGHT |
| 159 else: | 165 else: |
| 160 color += Style.NORMAL | 166 color += Style.NORMAL |
| 161 | 167 |
| 162 return color | 168 return color |
| 163 | 169 |
| 164 def __append_branch(self, branch, depth=0): | 170 def __append_branch(self, branch, depth=0): |
| 165 """Recurses through the tree structure and appends an OutputLine to the | 171 """Recurses through the tree structure and appends an OutputLine to the |
| 166 OutputManager for each branch.""" | 172 OutputManager for each branch.""" |
| 167 branch_info = self.__tracking_info[branch] | 173 branch_info = self.__branches_info[branch] |
| 168 branch_hash = branch_info.hash if branch_info else None | 174 branch_hash = branch_info.hash if branch_info else None |
| 169 | 175 |
| 170 line = OutputLine() | 176 line = OutputLine() |
| 171 | 177 |
| 172 # The branch name with appropriate indentation. | 178 # The branch name with appropriate indentation. |
| 173 suffix = '' | 179 suffix = '' |
| 174 if branch == self.__current_branch or ( | 180 if branch == self.__current_branch or ( |
| 175 self.__current_branch == 'HEAD' and branch == self.__current_hash): | 181 self.__current_branch == 'HEAD' and branch == self.__current_hash): |
| 176 suffix = ' *' | 182 suffix = ' *' |
| 177 branch_string = branch | 183 branch_string = branch |
| (...skipping 40 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 218 line.append(back_separator) | 224 line.append(back_separator) |
| 219 | 225 |
| 220 # The Rietveld issue associated with the branch. | 226 # The Rietveld issue associated with the branch. |
| 221 if self.verbosity >= 2: | 227 if self.verbosity >= 2: |
| 222 none_text = '' if self.__is_invalid_parent(branch) else 'None' | 228 none_text = '' if self.__is_invalid_parent(branch) else 'None' |
| 223 url = git_cl.Changelist(branchref=branch).GetIssueURL() | 229 url = git_cl.Changelist(branchref=branch).GetIssueURL() |
| 224 line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE) | 230 line.append(url or none_text, color=Fore.BLUE if url else Fore.WHITE) |
| 225 | 231 |
| 226 self.output.append(line) | 232 self.output.append(line) |
| 227 | 233 |
| 228 for child in sorted(self.parent_map.pop(branch, ())): | 234 for child in sorted(self.__parent_map.pop(branch, ())): |
| 229 self.__append_branch(child, depth=depth + 1) | 235 self.__append_branch(child, depth=depth + 1) |
| 230 | 236 |
| 231 | 237 |
| 232 def main(argv): | 238 def main(argv): |
| 233 colorama.init() | 239 colorama.init() |
| 234 if get_git_version() < MIN_UPSTREAM_TRACK_GIT_VERSION: | 240 if get_git_version() < MIN_UPSTREAM_TRACK_GIT_VERSION: |
| 235 print >> sys.stderr, ( | 241 print >> sys.stderr, ( |
| 236 'This tool will not show all tracking information for git version ' | 242 'This tool will not show all tracking information for git version ' |
| 237 'earlier than ' + | 243 'earlier than ' + |
| 238 '.'.join(str(x) for x in MIN_UPSTREAM_TRACK_GIT_VERSION) + | 244 '.'.join(str(x) for x in MIN_UPSTREAM_TRACK_GIT_VERSION) + |
| 239 '. Please consider upgrading.') | 245 '. Please consider upgrading.') |
| 240 | 246 |
| 241 parser = argparse.ArgumentParser( | 247 parser = argparse.ArgumentParser( |
| 242 description='Print a a tree of all branches parented by their upstreams') | 248 description='Print a a tree of all branches parented by their upstreams') |
| 243 parser.add_argument('-v', action='count', | 249 parser.add_argument('-v', action='count', |
| 244 help='Display branch hash and Rietveld URL') | 250 help='Display branch hash and Rietveld URL') |
| 245 parser.add_argument('--no-color', action='store_true', dest='nocolor', | 251 parser.add_argument('--no-color', action='store_true', dest='nocolor', |
| 246 help='Turn off colors.') | 252 help='Turn off colors.') |
| 247 | 253 |
| 248 opts = parser.parse_args(argv[1:]) | 254 opts = parser.parse_args(argv[1:]) |
| 249 | 255 |
| 250 mapper = BranchMapper() | 256 mapper = BranchMapper() |
| 251 mapper.verbosity = opts.v | 257 mapper.verbosity = opts.v |
| 252 mapper.output.nocolor = opts.nocolor | 258 mapper.output.nocolor = opts.nocolor |
| 253 mapper.start() | 259 mapper.start() |
| 254 print mapper.output.as_formatted_string() | 260 print mapper.output.as_formatted_string() |
| 255 | 261 |
| 256 if __name__ == '__main__': | 262 if __name__ == '__main__': |
| 257 sys.exit(main(sys.argv)) | 263 sys.exit(main(sys.argv)) |
| OLD | NEW |