Skip to content

Path Syntax

vost paths identify files on disk, files in the repo, or files on a specific branch/tag/commit. This document describes the full syntax, how each command interprets it, and how paths interact with flags.


Quick reference

Syntax Meaning
file.txt Local filesystem path
:file.txt Repo path on the current branch
: Repo root on the current branch
main:file.txt Repo path on the main branch
main: Repo root on the main branch
v1.0:data/file Repo path on the v1.0 tag
main~3:file.txt file.txt three commits back on main
~2:file.txt file.txt two commits back on the current branch
~1: Repo root one commit back on the current branch

Anatomy of a path

A path argument is parsed into three components:

[ref[~N]]:path
Component Optional Description
ref yes Branch name, tag name, or commit hash. Empty string means "current branch" (determined by -b or repo default). Absent (no colon at all) means "local filesystem".
~N yes Ancestor suffix. Walk back N parent commits from the ref. N must be a positive integer.
path yes File or directory path. May be empty (meaning the repo root).

Parsing rules

The parser looks for the first : in the argument and applies these rules in order:

  1. No colon -- the entire argument is a local filesystem path.

  2. Colon at position 0 (:path) -- repo path on the current branch. The ref is the empty string; the path is everything after the colon.

  3. Single letter before colon, followed by / or \ (e.g. C:/Users, D:\data) -- treated as a Windows drive letter. The entire argument is a local path.

  4. / or \ appears anywhere before the colon (e.g. ./local:file, /tmp/my:data) -- the colon is part of a filesystem path. The entire argument is a local path.

  5. Otherwise -- everything before the colon is the ref (possibly with a ~N suffix); everything after is the repo path.

Ancestor syntax

If the ref portion (the part before :) contains a tilde, the last ~ splits it into a base ref and an integer suffix:

main~3:file.txt    ->  ref="main", back=3, path="file.txt"
v1.0~1:data/       ->  ref="v1.0", back=1, path="data/"
~2:file.txt        ->  ref="" (current branch), back=2, path="file.txt"

Constraints:

  • The suffix after ~ must be a positive integer. main~abc:f is an error.
  • ~0 is invalid. Use main:f instead.

Path normalization

Once the repo path is extracted:

  • A leading / is silently stripped: :/foo is equivalent to :foo.
  • .. components are rejected (to prevent traversal above the repo root).
  • An empty path means the repo root.

How each command uses paths

Commands where the : prefix is optional

For ls, cat, rm, and write, arguments are always repo paths, so the colon is optional. vost cat file.txt and vost cat :file.txt are equivalent. However, the colon is required to use explicit ref syntax: vost cat main:file.txt reads from the main branch, while vost cat main:file.txt without the colon would look for a local file named main:file.txt -- but since there's no / or \ before the colon, the parser treats it as a ref:path anyway. In practice, the : matters only for distinguishing ref:path from plain filenames that happen to contain colons (rare on Unix, common on Windows -- handled by rule 3/4).

Commands where : is required

For cp, sync, and mv, source and destination may be either local or repo paths (for cp/sync) or must all be repo paths (for mv). The colon prefix is what distinguishes them.


ls

vost ls [PATH...]

Each PATH is parsed independently. Different paths may reference different branches, tags, or ancestors. Results are coalesced and deduplicated.

vost ls                          # root of current branch
vost ls :src                     # subdirectory
vost ls main:src                 # subdirectory on main
vost ls main:src dev:docs        # from two branches at once
vost ls main: --back 2           # main, two commits back
vost ls -R main~3:src            # recursive listing, 3 back on main
vost ls '*.py'                   # glob expansion

Glob patterns (*, ?, **) are expanded within the resolved repo tree. Quote them to prevent shell expansion.

Implicit root: with no arguments, lists the root of the current branch.


cat

vost cat PATH [PATH...]

Each PATH is parsed independently. File contents are written to stdout in argument order, concatenated with no separator (like UNIX cat).

vost cat :file.txt
vost cat main:config.json
vost cat v1.0:data/file.txt
vost cat main:file.txt --back 1       # one commit back on main
vost cat ~1:file.txt                  # one commit back on current branch

rm

vost rm PATH [PATH...]

Each PATH is parsed. All paths must target the same branch -- you cannot remove files from different branches in a single command. When a path has an explicit ref, it determines the target branch.

vost rm :old.txt                     # remove from current branch
vost rm dev:old.txt                  # remove from dev
vost rm dev:a.txt dev:b.txt          # ok -- same branch
vost rm dev:a.txt main:b.txt         # ERROR -- different branches

Tags and commit hashes are not writable:

vost rm v1.0:file.txt               # ERROR -- cannot write to tag

Accepts glob patterns and -R for directories.


mv

vost mv SOURCE... DEST

