Skip to content

Commit eb21edb

Browse files
committed
Add tag handling to scratch checkout
1 parent 65b7e75 commit eb21edb

2 files changed

Lines changed: 46 additions & 21 deletions

File tree

src/blueapi/cli/scratch.py

Lines changed: 28 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -48,12 +48,15 @@ def setup_scratch(
4848
)
4949
for repo in config.repositories:
5050
local_directory = config.root / repo.name
51-
ensure_repo(repo.remote_url, local_directory, repo.branch)
51+
ensure_repo(repo.remote_url, local_directory, repo.branch, repo.tag)
5252
scratch_install(local_directory, timeout=install_timeout)
5353

5454

5555
def ensure_repo(
56-
remote_url: str, local_directory: Path, branch: str | None = None
56+
remote_url: str,
57+
local_directory: Path,
58+
branch: str | None = None,
59+
tag: str | None = None,
5760
) -> None:
5861
"""
5962
Ensure that a repository is checked out for use in the scratch area.
@@ -64,34 +67,43 @@ def ensure_repo(
6467
local_directory: Output path for cloning
6568
"""
6669

70+
if tag and branch:
71+
raise ValueError(f"Can't use both branch ({branch!r}) and tag ({tag!r})")
72+
6773
# Set umask to DLS standard
6874
os.umask(stat.S_IWOTH)
6975

7076
if not local_directory.exists():
7177
LOGGER.info(f"Cloning {remote_url}")
7278
repo = Repo.clone_from(remote_url, local_directory)
7379
LOGGER.info(f"Cloned {remote_url} -> {local_directory}")
80+
if branch:
81+
if not (local := getattr(repo.heads, branch, None)):
82+
origin = repo.remotes[0]
83+
origin.fetch()
84+
LOGGER.info(
85+
"Creating branch '%s' to track remote '%s'",
86+
branch,
87+
origin.refs[branch],
88+
)
89+
local = repo.create_head(branch, origin.refs[branch])
90+
local.set_tracking_branch(origin.refs[branch])
91+
92+
LOGGER.info("Checking out branch %r", branch)
93+
local.checkout()
94+
elif tag:
95+
if tag_obj := getattr(repo.tags, tag, None):
96+
LOGGER.info("Checking out tag %r", tag)
97+
tag_obj.checkout()
98+
else:
99+
raise ValueError("Could not find tag: " + repr(tag))
74100
elif local_directory.is_dir():
75-
repo = Repo(local_directory)
76101
LOGGER.info(f"Found {local_directory}")
77102
else:
78103
raise KeyError(
79104
f"Unable to open {local_directory} as a git repository because it is a file"
80105
)
81106

82-
if branch:
83-
if not (local := getattr(repo.heads, branch, None)):
84-
origin = repo.remotes[0]
85-
origin.fetch()
86-
LOGGER.info(
87-
"Creating branch '%s' to track remote '%s'", branch, origin.refs[branch]
88-
)
89-
local = repo.create_head(branch, origin.refs[branch])
90-
local.set_tracking_branch(origin.refs[branch])
91-
92-
LOGGER.info("Checking out branch '%s'", branch)
93-
local.checkout()
94-
95107

96108
def scratch_install(path: Path, timeout: float = _DEFAULT_INSTALL_TIMEOUT) -> None:
97109
"""

src/blueapi/config.py

Lines changed: 18 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from functools import cached_property
77
from pathlib import Path
88
from string import Template
9-
from typing import Annotated, Any, ClassVar, Generic, Literal, TypeVar, cast
9+
from typing import Annotated, Any, ClassVar, Generic, Literal, Self, TypeVar, cast
1010

1111
import requests
1212
import yaml
@@ -21,6 +21,7 @@
2121
UrlConstraints,
2222
ValidationError,
2323
field_validator,
24+
model_validator,
2425
)
2526
from pydantic.json_schema import SkipJsonSchema
2627

@@ -192,10 +193,14 @@ class ScratchRepository(BlueapiBaseModel):
192193
default="https://github.com/example/example.git",
193194
)
194195
branch: str | SkipJsonSchema[None] = Field(
195-
description=(
196-
"Branch of repo to check out - defaults to remote's default when "
197-
"cloning and the existing branch when the repo already exists"
198-
),
196+
description="Branch of repo to check out - defaults to remote's default",
197+
exclude_if=lambda f: f is None,
198+
# using default_factory instead of default means the schema doesn't
199+
# include an invalid value
200+
default_factory=lambda: None,
201+
)
202+
tag: str | SkipJsonSchema[None] = Field(
203+
description="Tag of repo to check out.",
199204
exclude_if=lambda f: f is None,
200205
# using default_factory instead of default means the schema doesn't
201206
# include an invalid value
@@ -209,6 +214,14 @@ def check_remote_url(cls, value: str) -> str:
209214
raise ValueError(f"remote_url '{value}' is not allowed.")
210215
return value
211216

217+
@model_validator(mode="after")
218+
def check_clone_options(self) -> Self:
219+
if self.branch is not None and self.tag is not None:
220+
raise ValueError(
221+
f"Cannot set both branch ({self.branch}) and tag ({self.tag})"
222+
)
223+
return self
224+
212225

213226
class ScratchConfig(BlueapiBaseModel):
214227
root: Path = Field(

0 commit comments

Comments
 (0)