Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions specification.md
Original file line number Diff line number Diff line change
Expand Up @@ -207,6 +207,20 @@ Commit hash. Only read if `initialization.steps[*].type` is `checkout`.

Type: `string`

#### `initialization.steps[*].start-point`

Starting point for creating a new branch. Only read if
`initialization.steps[*].type` is `checkout`.

When provided, `branch-name` must also be specified and the branch must not
already exist. This creates a new branch at the specified commit reference
(equivalent to `git checkout -b <branch-name> <start-point>`).

Accepts any valid git revision: commit SHAs, relative references (e.g.,
`HEAD~1`), branch names, or tags.

Type: `string`

#### `initialization.steps[*].remote-name`

Remote name. Only read if `initialization.steps[*].type` is `remote` or `fetch`.
Expand Down
16 changes: 15 additions & 1 deletion src/repo_smith/steps/checkout_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,17 @@
class CheckoutStep(Step):
branch_name: Optional[str]
commit_hash: Optional[str]
start_point: Optional[str]

step_type: StepType = field(init=False, default=StepType.CHECKOUT)

def execute(self, repo: Repo) -> None:
if self.branch_name is not None:
if self.branch_name not in repo.heads:
if self.start_point is not None:
if self.branch_name in repo.heads:
raise ValueError('"start-point" cannot be provided when "branch-name" already exists in checkout step.')
repo.git.checkout("-b", self.branch_name, self.start_point)
elif self.branch_name not in repo.heads:
raise ValueError("Invalid branch name")
else:
repo.heads[self.branch_name].checkout()
Expand Down Expand Up @@ -44,17 +49,26 @@ def parse(
raise ValueError(
'Provide either "branch-name" or "commit-hash", not both, in checkout step.'
)

if step.get("start-point") is not None and step.get("branch-name") is None:
raise ValueError(
'"start-point" requires "branch-name" to be provided in checkout step.'
)

if step.get("branch-name") is not None and step["branch-name"].strip() == "":
raise ValueError('Empty "branch-name" field in checkout step.')

if step.get("commit-hash") is not None and step["commit-hash"].strip() == "":
raise ValueError('Empty "commit-hash" field in checkout step.')

if step.get("start-point") is not None and step["start-point"].strip() == "":
raise ValueError('Empty "start-point" field in checkout step.')

return cls(
name=name,
description=description,
id=id,
branch_name=step.get("branch-name"),
commit_hash=step.get("commit-hash"),
start_point=step.get("start-point"),
)
37 changes: 37 additions & 0 deletions tests/integration/steps/test_checkout_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,29 @@ def test_checkout_step_missing_branch():
pass


def test_checkout_step_start_point_without_branch():
with pytest.raises(Exception):
initialize_repo(
"tests/specs/checkout_step/checkout_step_start_point_without_branch.yml"
)


def test_checkout_step_start_point_with_commit_hash():
with pytest.raises(Exception):
initialize_repo(
"tests/specs/checkout_step/checkout_step_start_point_with_commit_hash.yml"
)


def test_checkout_step_start_point_branch_exists():
with pytest.raises(Exception):
repo_initializer = initialize_repo(
"tests/specs/checkout_step/checkout_step_start_point_branch_exists.yml"
)
with repo_initializer.initialize():
pass


def test_checkout_step():
def first_hook(r: Repo) -> None:
assert r.active_branch.name == "main"
Expand All @@ -38,3 +61,17 @@ def second_hook(r: Repo) -> None:
with repo_initializer.initialize() as r:
assert len(r.branches) == 2
assert "test" in r.heads


def test_checkout_step_with_start_point():
def checkout_hook(r: Repo) -> None:
assert r.active_branch.name == "new-branch"

repo_initializer = initialize_repo(
"tests/specs/checkout_step/checkout_step_with_start_point.yml"
)
repo_initializer.add_post_hook("checkout-with-start-point", checkout_hook)
with repo_initializer.initialize() as r:
assert len(r.branches) == 2
assert "new-branch" in r.heads
assert r.heads["new-branch"].commit.message.strip() == "first commit"
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Checkout step start-point branch exists
description: Checkout step start-point branch exists raises error
initialization:
steps:
- type: commit
message: first commit
empty: true
- type: branch
branch-name: existing-branch
- type: checkout
branch-name: main
- type: checkout
branch-name: existing-branch
start-point: HEAD~1
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
name: Checkout step start-point with commit hash
description: Checkout step start-point with commit hash raises error
initialization:
steps:
- type: commit
message: test commit
empty: true
- type: checkout
start-point: HEAD~1
commit-hash: abc123
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
name: Checkout step start-point without branch
description: Checkout step start-point without branch raises error
initialization:
steps:
- type: commit
message: test commit
empty: true
- type: checkout
start-point: HEAD~1
14 changes: 14 additions & 0 deletions tests/specs/checkout_step/checkout_step_with_start_point.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
name: Checkout step with start-point
description: Checkout creating a new branch at a specific commit
initialization:
steps:
- type: commit
message: first commit
empty: true
- type: commit
message: second commit
empty: true
- type: checkout
branch-name: new-branch
start-point: HEAD~1
id: checkout-with-start-point
26 changes: 26 additions & 0 deletions tests/unit/steps/test_checkout_step.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ def test_checkout_step_parse_both_branch_name_and_commit_hash():
)


def test_checkout_step_parse_start_point_with_commit_hash():
with pytest.raises(
ValueError,
match='"start-point" requires "branch-name" to be provided in checkout step.',
):
CheckoutStep.parse("n", "d", "id", {"start-point": "HEAD~1", "commit-hash": "abc123"})


def test_checkout_step_parse_empty_branch_name():
with pytest.raises(ValueError, match='Empty "branch-name" field in checkout step.'):
CheckoutStep.parse("n", "d", "id", {"branch-name": ""})
Expand All @@ -31,6 +39,11 @@ def test_checkout_step_parse_empty_commit_hash():
CheckoutStep.parse("n", "d", "id", {"commit-hash": ""})


def test_checkout_step_parse_empty_start_point():
with pytest.raises(ValueError, match='Empty "start-point" field in checkout step.'):
CheckoutStep.parse("n", "d", "id", {"start-point": "", "branch-name": "test"})


def test_checkout_step_parse_with_branch_name():
step = CheckoutStep.parse("n", "d", "id", {"branch-name": "test"})
assert isinstance(step, CheckoutStep)
Expand All @@ -39,6 +52,7 @@ def test_checkout_step_parse_with_branch_name():
assert step.id == "id"
assert step.branch_name == "test"
assert step.commit_hash is None
assert step.start_point is None


def test_checkout_step_parse_with_commit_hash():
Expand All @@ -49,3 +63,15 @@ def test_checkout_step_parse_with_commit_hash():
assert step.id == "id"
assert step.branch_name is None
assert step.commit_hash == "abc123"
assert step.start_point is None


def test_checkout_step_parse_with_start_point():
step = CheckoutStep.parse("n", "d", "id", {"branch-name": "test", "start-point": "HEAD~1"})
assert isinstance(step, CheckoutStep)
assert step.name == "n"
assert step.description == "d"
assert step.id == "id"
assert step.branch_name == "test"
assert step.commit_hash is None
assert step.start_point == "HEAD~1"