From bfcaca14bbb3a65d183a68904cff63e81f876df9 Mon Sep 17 00:00:00 2001 From: kotborealis Date: Tue, 23 Jun 2026 17:01:13 +0300 Subject: [PATCH] fix: make `workspace reset` accept either `--all` or elements list When using `bst workspace reset --all a.bst b.bst c.bst`, list of elements is ignored and it resets all workspaces, which could be confusing. This patch adds a check that either `--all` is specified or list of elementsis provided. This also fixes the following strange problem: Running `bst workspace reset --all --soft` (with en-dash in `--soft`) causes buildstream to parse it as: reset with `all` flag and with list of elements `--soft`, as it could not parse it as a flag due to en-dash. This leads to bst hard-resetting workspace when for user it seems like he requested the `--soft`-one. Now bst would throw error that either `--all` or element list needs to be provided, and would not reset user changes to workspace. Ref. https://github.com/apache/buildstream/pull/2136 --- src/buildstream/_frontend/cli.py | 11 ++++++-- tests/frontend/workspace.py | 43 ++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 2 deletions(-) diff --git a/src/buildstream/_frontend/cli.py b/src/buildstream/_frontend/cli.py index e9e6ddfb9..02ea0272f 100644 --- a/src/buildstream/_frontend/cli.py +++ b/src/buildstream/_frontend/cli.py @@ -1211,15 +1211,22 @@ def workspace_close(app, remove_dir, all_, elements): is_flag=True, help="Mark workspace to re-execute configuration steps (if any) on next build. Does not alter workspace contents.", ) -@click.option("--all", "-a", "all_", is_flag=True, help="Reset all open workspaces") +@click.option("--all", "-a", "all_", is_flag=True, help="Reset all open workspaces. Cannot be combined with ELEMENTS.") @click.argument("elements", nargs=-1, type=click.Path(readable=False)) @click.pass_obj def workspace_reset(app, soft, all_, elements): - """Reset a workspace to its original state""" + """Reset a workspace to its original state. + + Specify either one or more ELEMENTS, or use -a to target all + workspaces at once. These options are mutually exclusive. + """ # Check that the workspaces in question exist with app.initialized(): + if all_ and elements: + raise AppError("Specify either --all or elements, not both") + if not (all_ or elements): element = app.stream.get_default_target() if element: diff --git a/tests/frontend/workspace.py b/tests/frontend/workspace.py index e243dec54..25384330c 100644 --- a/tests/frontend/workspace.py +++ b/tests/frontend/workspace.py @@ -634,6 +634,49 @@ def test_reset_all(cli, tmpdir, datafiles): assert not os.path.exists(os.path.join(workspace_beta, "etc", "pony.conf")) +@pytest.mark.datafiles(DATA_DIR) +def test_reset_all_with_elements_error(cli, tmpdir, datafiles): + # Open a workspace + tmpdir_alpha = os.path.join(str(tmpdir), "alpha") + element_name, project, workspace = open_workspace(cli, tmpdir_alpha, datafiles, "tar", suffix="-alpha") + + # Modify workspace + shutil.rmtree(os.path.join(workspace, "usr", "bin")) + os.makedirs(os.path.join(workspace, "etc")) + with open(os.path.join(workspace, "etc", "pony.conf"), "w", encoding="utf-8") as f: + f.write("PONY='pink'") + + # Reset with --all and elements should fail + result = cli.run(project=project, args=["workspace", "reset", "--all", element_name]) + result.assert_main_error(ErrorDomain.APP, None) + + # Verify workspace content was NOT modified + assert not os.path.exists(os.path.join(workspace, "usr", "bin", "hello")) + assert os.path.exists(os.path.join(workspace, "etc", "pony.conf")) + + +@pytest.mark.datafiles(DATA_DIR) +def test_reset_en_dash_soft_all_error(cli, tmpdir, datafiles): + # Open a workspace + tmpdir_alpha = os.path.join(str(tmpdir), "alpha") + element_name, project, workspace = open_workspace(cli, tmpdir_alpha, datafiles, "tar", suffix="-alpha") + + # Modify workspace + shutil.rmtree(os.path.join(workspace, "usr", "bin")) + os.makedirs(os.path.join(workspace, "etc")) + with open(os.path.join(workspace, "etc", "pony.conf"), "w", encoding="utf-8") as f: + f.write("PONY='pink'") + + # Reset with en-dash-soft (copy-pasted from docs) should be treated as element + # and combined with --all should error + result = cli.run(project=project, args=["workspace", "reset", "\u2013-soft", "--all"]) + result.assert_main_error(ErrorDomain.APP, None) + + # Verify workspace content was NOT modified + assert not os.path.exists(os.path.join(workspace, "usr", "bin", "hello")) + assert os.path.exists(os.path.join(workspace, "etc", "pony.conf")) + + @pytest.mark.datafiles(DATA_DIR) def test_list(cli, tmpdir, datafiles): element_name, project, workspace = open_workspace(cli, tmpdir, datafiles, "tar")