mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-10-15 09:58:04 +00:00
info: reuse branch snapshots and combine history walks
Derive the current branch from GetBranches so attached HEAD and refs are resolved once, including overview reporting. Replace the two complementary rev-list calls in --all output with one --left-right symmetric-difference walk and partition its records in memory. Bug: 553599402 Change-Id: I01d5db804cbf591ecc0e08d6605971f0f5a38538 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/634005 Tested-by: Gavin Mak <gavinmak@google.com> Reviewed-by: Brian Gan <brgan@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
This commit is contained in:
1 parent
f6f5946422
commit
d5c17e9186
2 files changed
+174
-28
No files matched your search
+58
-26
@@ -18,7 +18,7 @@ import io
|
||||
import json
|
||||
import optparse
|
||||
import sys
|
||||
from typing import Any, Dict, List, NamedTuple
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
|
||||
|
||||
from color import Coloring
|
||||
from command import DEFAULT_LOCAL_JOBS
|
||||
@@ -189,18 +189,26 @@ class Info(PagedCommand):
|
||||
"superproject_revision": srev,
|
||||
}
|
||||
|
||||
@staticmethod
|
||||
def _GetCurrentBranch(branches: Dict[str, Any]) -> Optional[str]:
|
||||
"""Return the name of the current branch from a GetBranches mapping."""
|
||||
return next(
|
||||
(name for name, branch in branches.items() if branch.current), None
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def _getProjectData(cls, project) -> Dict[str, Any]:
|
||||
"""Gather project data as a dict."""
|
||||
branches = project.GetBranches()
|
||||
currentBranch = cls._GetCurrentBranch(branches)
|
||||
data = {
|
||||
"name": project.name,
|
||||
"mount_path": project.worktree,
|
||||
"current_revision": project.GetHeadRevisionId()
|
||||
or project.GetRevisionId(),
|
||||
"manifest_revision": project.revisionExpr,
|
||||
"local_branches": list(project.GetBranches()),
|
||||
"local_branches": list(branches),
|
||||
}
|
||||
currentBranch = project.CurrentBranch
|
||||
if currentBranch:
|
||||
data["current_branch"] = currentBranch
|
||||
return data
|
||||
@@ -285,6 +293,9 @@ class Info(PagedCommand):
|
||||
text = out.nofmt_printer("text")
|
||||
dimtext = out.printer("dimtext", attr="dim")
|
||||
|
||||
branches = project.GetBranches()
|
||||
currentBranch = cls._GetCurrentBranch(branches)
|
||||
|
||||
heading("Project: ")
|
||||
headtext(project.name)
|
||||
out.nl()
|
||||
@@ -297,7 +308,6 @@ class Info(PagedCommand):
|
||||
headtext(project.GetHeadRevisionId() or project.GetRevisionId())
|
||||
out.nl()
|
||||
|
||||
currentBranch = project.CurrentBranch
|
||||
if currentBranch:
|
||||
heading("Current branch: ")
|
||||
headtext(currentBranch)
|
||||
@@ -307,7 +317,7 @@ class Info(PagedCommand):
|
||||
headtext(project.revisionExpr)
|
||||
out.nl()
|
||||
|
||||
localBranches = list(project.GetBranches().keys())
|
||||
localBranches = list(branches)
|
||||
heading("Local Branches: ")
|
||||
redtext(str(len(localBranches)))
|
||||
if localBranches:
|
||||
@@ -327,25 +337,10 @@ class Info(PagedCommand):
|
||||
branch = branch[len(R_HEADS) :]
|
||||
logTarget = R_M + branch
|
||||
|
||||
bareTmp = project.bare_git._bare
|
||||
project.bare_git._bare = False
|
||||
localCommits = project.bare_git.rev_list(
|
||||
"--abbrev=8",
|
||||
"--abbrev-commit",
|
||||
"--pretty=oneline",
|
||||
logTarget + "..",
|
||||
"--",
|
||||
localCommits, originCommits = cls._GetDiffCommits(
|
||||
project, logTarget
|
||||
)
|
||||
|
||||
originCommits = project.bare_git.rev_list(
|
||||
"--abbrev=8",
|
||||
"--abbrev-commit",
|
||||
"--pretty=oneline",
|
||||
".." + logTarget,
|
||||
"--",
|
||||
)
|
||||
project.bare_git._bare = bareTmp
|
||||
|
||||
heading("Local Commits: ")
|
||||
redtext(str(len(localCommits)))
|
||||
dimtext(" (on current branch)")
|
||||
@@ -375,6 +370,35 @@ class Info(PagedCommand):
|
||||
|
||||
return buf.getvalue()
|
||||
|
||||
@classmethod
|
||||
def _GetDiffCommits(
|
||||
cls, project: Any, log_target: str
|
||||
) -> Tuple[List[str], List[str]]:
|
||||
"""Return local-only and remote-only commits from one history walk."""
|
||||
git = getattr(project, "work_git", None) or getattr(
|
||||
project, "bare_git", None
|
||||
)
|
||||
if git is None:
|
||||
return [], []
|
||||
commits = git.rev_list(
|
||||
"--left-right",
|
||||
"--abbrev=8",
|
||||
"--abbrev-commit",
|
||||
"--pretty=oneline",
|
||||
f"HEAD...{log_target}",
|
||||
"--",
|
||||
)
|
||||
if isinstance(commits, str):
|
||||
commits = commits.splitlines()
|
||||
local = []
|
||||
remote = []
|
||||
for commit in commits:
|
||||
if commit.startswith("<"):
|
||||
local.append(commit[1:])
|
||||
elif commit.startswith(">"):
|
||||
remote.append(commit[1:])
|
||||
return local, remote
|
||||
|
||||
def _printDiffInfo(self, opt, args):
|
||||
projs = self.GetProjects(args, all_manifests=not opt.this_manifest_only)
|
||||
|
||||
@@ -404,10 +428,18 @@ class Info(PagedCommand):
|
||||
project = cls.get_parallel_context()["projects"][project_idx]
|
||||
|
||||
branches = []
|
||||
br = [project.GetUploadableBranch(x) for x in project.GetBranches()]
|
||||
br = [x for x in br if x]
|
||||
local_branches = project.GetBranches()
|
||||
current_branch = cls._GetCurrentBranch(local_branches)
|
||||
if opt.current_branch:
|
||||
br = [x for x in br if x.name == project.CurrentBranch]
|
||||
candidate_branches = (
|
||||
[current_branch]
|
||||
if current_branch and current_branch in local_branches
|
||||
else []
|
||||
)
|
||||
else:
|
||||
candidate_branches = local_branches
|
||||
br = [project.GetUploadableBranch(x) for x in candidate_branches]
|
||||
br = [x for x in br if x]
|
||||
|
||||
for b in br:
|
||||
branches.append(
|
||||
@@ -416,7 +448,7 @@ class Info(PagedCommand):
|
||||
name=b.name,
|
||||
commits=b.commits,
|
||||
date=b.date,
|
||||
is_current=b.name == project.CurrentBranch,
|
||||
is_current=b.name == current_branch,
|
||||
)
|
||||
)
|
||||
return branches
|
||||
|
||||
+116
-2
@@ -208,7 +208,7 @@ def test_get_project_data_uses_head_revision() -> None:
|
||||
project.name = "foo"
|
||||
project.worktree = "/path/to/foo"
|
||||
project.revisionExpr = "refs/heads/main"
|
||||
project.GetBranches.return_value = []
|
||||
project.GetBranches.return_value = {}
|
||||
|
||||
# GetHeadRevisionId() returns a SHA, it should be used.
|
||||
project.GetHeadRevisionId.return_value = "head_sha_12345"
|
||||
@@ -235,7 +235,9 @@ def test_json_with_projects(capsys) -> None:
|
||||
project.name = "foo"
|
||||
project.worktree = "/path/to/foo"
|
||||
project.revisionExpr = "refs/heads/main"
|
||||
project.GetBranches.return_value = {"branch1": mock.MagicMock()}
|
||||
branch = mock.MagicMock()
|
||||
branch.current = True
|
||||
project.GetBranches.return_value = {"branch1": branch}
|
||||
project.GetHeadRevisionId.return_value = "head_sha_12345"
|
||||
project.CurrentBranch = "branch1"
|
||||
|
||||
@@ -253,3 +255,115 @@ def test_json_with_projects(capsys) -> None:
|
||||
assert project_data["manifest_revision"] == "refs/heads/main"
|
||||
assert project_data["local_branches"] == ["branch1"]
|
||||
assert project_data["current_branch"] == "branch1"
|
||||
|
||||
|
||||
def test_diff_commits_uses_one_left_right_walk() -> None:
|
||||
"""Local and remote commits are partitioned from one rev-list."""
|
||||
project = mock.MagicMock()
|
||||
project.work_git.rev_list.return_value = [
|
||||
"<11111111 local commit",
|
||||
">22222222 remote commit",
|
||||
]
|
||||
|
||||
local, remote = info.Info._GetDiffCommits(project, "refs/remotes/m/main")
|
||||
|
||||
assert local == ["11111111 local commit"]
|
||||
assert remote == ["22222222 remote commit"]
|
||||
project.work_git.rev_list.assert_called_once_with(
|
||||
"--left-right",
|
||||
"--abbrev=8",
|
||||
"--abbrev-commit",
|
||||
"--pretty=oneline",
|
||||
"HEAD...refs/remotes/m/main",
|
||||
"--",
|
||||
)
|
||||
|
||||
|
||||
def test_diff_commits_falls_back_to_bare_git_when_no_worktree() -> None:
|
||||
"""Bare or worktree-less projects fall back to bare_git for history walk."""
|
||||
project = mock.MagicMock()
|
||||
project.work_git = None
|
||||
project.bare_git.rev_list.return_value = [
|
||||
"<11111111 local commit",
|
||||
">22222222 remote commit",
|
||||
]
|
||||
|
||||
local, remote = info.Info._GetDiffCommits(project, "refs/remotes/m/main")
|
||||
|
||||
assert local == ["11111111 local commit"]
|
||||
assert remote == ["22222222 remote commit"]
|
||||
project.bare_git.rev_list.assert_called_once_with(
|
||||
"--left-right",
|
||||
"--abbrev=8",
|
||||
"--abbrev-commit",
|
||||
"--pretty=oneline",
|
||||
"HEAD...refs/remotes/m/main",
|
||||
"--",
|
||||
)
|
||||
|
||||
|
||||
def test_diff_commits_empty_output() -> None:
|
||||
"""Empty rev-list output produces empty local and remote commit lists."""
|
||||
project = mock.MagicMock()
|
||||
project.work_git.rev_list.return_value = []
|
||||
|
||||
local, remote = info.Info._GetDiffCommits(project, "refs/remotes/m/main")
|
||||
|
||||
assert local == []
|
||||
assert remote == []
|
||||
|
||||
|
||||
def test_get_current_branch() -> None:
|
||||
"""_GetCurrentBranch identifies the branch with current=True."""
|
||||
b1 = mock.MagicMock(current=False)
|
||||
b2 = mock.MagicMock(current=True)
|
||||
assert info.Info._GetCurrentBranch({"b1": b1, "b2": b2}) == "b2"
|
||||
assert info.Info._GetCurrentBranch({"b1": b1}) is None
|
||||
assert info.Info._GetCurrentBranch({}) is None
|
||||
|
||||
|
||||
def test_overview_helper_current_branch_filters_before_uploadable(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""_OverviewHelper only checks uploadable state for the current branch."""
|
||||
project = mock.MagicMock()
|
||||
project.RelPath.return_value = "proj"
|
||||
b1 = mock.MagicMock(current=False)
|
||||
b2 = mock.MagicMock(current=True)
|
||||
project.GetBranches.return_value = {"b1": b1, "b2": b2}
|
||||
uploadable = mock.MagicMock(commits=["c1"], date="2026-09-21")
|
||||
uploadable.name = "b2"
|
||||
project.GetUploadableBranch.return_value = uploadable
|
||||
monkeypatch.setattr(
|
||||
info.Info,
|
||||
"get_parallel_context",
|
||||
lambda: {"projects": [project]},
|
||||
)
|
||||
opt = mock.MagicMock(current_branch=True, this_manifest_only=False)
|
||||
|
||||
result = info.Info._OverviewHelper(0, opt)
|
||||
|
||||
project.GetUploadableBranch.assert_called_once_with("b2")
|
||||
assert len(result) == 1
|
||||
assert result[0].name == "b2"
|
||||
assert result[0].is_current is True
|
||||
|
||||
|
||||
def test_overview_helper_current_branch_detached_head_skips_uploadable(
|
||||
monkeypatch: pytest.MonkeyPatch,
|
||||
) -> None:
|
||||
"""_OverviewHelper skips GetUploadableBranch when detached with -b."""
|
||||
project = mock.MagicMock()
|
||||
b1 = mock.MagicMock(current=False)
|
||||
project.GetBranches.return_value = {"b1": b1}
|
||||
monkeypatch.setattr(
|
||||
info.Info,
|
||||
"get_parallel_context",
|
||||
lambda: {"projects": [project]},
|
||||
)
|
||||
opt = mock.MagicMock(current_branch=True, this_manifest_only=False)
|
||||
|
||||
result = info.Info._OverviewHelper(0, opt)
|
||||
|
||||
project.GetUploadableBranch.assert_not_called()
|
||||
assert result == []
|
||||
Reference in new issue
Block a user