Skip to main content

pijul_core/change/
text_changes.rs

1use crate::HashMap;
2use std::collections::hash_map::Entry;
3use std::io::BufRead;
4
5use super::*;
6use crate::change::parse::*;
7use crate::change::printable::*;
8use crate::changestore::*;
9
10#[derive(Debug, Error)]
11pub enum TextDeError {
12    #[error(transparent)]
13    Io(#[from] std::io::Error),
14    #[error(transparent)]
15    TomlDe(#[from] toml::de::Error),
16    #[error(transparent)]
17    Nom(#[from] nom::Err<nom::error::Error<String>>),
18    #[error("Missing dependency [{0}]")]
19    MissingChange(usize),
20    #[error("Byte position {0} from this change missing")]
21    MissingPosition(u64),
22}
23
24#[derive(Debug, Error)]
25pub enum TextDeErrorDeps<T: GraphTxnT> {
26    #[error(transparent)]
27    TextDe(#[from] TextDeError),
28    #[error(transparent)]
29    MakeChange(#[from] MakeChangeError<T>),
30}
31
32#[derive(Debug, Error)]
33pub enum TextSerError<C: std::error::Error + 'static> {
34    #[error(transparent)]
35    C(C),
36    #[error(transparent)]
37    Io(#[from] std::io::Error),
38    #[error(transparent)]
39    TomlSer(#[from] toml::ser::Error),
40    #[error("Missing contents in change {:?}", h)]
41    MissingContents { h: Hash },
42    #[error(transparent)]
43    Change(#[from] ChangeError),
44    #[error("Invalid change")]
45    InvalidChange,
46}
47
48impl LocalChange<Hunk<Option<Hash>, Local>, Author> {
49    const DEPS_LINE: &'static str = "# Dependencies\n";
50    const HUNKS_LINE: &'static str = "# Hunks\n";
51
52    pub fn write_all_deps_old<F: FnMut(Hash) -> Result<(), ChangeError>>(
53        &self,
54        mut f: F,
55    ) -> Result<(), ChangeError> {
56        for c in self.changes.iter() {
57            for c in c.iter() {
58                match *c {
59                    Atom::NewVertex(ref n) => {
60                        for change in n
61                            .up_context
62                            .iter()
63                            .chain(n.down_context.iter())
64                            .map(|c| c.change)
65                            .chain(std::iter::once(n.inode.change))
66                            .flatten()
67                        {
68                            if let Hash::None = change {
69                                continue;
70                            }
71                            f(change)?
72                        }
73                    }
74                    Atom::EdgeMap(ref e) => {
75                        for edge in e.edges.iter() {
76                            for change in &[
77                                edge.from.change,
78                                edge.to.change,
79                                edge.introduced_by,
80                                e.inode.change,
81                            ] {
82                                if let Some(change) = *change {
83                                    if let Hash::None = change {
84                                        continue;
85                                    }
86                                    f(change)?
87                                }
88                            }
89                        }
90                    }
91                }
92            }
93        }
94        Ok(())
95    }
96
97    pub fn write<W: WriteChangeLine, C: ChangeStore>(
98        &self,
99        changes: &C,
100        hash: Option<Hash>,
101        write_header: bool,
102        mut w: W,
103    ) -> Result<(), TextSerError<C::Error>> {
104        fn change_message<C: ChangeStore>(changes: &C, change: &Hash) -> String {
105            match changes.get_header(change) {
106                Ok(h) => h.message.lines().next().unwrap_or("").to_string(),
107                Err(e) => format!("[couldn't get change description: {}]", e),
108            }
109        }
110
111        if let Some(h) = hash {
112            // Check if we have the full contents
113            let mut hasher = Hasher::default();
114            hasher.update(&self.contents);
115            let hash = hasher.finish();
116            if hash != self.contents_hash {
117                return Err(TextSerError::MissingContents { h });
118            }
119        }
120
121        if write_header {
122            let s = toml::ser::to_string_pretty(&self.header)?;
123            writeln!(w, "{}", s)?;
124        }
125        let mut hashes = HashMap::default();
126        let mut i = 2;
127        let mut needs_newline = false;
128        if !self.dependencies.is_empty() {
129            w.write_all(Self::DEPS_LINE.as_bytes())?;
130            needs_newline = true;
131            for dep in self.dependencies.iter() {
132                hashes.insert(*dep, i);
133                writeln!(
134                    w,
135                    "[{}] {} # {}",
136                    i,
137                    dep.to_base32(),
138                    change_message(changes, dep)
139                )?;
140                i += 1;
141            }
142        }
143
144        self.write_all_deps(|change| {
145            if let Entry::Vacant(e) = hashes.entry(change) {
146                e.insert(i);
147                if !needs_newline {
148                    w.write_all(Self::DEPS_LINE.as_bytes())?;
149                    needs_newline = true;
150                }
151                writeln!(
152                    w,
153                    "[{}]+{} # {}",
154                    i,
155                    change.to_base32(),
156                    change_message(changes, &change)
157                )?;
158                i += 1;
159            }
160            Ok(())
161        })?;
162
163        if !self.extra_known.is_empty() {
164            needs_newline = true;
165            for dep in self.extra_known.iter() {
166                writeln!(
167                    w,
168                    "[*] {} # {}",
169                    dep.to_base32(),
170                    change_message(changes, dep)
171                )?;
172                #[allow(unused)]
173                {
174                    i += 1;
175                }
176            }
177        }
178
179        if !self.changes.is_empty() {
180            if needs_newline {
181                w.write_all(b"\n")?
182            }
183            w.write_all(Self::HUNKS_LINE.as_bytes())?;
184            for (n, rec) in self.changes.iter().enumerate() {
185                write!(w, "\n{}. ", n + 1)?;
186                rec.write(changes, &hashes, &self.contents, &mut w)?
187            }
188        }
189        Ok(())
190    }
191}
192
193impl Change {
194    pub fn read_and_deps<
195        R: BufRead,
196        T: ChannelTxnT + DepsTxnT<DepsError = <T as GraphTxnT>::GraphError>,
197    >(
198        r: R,
199        updatables: &mut HashMap<usize, crate::InodeUpdate>,
200        txn: &T,
201        channel: &ChannelRef<T>,
202    ) -> Result<Self, TextDeErrorDeps<T>> {
203        let (mut change, extra_dependencies) = Self::read_impl(r, updatables)?;
204        let (mut deps, extra) = dependencies(txn, &channel.read(), change.hashed.changes.iter())?;
205        deps.extend(extra_dependencies);
206        change.hashed.dependencies = deps;
207        change.hashed.extra_known = extra;
208        Ok(change)
209    }
210
211    pub fn read<R: BufRead>(
212        r: R,
213        updatables: &mut HashMap<usize, crate::InodeUpdate>,
214    ) -> Result<Self, TextDeError> {
215        Ok(Self::read_impl(r, updatables)?.0)
216    }
217
218    fn read_impl<R: BufRead>(
219        mut r: R,
220        updatables: &mut HashMap<usize, crate::InodeUpdate>,
221    ) -> Result<(Self, HashSet<Hash>), TextDeError> {
222        // read the data
223        // TODO: make this streaming
224        let mut s = String::new();
225        r.read_to_string(&mut s)?;
226        let i = &s;
227
228        // parse header
229        let (i, m_header) = parse_header(i).map_err(|e| e.to_owned())?;
230        let header = m_header?;
231
232        // parse dependencies
233        let (i, deps) = parse_dependencies(i).map_err(|e| e.to_owned())?;
234
235        // parse hunks
236        let (_, hunks) = parse_hunks(i).map_err(|e| e.to_owned())?;
237
238        Change::update(header, deps, hunks, updatables)
239    }
240
241    fn update(
242        header: ChangeHeader,
243        dependencies: Vec<PrintableDep>,
244        hunks: Vec<(u64, PrintableHunk)>,
245        updatables: &mut HashMap<usize, crate::InodeUpdate>,
246    ) -> Result<(Self, HashSet<Hash>), TextDeError> {
247        // TODO: get rid of this whole default change if possible
248        let mut change = Change {
249            offsets: Offsets::default(),
250            hashed: Hashed {
251                version: VERSION,
252                header,
253                dependencies: Vec::new(),
254                extra_known: Vec::new(),
255                metadata: Vec::new(),
256                changes: Vec::new(),
257                contents_hash: Hasher::default().finish(),
258            },
259            unhashed: None,
260            contents: Vec::new(),
261        };
262
263        // process dependencies
264        let mut deps = HashMap::default();
265        let mut extra_dependencies = HashSet::default();
266        for dep in dependencies {
267            debug!("dep hash {:?}", dep.hash);
268            let hash = Hash::from_base32(dep.hash.as_bytes()).unwrap();
269            match dep.type_ {
270                DepType::Numbered(n, false) => {
271                    change.hashed.dependencies.push(hash);
272                    deps.insert(n, hash);
273                }
274                DepType::Numbered(n, true) => {
275                    deps.insert(n, hash);
276                }
277                DepType::ExtraKnown => {
278                    change.hashed.extra_known.push(hash);
279                }
280                DepType::ExtraUnknown => {
281                    extra_dependencies.insert(hash);
282                }
283            }
284        }
285
286        // process hunks
287        let mut contents = Vec::new();
288        let mut offsets = HashMap::default();
289        for (n, hunk) in hunks {
290            let res =
291                Hunk::from_printable(updatables, &mut contents, &deps, &mut offsets, (n, hunk))?;
292            debug!("res = {:?}", res);
293            change.hashed.changes.push(res);
294        }
295        change.contents = contents;
296        change.contents_hash = {
297            let mut hasher = Hasher::default();
298            hasher.update(&change.contents);
299            hasher.finish()
300        };
301        Ok((change, extra_dependencies))
302    }
303}
304
305pub fn to_printable_new_vertex(
306    atom: &Atom<Option<Hash>>,
307    hashes: &HashMap<Hash, usize>,
308) -> PrintableNewVertex {
309    if let PrintableAtom::NewVertex(v) = to_printable_atom(atom, hashes) {
310        v
311    } else {
312        panic!("PrintableAtom::NewVertex expected here")
313    }
314}
315
316pub fn to_printable_edge_map(
317    atom: &Atom<Option<Hash>>,
318    hashes: &HashMap<Hash, usize>,
319) -> Vec<PrintableEdge> {
320    if let PrintableAtom::Edges(v) = to_printable_atom(atom, hashes) {
321        v
322    } else {
323        panic!("PrintableAtom::Edges expected here")
324    }
325}
326
327/// Panics if the Atom is not an EdgeMap
328fn to_printable_atom(atom: &Atom<Option<Hash>>, hashes: &HashMap<Hash, usize>) -> PrintableAtom {
329    match atom {
330        Atom::NewVertex(new_vertex) => PrintableAtom::NewVertex(PrintableNewVertex {
331            up_context: new_vertex
332                .up_context
333                .iter()
334                .map(|c| to_printable_pos(hashes, *c))
335                .collect(),
336            start: new_vertex.start.0.as_u64(),
337            end: new_vertex.end.0.as_u64(),
338            down_context: new_vertex
339                .down_context
340                .iter()
341                .map(|c| to_printable_pos(hashes, *c))
342                .collect(),
343        }),
344        Atom::EdgeMap(edge_map) => PrintableAtom::Edges(
345            edge_map
346                .edges
347                .iter()
348                .map(|c| PrintableEdge {
349                    previous: PrintableEdgeFlags::from(c.previous),
350                    flag: PrintableEdgeFlags::from(c.flag),
351                    from: to_printable_pos(hashes, c.from),
352                    to_start: to_printable_pos(hashes, c.to.start_pos()),
353                    to_end: c.to.end.0.as_u64(),
354                    introduced_by: *hashes.get(&c.introduced_by.unwrap()).unwrap_or_else(|| {
355                        panic!("introduced_by = {:?}, not found", c.introduced_by)
356                    }),
357                })
358                .collect(),
359        ),
360    }
361}
362
363fn from_printable_edge_map(
364    edges: &[PrintableEdge],
365    changes: &HashMap<usize, Hash>,
366) -> Result<Vec<NewEdge<Option<Hash>>>, TextDeError> {
367    let mut res = Vec::new();
368    for edge in edges {
369        let Position { change, pos } = from_printable_pos(changes, edge.to_start)?;
370        res.push(NewEdge {
371            previous: edge.previous.to(),
372            flag: edge.flag.to(),
373            from: from_printable_pos(changes, edge.from)?,
374            to: Vertex {
375                change,
376                start: pos,
377                end: ChangePosition(L64(edge.to_end.to_le())),
378            },
379            introduced_by: change_ref(changes, edge.introduced_by)?,
380        })
381    }
382    Ok(res)
383}
384
385impl Hunk<Option<Hash>, Local> {
386    fn write<W: WriteChangeLine, C: ChangeStore>(
387        &self,
388        changes: &C,
389        hashes: &HashMap<Hash, usize>,
390        change_contents: &[u8],
391        w: &mut W,
392    ) -> Result<(), TextSerError<C::Error>> {
393        use self::text_changes::*;
394        debug!("write {:?}", self);
395        match self {
396            Hunk::FileMove { del, add, path } => match add {
397                Atom::NewVertex(add) => {
398                    let FileMetadata {
399                        basename: name,
400                        metadata,
401                        ..
402                    } = FileMetadata::read(&change_contents[add.start.0.into()..add.end.0.into()]);
403                    PrintableHunk::FileMoveV {
404                        path: path.to_string(),
405                        name: name.to_string(),
406                        perms: PrintablePerms::from_metadata(metadata),
407                        pos: to_printable_pos(hashes, del.inode()),
408                        up_context: to_printable_pos_vec(hashes, &add.up_context),
409                        down_context: to_printable_pos_vec(hashes, &add.down_context),
410                        del: to_printable_edge_map(del, hashes),
411                    }
412                }
413                Atom::EdgeMap(_) => PrintableHunk::FileMoveE {
414                    path: path.to_string(),
415                    pos: to_printable_pos(hashes, del.inode()),
416                    add: to_printable_edge_map(add, hashes),
417                    del: to_printable_edge_map(del, hashes),
418                },
419            },
420            Hunk::FileDel {
421                del,
422                contents,
423                path,
424                encoding,
425            } => {
426                debug!("file del");
427                let (contents_data, content_edges) = if let Some(c) = contents {
428                    (
429                        get_change_contents(changes, c, change_contents)?,
430                        to_printable_edge_map(c, hashes),
431                    )
432                } else {
433                    (Vec::new(), Vec::new())
434                };
435
436                PrintableHunk::FileDel {
437                    path: path.to_string(),
438                    pos: to_printable_pos(hashes, del.inode()),
439                    encoding: encoding.clone(),
440                    del_edges: to_printable_edge_map(del, hashes),
441                    content_edges,
442                    contents: contents_data,
443                }
444            }
445            Hunk::FileUndel {
446                undel,
447                contents,
448                path,
449                encoding,
450            } => {
451                debug!("file undel");
452                let (contents_data, content_edges) = if let Some(c) = contents {
453                    (
454                        get_change_contents(changes, c, change_contents)?,
455                        to_printable_edge_map(c, hashes),
456                    )
457                } else {
458                    (Vec::new(), Vec::new())
459                };
460
461                PrintableHunk::FileUndel {
462                    path: path.to_string(),
463                    pos: to_printable_pos(hashes, undel.inode()),
464                    encoding: encoding.clone(),
465                    undel_edges: to_printable_edge_map(undel, hashes),
466                    content_edges,
467                    contents: contents_data,
468                }
469            }
470            Hunk::FileAdd {
471                add_name,
472                contents,
473                path,
474                encoding,
475                ..
476            } => {
477                if let Atom::NewVertex(n) = add_name {
478                    debug!("add_name {:?}", n);
479                    let (name, metadata) = if n.start == n.end {
480                        ("", InodeMetadata::DIR)
481                    } else {
482                        let FileMetadata {
483                            basename: name,
484                            metadata: perms,
485                            ..
486                        } = FileMetadata::read(&change_contents[n.start.0.into()..n.end.0.into()]);
487                        (name, perms)
488                    };
489
490                    let contents = if let Some(Atom::NewVertex(n)) = contents {
491                        change_contents[n.start.us()..n.end.us()].to_vec()
492                    } else {
493                        Vec::new()
494                    };
495                    assert!(n.down_context.is_empty());
496
497                    PrintableHunk::FileAddition {
498                        name: name.to_string(),
499                        parent: crate::path::parent(path).unwrap_or("").to_string(),
500                        perms: PrintablePerms::from_metadata(metadata),
501                        encoding: encoding.clone(),
502                        up_context: to_printable_pos_vec(hashes, &n.up_context),
503                        start: n.start.0.as_u64(),
504                        end: n.end.0.as_u64(),
505                        contents,
506                    }
507                } else {
508                    panic!("Invalid Hunk::FileAdd field add_name: {:?}", add_name);
509                }
510            }
511            Hunk::Edit {
512                change,
513                local,
514                encoding,
515            } => {
516                debug!("edit");
517                PrintableHunk::Edit {
518                    path: local.path.clone(),
519                    line: local.line,
520                    pos: to_printable_pos(hashes, change.inode()),
521                    encoding: encoding.clone(),
522                    change: to_printable_atom(change, hashes),
523                    contents: get_change_contents(changes, change, change_contents)?,
524                }
525            }
526            Hunk::Replacement {
527                change,
528                replacement,
529                local,
530                encoding,
531            } => {
532                debug!("replacement");
533                PrintableHunk::Replace {
534                    path: local.path.clone(),
535                    line: local.line,
536                    pos: to_printable_pos(hashes, change.inode()),
537                    encoding: encoding.clone(),
538                    change: to_printable_edge_map(change, hashes),
539                    replacement: to_printable_new_vertex(replacement, hashes),
540                    change_contents: get_change_contents(changes, change, change_contents)?,
541                    replacement_contents: get_change_contents(
542                        changes,
543                        replacement,
544                        change_contents,
545                    )?,
546                }
547            }
548            Hunk::SolveNameConflict { name, path } => PrintableHunk::SolveNameConflict {
549                path: path.clone(),
550                pos: to_printable_pos(hashes, name.inode()),
551                names: get_deleted_names(changes, name)?,
552                edges: to_printable_edge_map(name, hashes),
553            },
554            Hunk::UnsolveNameConflict { name, path } => PrintableHunk::UnsolveNameConflict {
555                path: path.clone(),
556                pos: to_printable_pos(hashes, name.inode()),
557                names: get_deleted_names(changes, name)?,
558                edges: to_printable_edge_map(name, hashes),
559            },
560            Hunk::SolveOrderConflict { change, local } => {
561                // TODO: pass in the encoding
562                let contents = get_change_contents(changes, change, change_contents)?;
563                let encoding = get_encoding(&contents);
564                PrintableHunk::SolveOrderConflict {
565                    path: local.path.clone(),
566                    line: local.line,
567                    pos: to_printable_pos(hashes, change.inode()),
568                    encoding: encoding.clone(),
569                    change: to_printable_new_vertex(change, hashes),
570                    contents: get_change_contents(changes, change, change_contents)?,
571                }
572            }
573            Hunk::UnsolveOrderConflict { change, local } => {
574                // TODO: pass in the encoding
575                let contents = get_change_contents(changes, change, change_contents)?;
576                let encoding = get_encoding(&contents);
577                PrintableHunk::UnsolveOrderConflict {
578                    path: local.path.clone(),
579                    line: local.line,
580                    pos: to_printable_pos(hashes, change.inode()),
581                    encoding: encoding.clone(),
582                    change: to_printable_edge_map(change, hashes),
583                    contents: get_change_contents(changes, change, change_contents)?,
584                }
585            }
586            Hunk::ResurrectZombies {
587                change,
588                local,
589                encoding,
590            } => PrintableHunk::ResurrectZombies {
591                path: local.path.clone(),
592                line: local.line,
593                pos: to_printable_pos(hashes, change.inode()),
594                encoding: encoding.clone(),
595                change: to_printable_edge_map(change, hashes),
596                contents: get_change_contents(changes, change, change_contents)?,
597            },
598            Hunk::AddRoot { name, .. } => {
599                if let Atom::NewVertex(n) = name {
600                    PrintableHunk::AddRoot {
601                        start: n.start.0.as_u64(),
602                    }
603                } else {
604                    unreachable!()
605                }
606            }
607            Hunk::DelRoot { inode, name } => PrintableHunk::DelRoot {
608                name: to_printable_edge_map(name, hashes),
609                inode: to_printable_edge_map(inode, hashes),
610            },
611        }
612        .write(w)?;
613        Ok(())
614    }
615}
616
617impl Hunk<Option<Hash>, Local> {
618    fn from_printable(
619        updatables: &mut HashMap<usize, crate::InodeUpdate>,
620        contents_: &mut Vec<u8>,
621        changes: &HashMap<usize, Hash>,
622        offsets: &mut HashMap<u64, ChangePosition>,
623        (hunk_id, hunk): (u64, PrintableHunk),
624    ) -> Result<Self, TextDeError> {
625        debug!("from_printable {:?}", hunk);
626        match hunk {
627            PrintableHunk::FileMoveV {
628                path,
629                name,
630                perms,
631                pos,
632                up_context,
633                down_context,
634                del,
635            } => {
636                let mut add = default_newvertex();
637                add.start = ChangePosition(contents_.len().into());
638                add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
639                let meta = FileMetadata {
640                    metadata: InodeMetadata(match perms {
641                        // TODO: deduplicate
642                        PrintablePerms::IsDir => 0o1100,
643                        PrintablePerms::IsExecutable => 0o100,
644                        PrintablePerms::IsFile => 0,
645                    }),
646                    basename: &name,
647                    encoding: None,
648                };
649                meta.write(contents_);
650                add.end = ChangePosition(contents_.len().into());
651                add.up_context = from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
652                add.down_context = from_printable_pos_vec_offsets(changes, offsets, &down_context)?;
653                contents_.push(0);
654
655                Ok(Hunk::FileMove {
656                    add: Atom::NewVertex(add),
657                    del: Atom::EdgeMap(EdgeMap {
658                        inode: from_printable_pos(changes, pos)?,
659                        edges: from_printable_edge_map(&del, changes)?,
660                    }),
661                    path,
662                })
663            }
664            PrintableHunk::FileMoveE {
665                path,
666                pos,
667                add,
668                del,
669            } => {
670                let inode = from_printable_pos(changes, pos)?;
671                Ok(Hunk::FileMove {
672                    add: Atom::EdgeMap(EdgeMap {
673                        inode,
674                        edges: from_printable_edge_map(&add, changes)?,
675                    }),
676                    del: Atom::EdgeMap(EdgeMap {
677                        inode,
678                        edges: from_printable_edge_map(&del, changes)?,
679                    }),
680                    path,
681                })
682            }
683            PrintableHunk::FileAddition {
684                name,
685                parent,
686                perms,
687                encoding,
688                up_context,
689                start,
690                end,
691                contents,
692            } => {
693                let meta = FileMetadata {
694                    metadata: InodeMetadata(match perms {
695                        PrintablePerms::IsDir => 0o1100,
696                        PrintablePerms::IsExecutable => 0o100,
697                        PrintablePerms::IsFile => 0,
698                    }),
699                    basename: &name,
700                    encoding: encoding.clone(),
701                };
702
703                let mut add_name = {
704                    let mut x = default_newvertex();
705                    x.start = ChangePosition(contents_.len().into());
706                    meta.write(contents_);
707                    x.end = ChangePosition(contents_.len().into());
708                    x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
709                    x
710                };
711
712                let add_inode = {
713                    let mut x = default_newvertex();
714                    x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
715                    x.up_context.push(Position {
716                        change: None,
717                        pos: ChangePosition(contents_.len().into()),
718                    });
719
720                    contents_.push(0);
721                    x.start = ChangePosition(contents_.len().into());
722                    x.end = ChangePosition(contents_.len().into());
723                    contents_.push(0);
724                    x
725                };
726
727                if let Entry::Occupied(mut e) = updatables.entry(hunk_id as usize)
728                    && let crate::InodeUpdate::Add { pos, .. } = e.get_mut()
729                {
730                    offsets.insert(pos.0.into(), add_inode.start);
731                    *pos = add_inode.start
732                }
733
734                // context
735                add_name.up_context =
736                    from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
737                offsets.insert(start, add_name.start);
738                offsets.insert(end, add_name.end);
739                offsets.insert(end + 1, add_name.end + 1);
740
741                // contents
742                let contents = if !contents.is_empty() {
743                    let mut x = default_newvertex();
744                    // The `-1` here comes from the extra 0
745                    // padding bytes pushed onto `contents_`.
746                    // TODO: verify this is correct
747                    let inode = Position {
748                        change: None,
749                        pos: ChangePosition((contents_.len() - 1).into()),
750                    };
751                    x.up_context.push(inode);
752                    x.inode = inode;
753                    x.flag = EdgeFlags::BLOCK;
754                    x.start = ChangePosition(contents_.len().into());
755                    contents_.extend(&contents);
756                    x.end = ChangePosition(contents_.len().into());
757                    Some(Atom::NewVertex(x))
758                } else {
759                    None
760                };
761                contents_.push(0);
762
763                Ok(Hunk::FileAdd {
764                    add_name: Atom::NewVertex(add_name),
765                    add_inode: Atom::NewVertex(add_inode),
766                    contents,
767                    path: if parent.is_empty() {
768                        name
769                    } else {
770                        parent + "/" + &name
771                    },
772                    encoding,
773                })
774            }
775            PrintableHunk::FileDel {
776                path,
777                pos,
778                encoding,
779                del_edges,
780                content_edges,
781                contents: _,
782            } => Ok(Hunk::FileDel {
783                del: Atom::EdgeMap(EdgeMap {
784                    edges: from_printable_edge_map(&del_edges, changes)?,
785                    inode: from_printable_pos(changes, pos)?,
786                }),
787                contents: if content_edges.is_empty() {
788                    None
789                } else {
790                    Some(Atom::EdgeMap(EdgeMap {
791                        edges: from_printable_edge_map(&content_edges, changes)?,
792                        inode: from_printable_pos(changes, pos)?,
793                    }))
794                },
795                path,
796                encoding,
797            }),
798            PrintableHunk::FileUndel {
799                path,
800                pos,
801                encoding,
802                undel_edges,
803                content_edges,
804                contents: _,
805            } => Ok(Hunk::FileUndel {
806                undel: Atom::EdgeMap(EdgeMap {
807                    edges: from_printable_edge_map(&undel_edges, changes)?,
808                    inode: from_printable_pos(changes, pos)?,
809                }),
810                contents: if content_edges.is_empty() {
811                    None
812                } else {
813                    Some(Atom::EdgeMap(EdgeMap {
814                        edges: from_printable_edge_map(&content_edges, changes)?,
815                        inode: from_printable_pos(changes, pos)?,
816                    }))
817                },
818                path,
819                encoding,
820            }),
821            PrintableHunk::Edit {
822                path,
823                line,
824                pos,
825                encoding,
826                change,
827                contents,
828            } => {
829                let inode = from_printable_pos(changes, pos)?;
830                let change = match change {
831                    PrintableAtom::NewVertex(new_vertex) => {
832                        assert!(!contents.is_empty());
833                        let mut x = default_newvertex();
834                        x.inode = inode;
835                        x.flag = EdgeFlags::BLOCK;
836                        x.up_context = from_printable_pos_vec_offsets(
837                            changes,
838                            offsets,
839                            &new_vertex.up_context,
840                        )?;
841                        x.down_context = from_printable_pos_vec_offsets(
842                            changes,
843                            offsets,
844                            &new_vertex.down_context,
845                        )?;
846                        x.start = ChangePosition(contents_.len().into());
847                        contents_.extend(&contents);
848                        x.end = ChangePosition(contents_.len().into());
849                        contents_.push(0);
850                        Atom::NewVertex(x)
851                    }
852                    PrintableAtom::Edges(edges) => Atom::EdgeMap(EdgeMap {
853                        edges: from_printable_edge_map(&edges, changes)?,
854                        inode,
855                    }),
856                };
857
858                Ok(Hunk::Edit {
859                    change,
860                    local: Local { path, line },
861                    encoding,
862                })
863            }
864            PrintableHunk::Replace {
865                path,
866                line,
867                pos,
868                encoding,
869                change,
870                replacement,
871                change_contents: _,
872                replacement_contents,
873            } => {
874                let inode = from_printable_pos(changes, pos)?;
875
876                let replacement = {
877                    let mut x = default_newvertex();
878                    x.inode = inode;
879                    x.flag = EdgeFlags::BLOCK;
880                    x.up_context =
881                        from_printable_pos_vec_offsets(changes, offsets, &replacement.up_context)?;
882                    x.down_context = from_printable_pos_vec_offsets(
883                        changes,
884                        offsets,
885                        &replacement.down_context,
886                    )?;
887                    x.start = ChangePosition(contents_.len().into());
888                    contents_.extend(&replacement_contents);
889                    x.end = ChangePosition(contents_.len().into());
890                    Atom::NewVertex(x)
891                };
892                contents_.push(0);
893
894                Ok(Hunk::Replacement {
895                    change: Atom::EdgeMap(EdgeMap {
896                        edges: from_printable_edge_map(&change, changes)?,
897                        inode,
898                    }),
899                    replacement,
900                    local: Local { path, line },
901                    encoding,
902                })
903            }
904            PrintableHunk::SolveNameConflict {
905                path,
906                pos,
907                names: _,
908                edges,
909            } => Ok(Hunk::SolveNameConflict {
910                name: Atom::EdgeMap(EdgeMap {
911                    inode: from_printable_pos(changes, pos)?,
912                    edges: from_printable_edge_map(&edges, changes)?,
913                }),
914                path,
915            }),
916            PrintableHunk::UnsolveNameConflict {
917                path,
918                pos,
919                names: _,
920                edges,
921            } => Ok(Hunk::UnsolveNameConflict {
922                name: Atom::EdgeMap(EdgeMap {
923                    inode: from_printable_pos(changes, pos)?,
924                    edges: from_printable_edge_map(&edges, changes)?,
925                }),
926                path,
927            }),
928            PrintableHunk::SolveOrderConflict {
929                path,
930                line,
931                pos,
932                encoding: _,
933                change,
934                contents,
935            } => {
936                // If `contents.is_empty()`, we still need to add a
937                // new empty vertex, so the following is ok:
938                let mut c = default_newvertex();
939                c.inode = from_printable_pos(changes, pos)?;
940                c.up_context =
941                    from_printable_pos_vec_offsets(changes, offsets, &change.up_context)?;
942                c.down_context =
943                    from_printable_pos_vec_offsets(changes, offsets, &change.down_context)?;
944                c.start = ChangePosition(contents_.len().into());
945                c.end = ChangePosition((contents_.len() as u64 + change.end - change.start).into());
946                offsets.insert(change.end, c.end);
947                c.start = ChangePosition(contents_.len().into());
948                contents_.extend(&contents);
949                c.end = ChangePosition(contents_.len().into());
950                contents_.push(0);
951
952                Ok(Hunk::SolveOrderConflict {
953                    change: Atom::NewVertex(c),
954                    local: Local { path, line },
955                })
956            }
957            PrintableHunk::UnsolveOrderConflict {
958                path,
959                line,
960                pos,
961                encoding: _,
962                change,
963                contents: _,
964            } => Ok(Hunk::UnsolveOrderConflict {
965                change: Atom::EdgeMap(EdgeMap {
966                    edges: from_printable_edge_map(&change, changes)?,
967                    inode: from_printable_pos(changes, pos)?,
968                }),
969                local: Local { path, line },
970            }),
971            PrintableHunk::ResurrectZombies {
972                path,
973                line,
974                pos,
975                encoding,
976                change,
977                contents: _,
978            } => Ok(Hunk::ResurrectZombies {
979                change: Atom::EdgeMap(EdgeMap {
980                    edges: from_printable_edge_map(&change, changes)?,
981                    inode: from_printable_pos(changes, pos)?,
982                }),
983                local: Local { path, line },
984                encoding,
985            }),
986            PrintableHunk::AddRoot { start } => {
987                contents_.push(0);
988                let root_inode = Position {
989                    change: Some(Hash::None),
990                    pos: ChangePosition(contents_.len().into()),
991                };
992                contents_.push(0);
993                let inode = contents_.len();
994                contents_.push(0);
995                if let Entry::Occupied(mut e) = updatables.entry(hunk_id as usize)
996                    && let crate::InodeUpdate::Add { pos, .. } = e.get_mut()
997                {
998                    offsets.insert(pos.0.into(), ChangePosition((start + 1).into()));
999                    *pos = ChangePosition((start + 1).into())
1000                }
1001                Ok(Hunk::AddRoot {
1002                    name: Atom::NewVertex(NewVertex {
1003                        up_context: vec![root_inode],
1004                        down_context: Vec::new(),
1005                        start: ChangePosition(start.into()),
1006                        end: ChangePosition(start.into()),
1007                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1008                        inode: root_inode,
1009                    }),
1010                    inode: Atom::NewVertex(NewVertex {
1011                        up_context: vec![Position {
1012                            change: None,
1013                            pos: ChangePosition(start.into()),
1014                        }],
1015                        down_context: Vec::new(),
1016                        start: ChangePosition(inode.into()),
1017                        end: ChangePosition(inode.into()),
1018                        flag: EdgeFlags::FOLDER | EdgeFlags::BLOCK,
1019                        inode: root_inode,
1020                    }),
1021                })
1022            }
1023            PrintableHunk::DelRoot { name, inode } => {
1024                let root_inode = PrintablePos(1, 0);
1025                Ok(Hunk::DelRoot {
1026                    name: Atom::EdgeMap(EdgeMap {
1027                        edges: from_printable_edge_map(&name, changes)?,
1028                        inode: from_printable_pos(changes, root_inode)?,
1029                    }),
1030                    inode: Atom::EdgeMap(EdgeMap {
1031                        edges: from_printable_edge_map(&inode, changes)?,
1032                        inode: from_printable_pos(changes, root_inode)?,
1033                    }),
1034                })
1035            }
1036        }
1037    }
1038}
1039
1040pub fn default_newvertex() -> NewVertex<Option<Hash>> {
1041    NewVertex {
1042        start: ChangePosition(L64(0)),
1043        end: ChangePosition(L64(0)),
1044        flag: EdgeFlags::empty(),
1045        up_context: Vec::new(),
1046        down_context: Vec::new(),
1047        inode: Position {
1048            change: Some(Hash::None),
1049            pos: ChangePosition(L64(0)),
1050        },
1051    }
1052}
1053
1054// TODO: rename
1055pub fn from_printable_pos_vec_offsets(
1056    changes: &HashMap<usize, Hash>,
1057    offsets: &HashMap<u64, ChangePosition>,
1058    s: &[PrintablePos],
1059) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
1060    let mut v = Vec::new();
1061    for PrintablePos(change, pos) in s {
1062        let pos = if *change == 0 {
1063            if let Some(&pos) = offsets.get(pos) {
1064                pos
1065            } else {
1066                debug!("inconsistent change: {:?} {:?}", s, offsets);
1067                return Err(TextDeError::MissingPosition(*pos));
1068            }
1069        } else {
1070            ChangePosition(L64(pos.to_le()))
1071        };
1072        v.push(Position {
1073            change: change_ref(changes, *change)?,
1074            pos,
1075        })
1076    }
1077    Ok(v)
1078}
1079
1080fn change_ref(changes: &HashMap<usize, Hash>, change: usize) -> Result<Option<Hash>, TextDeError> {
1081    debug!("change_ref {:?} {:?}", changes, change);
1082    if change == 0 {
1083        Ok(None)
1084    } else if change == 1 {
1085        Ok(Some(Hash::None))
1086    } else if let Some(&c) = changes.get(&change) {
1087        Ok(Some(c))
1088    } else {
1089        Err(TextDeError::MissingChange(change))
1090    }
1091}
1092
1093pub fn from_printable_pos(
1094    changes: &HashMap<usize, Hash>,
1095    pos: PrintablePos,
1096) -> Result<Position<Option<Hash>>, TextDeError> {
1097    Ok(Position {
1098        change: change_ref(changes, pos.0)?,
1099        pos: ChangePosition(L64(pos.1.to_le())),
1100    })
1101}
1102
1103pub trait WriteChangeLine: std::io::Write {
1104    fn write_change_line(&mut self, pref: &str, contents: &str) -> Result<(), std::io::Error> {
1105        writeln!(self, "{} {}", pref, contents)
1106    }
1107    fn write_change_line_binary(
1108        &mut self,
1109        pref: &str,
1110        contents: &[u8],
1111    ) -> Result<(), std::io::Error> {
1112        writeln!(self, "{}b{}", pref, data_encoding::BASE64.encode(contents))
1113    }
1114}
1115
1116impl WriteChangeLine for &mut Vec<u8> {}
1117impl WriteChangeLine for &mut std::io::Stderr {}
1118impl WriteChangeLine for &mut std::io::Stdout {}
1119
1120pub fn get_change_contents<C: ChangeStore>(
1121    changes: &C,
1122    change: &Atom<Option<Hash>>,
1123    change_contents: &[u8],
1124) -> Result<Vec<u8>, TextSerError<C::Error>> {
1125    debug!("get_change_contents {:?}", change);
1126    match change {
1127        Atom::NewVertex(n) => Ok(change_contents[n.start.us()..n.end.us()].to_vec()),
1128        Atom::EdgeMap(n) if n.edges.is_empty() => Err(TextSerError::InvalidChange),
1129        Atom::EdgeMap(n) if n.edges[0].flag.contains(EdgeFlags::DELETED) => {
1130            let mut buf = Vec::new();
1131            let mut z = 0;
1132            let mut current = None;
1133            for e in n.edges.iter() {
1134                if Some(e.to) == current {
1135                    continue;
1136                }
1137                let sz = e.to.end - e.to.start;
1138                buf.resize(z + sz, 0);
1139                changes
1140                    .get_contents_ext(e.to, &mut buf[z..])
1141                    .map_err(TextSerError::C)?;
1142                z += sz;
1143                current = Some(e.to)
1144            }
1145            Ok(buf)
1146        }
1147        _ => Ok(Vec::new()),
1148    }
1149}
1150
1151pub fn get_deleted_names<C: ChangeStore>(
1152    changes: &C,
1153    del: &Atom<Option<Hash>>,
1154) -> Result<Vec<String>, TextSerError<C::Error>> {
1155    let mut res = Vec::new();
1156    let mut h = HashSet::new();
1157    if let Atom::EdgeMap(e) = del {
1158        let mut tmp = Vec::new();
1159        for d in e.edges.iter() {
1160            if !h.insert(d.to) {
1161                continue;
1162            }
1163            tmp.resize(d.to.end - d.to.start, 0);
1164            changes
1165                .get_contents_ext(d.to, &mut tmp)
1166                .map_err(TextSerError::C)?;
1167            if !tmp.is_empty() {
1168                let FileMetadata { basename: name, .. } = FileMetadata::read(&tmp);
1169                res.push(name.to_string());
1170            }
1171        }
1172    }
1173    Ok(res)
1174}
1175
1176pub fn to_printable_pos(
1177    hashes: &HashMap<Hash, usize>,
1178    pos: Position<Option<Hash>>,
1179) -> PrintablePos {
1180    let change = if let Some(Hash::None) = pos.change {
1181        1
1182    } else if let Some(ref c) = pos.change {
1183        *hashes.get(c).unwrap()
1184    } else {
1185        0
1186    };
1187    PrintablePos(change, pos.pos.0.as_u64())
1188}
1189
1190pub fn to_printable_pos_vec(
1191    hashes: &HashMap<Hash, usize>,
1192    pos: &[Position<Option<Hash>>],
1193) -> Vec<PrintablePos> {
1194    pos.iter().map(|c| to_printable_pos(hashes, *c)).collect()
1195}