From 99088af3bed40ce5c2e85c542b6fc0256380d61f Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 16:59:20 +0000 Subject: [PATCH 1/9] Initial plan From 82f46cc8c59070a3471a6cfe2c86eff9a43e10f1 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:02:30 +0000 Subject: [PATCH 2/9] feat(submit): add --make-commit auto-commit option Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/__main__.py | 5 ++++ cluv/cli/submit.py | 57 +++++++++++++++++++++++++++++++----- tests/test_submit.py | 69 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+), 7 deletions(-) diff --git a/cluv/__main__.py b/cluv/__main__.py index 1c71424..af6aa1b 100644 --- a/cluv/__main__.py +++ b/cluv/__main__.py @@ -112,6 +112,11 @@ def add_submit_args( formatter_class=rich_argparse.RichHelpFormatter, usage="cluv submit [sbatch-args...] [-- program-args...]", ) + submit_parser.add_argument( + "--make-commit", + action="store_true", + help="Create a local commit with tracked changes before submitting the job.", + ) submit_parser.add_argument( "cluster", metavar="", diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 53d5da1..11c04ca 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -25,6 +25,7 @@ async def submit( job_script: Path, sbatch_args: list[str], program_args: list[str], + make_commit: bool = False, ) -> int | None: """Submit a SLURM job on a remote cluster. @@ -56,8 +57,12 @@ async def submit( ) ``` """ + submit_command = build_submit_command(cluster, job_script, sbatch_args, program_args, make_commit) + # Check git is clean locally (untracked files are fine) and capture current commit hash. - git_commit = ensure_clean_git_state() + git_commit = ensure_clean_git_state( + make_commit=make_commit, launched_job_command=submit_command + ) if cluster == "first": return await submit_first(job_script, sbatch_args, program_args, git_commit) @@ -186,17 +191,55 @@ async def submit_first( return start_job_id -def ensure_clean_git_state() -> str: +def build_submit_command( + cluster: str, + job_script: Path, + sbatch_args: list[str], + program_args: list[str], + make_commit: bool, +) -> str: + """Build the local `cluv submit` command line used to launch the job.""" + command_parts = ["cluv", "submit"] + if make_commit: + command_parts.append("--make-commit") + command_parts.extend([cluster, str(job_script), *sbatch_args]) + if program_args: + command_parts.extend(["--", *program_args]) + return shlex.join(command_parts) + + +def create_submit_commit(launched_job_command: str) -> None: + """Create a commit with tracked changes and include the launched job command in the body.""" + subprocess.run(["git", "add", "-u"], check=True) + subprocess.run( + [ + "git", + "commit", + "-m", + "cluv submit: auto-commit tracked changes", + "-m", + f"Launched job command:\n\n{launched_job_command}", + ], + check=True, + ) + + +def ensure_clean_git_state( + make_commit: bool = False, launched_job_command: str | None = None +) -> str: """ Check git is clean locally and return the current commit hash. """ git_status = subprocess.run(["git", "status", "--porcelain"], capture_output=True, text=True) dirty_lines = [line for line in git_status.stdout.splitlines() if not line.startswith("??")] - if dirty_lines and not (os.environ.get("SKIP_CLEAN_GIT_CHECK", "0") == "1"): - console.print( - "[red]Working directory is dirty. Please commit your changes before submitting.[/red]", - ) - sys.exit(1) + if dirty_lines: + if make_commit: + create_submit_commit(launched_job_command=launched_job_command or "cluv submit") + elif not (os.environ.get("SKIP_CLEAN_GIT_CHECK", "0") == "1"): + console.print( + "[red]Working directory is dirty. Please commit your changes before submitting.[/red]", + ) + sys.exit(1) # In GitHub Actions PR jobs we can be on a detached merge commit that doesn't exist on # the synced remote checkout. Prefer the branch tip commit in that case. diff --git a/tests/test_submit.py b/tests/test_submit.py index 8706ae4..3e98ca6 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -82,6 +82,75 @@ def test_only_override_slurm_vars_with_selected_cluster_vars(self, project_dir: class TestEnsureCleanGitState: + def test_dirty_repo_without_make_commit_exits(self, monkeypatch: pytest.MonkeyPatch) -> None: + def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: + assert kwargs.get("capture_output") is True + assert kwargs.get("text") is True + if command == ["git", "status", "--porcelain"]: + return subprocess.CompletedProcess(command, 0, stdout=" M cluv/cli/submit.py\n", stderr="") + raise AssertionError(f"Unexpected subprocess.run call: {command}") + + monkeypatch.setattr(subprocess, "run", mock_subprocess_run) + + with pytest.raises(SystemExit): + ensure_clean_git_state() + + def test_make_commit_creates_commit_with_tracked_changes_and_command( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + command_calls: list[tuple[list[str], dict]] = [] + + def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: + command_calls.append((command, kwargs)) + if command == ["git", "status", "--porcelain"]: + return subprocess.CompletedProcess( + command, 0, stdout=" M cluv/cli/submit.py\n?? notes.txt\n", stderr="" + ) + if command == ["git", "add", "-u"]: + assert kwargs.get("check") is True + return subprocess.CompletedProcess(command, 0, stdout="", stderr="") + if command[:2] == ["git", "commit"]: + assert kwargs.get("check") is True + assert command[2:4] == ["-m", "cluv submit: auto-commit tracked changes"] + assert command[4] == "-m" + assert ( + command[5] + == "Launched job command:\n\ncluv submit --make-commit mila scripts/job.sh -- --flag" + ) + return subprocess.CompletedProcess(command, 0, stdout="", stderr="") + raise AssertionError(f"Unexpected subprocess.run call: {command}") + + def mock_subprocess_check_output(command: list[str], **kwargs) -> str: + assert kwargs.get("text") is True + if command == ["git", "rev-parse", "--abbrev-ref", "HEAD"]: + return "main\n" + if command == ["git", "rev-parse", "HEAD"]: + return "dddddddddddddddddddddddddddddddddddddddd\n" + raise AssertionError(f"Unexpected subprocess.check_output call: {command}") + + monkeypatch.setattr(subprocess, "run", mock_subprocess_run) + monkeypatch.setattr(subprocess, "check_output", mock_subprocess_check_output) + + assert ( + ensure_clean_git_state( + make_commit=True, + launched_job_command="cluv submit --make-commit mila scripts/job.sh -- --flag", + ) + == "dddddddddddddddddddddddddddddddddddddddd" + ) + assert [call[0] for call in command_calls[:3]] == [ + ["git", "status", "--porcelain"], + ["git", "add", "-u"], + [ + "git", + "commit", + "-m", + "cluv submit: auto-commit tracked changes", + "-m", + "Launched job command:\n\ncluv submit --make-commit mila scripts/job.sh -- --flag", + ], + ] + def test_prefers_branch_tip_in_github_actions_detached_head( self, monkeypatch: pytest.MonkeyPatch ) -> None: From 4f7e771bd2253d4b2083d09543440c72e6bfd99e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:03:23 +0000 Subject: [PATCH 3/9] refactor(submit): tighten make-commit command handling Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 10 ++++++++-- tests/test_submit.py | 15 +++++++++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 11c04ca..40ff6f8 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -57,7 +57,11 @@ async def submit( ) ``` """ - submit_command = build_submit_command(cluster, job_script, sbatch_args, program_args, make_commit) + submit_command: str | None = None + if make_commit: + submit_command = build_submit_command( + cluster, job_script, sbatch_args, program_args, make_commit + ) # Check git is clean locally (untracked files are fine) and capture current commit hash. git_commit = ensure_clean_git_state( @@ -234,7 +238,9 @@ def ensure_clean_git_state( dirty_lines = [line for line in git_status.stdout.splitlines() if not line.startswith("??")] if dirty_lines: if make_commit: - create_submit_commit(launched_job_command=launched_job_command or "cluv submit") + if launched_job_command is None: + raise ValueError("launched_job_command is required when make_commit=True") + create_submit_commit(launched_job_command=launched_job_command) elif not (os.environ.get("SKIP_CLEAN_GIT_CHECK", "0") == "1"): console.print( "[red]Working directory is dirty. Please commit your changes before submitting.[/red]", diff --git a/tests/test_submit.py b/tests/test_submit.py index 3e98ca6..f0f3305 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -151,6 +151,21 @@ def mock_subprocess_check_output(command: list[str], **kwargs) -> str: ], ] + def test_make_commit_without_command_raises_value_error( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: + def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: + assert kwargs.get("capture_output") is True + assert kwargs.get("text") is True + if command == ["git", "status", "--porcelain"]: + return subprocess.CompletedProcess(command, 0, stdout=" M cluv/cli/submit.py\n", stderr="") + raise AssertionError(f"Unexpected subprocess.run call: {command}") + + monkeypatch.setattr(subprocess, "run", mock_subprocess_run) + + with pytest.raises(ValueError, match="launched_job_command is required"): + ensure_clean_git_state(make_commit=True) + def test_prefers_branch_tip_in_github_actions_detached_head( self, monkeypatch: pytest.MonkeyPatch ) -> None: From c2b1bb7d3b2eab4c7904cf2703fd8cc573871b13 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:04:14 +0000 Subject: [PATCH 4/9] chore(submit): address review feedback on make-commit flow Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 12 ++++-------- tests/test_submit.py | 11 +++++------ 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 40ff6f8..8421f24 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -57,15 +57,13 @@ async def submit( ) ``` """ - submit_command: str | None = None + launched_job_command: str | None = None if make_commit: - submit_command = build_submit_command( - cluster, job_script, sbatch_args, program_args, make_commit - ) + launched_job_command = build_submit_command(cluster, job_script, sbatch_args, program_args) # Check git is clean locally (untracked files are fine) and capture current commit hash. git_commit = ensure_clean_git_state( - make_commit=make_commit, launched_job_command=submit_command + make_commit=make_commit, launched_job_command=launched_job_command ) if cluster == "first": @@ -200,12 +198,10 @@ def build_submit_command( job_script: Path, sbatch_args: list[str], program_args: list[str], - make_commit: bool, ) -> str: """Build the local `cluv submit` command line used to launch the job.""" command_parts = ["cluv", "submit"] - if make_commit: - command_parts.append("--make-commit") + command_parts.append("--make-commit") command_parts.extend([cluster, str(job_script), *sbatch_args]) if program_args: command_parts.extend(["--", *program_args]) diff --git a/tests/test_submit.py b/tests/test_submit.py index f0f3305..e7d6cd1 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -98,6 +98,8 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro def test_make_commit_creates_commit_with_tracked_changes_and_command( self, monkeypatch: pytest.MonkeyPatch ) -> None: + launched_job_command = "cluv submit --make-commit mila scripts/job.sh -- --flag" + expected_commit_body = f"Launched job command:\n\n{launched_job_command}" command_calls: list[tuple[list[str], dict]] = [] def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: @@ -113,10 +115,7 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro assert kwargs.get("check") is True assert command[2:4] == ["-m", "cluv submit: auto-commit tracked changes"] assert command[4] == "-m" - assert ( - command[5] - == "Launched job command:\n\ncluv submit --make-commit mila scripts/job.sh -- --flag" - ) + assert command[5] == expected_commit_body return subprocess.CompletedProcess(command, 0, stdout="", stderr="") raise AssertionError(f"Unexpected subprocess.run call: {command}") @@ -134,7 +133,7 @@ def mock_subprocess_check_output(command: list[str], **kwargs) -> str: assert ( ensure_clean_git_state( make_commit=True, - launched_job_command="cluv submit --make-commit mila scripts/job.sh -- --flag", + launched_job_command=launched_job_command, ) == "dddddddddddddddddddddddddddddddddddddddd" ) @@ -147,7 +146,7 @@ def mock_subprocess_check_output(command: list[str], **kwargs) -> str: "-m", "cluv submit: auto-commit tracked changes", "-m", - "Launched job command:\n\ncluv submit --make-commit mila scripts/job.sh -- --flag", + expected_commit_body, ], ] From f76658d9601660db71ff33f0ddd800d8171419da Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:05:05 +0000 Subject: [PATCH 5/9] fix(submit): store launched command without make-commit flag Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 1 - tests/test_submit.py | 13 +++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 8421f24..81c9dc1 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -201,7 +201,6 @@ def build_submit_command( ) -> str: """Build the local `cluv submit` command line used to launch the job.""" command_parts = ["cluv", "submit"] - command_parts.append("--make-commit") command_parts.extend([cluster, str(job_script), *sbatch_args]) if program_args: command_parts.extend(["--", *program_args]) diff --git a/tests/test_submit.py b/tests/test_submit.py index e7d6cd1..bc83e8d 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -2,7 +2,7 @@ import subprocess from pathlib import Path -from cluv.cli.submit import ensure_clean_git_state, get_sbatch_command, get_config +from cluv.cli.submit import build_submit_command, ensure_clean_git_state, get_sbatch_command, get_config import pytest @@ -98,9 +98,18 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro def test_make_commit_creates_commit_with_tracked_changes_and_command( self, monkeypatch: pytest.MonkeyPatch ) -> None: - launched_job_command = "cluv submit --make-commit mila scripts/job.sh -- --flag" + launched_job_command = "cluv submit mila scripts/job.sh -- --flag" expected_commit_body = f"Launched job command:\n\n{launched_job_command}" command_calls: list[tuple[list[str], dict]] = [] + assert ( + build_submit_command( + cluster="mila", + job_script=Path("scripts/job.sh"), + sbatch_args=[], + program_args=["--flag"], + ) + == launched_job_command + ) def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: command_calls.append((command, kwargs)) From efab1eb1890b3f5c3fe66360bb3d1df27d1a7b51 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:05:57 +0000 Subject: [PATCH 6/9] test(submit): clarify command reconstruction coverage Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 6 +++--- tests/test_submit.py | 22 +++++++++++++--------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 81c9dc1..3b3ce66 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -57,9 +57,9 @@ async def submit( ) ``` """ - launched_job_command: str | None = None - if make_commit: - launched_job_command = build_submit_command(cluster, job_script, sbatch_args, program_args) + launched_job_command = ( + build_submit_command(cluster, job_script, sbatch_args, program_args) if make_commit else None + ) # Check git is clean locally (untracked files are fine) and capture current commit hash. git_commit = ensure_clean_git_state( diff --git a/tests/test_submit.py b/tests/test_submit.py index bc83e8d..58c00a3 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -81,6 +81,19 @@ def test_only_override_slurm_vars_with_selected_cluster_vars(self, project_dir: ) +class TestBuildSubmitCommand: + def test_build_submit_command_with_program_args(self) -> None: + assert ( + build_submit_command( + cluster="mila", + job_script=Path("scripts/job.sh"), + sbatch_args=[], + program_args=["--flag"], + ) + == "cluv submit mila scripts/job.sh -- --flag" + ) + + class TestEnsureCleanGitState: def test_dirty_repo_without_make_commit_exits(self, monkeypatch: pytest.MonkeyPatch) -> None: def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: @@ -101,15 +114,6 @@ def test_make_commit_creates_commit_with_tracked_changes_and_command( launched_job_command = "cluv submit mila scripts/job.sh -- --flag" expected_commit_body = f"Launched job command:\n\n{launched_job_command}" command_calls: list[tuple[list[str], dict]] = [] - assert ( - build_submit_command( - cluster="mila", - job_script=Path("scripts/job.sh"), - sbatch_args=[], - program_args=["--flag"], - ) - == launched_job_command - ) def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: command_calls.append((command, kwargs)) From c19e0d5cb5d27129203cd2918f38224750d2ab6c Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:06:59 +0000 Subject: [PATCH 7/9] perf(submit): defer command reconstruction until dirty check Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 19 +++++++++++-------- tests/test_submit.py | 4 ++-- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 3b3ce66..472bdad 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -6,6 +6,7 @@ import subprocess import sys from pathlib import Path +from typing import Callable from cluv.cli.sync import sync from cluv.config import ClusterConfig, find_pyproject, get_config @@ -57,13 +58,14 @@ async def submit( ) ``` """ - launched_job_command = ( - build_submit_command(cluster, job_script, sbatch_args, program_args) if make_commit else None - ) - # Check git is clean locally (untracked files are fine) and capture current commit hash. git_commit = ensure_clean_git_state( - make_commit=make_commit, launched_job_command=launched_job_command + make_commit=make_commit, + launched_job_command_builder=( + (lambda: build_submit_command(cluster, job_script, sbatch_args, program_args)) + if make_commit + else None + ), ) if cluster == "first": @@ -224,7 +226,7 @@ def create_submit_commit(launched_job_command: str) -> None: def ensure_clean_git_state( - make_commit: bool = False, launched_job_command: str | None = None + make_commit: bool = False, launched_job_command_builder: Callable[[], str] | None = None ) -> str: """ Check git is clean locally and return the current commit hash. @@ -233,8 +235,9 @@ def ensure_clean_git_state( dirty_lines = [line for line in git_status.stdout.splitlines() if not line.startswith("??")] if dirty_lines: if make_commit: - if launched_job_command is None: - raise ValueError("launched_job_command is required when make_commit=True") + if launched_job_command_builder is None: + raise ValueError("launched_job_command_builder is required when make_commit=True") + launched_job_command = launched_job_command_builder() create_submit_commit(launched_job_command=launched_job_command) elif not (os.environ.get("SKIP_CLEAN_GIT_CHECK", "0") == "1"): console.print( diff --git a/tests/test_submit.py b/tests/test_submit.py index 58c00a3..33b71cb 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -146,7 +146,7 @@ def mock_subprocess_check_output(command: list[str], **kwargs) -> str: assert ( ensure_clean_git_state( make_commit=True, - launched_job_command=launched_job_command, + launched_job_command_builder=lambda: launched_job_command, ) == "dddddddddddddddddddddddddddddddddddddddd" ) @@ -175,7 +175,7 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro monkeypatch.setattr(subprocess, "run", mock_subprocess_run) - with pytest.raises(ValueError, match="launched_job_command is required"): + with pytest.raises(ValueError, match="launched_job_command_builder is required"): ensure_clean_git_state(make_commit=True) def test_prefers_branch_tip_in_github_actions_detached_head( From dfbce420a4be20af2f4142f3939da64b70240105 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:07:41 +0000 Subject: [PATCH 8/9] test(submit): improve ensure_clean_git_state test naming Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- tests/test_submit.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/tests/test_submit.py b/tests/test_submit.py index 33b71cb..de1d2dd 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -95,7 +95,9 @@ def test_build_submit_command_with_program_args(self) -> None: class TestEnsureCleanGitState: - def test_dirty_repo_without_make_commit_exits(self, monkeypatch: pytest.MonkeyPatch) -> None: + def test_ensure_clean_git_state_exits_when_repo_dirty_without_make_commit( + self, monkeypatch: pytest.MonkeyPatch + ) -> None: def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: assert kwargs.get("capture_output") is True assert kwargs.get("text") is True @@ -108,7 +110,7 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro with pytest.raises(SystemExit): ensure_clean_git_state() - def test_make_commit_creates_commit_with_tracked_changes_and_command( + def test_ensure_clean_git_state_creates_commit_when_make_commit_enabled( self, monkeypatch: pytest.MonkeyPatch ) -> None: launched_job_command = "cluv submit mila scripts/job.sh -- --flag" @@ -163,7 +165,7 @@ def mock_subprocess_check_output(command: list[str], **kwargs) -> str: ], ] - def test_make_commit_without_command_raises_value_error( + def test_ensure_clean_git_state_raises_when_make_commit_without_builder( self, monkeypatch: pytest.MonkeyPatch ) -> None: def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedProcess[str]: From 5b049f9f9ae60d28e8676245e2211a178092ff63 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Tue, 12 May 2026 17:08:38 +0000 Subject: [PATCH 9/9] docs(submit): document make-commit arg and improve commit error output Agent-Logs-Url: https://github.com/mila-iqia/cluv/sessions/664c8c45-1991-4e4a-b007-e1247faca220 Co-authored-by: lebrice <13387299+lebrice@users.noreply.github.com> --- cluv/cli/submit.py | 35 +++++++++++++++++++++++------------ tests/test_submit.py | 4 ++++ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/cluv/cli/submit.py b/cluv/cli/submit.py index 472bdad..65c191f 100644 --- a/cluv/cli/submit.py +++ b/cluv/cli/submit.py @@ -43,6 +43,7 @@ async def submit( job_script: Path to the job script to submit, relative to the project root. sbatch_args: List of additional flags to pass to `sbatch`. program_args: List of arguments to pass to the job script, for example `["python", "main.py"]`. + make_commit: If True, automatically create a local commit with tracked changes before submitting. Returns: The job ID of the submitted job or None if the sbatch command fails. @@ -211,18 +212,28 @@ def build_submit_command( def create_submit_commit(launched_job_command: str) -> None: """Create a commit with tracked changes and include the launched job command in the body.""" - subprocess.run(["git", "add", "-u"], check=True) - subprocess.run( - [ - "git", - "commit", - "-m", - "cluv submit: auto-commit tracked changes", - "-m", - f"Launched job command:\n\n{launched_job_command}", - ], - check=True, - ) + try: + subprocess.run(["git", "add", "-u"], check=True, capture_output=True, text=True) + subprocess.run( + [ + "git", + "commit", + "-m", + "cluv submit: auto-commit tracked changes", + "-m", + f"Launched job command:\n\n{launched_job_command}", + ], + check=True, + capture_output=True, + text=True, + ) + except subprocess.CalledProcessError as err: + error_text = (err.stderr or err.stdout or str(err)).strip() + console.print( + "[red]Failed to create automatic submit commit before job submission:[/red] " + f"{error_text}" + ) + raise def ensure_clean_git_state( diff --git a/tests/test_submit.py b/tests/test_submit.py index de1d2dd..926918d 100644 --- a/tests/test_submit.py +++ b/tests/test_submit.py @@ -125,9 +125,13 @@ def mock_subprocess_run(command: list[str], **kwargs) -> subprocess.CompletedPro ) if command == ["git", "add", "-u"]: assert kwargs.get("check") is True + assert kwargs.get("capture_output") is True + assert kwargs.get("text") is True return subprocess.CompletedProcess(command, 0, stdout="", stderr="") if command[:2] == ["git", "commit"]: assert kwargs.get("check") is True + assert kwargs.get("capture_output") is True + assert kwargs.get("text") is True assert command[2:4] == ["-m", "cluv submit: auto-commit tracked changes"] assert command[4] == "-m" assert command[5] == expected_commit_body