OXYGENHUB
◆ v0.1.0 oxygen / crates / oxy-core / src / objects.rs
53.5 KiB · Rust · last changed Read a change's metadata without its operations 1 month ago source blame history raw
1
//! Typed objects and their canonical encodings (spec §6, §7, §13.2).
2
//!
3
//! Encoding conventions not fixed by the spec text are defined here:
4
//! - Enum-like items (`TreeOp`, `Edit`) are maps whose key 0 is the variant tag.
5
//! - An `OpRef`/atom reference is `[bytes32, uint]`; `AtomRef::Start` is `0`
6
//!   and `AtomRef::End` is `1`.
7
//! - A `Mode` is an unsigned integer: bit 0 = executable, bit 1 = opaque.
8
//! - `NodeKind`: 0 = file, 1 = dir, 2 = symlink.
9
10
use crate::cbor::{self, Value};
11
use crate::hash::{hash_object, ChangeHash, KeyId, ObjectRef, ObjectType};
12
use crate::ids::{ChangeId, NodeId};
13
use crate::lines::Lines;
14
use std::collections::BTreeMap;
15
16
pub const FORMAT_VERSION: u64 = 1;
17
18
/// The change format writers produce (spec §6.1). Version 2 stores each
19
/// large insert's lines as a text blob holding a CBOR array of them;
20
/// version 3 holds the lines' bytes themselves when splitting them by the
21
/// line rule gives them back. Readers accept 1 to 3, and a change keeps
22
/// the version it was made in, so its hash never moves.
23
pub const CHANGE_VERSION: u64 = 3;
24
25
/// How a text blob holds an insert's lines (spec §7.4).
26
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
27
pub enum TextForm {
28
    /// A CBOR array of the lines' byte strings: version 2.
29
    Cbor,
30
    /// The lines' bytes, split again by the line rule: version 3.
31
    Raw,
32
    /// A CBOR array in a version 3 change, for lines the line rule would
33
    /// not split back (marked by key 7): never for lines it would.
34
    CborFallback,
35
}
36
37
/// In a version 2 change, an insert whose lines encode (as a CBOR array of
38
/// byte strings) to at least this many bytes is stored as a text blob
39
/// holding exactly that encoding; a shorter one is inline (spec §6.1).
40
pub const TEXT_BLOB_MIN: usize = 4096;
41
42
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
43
pub enum ObjectError {
44
    #[error("cbor: {0}")]
45
    Cbor(#[from] cbor::CborError),
46
    #[error("schema: {0}")]
47
    Schema(String),
48
}
49
50
pub(crate) fn schema<T>(msg: impl Into<String>) -> Result<T, ObjectError> {
51
    Err(ObjectError::Schema(msg.into()))
52
}
53
54
pub(crate) type Map = BTreeMap<u64, Value>;
55
56
pub(crate) fn field<'a>(m: &'a Map, k: u64, what: &str) -> Result<&'a Value, ObjectError> {
57
    m.get(&k)
58
        .ok_or_else(|| ObjectError::Schema(format!("missing field {k} ({what})")))
59
}
60
pub(crate) fn uint(v: &Value, what: &str) -> Result<u64, ObjectError> {
61
    v.as_uint()
62
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected unsigned integer")))
63
}
64
pub(crate) fn u32_(v: &Value, what: &str) -> Result<u32, ObjectError> {
65
    u32::try_from(uint(v, what)?).map_err(|_| ObjectError::Schema(format!("{what}: exceeds u32")))
66
}
67
pub(crate) fn text<'a>(v: &'a Value, what: &str) -> Result<&'a str, ObjectError> {
68
    v.as_text()
69
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected text")))
70
}
71
pub(crate) fn array<'a>(v: &'a Value, what: &str) -> Result<&'a [Value], ObjectError> {
72
    v.as_array()
73
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected array")))
74
}
75
pub(crate) fn map<'a>(v: &'a Value, what: &str) -> Result<&'a Map, ObjectError> {
76
    v.as_map()
77
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected map")))
78
}
79
pub(crate) fn bytes<'a>(v: &'a Value, what: &str) -> Result<&'a [u8], ObjectError> {
80
    v.as_bytes()
