Skip to content

3. Stages & binding

A stage is a named data category inside a checkout, with a remote layout. A unit is one addressable item within a stage — a subdirectory, a whole directory, or a single file, depending on the stage's sync_by mode.

First, some data to manage. Two survey plots and a summary table:

mkdir -p data/raw/plot-01 data/raw/plot-02 data/processed

cat > data/raw/plot-01/notes.txt <<'EOF'
plot: 01
canopy: dense
mist: heavy at dawn
EOF
cat > data/raw/plot-01/temps.csv <<'EOF'
day,temp_c
1,11.2
2,9.8
EOF
cat > data/raw/plot-02/notes.txt <<'EOF'
plot: 02
canopy: open
mist: light
EOF
cat > data/raw/plot-02/temps.csv <<'EOF'
day,temp_c
1,12.4
2,10.1
EOF
cat > data/processed/summary.csv <<'EOF'
plot,mean_temp_c
plot-01,10.5
plot-02,11.25
EOF

Register and bind in one step

forest add STAGE PATH registers the stage and binds it to a local path:

forest add raw ./data/raw
forest add processed ./data/processed --sync-by directory
Added stage 'raw' -> data/raw
Added stage 'processed' -> data/processed

--sync-by picks how units are discovered inside the bound path:

sync_by mode A unit is… In our example
subdirectory (default) each subdirectory under the bound path rawplot-01, plot-02
directory the bound directory as a whole processed → one unit
file an individual file

So raw has two units that sync independently, while processed always moves as one.

The shared/local split

Look at what those two commands wrote. The shared half — stage names and their remote layout — went into the committed forest.yaml:

.forest/checkouts/survey/forest.yaml
project: survey
stages:
  raw:
    remote_path: survey/raw
  processed:
    remote_path: survey/processed
    sync_by: directory

The local half — where those stages live on your disk — went into the gitignored local.yaml:

.forest/checkouts/survey/local.yaml
active_remote: null
stage_paths:
  raw: data/raw
  processed: data/processed

Bindings are per-machine precisely because of this split: teammates share the same stages but can keep them at different local paths.

Rebinding and unbinding

forest bind with no arguments lists the current bindings:

forest bind
processed   data/processed
raw data/raw

unbind removes a local binding (the stage itself stays registered), and bind STAGE PATH points an existing stage somewhere else — or back:

forest unbind processed
forest bind processed ./data/processed
Unbound stage 'processed' from data/processed.
Bound stage 'processed' -> data/processed

Unbound stages warn and skip

Bare push/pull/status/diff cover every bound stage — unbound stages emit a warning and are skipped. Add --all when you want the command to fail unless all stages are bound. You'll see both in chapter 5.

Recap: forest add = register (shared) + bind (local) in one step; sync_by decides what a unit is; bindings are per-machine.

Next: somewhere for the data to go — remotes.