Skip to main content

pijul_core/
change.rs

1use crate::HashSet;
2use std::collections::BTreeSet;
3
4use crate::pristine::*;
5use crate::text_encoding::Encoding;
6use jiff::Timestamp;
7
8#[cfg(feature = "zstd")]
9use std::io::Write;
10
11#[cfg(feature = "text-changes")]
12mod parse;
13#[cfg(feature = "text-changes")]
14mod printable;
15#[cfg(feature = "text-changes")]
16mod text_changes;
17#[cfg(feature = "text-changes")]
18pub use parse::*; // for testing
19#[cfg(feature = "text-changes")]
20pub use printable::*; // for testing
21#[cfg(feature = "text-changes")]
22pub use text_changes::{TextDeError, TextSerError, WriteChangeLine, get_change_contents};
23
24#[cfg(feature = "zstd")]
25mod change_file;
26
27#[cfg(feature = "zstd")]
28pub use change_file::*;
29
30pub mod noenc;
31
32#[derive(Debug, Error)]
33pub enum ChangeError {
34    #[error("Version mismatch: got {}", got)]
35    VersionMismatch { got: u64 },
36    #[error(transparent)]
37    Io(#[from] std::io::Error),
38    #[error("while retrieving {:?}: {}", hash, err)]
39    IoHash {
40        err: std::io::Error,
41        hash: crate::pristine::Hash,
42    },
43    #[error(transparent)]
44    Bincode(#[from] bincode::Error),
45
46    #[cfg(feature = "zstd")]
47    #[error(transparent)]
48    Zstd(#[from] zstd_seekable::Error),
49
50    #[error(transparent)]
51    TomlDe(#[from] toml::de::Error),
52    #[error(transparent)]
53    TomlSer(#[from] toml::ser::Error),
54    #[error(transparent)]
55    Json(#[from] serde_json::Error),
56    #[error("Missing contents for change {:?}", hash)]
57    MissingContents { hash: crate::pristine::Hash },
58    #[error("Change hash mismatch, claimed {:?}, computed {:?}", claimed, computed)]
59    ChangeHashMismatch {
60        claimed: crate::pristine::Hash,
61        computed: crate::pristine::Hash,
62    },
63    #[error(
64        "Change contents hash mismatch, claimed {:?}, computed {:?}",
65        claimed,
66        computed
67    )]
68    ContentsHashMismatch {
69        claimed: crate::pristine::Hash,
70        computed: crate::pristine::Hash,
71    },
72}
73
74#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq, Hash)]
75pub enum Atom<Change> {
76    NewVertex(NewVertex<Change>),
77    EdgeMap(EdgeMap<Change>),
78}
79
80#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
81pub struct NewVertex<Change> {
82    pub up_context: Vec<Position<Change>>,
83    pub down_context: Vec<Position<Change>>,
84    pub flag: EdgeFlags,
85    pub start: ChangePosition,
86    pub end: ChangePosition,
87    pub inode: Position<Change>,
88}
89
90#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
91pub struct EdgeMap<Change> {
92    pub edges: Vec<NewEdge<Change>>,
93    pub inode: Position<Change>,
94}
95
96#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, Hash)]
97pub struct NewEdge<Change> {
98    pub previous: EdgeFlags,
99    pub flag: EdgeFlags,
100    /// The origin of the edge, i.e. if a vertex split is needed, the
101    /// left-hand side of the split will include `from.pos`. This
102    /// means that splitting vertex `[a, b[` to apply this edge
103    /// modification will yield vertices `[a, from.pos+1[` and
104    /// `[from.pos+1, b[`.
105    pub from: Position<Change>,
106    /// The destination of the edge, i.e. the last byte affected by
107    /// this change.
108    pub to: Vertex<Change>,
109    /// The change that introduced the previous version of the edge
110    /// (the one being replaced by this `NewEdge`).
111    pub introduced_by: Change,
112}
113
114impl<T: Clone> NewEdge<T> {
115    pub(crate) fn reverse(&self, introduced_by: T) -> Self {
116        NewEdge {
117            previous: self.flag,
118            flag: self.previous,
119            from: self.from.clone(),
120            to: self.to.clone(),
121            introduced_by,
122        }
123    }
124}
125
126/// The header of a change contains all the metadata about a change
127/// (but not the actual contents of a change).
128#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)]
129pub struct ChangeHeader_<Author> {
130    pub message: String,
131    pub description: Option<String>,
132    pub timestamp: Timestamp,
133    pub authors: Vec<Author>,
134}
135
136/// The header of a change contains all the metadata about a change
137/// (but not the actual contents of a change).
138pub type ChangeHeader = ChangeHeader_<Author>;
139
140impl Default for ChangeHeader {
141    fn default() -> Self {
142        ChangeHeader {
143            message: String::new(),
144            description: None,
145            timestamp: Timestamp::now(),
146            authors: Vec::new(),
147        }
148    }
149}
150
151#[derive(Clone, Debug, PartialEq)]
152pub struct LocalChange<Hunk, Author> {
153    pub offsets: Offsets,
154    pub hashed: Hashed<Hunk, Author>,
155    /// unhashed TOML extra contents.
156    pub unhashed: Option<serde_json::Value>,
157    /// The contents.
158    pub contents: Vec<u8>,
159}
160
161pub type LocalChangeLocal = LocalChange<Hunk<Option<Hash>, Local>, Author>;
162
163impl std::ops::Deref for LocalChange<Hunk<Option<Hash>, Local>, Author> {
164    type Target = Hashed<Hunk<Option<Hash>, Local>, Author>;
165    fn deref(&self) -> &Self::Target {
166        &self.hashed
167    }
168}
169
170impl std::ops::DerefMut for LocalChange<Hunk<Option<Hash>, Local>, Author> {
171    fn deref_mut(&mut self) -> &mut Self::Target {
172        &mut self.hashed
173    }
174}
175
176#[derive(Debug, PartialEq, Clone, Serialize, Deserialize)]
177pub struct Author(pub std::collections::BTreeMap<String, String>);
178
179// Beware of changes in the version, tags also use that.
180pub const VERSION: u64 = 6;
181pub const VERSION_NOENC: u64 = 4;
182
183#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184pub struct Hashed<Hunk, Author> {
185    /// Version, again (in order to hash it).
186    pub version: u64,
187    /// Header part, containing the metadata.
188    pub header: ChangeHeader_<Author>,
189    /// The dependencies of this change.
190    pub dependencies: Vec<Hash>,
191    /// Extra known "context" changes to recover from deleted contexts.
192    pub extra_known: Vec<Hash>,
193    /// Some space to write application-specific data.
194    pub metadata: Vec<u8>,
195    /// The changes, without the contents.
196    pub changes: Vec<Hunk>,
197    /// Hash of the contents, so that the "contents" field is
198    /// verifiable independently from the actions in this change.
199    pub contents_hash: Hash, // TODO: in future format changes, put this field at the beginning.
200}
201
202pub type Change = LocalChange<Hunk<Option<Hash>, Local>, Author>;
203
204pub type DepsAndKnown = (Vec<Hash>, Vec<Hash>);
205
206pub fn dependencies<
207    'a,
208    Local: 'a,
209    I: Iterator<Item = &'a Hunk<Option<Hash>, Local>>,
210    T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
211>(
212    txn: &T,
213    channel: &T::Channel,
214    changes: I,
215) -> Result<DepsAndKnown, MakeChangeError<T>> {
216    let mut deps = BTreeSet::new();
217    let mut zombie_deps = BTreeSet::new();
218    for ch in changes.flat_map(|r| r.iter()) {
219        debug!("ch {:?}", ch);
220        match *ch {
221            Atom::NewVertex(NewVertex {
222                ref up_context,
223                ref down_context,
224                ..
225            }) => {
226                for up in up_context.iter().chain(down_context.iter()) {
227                    match up.change {
228                        None | Some(Hash::None) => {}
229                        Some(ref dep) => {
230                            deps.insert(*dep);
231                        }
232                    }
233                }
234            }
235            Atom::EdgeMap(EdgeMap { ref edges, .. }) => {
236                for e in edges {
237                    assert!(!e.flag.contains(EdgeFlags::PARENT));
238                    assert!(e.introduced_by != Some(Hash::None));
239                    if let Some(p) = e.from.change {
240                        deps.insert(p);
241                    }
242                    if let Some(p) = e.introduced_by {
243                        deps.insert(p);
244                    }
245                    if let Some(p) = e.to.change {
246                        deps.insert(p);
247                    }
248                    add_zombie_deps_from(txn, txn.graph(channel), &mut zombie_deps, e.from)?;
249                    add_zombie_deps_to(txn, txn.graph(channel), &mut zombie_deps, e.to)?
250                }
251            }
252        }
253    }
254    debug!("minimize");
255    let deps_ = minimize_deps(txn, channel, &deps)?;
256    for d in deps_.iter() {
257        zombie_deps.remove(d);
258    }
259    let mut deps: Vec<Hash> = deps.into_iter().collect();
260    deps.sort_by(|a, b| {
261        let a = txn.get_internal(&a.into()).unwrap().unwrap();
262        let b = txn.get_internal(&b.into()).unwrap().unwrap();
263        txn.get_changeset(txn.changes(channel), a)
264            .unwrap()
265            .cmp(&txn.get_changeset(txn.changes(channel), b).unwrap())
266    });
267    let mut zombie_deps: Vec<Hash> = zombie_deps.into_iter().collect();
268    zombie_deps.sort_by(|a, b| {
269        let a = txn.get_internal(&a.into()).unwrap().unwrap();
270        let b = txn.get_internal(&b.into()).unwrap().unwrap();
271        txn.get_changeset(txn.changes(channel), a)
272            .unwrap()
273            .cmp(&txn.get_changeset(txn.changes(channel), b).unwrap())
274    });
275    Ok((deps, zombie_deps))
276}
277
278pub fn full_dependencies<T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>>(
279    txn: &T,
280    channel: &ChannelRef<T>,
281) -> Result<DepsAndKnown, TxnErr<T::DepsError>> {
282    let mut deps = BTreeSet::new();
283    let channel = channel.read();
284    for x in changeid_log(txn, &channel, L64(0))? {
285        let (_, p) = x?;
286        let h = txn.get_external(&p.a).optional()?.unwrap();
287        deps.insert(h.into());
288    }
289    let deps = minimize_deps(txn, &channel, &deps)?;
290    Ok((deps, Vec::new()))
291}
292
293fn add_zombie_deps_from<T: GraphTxnT>(
294    txn: &T,
295    channel: &T::Graph,
296    zombie_deps: &mut BTreeSet<Hash>,
297    e_from: Position<Option<Hash>>,
298) -> Result<(), MakeChangeError<T>> {
299    let e_from = if let Some(p) = e_from.change {
300        Position {
301            change: *txn.get_internal(&p.into())?.unwrap(),
302            pos: e_from.pos,
303        }
304    } else {
305        return Ok(());
306    };
307    let from = match txn.find_block_end(channel, e_from) {
308        Ok(from) => from,
309        _ => {
310            return Err(MakeChangeError::InvalidChange);
311        }
312    };
313    for edge in iter_adj_all(txn, channel, *from)? {
314        let edge = edge?;
315        debug!("add_zombie_deps_from {:?}", edge);
316        if let Some(ext) = txn.get_external(&edge.introduced_by()).optional()? {
317            let ext: Hash = ext.into();
318            if let Hash::None = ext {
319            } else {
320                zombie_deps.insert(ext);
321            }
322        }
323        if let Some(ext) = txn.get_external(&edge.dest().change).optional()? {
324            let ext: Hash = ext.into();
325            if let Hash::None = ext {
326            } else {
327                zombie_deps.insert(ext);
328            }
329        }
330    }
331    Ok(())
332}
333
334fn add_zombie_deps_to<T: GraphTxnT>(
335    txn: &T,
336    channel: &T::Graph,
337    zombie_deps: &mut BTreeSet<Hash>,
338    e_to: Vertex<Option<Hash>>,
339) -> Result<(), MakeChangeError<T>> {
340    debug!("e_to {:?}", e_to);
341    let to_pos = if let Some(p) = e_to.change {
342        Position {
343            change: *txn.get_internal(&p.into())?.unwrap(),
344            pos: e_to.start,
345        }
346    } else {
347        return Ok(());
348    };
349    let mut to = match txn.find_block(channel, to_pos) {
350        Ok(to) => to,
351        _ => {
352            return Err(MakeChangeError::InvalidChange);
353        }
354    };
355    debug!("to_pos {:?} to {:?}", to_pos, to);
356    let mut h = HashSet::new();
357    loop {
358        if !h.insert(*to) {
359            error!("seen twice: {:?}", to);
360            break;
361        }
362        for edge in iter_adj_all(txn, channel, *to)? {
363            debug!("add_zombie_deps_to {:?}", edge);
364            let edge = edge?;
365            if let Some(ext) = txn.get_external(&edge.introduced_by()).optional()? {
366                let ext = ext.into();
367                if let Hash::None = ext {
368                } else {
369                    zombie_deps.insert(ext);
370                }
371            }
372            if let Some(ext) = txn.get_external(&edge.dest().change).optional()? {
373                let ext = ext.into();
374                if let Hash::None = ext {
375                } else {
376                    zombie_deps.insert(ext);
377                }
378            }
379        }
380        debug!("to {:?} {:?}", to, e_to);
381        if to.end >= e_to.end {
382            break;
383        }
384        to = txn.find_block(channel, to.end_pos()).unwrap();
385    }
386    Ok(())
387}
388
389fn minimize_deps<T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>>(
390    txn: &T,
391    channel: &T::Channel,
392    deps: &BTreeSet<Hash>,
393) -> Result<Vec<Hash>, TxnErr<T::DepsError>> {
394    let mut min_time = u64::MAX;
395    let mut internal_deps = Vec::new();
396    let mut internal_deps_ = HashSet::default();
397    for h in deps.iter() {
398        if let Hash::None = h {
399            continue;
400        }
401        debug!("h = {:?}", h);
402        let id = txn.get_internal(&h.into())?.unwrap();
403        debug!("id = {:?}", id);
404        if let Some(time) = txn.get_changeset(txn.changes(channel), id)? {
405            let time = u64::from_le(time.0);
406            debug!("time = {:?}", time);
407            min_time = min_time.min(time);
408        }
409        internal_deps.push((id, true));
410        internal_deps_.insert(id);
411    }
412    internal_deps.sort_by_key(|a| a.1);
413    let mut visited = HashSet::default();
414    while let Some((id, is_root)) = internal_deps.pop() {
415        if is_root {
416            if !internal_deps_.contains(&id) {
417                continue;
418            }
419        } else if internal_deps_.remove(&id) {
420            debug!("removing dep {:?}", id);
421        }
422        if !visited.insert(id) {
423            continue;
424        }
425        let mut cursor = txn.iter_dep(id)?;
426        while let Some(x) = txn.cursor_dep_next(&mut cursor.cursor)? {
427            let (id0, dep) = x;
428            trace!("minimize loop = {:?} {:?}", id0, dep);
429            if id0 < id {
430                continue;
431            } else if id0 > id {
432                break;
433            }
434            let time = if let Some(time) = txn.get_changeset(txn.changes(channel), dep)? {
435                time
436            } else {
437                panic!(
438                    "not found in channel {:?}: id = {:?} depends on {:?}",
439                    txn.name(channel),
440                    id,
441                    dep
442                );
443            };
444            let time = u64::from_le(time.0);
445            trace!("time = {:?}", time);
446            if time >= min_time {
447                internal_deps.push((dep, false))
448            }
449        }
450    }
451    Ok(internal_deps_
452        .into_iter()
453        .map(|id| txn.get_external(id).unwrap().into())
454        .collect())
455}
456
457impl Change {
458    pub fn knows(&self, hash: &Hash) -> bool {
459        self.extra_known.contains(hash) || self.dependencies.contains(hash)
460    }
461
462    pub fn has_edge(
463        &self,
464        hash: Hash,
465        from: Position<Option<Hash>>,
466        to: Position<Option<Hash>>,
467        flags: crate::pristine::EdgeFlags,
468    ) -> bool {
469        debug!("has_edge: {:?} {:?} {:?} {:?}", hash, from, to, flags);
470        for change_ in self.changes.iter() {
471            for change_ in change_.iter() {
472                match change_ {
473                    Atom::NewVertex(n) => {
474                        debug!("has_edge: {:?}", n);
475                        if from.change == Some(hash) && from.pos >= n.start && from.pos <= n.end {
476                            if to.change == Some(hash) {
477                                // internal
478                                return flags | EdgeFlags::FOLDER
479                                    == EdgeFlags::BLOCK | EdgeFlags::FOLDER;
480                            } else {
481                                // down context
482                                if n.down_context.contains(&to) {
483                                    return flags.is_empty();
484                                } else {
485                                    return false;
486                                }
487                            }
488                        } else if to.change == Some(hash) && to.pos >= n.start && to.pos <= n.end {
489                            // up context
490                            if n.up_context.contains(&from) {
491                                return flags | EdgeFlags::FOLDER
492                                    == EdgeFlags::BLOCK | EdgeFlags::FOLDER;
493                            } else {
494                                return false;
495                            }
496                        }
497                    }
498                    Atom::EdgeMap(e) => {
499                        debug!("has_edge: {:?}", e);
500                        if e.edges
501                            .iter()
502                            .any(|e| e.from == from && e.to.start_pos() == to && e.flag == flags)
503                        {
504                            return true;
505                        }
506                    }
507                }
508            }
509        }
510        debug!("not found");
511        false
512    }
513}
514
515impl<A> Atom<A> {
516    pub fn as_newvertex(&self) -> &NewVertex<A> {
517        if let Atom::NewVertex(n) = self {
518            n
519        } else {
520            panic!("Not a NewVertex")
521        }
522    }
523}
524
525impl Atom<Option<Hash>> {
526    pub fn inode(&self) -> Position<Option<Hash>> {
527        match self {
528            Atom::NewVertex(n) => n.inode,
529            Atom::EdgeMap(n) => n.inode,
530        }
531    }
532
533    pub fn inverse(&self, hash: &Hash) -> Self {
534        match *self {
535            Atom::NewVertex(NewVertex {
536                ref up_context,
537                flag,
538                start,
539                end,
540                ref inode,
541                ..
542            }) => {
543                let mut edges = Vec::new();
544                for up in up_context {
545                    edges.push(NewEdge {
546                        previous: flag,
547                        flag: flag | EdgeFlags::DELETED,
548                        from: Position {
549                            change: Some(if let Some(ref h) = up.change {
550                                *h
551                            } else {
552                                *hash
553                            }),
554                            pos: up.pos,
555                        },
556                        to: Vertex {
557                            change: Some(*hash),
558                            start,
559                            end,
560                        },
561                        introduced_by: Some(*hash),
562                    })
563                }
564                Atom::EdgeMap(EdgeMap {
565                    edges,
566                    inode: Position {
567                        change: Some(if let Some(p) = inode.change { p } else { *hash }),
568                        pos: inode.pos,
569                    },
570                })
571            }
572            Atom::EdgeMap(EdgeMap {
573                ref edges,
574                ref inode,
575            }) => Atom::EdgeMap(EdgeMap {
576                inode: Position {
577                    change: Some(if let Some(p) = inode.change { p } else { *hash }),
578                    pos: inode.pos,
579                },
580                edges: edges
581                    .iter()
582                    .map(|e| {
583                        let mut e = e.clone();
584                        e.introduced_by = Some(*hash);
585                        std::mem::swap(&mut e.flag, &mut e.previous);
586                        e
587                    })
588                    .collect(),
589            }),
590        }
591    }
592}
593
594impl EdgeMap<Option<Hash>> {
595    fn concat(mut self, e: EdgeMap<Option<Hash>>) -> Self {
596        assert_eq!(self.inode, e.inode);
597        self.edges.extend(e.edges);
598        EdgeMap {
599            inode: self.inode,
600            edges: self.edges,
601        }
602    }
603}
604
605impl<L: Clone> Hunk<Option<Hash>, L> {
606    pub fn inverse(&self, hash: &Hash) -> Self {
607        match self {
608            Hunk::AddRoot { name, inode } => Hunk::DelRoot {
609                name: name.inverse(hash),
610                inode: inode.inverse(hash),
611            },
612            Hunk::DelRoot { name, inode } => Hunk::AddRoot {
613                name: name.inverse(hash),
614                inode: inode.inverse(hash),
615            },
616            Hunk::FileMove { del, add, path } => Hunk::FileMove {
617                del: add.inverse(hash),
618                add: del.inverse(hash),
619                path: path.clone(),
620            },
621            Hunk::FileDel {
622                del,
623                contents,
624                path,
625                encoding,
626            } => Hunk::FileUndel {
627                undel: del.inverse(hash),
628                contents: contents.as_ref().map(|c| c.inverse(hash)),
629                path: path.clone(),
630                encoding: encoding.clone(),
631            },
632            Hunk::FileUndel {
633                undel,
634                contents,
635                path,
636                encoding,
637            } => Hunk::FileDel {
638                del: undel.inverse(hash),
639                contents: contents.as_ref().map(|c| c.inverse(hash)),
640                path: path.clone(),
641                encoding: encoding.clone(),
642            },
643            Hunk::FileAdd {
644                add_name,
645                add_inode,
646                contents,
647                path,
648                encoding,
649            } => {
650                let del = match (add_name.inverse(hash), add_inode.inverse(hash)) {
651                    (Atom::EdgeMap(e0), Atom::EdgeMap(e1)) => Atom::EdgeMap(e0.concat(e1)),
652                    _ => unreachable!(),
653                };
654                Hunk::FileDel {
655                    del,
656                    contents: contents.as_ref().map(|c| c.inverse(hash)),
657                    path: path.clone(),
658                    encoding: encoding.clone(),
659                }
660            }
661            Hunk::SolveNameConflict { name, path } => Hunk::UnsolveNameConflict {
662                name: name.inverse(hash),
663                path: path.clone(),
664            },
665            Hunk::UnsolveNameConflict { name, path } => Hunk::SolveNameConflict {
666                name: name.inverse(hash),
667                path: path.clone(),
668            },
669            Hunk::Edit {
670                change,
671                local,
672                encoding,
673            } => Hunk::Edit {
674                change: change.inverse(hash),
675                local: local.clone(),
676                encoding: encoding.clone(),
677            },
678            Hunk::Replacement {
679                change,
680                replacement,
681                local,
682                encoding,
683            } => Hunk::Replacement {
684                change: replacement.inverse(hash),
685                replacement: change.inverse(hash),
686                local: local.clone(),
687                encoding: encoding.clone(),
688            },
689            Hunk::SolveOrderConflict { change, local } => Hunk::UnsolveOrderConflict {
690                change: change.inverse(hash),
691                local: local.clone(),
692            },
693            Hunk::UnsolveOrderConflict { change, local } => Hunk::SolveOrderConflict {
694                change: change.inverse(hash),
695                local: local.clone(),
696            },
697            Hunk::ResurrectZombies {
698                change,
699                local,
700                encoding,
701            } => Hunk::Edit {
702                change: change.inverse(hash),
703                local: local.clone(),
704                encoding: encoding.clone(),
705            },
706        }
707    }
708}
709
710impl Change {
711    pub fn inverse(&self, hash: &Hash, header: ChangeHeader, metadata: Vec<u8>) -> Self {
712        let dependencies = vec![*hash];
713        let contents_hash = Hasher::default().finish();
714        Change {
715            offsets: Offsets::default(),
716            hashed: Hashed {
717                version: VERSION,
718                header,
719                dependencies,
720                extra_known: self.extra_known.clone(),
721                metadata,
722                changes: self.changes.iter().map(|r| r.inverse(hash)).collect(),
723                contents_hash,
724            },
725            contents: Vec::new(),
726            unhashed: None,
727        }
728    }
729}
730
731#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
732pub struct LocalByte {
733    pub path: String,
734    pub line: usize,
735    pub inode: Inode,
736    pub byte: Option<usize>,
737}
738
739#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
740pub struct Local {
741    pub path: String,
742    pub line: usize,
743}
744
745pub type Hunk<Hash, Local> = BaseHunk<Atom<Hash>, Local>;
746
747#[derive(Debug, PartialEq, Eq, Clone, Serialize, Deserialize)]
748pub enum BaseHunk<Atom, Local> {
749    FileMove {
750        del: Atom,
751        add: Atom,
752        path: String,
753    },
754    FileDel {
755        del: Atom,
756        contents: Option<Atom>,
757        path: String,
758        encoding: Option<Encoding>,
759    },
760    FileUndel {
761        undel: Atom,
762        contents: Option<Atom>,
763        path: String,
764        encoding: Option<Encoding>,
765    },
766    FileAdd {
767        add_name: Atom,
768        add_inode: Atom,
769        contents: Option<Atom>,
770        path: String,
771        encoding: Option<Encoding>,
772    },
773    SolveNameConflict {
774        name: Atom,
775        path: String,
776    },
777    UnsolveNameConflict {
778        name: Atom,
779        path: String,
780    },
781    Edit {
782        change: Atom,
783        local: Local,
784        encoding: Option<Encoding>,
785    },
786    Replacement {
787        change: Atom,
788        replacement: Atom,
789        local: Local,
790        encoding: Option<Encoding>,
791    },
792    SolveOrderConflict {
793        change: Atom,
794        local: Local,
795    },
796    UnsolveOrderConflict {
797        change: Atom,
798        local: Local,
799    },
800    ResurrectZombies {
801        change: Atom,
802        local: Local,
803        encoding: Option<Encoding>,
804    },
805    AddRoot {
806        name: Atom,
807        inode: Atom,
808    },
809    DelRoot {
810        name: Atom,
811        inode: Atom,
812    },
813}
814
815#[doc(hidden)]
816pub struct HunkIter<R, C> {
817    rec: Option<R>,
818    extra: Option<C>,
819    extra2: Option<C>,
820}
821
822impl<Context, Local> IntoIterator for Hunk<Context, Local> {
823    type IntoIter = HunkIter<Hunk<Context, Local>, Atom<Context>>;
824    type Item = Atom<Context>;
825    fn into_iter(self) -> Self::IntoIter {
826        HunkIter {
827            rec: Some(self),
828            extra: None,
829            extra2: None,
830        }
831    }
832}
833
834impl<Context, Local> Hunk<Context, Local> {
835    pub fn iter(&self) -> HunkIter<&Hunk<Context, Local>, &Atom<Context>> {
836        HunkIter {
837            rec: Some(self),
838            extra: None,
839            extra2: None,
840        }
841    }
842    pub fn rev_iter(&self) -> RevHunkIter<&Hunk<Context, Local>, &Atom<Context>> {
843        RevHunkIter {
844            rec: Some(self),
845            extra: None,
846            extra2: None,
847        }
848    }
849}
850
851impl<Context, Local> Iterator for HunkIter<Hunk<Context, Local>, Atom<Context>> {
852    type Item = Atom<Context>;
853    fn next(&mut self) -> Option<Self::Item> {
854        match self.extra.take() {
855            Some(extra) => Some(extra),
856            _ => match self.extra2.take() {
857                Some(extra) => Some(extra),
858                _ => match self.rec.take() {
859                    Some(rec) => match rec {
860                        Hunk::FileMove { del, add, .. } => {
861                            self.extra = Some(add);
862                            Some(del)
863                        }
864                        Hunk::FileDel { del, contents, .. } => {
865                            self.extra = contents;
866                            Some(del)
867                        }
868                        Hunk::FileUndel {
869                            undel, contents, ..
870                        } => {
871                            self.extra = contents;
872                            Some(undel)
873                        }
874                        Hunk::FileAdd {
875                            add_name,
876                            add_inode,
877                            contents,
878                            ..
879                        } => {
880                            self.extra = Some(add_inode);
881                            self.extra2 = contents;
882                            Some(add_name)
883                        }
884                        Hunk::SolveNameConflict { name, .. } => Some(name),
885                        Hunk::UnsolveNameConflict { name, .. } => Some(name),
886                        Hunk::Edit { change, .. } => Some(change),
887                        Hunk::Replacement {
888                            change,
889                            replacement,
890                            ..
891                        } => {
892                            self.extra = Some(replacement);
893                            Some(change)
894                        }
895                        Hunk::SolveOrderConflict { change, .. } => Some(change),
896                        Hunk::UnsolveOrderConflict { change, .. } => Some(change),
897                        Hunk::ResurrectZombies { change, .. } => Some(change),
898                        Hunk::AddRoot { inode, name } | Hunk::DelRoot { inode, name } => {
899                            self.extra = Some(inode);
900                            Some(name)
901                        }
902                    },
903                    _ => None,
904                },
905            },
906        }
907    }
908}
909
910impl<'a, Context, Local> Iterator for HunkIter<&'a Hunk<Context, Local>, &'a Atom<Context>> {
911    type Item = &'a Atom<Context>;
912    fn next(&mut self) -> Option<Self::Item> {
913        if let Some(extra) = self.extra.take() {
914            Some(extra)
915        } else if let Some(extra) = self.extra2.take() {
916            Some(extra)
917        } else if let Some(rec) = self.rec.take() {
918            match *rec {
919                Hunk::FileMove {
920                    ref del, ref add, ..
921                } => {
922                    self.extra = Some(add);
923                    Some(del)
924                }
925                Hunk::FileDel {
926                    ref del,
927                    ref contents,
928                    ..
929                } => {
930                    self.extra = contents.as_ref();
931                    Some(del)
932                }
933                Hunk::FileUndel {
934                    ref undel,
935                    ref contents,
936                    ..
937                } => {
938                    self.extra = contents.as_ref();
939                    Some(undel)
940                }
941                Hunk::FileAdd {
942                    ref add_name,
943                    ref add_inode,
944                    ref contents,
945                    ..
946                } => {
947                    self.extra = Some(add_inode);
948                    self.extra2 = contents.as_ref();
949                    Some(add_name)
950                }
951                Hunk::SolveNameConflict { ref name, .. } => Some(name),
952                Hunk::UnsolveNameConflict { ref name, .. } => Some(name),
953                Hunk::Edit { change: ref c, .. } => Some(c),
954                Hunk::Replacement {
955                    replacement: ref r,
956                    change: ref c,
957                    ..
958                } => {
959                    self.extra = Some(r);
960                    Some(c)
961                }
962                Hunk::SolveOrderConflict { ref change, .. } => Some(change),
963                Hunk::UnsolveOrderConflict { ref change, .. } => Some(change),
964                Hunk::ResurrectZombies { ref change, .. } => Some(change),
965                Hunk::AddRoot {
966                    ref inode,
967                    ref name,
968                }
969                | Hunk::DelRoot {
970                    ref inode,
971                    ref name,
972                } => {
973                    self.extra = Some(inode);
974                    Some(name)
975                }
976            }
977        } else {
978            None
979        }
980    }
981}
982
983pub struct RevHunkIter<R, C> {
984    rec: Option<R>,
985    extra: Option<C>,
986    extra2: Option<C>,
987}
988
989impl<'a, Context, Local> Iterator for RevHunkIter<&'a Hunk<Context, Local>, &'a Atom<Context>> {
990    type Item = &'a Atom<Context>;
991    fn next(&mut self) -> Option<Self::Item> {
992        if let Some(extra) = self.extra.take() {
993            Some(extra)
994        } else if let Some(extra) = self.extra2.take() {
995            Some(extra)
996        } else if let Some(rec) = self.rec.take() {
997            match *rec {
998                Hunk::FileMove {
999                    ref del, ref add, ..
1000                } => {
1001                    self.extra = Some(del);
1002                    Some(add)
1003                }
1004                Hunk::FileDel {
1005                    ref del,
1006                    ref contents,
1007                    ..
1008                } => {
1009                    if let Some(c) = contents {
1010                        self.extra = Some(del);
1011                        Some(c)
1012                    } else {
1013                        Some(del)
1014                    }
1015                }
1016                Hunk::FileUndel {
1017                    ref undel,
1018                    ref contents,
1019                    ..
1020                } => {
1021                    if let Some(c) = contents {
1022                        self.extra = Some(undel);
1023                        Some(c)
1024                    } else {
1025                        Some(undel)
1026                    }
1027                }
1028                Hunk::FileAdd {
1029                    ref add_name,
1030                    ref add_inode,
1031                    ref contents,
1032                    ..
1033                } => {
1034                    if let Some(c) = contents {
1035                        self.extra = Some(add_inode);
1036                        self.extra2 = Some(add_name);
1037                        Some(c)
1038                    } else {
1039                        self.extra = Some(add_name);
1040                        Some(add_inode)
1041                    }
1042                }
1043                Hunk::SolveNameConflict { ref name, .. } => Some(name),
1044                Hunk::UnsolveNameConflict { ref name, .. } => Some(name),
1045                Hunk::Edit { change: ref c, .. } => Some(c),
1046                Hunk::Replacement {
1047                    replacement: ref r,
1048                    change: ref c,
1049                    ..
1050                } => {
1051                    self.extra = Some(c);
1052                    Some(r)
1053                }
1054                Hunk::SolveOrderConflict { ref change, .. } => Some(change),
1055                Hunk::UnsolveOrderConflict { ref change, .. } => Some(change),
1056                Hunk::ResurrectZombies { ref change, .. } => Some(change),
1057                Hunk::AddRoot {
1058                    ref name,
1059                    ref inode,
1060                }
1061                | Hunk::DelRoot {
1062                    ref name,
1063                    ref inode,
1064                } => {
1065                    self.extra = Some(inode);
1066                    Some(name)
1067                }
1068            }
1069        } else {
1070            None
1071        }
1072    }
1073}
1074
1075impl Atom<Option<ChangeId>> {
1076    fn globalize<T: GraphTxnT>(&self, txn: &T) -> Result<Atom<Option<Hash>>, T::GraphError> {
1077        debug!("globalize");
1078        match self {
1079            Atom::NewVertex(NewVertex {
1080                up_context,
1081                down_context,
1082                start,
1083                end,
1084                flag,
1085                inode,
1086            }) => {
1087                debug!(
1088                    "globalize vertex {:?} {:?}",
1089                    up_context.len(),
1090                    down_context.len()
1091                );
1092                Ok(Atom::NewVertex(NewVertex {
1093                    up_context: up_context
1094                        .iter()
1095                        .map(|&up| Position {
1096                            change: up
1097                                .change
1098                                .as_ref()
1099                                .map(|a| txn.get_external(a).unwrap().into()),
1100                            pos: up.pos,
1101                        })
1102                        .collect(),
1103                    down_context: down_context
1104                        .iter()
1105                        .map(|&down| Position {
1106                            change: down
1107                                .change
1108                                .as_ref()
1109                                .map(|a| txn.get_external(a).unwrap().into()),
1110                            pos: down.pos,
1111                        })
1112                        .collect(),
1113                    start: *start,
1114                    end: *end,
1115                    flag: *flag,
1116                    inode: Position {
1117                        change: inode
1118                            .change
1119                            .as_ref()
1120                            .map(|a| txn.get_external(a).unwrap().into()),
1121                        pos: inode.pos,
1122                    },
1123                }))
1124            }
1125            Atom::EdgeMap(EdgeMap { edges, inode }) => {
1126                debug!("globalize edgemap {:?}", edges);
1127                Ok(Atom::EdgeMap(EdgeMap {
1128                    edges: edges
1129                        .iter()
1130                        .map(|edge| NewEdge {
1131                            previous: edge.previous,
1132                            flag: edge.flag,
1133                            from: Position {
1134                                change: edge
1135                                    .from
1136                                    .change
1137                                    .as_ref()
1138                                    .map(|a| txn.get_external(a).unwrap().into()),
1139                                pos: edge.from.pos,
1140                            },
1141                            to: Vertex {
1142                                change: edge
1143                                    .to
1144                                    .change
1145                                    .as_ref()
1146                                    .map(|a| txn.get_external(a).unwrap().into()),
1147                                start: edge.to.start,
1148                                end: edge.to.end,
1149                            },
1150                            introduced_by: edge
1151                                .introduced_by
1152                                .as_ref()
1153                                .map(|a| txn.get_external(a).unwrap().into()),
1154                        })
1155                        .collect(),
1156                    inode: Position {
1157                        change: inode
1158                            .change
1159                            .as_ref()
1160                            .map(|a| txn.get_external(a).unwrap().into()),
1161                        pos: inode.pos,
1162                    },
1163                }))
1164            }
1165        }
1166    }
1167}
1168
1169impl<H> Hunk<H, Local> {
1170    pub fn local(&self) -> Option<&Local> {
1171        match self {
1172            Hunk::Edit { local, .. }
1173            | Hunk::Replacement { local, .. }
1174            | Hunk::SolveOrderConflict { local, .. }
1175            | Hunk::UnsolveOrderConflict { local, .. }
1176            | Hunk::ResurrectZombies { local, .. } => Some(local),
1177            _ => None,
1178        }
1179    }
1180
1181    pub fn path(&self) -> &str {
1182        match self {
1183            Hunk::FileMove { path, .. }
1184            | Hunk::FileDel { path, .. }
1185            | Hunk::FileUndel { path, .. }
1186            | Hunk::SolveNameConflict { path, .. }
1187            | Hunk::UnsolveNameConflict { path, .. }
1188            | Hunk::FileAdd { path, .. } => path,
1189            Hunk::Edit { local, .. }
1190            | Hunk::Replacement { local, .. }
1191            | Hunk::SolveOrderConflict { local, .. }
1192            | Hunk::UnsolveOrderConflict { local, .. }
1193            | Hunk::ResurrectZombies { local, .. } => &local.path,
1194            Hunk::AddRoot { .. } | Hunk::DelRoot { .. } => "/",
1195        }
1196    }
1197
1198    pub fn line(&self) -> Option<usize> {
1199        self.local().map(|x| x.line)
1200    }
1201}
1202
1203impl<A, Local> BaseHunk<A, Local> {
1204    pub fn atom_map<B, E, Loc, F: FnMut(A) -> Result<B, E>, L: FnMut(Local) -> Loc>(
1205        self,
1206        mut f: F,
1207        mut l: L,
1208    ) -> Result<BaseHunk<B, Loc>, E> {
1209        Ok(match self {
1210            BaseHunk::FileMove { del, add, path } => BaseHunk::FileMove {
1211                del: f(del)?,
1212                add: f(add)?,
1213                path,
1214            },
1215            BaseHunk::FileDel {
1216                del,
1217                contents,
1218                path,
1219                encoding,
1220            } => BaseHunk::FileDel {
1221                del: f(del)?,
1222                contents: if let Some(c) = contents {
1223                    Some(f(c)?)
1224                } else {
1225                    None
1226                },
1227                path,
1228                encoding,
1229            },
1230            BaseHunk::FileUndel {
1231                undel,
1232                contents,
1233                path,
1234                encoding,
1235            } => BaseHunk::FileUndel {
1236                undel: f(undel)?,
1237                contents: if let Some(c) = contents {
1238                    Some(f(c)?)
1239                } else {
1240                    None
1241                },
1242                path,
1243                encoding,
1244            },
1245            BaseHunk::SolveNameConflict { name, path } => BaseHunk::SolveNameConflict {
1246                name: f(name)?,
1247                path,
1248            },
1249            BaseHunk::UnsolveNameConflict { name, path } => BaseHunk::UnsolveNameConflict {
1250                name: f(name)?,
1251                path,
1252            },
1253            BaseHunk::FileAdd {
1254                add_inode,
1255                add_name,
1256                contents,
1257                path,
1258                encoding,
1259            } => BaseHunk::FileAdd {
1260                add_name: f(add_name)?,
1261                add_inode: f(add_inode)?,
1262                contents: if let Some(c) = contents {
1263                    Some(f(c)?)
1264                } else {
1265                    None
1266                },
1267                path,
1268                encoding,
1269            },
1270            BaseHunk::Edit {
1271                change,
1272                local,
1273                encoding,
1274            } => BaseHunk::Edit {
1275                change: f(change)?,
1276                local: l(local),
1277                encoding,
1278            },
1279            BaseHunk::Replacement {
1280                change,
1281                replacement,
1282                local,
1283                encoding,
1284            } => BaseHunk::Replacement {
1285                change: f(change)?,
1286                replacement: f(replacement)?,
1287                local: l(local),
1288                encoding,
1289            },
1290            BaseHunk::SolveOrderConflict { change, local } => BaseHunk::SolveOrderConflict {
1291                change: f(change)?,
1292                local: l(local),
1293            },
1294            BaseHunk::UnsolveOrderConflict { change, local } => BaseHunk::UnsolveOrderConflict {
1295                change: f(change)?,
1296                local: l(local),
1297            },
1298            BaseHunk::ResurrectZombies {
1299                change,
1300                local,
1301                encoding,
1302            } => BaseHunk::ResurrectZombies {
1303                change: f(change)?,
1304                local: l(local),
1305                encoding,
1306            },
1307            BaseHunk::AddRoot { name, inode } => BaseHunk::AddRoot {
1308                name: f(name)?,
1309                inode: f(inode)?,
1310            },
1311            BaseHunk::DelRoot { name, inode } => BaseHunk::DelRoot {
1312                name: f(name)?,
1313                inode: f(inode)?,
1314            },
1315        })
1316    }
1317}
1318
1319impl Hunk<Option<ChangeId>, LocalByte> {
1320    pub fn globalize<T: GraphTxnT>(
1321        self,
1322        txn: &T,
1323    ) -> Result<Hunk<Option<Hash>, Local>, T::GraphError> {
1324        self.atom_map(
1325            |x| x.globalize(txn),
1326            |l| Local {
1327                path: l.path,
1328                line: l.line,
1329            },
1330        )
1331    }
1332}
1333
1334/// A table of contents of a change, indicating where each section is,
1335/// to allow seeking inside a change file.
1336#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
1337pub struct Offsets {
1338    pub version: u64,
1339    pub hashed_len: u64, // length of the hashed contents
1340    pub unhashed_off: u64,
1341    pub unhashed_len: u64, // length of the unhashed contents
1342    pub contents_off: u64,
1343    pub contents_len: u64,
1344    pub total: u64,
1345}
1346
1347#[derive(Error)]
1348pub enum MakeChangeError<T: GraphTxnT> {
1349    #[error(transparent)]
1350    Graph(#[from] TxnErr<<T as GraphTxnT>::GraphError>),
1351    #[error("Invalid change")]
1352    InvalidChange,
1353}
1354
1355impl<T: GraphTxnT> std::fmt::Debug for MakeChangeError<T> {
1356    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
1357        match self {
1358            MakeChangeError::Graph(e) => std::fmt::Debug::fmt(e, fmt),
1359            MakeChangeError::InvalidChange => std::fmt::Debug::fmt("InvalidChange", fmt),
1360        }
1361    }
1362}
1363
1364impl LocalChange<Hunk<Option<Hash>, Local>, Author> {
1365    pub const OFFSETS_SIZE: u64 = 56;
1366
1367    pub fn make_change<T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>>(
1368        txn: &T,
1369        channel: &ChannelRef<T>,
1370        changes: Vec<Hunk<Option<Hash>, Local>>,
1371        contents: Vec<u8>,
1372        header: ChangeHeader,
1373        metadata: Vec<u8>,
1374    ) -> Result<Self, MakeChangeError<T>> {
1375        info!("computing dependencies");
1376        let (dependencies, extra_known) = dependencies(txn, &channel.read(), changes.iter())?;
1377        trace!("make_change, contents = {:?}", contents);
1378        info!("hashing patch");
1379        let contents_hash = {
1380            let mut hasher = Hasher::default();
1381            hasher.update(&contents);
1382            hasher.finish()
1383        };
1384        debug!("make_change, contents_hash = {:?}", contents_hash);
1385        Ok(LocalChange {
1386            offsets: Offsets::default(),
1387            hashed: Hashed {
1388                version: VERSION,
1389                header,
1390                changes,
1391                contents_hash,
1392                metadata,
1393                dependencies,
1394                extra_known,
1395            },
1396            contents,
1397            unhashed: None,
1398        })
1399    }
1400
1401    pub fn write_all_deps<F: FnMut(Hash) -> Result<(), ChangeError>>(
1402        &self,
1403        f: F,
1404    ) -> Result<(), ChangeError> {
1405        self.hashed.write_all_deps(f)
1406    }
1407}
1408
1409impl Default for LocalChange<Hunk<Option<Hash>, Local>, Author> {
1410    fn default() -> Self {
1411        LocalChange {
1412            offsets: Offsets::default(),
1413            hashed: Hashed {
1414                version: VERSION,
1415                header: ChangeHeader::default(),
1416                changes: Vec::new(),
1417                contents_hash: Hasher::default().finish(),
1418                metadata: Vec::new(),
1419                dependencies: Vec::new(),
1420                extra_known: Vec::new(),
1421            },
1422            unhashed: None,
1423            contents: Vec::new(),
1424        }
1425    }
1426}
1427
1428impl Hashed<Hunk<Option<Hash>, Local>, Author> {
1429    pub fn write_all_deps<F: FnMut(Hash) -> Result<(), ChangeError>>(
1430        &self,
1431        mut f: F,
1432    ) -> Result<(), ChangeError> {
1433        for c in self.changes.iter() {
1434            for c in c.iter() {
1435                match *c {
1436                    Atom::NewVertex(ref n) => {
1437                        for change in n
1438                            .up_context
1439                            .iter()
1440                            .chain(n.down_context.iter())
1441                            .map(|c| c.change)
1442                            .chain(std::iter::once(n.inode.change))
1443                            .flatten()
1444                        {
1445                            if let Hash::None = change {
1446                                continue;
1447                            }
1448                            f(change)?
1449                        }
1450                    }
1451                    Atom::EdgeMap(ref e) => {
1452                        for edge in e.edges.iter() {
1453                            for change in &[
1454                                edge.from.change,
1455                                edge.to.change,
1456                                edge.introduced_by,
1457                                e.inode.change,
1458                            ] {
1459                                if let Some(change) = *change {
1460                                    if let Hash::None = change {
1461                                        continue;
1462                                    }
1463                                    f(change)?
1464                                }
1465                            }
1466                        }
1467                    }
1468                }
1469            }
1470        }
1471        Ok(())
1472    }
1473}
1474
1475#[cfg(feature = "zstd")]
1476const LEVEL: usize = 3;
1477#[cfg(feature = "zstd")]
1478const FRAME_SIZE: usize = 4096;
1479#[cfg(feature = "zstd")]
1480fn compress(input: &[u8], w: &mut Vec<u8>) -> Result<(), ChangeError> {
1481    info!(
1482        "compressing with ZStd {}",
1483        zstd_seekable::version().to_str().unwrap()
1484    );
1485    let mut level = LEVEL;
1486    if let Ok(l) = std::env::var("ZSTD_LEVEL")
1487        && let Ok(l) = l.parse()
1488    {
1489        level = l
1490    }
1491    let mut cstream = zstd_seekable::SeekableCStream::new(level, FRAME_SIZE).unwrap();
1492    let mut output = [0; 4096];
1493    let mut input_pos = 0;
1494    while input_pos < input.len() {
1495        let (out_pos, inp_pos) = cstream.compress(&mut output, &input[input_pos..])?;
1496        w.write_all(&output[..out_pos])?;
1497        input_pos += inp_pos;
1498    }
1499    while let Ok(n) = cstream.end_stream(&mut output) {
1500        if n == 0 {
1501            break;
1502        }
1503        w.write_all(&output[..n])?;
1504    }
1505    Ok(())
1506}
1507
1508impl Change {
1509    pub fn size_no_contents<R: std::io::Read + std::io::Seek>(
1510        r: &mut R,
1511    ) -> Result<u64, ChangeError> {
1512        let pos = r.stream_position()?;
1513        let mut off = [0u8; Self::OFFSETS_SIZE as usize];
1514        r.read_exact(&mut off)?;
1515        let off: Offsets = bincode::deserialize(&off)?;
1516        if off.version != VERSION && off.version != VERSION_NOENC {
1517            return Err(ChangeError::VersionMismatch { got: off.version });
1518        }
1519        r.seek(std::io::SeekFrom::Start(pos))?;
1520        Ok(off.contents_off)
1521    }
1522
1523    /// Serialise the change as a file named "<hash>.change" in
1524    /// directory `dir`, where "<hash>" is the actual hash of the
1525    /// change.
1526    #[cfg(feature = "zstd")]
1527    pub fn serialize<
1528        W: Write,
1529        E: From<ChangeError>,
1530        F: FnOnce(&mut Self, &Hash) -> Result<(), E>,
1531    >(
1532        &mut self,
1533        mut w: W,
1534        f: F,
1535    ) -> Result<Hash, E> {
1536        // Hashed part.
1537        let mut hashed = Vec::new();
1538        bincode::serialize_into(&mut hashed, &self.hashed).map_err(From::from)?;
1539        trace!("hashed = {:?}", hashed);
1540        let mut hasher = Hasher::default();
1541        hasher.update(&hashed);
1542        let hash = hasher.finish();
1543        debug!("{:?}", hash);
1544
1545        f(self, &hash)?;
1546
1547        // Unhashed part.
1548        let unhashed = if let Some(ref un) = self.unhashed {
1549            let s = serde_json::to_string(un).unwrap();
1550            s.into()
1551        } else {
1552            Vec::new()
1553        };
1554
1555        // Compress the change.
1556        let mut hashed_comp = Vec::new();
1557        let now = std::time::Instant::now();
1558        compress(&hashed, &mut hashed_comp)?;
1559        debug!("compressed hashed in {:?}", now.elapsed());
1560        let now = std::time::Instant::now();
1561        let unhashed_off = Self::OFFSETS_SIZE + hashed_comp.len() as u64;
1562        let mut unhashed_comp = Vec::new();
1563        compress(&unhashed, &mut unhashed_comp)?;
1564        debug!("compressed unhashed in {:?}", now.elapsed());
1565        let contents_off = unhashed_off + unhashed_comp.len() as u64;
1566        let mut contents_comp = Vec::new();
1567        let now = std::time::Instant::now();
1568        compress(&self.contents, &mut contents_comp)?;
1569        debug!(
1570            "compressed {:?} bytes of contents in {:?}",
1571            self.contents.len(),
1572            now.elapsed()
1573        );
1574
1575        let offsets = Offsets {
1576            version: VERSION,
1577            hashed_len: hashed.len() as u64,
1578            unhashed_off,
1579            unhashed_len: unhashed.len() as u64,
1580            contents_off,
1581            contents_len: self.contents.len() as u64,
1582            total: contents_off + contents_comp.len() as u64,
1583        };
1584
1585        bincode::serialize_into(&mut w, &offsets).map_err(From::from)?;
1586        w.write_all(&hashed_comp).map_err(From::from)?;
1587        w.write_all(&unhashed_comp).map_err(From::from)?;
1588        w.write_all(&contents_comp).map_err(From::from)?;
1589        debug!("change serialized");
1590
1591        Ok(hash)
1592    }
1593
1594    /// Deserialise a change from the file given as input `file`.
1595    #[cfg(feature = "zstd")]
1596    pub fn check_from_buffer(buf: &[u8], hash: &Hash) -> Result<(), ChangeError> {
1597        let offsets: Offsets = bincode::deserialize_from(&buf[..Self::OFFSETS_SIZE as usize])?;
1598        if offsets.version != VERSION && offsets.version != VERSION_NOENC {
1599            return Err(ChangeError::VersionMismatch {
1600                got: offsets.version,
1601            });
1602        }
1603
1604        debug!("check_from_buffer, offsets = {:?}", offsets);
1605        let mut s = zstd_seekable::Seekable::init_buf(
1606            &buf[Self::OFFSETS_SIZE as usize..offsets.unhashed_off as usize],
1607        )?;
1608        let mut buf_ = vec![0; offsets.hashed_len as usize];
1609        s.decompress(&mut buf_[..], 0)?;
1610        trace!("check_from_buffer, buf_ = {:?}", buf_);
1611        let mut hasher = Hasher::default();
1612        hasher.update(&buf_);
1613        let computed_hash = hasher.finish();
1614        debug!("{:?} {:?}", computed_hash, hash);
1615        if &computed_hash != hash {
1616            return Err(ChangeError::ChangeHashMismatch {
1617                claimed: *hash,
1618                computed: computed_hash,
1619            });
1620        }
1621
1622        let hashed: Hashed<Hunk<Option<Hash>, Local>, Author> = if offsets.version == VERSION {
1623            bincode::deserialize(&buf_)?
1624        } else {
1625            let h: Hashed<noenc::Hunk<Option<Hash>, Local>, noenc::Author> =
1626                bincode::deserialize(&buf_)?;
1627            h.into()
1628        };
1629        buf_.clear();
1630        buf_.resize(offsets.contents_len as usize, 0);
1631        let mut s = zstd_seekable::Seekable::init_buf(&buf[offsets.contents_off as usize..])?;
1632        buf_.resize(offsets.contents_len as usize, 0);
1633        s.decompress(&mut buf_[..], 0)?;
1634        let mut hasher = Hasher::default();
1635        trace!("contents = {:?}", buf_);
1636        hasher.update(&buf_);
1637        let computed_hash = hasher.finish();
1638        debug!(
1639            "contents hash: {:?}, computed: {:?}",
1640            hashed.contents_hash, computed_hash
1641        );
1642        if computed_hash != hashed.contents_hash {
1643            return Err(ChangeError::ContentsHashMismatch {
1644                claimed: hashed.contents_hash,
1645                computed: computed_hash,
1646            });
1647        }
1648        Ok(())
1649    }
1650
1651    /// Deserialise a change from the file given as input `file`.
1652    #[cfg(feature = "zstd")]
1653    pub fn deserialize(file: &str, hash: Option<&Hash>) -> Result<Self, ChangeError> {
1654        use std::io::Read;
1655        let mut r = std::fs::File::open(file)
1656            .map_err(|err| {
1657                if let Some(h) = hash {
1658                    ChangeError::IoHash { err, hash: *h }
1659                } else {
1660                    ChangeError::Io(err)
1661                }
1662            })
1663            .unwrap();
1664        let mut buf = vec![0u8; Self::OFFSETS_SIZE as usize];
1665        r.read_exact(&mut buf)?;
1666        let offsets: Offsets = bincode::deserialize(&buf)?;
1667        if offsets.version == VERSION_NOENC {
1668            return Self::deserialize_noenc(offsets, r, hash);
1669        } else if offsets.version != VERSION {
1670            return Err(ChangeError::VersionMismatch {
1671                got: offsets.version,
1672            });
1673        }
1674        debug!("offsets = {:?}", offsets);
1675        buf.clear();
1676        buf.resize((offsets.unhashed_off - Self::OFFSETS_SIZE) as usize, 0);
1677        r.read_exact(&mut buf)?;
1678
1679        let hashed: Hashed<Hunk<Option<Hash>, Local>, Author> = {
1680            let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
1681            let mut out = vec![0u8; offsets.hashed_len as usize];
1682            s.decompress(&mut out[..], 0)?;
1683            let mut hasher = Hasher::default();
1684            hasher.update(&out);
1685            let computed_hash = hasher.finish();
1686            if let Some(hash) = hash
1687                && &computed_hash != hash
1688            {
1689                return Err(ChangeError::ChangeHashMismatch {
1690                    claimed: *hash,
1691                    computed: computed_hash,
1692                });
1693            }
1694            bincode::deserialize_from(&out[..])?
1695        };
1696        buf.clear();
1697        buf.resize((offsets.contents_off - offsets.unhashed_off) as usize, 0);
1698        let unhashed = if buf.is_empty() {
1699            None
1700        } else {
1701            r.read_exact(&mut buf)?;
1702            let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
1703            let mut out = vec![0u8; offsets.unhashed_len as usize];
1704            s.decompress(&mut out[..], 0)?;
1705            debug!("parsing unhashed: {:?}", std::str::from_utf8(&out));
1706            serde_json::from_slice(&out).ok()
1707        };
1708        debug!("unhashed = {:?}", unhashed);
1709
1710        buf.clear();
1711        buf.resize((offsets.total - offsets.contents_off) as usize, 0);
1712        let contents = if r.read_exact(&mut buf).is_ok() {
1713            let mut s = zstd_seekable::Seekable::init_buf(&buf[..])?;
1714            let mut contents = vec![0u8; offsets.contents_len as usize];
1715            s.decompress(&mut contents[..], 0)?;
1716            contents
1717        } else {
1718            Vec::new()
1719        };
1720        debug!("contents = {:?}", contents);
1721
1722        Ok(LocalChange {
1723            offsets,
1724            hashed,
1725            unhashed,
1726            contents,
1727        })
1728    }
1729
1730    /// Compute the hash of this change. If the `zstd` feature is
1731    /// enabled, it is probably more efficient to serialise the change
1732    /// (using the `serialize` method) at the same time, which also
1733    /// returns the hash.
1734    pub fn hash(&self) -> Result<Hash, bincode::Error> {
1735        let input = bincode::serialize(&self.hashed)?;
1736        let mut hasher = Hasher::default();
1737        hasher.update(&input);
1738        Ok(hasher.finish())
1739    }
1740}