diff --git a/docs/fs-cli.md b/docs/fs-cli.md index bfea69ab..bc7fbbaa 100644 --- a/docs/fs-cli.md +++ b/docs/fs-cli.md @@ -236,9 +236,9 @@ publish = true # FeatureScripts: finalize it at once, and tag it (t They run in name order. Each runs as `fs release` or `fs parts SET --release` would, then its file is deleted and what the release recorded (`fs-studios.json`, a part set's locations and exports) is committed. Every version a release makes -ends its description with the file's name (`[releases/frame-hubs.toml]`), so a file whose release Onshape already has, -whole or partway, stops `fs run-releases` rather than releasing twice: check what it made, finish it by hand, and -delete the file. `fs releases` lists and checks the pending files without calling Onshape. +ends its description with the file's name and the commit that added it (`[releases/frame-hubs.toml 1a2b3c4d5e6f]`), so a +file whose release Onshape already has, whole or partway, stops `fs run-releases` rather than releasing twice: check +what it made, finish it by hand, and delete the file. A file added again later, under the same name, is a new release. `fs releases` lists and checks the pending files without calling Onshape. ### The Onshape workflow diff --git a/fs_cli/git.py b/fs_cli/git.py index 4b3c9315..b5145748 100644 --- a/fs_cli/git.py +++ b/fs_cli/git.py @@ -144,6 +144,12 @@ def commit_with_contents(root: pathlib.Path, file: pathlib.Path, contents: str) return None +def added_in(root: pathlib.Path, file: pathlib.Path) -> str | None: + """The commit that last added a file, or None while it's uncommitted.""" + output = _git(root, "log", "--diff-filter=A", "-1", "--format=%H", "--", str(file)) + return output.strip() if output and output.strip() else None + + def has_tag(root: pathlib.Path, name: str) -> bool: return _git(root, "rev-parse", "--verify", "--quiet", f"refs/tags/{name}") is not None diff --git a/fs_cli/release_files.py b/fs_cli/release_files.py index 0af4fa50..ee21ef54 100644 --- a/fs_cli/release_files.py +++ b/fs_cli/release_files.py @@ -20,6 +20,7 @@ import tomllib from fs_cli.config import Config +from fs_cli.git import added_in from fs_cli.versions import VersionType RELEASES_FOLDER = "releases" @@ -39,6 +40,8 @@ class ReleaseFile: part_set: str | None = None beta: bool = False publish: bool = True + # The commit that added it, so a file of the same name asking for a later release isn't taken for this one + added: str | None = None @property def target(self) -> str: @@ -47,7 +50,8 @@ def target(self) -> str: @property def marker(self) -> str: """Ends the description of what it releases, so a release Onshape already has is found again.""" - return f"[{RELEASES_FOLDER}/{self.path.name}]" + added = f" {self.added[:12]}" if self.added else "" + return f"[{RELEASES_FOLDER}/{self.path.name}{added}]" @property def described(self) -> str: @@ -92,4 +96,5 @@ def load(config: Config, path: pathlib.Path) -> ReleaseFile: data.get("part_set"), bool(data.get("beta", False)), bool(data.get("publish", True)), + added_in(config.root, path), ) diff --git a/tests/fs_cli_test.py b/tests/fs_cli_test.py index c0a1fe00..371f3141 100644 --- a/tests/fs_cli_test.py +++ b/tests/fs_cli_test.py @@ -2343,22 +2343,31 @@ def test_run_releases_makes_each_release_once(repo, onshape, capsys): 'script = "robotFrame"\nbump = "minor"\ndescription = "First"\npublish = false\n' ) commit_all(repo, "Ask for a release") + asked = git_output(repo, "rev-parse", "HEAD").strip()[:12] assert run(onshape, "run-releases", "--no-push-tags") == 0 released = onshape.versions_by_document["back"][-1] - assert (released.name, released.description) == ("Robot frame - v0.1.0", "First [releases/frame.toml]") + assert (released.name, released.description) == ("Robot frame - v0.1.0", f"First [releases/frame.toml {asked}]") assert element_id in onshape.studios("back", released.id) assert not (repo / "releases" / "frame.toml").exists() assert git_output(repo, "log", "-1", "--format=%s") == "Release robotFrame (releases/frame.toml)\n" assert git_output(repo, "status", "--porcelain") == "" - # Asked for again, it's found made rather than made twice - (repo / "releases" / "frame.toml").write_text('script = "robotFrame"\nbump = "minor"\n') - commit_all(repo, "Ask again") + # Run again before its file was deleted, as after a run that stopped partway, it's found made rather than made twice + git(repo, "reset", "-q", "--hard", "HEAD~1") capsys.readouterr() assert run(onshape, "run-releases", "--no-push-tags") == 2 assert "already made Robot frame - v0.1.0" in capsys.readouterr().err + # A file of the same name asking for a later release makes it + git(repo, "rm", "-q", "releases/frame.toml") + commit_all(repo, "Released by hand") + (repo / "releases").mkdir(exist_ok=True) + (repo / "releases" / "frame.toml").write_text('script = "robotFrame"\nbump = "minor"\n') + commit_all(repo, "Ask again") + assert run(onshape, "run-releases", "--no-push-tags") == 0 + assert onshape.versions_by_document["back"][-1].name == "Robot frame - v0.2.0" + def test_branches_prunes_only_its_workspaces_of_branches_gone_from_origin(repo, onshape, tmp_path_factory, capsys): from fs_cli.branches import MADE_BY_FS