From 340b587e94c5397de197cc59635cafa1c3c488fb Mon Sep 17 00:00:00 2001 From: Rahul Yadav Date: Tue, 6 Oct 2026 14:07:21 +0000 Subject: [PATCH] sync: Support SYNC_BRANCH to override the smart sync branch `repo sync -s` always sends the manifest branch to the manifest server as the `branch` argument of GetApprovedManifest. Some servers map one manifest branch to several server-side (CI) branches, and the only way to pick among them was indirectly through the target. Honor a SYNC_BRANCH environment variable, mirroring SYNC_TARGET: if set and non-empty, send it instead of the manifest branch. An empty value is treated as unset. -t/--smart-tag is unaffected. Bug: 568962967 Test: ./run_tests Change-Id: I26700e8997c21487f83011258100fd91a5c87508 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/639123 Commit-Queue: Rahul Yadav Reviewed-by: Gavin Mak Tested-by: Rahul Yadav --- docs/smart-sync.md | 5 ++- subcmds/sync.py | 14 ++++++-- tests/test_subcmds_sync.py | 71 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 87 insertions(+), 3 deletions(-) diff --git a/docs/smart-sync.md b/docs/smart-sync.md index 28603799d..77f7a9cf9 100644 --- a/docs/smart-sync.md +++ b/docs/smart-sync.md @@ -174,7 +174,10 @@ Explicitly enables Smart Sync. Repo will call `GetApprovedManifest(branch[, target])`. The `branch` is determined by the current manifest branch as specified by -`--manifest-branch=BRANCH` when running `repo init`. +`--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. 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 diff --git a/subcmds/sync.py b/subcmds/sync.py index 46d9f248c..fe1b14318 100644 --- a/subcmds/sync.py +++ b/subcmds/sync.py @@ -2059,7 +2059,12 @@ later is required to fix a server side protocol bug. return server_url, transport - def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest): + def _SmartSyncSetup( + self, + opt: optparse.Values, + smart_sync_manifest_path: str, + manifest: XmlManifest, + ) -> str: if not manifest.manifest_server: raise SmartSyncError( "error: cannot smart sync: no manifest server defined in " @@ -2077,7 +2082,12 @@ later is required to fix a server side protocol bug. try: server = xmlrpc.client.Server(server_url, transport=transport) if opt.smart_sync: - branch = self._GetBranch(manifest.manifestProject) + # 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") + if not branch: + branch = self._GetBranch(manifest.manifestProject) target = None if "SYNC_TARGET" in os.environ: target = os.environ["SYNC_TARGET"] diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py index 9e9b9a556..580389a26 100644 --- a/tests/test_subcmds_sync.py +++ b/tests/test_subcmds_sync.py @@ -2982,3 +2982,74 @@ class TestSmartSyncSetupRemoteHelper(unittest.TestCase): "Stderr was: debugging logs", str(context.exception), ) + + +class TestSmartSyncSetupBranch(unittest.TestCase): + """Tests for how _SmartSyncSetup picks the branch sent to the server.""" + + def setUp(self) -> None: + self.cmd = sync.Sync() + self.opt = mock.MagicMock() + self.opt.quiet = True + self.opt.smart_sync = True + self.opt.smart_tag = None + self.manifest = mock.MagicMock() + self.manifest.manifest_server = ( + "http://android-smartsync.corp.google.com/manifestserver" + ) + self.manifest.manifest_server_helper = None + self.smart_sync_manifest_path = "/fake/path/to/manifest.xml" + self.mock_server = mock.MagicMock() + self.mock_server.GetApprovedManifest.return_value = [ + True, + "", + ] + self.cmd._GetBranch = mock.MagicMock(return_value="main") + self.cmd._ReloadManifest = mock.MagicMock() + self.cmd._ResolveManifestServerTransport = mock.MagicMock( + return_value=(self.manifest.manifest_server, mock.MagicMock()) + ) + + def _RunSetup(self, env: Dict[str, str]) -> str: + with mock.patch.dict(os.environ, env, clear=True), mock.patch( + "xmlrpc.client.Server", return_value=self.mock_server + ), mock.patch("builtins.open", mock.mock_open()): + return self.cmd._SmartSyncSetup( + self.opt, self.smart_sync_manifest_path, self.manifest + ) + + def test_default_branch(self) -> None: + """Without SYNC_BRANCH the manifest branch is used.""" + self._RunSetup({}) + self.cmd._GetBranch.assert_called_once_with( + self.manifest.manifestProject + ) + self.mock_server.GetApprovedManifest.assert_called_once_with("main") + + def test_sync_branch(self) -> None: + """SYNC_BRANCH replaces the manifest branch.""" + self._RunSetup({"SYNC_BRANCH": "git_main-abfs"}) + self.cmd._GetBranch.assert_not_called() + self.mock_server.GetApprovedManifest.assert_called_once_with( + "git_main-abfs" + ) + + def test_sync_branch_with_target(self) -> None: + """SYNC_BRANCH and SYNC_TARGET are both forwarded.""" + self._RunSetup( + { + "SYNC_BRANCH": "git_main-abfs", + "SYNC_TARGET": "cf_x86_64_phone-trunk_staging-eng", + } + ) + self.mock_server.GetApprovedManifest.assert_called_once_with( + "git_main-abfs", "cf_x86_64_phone-trunk_staging-eng" + ) + + def test_empty_sync_branch(self) -> None: + """An empty SYNC_BRANCH falls back to the manifest branch.""" + self._RunSetup({"SYNC_BRANCH": ""}) + self.cmd._GetBranch.assert_called_once_with( + self.manifest.manifestProject + ) + self.mock_server.GetApprovedManifest.assert_called_once_with("main")