Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Singular remote fixes #383

Merged
merged 10 commits into from
Dec 26, 2023
1 change: 1 addition & 0 deletions THANKS
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ Tronje Krabbe
Matthew Lovell
Atte Pellikka
Johann Chang
Albert De La Fuente Vigliotti
Greg Dubicki


Expand Down
34 changes: 27 additions & 7 deletions docs/changelog.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,35 @@
# Changelog

## Unrelased
## 3.0.0

### Optionnal correct branch on sync
### Breaking: correct branch on sync

If any of the repositories is not on the configured branch, but it is clean
and the `--correct-branch`/`-c` flag is set, then the branch is changed to
the configured one and then the repository is updated. Otherwise that repository
will not be not updated.
If any of the repositories is not on the configured branch, but it is
clean then the branch is changed to the configured one and then the
repository is updated. Otherwise that repository will not be not updated.

Implemented by Greg Dubicki
Previously, `tsrc sync` would print an error and *not* checkout the branch.

To have `tsrc sync` behave like this, use the new `--no-correct-branch` flag.

### Breaking: add --singular-remote argument to tsrc sync too

In `tsrc 2.7` you could use `-r` in `tsrc init` to only use one
remote. But you had no way to pass the same option to `tsrc sync`.

In this version, you can use `-r` or `--singular-remote` for both
`tsrc init` and `tsrc sync`

Unfortunately , this means you must now use `-i, --include <regex>`
instead of of `-r <regex>` when selecting repositories based on a regex.

Implemented by:

* Albert De La Fuente Vigliotti
* Greg Dubicki
* Dimitri Merejkowsky

Original issue reportedy by Maxime Réty

### Other changes

Expand Down
6 changes: 3 additions & 3 deletions docs/guide/groups.md
Original file line number Diff line number Diff line change
Expand Up @@ -39,16 +39,16 @@ tsrc init [email protected]:acme/manifest --group g1

## Filtering repositories in groups with regular expressions

You can utilize inclusive regular expression with the `-r`-flag and
exclusive regular expression with the `-i`-flag. This allows you to filter
You can utilize inclusive regular expression with the `-i`-flag and
exclusive regular expression with the `-e`-flag. This allows you to filter
repositories within a group or a set of groups for the given action.


To include all repositories in the group g1 matching "config" and excluding "template",
you can do the following:

```
tsrc init [email protected]:acme/manifest --group g1 -r config -i template
tsrc init [email protected]:acme/manifest --group g1 -i config -e template
```