81
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected bytes")))
82
}
83
pub(crate) fn oref(v: &Value, what: &str) -> Result<ObjectRef, ObjectError> {
84
    ObjectRef::from_slice(bytes(v, what)?)
85
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 32 bytes")))
86
}
87
pub(crate) fn node_id(v: &Value, what: &str) -> Result<NodeId, ObjectError> {
88
    NodeId::from_slice(bytes(v, what)?)
89
        .ok_or_else(|| ObjectError::Schema(format!("{what}: expected 16 bytes")))
90
}
91
pub(crate) fn check_version(m: &Map) -> Result<(), ObjectError> {
92
    let v = uint(field(m, 0, "version")?, "version")?;
93
    if v != FORMAT_VERSION {
94
        return schema(format!("unsupported format version {v}"));
95
    }
96
    Ok(())
97
}
98
99
// ---------------------------------------------------------------- Hlc
100
101
/// Hybrid logical clock (spec §6.4).
102
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
103
pub struct Hlc {
104
    pub wall_ms: u64,
105
    pub logical: u32,
106
}
107
108
impl Hlc {
109
    pub fn now() -> Hlc {
110
        let wall_ms = std::time::SystemTime::now()
111
            .duration_since(std::time::UNIX_EPOCH)
112
            .map(|d| d.as_millis() as u64)
113
            .unwrap_or(0);
114
        Hlc {
115
            wall_ms,
116
            logical: 0,
117
        }
118
    }
119
    pub(crate) fn to_value(self) -> Value {
120
        Value::map([
121
            (0, Value::Uint(self.wall_ms)),
122
            (1, Value::Uint(self.logical as u64)),
123
        ])
124
    }
125
    pub(crate) fn from_value(v: &Value) -> Result<Hlc, ObjectError> {
126
        let m = map(v, "hlc")?;
127
        Ok(Hlc {
128
            wall_ms: uint(field(m, 0, "hlc.wall_ms")?, "hlc.wall_ms")?,
129
            logical: u32_(field(m, 1, "hlc.logical")?, "hlc.logical")?,
130
        })
131
    }
132
}
133
134
// ---------------------------------------------------------------- tree
135
136
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
137
pub enum NodeKind {
138
    File,
139
    Dir,
140
    Symlink,
141
    /// A nested repository pinned to a state (spec §7.8); content is a pin.
142
    Subrepo,
143
}
144
145
impl NodeKind {
146
    pub(crate) fn tag(self) -> u64 {
147
        match self {
148
            NodeKind::File => 0,
149
            NodeKind::Dir => 1,
150
            NodeKind::Symlink => 2,
151
            NodeKind::Subrepo => 3,
152
        }
153
    }
154
    pub(crate) fn from_tag(t: u64) -> Result<NodeKind, ObjectError> {
155
        Ok(match t {
156
            0 => NodeKind::File,
157
            1 => NodeKind::Dir,
158
            2 => NodeKind::Symlink,
159
            3 => NodeKind::Subrepo,
160
            _ => return schema(format!("unknown node kind {t}")),
161
        })
162
    }
163
}
164
165
/// Executable bit plus content mode (spec §7.1, §7.5).
166
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default, PartialOrd, Ord)]
167
pub struct Mode {
168
    pub exec: bool,
169
    pub opaque: bool,
170
}
171
172
impl Mode {
173
    pub const TEXT: Mode = Mode {
174
        exec: false,
175
        opaque: false,
176
    };
177
    pub const OPAQUE: Mode = Mode {
178
        exec: false,
179
        opaque: true,
180
    };
181
    pub(crate) fn bits(self) -> u64 {
182
        (self.exec as u64) | ((self.opaque as u64) << 1)
183
    }
184
    pub(crate) fn from_bits(b: u64) -> Result<Mode, ObjectError> {
185
        if b > 3 {
186
            return schema(format!("unknown mode bits {b}"));
187
        }
188
        Ok(Mode {
189
            exec: b & 1 != 0,
190
            opaque: b & 2 != 0,
191
        })
192
    }
193
}
194
195
/// Reference to a tree operation of some change: the placement or mode atom it
196
/// introduced. `change == ObjectRef::ZERO` means this change.
197
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
198
pub struct OpRef {
199
    pub change: ChangeHash,
200
    pub index: u32,
201
}
202
203
impl OpRef {
204
    fn to_value(self) -> Value {
205
        Value::Array(vec![
206
            Value::Bytes(self.change.0.to_vec()),
207
            Value::Uint(self.index as u64),
208
        ])
209
    }
210
    fn from_value(v: &Value, what: &str) -> Result<OpRef, ObjectError> {
211
        let a = array(v, what)?;
212
        if a.len() != 2 {
213
            return schema(format!("{what}: expected [hash, index]"));
214
        }
215
        Ok(OpRef {
216
            change: oref(&a[0], what)?,
217
            index: u32_(&a[1], what)?,
218
        })
219
    }
220
    /// Replace a self reference with the concrete change hash.
221
    pub fn resolve(self, me: ChangeHash) -> OpRef {
222
        if self.change.is_zero() {
223
            OpRef { change: me, ..self }
224
        } else {
225
            self
226
        }
227
    }
228
}
229
230
#[derive(Debug, Clone, PartialEq, Eq)]
231
pub enum TreeOp {
232
    AddNode {
233
        node: NodeId,
234
        kind: NodeKind,
235
        mode: Mode,
236
        parent: NodeId,
237
        name: String,
238
    },
239
    RemoveNode {
240
        node: NodeId,
241
        placement: OpRef,
242
    },
243
    Move {
244
        node: NodeId,
245
        old: OpRef,
246
        parent: NodeId,
247
        name: String,
248
    },
249
    SetMode {
250
        node: NodeId,
251
        old: OpRef,
252
        mode: Mode,
253
    },
254
}
255
256
/// Names must be valid, non-empty, not `.`/`..`, and free of `/` and NUL (spec §7.1).
257
pub fn validate_name(name: &str) -> Result<(), ObjectError> {
258
    if name.is_empty() || name == "." || name == ".." || name.contains('/') || name.contains('\0') {
259
        return schema(format!("invalid placement name {name:?}"));
260
    }
261
    Ok(())
262
}
263
264
impl TreeOp {
265
    pub fn node(&self) -> NodeId {
266
        match self {
267
            TreeOp::AddNode { node, .. }
268
            | TreeOp::RemoveNode { node, .. }
269
            | TreeOp::Move { node, .. }
270
            | TreeOp::SetMode { node, .. } => *node,
271
        }
272
    }
273
274
    fn to_value(&self) -> Value {
275
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
276
        match self {
277
            TreeOp::AddNode {
278
                node,
279
                kind,
280
                mode,
281
                parent,
282
                name,
283
            } => Value::map([
284
                (0, Value::Uint(0)),
285
                (1, nid(node)),
286
                (2, Value::Uint(kind.tag())),
287
                (3, Value::Uint(mode.bits())),
288
                (4, nid(parent)),
289
                (5, Value::Text(name.clone())),
290
            ]),
291
            TreeOp::RemoveNode { node, placement } => Value::map([
292
                (0, Value::Uint(1)),
293
                (1, nid(node)),
294
                (2, placement.to_value()),
295
            ]),
296
            TreeOp::Move {
297
                node,
298
                old,
299
                parent,
300
                name,
301
            } => Value::map([
302
                (0, Value::Uint(2)),
303
                (1, nid(node)),
304
                (2, old.to_value()),
305
                (4, nid(parent)),
306
                (5, Value::Text(name.clone())),
307
            ]),
308
            TreeOp::SetMode { node, old, mode } => Value::map([
309
                (0, Value::Uint(3)),
310
                (1, nid(node)),
311
                (2, old.to_value()),
312
                (3, Value::Uint(mode.bits())),
313
            ]),
314
        }
315
    }
316
317
    fn from_value(v: &Value) -> Result<TreeOp, ObjectError> {
318
        let m = map(v, "tree_op")?;
319
        let tag = uint(field(m, 0, "tree_op tag")?, "tree_op tag")?;
320
        let node = node_id(field(m, 1, "tree_op.node")?, "tree_op.node")?;
321
        let op = match tag {
322
            0 => {
323
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
324
                validate_name(&name)?;
325
                TreeOp::AddNode {
326
                    node,
327
                    kind: NodeKind::from_tag(uint(field(m, 2, "kind")?, "kind")?)?,
328
                    mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
329
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
330
                    name,
331
                }
332
            }
333
            1 => TreeOp::RemoveNode {
334
                node,
335
                placement: OpRef::from_value(field(m, 2, "placement")?, "placement")?,
336
            },
337
            2 => {
338
                let name = text(field(m, 5, "name")?, "name")?.to_owned();
339
                validate_name(&name)?;
340
                TreeOp::Move {
341
                    node,
342
                    old: OpRef::from_value(field(m, 2, "old")?, "old")?,
343
                    parent: node_id(field(m, 4, "parent")?, "parent")?,
344
                    name,
345
                }
346
            }
347
            3 => TreeOp::SetMode {
348
                node,
349
                old: OpRef::from_value(field(m, 2, "old")?, "old")?,
350
                mode: Mode::from_bits(uint(field(m, 3, "mode")?, "mode")?)?,
351
            },
352
            _ => return schema(format!("unknown tree op tag {tag}")),
353
        };
354
        if node.is_root() {
355
            return schema("tree op on the root node");
356
        }
357
        Ok(op)
358
    }
359
}
360
361
// ---------------------------------------------------------------- edits
362
363
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
364
pub enum AtomRef {
365
    Start,
366
    End,
367
    Atom { change: ChangeHash, index: u32 },
368
}
369
370
impl AtomRef {
371
    fn to_value(self) -> Value {
372
        match self {
373
            AtomRef::Start => Value::Uint(0),
374
            AtomRef::End => Value::Uint(1),
375
            AtomRef::Atom { change, index } => Value::Array(vec![
376
                Value::Bytes(change.0.to_vec()),
377
                Value::Uint(index as u64),
378
            ]),
379
        }
380
    }
381
    fn from_value(v: &Value, what: &str) -> Result<AtomRef, ObjectError> {
382
        match v {
383
            Value::Uint(0) => Ok(AtomRef::Start),
384
            Value::Uint(1) => Ok(AtomRef::End),
385
            Value::Array(a) if a.len() == 2 => Ok(AtomRef::Atom {
386
                change: oref(&a[0], what)?,
387
                index: u32_(&a[1], what)?,
388
            }),
389
            _ => schema(format!("{what}: expected atom reference")),
390
        }
391
    }
392
    pub fn resolve(self, me: ChangeHash) -> AtomRef {
393
        match self {
394
            AtomRef::Atom { change, index } if change.is_zero() => {
395
                AtomRef::Atom { change: me, index }
396
            }
397
            other => other,
398
        }
399
    }
400
}
401
402
/// Half-open range of atoms introduced by one change.
403
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
404
pub struct AtomRange {
405
    pub change: ChangeHash,
406
    pub from: u32,
407
    pub to: u32,
408
}
409
410
impl AtomRange {
411
    fn to_value(self) -> Value {
412
        Value::Array(vec![
413
            Value::Bytes(self.change.0.to_vec()),
414
            Value::Uint(self.from as u64),
415
            Value::Uint(self.to as u64),
416
        ])
417
    }
418
    fn from_value(v: &Value) -> Result<AtomRange, ObjectError> {
419
        let a = array(v, "atom range")?;
420
        if a.len() != 3 {
421
            return schema("atom range: expected [hash, from, to]");
422
        }
423
        let r = AtomRange {
424
            change: oref(&a[0], "range.change")?,
425
            from: u32_(&a[1], "range.from")?,
426
            to: u32_(&a[2], "range.to")?,
427
        };
428
        if r.from >= r.to {
429
            return schema("atom range: empty or inverted");
430
        }
431
        Ok(r)
432
    }
433
    pub fn resolve(self, me: ChangeHash) -> AtomRange {
434
        if self.change.is_zero() {
435
            AtomRange { change: me, ..self }
436
        } else {
437
            self
438
        }
439
    }
440
}
441
442
#[derive(Debug, Clone, PartialEq, Eq)]
443
pub enum Edit {
444
    Insert {
445
        node: NodeId,
446
        after: AtomRef,
447
        before: AtomRef,
448
        lines: crate::lines::Lines,
449
    },
450
    Delete {
451
        node: NodeId,
452
        atoms: Vec<AtomRange>,
453
    },
454
    Replace {
455
        node: NodeId,
456
        old: Option<ObjectRef>,
457
        new: Option<ObjectRef>,
458
    },
459
}
460
461
impl Edit {
462
    pub fn node(&self) -> NodeId {
463
        match self {
464
            Edit::Insert { node, .. } | Edit::Delete { node, .. } | Edit::Replace { node, .. } => {
465
                *node
466
            }
467
        }
468
    }
469
470
    /// Whether this insert is stored as a text blob in a change of `version`.
471
    fn stored_text(&self, version: u64) -> bool {
472
        matches!(self, Edit::Insert { lines, .. }
473
            if version >= 2 && cbor::byte_strings_len(lines) >= TEXT_BLOB_MIN)
474
    }
475
476
    /// The edit's value; `stored` is the text blob of an insert stored as
477
    /// one, and its form (see [`Change::value_with`], which works them out).
478
    fn to_value(&self, stored: Option<(ObjectRef, TextForm)>) -> Value {
479
        let nid = |n: &NodeId| Value::Bytes(n.0.to_vec());
480
        let opt = |o: &Option<ObjectRef>| match o {
481
            Some(r) => Value::Bytes(r.0.to_vec()),
482
            None => Value::Null,
483
        };
484
        match (self, stored) {
485
            (
486
                Edit::Insert {
487
                    node,
488
                    after,
489
                    before,
490
                    lines,
491
                },
492
                Some((r, form)),
493
            ) => {
494
                let mut fields = vec![
495
                    (0, Value::Uint(0)),
496
                    (1, nid(node)),
497
                    (2, after.to_value()),
498
                    (3, before.to_value()),
499
                    (5, Value::Bytes(r.0.to_vec())),
500
                    (6, Value::Uint(lines.len() as u64)),
501
                ];
502
                if form == TextForm::CborFallback {
503
                    fields.push((7, Value::Uint(1)));
504
                }
505
                Value::map(fields)
506
            }
507
            (
508
                Edit::Insert {
509
                    node,
510
                    after,
511
                    before,
512
                    lines,
513
                },
514
                None,
515
            ) => Value::map([
516
                (0, Value::Uint(0)),
517
                (1, nid(node)),
518
                (2, after.to_value()),
519
                (3, before.to_value()),
520
                (
521
                    4,
522
                    Value::Array(lines.iter().map(|l| Value::Bytes(l.to_vec())).collect()),
523
                ),
524
            ]),
525
            (Edit::Delete { node, atoms }, _) => Value::map([
526
                (0, Value::Uint(1)),
527
                (1, nid(node)),
528
                (
529
                    2,
530
                    Value::Array(atoms.iter().map(|r| r.to_value()).collect()),
531
                ),
532
            ]),
533
            (Edit::Replace { node, old, new }, _) => Value::map([
534
                (0, Value::Uint(2)),
535
                (1, nid(node)),
536
                (2, opt(old)),
537
                (3, opt(new)),
538
            ]),
539
        }
540
    }
541
542
    fn from_value(v: &Value, version: u64, resolve: &mut Resolver) -> Result<Edit, ObjectError> {
543
        let m = map(v, "edit")?;
544
        let tag = uint(field(m, 0, "edit tag")?, "edit tag")?;
545
        let node = node_id(field(m, 1, "edit.node")?, "edit.node")?;
546
        let opt = |v: &Value, what: &str| -> Result<Option<ObjectRef>, ObjectError> {
547
            if v.is_null() {
548
                Ok(None)
549
            } else {
550
                Ok(Some(oref(v, what)?))
551
            }
552
        };
553
        Ok(match tag {
554
            0 => {
555
                let lines = match (m.get(&4), m.get(&5)) {
556
                    (Some(inline), None) => {
557
                        let lines = array_lines(inline)?;
558
                        if version >= 2 && cbor::byte_strings_len(&lines) >= TEXT_BLOB_MIN {
559
                            return schema("an insert this large is stored as a text blob in a version 2 change");
560
                        }
561
                        if lines.is_empty() {
562
                            return schema("insert with no lines");
563
                        }
564
                        lines
565
                    }
566
                    (None, Some(text)) if version >= 2 => {
567
                        let text = oref(text, "insert text")?;
568
                        let count = u32_(field(m, 6, "insert line count")?, "insert line count")?;
569
                        if count == 0 {
570
                            return schema("insert with no lines");
571
                        }
572
                        let form = match (version, m.get(&7)) {
573
                            (2, None) => TextForm::Cbor,
574
                            (_, None) => TextForm::Raw,
575
                            (3.., Some(Value::Uint(1))) => TextForm::CborFallback,
576
                            _ => return schema("unknown text blob form"),
577
                        };
578
                        // The resolver checks the count (`text_lines`).
579
                        resolve(&text, count, form)?
580
                    }
581
                    _ => {
582
                        return schema(
583
                            "an insert has either its lines or, in a version 2 change, a text blob",
584
                        )
585
                    }
586
                };
587
                Edit::Insert {
588
                    node,
589
                    after: AtomRef::from_value(field(m, 2, "after")?, "after")?,
590
                    before: AtomRef::from_value(field(m, 3, "before")?, "before")?,
591
                    lines,
592
                }
593
            }
594
            1 => {
595
                let atoms = array(field(m, 2, "atoms")?, "atoms")?
596
                    .iter()
597
                    .map(AtomRange::from_value)
598
                    .collect::<Result<Vec<_>, _>>()?;
599
                if atoms.is_empty() {
600
                    return schema("delete with no atoms");
601
                }
602
                Edit::Delete { node, atoms }
603
            }
604
            2 => Edit::Replace {
605
                node,
606
                old: opt(field(m, 2, "old")?, "old")?,
607
                new: opt(field(m, 3, "new")?, "new")?,
608
            },
609
            _ => return schema(format!("unknown edit tag {tag}")),
610
        })
611
    }
612
}
613
614
// ---------------------------------------------------------------- change
615
616
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
617
pub enum ChangeKind {
618
    Normal,
619
    Resolution,
620
    WorkingCopy,
621
}
622
623
impl ChangeKind {
624
    pub(crate) fn tag(self) -> u64 {
625
        match self {
626
            ChangeKind::Normal => 0,
627
            ChangeKind::Resolution => 1,
628
            ChangeKind::WorkingCopy => 2,
629
        }
630
    }
631
    pub(crate) fn from_tag(t: u64) -> Result<ChangeKind, ObjectError> {
632
        Ok(match t {
633
            0 => ChangeKind::Normal,
634
            1 => ChangeKind::Resolution,
635
            2 => ChangeKind::WorkingCopy,
636
            _ => return schema(format!("unknown change kind {t}")),
637
        })
638
    }
639
}
640
641
/// A change (spec §6.1). `deps` is kept sorted and unique by `normalize`.
642
#[derive(Debug, Clone, PartialEq, Eq)]
643
pub struct Change {
644
    /// The format this change is encoded in: [`CHANGE_VERSION`] for a new
645
    /// one, whatever it was made in for one read back.
646
    pub version: u64,
647
    pub change_id: ChangeId,
648
    pub deps: Vec<ChangeHash>,
649
    pub author: KeyId,
650
    pub hlc: Hlc,
651
    pub message: String,
652
    pub tree_ops: Vec<TreeOp>,
653
    pub edits: Vec<Edit>,
654
    pub kind: ChangeKind,
655
}
656
657
impl Change {
658
    pub fn normalize(&mut self) {
659
        self.deps.sort();
660
        self.deps.dedup();
661
    }
662
663
    pub fn to_value(&self) -> Value {
664
        self.value_with(&mut |_| true)
665
    }
666
667
    /// The text blob of each edit stored as one, `None` for the others;
668
    /// the stored texts go to `texts`, in order, when it is given. Worked
669
    /// out on every core, a bounded batch at a time: chunking and hashing
670
    /// a whole tree's text is most of encoding a change that adds it.
671
    fn stored_texts(&self, sink: &mut TextSink) -> Vec<Option<(ObjectRef, TextForm)>> {
672
        use rayon::prelude::*;
673
        let mut refs: Vec<Option<(ObjectRef, TextForm)>> = vec![None; self.edits.len()];
674
        let stored: Vec<usize> = (0..self.edits.len())
675
            .filter(|i| self.edits[*i].stored_text(self.version))
676
            .collect();
677
        for batch in stored.chunks(256) {
678
            let made: Vec<StoredText> = batch
679
                .par_iter()
680
                .map(|i| {
681
                    let Edit::Insert { lines, .. } = &self.edits[*i] else {
682
                        unreachable!("only inserts are stored as text");
683
                    };
684
                    let form = text_form(self.version, lines);
685
                    let content = match form {
686
                        TextForm::Raw => lines.bytes().to_vec(),
687
                        TextForm::Cbor | TextForm::CborFallback => cbor::encode_byte_strings(lines),
688
                    };
689
                    StoredText {
690
                        blob: Blob::of(&content),
691
                        content,
692
                        form,
693
                    }
694
                })
695
                .collect();
696
            for (i, t) in batch.iter().zip(&made) {
697
                refs[*i] = Some((t.blob.hash(), t.form));
698
            }
699
            if !sink(made) {
700
                break;
701
            }
702
        }
703
        refs
704
    }
705
706
    fn value_with(&self, sink: &mut TextSink) -> Value {
707
        Value::map([
708
            (0, Value::Uint(self.version)),
709
            (1, Value::Bytes(self.change_id.0.to_vec())),
710
            (
711
                2,
712
                Value::Array(
713
                    self.deps
714
                        .iter()
715
                        .map(|d| Value::Bytes(d.0.to_vec()))
716
                        .collect(),
717
                ),
718
            ),
719
            (3, Value::Bytes(self.author.0.to_vec())),
720
            (4, self.hlc.to_value()),
721
            (5, Value::Text(self.message.clone())),
722
            (
723
                6,
724
                Value::Array(self.tree_ops.iter().map(|t| t.to_value()).collect()),
725
            ),
726
            (
727
                7,
728
                Value::Array(
729
                    self.edits
730
                        .iter()
731
                        .zip(self.stored_texts(sink))
732
                        .map(|(e, stored)| e.to_value(stored))
733
                        .collect(),
734
                ),
735
            ),
736
            (8, Value::Uint(self.kind.tag())),
737
            (9, Value::Map(BTreeMap::new())),
738
        ])
739
    }
740
741
    pub fn encode(&self) -> Vec<u8> {
742
        debug_assert!(
743
            self.deps.windows(2).all(|w| w[0] < w[1]),
744
            "deps must be normalized"
745
        );
746
        cbor::encode(&self.to_value())
747
    }
748
749
    /// The encoding and the text blobs its large inserts are stored as,
750
    /// which must be stored with it.
751
    pub fn encode_parts(&self) -> (Vec<u8>, Vec<StoredText>) {
752
        debug_assert!(
753
            self.deps.windows(2).all(|w| w[0] < w[1]),
754
            "deps must be normalized"
755
        );
756
        let mut texts = Vec::new();
757
        let bytes = cbor::encode(&self.value_with(&mut |batch| {
758
            texts.extend(batch);
759
            true
760
        }));
761
        (bytes, texts)
762
    }
763
764
    /// The encoding, handing the stored texts to `store` a batch at a time
765
    /// as they are made, so a change adding a whole tree never holds all
766
    /// its text twice. The first error from `store` is returned.
767
    pub fn encode_streaming<E>(
768
        &self,
769
        mut store: impl FnMut(Vec<StoredText>) -> Result<(), E>,
770
    ) -> Result<Vec<u8>, E> {
771
        debug_assert!(
772
            self.deps.windows(2).all(|w| w[0] < w[1]),
773
            "deps must be normalized"
774
        );
775
        let mut failed = None;
776
        let value = self.value_with(&mut |batch| match store(batch) {
777
            Ok(()) => true,
778
            Err(e) => {
779
                failed = Some(e);
780
                false
781
            }
782
        });
783
        match failed {
784
            Some(e) => Err(e),
785
            None => Ok(cbor::encode(&value)),
786
        }
787
    }
788
789
    /// Whether any insert is stored as a text blob.
790
    pub fn has_stored_text(&self) -> bool {
791
        self.edits.iter().any(|e| e.stored_text(self.version))
792
    }
793
794
    pub fn hash(&self) -> ChangeHash {
795
        hash_object(ObjectType::Change, &self.encode())
796
    }
797
798
    /// Decode a change that stores no text blob; one that does needs
799
    /// [`Change::decode_with`] and something to read them from.
800
    pub fn decode(bytes: &[u8]) -> Result<Change, ObjectError> {
801
        Self::decode_with(bytes, &mut |r, _, _| {
802
            schema(format!(
803
                "text blob {} needs a store to read it from",
804
                r.short(12)
805
            ))
806
        })
807
    }
808
809
    /// Decode a change, reading each stored insert's lines with `resolve`
810
    /// (given the text blob and the line count the insert claims).
811
    pub fn decode_with(bytes: &[u8], resolve: &mut Resolver) -> Result<Change, ObjectError> {
812
        let v = cbor::decode(bytes)?;
813
        Self::from_value_with(&v, resolve)
814
    }
815
816
    pub fn from_value(v: &Value) -> Result<Change, ObjectError> {
817
        Self::from_value_with(v, &mut |r, _, _| {
818
            schema(format!(
819
                "text blob {} needs a store to read it from",
820
                r.short(12)
821
            ))
822
        })
823
    }
824
825
    pub(crate) fn from_value_with(
826
        v: &Value,
827
        resolve: &mut Resolver,
828
    ) -> Result<Change, ObjectError> {
829
        let m = map(v, "change")?;
830
        let version = uint(field(m, 0, "version")?, "version")?;
831
        if !(1..=CHANGE_VERSION).contains(&version) {
832
            return schema(format!("unsupported change format version {version}"));
833
        }
834
        let change_id = ChangeId::from_slice(bytes(field(m, 1, "change_id")?, "change_id")?)
835
            .ok_or_else(|| ObjectError::Schema("change_id: expected 16 bytes".into()))?;
836
        let deps = array(field(m, 2, "deps")?, "deps")?
837
            .iter()
838
            .map(|d| oref(d, "dep"))
839
            .collect::<Result<Vec<_>, _>>()?;
840
        if !deps.windows(2).all(|w| w[0] < w[1]) {
841
            return schema("deps must be sorted and unique");
842
        }
843
        if deps.iter().any(|d| d.is_zero()) {
844
            return schema("deps may not contain the self reference");
845
        }
846
        let extra = map(field(m, 9, "extra")?, "extra")?;
847
        if !extra.is_empty() {
848
            return schema("extra must be empty in format version 1");
849
        }
850
        Ok(Change {
851
            version,
852
            change_id,
853
            deps,
854
            author: oref(field(m, 3, "author")?, "author")?,
855
            hlc: Hlc::from_value(field(m, 4, "hlc")?)?,
856
            message: text(field(m, 5, "message")?, "message")?.to_owned(),
857
            tree_ops: array(field(m, 6, "tree_ops")?, "tree_ops")?
858
                .iter()
859
                .map(TreeOp::from_value)
860
                .collect::<Result<Vec<_>, _>>()?,
861
            edits: array(field(m, 7, "edits")?, "edits")?
862
                .iter()
863
                .map(|e| Edit::from_value(e, version, resolve))
864
                .collect::<Result<Vec<_>, _>>()?,
865
            kind: ChangeKind::from_tag(uint(field(m, 8, "kind")?, "kind")?)?,
866
        })
867
    }
868
}
869
870
/// Reads a stored insert's lines, given the text blob and the line count
871
/// the insert claims; [`text_lines`] reads and checks a blob's content.
872
/// A resolver may also return no lines, leaving a placeholder for a reader
873
/// that fills it in later (history-only states, spec §11).
874
pub type Resolver<'a> = dyn FnMut(&ObjectRef, u32, TextForm) -> Result<Lines, ObjectError> + 'a;
875
876
/// Receives the stored texts of a change being encoded, a batch at a
877
/// time; returns false to stop making more.
878
type TextSink<'a> = dyn FnMut(Vec<StoredText>) -> bool + 'a;
879
880
/// Where a history-only load finds an insert's lines when it builds that
881
/// file's graph (spec §11): it keeps no line until then.
882
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
883
pub enum TextRef {
884
    /// A stored insert: its text blob, line count, and the blob's form.
885
    Blob(ObjectRef, u32, TextForm),
886
    /// An inline insert: the byte range of its lines' array in the
887
    /// encoding of `change`.
888
    Inline {
889
        change: ChangeHash,
890
        start: u32,
891
        end: u32,
892
    },
893
}
894
895
/// The lines of an inline insert from its array's bytes (see
896
/// [`TextRef::Inline`]).
897
pub fn inline_lines(array: &[u8]) -> Result<Lines, ObjectError> {
898
    let v = cbor::decode(array)?;
899
    let lines = array_lines(&v)?;
900
    if lines.is_empty() {
901
        return schema("insert with no lines");
902
    }
903
    Ok(lines)
904
}
905
906
/// The lines of a CBOR array of byte strings, copied once into one buffer.
907
fn array_lines(v: &Value) -> Result<Lines, ObjectError> {
908
    Ok(array(v, "lines")?
909
        .iter()
910
        .map(|l| bytes(l, "line"))
911
        .collect::<Result<Vec<&[u8]>, _>>()?
912
        .into_iter()
913
        .collect())
914
}
915
916
impl Change {
917
    /// Decode a change for a history-only state (spec §11): every insert is
918
    /// left without its lines (an empty placeholder), and the result says
919
    /// where each one's lines are, in order. The inline lines are stepped
920
    /// over, not decoded: for a change that adds a whole tree, most of its
921
    /// bytes. `me` is the change's hash.
922
    pub fn decode_skeleton(
923
        bytes: &[u8],
924
        me: ChangeHash,
925
    ) -> Result<(Change, Vec<TextRef>), ObjectError> {
926
        let mut c = cbor::Cursor::new(bytes);
927
        let (major, n) = c.head()?;
928
        if major != 5 {
929
            return schema("change: expected map");
930
        }
931
        let mut fields = Map::new();
932
        let mut edits = Vec::new();
933
        let mut texts = Vec::new();
934
        let mut version = None;
935
        let mut last: Option<u64> = None;
936
        for _ in 0..n {
937
            let k = map_key(&mut c, &mut last)?;
938
            if k == 7 {
939
                let Some(v) = version else {
940
                    return schema("change: version must come first");
941
                };
942
                let (am, an) = c.head()?;
943
                if am != 4 {
944
                    return schema("edits: expected array");
945
                }
946
                for _ in 0..an {
947
                    edits.push(edit_skeleton(&mut c, v, me, &mut texts)?);
948
                }
949
                fields.insert(7, Value::Array(Vec::new()));
950
            } else {
951
                let val = c.value()?;
952
                if k == 0 {
953
                    version = Some(uint(&val, "version")?);
954
                }
955
                fields.insert(k, val);
956
            }
957
        }
958
        if !c.at_end() {
959
            return Err(ObjectError::Cbor(cbor::CborError::TrailingBytes));
960
        }
961
        let mut change = Change::from_value(&Value::Map(fields))?;
962
        change.edits = edits;
963
        Ok((change, texts))
964
    }
965
}
966
967
impl Change {
968
    /// A change's metadata alone: its tree operations and edits are
969
    /// stepped over, not decoded. For log, signature checks and walks of
970
    /// the dependency graph, which read nothing else.
971
    pub fn decode_meta(bytes: &[u8]) -> Result<crate::state::ChangeMeta, ObjectError> {
972
        let mut c = cbor::Cursor::new(bytes);
973
        let (major, n) = c.head()?;
974
        if major != 5 {
975
            return schema("change: expected map");
976
        }
977
        let mut fields = Map::new();
978
        let mut last: Option<u64> = None;
979
        for _ in 0..n {
980
            let k = map_key(&mut c, &mut last)?;
981
            if k == 6 || k == 7 {
982
                c.skip()?;
983
                fields.insert(k, Value::Array(Vec::new()));
984
            } else {
985
                fields.insert(k, c.value()?);
986
            }
987
        }
988
        if !c.at_end() {
989
            return Err(ObjectError::Cbor(cbor::CborError::TrailingBytes));
990
        }
991
        Ok(crate::state::ChangeMeta::of(&Change::from_value(
992
            &Value::Map(fields),
993
        )?))
994
    }
995
}
996
997
/// A canonical map key: an unsigned integer above the previous one.
998
fn map_key(c: &mut cbor::Cursor, last: &mut Option<u64>) -> Result<u64, ObjectError> {
999
    let (km, k) = c.head()?;
1000
    if km != 0 || last.is_some_and(|l| k <= l) {
1001
        return Err(ObjectError::Cbor(cbor::CborError::BadMapKey));
1002
    }
1003
    *last = Some(k);
1004
    Ok(k)
1005
}
1006
1007
/// One edit of [`Change::decode_skeleton`]: an insert's lines are left out.
1008
fn edit_skeleton(
1009
    c: &mut cbor::Cursor,
1010
    version: u64,
1011
    me: ChangeHash,
1012
    texts: &mut Vec<TextRef>,
1013
) -> Result<Edit, ObjectError> {
1014
    let (major, n) = c.head()?;
1015
    if major != 5 {
1016
        return schema("edit: expected map");
1017
    }
1018
    let mut fields = Map::new();
1019
    let mut inline: Option<(usize, usize, u64)> = None;
1020
    let mut last: Option<u64> = None;
1021
    for _ in 0..n {
1022
        let k = map_key(c, &mut last)?;
1023
        if k == 4 {
1024
            let start = c.pos();
1025
            let (am, count) = c.head()?;
1026
            if am != 4 {
1027
                return schema("lines: expected array");
1028
            }
1029
            for _ in 0..count {
1030
                c.skip()?;
1031
            }
1032
            inline = Some((start, c.pos(), count));
1033
            continue;
1034
        }
1035
        fields.insert(k, c.value()?);
1036
    }
1037
    let Some((start, end, count)) = inline else {
1038
        return Edit::from_value(&Value::Map(fields), version, &mut |text, count, form| {
1039
            texts.push(TextRef::Blob(*text, count, form));
1040
            Ok(Lines::default())
1041
        });
1042
    };
1043
    if uint(field(&fields, 0, "edit tag")?, "edit tag")? != 0 || fields.contains_key(&5) {
1044
        return schema("only an insert has inline lines, and not with a text blob");
1045
    }
1046
    if count == 0 {
1047
        return schema("insert with no lines");
1048
    }
1049
    if version >= 2 && end - start >= TEXT_BLOB_MIN {
1050
        return schema("an insert this large is stored as a text blob in a version 2 change");
1051
    }
1052
    let (Ok(start), Ok(end)) = (u32::try_from(start), u32::try_from(end)) else {
1053
        return schema("change too large");
1054
    };
1055
    texts.push(TextRef::Inline {
1056
        change: me,
1057
        start,
1058
        end,
1059
    });
1060
    Ok(Edit::Insert {
1061
        node: node_id(field(&fields, 1, "edit.node")?, "edit.node")?,
1062
        after: AtomRef::from_value(field(&fields, 2, "after")?, "after")?,
1063
        before: AtomRef::from_value(field(&fields, 3, "before")?, "before")?,
1064
        lines: Lines::default(),
1065
    })
1066
}
1067
1068
/// A large insert's lines as stored (spec §6.1): the blob and its content,
1069
/// the canonical CBOR array of the lines' byte strings.
1070
#[derive(Debug, Clone, PartialEq, Eq)]
1071
pub struct StoredText {
1072
    pub blob: Blob,
1073
    pub content: Vec<u8>,
1074
    pub form: TextForm,
1075
}
1076
1077
/// The form a change of `version` stores these lines in.
1078
fn text_form(version: u64, lines: &Lines) -> TextForm {
1079
    match version {
1080
        ..=2 => TextForm::Cbor,
1081
        _ if lines.is_split_form() => TextForm::Raw,
1082
        _ => TextForm::CborFallback,
1083
    }
1084
}
1085
1086
/// The lines a text blob's `content` holds, checked against the rules for
1087
/// one: canonical, as many as the insert claims, and not short enough to
1088
/// have been inline.
1089
pub fn text_lines(content: &[u8], count: u32, form: TextForm) -> Result<Lines, ObjectError> {
1090
    let lines = match form {
1091
        TextForm::Raw => Lines::split(content),
1092
        TextForm::Cbor | TextForm::CborFallback => array_lines(&cbor::decode(content)?)?,
1093
    };
1094
    // One encoding per change: the size rule and the form are both fixed
1095
    // by the lines.
1096
    if cbor::byte_strings_len(&lines) < TEXT_BLOB_MIN {
1097
        return schema(format!(
1098
            "a text blob of {} bytes is short enough to be inline",
1099
            content.len()
1100
        ));
1101
    }
1102
    if form == TextForm::CborFallback && lines.is_split_form() {
1103
        return schema("these lines are stored as their bytes in a version 3 change");
1104
    }
1105
    if lines.len() != count as usize {
1106
        return schema(format!(
1107
            "a text blob holds {} lines, the insert says {count}",
1108
            lines.len()
1109
        ));
1110
    }
1111
    Ok(lines)
1112
}
1113
1114
// ---------------------------------------------------------------- blob
1115
1116
/// File content as an ordered list of chunks (spec §13.2).
1117
#[derive(Debug, Clone, PartialEq, Eq)]
1118
pub struct Blob {
1119
    pub length: u64,
1120
    pub chunks: Vec<ObjectRef>,
1121
}
1122
1123
impl Blob {
1124
    /// The blob for `content`: its chunks as content-defined chunking cuts
1125
    /// them (spec §13.2).
1126
    pub fn of(content: &[u8]) -> Blob {
1127
        Blob {
1128
            length: content.len() as u64,
1129
            chunks: crate::chunk::chunks(content)
1130
                .into_iter()
1131
                .map(|c| hash_object(ObjectType::Chunk, c))
1132
                .collect(),
1133
        }
1134
    }
1135
1136
    pub fn encode(&self) -> Vec<u8> {
1137
        cbor::encode(&Value::map([
1138
            (0, Value::Uint(FORMAT_VERSION)),
1139
            (1, Value::Uint(self.length)),
1140
            (
1141
                2,
1142
                Value::Array(
1143
                    self.chunks
1144
                        .iter()
1145
                        .map(|c| Value::Bytes(c.0.to_vec()))
1146
                        .collect(),
1147
                ),
1148
            ),
1149
        ]))
1150
    }
1151
    pub fn hash(&self) -> ObjectRef {
1152
        hash_object(ObjectType::Blob, &self.encode())
1153
    }
1154
    pub fn decode(bytes: &[u8]) -> Result<Blob, ObjectError> {
1155
        let v = cbor::decode(bytes)?;
1156
        let m = map(&v, "blob")?;
1157
        check_version(m)?;
1158
        Ok(Blob {
1159
            length: uint(field(m, 1, "length")?, "length")?,
1160
            chunks: array(field(m, 2, "chunks")?, "chunks")?
1161
                .iter()
1162
                .map(|c| oref(c, "chunk"))
1163
                .collect::<Result<Vec<_>, _>>()?,
1164
        })
1165
    }
1166
}
1167
1168
// ---------------------------------------------------------------- checkpoint
1169
1170
/// A materialized state (spec §11): the heads it stands for, how many changes
1171
/// it holds, and a blob with the serialized graphs (`State::checkpoint_payload`).
1172
#[derive(Debug, Clone, PartialEq, Eq)]
1173
pub struct Checkpoint {
1174
    pub heads: Vec<ChangeHash>,
1175
    pub count: u64,
1176
    pub payload: ObjectRef,
1177
}
1178
1179
impl Checkpoint {
1180
    pub fn encode(&self) -> Vec<u8> {
1181
        cbor::encode(&Value::map([
1182
            (0, Value::Uint(FORMAT_VERSION)),
1183
            (
1184
                1,
1185
                Value::Array(
1186
                    self.heads
1187
                        .iter()
1188
                        .map(|h| Value::Bytes(h.0.to_vec()))
1189
                        .collect(),
1190
                ),
1191
            ),
1192
            (2, Value::Uint(self.count)),
1193
            (3, Value::Bytes(self.payload.0.to_vec())),
1194
        ]))
1195
    }
1196
    pub fn hash(&self) -> ObjectRef {
1197
        hash_object(ObjectType::Checkpoint, &self.encode())
1198
    }
1199
    pub fn decode(bytes: &[u8]) -> Result<Checkpoint, ObjectError> {
1200
        let v = cbor::decode(bytes)?;
1201
        let m = map(&v, "checkpoint")?;
1202
        check_version(m)?;
1203
        Ok(Checkpoint {
1204
            heads: array(field(m, 1, "heads")?, "heads")?
1205
                .iter()
1206
                .map(|h| oref(h, "head"))
1207
                .collect::<Result<Vec<_>, _>>()?,
1208
            count: uint(field(m, 2, "count")?, "count")?,
1209
            payload: oref(field(m, 3, "payload")?, "payload")?,
1210
        })
1211
    }
1212
}
1213
1214
// ---------------------------------------------------------------- state
1215
1216
/// A state named by its heads (spec §10.1): what a bookmark or tag points at.
1217
#[derive(Debug, Clone, PartialEq, Eq, Default)]
1218
pub struct StateObject {
1219
    pub heads: Vec<ChangeHash>,
1220
}
1221
1222
impl StateObject {
1223
    pub fn new(mut heads: Vec<ChangeHash>) -> StateObject {
1224
        heads.sort();
1225
        heads.dedup();
1226
        StateObject { heads }
1227
    }
1228
    pub fn encode(&self) -> Vec<u8> {
1229
        debug_assert!(self.heads.windows(2).all(|w| w[0] < w[1]));
1230
        cbor::encode(&Value::map([
1231
            (0, Value::Uint(FORMAT_VERSION)),
1232
            (
1233
                1,
1234
                Value::Array(
1235
                    self.heads
1236
                        .iter()
1237
                        .map(|h| Value::Bytes(h.0.to_vec()))
1238
                        .collect(),
1239
                ),
1240
            ),
1241
        ]))
1242
    }
1243
    pub fn hash(&self) -> ObjectRef {
1244
        hash_object(ObjectType::State, &self.encode())
1245
    }
1246
    pub fn decode(bytes: &[u8]) -> Result<StateObject, ObjectError> {
1247
        let v = cbor::decode(bytes)?;
1248
        let m = map(&v, "state")?;
1249
        check_version(m)?;
1250
        let heads = array(field(m, 1, "heads")?, "heads")?
1251
            .iter()
1252
            .map(|h| oref(h, "head"))
1253
            .collect::<Result<Vec<_>, _>>()?;
1254
        if !heads.windows(2).all(|w| w[0] < w[1]) {
1255
            return schema("state heads must be sorted and unique");
1256
        }
1257
        Ok(StateObject { heads })
1258
    }
1259
}
1260
1261
#[cfg(test)]
1262
pub(crate) mod tests {
1263
    use super::*;
1264
1265
    pub(crate) fn sample_change() -> Change {
1266
        let n = NodeId([7; 16]);
1267
        let dep = ObjectRef([1; 32]);
1268
        let mut c = Change {
1269
            version: crate::objects::CHANGE_VERSION,
1270
            change_id: ChangeId([9; 16]),
1271
            deps: vec![ObjectRef([2; 32]), dep],
1272
            author: ObjectRef([3; 32]),
1273
            hlc: Hlc {
1274
                wall_ms: 1_700_000_000_000,
1275
                logical: 4,
1276
            },
1277
            message: "Add greeting".into(),
1278
            tree_ops: vec![
1279
                TreeOp::AddNode {
1280
                    node: n,
1281
                    kind: NodeKind::File,
1282
                    mode: Mode::TEXT,
1283
                    parent: NodeId::ROOT,
1284
                    name: "hello.txt".into(),
1285
                },
1286
                TreeOp::SetMode {
1287
                    node: n,
1288
                    old: OpRef {
1289
                        change: ObjectRef::ZERO,
1290
                        index: 0,
1291
                    },
1292
                    mode: Mode {
1293
                        exec: true,
1294
                        opaque: false,
1295
                    },
1296
                },
1297
            ],
1298
            edits: vec![
1299
                Edit::Insert {
1300
                    node: n,
1301
                    after: AtomRef::Start,
1302
                    before: AtomRef::End,
1303
                    lines: vec![b"hello\n".to_vec(), b"world\n".to_vec()].into(),
1304
                },
1305
                Edit::Delete {
1306
                    node: n,
1307
                    atoms: vec![AtomRange {
1308
                        change: dep,
1309
                        from: 0,
1310
                        to: 2,
1311
                    }],
1312
                },
1313
                Edit::Replace {
1314
                    node: n,
1315
                    old: None,
1316
                    new: Some(ObjectRef([5; 32])),
1317
                },
1318
            ],
1319
            kind: ChangeKind::Normal,
1320
        };
1321
        c.normalize();
1322
        c
1323
    }
1324
1325
    #[test]
1326
    fn change_round_trips_and_hash_is_stable() {
1327
        let c = sample_change();
1328
        let bytes = c.encode();
1329
        let d = Change::decode(&bytes).unwrap();
1330
        assert_eq!(c, d);
1331
        assert_eq!(c.hash(), d.hash());
1332
        assert_eq!(d.encode(), bytes);
1333
        // Any semantic difference changes the hash.
1334
        let mut e = c.clone();
1335
        e.message.push('!');
1336
        assert_ne!(e.hash(), c.hash());
1337
    }
1338
1339
    #[test]
1340
    fn decode_rejects_bad_changes() {
1341
        let c = sample_change();
1342
        let mut v = c.to_value();
1343
        if let Value::Map(m) = &mut v {
1344
            m.insert(0, Value::Uint(4));
1345
        }
1346
        assert!(
1347
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("version"))
1348
        );
1349
1350
        let mut v = c.to_value();
1351
        if let Value::Map(m) = &mut v {
1352
            m.insert(
1353
                2,
1354
                Value::Array(vec![Value::Bytes(vec![2; 32]), Value::Bytes(vec![1; 32])]),
1355
            );
1356
        }
1357
        assert!(
1358
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("sorted"))
1359
        );
