Skip to content

Commit cf35c02

Browse files
authored
Merge pull request #1072 from projectsyn/feat/template-version-update-sync
Add support for custom template versions for dependency update and sync
2 parents 0d03e44 + 26563d4 commit cf35c02

File tree

10 files changed

+248
-64
lines changed

10 files changed

+248
-64
lines changed

commodore/cli/component.py

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -291,12 +291,7 @@ def component_group(config: Config, verbose):
291291
show_default=True,
292292
help="The URL of the component cookiecutter template.",
293293
)
294-
@click.option(
295-
"--template-version",
296-
default="main",
297-
show_default=True,
298-
help="The component template version (Git tree-ish) to use.",
299-
)
294+
@options.template_version("main")
300295
@new_update_options(new_cmd=True)
301296
@options.verbosity
302297
@options.pass_config
@@ -430,13 +425,15 @@ def component_new(
430425
default=True,
431426
help="Whether to commit the rendered template changes.",
432427
)
428+
@options.template_version(None)
433429
@options.verbosity
434430
@options.pass_config
435431
def component_update(
436432
config: Config,
437433
verbose: int,
438434
component_path: str,
439435
copyright_holder: str,
436+
template_version: Optional[str],
440437
golden_tests: Optional[bool],
441438
matrix_tests: Optional[bool],
442439
lib: Optional[bool],
@@ -492,6 +489,8 @@ def component_update(
492489
t.automerge_patch_v0 = automerge_patch_v0
493490
if autorelease is not None:
494491
t.autorelease = autorelease
492+
if template_version is not None:
493+
t.template_version = template_version
495494

496495
test_cases = t.test_cases
497496
test_cases.extend(additional_test_case)
@@ -625,6 +624,7 @@ def component_compile(
625624
@options.pr_batch_size
626625
@options.github_pause
627626
@options.dependency_filter
627+
@options.template_version(None)
628628
def component_sync(
629629
config: Config,
630630
verbose: int,
@@ -636,6 +636,7 @@ def component_sync(
636636
pr_batch_size: int,
637637
github_pause: int,
638638
filter: str,
639+
template_version: Optional[str],
639640
):
640641
"""This command processes all components listed in the provided `COMPONENT_LIST`
641642
YAML file.
@@ -672,4 +673,5 @@ def component_sync(
672673
pr_batch_size,
673674
timedelta(seconds=github_pause),
674675
filter,
676+
template_version,
675677
)

commodore/cli/options.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
"""Click options which are reused for multiple commands"""
22

3+
from typing import Optional
4+
35
import click
46

57
from commodore.config import Config
@@ -125,6 +127,22 @@
125127
)
126128

127129

130+
def template_version(default: Optional[str]):
131+
help_str = "The component template version (Git tree-ish) to use."
132+
if default is None:
133+
help_str = (
134+
help_str
135+
+ " If not provided, the currently active template version will be used."
136+
)
137+
138+
return click.option(
139+
"--template-version",
140+
default=default,
141+
show_default=default is not None,
142+
help=help_str,
143+
)
144+
145+
128146
def local(help: str):
129147
return click.option(
130148
"--local",

commodore/cli/package.py

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,7 @@ def package_group(config: Config, verbose: int):
6161
show_default=True,
6262
help="The URL of the package cookiecutter template.",
6363
)
64-
@click.option(
65-
"--template-version",
66-
default="main",
67-
show_default=True,
68-
help="The package template version (Git tree-ish) to use.",
69-
)
64+
@options.template_version("main")
7065
@click.option(
7166
"--output-dir",
7267
default="",
@@ -167,6 +162,7 @@ def package_new(
167162
default=True,
168163
help="Whether to commit the rendered template changes.",
169164
)
165+
@options.template_version(None)
170166
@options.verbosity
171167
@options.pass_config
172168
# pylint: disable=too-many-arguments
@@ -180,6 +176,7 @@ def package_update(
180176
additional_test_case: Iterable[str],
181177
remove_test_case: Iterable[str],
182178
commit: bool,
179+
template_version: Optional[str],
183180
):
184181
"""This command updates the package at PACKAGE_PATH to the latest version of the
185182
template which was originally used to create it, if the template version is given as
@@ -201,6 +198,9 @@ def package_update(
201198
t.golden_tests = golden_tests
202199
if update_copyright_year:
203200
t.copyright_year = None
201+
if template_version is not None:
202+
t.template_version = template_version
203+
204204
test_cases = t.test_cases
205205
test_cases.extend(additional_test_case)
206206
t.test_cases = [tc for tc in test_cases if tc not in remove_test_case]
@@ -278,6 +278,7 @@ def package_compile(
278278
@options.pr_batch_size
279279
@options.github_pause
280280
@options.dependency_filter
281+
@options.template_version(None)
281282
def package_sync(
282283
config: Config,
283284
verbose: int,
@@ -289,6 +290,7 @@ def package_sync(
289290
pr_batch_size: int,
290291
github_pause: int,
291292
filter: str,
293+
template_version: Optional[str],
292294
):
293295
"""This command processes all packages listed in the provided `PACKAGE_LIST` YAML file.
294296
@@ -324,4 +326,5 @@ def package_sync(
324326
pr_batch_size,
325327
timedelta(seconds=github_pause),
326328
filter,
329+
template_version,
327330
)

commodore/dependency_syncer.py

Lines changed: 68 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from collections.abc import Iterable
77
from datetime import timedelta
88
from pathlib import Path
9-
from typing import Union, Type
9+
from typing import Optional, Union, Type
1010

1111
import click
1212
import git
@@ -36,11 +36,17 @@ def sync_dependencies(
3636
pr_batch_size: int = 10,
3737
pause: timedelta = timedelta(seconds=120),
3838
depfilter: str = "",
39+
template_version: Optional[str] = None,
3940
) -> None:
4041
if not config.github_token:
4142
raise click.ClickException("Can't continue, missing GitHub API token.")
4243

43-
deptype_str = deptype.__name__.lower()
44+
if template_version is not None and not dry_run:
45+
click.secho(
46+
" > Custom template version provided for sync, but dry run not active. Forcing dry run",
47+
fg="yellow",
48+
)
49+
dry_run = True
4450

4551
deps = read_dependency_list(dependency_list, depfilter)
4652
dep_count = len(deps)
@@ -49,38 +55,17 @@ def sync_dependencies(
4955
# Keep track of how many PRs we've created to better avoid running into rate limits
5056
update_count = 0
5157
for i, dn in enumerate(deps, start=1):
52-
click.secho(f"Synchronizing {dn}", bold=True)
53-
_, dreponame = dn.split("/")
54-
dname = dreponame.replace(f"{deptype_str}-", "", 1)
55-
56-
# Clone dependency
57-
try:
58-
gr = gh.get_repo(dn)
59-
except github.UnknownObjectException:
60-
click.secho(f" > Repository {dn} doesn't exist, skipping...", fg="yellow")
61-
continue
62-
63-
if gr.archived:
64-
click.secho(f" > Repository {dn} is archived, skipping...", fg="yellow")
65-
continue
66-
67-
d = deptype.clone(config, gr.clone_url, dname, version=gr.default_branch)
68-
69-
if not (d.target_dir / ".cruft.json").is_file():
70-
click.echo(f" > Skipping repo {dn} which doesn't have `.cruft.json`")
71-
continue
72-
73-
# Update the dependency
74-
t = templater.from_existing(config, d.target_dir)
75-
changed = t.update(
76-
print_completion_message=False,
77-
commit=not dry_run,
78-
ignore_template_commit=True,
58+
changed = sync_dependency(
59+
config,
60+
gh,
61+
dn,
62+
deptype,
63+
templater,
64+
template_version,
65+
dry_run,
66+
pr_branch,
67+
pr_label,
7968
)
80-
81-
# Create or update PR if there were updates
82-
comment = render_pr_comment(d)
83-
create_or_update_pr(d, dn, gr, changed, pr_branch, pr_label, dry_run, comment)
8469
if changed:
8570
update_count += 1
8671
if not dry_run and i < dep_count:
@@ -90,6 +75,56 @@ def sync_dependencies(
9075
_maybe_pause(update_count, pr_batch_size, pause)
9176

9277

78+
def sync_dependency(
79+
config: Config,
80+
gh: github.Github,
81+
depname: str,
82+
deptype: Type[Union[Component, Package]],
83+
templater,
84+
template_version: Optional[str],
85+
dry_run: bool,
86+
pr_branch: str,
87+
pr_label: Iterable[str],
88+
):
89+
deptype_str = deptype.__name__.lower()
90+
91+
click.secho(f"Synchronizing {depname}", bold=True)
92+
_, dreponame = depname.split("/")
93+
dname = dreponame.replace(f"{deptype_str}-", "", 1)
94+
95+
# Clone dependency
96+
try:
97+
gr = gh.get_repo(depname)
98+
except github.UnknownObjectException:
99+
click.secho(f" > Repository {depname} doesn't exist, skipping...", fg="yellow")
100+
return
101+
102+
if gr.archived:
103+
click.secho(f" > Repository {depname} is archived, skipping...", fg="yellow")
104+
return
105+
106+
d = deptype.clone(config, gr.clone_url, dname, version=gr.default_branch)
107+
108+
if not (d.target_dir / ".cruft.json").is_file():
109+
click.echo(f" > Skipping repo {depname} which doesn't have `.cruft.json`")
110+
return
111+
112+
# Update the dependency
113+
t = templater.from_existing(config, d.target_dir)
114+
if template_version is not None:
115+
t.template_version = template_version
116+
changed = t.update(
117+
print_completion_message=False,
118+
commit=not dry_run,
119+
ignore_template_commit=True,
120+
)
121+
122+
# Create or update PR if there were updates
123+
comment = render_pr_comment(d)
124+
create_or_update_pr(d, depname, gr, changed, pr_branch, pr_label, dry_run, comment)
125+
return changed
126+
127+
93128
def read_dependency_list(dependency_list: Path, depfilter: str) -> list[str]:
94129
try:
95130
deps = yaml_load(dependency_list)

commodore/dependency_templater.py

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,20 @@ def _ignore_cruft_json_commit_id(
4949
n=0,
5050
)
5151
)[3:]
52-
# If the context-less diff has exactly 2 lines and both of them start with
53-
# '[-+] "commit":', we omit the diff
5452
if (
53+
# If the context-less diff has exactly 2 lines and both of them start with
54+
# '[-+] "commit":', we omit the diff
5555
len(minimal_diff) == 2
5656
and minimal_diff[0].startswith('- "commit":')
5757
and minimal_diff[1].startswith('+ "commit":')
58+
) or (
59+
# If the context-less diff has exactly 4 lines and the two pairs start with
60+
# '[-+] "commit":' and '[-+] "checkout":', we omit the diff
61+
len(minimal_diff) == 4
62+
and minimal_diff[0].startswith('- "commit":')
63+
and minimal_diff[1].startswith('- "checkout":')
64+
and minimal_diff[2].startswith('+ "commit":')
65+
and minimal_diff[3].startswith('+ "checkout":')
5866
):
5967
omit = True
6068
# never suppress diffs in default difffunc

docs/modules/ROOT/pages/reference/cli.adoc

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -308,6 +308,10 @@ NOTE: If autorelease is enabled, new releases will be generated for automerged d
308308
*--commit / --no-commit*::
309309
Whether to commit the rendered template changes.
310310

311+
*--template-version* TEXT::
312+
The component template version (Git tree-ish) to use.
313+
If not provided, the currently active template version will be used.
314+
311315
*--automerge-patch / --no-automerge-patch*::
312316
Enable automerging of patch-level dependency PRs.
313317

@@ -440,6 +444,10 @@ However, labels added by previous runs can't be removed since we've got no easy
440444
Regex to select which dependencies to sync.
441445
If the option isn't given, all dependencies listed in the provided YAML are synced.
442446

447+
*--template-version* TEXT::
448+
The component template version (Git tree-ish) to use.
449+
If not provided, the currently active template version will be used.
450+
443451
== Inventory Components / Packages / Show
444452

445453
*-f, --values*::
@@ -549,6 +557,10 @@ However, labels added by previous runs can't be removed since we've got no easy
549557
*--commit / --no-commit*::
550558
Whether to commit the rendered template changes.
551559

560+
*--template-version* TEXT::
561+
The component template version (Git tree-ish) to use.
562+
If not provided, the currently active template version will be used.
563+
552564
== Package Compile
553565

554566
*-f, --values* FILE::
@@ -638,3 +650,7 @@ However, labels added by previous runs can't be removed since we've got no easy
638650
*--filter* REGEX::
639651
Regex to select which dependencies to sync.
640652
If the option isn't given, all dependencies listed in the provided YAML are synced.
653+
654+
*--template-version* TEXT::
655+
The component template version (Git tree-ish) to use.
656+
If not provided, the currently active template version will be used.

tests/conftest.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,3 +141,24 @@ def bare_repo(self) -> GitRepo:
141141
@pytest.fixture
142142
def mockdep(tmp_path):
143143
return MockMultiDependency(Repo.init(tmp_path / "repo.git"))
144+
145+
146+
class MockTemplater:
147+
def __init__(self):
148+
self.template_version = None
149+
self.test_cases = []
150+
151+
def update(self, *args, **kwargs):
152+
pass
153+
154+
155+
def make_mock_templater(mock_templater, expected_path):
156+
mt = MockTemplater()
157+
158+
def mock_from_existing(_config: Config, path: Path):
159+
assert path == expected_path
160+
return mt
161+
162+
mock_templater.from_existing = mock_from_existing
163+
164+
return mt

0 commit comments

Comments
 (0)