Expand Down
6 changes: 3 additions & 3 deletions docs/guide/remotes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ In that case, you can use an alternative syntax:
repos:
# foo is open source and thus needs two remotes:
- dest: foo
- remotes:
remotes:
- name: origin
url: [email protected]/your-team/foo
- name: github
Expand Down Expand Up @@ -57,14 +57,14 @@ In that case, you can create a manifest looking like this:
```yaml
repos:
- dest: foo
- remotes:
remotes:
- name: origin
url: [email protected]/your-team/foo
- name: vpn
url: [email protected]/gitlab/your-team/foo

- dest: bar
- remotes:
remotes:
- name: origin
url: [email protected]/your-team/bar
- name: vpn
Expand Down
4 changes: 2 additions & 2 deletions docs/ref/cli.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ tsrc init MANIFEST_URL [--group GROUP1, GROUP2] [--singular-remote SINGULAR_REMO
The `-g,--groups` option can be used to specify a list of groups
to use when cloning repositories.

The `-r` "inclusive regular expression" and `-i` "exclusive regular expression" options
can be combined with the group option to filter for repositories within a group. `-r` takes
The `-i` "inclusive regular expression" and `-e` "exclusive regular expression" options
can be combined with the group option to filter for repositories within a group. `-i` takes
precedence if both options are present.

The `-s,--shallow` option can be used to make shallow clone of all repositories.
Expand Down
2 changes: 1 addition & 1 deletion docs/ref/manifest-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ repos:
dest: foo
branch: next

- remotes:
remotes:
- name: origin
url: [email protected]:proj1/bar
- name: upstream
Expand Down
6 changes: 5 additions & 1 deletion docs/ref/workspace-config.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ singular_remote:
* `shallow_clones`: whether to use only shallow clones when cloning missing repositories
* `repo_groups`: the list of groups to use - every mentioned group must be present in the `manifest.yml` file (see above)
* `clone_all_repos`: whether to ignore groups entirely and clone every repository from the manifest instead
* `singular_remote`: if set to `<remote-name>`, behaves as if `tsrc sync` and `tsrc init` were called with `--singular-remote <remote-name>` option. See the [Using remotes guide](../guide/remotes.md) for details.
* `singular_remote`: if set to `<remote-name>`, behaves as if `tsrc sync` and
`tsrc init` were called with `--singular-remote <remote-name>` option. See the
[Using remotes guide](../guide/remotes.md) for details. If `tsrc sync -r
<remote-name>` is used, it will take precedence over the file configuration
parameter.
37 changes: 25 additions & 12 deletions tsrc/cli/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -75,12 +75,12 @@ def add_repos_selection_args(parser: argparse.ArgumentParser) -> None:
help="run on all cloned repos",
)
parser.add_argument(
"-r",
dest="regex",
"-i",
dest="include_regex",
help="Include only repositories matching the regex",
)
parser.add_argument(
"-i", dest="iregex", help="Exclude repositories matching the regex"
"-e", dest="exclude_regex", help="Exclude repositories matching the regex"
)


Expand All @@ -107,19 +107,20 @@ def get_workspace_with_repos(namespace: argparse.Namespace) -> Workspace:
workspace,
groups=namespace.groups,
all_cloned=namespace.all_cloned,
regex=namespace.regex,
iregex=namespace.iregex,
include_regex=namespace.include_regex,
exclude_regex=namespace.exclude_regex,
)
return workspace


def resolve_repos(
workspace: Workspace,
*,
singular_remote: str = "",
groups: Optional[List[str]],
all_cloned: bool,
regex: str = "",
iregex: str = "",
include_regex: str = "",
exclude_regex: str = "",
) -> List[Repo]:
"""
Given a workspace with its config and its local manifest,
Expand All @@ -138,11 +139,23 @@ def resolve_repos(
else:
repos = repos_from_config(manifest, workspace.config)

if regex:
repos = [repo for repo in repos if re.search(regex, repo.dest)]

if iregex:
repos = [repo for repo in repos if not re.search(iregex, repo.dest)]
if singular_remote:
filtered_repos = []
for repo in repos:
remotes = [
remote
for remote in repo.remotes
if singular_remote.lower() == remote.name.lower()
]
if remotes:
filtered_repos.append(repo)
repos = filtered_repos

if include_regex:
repos = [repo for repo in repos if re.search(include_regex, repo.dest)]

if exclude_regex:
repos = [repo for repo in repos if not re.search(exclude_regex, repo.dest)]

# At this point, nothing was requested on the command line, time to
# use the workspace configuration:
Expand Down
24 changes: 20 additions & 4 deletions tsrc/cli/sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,11 @@ def configure_parser(subparser: argparse._SubParsersAction) -> None:
dest="correct_branch",
help="prevent going back to the configured branch, if the repo is clean",
)
parser.add_argument(
"-r",
"--singular-remote",
help="only use this remote when cloning repositories",
)
add_num_jobs_arg(parser)
parser.set_defaults(run=run)

Expand All @@ -43,8 +48,9 @@ def run(args: argparse.Namespace) -> None:
update_manifest = args.update_manifest
groups = args.groups
all_cloned = args.all_cloned
regex = args.regex
iregex = args.iregex
singular_remote = args.singular_remote
include_regex = args.include_regex
exclude_regex = args.exclude_regex
correct_branch = args.correct_branch
workspace = get_workspace(args)
num_jobs = get_num_jobs(args)
Expand All @@ -56,11 +62,21 @@ def run(args: argparse.Namespace) -> None:
ui.info_2("Not updating manifest")

workspace.repos = resolve_repos(
workspace, groups=groups, all_cloned=all_cloned, regex=regex, iregex=iregex
workspace,
singular_remote=singular_remote,
groups=groups,
all_cloned=all_cloned,
include_regex=include_regex,
exclude_regex=exclude_regex,
)

workspace.clone_missing(num_jobs=num_jobs)
workspace.set_remotes(num_jobs=num_jobs)
workspace.sync(force=force, num_jobs=num_jobs, correct_branch=correct_branch)
workspace.sync(
force=force,
singular_remote=singular_remote,
correct_branch=correct_branch,
num_jobs=num_jobs,
)
workspace.perform_filesystem_operations()
ui.info_1("Workspace synchronized")
12 changes: 6 additions & 6 deletions tsrc/test/cli/test_init.py
Original file line number Diff line number Diff line change
Expand Up @@ -359,16 +359,16 @@ def test_singular_remote(
* Check that foo only has one remote called 'origin'
"""
foo_url = git_server.add_repo("foo")
vpn_url = "/does/not/exist"
# fmt: off
git_server.manifest.set_repo_remotes(
"foo",
[("origin", foo_url),
("vpn", vpn_url)])
# fmt: on
[
("origin", foo_url),
("vpn", "/does/not/exist"),
],
)

# only use "origin" remote
tsrc_cli.run("init", git_server.manifest_url, "-r", "origin")
tsrc_cli.run("init", git_server.manifest_url, "--singular-remote", "origin")

foo_path = workspace_path / "foo"
_, output = run_git_captured(foo_path, "remote", "show", check=True)
Expand Down
12 changes: 6 additions & 6 deletions tsrc/test/cli/test_sync.py
Original file line number Diff line number Diff line change
Expand Up @@ -742,14 +742,14 @@ def test_sync_with_singular_remote(
* Check that 'tsrc sync' does not try and fetch the 'vpn' remote
"""
foo_url = git_server.add_repo("foo")
vpn_url = "/does/not/exist"
# fmt: off
git_server.manifest.set_repo_remotes(
"foo",
[("origin", foo_url),
("vpn", vpn_url)])
# fmt: on
tsrc_cli.run("init", git_server.manifest_url, "-r", "origin")
[
("origin", foo_url),
("vpn", "/does/not/exist"),
],
)
tsrc_cli.run("init", git_server.manifest_url, "--singular-remote", "origin")

tsrc_cli.run("sync")

Expand Down
22 changes: 15 additions & 7 deletions tsrc/test/test_resolve_repos.py
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ def test_filter_inclusive(tmp_path: Path) -> None:
* A repo named 'other' in the manifest
* Workspace configured with repo_group=[group]
* --group group1 used on the command line
* -r foo used on the command line
* -i foo used on the command line

Should return repos foo and foo2 from group1
"""
Expand All @@ -177,7 +177,9 @@ def test_filter_inclusive(tmp_path: Path) -> None:
)
workspace = create_workspace(tmp_path, repo_groups=["group1"])

actual = resolve_repos(workspace, groups=["group1"], all_cloned=False, regex="foo")
actual = resolve_repos(
workspace, groups=["group1"], all_cloned=False, include_regex="foo"
)
assert repo_names(actual) == ["foo", "foo2"]


Expand All @@ -187,7 +189,7 @@ def test_filter_exclusive(tmp_path: Path) -> None:
* A repo named 'other' in the manifest
* Workspace configured with repo_group=[group]
* --group group1 used on the command line
* -i foo used on the command line
* -e foo used on the command line

Should return repos bar and bar2 from group1
"""
Expand All @@ -197,7 +199,9 @@ def test_filter_exclusive(tmp_path: Path) -> None:
)
workspace = create_workspace(tmp_path, repo_groups=["group1"])

actual = resolve_repos(workspace, groups=["group1"], all_cloned=False, iregex="foo")
actual = resolve_repos(
workspace, groups=["group1"], all_cloned=False, exclude_regex="foo"
)
assert repo_names(actual) == ["bar", "bar2"]


Expand All @@ -207,8 +211,8 @@ def test_filter_inclusive_exclusive(tmp_path: Path) -> None:
* A repo named 'other' in the manifest
* Workspace configured with repo_group=[group]
* --group group1 used on the command line
* -r foo used on the command line
* -i 2 used on the command line
* -i foo used on the command line
* -e 2 used on the command line

Should return repo foo from group1
"""
Expand All @@ -219,6 +223,10 @@ def test_filter_inclusive_exclusive(tmp_path: Path) -> None:
workspace = create_workspace(tmp_path, repo_groups=["group1"])

actual = resolve_repos(
workspace, groups=["group1"], all_cloned=False, regex="foo", iregex="2"
workspace,
groups=["group1"],
all_cloned=False,
include_regex="foo",
exclude_regex="2",
)
assert repo_names(actual) == ["foo"]
15 changes: 13 additions & 2 deletions tsrc/workspace.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,14 +125,25 @@ def perform_filesystem_operations(
raise FileSystemOperatorError

def sync(
self, *, force: bool = False, num_jobs: int = 1, correct_branch: bool = False
self,
*,
singular_remote: str = "",
correct_branch: bool = False,
force: bool = False,
num_jobs: int = 1,
) -> None:
remote_name = ""
if singular_remote:
remote_name = singular_remote
elif self.config.singular_remote:
remote_name = self.config.singular_remote
syncer = Syncer(
self.root_path,
force=force,
remote_name=self.config.singular_remote,
remote_name=remote_name,
correct_branch=correct_branch,
)

repos = self.repos
ui.info_2("Synchronizing repos")
collection = process_items(repos, syncer, num_jobs=num_jobs)
Expand Down