All arguments must be repo paths (colon prefix required). All paths must target the same branch — cross-branch moves are not supported.

vost mv :old.txt :new.txt               # rename
vost mv ':*.txt' :archive/              # glob move
vost mv -R :data :backup                # rename directory
vost mv dev:old.txt dev:new.txt         # explicit branch
vost mv main:a.txt dev:b.txt            # ERROR — cross-branch

Tags and ancestors are not writable:

vost mv v1.0:a.txt v1.0:b.txt          # ERROR — cannot write to tag
vost mv :a.txt main~1:b.txt            # ERROR — can't write to history

Accepts glob patterns and -R for directories.


write

vost write PATH

Reads stdin and writes it as a single file. The PATH is parsed:

echo "data" | vost write :file.txt          # current branch
echo "data" | vost write dev:file.txt        # write to dev
echo "data" | vost write ~1:file.txt         # ERROR -- can't write to history

When the path has an explicit ref, it overrides -b:

echo "x" | vost write dev:f.txt -b main     # ERROR -- conflicting
echo "x" | vost write dev:f.txt              # writes to dev

cp

vost cp SOURCE... DEST

The last argument is the destination. Direction is determined by which arguments are repo paths:

Sources Destination Direction
All local repo (:...) disk -> repo
All repo (:...) local repo -> disk
All repo (:...) repo (:...) repo -> repo
Mixed local+repo any ERROR
# disk -> repo
vost cp file.txt :
vost cp file.txt :dest/file.txt
vost cp dir/ :dest

# repo -> disk
vost cp :file.txt ./local.txt
vost cp main:file.txt ./local.txt
vost cp main~1:file.txt ./out/

# repo -> repo (cross-branch copy)
vost cp session:/ :                          # overlay session onto current branch
vost cp main:a.txt :backup/                  # copy file from main to backup/ on current branch
vost cp main~1:a.txt dev:b.txt :dest/        # ERROR -- mixed local+repo (just kidding, all repo is fine)

Per-source ref resolution: each source can specify its own ref. Sources without an explicit ref use the default (set by -b / --ref / snapshot filters, or the repo default branch).

vost cp main:a.txt :backup/a.txt             # source from main
vost cp main:a.txt dev:b.txt :merged/         # two sources, two branches

Destination ref resolution: the destination ref determines which branch is written to.

vost cp file.txt :                            # write to current branch (from -b or default)
vost cp file.txt dev:                         # write to dev branch
vost cp file.txt dev~1:path                   # ERROR -- can't write to history
vost cp file.txt v1.0:path                    # ERROR -- can't write to tag

sync

vost sync PATH                    # 1-arg: local dir -> repo root
vost sync SOURCE DEST            # 2-arg: direction from colon prefix

Sync makes the destination identical to the source (like rsync --delete).

One argument: always a local directory synced to the repo root.

Two arguments: direction is determined by the colon prefix:

Source Destination Direction
local :... disk -> repo
:... or ref:... local repo -> disk
ref:... :... or ref:... repo -> repo
vost sync ./dir                              # dir -> repo root
vost sync ./local :data                      # disk -> repo
vost sync :data ./local                      # repo -> disk
vost sync main:data ./local                  # explicit source ref
vost sync main: dev:                         # repo -> repo (main overwrites dev)
vost sync main~1: dev:                       # from one commit back on main
vost sync main: dev: --back 2                # ERROR if -b is also given

log

vost log [TARGET]

The optional TARGET is parsed with ref:path syntax. It sets the starting ref, ancestor depth, and/or path filter:

vost log                                      # all commits on current branch
vost log main:config.json                     # log of config.json on main
vost log ~3:                                  # starting 3 back on current branch
vost log main~3:                              # starting 3 back on main
vost log config.json                          # plain path (no colon) -> --path filter

The positional TARGET merges with flags. If both specify the same thing, it's an error:

vost log main: --ref main                    # ERROR -- ref specified twice
vost log main~3: --back 1                    # ERROR -- ancestor specified twice
vost log main:file.txt --path file.txt       # ERROR -- path specified twice
vost log main: -b dev                        # ERROR -- branch conflicts with ref

diff

vost diff [BASELINE]

The optional BASELINE selects what to compare against HEAD. Parsed with ref:path syntax:

vost diff                                     # needs --back, --ref, etc.
vost diff ~3:                                 # HEAD vs 3 commits back
vost diff dev:                                # HEAD vs dev branch
vost diff main~2:                             # HEAD vs main, 2 back

Same conflict rules as log.


Interaction with flags

-b / --branch

Sets the default branch for the command. When no explicit ref appears in any path, -b determines which branch is used.

Conflicts with explicit ref:. If any path argument contains an explicit ref (non-empty string before :), -b is an error:

