From 91986011b012ca7cf881246976282dd581b88ad3 Mon Sep 17 00:00:00 2001 From: Gavin Mak Date: Mon, 29 Jun 2026 10:42:01 -0700 Subject: [PATCH] info: Parallelize project data gathering for JSON output https://gerrit-review.googlesource.com/c/git-repo/+/581921 parallelized `repo info` for text output. This commit does the same for JSON output format. Benchmarked `repo info --format=json` on an Android workspace with ~3k projects (N=3): - Before (sequential): 1m 30s average - After (parallelized): 46s average (~2x speedup) Verified that the JSON output is identical before and after. Bug: 526685287 Change-Id: If573223aba584f8b932f87d29e34ed565c5c930a Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/601861 Commit-Queue: Gavin Mak Reviewed-by: Brian Gan Tested-by: Gavin Mak --- subcmds/info.py | 26 ++++++++++++++++++++++++-- tests/test_subcmds_info.py | 30 ++++++++++++++++++++++++++++++ 2 files changed, 54 insertions(+), 2 deletions(-) diff --git a/subcmds/info.py b/subcmds/info.py index a089838a4..348a7c827 100644 --- a/subcmds/info.py +++ b/subcmds/info.py @@ -191,7 +191,8 @@ class Info(PagedCommand): "superproject_revision": srev, } - def _getProjectData(self, project) -> Dict[str, Any]: + @classmethod + def _getProjectData(cls, project) -> Dict[str, Any]: """Gather project data as a dict.""" data = { "name": project.name, @@ -206,6 +207,12 @@ class Info(PagedCommand): data["current_branch"] = currentBranch return data + @classmethod + def _ProjectDataHelper(cls, project_idx: int) -> Dict[str, Any]: + """Helper to get project data in parallel.""" + project = cls.get_parallel_context()["projects"][project_idx] + return cls._getProjectData(project) + def _ExecuteJson(self, opt, args) -> None: """Output info as JSON.""" result = {} @@ -215,7 +222,22 @@ class Info(PagedCommand): projs = self.GetProjects( args, all_manifests=not opt.this_manifest_only ) - result["projects"] = [self._getProjectData(p) for p in projs] + project_data = [] + + def _ProcessResults(_pool, _output, results): + project_data.extend(results) + + with self.ParallelContext(): + self.get_parallel_context()["projects"] = projs + self.ExecuteInParallel( + opt.jobs, + self._ProjectDataHelper, + range(len(projs)), + callback=_ProcessResults, + ordered=True, + chunksize=1, + ) + result["projects"] = project_data json_settings = { # JSON style guide says Unicode characters are fully allowed. diff --git a/tests/test_subcmds_info.py b/tests/test_subcmds_info.py index dd6065339..922b1088c 100644 --- a/tests/test_subcmds_info.py +++ b/tests/test_subcmds_info.py @@ -223,3 +223,33 @@ def test_get_project_data_uses_head_revision() -> None: project.GetHeadRevisionId.return_value = None data = cmd._getProjectData(project) assert data["current_revision"] == "manifest_sha_54321" + + +def test_json_with_projects(capsys) -> None: + """--format=json should emit project data.""" + cmd = _get_cmd() + opts, args = cmd.OptionParser.parse_args(["--format=json"]) + opts.jobs = 1 # To avoid multiprocessing pickle issues with mocks + + project = mock.MagicMock() + project.name = "foo" + project.worktree = "/path/to/foo" + project.revisionExpr = "refs/heads/main" + project.GetBranches.return_value = {"branch1": mock.MagicMock()} + project.GetHeadRevisionId.return_value = "head_sha_12345" + project.CurrentBranch = "branch1" + + cmd.GetProjects = mock.MagicMock(return_value=[project]) + + cmd.Execute(opts, args) + + data = json.loads(capsys.readouterr().out) + assert "projects" in data + assert len(data["projects"]) == 1 + project_data = data["projects"][0] + assert project_data["name"] == "foo" + assert project_data["mount_path"] == "/path/to/foo" + assert project_data["current_revision"] == "head_sha_12345" + assert project_data["manifest_revision"] == "refs/heads/main" + assert project_data["local_branches"] == ["branch1"] + assert project_data["current_branch"] == "branch1"