1360
1361
        let mut v = c.to_value();
1362
        if let Value::Map(m) = &mut v {
1363
            m.insert(9, Value::map([(0, Value::Null)]));
1364
        }
1365
        assert!(
1366
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("extra"))
1367
        );
1368
    }
1369
1370
    fn big_lines(n: usize) -> Lines {
1371
        (0..n)
1372
            .map(|i| format!("line {i} of a large insert\n").into_bytes())
1373
            .collect()
1374
    }
1375
1376
    fn with_insert(mut c: Change, lines: Lines) -> Change {
1377
        c.edits[0] = Edit::Insert {
1378
            node: NodeId([7; 16]),
1379
            after: AtomRef::Start,
1380
            before: AtomRef::End,
1381
            lines,
1382
        };
1383
        c
1384
    }
1385
1386
    #[test]
1387
    fn large_inserts_are_stored_as_text_blobs() {
1388
        assert!(
1389
            !sample_change().has_stored_text(),
1390
            "a small insert stays inline"
1391
        );
1392
        let lines = big_lines(500);
1393
        let c = with_insert(sample_change(), lines.clone());
1394
        assert!(c.has_stored_text());
1395
        let (bytes, texts) = c.encode_parts();
1396
        assert_eq!(bytes, c.encode());
1397
        assert!(bytes.len() < 1024, "{}", bytes.len());
1398
        assert_eq!(texts.len(), 1);
1399
        // Version 3: the lines' bytes themselves.
1400
        assert_eq!(texts[0].form, TextForm::Raw);
1401
        assert_eq!(texts[0].content, lines.bytes());
1402
        assert_eq!(texts[0].blob, Blob::of(&texts[0].content));
1403
        assert!(Change::decode(&bytes).is_err(), "reading it needs the text");
1404
        let d = Change::decode_with(&bytes, &mut |r, n, form| {
1405
            assert_eq!(*r, texts[0].blob.hash());
1406
            assert_eq!(form, TextForm::Raw);
1407
            text_lines(&texts[0].content, n, form)
1408
        })
1409
        .unwrap();
1410
        assert_eq!(d, c);
1411
        assert_eq!(d.hash(), c.hash());
1412
        // Version 2 held the same lines as a CBOR array, and still reads.
1413
        let mut v2 = c.clone();
1414
        v2.version = 2;
1415
        let (b2, t2) = v2.encode_parts();
1416
        assert_eq!(t2[0].form, TextForm::Cbor);
1417
        assert_eq!(t2[0].content, cbor::encode_byte_strings(&lines));
1418
        let d2 = Change::decode_with(&b2, &mut |_, n, form| text_lines(&t2[0].content, n, form))
1419
            .unwrap();
1420
        assert_eq!(d2, v2);
1421
        // Lines the line rule would not split back stay a CBOR array,
1422
        // marked; and a marked array of lines that would is refused.
1423
        let odd: Lines = (0..500)
1424
            .map(|i| format!("no newline {i}").into_bytes())
1425
            .collect();
1426
        let c3 = with_insert(sample_change(), odd.clone());
1427
        let (b3, t3) = c3.encode_parts();
1428
        assert_eq!(t3[0].form, TextForm::CborFallback);
1429
        let d3 = Change::decode_with(&b3, &mut |_, n, form| text_lines(&t3[0].content, n, form))
1430
            .unwrap();
1431
        assert_eq!(d3, c3);
1432
        let marked = cbor::encode_byte_strings(&lines);
1433
        assert!(text_lines(&marked, 500, TextForm::CborFallback).is_err());
1434
    }
