mirror of
https://gerrit.googlesource.com/git-repo
synced 2026-10-20 20:28:25 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
46e9866ca0 | ||
|
|
404aec7b53 |
No files matched your search
@@ -253,6 +253,7 @@ setting `REPO_CONFIG_DIR`.
|
|||||||
|
|
||||||
* `.repoconfig/`: Repo's per-user directory for all random config files/state.
|
* `.repoconfig/`: Repo's per-user directory for all random config files/state.
|
||||||
* `.repoconfig/config`: Per-user settings using [git-config] file format.
|
* `.repoconfig/config`: Per-user settings using [git-config] file format.
|
||||||
|
See [repoconfig.md](./repoconfig.md) for the available settings.
|
||||||
* `.repoconfig/keyring-version`: Cache file for checking if the gnupg subdir
|
* `.repoconfig/keyring-version`: Cache file for checking if the gnupg subdir
|
||||||
has all the same keys as the repo launcher. Used to avoid running gpg
|
has all the same keys as the repo launcher. Used to avoid running gpg
|
||||||
constantly as that can be quite slow.
|
constantly as that can be quite slow.
|
||||||
|
|||||||
@@ -0,0 +1,103 @@
|
|||||||
|
# repoconfig: User Configuration
|
||||||
|
|
||||||
|
Repo keeps a small number of settings that apply to the current user rather
|
||||||
|
than to one checkout. They live in the per-user configuration file
|
||||||
|
`~/.repoconfig/config`, which is separate from both git's own `~/.gitconfig`
|
||||||
|
and the per-checkout settings stored in `.repo/manifests.git/config` (see
|
||||||
|
[internal-fs-layout.md](./internal-fs-layout.md#Global-settings) for those).
|
||||||
|
|
||||||
|
[TOC]
|
||||||
|
|
||||||
|
## Location
|
||||||
|
|
||||||
|
The file is `.repoconfig/config` inside the user's home directory. The parent
|
||||||
|
directory can be changed by setting the `REPO_CONFIG_DIR` environment variable,
|
||||||
|
in which case repo reads `$REPO_CONFIG_DIR/.repoconfig/config` instead.
|
||||||
|
|
||||||
|
The file is created on demand; it's fine for it to be missing.
|
||||||
|
|
||||||
|
## Format
|
||||||
|
|
||||||
|
The file uses the [git-config] syntax, so it can be edited by hand:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[sync]
|
||||||
|
smartsync = true
|
||||||
|
```
|
||||||
|
|
||||||
|
or managed with `git config` by pointing it at the file explicitly:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Set a value.
|
||||||
|
git config --file ~/.repoconfig/config sync.smartsync true
|
||||||
|
|
||||||
|
# Read a value.
|
||||||
|
git config --file ~/.repoconfig/config sync.smartsync
|
||||||
|
|
||||||
|
# Remove a value.
|
||||||
|
git config --file ~/.repoconfig/config --unset sync.smartsync
|
||||||
|
```
|
||||||
|
|
||||||
|
Boolean settings accept `true`/`yes` and `false`/`no`.
|
||||||
|
|
||||||
|
Repo caches a parsed copy of the file in `.repoconfig/.repo_config.json`; it is
|
||||||
|
refreshed automatically whenever `config` changes and can be safely deleted.
|
||||||
|
|
||||||
|
## Settings
|
||||||
|
|
||||||
|
### alias.*
|
||||||
|
|
||||||
|
`alias.<name>` defines `repo <name>` as a shortcut for another subcommand,
|
||||||
|
optionally with arguments, in the same way as git aliases:
|
||||||
|
|
||||||
|
```ini
|
||||||
|
[alias]
|
||||||
|
ss = sync -s
|
||||||
|
st = status -o
|
||||||
|
```
|
||||||
|
|
||||||
|
Aliases defined in a checkout's `.repo/config` take precedence over per-user
|
||||||
|
ones. Existing subcommands cannot be overridden by an alias.
|
||||||
|
|
||||||
|
### sync
|
||||||
|
|
||||||
|
#### smartsync
|
||||||
|
|
||||||
|
`sync.smartsync` is a boolean that lets a user decide once, for all their
|
||||||
|
checkouts, whether `repo sync` should [Smart Sync](./smart-sync.md) by
|
||||||
|
default:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Always Smart Sync, as if -s/--smart-sync were passed.
|
||||||
|
git config --file ~/.repoconfig/config sync.smartsync true
|
||||||
|
|
||||||
|
# Never Smart Sync by default, even if the manifest sets sync-smartsync="true".
|
||||||
|
git config --file ~/.repoconfig/config sync.smartsync false
|
||||||
|
|
||||||
|
# Remove the preference and go back to following the manifest.
|
||||||
|
git config --file ~/.repoconfig/config --unset sync.smartsync
|
||||||
|
```
|
||||||
|
|
||||||
|
The effective behavior is resolved in this order; the first match wins:
|
||||||
|
|
||||||
|
1. Command line: `-s`/`--smart-sync`, `--no-smart-sync`, or `-t`/`--smart-tag`.
|
||||||
|
2. User config: `sync.smartsync` in `~/.repoconfig/config`.
|
||||||
|
3. Manifest: `sync-smartsync="true"` on the `<default>` element.
|
||||||
|
4. Otherwise, a regular ToT sync.
|
||||||
|
|
||||||
|
So `sync.smartsync=false` turns off the manifest-driven default, but an
|
||||||
|
explicit `repo sync -s`, `repo sync -t TAG` or `repo smartsync` still works.
|
||||||
|
Likewise `sync.smartsync=true` is overridden by `--no-smart-sync`.
|
||||||
|
|
||||||
|
A Smart Sync enabled by `sync.smartsync=true` is treated as an explicit
|
||||||
|
request: it is subject to the same restrictions as `-s` (it is skipped when
|
||||||
|
`-l`/`--local-only`, `-m`/`--manifest-name`, `-t`/`--smart-tag` or
|
||||||
|
`--superproject-revision` is used), and a failure to obtain a manifest from
|
||||||
|
the server fails the sync instead of silently falling back to ToT.
|
||||||
|
|
||||||
|
`sync.smartsync` does not affect `-l`/`--local-only`: a local-only sync keeps
|
||||||
|
whatever `smart_sync_override.xml` the previous network sync left behind. See
|
||||||
|
[smart-sync.md](./smart-sync.md#Client-Options) for details.
|
||||||
|
|
||||||
|
|
||||||
|
[git-config]: https://git-scm.com/docs/git-config
|
||||||
@@ -204,6 +204,10 @@ still fails the sync, as before. Options that specify an explicit target or
|
|||||||
manifest source (such as `-t`/`--smart-tag`, `-m`/`--manifest-name` or
|
manifest source (such as `-t`/`--smart-tag`, `-m`/`--manifest-name` or
|
||||||
`--superproject-revision`) will also disable the default Smart Sync behavior.
|
`--superproject-revision`) will also disable the default Smart Sync behavior.
|
||||||
|
|
||||||
|
Users can override this manifest default on their workstation with the
|
||||||
|
`sync.smartsync` user setting; see
|
||||||
|
[Per-user default](#Per_user-default-sync_smartsync) below.
|
||||||
|
|
||||||
### --smart-sync / -s
|
### --smart-sync / -s
|
||||||
|
|
||||||
Explicitly enables Smart Sync. Repo will call
|
Explicitly enables Smart Sync. Repo will call
|
||||||
@@ -231,6 +235,18 @@ match the settings Android build environments automatically setup.
|
|||||||
Explicitly disables Smart Sync, overriding any `sync-smartsync="true"` setting
|
Explicitly disables Smart Sync, overriding any `sync-smartsync="true"` setting
|
||||||
declared in the manifest `<default>` element.
|
declared in the manifest `<default>` element.
|
||||||
|
|
||||||
|
### Per-user default (sync.smartsync)
|
||||||
|
|
||||||
|
A user can decide once, for all their checkouts, whether `repo sync` should
|
||||||
|
Smart Sync by default by setting the boolean `sync.smartsync` key in their
|
||||||
|
[per-user repo config](./repoconfig.md#smartsync).
|
||||||
|
|
||||||
|
Command line flags (`-s`, `--no-smart-sync`, `-t`) override the user setting,
|
||||||
|
which in turn overrides the manifest's `sync-smartsync` default. A Smart Sync
|
||||||
|
enabled this way is treated as an explicit request, so it is subject to the
|
||||||
|
same restrictions as `-s` and fails the sync if no manifest can be obtained
|
||||||
|
from the server instead of silently falling back to ToT.
|
||||||
|
|
||||||
### --smart-tag=TAG / -t
|
### --smart-tag=TAG / -t
|
||||||
|
|
||||||
Repo will call `GetManifest(TAG)`.
|
Repo will call `GetManifest(TAG)`.
|
||||||
+5
-1
@@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "September 2026" "repo sync" "Repo Manual"
|
.TH REPO "1" "October 2026" "repo sync" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo sync - manual page for repo sync
|
repo \- repo sync - manual page for repo sync
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@@ -188,6 +188,10 @@ The \fB\-s\fR/\-\-smart\-sync option can be used to sync to a known good build a
|
|||||||
specified by the manifest\-server element in the current manifest. The
|
specified by the manifest\-server element in the current manifest. The
|
||||||
\fB\-t\fR/\-\-smart\-tag option is similar and allows you to specify a custom tag/label.
|
\fB\-t\fR/\-\-smart\-tag option is similar and allows you to specify a custom tag/label.
|
||||||
.PP
|
.PP
|
||||||
|
Whether 'repo sync' smart syncs by default can also be controlled with the
|
||||||
|
sync.smartsync per\-user setting; see docs/repoconfig.md. Explicit
|
||||||
|
\fB\-s\fR/\-\-no\-smart\-sync/\-t flags always override it.
|
||||||
|
.PP
|
||||||
The \fB\-u\fR/\-\-manifest\-server\-username and \fB\-p\fR/\-\-manifest\-server\-password options can
|
The \fB\-u\fR/\-\-manifest\-server\-username and \fB\-p\fR/\-\-manifest\-server\-password options can
|
||||||
be used to specify a username and password to authenticate with the manifest
|
be used to specify a username and password to authenticate with the manifest
|
||||||
server when using the \fB\-s\fR or \fB\-t\fR option.
|
server when using the \fB\-s\fR or \fB\-t\fR option.
|
||||||
|
|||||||
+23
-4
@@ -69,6 +69,7 @@ import event_log
|
|||||||
from git_command import git_require
|
from git_command import git_require
|
||||||
from git_command import GitCommand
|
from git_command import GitCommand
|
||||||
from git_config import GetUrlCookieFile
|
from git_config import GetUrlCookieFile
|
||||||
|
from git_config import RepoConfig
|
||||||
from git_refs import HEAD
|
from git_refs import HEAD
|
||||||
from git_refs import R_HEADS
|
from git_refs import R_HEADS
|
||||||
import git_superproject
|
import git_superproject
|
||||||
@@ -491,6 +492,10 @@ build as specified by the manifest-server element in the current
|
|||||||
manifest. The -t/--smart-tag option is similar and allows you to
|
manifest. The -t/--smart-tag option is similar and allows you to
|
||||||
specify a custom tag/label.
|
specify a custom tag/label.
|
||||||
|
|
||||||
|
Whether '%prog' smart syncs by default can also be controlled with the
|
||||||
|
sync.smartsync per-user setting; see docs/repoconfig.md. Explicit
|
||||||
|
-s/--no-smart-sync/-t flags always override it.
|
||||||
|
|
||||||
The -u/--manifest-server-username and -p/--manifest-server-password
|
The -u/--manifest-server-username and -p/--manifest-server-password
|
||||||
options can be used to specify a username and password to authenticate
|
options can be used to specify a username and password to authenticate
|
||||||
with the manifest server when using the -s or -t option.
|
with the manifest server when using the -s or -t option.
|
||||||
@@ -2522,22 +2527,36 @@ later is required to fix a server side protocol bug.
|
|||||||
def _ResolveSmartSyncOption(
|
def _ResolveSmartSyncOption(
|
||||||
self, opt: optparse.Values, manifest: XmlManifest
|
self, opt: optparse.Values, manifest: XmlManifest
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Resolve opt.smart_sync from the CLI flags and manifest default.
|
"""Resolve opt.smart_sync from CLI flags, user config and manifest.
|
||||||
|
|
||||||
|
Precedence: CLI flags > per-user settings > per-manifest settings.
|
||||||
|
See docs/repoconfig.md and docs/smart-sync.md for details.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
True if smart_sync was enabled implicitly by the manifest default,
|
True if smart_sync was enabled implicitly by the manifest default,
|
||||||
False otherwise.
|
False otherwise. Smart sync enabled via per-user settings counts
|
||||||
|
as an explicit user request, not implicit.
|
||||||
"""
|
"""
|
||||||
implicit = (
|
user_pref = RepoConfig.ForUser().GetBoolean("sync.smartsync")
|
||||||
|
default_allowed = (
|
||||||
opt.smart_sync is None
|
opt.smart_sync is None
|
||||||
and not opt.smart_tag
|
and not opt.smart_tag
|
||||||
and not opt.manifest_name
|
and not opt.manifest_name
|
||||||
and not opt.superproject_revision
|
and not opt.superproject_revision
|
||||||
and not opt.local_only
|
and not opt.local_only
|
||||||
|
)
|
||||||
|
implicit = (
|
||||||
|
default_allowed
|
||||||
|
and user_pref is None
|
||||||
and getattr(manifest.default, "sync_smartsync", False) is True
|
and getattr(manifest.default, "sync_smartsync", False) is True
|
||||||
)
|
)
|
||||||
if opt.smart_sync is None:
|
if opt.smart_sync is None:
|
||||||
opt.smart_sync = implicit
|
if user_pref is False:
|
||||||
|
opt.smart_sync = False
|
||||||
|
elif user_pref is True:
|
||||||
|
opt.smart_sync = default_allowed
|
||||||
|
else:
|
||||||
|
opt.smart_sync = implicit
|
||||||
|
|
||||||
if (
|
if (
|
||||||
opt.manifest_server_username or opt.manifest_server_password
|
opt.manifest_server_username or opt.manifest_server_password
|
||||||
|
|||||||
@@ -742,6 +742,22 @@ Gerrit Code Review: https://www.gerritcodereview.com/
|
|||||||
avail = project.GetUploadableBranches(opt.branch)
|
avail = project.GetUploadableBranches(opt.branch)
|
||||||
return (project_idx, avail, cbr)
|
return (project_idx, avail, cbr)
|
||||||
|
|
||||||
|
def ValidateOptions(self, opt: optparse.Values, args: List[str]) -> None:
|
||||||
|
if opt.fix:
|
||||||
|
if opt.ignore_hooks:
|
||||||
|
self.OptionParser.error(
|
||||||
|
"--fix and --ignore-hooks are mutually exclusive options: "
|
||||||
|
"with --ignore-hooks, the original commits are uploaded, "
|
||||||
|
"so fixups would only modify the working tree and would "
|
||||||
|
"not be part of the upload"
|
||||||
|
)
|
||||||
|
if opt.bypass_hooks:
|
||||||
|
self.OptionParser.error(
|
||||||
|
"--fix and --no-verify are mutually exclusive options: "
|
||||||
|
"--no-verify skips the pre-upload hook, so --fix would "
|
||||||
|
"have no effect"
|
||||||
|
)
|
||||||
|
|
||||||
def Execute(self, opt, args):
|
def Execute(self, opt, args):
|
||||||
projects = self.GetProjects(
|
projects = self.GetProjects(
|
||||||
args, all_manifests=not opt.this_manifest_only
|
args, all_manifests=not opt.this_manifest_only
|
||||||
|
|||||||
+151
-16
@@ -501,27 +501,116 @@ def test_cli_smart_sync(
|
|||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize(
|
@pytest.mark.parametrize(
|
||||||
"argv, sync_smartsync_manifest, expected_exception",
|
"argv, user_pref, sync_smartsync_manifest, expected_smart_sync, "
|
||||||
|
"expected_implicit",
|
||||||
[
|
[
|
||||||
(["-u", "user", "-p", "pass"], False, sync.SmartSyncError),
|
# sync.smartsync=false: never smart sync unless -s/-t is given.
|
||||||
(["-u", "user", "-p", "pass"], True, None),
|
([], False, True, False, False),
|
||||||
(["-s", "-u", "user", "-p", "pass"], False, None),
|
([], False, False, False, False),
|
||||||
(["-t", "tag", "-u", "user", "-p", "pass"], False, None),
|
(["-s"], False, True, True, False),
|
||||||
|
(["--smart-sync"], False, False, True, False),
|
||||||
|
(["-t", "tag123"], False, True, False, False),
|
||||||
|
(["--nmu"], False, True, False, False),
|
||||||
|
# sync.smartsync=true: like -s, counted as explicit, not implicit.
|
||||||
|
([], True, False, True, False),
|
||||||
|
([], True, True, True, False),
|
||||||
|
(["--nmu"], True, False, True, False),
|
||||||
|
(["--no-smart-sync"], True, True, False, False),
|
||||||
|
(["-t", "tag123"], True, False, False, False),
|
||||||
|
(["-m", "other.xml"], True, False, False, False),
|
||||||
|
(["-l"], True, False, False, False),
|
||||||
|
(["--superproject-revision=abc"], True, False, False, False),
|
||||||
|
# sync.smartsync unset: manifest default applies as before.
|
||||||
|
([], None, True, True, True),
|
||||||
|
([], None, False, False, False),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_user_config_smart_sync(
|
||||||
|
argv: List[str],
|
||||||
|
user_pref: Optional[bool],
|
||||||
|
sync_smartsync_manifest: bool,
|
||||||
|
expected_smart_sync: bool,
|
||||||
|
expected_implicit: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Tests the sync.smartsync user setting against CLI and manifest."""
|
||||||
|
manifest = mock.MagicMock()
|
||||||
|
manifest.default.sync_smartsync = sync_smartsync_manifest
|
||||||
|
|
||||||
|
user_cfg = mock.MagicMock()
|
||||||
|
user_cfg.GetBoolean.return_value = user_pref
|
||||||
|
|
||||||
|
cmd = sync.Sync(manifest=manifest)
|
||||||
|
opts, args = cmd.OptionParser.parse_args(argv)
|
||||||
|
cmd.ValidateOptions(opts, args)
|
||||||
|
with mock.patch.object(sync.RepoConfig, "ForUser", return_value=user_cfg):
|
||||||
|
implicit = cmd._ResolveSmartSyncOption(opts, manifest)
|
||||||
|
user_cfg.GetBoolean.assert_called_once_with("sync.smartsync")
|
||||||
|
assert opts.smart_sync == expected_smart_sync
|
||||||
|
assert implicit == expected_implicit
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"value, expected_smart_sync",
|
||||||
|
[("true", True), ("false", False), ("yes", True), ("no", False)],
|
||||||
|
)
|
||||||
|
def test_user_config_smart_sync_from_file(
|
||||||
|
monkeypatch: pytest.MonkeyPatch,
|
||||||
|
tmp_home_dir: Path,
|
||||||
|
value: str,
|
||||||
|
expected_smart_sync: bool,
|
||||||
|
) -> None:
|
||||||
|
"""Tests that sync.smartsync is read from ~/.repoconfig/config."""
|
||||||
|
# ForUser() caches its instance process-wide; make it pick up this HOME.
|
||||||
|
monkeypatch.setattr(sync.RepoConfig, "_ForUser", None)
|
||||||
|
monkeypatch.delenv("REPO_CONFIG_DIR", raising=False)
|
||||||
|
cfg_dir = tmp_home_dir / ".repoconfig"
|
||||||
|
cfg_dir.mkdir()
|
||||||
|
(cfg_dir / "config").write_text(f"[sync]\n\tsmartsync = {value}\n")
|
||||||
|
|
||||||
|
manifest = mock.MagicMock()
|
||||||
|
# Set the manifest default to the opposite so we know the user config won.
|
||||||
|
manifest.default.sync_smartsync = not expected_smart_sync
|
||||||
|
|
||||||
|
cmd = sync.Sync(manifest=manifest)
|
||||||
|
opts, args = cmd.OptionParser.parse_args([])
|
||||||
|
cmd.ValidateOptions(opts, args)
|
||||||
|
assert cmd._ResolveSmartSyncOption(opts, manifest) is False
|
||||||
|
assert opts.smart_sync == expected_smart_sync
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"argv, user_pref, sync_smartsync_manifest, expected_exception",
|
||||||
|
[
|
||||||
|
(["-u", "user", "-p", "pass"], None, False, sync.SmartSyncError),
|
||||||
|
(["-u", "user", "-p", "pass"], None, True, None),
|
||||||
|
(["-s", "-u", "user", "-p", "pass"], None, False, None),
|
||||||
|
(["-t", "tag", "-u", "user", "-p", "pass"], None, False, None),
|
||||||
(
|
(
|
||||||
["--no-smart-sync", "-u", "user", "-p", "pass"],
|
["--no-smart-sync", "-u", "user", "-p", "pass"],
|
||||||
|
None,
|
||||||
True,
|
True,
|
||||||
sync.SmartSyncError,
|
sync.SmartSyncError,
|
||||||
),
|
),
|
||||||
(["-l", "-u", "user", "-p", "pass"], True, sync.SmartSyncError),
|
(["-l", "-u", "user", "-p", "pass"], None, True, sync.SmartSyncError),
|
||||||
(["--nmu", "-u", "user", "-p", "pass"], True, None),
|
(["--nmu", "-u", "user", "-p", "pass"], None, True, None),
|
||||||
(["--nmu", "-u", "user", "-p", "pass"], False, sync.SmartSyncError),
|
(
|
||||||
(["-s", "-l", "-u", "user", "-p", "pass"], True, None),
|
["--nmu", "-u", "user", "-p", "pass"],
|
||||||
(["-u", "user"], False, SystemExit),
|
None,
|
||||||
(["-p", "pass"], False, SystemExit),
|
False,
|
||||||
|
sync.SmartSyncError,
|
||||||
|
),
|
||||||
|
(["-s", "-l", "-u", "user", "-p", "pass"], None, True, None),
|
||||||
|
(["-u", "user"], None, False, SystemExit),
|
||||||
|
(["-p", "pass"], None, False, SystemExit),
|
||||||
|
# -u/-p are accepted when smart sync comes from sync.smartsync=true...
|
||||||
|
(["-u", "user", "-p", "pass"], True, False, None),
|
||||||
|
# ...and rejected when sync.smartsync=false drops the manifest default.
|
||||||
|
(["-u", "user", "-p", "pass"], False, True, sync.SmartSyncError),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
def test_cli_manifest_server_credentials(
|
def test_cli_manifest_server_credentials(
|
||||||
argv: List[str],
|
argv: List[str],
|
||||||
|
user_pref: Optional[bool],
|
||||||
sync_smartsync_manifest: bool,
|
sync_smartsync_manifest: bool,
|
||||||
expected_exception: Optional[Type[BaseException]],
|
expected_exception: Optional[Type[BaseException]],
|
||||||
) -> None:
|
) -> None:
|
||||||
@@ -529,15 +618,19 @@ def test_cli_manifest_server_credentials(
|
|||||||
manifest = mock.MagicMock()
|
manifest = mock.MagicMock()
|
||||||
manifest.default.sync_smartsync = sync_smartsync_manifest
|
manifest.default.sync_smartsync = sync_smartsync_manifest
|
||||||
|
|
||||||
|
user_cfg = mock.MagicMock()
|
||||||
|
user_cfg.GetBoolean.return_value = user_pref
|
||||||
|
|
||||||
cmd = sync.Sync(manifest=manifest)
|
cmd = sync.Sync(manifest=manifest)
|
||||||
opts, args = cmd.OptionParser.parse_args(argv)
|
opts, args = cmd.OptionParser.parse_args(argv)
|
||||||
if expected_exception:
|
with mock.patch.object(sync.RepoConfig, "ForUser", return_value=user_cfg):
|
||||||
with pytest.raises(expected_exception):
|
if expected_exception:
|
||||||
|
with pytest.raises(expected_exception):
|
||||||
|
cmd.ValidateOptions(opts, args)
|
||||||
|
cmd._ResolveSmartSyncOption(opts, manifest)
|
||||||
|
else:
|
||||||
cmd.ValidateOptions(opts, args)
|
cmd.ValidateOptions(opts, args)
|
||||||
cmd._ResolveSmartSyncOption(opts, manifest)
|
cmd._ResolveSmartSyncOption(opts, manifest)
|
||||||
else:
|
|
||||||
cmd.ValidateOptions(opts, args)
|
|
||||||
cmd._ResolveSmartSyncOption(opts, manifest)
|
|
||||||
|
|
||||||
|
|
||||||
class LocalSyncState(unittest.TestCase):
|
class LocalSyncState(unittest.TestCase):
|
||||||
@@ -1699,6 +1792,48 @@ class SyncCommand(unittest.TestCase):
|
|||||||
mock_setup.assert_called_once()
|
mock_setup.assert_called_once()
|
||||||
self.assertFalse(mock_setup.call_args.kwargs.get("best_effort"))
|
self.assertFalse(mock_setup.call_args.kwargs.get("best_effort"))
|
||||||
|
|
||||||
|
def test_user_config_smart_sync_disabled(self) -> None:
|
||||||
|
"""Ensure sync.smartsync=false skips the manifest's default."""
|
||||||
|
self.manifest.default.sync_smartsync = True
|
||||||
|
self.opt.smart_sync = None
|
||||||
|
mock.patch.object(self.cmd, "_UpdateAllManifestProjects").start()
|
||||||
|
user_cfg = mock.MagicMock()
|
||||||
|
user_cfg.GetBoolean.return_value = False
|
||||||
|
with mock.patch.object(
|
||||||
|
sync.RepoConfig, "ForUser", return_value=user_cfg
|
||||||
|
):
|
||||||
|
with mock.patch.object(self.cmd, "_SmartSyncSetup") as mock_setup:
|
||||||
|
with mock.patch.object(self.cmd, "_UpdateRepoProject"):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.cmd, "_ValidateOptionsWithManifest"
|
||||||
|
):
|
||||||
|
with mock.patch.object(self.cmd, "_SyncInterleaved"):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.cmd, "_RunPostSyncHook"
|
||||||
|
):
|
||||||
|
self.cmd.Execute(self.opt, [])
|
||||||
|
mock_setup.assert_not_called()
|
||||||
|
self.assertFalse(self.opt.smart_sync)
|
||||||
|
|
||||||
|
def test_user_config_smart_sync_enabled_error(self) -> None:
|
||||||
|
"""Ensure sync.smartsync=true fails hard instead of falling back."""
|
||||||
|
self.manifest.default.sync_smartsync = False
|
||||||
|
self.opt.smart_sync = None
|
||||||
|
self.opt.mp_update = False
|
||||||
|
user_cfg = mock.MagicMock()
|
||||||
|
user_cfg.GetBoolean.return_value = True
|
||||||
|
with mock.patch.object(
|
||||||
|
sync.RepoConfig, "ForUser", return_value=user_cfg
|
||||||
|
):
|
||||||
|
with mock.patch.object(
|
||||||
|
self.cmd,
|
||||||
|
"_SmartSyncSetup",
|
||||||
|
side_effect=sync.SmartSyncError("unreachable"),
|
||||||
|
) as mock_setup:
|
||||||
|
with self.assertRaises(sync.SmartSyncError):
|
||||||
|
self.cmd.Execute(self.opt, [])
|
||||||
|
mock_setup.assert_called_once()
|
||||||
|
|
||||||
def test_implicit_smart_sync_unparsable_manifest(self) -> None:
|
def test_implicit_smart_sync_unparsable_manifest(self) -> None:
|
||||||
"""Ensure an unparsable smart sync manifest falls back to ToT."""
|
"""Ensure an unparsable smart sync manifest falls back to ToT."""
|
||||||
self.manifest.default.sync_smartsync = True
|
self.manifest.default.sync_smartsync = True
|
||||||
|
|||||||
@@ -233,3 +233,45 @@ def test_MultipleBranches_yes_without_branch_or_cbr_uses_editor(
|
|||||||
cmd._MultipleBranches(opt, pending, _STUB_PEOPLE)
|
cmd._MultipleBranches(opt, pending, _STUB_PEOPLE)
|
||||||
mock_edit.assert_called_once()
|
mock_edit.assert_called_once()
|
||||||
mock_upload.assert_called_once_with(opt, [branch1], _STUB_PEOPLE)
|
mock_upload.assert_called_once_with(opt, [branch1], _STUB_PEOPLE)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"argv, expected",
|
||||||
|
(
|
||||||
|
(["--fix", "--ignore-hooks"], "not be part of the upload"),
|
||||||
|
(["--fix", "--no-verify"], "--fix would have no effect"),
|
||||||
|
(
|
||||||
|
["--fix", "--ignore-hooks", "--no-verify"],
|
||||||
|
"--fix and --ignore-hooks are mutually exclusive",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_ValidateOptions_fix_conflicts(
|
||||||
|
cmd: upload.Upload,
|
||||||
|
capsys: pytest.CaptureFixture,
|
||||||
|
argv: List[str],
|
||||||
|
expected: str,
|
||||||
|
) -> None:
|
||||||
|
"""Check --fix is rejected with --ignore-hooks or --no-verify."""
|
||||||
|
opt, args = cmd.OptionParser.parse_args(argv)
|
||||||
|
with pytest.raises(SystemExit):
|
||||||
|
cmd.ValidateOptions(opt, args)
|
||||||
|
assert expected in capsys.readouterr().err
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"argv",
|
||||||
|
(
|
||||||
|
[],
|
||||||
|
["--fix"],
|
||||||
|
["--fix", "--verify"],
|
||||||
|
["--fix", "-y"],
|
||||||
|
["--ignore-hooks"],
|
||||||
|
["--no-verify"],
|
||||||
|
["--ignore-hooks", "--no-verify"],
|
||||||
|
),
|
||||||
|
)
|
||||||
|
def test_ValidateOptions_allowed(cmd: upload.Upload, argv: List[str]) -> None:
|
||||||
|
"""Check valid hook option combinations are accepted."""
|
||||||
|
opt, args = cmd.OptionParser.parse_args(argv)
|
||||||
|
cmd.ValidateOptions(opt, args)
|
||||||
Reference in new issue
Block a user