sync: Support manifest-driven default smart sync

Add support for enabling smart sync by default via the
sync-smartsync="true" attribute on the manifest <default> element.

When sync-smartsync is set to true in the manifest defaults:
- Plain `repo sync` triggers Smart Sync automatically.
- If that implicit Smart Sync fails (manifest server unreachable, or
  the returned manifest cannot be parsed), repo silently falls back to
  a regular ToT sync. The reason is logged with --verbose.
- `--no-smart-sync` CLI flag overrides the manifest default and
  syncs to ToT.
- Passing explicit `-s` / `--smart-sync` forces Smart Sync as before,
  and its failures are still fatal.
- `-t`, `--smart-tag`, `-m`, `--manifest-name`, and
  `--superproject-revision` disable the default smart sync.
- `-l` / `--local-only` and `--no-manifest-update` skip the manifest
  server and reuse the existing smart_sync_override.xml, if any.

Update docs, completion, regenerate man pages, and add tests for XML
parsing, serialization, CLI option resolution, and the fallback and
reuse paths.

Bug: 545137090
Change-Id: If75569c58dd525d170e9801e70ccbf02206a52f2
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/617641
Tested-by: Rahul Yadav <yadavrah@google.com>
Commit-Queue: Rahul Yadav <yadavrah@google.com>
Reviewed-by: Gavin Mak <gavinmak@google.com>
This commit is contained in:
Rahul Yadav authored and gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com committed 2026-10-01 03:13:33 -07:00
1 parent 5d4149f32e
commit d53a4278f5
9 files changed
+527 -33

No files matched your search

+87 -8
View File
@@ -58,6 +58,7 @@ from command import DEFAULT_LOCAL_JOBS
from command import MirrorSafeCommand
from command import WORKER_BATCH_SIZE
from error import GitError
from error import ManifestParseError
from error import RepoChangedException
from error import RepoError
from error import RepoExitError
@@ -764,9 +765,16 @@ later is required to fix a server side protocol bug.
"-s",
"--smart-sync",
action="store_true",
default=None,
help="smart sync using manifest from the latest known good "
"build",
)
p.add_option(
"--no-smart-sync",
dest="smart_sync",
action="store_false",
help="disable smart sync and sync to ToT instead",
)
p.add_option(
"-t",
"--smart-tag",
@@ -2249,10 +2257,6 @@ later is required to fix a server side protocol bug.
if opt.manifest_name and opt.smart_tag:
self.OptionParser.error("cannot combine -m and -t")
if opt.manifest_server_username or opt.manifest_server_password:
if not (opt.smart_sync or opt.smart_tag):
self.OptionParser.error(
"-u and -p may only be combined with -s or -t"
)
if None in [
opt.manifest_server_username,
opt.manifest_server_password,
@@ -2413,6 +2417,34 @@ later is required to fix a server side protocol bug.
"failed to sync manifest project", aggregate_errors=[e]
)
def _ResolveSmartSyncOption(
self, opt: optparse.Values, manifest: XmlManifest
) -> bool:
"""Resolve opt.smart_sync from the CLI flags and manifest default.
Returns:
True if smart_sync was enabled implicitly by the manifest default,
False otherwise.
"""
implicit = (
opt.smart_sync is None
and not opt.smart_tag
and not opt.manifest_name
and not opt.superproject_revision
and not opt.local_only
and opt.mp_update
and getattr(manifest.default, "sync_smartsync", False) is True
)
if opt.smart_sync is None:
opt.smart_sync = implicit
if (
opt.manifest_server_username or opt.manifest_server_password
) and not (opt.smart_sync or opt.smart_tag):
raise SmartSyncError("-u and -p may only be combined with -s or -t")
return implicit
def _ExecuteHelper(self, opt, args, errors):
manifest = self.outer_manifest
if not opt.outer_manifest:
@@ -2427,11 +2459,58 @@ later is required to fix a server side protocol bug.
if opt.clone_bundle is None:
opt.clone_bundle = manifest.CloneBundle
# An onboarded client run with -l or --no-manifest-update keeps the
# smart sync manifest it last synced to instead of contacting the
# manifest server. Computed before _ResolveSmartSyncOption, which
# makes an unset opt.smart_sync indistinguishable from --no-smart-sync.
reuse_smart_sync_override = (
(opt.local_only or not opt.mp_update)
and opt.smart_sync is None
and not opt.smart_tag
and not opt.manifest_name
and not opt.superproject_revision
and getattr(manifest.default, "sync_smartsync", False) is True
and os.path.isfile(smart_sync_manifest_path)
)
implicit_smart_sync = self._ResolveSmartSyncOption(opt, manifest)
if opt.smart_sync or opt.smart_tag:
manifest_name = self._SmartSyncSetup(
opt, smart_sync_manifest_path, manifest
)
else:
try:
manifest_name = self._SmartSyncSetup(
opt, smart_sync_manifest_path, manifest
)
except (SmartSyncError, ManifestParseError) as e:
if not implicit_smart_sync:
raise
# The user did not ask for a smart sync, so failing to get a
# usable manifest from the server should not fail the sync.
# Fall back to a regular ToT sync instead.
opt.smart_sync = False
# Override() records the override before parsing it, so an
# unparsable manifest would otherwise leave the client pinned
# to a file it cannot load.
manifest.ClearOverride()
if opt.verbose:
logger.warning(
"warning: smart sync failed; falling back to ToT: %s", e
)
elif reuse_smart_sync_override:
manifest_name = os.path.basename(smart_sync_manifest_path)
try:
self._ReloadManifest(manifest_name, manifest)
except ManifestParseError as e:
reuse_smart_sync_override = False
manifest_name = None
manifest.ClearOverride()
if opt.verbose:
logger.warning(
"warning: failed to load existing smart sync manifest; "
"falling back to ToT: %s",
e,
)
if not (opt.smart_sync or opt.smart_tag or reuse_smart_sync_override):
if os.path.isfile(smart_sync_manifest_path):
try:
platform_utils.remove(smart_sync_manifest_path)