1435
1436
    #[test]
1437
    fn version_1_changes_keep_their_inline_inserts_and_hashes() {
1438
        let mut c = with_insert(sample_change(), big_lines(500));
1439
        c.version = 1;
1440
        let bytes = c.encode();
1441
        assert!(bytes.len() > TEXT_BLOB_MIN);
1442
        let (again, texts) = c.encode_parts();
1443
        assert!(texts.is_empty());
1444
        assert_eq!(again, bytes);
1445
        let d = Change::decode(&bytes).unwrap();
1446
        assert_eq!(d.version, 1);
1447
        assert_eq!(d.encode(), bytes);
1448
        // The same inline insert in a version 2 change is not canonical.
1449
        let mut v = c.to_value();
1450
        if let Value::Map(m) = &mut v {
1451
            m.insert(0, Value::Uint(2));
1452
        }
1453
        assert!(
1454
            matches!(Change::from_value(&v), Err(ObjectError::Schema(s)) if s.contains("text blob"))
1455
        );
1456
    }
1457
1458
    #[test]
1459
    fn a_skeleton_leaves_every_insert_out_and_says_where_its_lines_are() {
1460
        let mut c = with_insert(sample_change(), big_lines(500));
1461
        c.edits.push(Edit::Insert {
1462
            node: NodeId([8; 16]),
1463
            after: AtomRef::Start,
1464
            before: AtomRef::End,
1465
            lines: vec![b"small\n".to_vec(), b"insert\n".to_vec()].into(),
1466
        });
1467
        let (bytes, stored) = c.encode_parts();
1468
        let me = ObjectRef([4; 32]);
1469
        let (skel, texts) = Change::decode_skeleton(&bytes, me).unwrap();
1470
        assert_eq!(texts.len(), 2);
1471
        let mut filled = skel.clone();
1472
        let mut next = texts.iter();
1473
        for e in filled.edits.iter_mut() {
1474
            if let Edit::Insert { lines, .. } = e {
1475
                assert!(lines.is_empty(), "no line kept");
1476
                *lines = match next.next().unwrap() {
1477
                    TextRef::Blob(r, n, form) => {
1478
                        assert_eq!(*r, stored[0].blob.hash());
1479
                        text_lines(&stored[0].content, *n, *form).unwrap()
1480
                    }
1481
                    TextRef::Inline { change, start, end } => {
1482
                        assert_eq!(*change, me);
1483
                        inline_lines(&bytes[*start as usize..*end as usize]).unwrap()
1484
                    }
1485
                };
1486
            }
1487
        }
1488
        assert_eq!(filled, c, "filled in, it is the change");
1489
        // A version 1 change's large inline insert is fine; in version 2 it is not.
1490
        let mut v1 = with_insert(sample_change(), big_lines(500));
1491
        v1.version = 1;
1492
        assert!(Change::decode_skeleton(&v1.encode(), me).is_ok());
1493
        let mut v = v1.to_value();
1494
        if let Value::Map(m) = &mut v {
1495
            m.insert(0, Value::Uint(2));
1496
        }
1497
        assert!(Change::decode_skeleton(&cbor::encode(&v), me).is_err());
1498
    }
