Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,95 @@
# Changelog

## Unreleased

### Added

- **Archived objects are a typed error.** A read the store refuses with
S3's `InvalidObjectState` (an object in GLACIER or DEEP_ARCHIVE that was
not restored) fails with `bigstore::backend::Error::Archived { key }`,
and in folder mode with the new `bigstore::folder::Error::Archived { key }`
(pull, log, and the history reads of push and status). Any other 403
stays the permission error it was. object_store exposes neither S3's
error code nor the storage class as data, so the code is read from the
error response it reports.
- **Folder history is a graph of parent-linked versions.** Each record is
a DVC 3 `.dvc` pointer plus `meta: {bigstore: {parents, writer, time}}`,
stored create-once as `bigstore-history/<key>/<parents>/<id>.dvc`
(`<id>`: the first 32 hex characters of the SHA-256 of the record;
`<parents>`: `root`, or the parent ids sorted and `+`-joined, at most 8).
One listing gives the graph; the latest version is the only *head* (a
record no other follows). A record whose bytes do not hash to its name's
id, or whose parents differ from its name's, is refused as a bad record.
- **Push checks the output's base**, the version its `.dvc` records in
`meta: {bigstore: {base: <id>}}`, before uploading anything. An output
equal to the only head adopts it and publishes nothing (this also
repairs a crash between the record and the `.dvc`). A `.dvc` without a
base that records the only head's content counts as based on it.
Otherwise push is refused with the new
`folder::Error::StaleBase { base, heads }` if there is no `.dvc` while
history has versions, or the base is not the only head;
and with `folder::Error::Diverged { heads }` if the history has forked,
unless `PushOptions::resolve` is `Resolve::Merge` (CLI:
`folder push --resolve merge`) and the base is one of the heads. The
record is published before the `.dvc`, which is written last. History is
then listed again: pushes that raced from the same base are reported in
`PushReport::forked_with` (a CLI warning).
- `PushOptions::writer` (the host name by default) is recorded in each
version; `PushReport::version` is the version the output now is.
- **Pull from history writes `<into>.dvc`** with the pulled version as its
base, last, once every file is in place; a cancelled or failed pull
leaves the old `.dvc`. A `.dvc` there that push would refuse to replace
is refused first. Pulling an older version makes the next push of it
`StaleBase`. `Selector::Latest` in a forked history is `Diverged`.
- `SyncState::Stale { base, head }` and `SyncState::Diverged { heads }`;
`folder status` prints both. `folder log` prints each version's parents
and writer, and marks heads, fork points and merges.
- `HistoryRecord` has `id`, `parents` and `writer` fields;
`HistoryRecord::output_id()` is the content id (manifest id or md5).
- `bigstore::dvc::{RecordId, BigstoreMeta}`, and `DvcPointer::meta`: the
strict parser accepts exactly bigstore's two `meta:` shapes, and still
refuses any other `meta:` (so push never replaces a `.dvc` carrying
someone else's annotations).

### Changed

- **Breaking (library): `bigstore::backend::Store`** replaces the `Backend`
enum and the free functions `from_config`, `exists`, `put_bytes`,
`get_bytes`, `list`, `upload`, `download` and `download_verified`. Build
one with `Store::open(&cfg)` or `Store::from_object_store(client)`; its
methods are `head(key)` (size and modification time, `None` if absent;
replaces `exists`), `get(key, limit)`, `put(key, bytes)`,
`put_file(key, path)` (streamed, multipart above 10 MiB, aborted on a
failed read), `list(prefix)` (sorted `Listed { key, size, modified }`)
and `download_verified(key, expected, dir)`. The unverified `download` is
gone: the LFS adapter and `git bigstore pull` both download through
`download_verified`. `transfer::Remote::store` is now a `&Store`.
- rclone remotes are listed with `rclone lsjson` instead of `rclone lsf`,
for object sizes and times; the keys listed are unchanged.
- **Breaking (folder history): a hard cutover per history key.** 0.3 reads
0.2's `<time>-<content id>.dvc` records as a straight line in time order
(each with the id of its file name) and continues it, but never writes
that form; 0.2 clients do not see 0.3 records, so a 0.2 writer left on a
key forks it. Upgrade every writer of a key together. A `.dvc` 0.2 wrote
has no base; if it records the latest version's content, its output
counts as based on that version, so a changed output's first push
follows it. If it records an older version, the push is `StaleBase`.
- **Breaking (library):** `HistoryRecord::id()` is gone: `id` is now the
record id, and `Selector::Id` (`--at <hex>`) matches record ids as
`folder log` prints them. Content ids still select 0.2 records (from the
md5 in their names), but not 0.3 ones. `Selector::AtOrBefore` uses
each record's own time and fetches every record. `SyncState::Diverged {
latest }` (changed locally and remotely) is now `Stale { base, head }`.
`PushOptions::cancel` stops a push before its record is published (it
was: before its `.dvc` was written).

### Fixed

- A read whose body ends before the object's length now fails with
`incomplete download of <key> (<got>/<expected> bytes)`. Before, a small
object (a `.dir` manifest or history record) was returned truncated, and
a download failed only as an integrity check.

## 0.2.1 — 2026-09-30

### Added
Expand Down
15 changes: 13 additions & 2 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ anyhow = "1"
chrono = { version = "0.4", default-features = false, features = ["clock", "std"] }
clap = { version = "4", features = ["derive", "env"], optional = true }
futures = "0.3.32"
gethostname = "1.1.0"
globset = "0.4.18"
hex = "0.4"
indicatif = { version = "0.18.4", optional = true }
Expand Down
Loading
Loading