mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-11-15 00:37:13 +00:00
init, sync: support per-branch smart sync branch config
Add `repo init --smart-sync-branch=BRANCH` to persist a server-side Smart Sync branch override in `repo.<manifest-branch>.smartsyncbranch` inside .repo/manifests.git/config (with an empty string clearing it). During Smart Sync, non-empty SYNC_BRANCH still takes top precedence, while SYNC_BRANCH="" forces the raw manifest branch over checkout config. Bug: 543101585 Bug: 585378823 Change-Id: I933a48d03ed5ebd83aa5aba9ddde761dfac23243 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/655063 Reviewed-by: Brian Gan <brgan@google.com> Tested-by: Gavin Mak <gavinmak@google.com> Commit-Queue: Gavin Mak <gavinmak@google.com>
This commit is contained in:
1 parent
af53524261
commit
032f2341ec
13 files changed
+387
-50
No files matched your search
@@ -249,6 +249,7 @@ _repo() {
|
||||
'(-s --smart-sync)'{-s,--smart-sync}'[Default to smart sync]' \
|
||||
'--no-smart-sync[Do not default to smart sync]' \
|
||||
'--head[Do not default to smart sync]' \
|
||||
'--smart-sync-branch=[Smart sync branch]:branch:' \
|
||||
'--clone-bundle[Use clone bundle]' \
|
||||
'--no-clone-bundle[Do not use clone bundle]' \
|
||||
'--git-lfs[Use git lfs]' \
|
||||
|
||||
@@ -180,6 +180,7 @@ User controlled settings are initialized when running `repo init`.
|
||||
| repo.partialcloneexclude | `--partial-clone-exclude` | Comma-delimited list of project names (not paths) to exclude while using [partial git clones] |
|
||||
| repo.reference | `--reference` | Reference repo client checkout |
|
||||
| repo.smartsync | `--smart-sync` | Default to [Smart Sync](./smart-sync.md) when syncing |
|
||||
| repo.\<branch\>.smartsyncbranch | `--smart-sync-branch` | Server branch to query when [Smart Sync](./smart-sync.md)ing `<branch>` |
|
||||
| repo.submodules | `--submodules` | Sync git submodules |
|
||||
| repo.superproject | `--use-superproject` | Sync [superproject] |
|
||||
| repo.worktree | `--worktree` | Use [git worktree] for checkouts |
|
||||
|
||||
+33
-8
@@ -205,6 +205,23 @@ Sync fails (e.g. the manifest checkout is not tracking a remote branch,
|
||||
`<manifest-server>` is not defined, or the server cannot provide a usable
|
||||
manifest).
|
||||
|
||||
### `repo init --smart-sync-branch=BRANCH`
|
||||
|
||||
Users can configure a server-side Smart Sync branch override for the initialized
|
||||
manifest branch by passing `--smart-sync-branch=BRANCH` to `repo init`:
|
||||
|
||||
```bash
|
||||
repo init -u <url> -b main -s --smart-sync-branch=git_main-warmup
|
||||
```
|
||||
|
||||
This persists `repo.<manifest-branch>.smartsyncbranch` in
|
||||
`.repo/manifests.git/config`. Because the setting is scoped to the manifest
|
||||
branch, switching branches with `repo init -b <other>` will not send the
|
||||
override branch when syncing `<other>`. Passing `--smart-sync-branch=""` clears
|
||||
the override for the target manifest branch. Setting a non-empty override
|
||||
requires the manifest checkout to track a remote branch (not a tag or commit
|
||||
SHA).
|
||||
|
||||
### sync-smartsync (Manifest Attribute)
|
||||
|
||||
The manifest can enable Smart Sync by default for all `repo sync` invocations
|
||||
@@ -238,14 +255,22 @@ Explicitly enables Smart Sync. Repo will call
|
||||
`GetApprovedManifestV2({branch[, target]})`, or
|
||||
`GetApprovedManifest(branch[, target])` if the server does not implement it.
|
||||
|
||||
The `branch` is determined by the current manifest branch as specified by
|
||||
`--manifest-branch=BRANCH` when running `repo init`. If `${SYNC_BRANCH}` is
|
||||
defined and non-empty, its value is sent instead. This allows selecting a
|
||||
branch other than the one the checkout tracks, e.g. a server-side (CI) branch
|
||||
name that builds from the same manifest branch. If `${SYNC_BRANCH}` is unset or
|
||||
empty and the manifest checkout is not tracking a remote branch (e.g. it is on a
|
||||
detached HEAD, or was initialized with `-b <sha>` or `-b refs/tags/<tag>`),
|
||||
Smart Sync fails without contacting the manifest server.
|
||||
The `branch` sent to the manifest server is resolved in the following order:
|
||||
|
||||
1. `${SYNC_BRANCH}`: If defined and non-empty, its value is sent directly.
|
||||
If defined as an empty string (`SYNC_BRANCH=""`), repo sends the checkout's
|
||||
raw manifest branch, bypassing `repo.<manifest-branch>.smartsyncbranch`.
|
||||
2. `repo.<manifest-branch>.smartsyncbranch`: If configured via
|
||||
`repo init --smart-sync-branch=BRANCH`, its value is sent for that manifest
|
||||
branch.
|
||||
3. The current manifest branch (as selected by `--manifest-branch=BRANCH` when
|
||||
running `repo init`).
|
||||
|
||||
Overriding the branch allows selecting a server-side (CI) branch name that
|
||||
builds or warms caches from the same manifest branch. If `${SYNC_BRANCH}` is
|
||||
unset or empty and the manifest checkout is not tracking a remote branch (e.g.
|
||||
it is on a detached HEAD, or was initialized with `-b <sha>` or
|
||||
`-b refs/tags/<tag>`), Smart Sync fails without contacting the manifest server.
|
||||
|
||||
The `target` is defined by environment variables in the order below. If none
|
||||
of them match, then `target` is omitted. These variables were decided as they
|
||||
|
||||
+4
-1
@@ -1,5 +1,5 @@
|
||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||
.TH REPO "1" "June 2026" "repo init" "Repo Manual"
|
||||
.TH REPO "1" "November 2026" "repo init" "Repo Manual"
|
||||
.SH NAME
|
||||
repo \- repo init - manual page for repo init
|
||||
.SH SYNOPSIS
|
||||
@@ -109,6 +109,9 @@ default to smart sync when syncing projects
|
||||
\fB\-\-no\-smart\-sync\fR, \fB\-\-head\fR
|
||||
do not default to smart sync when syncing projects
|
||||
.TP
|
||||
\fB\-\-smart\-sync\-branch\fR=\fI\,BRANCH\/\fR
|
||||
server branch to query when smart syncing the manifest branch (empty string to clear)
|
||||
.TP
|
||||
\fB\-\-clone\-bundle\fR
|
||||
enable use of \fI\,/clone.bundle\/\fP on HTTP/HTTPS (default if not \fB\-\-partial\-clone\fR)
|
||||
.TP
|
||||
|
||||
+67
-30
@@ -29,7 +29,7 @@ import sys
|
||||
import tarfile
|
||||
import tempfile
|
||||
import time
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple
|
||||
from typing import Any, Dict, List, NamedTuple, Optional, Tuple, Union
|
||||
import urllib.parse
|
||||
|
||||
import color
|
||||
@@ -6081,6 +6081,27 @@ class ManifestProject(MetaProject):
|
||||
"""Whether we use smart sync by default."""
|
||||
return self.config.GetBoolean("repo.smartsync")
|
||||
|
||||
@staticmethod
|
||||
def _SmartSyncBranchKey(branch: str) -> str:
|
||||
"""Return the git config key for a branch's smart sync branch."""
|
||||
return f"repo.{branch}.smartsyncbranch"
|
||||
|
||||
def GetSmartSyncBranch(self, branch: Optional[str] = None) -> Optional[str]:
|
||||
"""Return the configured smart sync branch for |branch|."""
|
||||
if branch is None:
|
||||
branch = self.GetMergeBranch(heads_only=True)
|
||||
if not branch:
|
||||
return None
|
||||
return self.config.GetString(self._SmartSyncBranchKey(branch))
|
||||
|
||||
def SetSmartSyncBranch(
|
||||
self, branch: str, smart_sync_branch: Optional[str]
|
||||
) -> None:
|
||||
"""Set or clear the smart sync branch for |branch|."""
|
||||
self.config.SetString(
|
||||
self._SmartSyncBranchKey(branch), smart_sync_branch or None
|
||||
)
|
||||
|
||||
@property
|
||||
def partial_clone(self):
|
||||
"""Whether this is a partial clone."""
|
||||
@@ -6189,36 +6210,37 @@ class ManifestProject(MetaProject):
|
||||
|
||||
def Sync(
|
||||
self,
|
||||
_kwargs_only=(),
|
||||
manifest_url="",
|
||||
manifest_branch=None,
|
||||
standalone_manifest=False,
|
||||
groups="",
|
||||
mirror=False,
|
||||
reference="",
|
||||
dissociate=False,
|
||||
worktree=False,
|
||||
submodules=False,
|
||||
archive=False,
|
||||
partial_clone=None,
|
||||
depth=None,
|
||||
clone_filter="blob:none",
|
||||
partial_clone_exclude=None,
|
||||
clone_bundle=None,
|
||||
git_lfs=None,
|
||||
use_superproject=None,
|
||||
_kwargs_only: Tuple[Any, ...] = (),
|
||||
manifest_url: Optional[str] = "",
|
||||
manifest_branch: Optional[str] = None,
|
||||
standalone_manifest: Union[bool, str, None] = False,
|
||||
groups: Optional[str] = "",
|
||||
mirror: Optional[bool] = False,
|
||||
reference: Optional[str] = "",
|
||||
dissociate: Optional[bool] = False,
|
||||
worktree: Optional[bool] = False,
|
||||
submodules: Optional[bool] = False,
|
||||
archive: Optional[bool] = False,
|
||||
partial_clone: Optional[bool] = None,
|
||||
depth: Optional[int] = None,
|
||||
clone_filter: Optional[str] = "blob:none",
|
||||
partial_clone_exclude: Optional[str] = None,
|
||||
clone_bundle: Optional[bool] = None,
|
||||
git_lfs: Optional[bool] = None,
|
||||
use_superproject: Optional[bool] = None,
|
||||
smart_sync: Optional[bool] = None,
|
||||
verbose=False,
|
||||
current_branch_only=False,
|
||||
git_event_log=None,
|
||||
platform="",
|
||||
manifest_name="default.xml",
|
||||
tags="",
|
||||
this_manifest_only=False,
|
||||
outer_manifest=True,
|
||||
clone_filter_for_depth=None,
|
||||
use_local_gitdirs=False,
|
||||
):
|
||||
smart_sync_branch: Optional[str] = None,
|
||||
verbose: Optional[bool] = False,
|
||||
current_branch_only: Optional[bool] = False,
|
||||
git_event_log: Optional[git_trace2_event_log.EventLog] = None,
|
||||
platform: Optional[str] = "",
|
||||
manifest_name: Optional[str] = "default.xml",
|
||||
tags: Union[bool, str, None] = "",
|
||||
this_manifest_only: Optional[bool] = False,
|
||||
outer_manifest: bool = True,
|
||||
clone_filter_for_depth: Optional[str] = None,
|
||||
use_local_gitdirs: Optional[bool] = False,
|
||||
) -> bool:
|
||||
"""Sync the manifest and all submanifests.
|
||||
|
||||
Args:
|
||||
@@ -6250,6 +6272,8 @@ class ManifestProject(MetaProject):
|
||||
use_superproject: a boolean, whether to use the manifest
|
||||
superproject to sync projects.
|
||||
smart_sync: a boolean, whether to default to smart sync.
|
||||
smart_sync_branch: a string, server branch to query when smart
|
||||
syncing the manifest branch (empty string to clear).
|
||||
verbose: a boolean, whether to show all output, rather than only
|
||||
errors.
|
||||
current_branch_only: a boolean, whether to only fetch the current
|
||||
@@ -6298,6 +6322,7 @@ class ManifestProject(MetaProject):
|
||||
git_lfs=git_lfs,
|
||||
use_superproject=use_superproject,
|
||||
smart_sync=smart_sync,
|
||||
smart_sync_branch=smart_sync_branch,
|
||||
verbose=verbose,
|
||||
current_branch_only=current_branch_only,
|
||||
tags=tags,
|
||||
@@ -6619,6 +6644,18 @@ class ManifestProject(MetaProject):
|
||||
)
|
||||
return False
|
||||
|
||||
if smart_sync_branch is not None:
|
||||
target_branch = self.GetMergeBranch(heads_only=True)
|
||||
if target_branch:
|
||||
self.SetSmartSyncBranch(target_branch, smart_sync_branch)
|
||||
elif smart_sync_branch:
|
||||
logger.error(
|
||||
"fatal: --smart-sync-branch requires the manifest "
|
||||
"checkout to track a branch; run `repo init -b "
|
||||
"<branch>`"
|
||||
)
|
||||
return False
|
||||
|
||||
if not manifest_name:
|
||||
logger.error("fatal: manifest name (-m) is required.")
|
||||
return False
|
||||
|
||||
@@ -127,7 +127,7 @@ REPO_REV = os.environ.get("REPO_REV") or "stable"
|
||||
BUG_URL = "https://issues.gerritcodereview.com/issues/new?component=1370071"
|
||||
|
||||
# increment this whenever we make important changes to this script
|
||||
VERSION = (2, 71)
|
||||
VERSION = (2, 72)
|
||||
|
||||
# increment this if the MAINTAINER_KEYS block is modified
|
||||
KEYRING_VERSION = (2, 3)
|
||||
@@ -249,7 +249,7 @@ def GetParser():
|
||||
return parser
|
||||
|
||||
|
||||
def InitParser(parser):
|
||||
def InitParser(parser: optparse.OptionParser) -> optparse.OptionParser:
|
||||
"""Setup the CLI parser."""
|
||||
# NB: Keep in sync with command.py:_CommonOptions().
|
||||
|
||||
@@ -455,6 +455,14 @@ def InitParser(parser):
|
||||
dest="smart_sync",
|
||||
help="do not default to smart sync when syncing projects",
|
||||
)
|
||||
group.add_option(
|
||||
"--smart-sync-branch",
|
||||
action="store",
|
||||
default=None,
|
||||
metavar="BRANCH",
|
||||
help="server branch to query when smart syncing the manifest branch "
|
||||
"(empty string to clear)",
|
||||
)
|
||||
group.add_option(
|
||||
"--clone-bundle",
|
||||
action="store_true",
|
||||
|
||||
+10
-2
@@ -12,8 +12,10 @@
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
import optparse
|
||||
import os
|
||||
import sys
|
||||
from typing import List
|
||||
|
||||
import color
|
||||
import command
|
||||
@@ -128,7 +130,7 @@ to update the working directory files.
|
||||
"REPO_GIT_LFS": "git_lfs",
|
||||
}
|
||||
|
||||
def _SyncManifest(self, opt):
|
||||
def _SyncManifest(self, opt: optparse.Values) -> None:
|
||||
"""Call manifestProject.Sync with arguments from opt.
|
||||
|
||||
Args:
|
||||
@@ -162,6 +164,7 @@ to update the working directory files.
|
||||
git_lfs=opt.git_lfs,
|
||||
use_superproject=opt.use_superproject,
|
||||
smart_sync=opt.smart_sync,
|
||||
smart_sync_branch=opt.smart_sync_branch,
|
||||
verbose=opt.verbose,
|
||||
current_branch_only=opt.current_branch_only,
|
||||
tags=opt.tags,
|
||||
@@ -294,7 +297,7 @@ to update the working directory files.
|
||||
print(" rm -r %s" % os.path.join(self.manifest.topdir, ".repo"))
|
||||
print("and try again.")
|
||||
|
||||
def ValidateOptions(self, opt, args):
|
||||
def ValidateOptions(self, opt: optparse.Values, args: List[str]) -> None:
|
||||
if opt.reference:
|
||||
opt.reference = os.path.expanduser(opt.reference)
|
||||
|
||||
@@ -323,6 +326,11 @@ to update the working directory files.
|
||||
" be used with --standalone-manifest."
|
||||
)
|
||||
|
||||
if opt.standalone_manifest and opt.smart_sync_branch is not None:
|
||||
self.OptionParser.error(
|
||||
"--smart-sync-branch cannot be used with --standalone-manifest."
|
||||
)
|
||||
|
||||
if opt.manifest_upstream_branch and opt.manifest_branch is None:
|
||||
self.OptionParser.error(
|
||||
"--manifest-upstream-branch cannot be used without "
|
||||
|
||||
+15
-4
@@ -2309,10 +2309,21 @@ later is required to fix a server side protocol bug.
|
||||
if opt.smart_sync:
|
||||
# SYNC_BRANCH overrides the branch sent to the manifest server
|
||||
# (e.g. to select a specific CI branch that builds the same
|
||||
# manifest branch). An empty value is treated as unset.
|
||||
branch = os.environ.get("SYNC_BRANCH") or (
|
||||
manifest.manifestProject.GetMergeBranch(heads_only=True)
|
||||
)
|
||||
# manifest branch). Setting SYNC_BRANCH="" forces the raw
|
||||
# manifest branch, bypassing repo.<branch>.smartsyncbranch.
|
||||
if "SYNC_BRANCH" in os.environ:
|
||||
branch = os.environ["SYNC_BRANCH"] or (
|
||||
manifest.manifestProject.GetMergeBranch(heads_only=True)
|
||||
)
|
||||
else:
|
||||
manifest_branch = manifest.manifestProject.GetMergeBranch(
|
||||
heads_only=True
|
||||
)
|
||||
if manifest_branch:
|
||||
cfg_branch = manifest.manifestProject.GetSmartSyncBranch(
|
||||
manifest_branch
|
||||
)
|
||||
branch = cfg_branch or manifest_branch
|
||||
# Fail before running the helper or contacting the server.
|
||||
if not branch:
|
||||
raise SmartSyncError(
|
||||
|
||||
@@ -166,6 +166,12 @@ def clean_environ() -> Dict[str, str]:
|
||||
"pythonwarnings",
|
||||
# LUCI's git wrapper replaces HOME with this.
|
||||
"infra_git_wrapper_home",
|
||||
# Smart sync reads these (e.g. set by Android's envsetup/lunch).
|
||||
"sync_branch",
|
||||
"sync_target",
|
||||
"target_product",
|
||||
"target_release",
|
||||
"target_build_variant",
|
||||
):
|
||||
continue
|
||||
env[key] = value
|
||||
|
||||
+118
-1
@@ -14,8 +14,11 @@
|
||||
|
||||
"""E2E tests for the core `repo init` & `repo sync` options."""
|
||||
|
||||
import contextlib
|
||||
from pathlib import Path
|
||||
from typing import List, Set
|
||||
import threading
|
||||
from typing import Any, Callable, Dict, Iterator, List, Set
|
||||
import xmlrpc.server
|
||||
|
||||
from harness import GIT_DEFAULTS_PARAMS
|
||||
from harness import GitHost
|
||||
@@ -353,3 +356,117 @@ def test_manifest_depth(host: GitHost, client: RepoClient) -> None:
|
||||
assert client.is_shallow(manifests)
|
||||
assert not client.is_shallow("platform/foo")
|
||||
assert client.git("platform/foo", "rev-list", "--count", "HEAD") == "2"
|
||||
|
||||
|
||||
@contextlib.contextmanager
|
||||
def _manifest_server(
|
||||
handler: Callable[[Any], Dict[str, Any]],
|
||||
) -> Iterator[str]:
|
||||
"""Run a local XML-RPC manifest server exposing GetApprovedManifestV2."""
|
||||
server = xmlrpc.server.SimpleXMLRPCServer(
|
||||
("127.0.0.1", 0), logRequests=False
|
||||
)
|
||||
server.register_function(handler, "GetApprovedManifestV2")
|
||||
thread = threading.Thread(target=server.serve_forever, daemon=True)
|
||||
thread.start()
|
||||
try:
|
||||
addr, port = server.server_address[:2]
|
||||
yield f"http://{addr}:{port}/"
|
||||
finally:
|
||||
server.shutdown()
|
||||
server.server_close()
|
||||
thread.join()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("git_defaults", GIT_DEFAULTS_PARAMS)
|
||||
def test_smart_sync_branch(host: GitHost, client: RepoClient) -> None:
|
||||
"""`init --smart-sync-branch` persists per-manifest-branch server branch."""
|
||||
foo = host.create_repo("platform/foo", {"f": "v1"})
|
||||
v1 = foo.rev_parse("main")
|
||||
warmup = foo.commit({"f": "warmup"})
|
||||
foo.commit({"f": "tot"})
|
||||
r16 = foo.commit({"f": "r16"}, branch="release/16")
|
||||
|
||||
requests: List[Dict[str, Any]] = []
|
||||
by_branch = {
|
||||
"main": v1,
|
||||
"git_main-dev-warmup-abfs": warmup,
|
||||
"release/16": r16,
|
||||
}
|
||||
|
||||
def handle_v2(req: Dict[str, Any]) -> Dict[str, Any]:
|
||||
requests.append(req)
|
||||
rev = by_branch[req["branch"]]
|
||||
return {
|
||||
"success": True,
|
||||
"manifest_xml": manifest_xml(
|
||||
f'<project name="platform/foo" path="foo" revision="{rev}" />',
|
||||
fetch=host.url,
|
||||
),
|
||||
}
|
||||
|
||||
with _manifest_server(handle_v2) as server_url:
|
||||
m_xml = manifest_xml(
|
||||
f'<manifest-server url="{server_url}" />',
|
||||
'<project name="platform/foo" path="foo" />',
|
||||
)
|
||||
manifest = host.create_manifest(m_xml)
|
||||
manifest.commit({"default.xml": m_xml}, branch="release/16")
|
||||
|
||||
client.init(
|
||||
host.manifest_url,
|
||||
"-s",
|
||||
"--smart-sync-branch=git_main-dev-warmup-abfs",
|
||||
)
|
||||
assert (
|
||||
client.config(".repo/manifests", "repo.main.smartsyncbranch")
|
||||
== "git_main-dev-warmup-abfs"
|
||||
)
|
||||
|
||||
client.sync()
|
||||
assert requests[-1]["branch"] == "git_main-dev-warmup-abfs"
|
||||
assert client.head("foo") == warmup
|
||||
|
||||
# SYNC_BRANCH="" forces the raw manifest branch over checkout config.
|
||||
saved_env = dict(client.env)
|
||||
client.env["SYNC_BRANCH"] = ""
|
||||
try:
|
||||
client.sync()
|
||||
finally:
|
||||
client.env = saved_env
|
||||
assert requests[-1]["branch"] == "main"
|
||||
assert client.head("foo") == v1
|
||||
|
||||
# Switching manifest branches does not leak main's override.
|
||||
client.init(host.manifest_url, "-b", "release/16")
|
||||
client.sync()
|
||||
assert requests[-1]["branch"] == "release/16"
|
||||
assert client.head("foo") == r16
|
||||
|
||||
# Switching back to main restores main's configured override.
|
||||
client.init(host.manifest_url, "-b", "main")
|
||||
client.sync()
|
||||
assert requests[-1]["branch"] == "git_main-dev-warmup-abfs"
|
||||
assert client.head("foo") == warmup
|
||||
|
||||
# Passing an empty --smart-sync-branch clears the override.
|
||||
client.init(host.manifest_url, "--smart-sync-branch=")
|
||||
assert (
|
||||
client.config(".repo/manifests", "repo.main.smartsyncbranch")
|
||||
is None
|
||||
)
|
||||
client.sync()
|
||||
assert requests[-1]["branch"] == "main"
|
||||
assert client.head("foo") == v1
|
||||
|
||||
# Non-empty --smart-sync-branch requires tracking a remote branch.
|
||||
manifest_sha = manifest.rev_parse("main")
|
||||
res = client.init(
|
||||
host.manifest_url,
|
||||
"-b",
|
||||
manifest_sha,
|
||||
"--smart-sync-branch=git_main-dev-warmup-abfs",
|
||||
check=False,
|
||||
)
|
||||
assert res.returncode != 0
|
||||
assert "requires the manifest checkout to track a branch" in res.stderr
|
||||
@@ -2000,6 +2000,64 @@ class ManifestPropertiesFetchedCorrectly(unittest.TestCase):
|
||||
fakeproj.config.SetString("manifest.platform", "auto")
|
||||
self.assertEqual(fakeproj.manifest_platform, "auto")
|
||||
|
||||
def test_smart_sync_branch_config(self) -> None:
|
||||
"""Test branch-scoped smart sync branch getters and setters."""
|
||||
with utils_for_test.TempGitTree() as tempdir:
|
||||
fakeproj = self.setUpManifest(tempdir)
|
||||
|
||||
for branch, target in (
|
||||
("main", "git_main-dev-warmup-abfs"),
|
||||
("Main", "git_Main-warmup"),
|
||||
("release/16", "git_r16-warmup"),
|
||||
("16-release", "git_16-warmup"),
|
||||
):
|
||||
fakeproj.SetSmartSyncBranch(branch, target)
|
||||
|
||||
self.assertEqual(
|
||||
fakeproj.GetSmartSyncBranch("main"),
|
||||
"git_main-dev-warmup-abfs",
|
||||
)
|
||||
self.assertEqual(
|
||||
fakeproj.GetSmartSyncBranch("Main"), "git_Main-warmup"
|
||||
)
|
||||
self.assertEqual(
|
||||
fakeproj.GetSmartSyncBranch("release/16"), "git_r16-warmup"
|
||||
)
|
||||
self.assertEqual(
|
||||
fakeproj.GetSmartSyncBranch("16-release"), "git_16-warmup"
|
||||
)
|
||||
self.assertIsNone(fakeproj.GetSmartSyncBranch("other"))
|
||||
self.assertEqual(
|
||||
fakeproj.config.GetString("repo.release/16.smartsyncbranch"),
|
||||
"git_r16-warmup",
|
||||
)
|
||||
|
||||
# Default lookup resolves the tracked manifest branch.
|
||||
fakeproj.work_git.checkout("-q", "-b", "default")
|
||||
b = fakeproj.GetBranch("default")
|
||||
b.merge = "refs/heads/release/16"
|
||||
b.Save()
|
||||
self.assertEqual(
|
||||
fakeproj.GetMergeBranch(heads_only=True), "release/16"
|
||||
)
|
||||
self.assertEqual(fakeproj.GetSmartSyncBranch(), "git_r16-warmup")
|
||||
|
||||
# Empty string or None clears only that branch's setting.
|
||||
fakeproj.SetSmartSyncBranch("release/16", "")
|
||||
self.assertIsNone(fakeproj.GetSmartSyncBranch("release/16"))
|
||||
self.assertIsNone(fakeproj.GetSmartSyncBranch())
|
||||
self.assertEqual(
|
||||
fakeproj.GetSmartSyncBranch("main"),
|
||||
"git_main-dev-warmup-abfs",
|
||||
)
|
||||
fakeproj.SetSmartSyncBranch("main", None)
|
||||
self.assertIsNone(fakeproj.GetSmartSyncBranch("main"))
|
||||
|
||||
# Non-branch refs (e.g. tags) are ignored by default lookup.
|
||||
b.merge = "refs/tags/v1.0"
|
||||
b.Save()
|
||||
self.assertIsNone(fakeproj.GetSmartSyncBranch())
|
||||
|
||||
def test_check_immutable_revision_metaproject_skips_manifest_load(self):
|
||||
"""MetaProjects must not parse manifest.xml during immutable check.
|
||||
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
"""Unittests for the subcmds/init.py module."""
|
||||
|
||||
from typing import List
|
||||
from typing import List, Optional
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -29,6 +29,8 @@ from subcmds import init
|
||||
["--smart-sync"],
|
||||
["--no-smart-sync"],
|
||||
["--head"],
|
||||
["--smart-sync-branch=git_main-warmup"],
|
||||
["--smart-sync-branch="],
|
||||
),
|
||||
)
|
||||
def test_cli_parser_good(argv: List[str]) -> None:
|
||||
@@ -38,6 +40,24 @@ def test_cli_parser_good(argv: List[str]) -> None:
|
||||
cmd.ValidateOptions(opts, args)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"argv,expected",
|
||||
(
|
||||
([], None),
|
||||
(["--smart-sync-branch=git_main-warmup"], "git_main-warmup"),
|
||||
(["--smart-sync-branch="], ""),
|
||||
),
|
||||
)
|
||||
def test_cli_smart_sync_branch_value(
|
||||
argv: List[str], expected: Optional[str]
|
||||
) -> None:
|
||||
"""Check --smart-sync-branch parsed values."""
|
||||
cmd = init.Init()
|
||||
opts, args = cmd.OptionParser.parse_args(argv)
|
||||
cmd.ValidateOptions(opts, args)
|
||||
assert opts.smart_sync_branch == expected
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"argv",
|
||||
(
|
||||
@@ -45,6 +65,8 @@ def test_cli_parser_good(argv: List[str]) -> None:
|
||||
["url", "asdf"],
|
||||
# Conflicting options.
|
||||
["--mirror", "--archive"],
|
||||
["--standalone-manifest", "--smart-sync-branch=git_main-warmup"],
|
||||
["--standalone-manifest", "--smart-sync-branch="],
|
||||
),
|
||||
)
|
||||
def test_cli_parser_bad(argv: List[str]) -> None:
|
||||
|
||||
@@ -3936,6 +3936,7 @@ class TestSmartSyncSetupRemoteHelper(unittest.TestCase):
|
||||
self.opt.smart_sync = True
|
||||
self.manifest = mock.MagicMock()
|
||||
self.manifest.manifestProject.GetMergeBranch.return_value = "main"
|
||||
self.manifest.manifestProject.GetSmartSyncBranch.return_value = None
|
||||
self.smart_sync_manifest_path = "/fake/path/to/manifest.xml"
|
||||
|
||||
@mock.patch.object(shutil, "which")
|
||||
@@ -4198,6 +4199,7 @@ class TestSmartSyncSetupBranch:
|
||||
)
|
||||
self.manifest.manifest_server_helper = None
|
||||
self.manifest.manifestProject.GetMergeBranch.return_value = "main"
|
||||
self.manifest.manifestProject.GetSmartSyncBranch.return_value = None
|
||||
self.smart_sync_manifest_path = "/fake/path/to/manifest.xml"
|
||||
self.mock_server = mock.MagicMock()
|
||||
self.mock_server.GetApprovedManifestV2.return_value = {
|
||||
@@ -4265,13 +4267,16 @@ class TestSmartSyncSetupBranch:
|
||||
def test_no_branch(self, env: Dict[str, str]) -> None:
|
||||
"""Without any branch, fail before printing or contacting the server."""
|
||||
self.opt.quiet = False
|
||||
self.manifest.manifestProject.GetMergeBranch.return_value = None
|
||||
mp = self.manifest.manifestProject
|
||||
mp.GetMergeBranch.return_value = None
|
||||
mp.GetSmartSyncBranch.return_value = "git_main-warmup"
|
||||
with mock.patch("builtins.print") as mock_print:
|
||||
with pytest.raises(
|
||||
sync.SmartSyncError, match="not tracking a remote branch"
|
||||
):
|
||||
self._RunSetup(env)
|
||||
mock_print.assert_not_called()
|
||||
mp.GetSmartSyncBranch.assert_not_called()
|
||||
self.cmd._ResolveManifestServerTransport.assert_not_called()
|
||||
self.mock_server.GetApprovedManifestV2.assert_not_called()
|
||||
|
||||
@@ -4289,6 +4294,41 @@ class TestSmartSyncSetupBranch:
|
||||
mp.GetMergeBranch.assert_not_called()
|
||||
self.mock_server.GetManifest.assert_called_once_with("build-1234")
|
||||
|
||||
def test_config_smart_sync_branch(self) -> None:
|
||||
"""Configured repo.<branch>.smartsyncbranch overrides manifest
|
||||
branch."""
|
||||
mp = self.manifest.manifestProject
|
||||
mp.GetSmartSyncBranch.return_value = "git_main-warmup"
|
||||
self._RunSetup({})
|
||||
mp.GetMergeBranch.assert_called_once_with(heads_only=True)
|
||||
mp.GetSmartSyncBranch.assert_called_once_with("main")
|
||||
self.mock_server.GetApprovedManifestV2.assert_called_once_with(
|
||||
{"branch": "git_main-warmup"}
|
||||
)
|
||||
|
||||
def test_sync_branch_overrides_config(self) -> None:
|
||||
"""Non-empty SYNC_BRANCH takes precedence over checkout config."""
|
||||
mp = self.manifest.manifestProject
|
||||
mp.GetSmartSyncBranch.return_value = "git_main-warmup"
|
||||
self._RunSetup({"SYNC_BRANCH": "git_main-env"})
|
||||
mp.GetMergeBranch.assert_not_called()
|
||||
mp.GetSmartSyncBranch.assert_not_called()
|
||||
self.mock_server.GetApprovedManifestV2.assert_called_once_with(
|
||||
{"branch": "git_main-env"}
|
||||
)
|
||||
|
||||
def test_empty_sync_branch_bypasses_config(self) -> None:
|
||||
"""SYNC_BRANCH='' forces the raw manifest branch over checkout
|
||||
config."""
|
||||
mp = self.manifest.manifestProject
|
||||
mp.GetSmartSyncBranch.return_value = "git_main-warmup"
|
||||
self._RunSetup({"SYNC_BRANCH": ""})
|
||||
mp.GetMergeBranch.assert_called_once_with(heads_only=True)
|
||||
mp.GetSmartSyncBranch.assert_not_called()
|
||||
self.mock_server.GetApprovedManifestV2.assert_called_once_with(
|
||||
{"branch": "main"}
|
||||
)
|
||||
|
||||
|
||||
class GetApprovedManifestTests(unittest.TestCase):
|
||||
"""Tests for _GetApprovedManifest."""
|
||||
|
||||
Reference in new issue
Block a user