1499
1500
    #[test]
1501
    fn metadata_reads_without_the_operations() {
1502
        let c = with_insert(sample_change(), big_lines(500));
1503
        let (bytes, _) = c.encode_parts();
1504
        let m = Change::decode_meta(&bytes).unwrap();
1505
        assert_eq!(m, crate::state::ChangeMeta::of(&c));
1506
    }
1507
1508
    #[test]
1509
    fn text_blobs_are_checked() {
1510
        let lines = big_lines(500);
1511
        let content = cbor::encode_byte_strings(&lines);
1512
        assert_eq!(content.len(), cbor::byte_strings_len(&lines));
1513
        assert_eq!(
1514
            content,
1515
            cbor::encode(&Value::Array(
1516
                lines.iter().map(|l| Value::Bytes(l.to_vec())).collect()
1517
            ))
1518
        );
1519
        assert_eq!(text_lines(&content, 500, TextForm::Cbor).unwrap(), lines);
1520
        assert!(text_lines(&content, 499, TextForm::Cbor).is_err());
1521
        assert_eq!(
1522
            text_lines(lines.bytes(), 500, TextForm::Raw).unwrap(),
1523
            lines
1524
        );
1525
        assert!(text_lines(lines.bytes(), 501, TextForm::Raw).is_err());
1526
        let small = cbor::encode_byte_strings(&vec![b"x\n".to_vec()].into());
1527
        assert!(
1528
            text_lines(&small, 1, TextForm::Cbor).is_err(),
1529
            "short enough to be inline"
1530
        );
1531
        assert!(
1532
            text_lines(b"x\n", 1, TextForm::Raw).is_err(),
1533
            "short enough to be inline"
1534
        );
1535
    }