vost ls main: -b dev                         # ERROR
vost cat main:file.txt -b main               # ERROR
vost cp main:a.txt : -b dev                  # ERROR
vost sync main: dev: -b main                 # ERROR

Bare-colon paths (:path) do not conflict with -b:

vost ls :src -b dev                          # ok -- :src uses the -b branch
vost cp :file.txt ./out -b dev               # ok -- reads from dev

--ref

Selects a branch, tag, or commit hash to read from. It is another way to specify the source ref.

Conflicts with explicit ref:. If any path argument has an explicit ref, --ref is an error:

vost ls main: --ref main                     # ERROR
vost cp main:a.txt ./out --ref v1.0          # ERROR

Bare-colon paths do not conflict -- --ref fills in the ref for : paths:

vost cp :a.txt ./out --ref v1.0              # ok -- reads a.txt from v1.0

--back N

Walk back N parent commits from the resolved ref.

Conflicts with ~N. If any path argument uses the ~N ancestor suffix, --back is an error:

vost cat ~1:file.txt --back 1                # ERROR
vost cat main~2:file.txt --back 1            # ERROR

When there is no ~N in any path, --back applies to the resolved ref (whether from explicit ref: or from -b/--ref/default):

vost cat main:file.txt --back 2              # ok -- main, 2 commits back
vost ls main: --back 1                       # ok -- main, 1 commit back

--before, --path, --match

These snapshot filters narrow the commit selection. They always apply to the resolved ref and work alongside --back and explicit ref::

vost ls main: --before 2024-06-01            # main as of June 1
vost cat main:config.json --before 2024-01-01  # config.json on main before Jan 1
vost log main: --match "deploy*"             # deploy commits on main

Multiple different refs with filters

When a command has multiple paths targeting different explicit refs, snapshot filters (--back, --before, --path, --match) are ambiguous -- which ref do they apply to?

vost ls main: dev: --back 1                  # ERROR -- different refs + filter
vost cp main:a.txt dev:b.txt : --back 1      # ERROR -- different source refs + filter

Paths targeting the same explicit ref are fine:

vost ls main:src main:docs --back 1          # ok -- both from main, 1 back

And bare-colon paths mixed with a single explicit ref are unambiguous:

vost ls main:src :docs --back 1              # ok -- main:src gets --back 1,
                                                 #        :docs uses default + --back 1

Summary table

Flag Conflicts with OK with
-b any explicit ref: in path bare :path, no-colon paths
--ref any explicit ref: in path bare :path, no-colon paths
--back any ~N in path explicit ref: (applied to that ref)
--before multiple different explicit refs single ref or same ref
--path multiple different explicit refs single ref or same ref
--match multiple different explicit refs single ref or same ref

Writability

Some commands modify the repo. Only branches are writable -- tags and commit hashes are read-only, and historical commits (via ~N) are immutable.

Path Writable?
:path Yes (current branch)
dev:path Yes (if dev is a branch)
v1.0:path No (tag)
abc123:path No (commit hash)
dev~1:path No (historical commit)
~1:path No (historical commit)

Commands that write (rm, mv, write, cp destination, sync destination) validate this and produce a clear error:

Error: Cannot write to tag 'v1.0' -- use a branch
Error: Cannot write to a historical commit (remove ~N from destination)

Direction detection in cp and sync

cp

The parser examines all arguments:

  1. Parse every argument with the ref:path rules.
  2. All sources local + dest is repo -> disk to repo.
  3. All sources repo + dest is local -> repo to disk.
  4. All sources repo + dest is repo -> repo to repo.
  5. Mixed local and repo sources -> error.
  6. All local (including dest) -> error (no repo involvement).

sync

  1. One argument (no colon): always local directory -> repo root.
  2. Two arguments: parse both. If both are repo paths, it's repo-to-repo. Otherwise, the one with : is the repo side.

Cross-branch workflows

The ref:path syntax enables cross-branch operations without a dedicated transaction system. A typical pattern:

# 1. Create a temporary working branch
vost branch set session

# 2. Make changes on the temporary branch
echo "data" | vost write session:file.txt
vost cp ./new-files/ session:data/

# 3. Copy results to the main branch
vost cp session:/ :

# 4. Clean up
vost branch delete session

Or sync an entire branch:

# Make dev identical to main
vost sync main: dev:

# Sync from a historical snapshot
vost sync main~5: dev:

Ref name restrictions

Branch and tag names must not contain:

Character Reason
: (colon) Used as the ref:path delimiter
(space) Ambiguous in shell arguments
\t (tab) Whitespace
\n (newline) Whitespace

Attempting to create a branch or tag with these characters produces an error. Names with /, ., -, and _ are allowed (e.g. feature/login, release-1.0).