branches.py 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220
  1. # -*- coding:utf-8 -*-
  2. #
  3. # Copyright (C) 2009 The Android Open Source Project
  4. #
  5. # Licensed under the Apache License, Version 2.0 (the "License");
  6. # you may not use this file except in compliance with the License.
  7. # You may obtain a copy of the License at
  8. #
  9. # http://www.apache.org/licenses/LICENSE-2.0
  10. #
  11. # Unless required by applicable law or agreed to in writing, software
  12. # distributed under the License is distributed on an "AS IS" BASIS,
  13. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  14. # See the License for the specific language governing permissions and
  15. # limitations under the License.
  16. from __future__ import print_function
  17. import itertools
  18. import multiprocessing
  19. import sys
  20. from color import Coloring
  21. from command import Command
  22. # Number of projects to submit to a single worker process at a time.
  23. # This number represents a tradeoff between the overhead of IPC and finer
  24. # grained opportunity for parallelism. This particular value was chosen by
  25. # iterating through powers of two until the overall performance no longer
  26. # improved. The performance of this batch size is not a function of the
  27. # number of cores on the system.
  28. WORKER_BATCH_SIZE = 32
  29. class BranchColoring(Coloring):
  30. def __init__(self, config):
  31. Coloring.__init__(self, config, 'branch')
  32. self.current = self.printer('current', fg='green')
  33. self.local = self.printer('local')
  34. self.notinproject = self.printer('notinproject', fg='red')
  35. class BranchInfo(object):
  36. def __init__(self, name):
  37. self.name = name
  38. self.current = 0
  39. self.published = 0
  40. self.published_equal = 0
  41. self.projects = []
  42. def add(self, b):
  43. if b.current:
  44. self.current += 1
  45. if b.published:
  46. self.published += 1
  47. if b.revision == b.published:
  48. self.published_equal += 1
  49. self.projects.append(b)
  50. @property
  51. def IsCurrent(self):
  52. return self.current > 0
  53. @property
  54. def IsSplitCurrent(self):
  55. return self.current != 0 and self.current != len(self.projects)
  56. @property
  57. def IsPublished(self):
  58. return self.published > 0
  59. @property
  60. def IsPublishedEqual(self):
  61. return self.published_equal == len(self.projects)
  62. class Branches(Command):
  63. common = True
  64. helpSummary = "View current topic branches"
  65. helpUsage = """
  66. %prog [<project>...]
  67. Summarizes the currently available topic branches.
  68. # Branch Display
  69. The branch display output by this command is organized into four
  70. columns of information; for example:
  71. *P nocolor | in repo
  72. repo2 |
  73. The first column contains a * if the branch is the currently
  74. checked out branch in any of the specified projects, or a blank
  75. if no project has the branch checked out.
  76. The second column contains either blank, p or P, depending upon
  77. the upload status of the branch.
  78. (blank): branch not yet published by repo upload
  79. P: all commits were published by repo upload
  80. p: only some commits were published by repo upload
  81. The third column contains the branch name.
  82. The fourth column (after the | separator) lists the projects that
  83. the branch appears in, or does not appear in. If no project list
  84. is shown, then the branch appears in all projects.
  85. """
  86. def _Options(self, p):
  87. """Add flags to CLI parser for this subcommand."""
  88. default_jobs = min(multiprocessing.cpu_count(), 8)
  89. p.add_option(
  90. '-j',
  91. '--jobs',
  92. type=int,
  93. default=default_jobs,
  94. help='Number of worker processes to spawn '
  95. '(default: %s)' % default_jobs)
  96. def Execute(self, opt, args):
  97. projects = self.GetProjects(args)
  98. out = BranchColoring(self.manifest.manifestProject.config)
  99. all_branches = {}
  100. project_cnt = len(projects)
  101. with multiprocessing.Pool(processes=opt.jobs) as pool:
  102. project_branches = pool.imap_unordered(
  103. expand_project_to_branches, projects, chunksize=WORKER_BATCH_SIZE)
  104. for name, b in itertools.chain.from_iterable(project_branches):
  105. if name not in all_branches:
  106. all_branches[name] = BranchInfo(name)
  107. all_branches[name].add(b)
  108. names = sorted(all_branches)
  109. if not names:
  110. print(' (no branches)', file=sys.stderr)
  111. return
  112. width = 25
  113. for name in names:
  114. if width < len(name):
  115. width = len(name)
  116. for name in names:
  117. i = all_branches[name]
  118. in_cnt = len(i.projects)
  119. if i.IsCurrent:
  120. current = '*'
  121. hdr = out.current
  122. else:
  123. current = ' '
  124. hdr = out.local
  125. if i.IsPublishedEqual:
  126. published = 'P'
  127. elif i.IsPublished:
  128. published = 'p'
  129. else:
  130. published = ' '
  131. hdr('%c%c %-*s' % (current, published, width, name))
  132. out.write(' |')
  133. if in_cnt < project_cnt:
  134. fmt = out.write
  135. paths = []
  136. non_cur_paths = []
  137. if i.IsSplitCurrent or (in_cnt < project_cnt - in_cnt):
  138. in_type = 'in'
  139. for b in i.projects:
  140. if not i.IsSplitCurrent or b.current:
  141. paths.append(b.project.relpath)
  142. else:
  143. non_cur_paths.append(b.project.relpath)
  144. else:
  145. fmt = out.notinproject
  146. in_type = 'not in'
  147. have = set()
  148. for b in i.projects:
  149. have.add(b.project)
  150. for p in projects:
  151. if p not in have:
  152. paths.append(p.relpath)
  153. s = ' %s %s' % (in_type, ', '.join(paths))
  154. if not i.IsSplitCurrent and (width + 7 + len(s) < 80):
  155. fmt = out.current if i.IsCurrent else fmt
  156. fmt(s)
  157. else:
  158. fmt(' %s:' % in_type)
  159. fmt = out.current if i.IsCurrent else out.write
  160. for p in paths:
  161. out.nl()
  162. fmt(width * ' ' + ' %s' % p)
  163. fmt = out.write
  164. for p in non_cur_paths:
  165. out.nl()
  166. fmt(width * ' ' + ' %s' % p)
  167. else:
  168. out.write(' in all projects')
  169. out.nl()
  170. def expand_project_to_branches(project):
  171. """Expands a project into a list of branch names & associated information.
  172. Args:
  173. project: project.Project
  174. Returns:
  175. List[Tuple[str, git_config.Branch]]
  176. """
  177. branches = []
  178. for name, b in project.GetBranches().items():
  179. b.project = project
  180. branches.append((name, b))
  181. return branches