1536
1537
    #[test]
1538
    fn names_are_validated() {
1539
        for bad in ["", ".", "..", "a/b", "a\0b"] {
1540
            assert!(validate_name(bad).is_err(), "{bad:?}");
1541
        }
1542
        assert!(validate_name("Foo.c").is_ok());
1543
        assert!(validate_name("...").is_ok());
1544
    }
1545
1546
    #[test]
1547
    fn mode_and_kind_tags() {
1548
        for m in [
1549
            Mode::TEXT,
1550
            Mode::OPAQUE,
1551
            Mode {
1552
                exec: true,
1553
                opaque: true,
1554
            },
1555
        ] {
1556
            assert_eq!(Mode::from_bits(m.bits()).unwrap(), m);
1557
        }
1558
        assert!(Mode::from_bits(4).is_err());
1559
        for k in [
1560
            NodeKind::File,
1561
            NodeKind::Dir,
1562
            NodeKind::Symlink,
1563
            NodeKind::Subrepo,
1564
        ] {
1565
            assert_eq!(NodeKind::from_tag(k.tag()).unwrap(), k);
1566
        }
1567
    }
1568
1569
    #[test]
1570
    fn self_references_resolve() {
1571
        let me = ObjectRef([8; 32]);
1572
        assert_eq!(
1573
            AtomRef::Atom {
1574
                change: ObjectRef::ZERO,
1575
                index: 3
1576
            }
1577
            .resolve(me),
1578
            AtomRef::Atom {
1579
                change: me,
1580
                index: 3
1581
            }
1582
        );
1583
        assert_eq!(AtomRef::Start.resolve(me), AtomRef::Start);
1584
        let other = ObjectRef([1; 32]);
1585
        assert_eq!(
1586
            OpRef {
1587
                change: other,
1588
                index: 1
1589
            }
1590
            .resolve(me)
1591
            .change,
1592
            other
1593
        );
1594
    }
1595
1596
    #[test]
1597
    fn state_object_round_trip() {
1598
        let st = StateObject::new(vec![
1599
            ObjectRef([2; 32]),
1600
            ObjectRef([1; 32]),
1601
            ObjectRef([2; 32]),
1602
        ]);
1603
        assert_eq!(st.heads, vec![ObjectRef([1; 32]), ObjectRef([2; 32])]);
1604
        assert_eq!(StateObject::decode(&st.encode()).unwrap(), st);
1605
        assert_eq!(StateObject::new(vec![]).heads, vec![]);
1606
        assert_ne!(StateObject::new(vec![]).hash(), st.hash());
1607
    }
1608
1609
    #[test]
1610
    fn blob_round_trip() {
1611
        let b = Blob {
1612
            length: 10,
1613
            chunks: vec![ObjectRef([1; 32]), ObjectRef([2; 32])],
1614
        };
1615
        assert_eq!(Blob::decode(&b.encode()).unwrap(), b);
1616
    }
1617
}