# Copyright (C) 2009 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import itertools import sys from color import Coloring from command import Command from command import DEFAULT_LOCAL_JOBS class BranchColoring(Coloring): def __init__(self, config): Coloring.__init__(self, config, "branch") self.current = self.printer("current", fg="green") self.local = self.printer("local") self.notinproject = self.printer("notinproject", fg="red") class BranchInfo: def __init__(self, name): self.name = name self.current = 0 self.published = 0 self.published_equal = 0 self.projects = [] def add(self, b): if b.current: self.current += 1 if b.published: self.published += 1 if b.revision == b.published: self.published_equal += 1 self.projects.append(b) @property def IsCurrent(self): return self.current > 0 @property def IsSplitCurrent(self): return self.current != 0 and self.current != len(self.projects) @property def IsPublished(self): return self.published > 0 @property def IsPublishedEqual(self): return self.published_equal == len(self.projects) class Branches(Command): COMMON = True helpSummary = "View current topic branches" helpUsage = """ %prog [...] Summarizes the currently available topic branches. # Branch Display The branch display output by this command is organized into four columns of information; for example: *P nocolor | in repo repo2 | The first column contains a * if the branch is the currently checked out branch in any of the specified projects, or a blank if no project has the branch checked out. The second column contains either blank, p or P, depending upon the upload status of the branch. (blank): branch not yet published by repo upload P: all commits were published by repo upload p: only some commits were published by repo upload The third column contains the branch name. The fourth column (after the | separator) lists the projects that the branch appears in, or does not appear in. If no project list is shown, then the branch appears in all projects. """ PARALLEL_JOBS = DEFAULT_LOCAL_JOBS @classmethod def _ExpandProjectToBranches(cls, project_idx): """Expands a project into a list of branch names & associated info. Args: project_idx: project.Project index Returns: List[Tuple[str, git_config.Branch, int]] """ branches = [] project = cls.get_parallel_context()["projects"][project_idx] for name, b in project.GetBranches().items(): branches.append((name, b, project_idx)) return branches def Execute(self, opt, args): projects = self.GetProjects( args, all_manifests=not opt.this_manifest_only ) out = BranchColoring(self.manifest.manifestProject.config) all_branches = {} project_cnt = len(projects) def _ProcessResults(_pool, _output, results): for name, b, project_idx in itertools.chain.from_iterable(results): b.project = projects[project_idx] if name not in all_branches: all_branches[name] = BranchInfo(name) all_branches[name].add(b) with self.ParallelContext(): self.get_parallel_context()["projects"] = projects self.ExecuteInParallel( opt.jobs, self._ExpandProjectToBranches, range(len(projects)), callback=_ProcessResults, ) names = sorted(all_branches) if not names: print(" (no branches)", file=sys.stderr) return width = 25 for name in names: if width < len(name): width = len(name) for name in names: i = all_branches[name] in_cnt = len(i.projects) if i.IsCurrent: current = "*" hdr = out.current else: current = " " hdr = out.local if i.IsPublishedEqual: published = "P" elif i.IsPublished: published = "p" else: published = " " # A branch name can contain a percent sign, so we need to escape it. # Escape after f-string formatting to properly account for leading # spaces. hdr(f"{current}{published} {name:{width}}".replace("%", "%%")) out.write(" |") _RelPath = lambda p: p.RelPath(local=opt.this_manifest_only) if in_cnt < project_cnt: fmt = out.write paths = [] non_cur_paths = [] if i.IsSplitCurrent or (in_cnt <= project_cnt - in_cnt): in_type = "in" for b in i.projects: relpath = _RelPath(b.project) if not i.IsSplitCurrent or b.current: paths.append(relpath) else: non_cur_paths.append(relpath) else: fmt = out.notinproject in_type = "not in" have = set() for b in i.projects: have.add(_RelPath(b.project)) for p in projects: if _RelPath(p) not in have: paths.append(_RelPath(p)) s = f" {in_type} {', '.join(paths)}" if not i.IsSplitCurrent and (width + 7 + len(s) < 80): fmt = out.current if i.IsCurrent else fmt fmt(s) else: fmt(" %s:" % in_type) fmt = out.current if i.IsCurrent else out.write for p in paths: out.nl() fmt(width * " " + " %s" % p) fmt = out.write for p in non_cur_paths: out.nl() fmt(width * " " + " %s" % p) else: out.write(" in all projects") out.nl()