Commit a workspace

WarningContent review status

This page contains unreviewed content created with large language model (LLM) tools.

Committing a workspace merges its overlay into its parent and closes the workspace. The parent may be the base dataset or another workspace, although the current bitsio implementation supports writes only in workspaces opened directly from the base dataset.

1 Commit

import numpy as np
from bitsio import BITSZarrDataset

ds = BITSZarrDataset.open("ds000114.bits", read_only=False)
ds.open_workspace("mask")
exp = ds.at("mask", read_only=False)
key = exp.write_array("sub-01/ses-test/anat/sub-01_ses-test_desc-brain_mask.nii", np.ones((4, 4, 4), "uint8"))
outcome = exp.commit()
print(type(outcome).__name__, outcome.overlay_id.startswith("exp-"))
print(outcome.scopes_appended, outcome.keys_removed, outcome.unfrozen)
print(ds.lineage(key).source_tree, ds.workspaces().markers)
CommitOutcome True
('01',) () False
base ()

exp.commit() closes the workspace addressed by exp. ds.commit(workspace="mask") performs the same operation from the base dataset. A successful commit rebuilds the parent’s indexes and returns a CommitOutcome describing the merged journal scopes and store keys. The keys_removed, keys_retained, and keys_relocated fields report how the storage implementation handled replaced data.

2 Handle a concurrent parent change

Each workspace records the state of its parent when it opens. If two sibling workspaces start from the same state, the first commit changes the parent. A subsequent commit returns CommitRefusal rather than merging against an unexpected parent state:

ds.open_workspace("a")
ds.open_workspace("b")
ds.at("a", read_only=False).write_array("sub-02/ses-test/anat/sub-02_ses-test_desc-a_mask.nii", np.ones((4, 4, 4), "uint8"))
ds.at("b", read_only=False).write_array("sub-03/ses-test/anat/sub-03_ses-test_desc-b_mask.nii", np.ones((4, 4, 4), "uint8"))
ds.commit(workspace="a")
refused = ds.commit(workspace="b")
print(type(refused).__name__)
print(refused.reason.split(" (")[0])
CommitRefusal
base fingerprint drifted since the experiment opened

If the workspace’s changes remain valid against the updated parent, rebase it and retry the commit:

rebased = ds.rebase(workspace="b")
print(rebased.marker_repinned)
print(type(ds.commit(workspace="b")).__name__)
True
CommitOutcome

A conflicting store key can also cause a refusal; store_key identifies the conflict. With the default validate=True, the implementation verifies that the merged parent has the expected fingerprint and raises WorkspaceLifecycleError if validation fails.

3 Nested workspaces

The protocol permits a workspace to have another workspace as its parent. bitsio can read such nested overlays, but it does not yet route writes through multiple overlay layers. A nested workspace therefore opens read-only:

from tensorbids.exceptions import TensorBIDSError

ds.open_workspace("outer")
ds.at("outer", read_only=False).open_workspace("inner")
try:
    ds.at("inner", read_only=False)
except TensorBIDSError as error:
    print(str(error).split(";")[0])
print(sorted(marker.name for marker in ds.workspaces().markers))
workspace 'inner' is nested
['inner', 'outer']

For writable work, open the workspace directly from the base dataset. An outer workspace cannot commit while a child workspace remains open.

4 Commit through bitsfuse

bitsfuse commit ds000114.bits --workspace mask

The bitsfuse command is the CLI integration for writable mounts. It prints the resulting fingerprint on success or the refusal reason on failure, and it does not commit while staged files remain pending.

5 Constraints

  • While a workspace is open, direct writes to its parent raise WorkspaceOpenError.
  • A concurrent commit, discard, or rebase can acquire the workspace lease. Another lifecycle operation then raises WorkspaceLeaseError until the lease is released.
  • Rebase does not merge conflicting content. Inspect a refusal before deciding whether retrying against the new parent is valid.

6 See also