Skip to content

Commit d0178b1

Browse files
committed
Use gitpython branch handling during clone
1 parent eb21edb commit d0178b1

5 files changed

Lines changed: 23 additions & 67 deletions

File tree

helm/blueapi/config_schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -431,7 +431,7 @@
431431
"type": "string"
432432
},
433433
"branch": {
434-
"description": "Branch of repo to check out - defaults to remote's default when cloning and the existing branch when the repo already exists",
434+
"description": "Branch (or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.",
435435
"title": "Branch",
436436
"type": "string"
437437
}

helm/blueapi/values.schema.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -854,7 +854,7 @@
854854
"properties": {
855855
"branch": {
856856
"title": "Branch",
857-
"description": "Branch of repo to check out - defaults to remote's default when cloning and the existing branch when the repo already exists",
857+
"description": "Branch (or tag) to check out when cloning - defaults to remote's HEAD. If a tag is used, the repo will be left in a 'detached head' state.",
858858
"type": "string"
859859
},
860860
"name": {

src/blueapi/cli/scratch.py

Lines changed: 6 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,12 @@ 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, repo.tag)
51+
ensure_repo(repo.remote_url, local_directory, repo.branch)
5252
scratch_install(local_directory, timeout=install_timeout)
5353

5454

5555
def ensure_repo(
56-
remote_url: str,
57-
local_directory: Path,
58-
branch: str | None = None,
59-
tag: str | None = None,
56+
remote_url: str, local_directory: Path, branch: str | None = None
6057
) -> None:
6158
"""
6259
Ensure that a repository is checked out for use in the scratch area.
@@ -67,41 +64,20 @@ def ensure_repo(
6764
local_directory: Output path for cloning
6865
"""
6966

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

7670
if not local_directory.exists():
7771
LOGGER.info(f"Cloning {remote_url}")
78-
repo = Repo.clone_from(remote_url, local_directory)
72+
Repo.clone_from(remote_url, local_directory, branch=branch)
7973
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))
10074
elif local_directory.is_dir():
75+
Repo(local_directory)
10176
LOGGER.info(f"Found {local_directory}")
10277
else:
10378
raise KeyError(
104-
f"Unable to open {local_directory} as a git repository because it is a file"
79+
f"Unable to open {local_directory} as a git repository because it is not a "
80+
"directory"
10581
)
10682

10783

src/blueapi/config.py

Lines changed: 5 additions & 18 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, Self, TypeVar, cast
9+
from typing import Annotated, Any, ClassVar, Generic, Literal, TypeVar, cast
1010

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

@@ -193,14 +192,10 @@ class ScratchRepository(BlueapiBaseModel):
193192
default="https://github.com/example/example.git",
194193
)
195194
branch: str | SkipJsonSchema[None] = Field(
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.",
195+
description=(
196+
"Branch (or tag) to check out when cloning - defaults to remote's HEAD. "
197+
"If a tag is used, the repo will be left in a 'detached head' state."
198+
),
204199
exclude_if=lambda f: f is None,
205200
# using default_factory instead of default means the schema doesn't
206201
# include an invalid value
@@ -214,14 +209,6 @@ def check_remote_url(cls, value: str) -> str:
214209
raise ValueError(f"remote_url '{value}' is not allowed.")
215210
return value
216211

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-
225212

226213
class ScratchConfig(BlueapiBaseModel):
227214
root: Path = Field(

tests/unit_tests/cli/test_scratch.py

Lines changed: 10 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,7 @@ def test_repo_cloned_if_not_found_locally(
123123
ensure_repo("http://example.com/foo.git", nonexistant_path)
124124
mock_repo.assert_not_called()
125125
mock_repo.clone_from.assert_called_once_with(
126-
"http://example.com/foo.git", nonexistant_path
126+
"http://example.com/foo.git", nonexistant_path, branch=None
127127
)
128128

129129

@@ -140,7 +140,7 @@ def write_repo_files():
140140
with file_path.open("w") as stream:
141141
stream.write("foo")
142142

143-
mock_repo.clone_from.side_effect = lambda url, path: write_repo_files()
143+
mock_repo.clone_from.side_effect = lambda url, path, branch=None: write_repo_files()
144144

145145
ensure_repo("http://example.com/foo.git", repo_root)
146146
assert file_path.exists()
@@ -162,26 +162,23 @@ def test_cloned_repo_changes_to_new_branch(mock_repo, directory_path: Path):
162162

163163
ensure_repo("http://example.com/foo.git", directory_path / "demo_branch", "demo")
164164

165-
mock_repo.clone_from.assert_called_once_with("http://example.com/foo.git", ANY)
166-
repo.create_head.assert_called_once_with("demo", ANY)
167-
repo.create_head().checkout.assert_called_once()
165+
mock_repo.clone_from.assert_called_once_with(
166+
"http://example.com/foo.git", ANY, branch="demo"
167+
)
168168

169169

170170
@patch("blueapi.cli.scratch.Repo")
171-
def test_existing_repo_changes_to_existing_branch(mock_repo, directory_path: Path):
171+
def test_existing_repo_not_changed_to_existing_branch(mock_repo, directory_path: Path):
172172
(directory_path / "demo_branch").mkdir()
173-
repo = Mock(spec=Repo)
174-
mock_repo.return_value = repo
175173

176174
ensure_repo("http://example.com/foo.git", directory_path / "demo_branch", "demo")
177175

176+
mock_repo.assert_called_once_with(directory_path / "demo_branch")
178177
mock_repo.clone_from.assert_not_called()
179-
repo.create_head.assert_not_called()
180-
repo.heads.demo.checkout.assert_called_once()
181178

182179

183180
@patch("blueapi.cli.scratch.Repo")
184-
def test_existing_repo_changes_to_new_branch(mock_repo, directory_path: Path):
181+
def test_existing_repo_not_changed_to_new_branch(mock_repo, directory_path: Path):
185182
(directory_path / "demo_branch").mkdir()
186183
repo = MagicMock(name="ExistingRepo", spec=Repo)
187184
repo.heads.demo = None
@@ -190,8 +187,7 @@ def test_existing_repo_changes_to_new_branch(mock_repo, directory_path: Path):
190187
ensure_repo("http://example.com/foo.git", directory_path / "demo_branch", "demo")
191188

192189
mock_repo.clone_from.assert_not_called()
193-
repo.create_head.assert_called_once_with("demo", repo.remotes[0].refs["demo"])
194-
repo.create_head().checkout.assert_called_once()
190+
repo.create_head.assert_not_called()
195191

196192

197193
def test_setup_scratch_fails_on_nonexistant_root(
@@ -292,10 +288,7 @@ def test_setup_scratch_iterates_repos(
292288
config = ScratchConfig(
293289
root=directory_path_with_sgid,
294290
repositories=[
295-
ScratchRepository(
296-
name="foo",
297-
remote_url="http://example.com/foo.git",
298-
),
291+
ScratchRepository(name="foo", remote_url="http://example.com/foo.git"),
299292
ScratchRepository(
300293
name="bar",
301294
remote_url="http://example.com/bar.git",

0 commit comments

Comments
 (0)