branches.py 6.1 KB

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