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::*; #[cfg(feature = "text-changes")]
20pub use printable::*; #[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 pub from: Position<Change>,
106 pub to: Vertex<Change>,
109 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#[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
136pub 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 pub unhashed: Option<serde_json::Value>,
157 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
179pub const VERSION: u64 = 6;
181pub const VERSION_NOENC: u64 = 4;
182
183#[derive(Clone, Debug, PartialEq, Serialize, Deserialize)]
184pub struct Hashed<Hunk, Author> {
185 pub version: u64,
187 pub header: ChangeHeader_<Author>,
189 pub dependencies: Vec<Hash>,
191 pub extra_known: Vec<Hash>,
193 pub metadata: Vec<u8>,
195 pub changes: Vec<Hunk>,
197 pub contents_hash: Hash, }
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 return flags | EdgeFlags::FOLDER
479 == EdgeFlags::BLOCK | EdgeFlags::FOLDER;
480 } else {
481 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 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#[derive(Debug, Serialize, Deserialize, Clone, Default, PartialEq, Eq)]
1337pub struct Offsets {
1338 pub version: u64,
1339 pub hashed_len: u64, pub unhashed_off: u64,
1341 pub unhashed_len: u64, 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 #[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 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 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 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 #[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 #[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 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}