Re-implement change printing and parsing

[?]
FHRXP5Jnb2MWLDrPrnLnkN2ryWcGCo6CRr1dXR9FW2YA
Sep 12, 2021, 7:01 PM
5FI6SBEZ6RERERUAIWQJVAY66BEZ7YQOYOUNK2DPOLRGS2X326RAC

Dependencies

  • [2] RUBSM5DR Fixing a bug when outputting changes in text format
  • [3] YDMAIJ5V Fixing the format of text changes (file additions under a new directory were not always accepted by the parser)
  • [4] V4T4SC7O Testing binary diff
  • [5] LWBBN2IB Fixing a bug with double-quotes in the text format serialisation
  • [6] IUH7IMWE Contributor license agreements
  • [7] YN63NUZO Sanakirja 1.0
  • [8] X243Z3Y5 Recording only the required metadata (can even be changed later!)
  • [9] VO5OQW4W Removing anyhow in libpijul
  • [10] EAZ45JTF Fixing a bug in change printing
  • [11] 6HNRL5RT detect non-utf8 text files
  • [12] PDTUHOMV fix left over conflicts
  • [13] 246V5TYI decode existing files
  • [14] TVVW53HZ Conflict resolution
  • [15] VMOYG7MK text file decoding for new files
  • [16] SGXOEWHU Adding a patched chardetng (temporarily)
  • [17] FXEDPLRI Resurrecting tests, and type cleanup (no need for Arc<RwLock<…>> anymore)
  • [18] KJDQ2WOM Fixing the parsing of section headers in the text change format
  • [19] 6YMDOZIB Refactoring apply
  • [20] RRCSHAYZ Formatting
  • [21] 73NW2X2M Returning a parse error instead of panicking when parsing a text change
  • [22] NE4A4WUK Parsing of file addition metadata
  • [23] NG3Z3DOK roundtrip text encoding when recording
  • [24] CFNFIUJV Parsing the correct flags for new vertices
  • [25] ZSF3YFZT encoded file deletion
  • [26] YTQS4ES3 Fixing a parsing problem (related to permissions), and the associated permissions
  • [27] LV34DUJY Formatting
  • [28] KL5737GR Text format: correct position of encoding for deletion/undeletion
  • [29] 65S67T3E Parsing \\ for empty additions (rare problem)
  • [30] NYOF5766 track file encoding in the record, including change text for file adds
  • [31] XSEODPNE Fixing conflicts
  • [32] 6CZYYOG7 Faster guessing of encoding
  • [33] CCFJ7VO3 Renaming "Record" to "Hunk" in the changes
  • [34] 3S6LU2U5 abstract out FileMetadata (de)serialistion
  • [35] UM5DLRPB store new non-UTF-8 files raw and decode to deplay the contents
  • [36] CIEUBH46 Fixing an index-out-of-bounds error when serialising bad changes
  • [37] BZCGXVS7 Fixing two bugs around conflicts on the last line, where invalid patches were produced (first bug) and applied (second bug)
  • [38] CCLLB7OI Upgrading to Sanakirja 0.15 + version bump
  • [39] VYHHOEYH Versions and formatting
  • [40] IIV3EL2X Cleanup, formatting, and fixing the Git feature
  • [41] XR7MNOMU file encoding in updates
  • [42] 4DNDMC7I Fixing a number of bugs related to encodings (extra newlines + misdetection in linux2x)
  • [43] SFJ3XRTF Proper escaping of UTF-8 filenames in the patch text format
  • [44] ZRUPLBBT Colours in diff and change: separating concerns and dependencies
  • [45] EQLDTLXV Fixing a bug with empty new files, and another one with empty replacements
  • [46] I24UEJQL Various post-fire fixes
  • [47] Q3GU26WD merge with changes from sanakirja v1.1.2
  • [*] SXEYMYF7 Fixing the bad changes in history (unfortunately, by rebooting).
  • [*] IACED7RW text_encoding module
  • [*] SHSJ3Y53 Fixing more tests
  • [*] FYUDBQ3C Formatting changes + version bump
  • [*] TNN56XYK libpijul alpha.43
  • [*] QJXNUQFJ Solving conflicts

Change contents

  • replacement in libpijul/src/working_copy/mod.rs at line 31
    [7.171][7.0:127]()
    let (encoding, score) = detector.guess_assess(None, true);
    if score {
    Ok(Some(Encoding(encoding)))
    [7.171]
    [7.905]
    if let Some(e) = detector.get_valid(None, true, &buffer[init..]) {
    Ok(Some(Encoding(e)))
  • edit in libpijul/src/text_encoding.rs at line 5
    [50.86]
    [50.86]
    #[cfg(test)]
    use quickcheck::{Arbitrary, Gen};
  • edit in libpijul/src/text_encoding.rs at line 44
    [50.895]
    [50.895]
    #[cfg(test)]
    // TODO: more encodings. This is not critical, since it is not used ATM.
    // But the instance is needed.
    impl Arbitrary for Encoding {
    fn arbitrary(g: &mut Gen) -> Self {
    g.choose(&[
    Encoding(encoding_rs::UTF_8),
    Encoding(encoding_rs::SHIFT_JIS),
    ])
    .unwrap()
    .clone()
    }
    }
  • file addition: text_changes.rs (----------)
    [49.248792]
    use crate::change::*;
    use crate::changestore::*;
    use crate::pristine::*;
    use crate::record::*;
    use crate::working_copy::*;
    use crate::*;
    use std::io::Write;
    use super::*;
    fn hash_mismatch(change: &Change) -> Result<(), anyhow::Error> {
    env_logger::try_init().unwrap_or(());
    use crate::change::*;
    let mut buf = tempfile::NamedTempFile::new()?;
    let mut h = change.serialize(&mut buf)?;
    match h {
    crate::pristine::Hash::Blake3(ref mut h) => h[0] = h[0].wrapping_add(1),
    _ => unreachable!(),
    }
    match Change::deserialize(buf.path().to_str().unwrap(), Some(&h)) {
    Err(ChangeError::ChangeHashMismatch { .. }) => {}
    _ => unreachable!(),
    }
    let f = ChangeFile::open(h, buf.path().to_str().unwrap())?;
    assert_eq!(f.hashed(), &change.hashed);
    Ok(())
    }
    #[test]
    fn hash_mism() -> Result<(), anyhow::Error> {
    env_logger::try_init().unwrap_or(());
    let contents = b"a\nb\nc\nd\ne\nf\n";
    let repo = working_copy::memory::Memory::new();
    let store = changestore::memory::Memory::new();
    repo.add_file("file", contents.to_vec());
    repo.add_file("file2", contents.to_vec());
    let env = pristine::sanakirja::Pristine::new_anon()?;
    let txn = env.arc_txn_begin().unwrap();
    let mut channel = txn.write().open_or_create_channel("main")?;
    txn.write().add_file("file", 0)?;
    txn.write().add_file("file2", 0)?;
    let mut state = Builder::new();
    state
    .record(
    txn.clone(),
    Algorithm::Myers,
    channel.clone(),
    &repo,
    &store,
    "",
    0,
    )
    .unwrap();
    let rec = state.finish();
    let changes: Vec<_> = rec
    .actions
    .into_iter()
    .map(|rec| rec.globalize(&*txn.read()).unwrap())
    .collect();
    info!("changes = {:?}", changes);
    let change0 = crate::change::Change::make_change(
    &*txn.read(),
    &channel,
    changes,
    std::mem::take(&mut *rec.contents.lock()),
    crate::change::ChangeHeader {
    message: "test".to_string(),
    authors: vec![],
    description: None,
    timestamp: chrono::Utc::now(),
    },
    Vec::new(),
    )
    .unwrap();
    let hash0 = store.save_change(&change0)?;
    apply::apply_local_change(
    &mut *txn.write(),
    &mut channel,
    &change0,
    &hash0,
    &rec.updatables,
    )?;
    hash_mismatch(&change0)?;
    Ok(())
    }
    #[cfg(feature = "text-changes")]
    #[test]
    fn text_changes() -> Result<(), anyhow::Error> {
    env_logger::try_init().unwrap_or(());
    let contents = b"a\nb\nc\nd\ne\nf\n";
    let repo = working_copy::memory::Memory::new();
    let store = changestore::memory::Memory::new();
    repo.add_file("file", contents.to_vec());
    repo.add_file("file2", contents.to_vec());
    let env = pristine::sanakirja::Pristine::new_anon()?;
    let txn = env.arc_txn_begin().unwrap();
    let channel = txn.write().open_or_create_channel("main")?;
    txn.write().add_file("file", 0)?;
    txn.write().add_file("file2", 0)?;
    let h0 = record_all(&repo, &store, &txn, &channel, "")?;
    let change0 = store.get_change(&h0).unwrap();
    text_test(&store, &change0, h0);
    write!(repo.write_file("file")?, "a\nx\nc\ne\ny\nf\n")?;
    let h1 = record_all(&repo, &store, &txn, &channel, "")?;
    let change1 = store.get_change(&h1).unwrap();
    text_test(&store, &change1, h1);
    repo.remove_path("file2", false)?;
    let h2 = record_all(&repo, &store, &txn, &channel, "")?;
    let change2 = store.get_change(&h2).unwrap();
    text_test(&store, &change2, h2);
    repo.rename("file", "file3")?;
    txn.write().move_file("file", "file3", 0)?;
    let h3 = record_all(&repo, &store, &txn, &channel, "")?;
    let change3 = store.get_change(&h3).unwrap();
    text_test(&store, &change3, h3);
    // name conflicts
    let env2 = pristine::sanakirja::Pristine::new_anon()?;
    let txn2 = env2.arc_txn_begin().unwrap();
    let channel2 = txn2.write().open_or_create_channel("main")?;
    let repo2 = working_copy::memory::Memory::new();
    apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h0)?;
    apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h1)?;
    apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h2)?;
    output::output_repository_no_pending(&repo2, &store, &txn2, &channel2, "", true, None, 1, 0)?;
    repo2.rename("file", "file4")?;
    txn2.write().move_file("file", "file4", 0)?;
    record_all(&repo2, &store, &txn2, &channel2, "")?;
    apply::apply_change(&store, &mut *txn2.write(), &mut *channel2.write(), &h3)?;
    output::output_repository_no_pending(&repo2, &store, &txn2, &channel2, "", true, None, 1, 0)?;
    let h = record_all(&repo2, &store, &txn2, &channel2, "")?;
    let solution = store.get_change(&h).unwrap();
    text_test(&store, &solution, h);
    Ok(())
    }
    fn text_test<C: ChangeStore>(c: &C, change0: &Change, h: Hash) {
    let mut v = Vec::new();
    // let channel = channel.borrow();
    change0.write(c, Some(h), true, &mut v).unwrap();
    println!("{}", String::from_utf8_lossy(&v));
    for i in std::str::from_utf8(&v).unwrap().lines() {
    debug!("{}", i);
    }
    let change1 = Change::read(std::io::Cursor::new(&v[..]), &mut HashMap::default()).unwrap();
    if change0.header != change1.header {
    error!("header: {:#?} != {:#?}", change0.header, change1.header);
    }
    if change0.dependencies != change1.dependencies {
    error!(
    "deps: {:#?} != {:#?}",
    change0.dependencies, change1.dependencies
    );
    }
    if change0.extra_known != change1.extra_known {
    error!(
    "extra: {:#?} != {:#?}",
    change0.extra_known, change1.extra_known
    );
    }
    if change0.metadata != change1.metadata {
    error!("meta: {:#?} != {:#?}", change0.metadata, change1.metadata);
    }
    if change0.changes != change1.changes {
    if change0.changes.len() != change1.changes.len() {
    trace!("change0.changes = {:#?}", change0.changes);
    trace!("change1.changes = {:#?}", change1.changes);
    } else {
    for (a, b) in change0.changes.iter().zip(change1.changes.iter()) {
    trace!("change0: {:#?}", a);
    trace!("change1: {:#?}", b);
    for (a, b) in a.iter().zip(b.iter()) {
    if a != b {
    error!("change0 -> {:#?}", a);
    error!("change1 -> {:#?}", b);
    }
    }
    }
    }
    }
    if change0.contents != change1.contents {
    error!("change0.contents = {:?}", change0.contents);
    error!("change1.contents = {:?}", change1.contents);
    }
    assert_eq!(change0, &change1);
    }
    quickcheck! {
    fn string_roundtrip(s1: String) -> () {
    let mut w = Vec::new();
    write!(&mut w, "{}", Escaped(&s1)).unwrap();
    let utf = std::str::from_utf8(&w).unwrap();
    let (_, s2) = parse_string(&utf).unwrap();
    assert_eq!(s1, s2);
    }
    }
    #[test]
    #[cfg(feature = "text-changes")]
    fn hunk_roundtrip_test() {
    fn go(hunk: PrintableHunk) {
    let mut w = Vec::new();
    hunk.write(&mut &mut w).unwrap();
    let s = std::str::from_utf8(&w).unwrap();
    match parse_hunk(&s) {
    Ok((_, hunk2)) => {
    if hunk != hunk2 {
    eprintln!("## Hunk: \n\n{}", s);
    eprintln!("In: {:?}\n\nOut: {:?}\n", hunk, hunk2);
    panic!("Hunks don't match.");
    }
    }
    Err(e) => {
    eprintln!("Hunk: \n\n{}", s);
    panic!("Can't parse hunk. Error: {:?}", e);
    }
    }
    }
    // Create a new thread with custom stack size
    std::thread::Builder::new()
    // frequently blown by shrinking :(
    // You can disable shrinking by commenting out the shrink function
    // for PrintableHunk
    .stack_size(20 * 1024 * 1024)
    .spawn(move || {
    quickcheck::QuickCheck::new()
    .tests(1000)
    .gen(quickcheck::Gen::new(3))
    .max_tests(100000000)
    .quickcheck(go as fn(PrintableHunk) -> ());
    })
    .unwrap()
    .join()
    .unwrap();
    }
  • edit in libpijul/src/tests/mod.rs at line 22
    [51.842]
    [7.19976]
    mod text_changes;
  • edit in libpijul/src/pristine/vertex.rs at line 3
    [7.7480]
    [49.525323]
    #[cfg(test)]
    use quickcheck::{Arbitrary, Gen};
  • edit in libpijul/src/pristine/vertex.rs at line 113
    [7.56549]
    [7.56549]
    }
    }
    #[cfg(test)]
    impl Arbitrary for ChangePosition {
    fn arbitrary(g: &mut Gen) -> Self {
    ChangePosition(L64(u64::arbitrary(g)))
  • edit in libpijul/src/pristine/vertex.rs at line 121
    [7.56555]
    [7.56555]
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    Box::new(self.0 .0.shrink().map(|x| ChangePosition(L64(x))))
    }
  • edit in libpijul/src/lib.rs at line 13
    [49.717605]
    [49.717605]
    #[cfg(test)]
    #[macro_use]
    extern crate quickcheck;
  • edit in libpijul/src/chardetng/mod.rs at line 2917
    [7.126524]
    [7.128268]
    }
    /// Get a valid encoding. This is checked by round-tripping the encoding on `buffer`.
    /// TODO: This is a hack. Make it better. Make it faster.
    pub fn get_valid(
    &self,
    tld: Option<&[u8]>,
    allow_utf8: bool,
    buffer: &[u8],
    ) -> Option<&'static Encoding> {
    if let (encoding, true) = self.guess_assess(tld, allow_utf8) {
    if let (s, e, false) = encoding.decode(buffer) {
    if encoding.encode(&s).0 == buffer {
    return Some(e);
    }
    }
    }
    None
  • edit in libpijul/src/change.rs at line 11
    [7.36085]
    [7.36085]
    #[cfg(feature = "text-changes")]
    mod parse;
    #[cfg(feature = "text-changes")]
    mod printable;
  • edit in libpijul/src/change.rs at line 17
    [7.36136]
    [7.88785]
    pub use parse::*; // for testing
    pub use printable::*; // for testing
  • edit in libpijul/src/change/text_changes.rs at line 4
    [7.38111][7.2436:2458]()
    use regex::Captures;
  • edit in libpijul/src/change/text_changes.rs at line 6
    [7.1716]
    [7.1716]
    use crate::change::parse::*;
    use crate::change::printable::*;
  • edit in libpijul/src/change/text_changes.rs at line 16
    [7.38280]
    [7.0]
    #[error(transparent)]
    Nom(#[from] nom::Err<nom::error::Error<String>>),
  • replacement in libpijul/src/change/text_changes.rs at line 169
    [7.43112][7.43112:43188]()
    let (mut change, extra_dependencies) = Self::read_(r, updatables)?;
    [7.43112]
    [7.122912]
    let (mut change, extra_dependencies) = Self::read_new(r, updatables)?;
  • replacement in libpijul/src/change/text_changes.rs at line 182
    [7.43584][7.43584:43626]()
    Ok(Self::read_(r, updatables)?.0)
    [7.43584]
    [7.43626]
    Ok(Self::read_new(r, updatables)?.0)
  • replacement in libpijul/src/change/text_changes.rs at line 185
    [7.43633][7.43633:43659]()
    fn read_<R: BufRead>(
    [7.43633]
    [7.43659]
    fn read_new<R: BufRead>(
  • replacement in libpijul/src/change/text_changes.rs at line 189
    [7.43792][7.43792:43885]()
    use self::text_changes::*;
    let mut section = Section::Header(String::new());
    [7.43792]
    [7.43885]
    // read the data
    // TODO: make this streaming
    let mut s = String::new();
    r.read_to_string(&mut s)?;
    let i = &s;
    // parse header
    let (i, m_header) = parse_header(i).map_err(|e| e.to_owned())?;
    let header = m_header?;
    dbg!(&header);
    // parse dependencies
    let (i, deps) = parse_dependencies(i).map_err(|e| e.to_owned())?;
    dbg!(&deps);
    // parse hunks
    let (_, hunks) = parse_hunks(i).map_err(|e| e.to_owned())?;
    dbg!(&hunks);
    Change::update(header, deps, hunks, updatables)
    }
    fn update(
    header: ChangeHeader,
    dependencies: Vec<PrintableDep>,
    hunks: Vec<(u64, PrintableHunk)>,
    updatables: &mut HashMap<usize, crate::InodeUpdate>,
    ) -> Result<(Self, HashSet<Hash>), TextDeError> {
    // TODO: get rid of this whole default change if possible
  • replacement in libpijul/src/change/text_changes.rs at line 222
    [7.44023][7.44023:44256]()
    header: ChangeHeader {
    authors: Vec::new(),
    message: String::new(),
    description: None,
    timestamp: chrono::Utc::now(),
    },
    [7.44023]
    [7.44256]
    header,
  • edit in libpijul/src/change/text_changes.rs at line 231
    [7.44550][7.44550:45553]()
    };
    let conclude_section = |change: &mut Change,
    section: Section,
    contents: &mut Vec<u8>|
    -> Result<(), TextDeError> {
    match section {
    Section::Header(ref s) => {
    debug!("header = {:?}", s);
    change.header = toml::de::from_str(&s)?;
    Ok(())
    }
    Section::Deps => Ok(()),
    Section::Changes {
    mut changes,
    current,
    ..
    } => {
    if has_newvertices(&current) {
    contents.push(0)
    }
    if let Some(c) = current {
    debug!("next action = {:?}", c);
    changes.push(c)
    }
    change.changes = changes;
    Ok(())
    }
    }
  • replacement in libpijul/src/change/text_changes.rs at line 232
    [7.45564][7.45564:45638]()
    let mut h = String::new();
    let mut contents = Vec::new();
    [7.45564]
    [7.89272]
    // process dependencies
  • replacement in libpijul/src/change/text_changes.rs at line 236
    [7.89372][7.45730:45994](),[7.45730][7.45730:45994](),[7.45994][7.8546:8592](),[7.8592][7.46042:46248](),[7.46042][7.46042:46248](),[7.46248][7.89373:89426](),[7.89426][7.46297:46674](),[7.46297][7.46297:46674]()
    while r.read_line(&mut h)? > 0 {
    debug!("h = {:?}", h);
    if h == Self::DEPS_LINE {
    let section = std::mem::replace(&mut section, Section::Deps);
    conclude_section(&mut change, section, &mut contents)?;
    } else if h == Self::HUNKS_LINE {
    let section = std::mem::replace(
    &mut section,
    Section::Changes {
    changes: Vec::new(),
    current: None,
    offsets: HashMap::default(),
    },
    );
    conclude_section(&mut change, section, &mut contents)?;
    } else {
    use regex::Regex;
    lazy_static! {
    static ref DEPS: Regex = Regex::new(r#"\[(\d*|\*)\](\+| ) *(\S*)"#).unwrap();
    static ref KNOWN: Regex = Regex::new(r#"(\S*)"#).unwrap();
    [7.89372]
    [7.46674]
    for dep in dependencies {
    let hash = Hash::from_base32(dep.hash.as_bytes()).unwrap();
    match dep.type_ {
    DepType::Numbered(n, false) => {
    change.hashed.dependencies.push(hash);
    deps.insert(n, hash);
  • replacement in libpijul/src/change/text_changes.rs at line 243
    [7.46692][7.46692:47781](),[7.47781][7.8593:8689](),[7.8689][7.47879:48064](),[7.47879][7.47879:48064]()
    match section {
    Section::Header(ref mut s) => s.push_str(&h),
    Section::Deps => {
    if let Some(d) = DEPS.captures(&h) {
    let hash = Hash::from_base32(d[3].as_bytes()).unwrap();
    if let Ok(n) = d[1].parse() {
    if &d[2] == " " {
    change.hashed.dependencies.push(hash);
    }
    deps.insert(n, hash);
    } else if &d[1] == "*" {
    change.hashed.extra_known.push(hash);
    } else {
    extra_dependencies.insert(hash);
    }
    }
    }
    Section::Changes {
    ref mut current,
    ref mut changes,
    ref mut offsets,
    } => {
    if let Some(next) =
    Hunk::read(updatables, current, &mut contents, &deps, offsets, &h)?
    {
    debug!("next action = {:?}", next);
    changes.push(next)
    }
    }
    [7.46692]
    [7.48064]
    DepType::Numbered(n, true) => {
    deps.insert(n, hash);
    }
    DepType::ExtraKnown => {
    change.hashed.extra_known.push(hash);
    }
    DepType::ExtraUnknown => {
    extra_dependencies.insert(hash);
  • replacement in libpijul/src/change/text_changes.rs at line 253
    [7.48096][7.48096:48119]()
    h.clear();
    [7.48096]
    [7.48119]
    }
    // process hunks
    let mut contents = Vec::new();
    let mut offsets = HashMap::default();
    for (n, hunk) in hunks {
    let res =
    Hunk::from_printable(updatables, &mut contents, &deps, &mut offsets, (n, hunk))?;
    change.hashed.changes.push(res);
  • edit in libpijul/src/change/text_changes.rs at line 263
    [7.48129][7.48129:48193]()
    conclude_section(&mut change, section, &mut contents)?;
  • replacement in libpijul/src/change/text_changes.rs at line 273
    [7.3140][7.3140:3177]()
    const BINARY_LABEL: &str = "binary";
    [7.3140]
    [7.127]
    pub fn to_printable_new_vertex(
    atom: &Atom<Option<Hash>>,
    hashes: &HashMap<Hash, usize>,
    ) -> PrintableNewVertex {
    if let PrintableAtom::NewVertex(v) = to_printable_atom(atom, hashes) {
    v
    } else {
    panic!("PrintableAtom::NewVertex expected here")
    }
    }
  • replacement in libpijul/src/change/text_changes.rs at line 284
    [7.128][7.128:157]()
    struct Escaped<'a>(&'a str);
    [7.128]
    [7.157]
    pub fn to_printable_edge_map(
    atom: &Atom<Option<Hash>>,
    hashes: &HashMap<Hash, usize>,
    ) -> Vec<PrintableEdge> {
    if let PrintableAtom::Edges(v) = to_printable_atom(atom, hashes) {
    v
    } else {
    panic!("PrintableAtom::Edges expected here")
    }
    }
  • replacement in libpijul/src/change/text_changes.rs at line 295
    [7.158][7.158:599]()
    impl<'a> std::fmt::Display for Escaped<'a> {
    fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
    write!(fmt, "\"")?;
    for c in self.0.chars() {
    if c == '"' {
    write!(fmt, "\\{}", c)?
    } else if c == '\\' {
    write!(fmt, "\\\\")?
    } else {
    write!(fmt, "{}", c)?
    }
    }
    write!(fmt, "\"")?;
    Ok(())
    [7.158]
    [7.599]
    /// Panics if the Atom is not an EdgeMap
    fn to_printable_atom(atom: &Atom<Option<Hash>>, hashes: &HashMap<Hash, usize>) -> PrintableAtom {
    match atom {
    Atom::NewVertex(ref new_vertex) => PrintableAtom::NewVertex(PrintableNewVertex {
    up_context: new_vertex
    .up_context
    .iter()
    .map(|c| to_printable_pos(hashes, *c))
    .collect(),
    start: new_vertex.start.0 .0,
    end: new_vertex.end.0 .0,
    down_context: new_vertex
    .down_context
    .iter()
    .map(|c| to_printable_pos(hashes, *c))
    .collect(),
    }),
    Atom::EdgeMap(ref edge_map) => PrintableAtom::Edges(
    edge_map
    .edges
    .iter()
    .map(|c| PrintableEdge {
    previous: PrintableEdgeFlags::from(c.previous),
    flag: PrintableEdgeFlags::from(c.flag),
    from: to_printable_pos(hashes, c.from),
    to_start: to_printable_pos(hashes, c.to.start_pos()),
    to_end: c.to.end.0 .0,
    introduced_by: *hashes.get(&c.introduced_by.unwrap()).unwrap_or_else(|| {
    panic!("introduced_by = {:?}, not found", c.introduced_by)
    }),
    })
    .collect(),
    ),
  • replacement in libpijul/src/change/text_changes.rs at line 331
    [7.608][7.608:1174]()
    fn unescape(s: &str) -> std::borrow::Cow<str> {
    let mut b = 0;
    let mut result = String::new();
    let mut ch = s.chars();
    while let Some(c) = ch.next() {
    if c == '\\' {
    if result.is_empty() {
    result = s.split_at(b).0.to_string();
    }
    if let Some(c) = ch.next() {
    result.push(c)
    }
    } else if !result.is_empty() {
    result.push(c)
    }
    b += c.len_utf8()
    }
    if result.is_empty() {
    s.into()
    } else {
    result.into()
    [7.608]
    [7.1174]
    fn from_printable_edge_map(
    edges: &[PrintableEdge],
    changes: &HashMap<usize, Hash>,
    ) -> Result<Vec<NewEdge<Option<Hash>>>, TextDeError> {
    let mut res = Vec::new();
    for edge in edges {
    let Position { change, pos } = from_printable_pos(changes, edge.to_start)?;
    res.push(NewEdge {
    previous: edge.previous.to(),
    flag: edge.flag.to(),
    from: from_printable_pos(changes, edge.from)?,
    to: Vertex {
    change,
    start: pos,
    end: ChangePosition(L64(edge.to_end)),
    },
    introduced_by: change_ref(changes, edge.introduced_by)?,
    })
  • edit in libpijul/src/change/text_changes.rs at line 350
    [7.1180]
    [7.1180]
    Ok(res)
  • replacement in libpijul/src/change/text_changes.rs at line 359
    [7.48744][7.2690:2713]()
    mut w: &mut W,
    [7.48744]
    [7.48762]
    w: &mut W,
  • edit in libpijul/src/change/text_changes.rs at line 361
    [7.48808][7.1183:1287]()
    // let file_name = |local: &Local, _| -> String { format!("{}:{}", local.path, local.line) };
  • replacement in libpijul/src/change/text_changes.rs at line 367
    [7.2757][7.2757:2798]()
    metadata: perms,
    [7.2757]
    [7.4770]
    metadata,
  • replacement in libpijul/src/change/text_changes.rs at line 370
    [2.100][7.1482:1537](),[7.2860][7.1482:1537](),[7.49359][7.1482:1537](),[7.1537][7.1288:1411](),[7.1411][7.1644:1944](),[7.1644][7.1644:1944](),[7.1944][7.49438:49991](),[7.49438][7.49438:49991]()
    write!(
    w,
    "Moved: {} {} {}",
    Escaped(&path),
    Escaped(&name),
    if perms.0 & 0o1000 == 0o1000 {
    "+dx "
    } else if perms.0 & 0o100 == 0o100 {
    "+x "
    } else {
    ""
    }
    )?;
    write_pos(&mut w, hashes, del.inode())?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &del)?;
    write!(w, "up")?;
    for c in add.up_context.iter() {
    write!(w, " ")?;
    write_pos(&mut w, hashes, *c)?
    }
    write!(w, ", down")?;
    for c in add.down_context.iter() {
    write!(w, " ")?;
    write_pos(&mut w, hashes, *c)?
    [2.100]
    [7.49991]
    PrintableHunk::FileMoveV {
    path: path.to_string(),
    name: name.to_string(),
    perms: PrintablePerms::from_metadata(metadata),
    pos: to_printable_pos(hashes, del.inode()),
    up_context: to_printable_pos_vec(hashes, &add.up_context),
    down_context: to_printable_pos_vec(hashes, &add.down_context),
    del: to_printable_edge_map(del, hashes),
  • edit in libpijul/src/change/text_changes.rs at line 379
    [7.50013][7.50013:50054]()
    w.write_all(b"\n")?;
  • replacement in libpijul/src/change/text_changes.rs at line 380
    [7.50072][7.50072:50387]()
    Atom::EdgeMap(_) => {
    write!(w, "Moved: {:?} ", path)?;
    write_pos(&mut w, hashes, del.inode())?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &add)?;
    write_atom(&mut w, hashes, &del)?;
    }
    [7.50072]
    [7.50387]
    Atom::EdgeMap(_) => PrintableHunk::FileMoveE {
    path: path.to_string(),
    pos: to_printable_pos(hashes, del.inode()),
    add: to_printable_edge_map(add, hashes),
    del: to_printable_edge_map(del, hashes),
    },
  • replacement in libpijul/src/change/text_changes.rs at line 394
    [7.269][7.1412:1477](),[7.1477][7.50578:50635](),[7.5011][7.50578:50635](),[7.105716][7.50578:50635](),[7.50578][7.50578:50635](),[7.50635][7.45:110]()
    write!(w, "File deletion: {} ", Escaped(path))?;
    write_pos(&mut w, hashes, del.inode())?;
    writeln!(w, " {:?}", encoding_label(encoding))?;
    [7.269]
    [7.110]
    let (contents_data, content_edges) = if let Some(ref c) = contents {
    (
    get_change_contents(changes, c, change_contents)?,
    to_printable_edge_map(c, hashes),
    )
    } else {
    (Vec::new(), Vec::new())
    };
  • replacement in libpijul/src/change/text_changes.rs at line 403
    [7.111][7.50665:50865](),[7.50665][7.50665:50865](),[7.50865][7.5012:5105](),[7.2549][7.50953:51012](),[7.2797][7.50953:51012](),[7.5105][7.50953:51012](),[7.50953][7.50953:51012]()
    write_atom(&mut w, hashes, &del)?;
    if let Some(ref contents) = contents {
    write_atom(&mut w, hashes, &contents)?;
    writeln!(w)?;
    print_change_contents(w, changes, contents, change_contents, encoding)?;
    } else {
    writeln!(w)?;
    [7.111]
    [7.51012]
    PrintableHunk::FileDel {
    path: path.to_string(),
    pos: to_printable_pos(hashes, del.inode()),
    encoding: encoding.clone(),
    del_edges: to_printable_edge_map(del, hashes),
    content_edges: content_edges,
    contents: contents_data,
  • replacement in libpijul/src/change/text_changes.rs at line 419
    [7.308][7.1478:1546](),[7.1546][7.51227:51286](),[7.5322][7.51227:51286](),[7.105779][7.51227:51286](),[7.51227][7.51227:51286](),[7.51286][7.160:225](),[7.225][7.51316:51484](),[7.51316][7.51316:51484](),[7.51484][7.5323:5416]()
    write!(w, "File un-deletion: {} ", Escaped(path))?;
    write_pos(&mut w, hashes, undel.inode())?;
    writeln!(w, " {:?}", encoding_label(encoding))?;
    write_atom(&mut w, hashes, &undel)?;
    if let Some(ref contents) = contents {
    write_atom(&mut w, hashes, &contents)?;
    print_change_contents(w, changes, contents, change_contents, encoding)?;
    [7.308]
    [7.51572]
    let (contents_data, content_edges) = if let Some(ref c) = contents {
    (
    get_change_contents(changes, c, change_contents)?,
    to_printable_edge_map(c, hashes),
    )
  • replacement in libpijul/src/change/text_changes.rs at line 425
    [7.51597][7.51597:51631]()
    writeln!(w)?;
    [7.51597]
    [7.51631]
    (Vec::new(), Vec::new())
    };
    PrintableHunk::FileUndel {
    path: path.to_string(),
    pos: to_printable_pos(hashes, undel.inode()),
    encoding: encoding.clone(),
    undel_edges: to_printable_edge_map(undel, hashes),
    content_edges: content_edges,
    contents: contents_data,
  • replacement in libpijul/src/change/text_changes.rs at line 448
    [7.2940][7.2940:2981]()
    metadata: perms,
    [7.2940]
    [7.5417]
    metadata,
  • replacement in libpijul/src/change/text_changes.rs at line 451
    [2.197][7.52241:52483](),[7.3043][7.52241:52483](),[7.52241][7.52241:52483]()
    let parent = if let Some(p) = crate::path::parent(&path) {
    if p.is_empty() {
    "/"
    } else {
    p
    }
    [2.197]
    [7.52483]
    let contents = if let Some(Atom::NewVertex(ref n)) = contents {
    change_contents[n.start.us()..n.end.us()].to_vec()
  • replacement in libpijul/src/change/text_changes.rs at line 455
    [7.52512][7.52512:52540]()
    "/"
    [7.52512]
    [7.52540]
    Vec::new()
  • edit in libpijul/src/change/text_changes.rs at line 457
    [7.52563][7.52563:52618](),[7.52618][7.1547:1693](),[7.1693][7.2070:2320](),[7.2070][7.2070:2320](),[7.2320][7.166:193](),[7.193][7.2641:2690](),[7.232][7.52730:52754](),[7.2346][7.52730:52754](),[7.2690][7.52730:52754](),[7.3523][7.52730:52754](),[7.52730][7.52730:52754]()
    write!(
    w,
    "File addition: {} in {}{} \"{}\"\n up",
    Escaped(name),
    Escaped(parent),
    if perms.0 & 0o1000 == 0o1000 {
    " +dx"
    } else if perms.0 & 0o100 == 0o100 {
    " +x"
    } else {
    ""
    },
    encoding_label(encoding)
    )?;
  • replacement in libpijul/src/change/text_changes.rs at line 458
    [7.52810][7.52810:52957]()
    for c in n.up_context.iter() {
    write!(w, " ")?;
    write_pos(&mut w, hashes, *c)?
    [7.52810]
    [7.52957]
    PrintableHunk::FileAddition {
    name: name.to_string(),
    parent: crate::path::parent(&path).unwrap_or("").to_string(),
    perms: PrintablePerms::from_metadata(metadata),
    encoding: encoding.clone(),
    up_context: to_printable_pos_vec(hashes, &n.up_context),
    start: n.start.0 .0,
    end: n.end.0 .0,
    contents,
  • replacement in libpijul/src/change/text_changes.rs at line 469
    [7.52979][7.52979:53131](),[7.53131][7.140788:140860](),[7.140860][7.1177:1235](),[7.448][7.53268:53313](),[7.1235][7.53268:53313](),[7.2930][7.53268:53313](),[7.53268][7.53268:53313](),[7.53313][7.123:168](),[7.168][7.53356:53378](),[7.53356][7.53356:53378]()
    writeln!(w, ", new {}:{}", n.start.0, n.end.0)?;
    }
    if let Some(Atom::NewVertex(ref n)) = contents {
    let c = &change_contents[n.start.us()..n.end.us()];
    print_contents(w, "+", c, encoding)?;
    if !c.ends_with(b"\n") {
    writeln!(w, "\n\\")?
    }
    [7.52979]
    [7.53378]
    } else {
    // TODO: should this panic?
    panic!("Invalid Hunk::FileAdd field add_name: {:?}", add_name);
  • replacement in libpijul/src/change/text_changes.rs at line 480
    [7.341][5.106:186](),[5.186][7.53536:53596](),[7.1778][7.53536:53596](),[7.53536][7.53536:53596](),[7.53596][7.2809:2872](),[7.2872][7.53596:53680](),[7.53596][7.53596:53680](),[7.53680][7.2873:2960]()
    write!(w, "Edit in {}:{} ", Escaped(&local.path), local.line)?;
    write_pos(&mut w, hashes, change.inode())?;
    write!(w, " {:?}", encoding_label(encoding))?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &change)?;
    print_change_contents(w, changes, change, change_contents, encoding)?;
    [7.341]
    [7.53762]
    PrintableHunk::Edit {
    path: local.path.clone(),
    line: local.line,
    pos: to_printable_pos(hashes, change.inode()),
    encoding: encoding.clone(),
    change: to_printable_atom(change, hashes),
    contents: get_change_contents(changes, change, change_contents)?,
    }
  • replacement in libpijul/src/change/text_changes.rs at line 496
    [7.381][5.187:274](),[5.274][7.53990:54050](),[7.1870][7.53990:54050](),[7.53990][7.53990:54050](),[7.54050][7.2988:3051](),[7.3051][7.54050:54193](),[7.54050][7.54050:54193](),[7.54193][7.3052:3231](),[7.3168][7.54362:54376](),[7.3231][7.54362:54376](),[7.54362][7.54362:54376](),[7.54376][7.8955:9011](),[7.9011][7.1871:1948](),[7.1948][7.54504:54753](),[7.54504][7.54504:54753](),[7.54753][7.9012:9070](),[7.9070][7.1949:2029](),[7.2029][7.54886:55121](),[7.54886][7.54886:55121]()
    write!(w, "Replacement in {}:{} ", Escaped(&local.path), local.line)?;
    write_pos(&mut w, hashes, change.inode())?;
    write!(w, " {:?}", encoding_label(encoding))?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &change)?;
    write_atom(&mut w, hashes, &replacement)?;
    print_change_contents(w, changes, change, change_contents, encoding)?;
    print_change_contents(w, changes, replacement, change_contents, encoding)?;
    }
    Hunk::SolveNameConflict { name, path } => {
    write!(w, "Solving a name conflict in {} ", Escaped(path))?;
    write_pos(&mut w, hashes, name.inode())?;
    write!(w, ": ")?;
    write_deleted_names(&mut w, changes, name)?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &name)?;
    }
    Hunk::UnsolveNameConflict { name, path } => {
    write!(w, "Un-solving a name conflict in {} ", Escaped(path))?;
    write_pos(&mut w, hashes, name.inode())?;
    write!(w, ": ")?;
    write_deleted_names(&mut w, changes, name)?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &name)?;
    [7.381]
    [7.55121]
    PrintableHunk::Replace {
    path: local.path.clone(),
    line: local.line,
    pos: to_printable_pos(hashes, change.inode()),
    encoding: encoding.clone(),
    change: to_printable_edge_map(change, hashes),
    replacement: to_printable_new_vertex(replacement, hashes),
    change_contents: get_change_contents(changes, change, change_contents)?,
    replacement_contents: get_change_contents(
    changes,
    replacement,
    change_contents,
    )?,
    }
  • edit in libpijul/src/change/text_changes.rs at line 511
    [7.55135]
    [7.9071]
    Hunk::SolveNameConflict { name, path } => PrintableHunk::SolveNameConflict {
    path: path.clone(),
    pos: to_printable_pos(hashes, name.inode()),
    names: get_deleted_names(changes, name)?,
    edges: to_printable_edge_map(name, hashes),
    },
    Hunk::UnsolveNameConflict { name, path } => PrintableHunk::UnsolveNameConflict {
    path: path.clone(),
    pos: to_printable_pos(hashes, name.inode()),
    names: get_deleted_names(changes, name)?,
    edges: to_printable_edge_map(name, hashes),
    },
  • replacement in libpijul/src/change/text_changes.rs at line 524
    [7.9131][7.382:430](),[7.55197][7.382:430](),[7.430][7.55197:55244](),[7.55197][7.55197:55244](),[7.55244][5.275:334](),[5.334][7.2093:2167](),[7.2093][7.2093:2167](),[7.2167][7.55354:55518](),[7.55354][7.55354:55518](),[7.55518][7.3232:3316]()
    debug!("solve order conflict");
    write!(
    w,
    "Solving an order conflict in {}:{} ",
    Escaped(&local.path),
    local.line,
    )?;
    write_pos(&mut w, hashes, change.inode())?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &change)?;
    print_change_contents(w, changes, change, change_contents, &None)?;
    [7.9131]
    [7.55600]
    // TODO: pass in the encoding
    let contents = get_change_contents(changes, change, change_contents)?;
    let encoding = get_encoding(&contents);
    PrintableHunk::SolveOrderConflict {
    path: local.path.clone(),
    line: local.line,
    pos: to_printable_pos(hashes, change.inode()),
    encoding: encoding.clone(),
    change: to_printable_new_vertex(change, hashes),
    contents: get_change_contents(changes, change, change_contents)?,
    }
  • replacement in libpijul/src/change/text_changes.rs at line 537
    [7.9194][7.431:481](),[7.55678][7.431:481](),[7.481][7.55678:55725](),[7.55678][7.55678:55725](),[7.55725][5.335:397](),[5.397][7.2234:2308](),[7.2234][7.2234:2308](),[7.2308][7.55838:56002](),[7.55838][7.55838:56002](),[7.56002][7.3317:3401]()
    debug!("unsolve order conflict");
    write!(
    w,
    "Un-solving an order conflict in {}:{} ",
    Escaped(&local.path),
    local.line,
    )?;
    write_pos(&mut w, hashes, change.inode())?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &change)?;
    print_change_contents(w, changes, change, change_contents, &None)?;
    [7.9194]
    [7.56084]
    // TODO: pass in the encoding
    let contents = get_change_contents(changes, change, change_contents)?;
    let encoding = get_encoding(&contents);
    PrintableHunk::UnsolveOrderConflict {
    path: local.path.clone(),
    line: local.line,
    pos: to_printable_pos(hashes, change.inode()),
    encoding: encoding.clone(),
    change: to_printable_edge_map(change, hashes),
    contents: get_change_contents(changes, change, change_contents)?,
    }
  • replacement in libpijul/src/change/text_changes.rs at line 553
    [7.3512][7.3512:3531](),[7.3531][7.482:527](),[7.9253][7.482:527](),[7.56158][7.482:527](),[7.527][7.56158:56205](),[7.56158][7.56158:56205](),[7.56205][7.2309:2368](),[7.2368][5.398:471](),[5.471][7.56309:56389](),[7.2421][7.56309:56389](),[7.56309][7.56309:56389](),[7.56389][7.2422:2487](),[7.2487][7.56389:56473](),[7.3595][7.56389:56473](),[7.56389][7.56389:56473](),[7.56473][7.5445:5532](),[7.3402][7.56555:56569](),[7.3680][7.56555:56569](),[7.5532][7.56555:56569](),[7.56555][7.56555:56569]()
    } => {
    debug!("resurrect zombies");
    write!(
    w,
    "Resurrecting zombie lines in {}:{} ",
    Escaped(&local.path),
    local.line
    )?;
    write_pos(&mut w, hashes, change.inode())?;
    write!(w, " \"{}\"", encoding_label(encoding))?;
    writeln!(w)?;
    write_atom(&mut w, hashes, &change)?;
    print_change_contents(w, changes, change, change_contents, encoding)?;
    }
    [7.3512]
    [7.56569]
    } => PrintableHunk::ResurrectZombies {
    path: local.path.clone(),
    line: local.line,
    pos: to_printable_pos(hashes, change.inode()),
    encoding: encoding.clone(),
    change: to_printable_edge_map(change, hashes),
    contents: get_change_contents(changes, change, change_contents)?,
    },
  • edit in libpijul/src/change/text_changes.rs at line 562
    [7.56579]
    [7.56579]
    .write(w)?;
  • edit in libpijul/src/change/text_changes.rs at line 567
    [7.3690][7.3690:3839](),[7.3839][7.56594:56603](),[7.56594][7.56594:56603]()
    fn encoding_label(encoding: &Option<Encoding>) -> &str {
    match encoding {
    Some(encoding) => encoding.label(),
    _ => BINARY_LABEL,
    }
    }
  • replacement in libpijul/src/change/text_changes.rs at line 568
    [7.9287][7.56638:56651](),[7.56638][7.56638:56651]()
    fn read(
    [7.9287]
    [7.56651]
    fn from_printable(
  • edit in libpijul/src/change/text_changes.rs at line 570
    [7.56712][7.56712:56748]()
    current: &mut Option<Self>,
  • replacement in libpijul/src/change/text_changes.rs at line 573
    [7.56877][7.56877:57069](),[7.57069][7.2083:2233](),[7.127][7.57195:57232](),[7.286][7.57195:57232](),[7.384][7.57195:57232](),[7.2233][7.57195:57232](),[7.2476][7.57195:57232](),[7.3673][7.57195:57232](),[7.3992][7.57195:57232](),[7.89589][7.57195:57232](),[7.57195][7.57195:57232](),[7.57232][7.3993:4103](),[7.216][7.57316:57360](),[7.4103][7.57316:57360](),[7.57316][7.57316:57360](),[7.57360][7.4104:4221](),[7.312][7.57451:57497](),[7.4221][7.57451:57497](),[7.57451][7.57451:57497](),[7.57497][7.5533:5646](),[7.404][7.57584:57632](),[7.5646][7.57584:57632](),[7.57584][7.57584:57632](),[7.57632][7.5647:5763](),[7.499][7.57722:57759](),[7.5763][7.57722:57759](),[7.57722][7.57722:57759](),[7.57759][7.287:417](),[7.417][7.627:726](),[7.2607][7.627:726](),[7.627][7.627:726](),[7.726][7.57977:58035](),[7.57977][7.57977:58035](),[7.58035][7.727:823](),[7.823][7.58127:58223](),[7.58127][7.58127:58223](),[7.58223][7.824:925](),[7.925][7.58320:58396](),[7.58320][7.58320:58396](),[7.58396][7.4222:4370](),[7.1052][7.58518:58878](),[7.4370][7.58518:58878](),[7.58518][7.58518:58878]()
    h: &str,
    ) -> Result<Option<Self>, TextDeError> {
    use self::text_changes::*;
    use regex::Regex;
    lazy_static! {
    static ref FILE_ADDITION: Regex =
    Regex::new(r#"^(?P<n>\d+)\. File addition: "(?P<name>[^"]*)" in "(?P<parent>[^"]*)"(?P<perm> \S+)? "(?P<encoding>[^"]*)""#).unwrap();
    static ref EDIT: Regex =
    Regex::new(r#"^([0-9]+)\. Edit in ([^:]+):(\d+) (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
    static ref REPLACEMENT: Regex =
    Regex::new(r#"^([0-9]+)\. Replacement in ([^:]+):(\d+) (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
    static ref FILE_DELETION: Regex =
    Regex::new(r#"^([0-9]+)\. File deletion: "([^"]*)" (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
    static ref FILE_UNDELETION: Regex =
    Regex::new(r#"^([0-9]+)\. File un-deletion: "([^"]*)" (\d+\.\d+) "(?P<encoding>[^"]*)""#).unwrap();
    static ref MOVE: Regex =
    Regex::new(r#"^([0-9]+)\. Moved: "(?P<former>[^"]*)" "(?P<new>[^"]*)" (?P<perm>[^ ]+ )?(?P<inode>.*)"#).unwrap();
    static ref MOVE_: Regex = Regex::new(r#"^([0-9]+)\. Moved: "([^"]*)" (.*)"#).unwrap();
    static ref NAME_CONFLICT: Regex = Regex::new(
    r#"^([0-9]+)\. ((Solving)|(Un-solving)) a name conflict in "([^"]*)" (.*): .*"#
    )
    .unwrap();
    static ref ORDER_CONFLICT: Regex = Regex::new(
    r#"^([0-9]+)\. ((Solving)|(Un-solving)) an order conflict in (.*):(\d+) (\d+\.\d+)"#
    )
    .unwrap();
    static ref ZOMBIE: Regex =
    Regex::new(r#"^([0-9]+)\. Resurrecting zombie lines in (?P<path>"[^"]+"):(?P<line>\d+) (?P<inode>\d+\.\d+) "(?P<encoding>[^"]*)""#)
    .unwrap();
    static ref CONTEXT: Regex = Regex::new(
    r#"up ((\d+\.\d+ )*\d+\.\d+)(, new (\d+):(\d+))?(, down ((\d+\.\d+ )*\d+\.\d+))?"#
    )
    .unwrap();
    }
    if let Some(cap) = FILE_ADDITION.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    [7.56877]
    [7.58878]
    (hunk_id, hunk): (u64, PrintableHunk),
    ) -> Result<Self, TextDeError> {
    // TODO: should we push, or not?
    contents_.push(0);
    match hunk {
    PrintableHunk::FileMoveV {
    path,
    name,
    perms,
    pos,
    up_context,
    down_context,
    del,
    } => {
    let mut add = default_newvertex();
    add.start = ChangePosition(contents_.len().into());
    add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    let meta = FileMetadata {
    metadata: InodeMetadata(match perms {
    // TODO: deduplicate
    PrintablePerms::IsDir => 0o1100,
    PrintablePerms::IsExecutable => 0o100,
    PrintablePerms::IsFile => 0,
    }),
    basename: &name,
    encoding: None,
    };
    meta.write(contents_);
    add.end = ChangePosition(contents_.len().into());
    add.up_context = from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
    add.down_context = from_printable_pos_vec_offsets(changes, offsets, &down_context)?;
    Ok(Hunk::FileMove {
    add: Atom::NewVertex(add),
    del: Atom::EdgeMap(EdgeMap {
    inode: from_printable_pos(changes, pos)?,
    edges: from_printable_edge_map(&del, changes)?,
    }),
    path,
    })
    }
    PrintableHunk::FileMoveE {
    path,
    pos,
    add,
    del,
    } => {
    let inode = from_printable_pos(changes, pos)?;
    Ok(Hunk::FileMove {
    add: Atom::EdgeMap(EdgeMap {
    inode,
    edges: from_printable_edge_map(&add, changes)?,
    }),
    del: Atom::EdgeMap(EdgeMap {
    inode,
    edges: from_printable_edge_map(&del, changes)?,
    }),
    path,
    })
  • replacement in libpijul/src/change/text_changes.rs at line 634
    [7.58892][7.58892:58944](),[7.58944][7.140861:140930](),[7.140930][7.59013:59079](),[7.59013][7.59013:59079](),[7.59079][7.2488:2558](),[7.2558][7.59139:59326](),[7.59139][7.59139:59326](),[7.59326][7.2559:2642](),[7.2642][7.59391:59406](),[7.59391][7.59391:59406](),[7.59406][7.89590:89629](),[7.89629][7.2608:2670](),[7.59406][7.2608:2670](),[7.2670][7.418:463](),[7.463][7.2714:2741](),[7.2714][7.2714:2741](),[7.2741][7.464:515](),[7.515][7.2791:2864](),[7.2791][7.2791:2864]()
    let mut add_name = default_newvertex();
    add_name.start = ChangePosition(contents_.len().into());
    add_name.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    let name = unescape(&cap.name("name").unwrap().as_str());
    let path = {
    let parent = cap.name("parent").unwrap().as_str();
    (if parent == "/" {
    String::new()
    } else {
    unescape(&parent).to_string() + "/"
    }) + &name
    };
    debug!("cap = {:?}", cap);
    let meta = if let Some(perm) = cap.name("perm") {
    if perm.as_str() == " +dx" {
    0o1100
    } else if perm.as_str() == " +x" {
    0o100
    } else {
    0
    [7.58892]
    [7.2864]
    PrintableHunk::FileAddition {
    name,
    parent,
    perms,
    encoding,
    up_context,
    start,
    end,
    contents,
    } => {
    let meta = FileMetadata {
    metadata: InodeMetadata(match perms {
    PrintablePerms::IsDir => 0o1100,
    PrintablePerms::IsExecutable => 0o100,
    PrintablePerms::IsFile => 0,
    }),
    basename: &name,
    encoding: encoding.clone(),
    };
    let add_name = {
    let mut x = default_newvertex();
    x.start = ChangePosition(contents_.len().into());
    meta.write(contents_);
    x.end = ChangePosition(contents_.len().into());
    x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    x.up_context = from_printable_pos_vec_offsets(changes, offsets, &up_context)?;
    x
    };
    let add_inode = {
    let mut x = default_newvertex();
    x.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    x.up_context.push(Position {
    change: None,
    pos: ChangePosition(contents_.len().into()),
    });
    contents_.push(0);
    x.start = ChangePosition(contents_.len().into());
    x.end = ChangePosition(contents_.len().into());
    contents_.push(0);
    x
    };
    if let Entry::Occupied(mut e) = updatables.entry(hunk_id as usize) {
    if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
    offsets.insert(pos.0.into(), add_inode.start);
    *pos = add_inode.start
    }
  • edit in libpijul/src/change/text_changes.rs at line 685
    [7.2882][7.2882:2936](),[7.2936][7.5764:5887](),[7.5887][7.3044:3129](),[7.2936][7.3044:3129](),[7.3129][7.2643:2676](),[7.2676][7.5888:5932](),[7.3161][7.5888:5932](),[7.5932][7.3161:3176](),[7.3161][7.3161:3176](),[7.3176][7.2234:2269](),[7.2269][7.140931:140998](),[7.3216][7.140931:140998](),[7.59747][7.140931:140998]()
    } else {
    0
    };
    let n = cap.name("n").unwrap().as_str().parse().unwrap();
    let encoding = encoding_from_label(cap);
    let meta = FileMetadata {
    metadata: InodeMetadata(meta),
    basename: &name,
    encoding: encoding.clone(),
    };
    meta.write(contents_);
    add_name.end = ChangePosition(contents_.len().into());
  • replacement in libpijul/src/change/text_changes.rs at line 686
    [7.59815][7.59815:60014](),[7.60014][7.140999:141060](),[7.141060][7.60075:60091](),[7.60075][7.60075:60091]()
    let mut add_inode = default_newvertex();
    add_inode.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    add_inode.up_context.push(Position {
    change: None,
    pos: ChangePosition(contents_.len().into()),
    });
    [7.59815]
    [7.60091]
    // context
    offsets.insert(start, add_name.start);
    offsets.insert(end, add_name.end);
    offsets.insert(end + 1, add_name.end + 1);
  • replacement in libpijul/src/change/text_changes.rs at line 691
    [7.60092][7.60092:60123](),[7.60123][7.141061:141199](),[7.141199][7.60261:60292](),[7.60261][7.60261:60292](),[7.60362][7.60362:60511](),[7.60511][3.0:67]()
    contents_.push(0);
    add_inode.start = ChangePosition(contents_.len().into());
    add_inode.end = ChangePosition(contents_.len().into());
    contents_.push(0);
    if let Entry::Occupied(mut e) = updatables.entry(n) {
    if let crate::InodeUpdate::Add { ref mut pos, .. } = e.get_mut() {
    offsets.insert(pos.0.into(), add_inode.start);
    [7.60092]
    [3.67]
    // contents
    let contents_res = {
    let mut x = default_newvertex();
    // The `-1` here comes from the extra 0
    // padding bytes pushed onto `contents_`.
    // TODO: verify this is correct
    let inode = Position {
    change: None,
    pos: ChangePosition((contents_.len() - 1).into()),
    };
    x.up_context.push(inode);
    x.inode = inode;
    x.flag = EdgeFlags::BLOCK;
    x.start = ChangePosition(contents_.len().into());
    contents_.extend(&contents);
    x.end = ChangePosition(contents_.len().into());
    x
    };
  • replacement in libpijul/src/change/text_changes.rs at line 710
    [3.68][7.60511:60586](),[7.60511][7.60511:60586](),[7.3925][7.60586:60645](),[7.60586][7.60586:60645](),[7.60645][7.9288:9325]()
    *pos = add_inode.start
    }
    }
    Ok(std::mem::replace(
    current,
    Some(Hunk::FileAdd {
    [3.68]
    [7.60684]
    Ok(Hunk::FileAdd {
  • replacement in libpijul/src/change/text_changes.rs at line 713
    [7.60800][7.60800:60862]()
    contents: None,
    path,
    [7.60800]
    [7.5933]
    contents: Some(Atom::NewVertex(contents_res)),
    path: parent + "/" + &name,
  • replacement in libpijul/src/change/text_changes.rs at line 716
    [7.3956][7.60862:61026](),[7.4427][7.60862:61026](),[7.5963][7.60862:61026](),[7.60862][7.60862:61026]()
    }),
    ))
    } else if let Some(cap) = EDIT.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    [7.5963]
    [7.61026]
    })
  • replacement in libpijul/src/change/text_changes.rs at line 718
    [7.61040][7.61040:61086](),[7.61086][7.128:180](),[7.180][7.69:108](),[7.108][7.61137:61196](),[7.180][7.61137:61196](),[7.61137][7.61137:61196](),[7.61196][7.9326:9360](),[7.9360][7.61232:61443](),[7.61232][7.61232:61443](),[7.61443][7.4428:4484]()
    let mut v = default_newvertex();
    v.inode = parse_pos(changes, &cap[4])?;
    v.flag = EdgeFlags::BLOCK;
    Ok(std::mem::replace(
    current,
    Some(Hunk::Edit {
    change: Atom::NewVertex(v),
    local: Local {
    path: cap[2].to_string(),
    line: cap[3].parse().unwrap(),
    },
    encoding: encoding_from_label(cap),
    [7.61040]
    [7.61443]
    PrintableHunk::FileDel {
    path,
    pos,
    encoding,
    del_edges,
    content_edges,
    contents: _,
    } => Ok(Hunk::FileDel {
    del: Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&del_edges, changes)?,
    inode: from_printable_pos(changes, pos)?,
  • replacement in libpijul/src/change/text_changes.rs at line 730
    [7.61463][7.61463:61673](),[7.61673][7.181:233](),[7.233][7.109:148](),[7.148][7.61724:61783](),[7.233][7.61724:61783](),[7.61724][7.61724:61783](),[7.61783][7.9361:9402](),[7.9402][7.61826:62098](),[7.61826][7.61826:62098](),[7.62098][7.4485:4541](),[7.4541][7.62098:62330](),[7.62098][7.62098:62330](),[7.62330][7.234:288](),[7.288][7.62383:62442](),[7.62383][7.62383:62442](),[7.62442][7.9403:9440](),[7.9440][7.62481:62608](),[7.62481][7.62481:62608](),[7.62608][7.5964:6020](),[7.6020][7.62608:62844](),[7.62608][7.62608:62844](),[7.62844][7.289:345](),[7.345][7.62899:62958](),[7.62899][7.62899:62958](),[7.62958][7.9441:9480](),[7.9480][7.62999:63130](),[7.62999][7.62999:63130](),[7.63130][7.6021:6077]()
    ))
    } else if let Some(cap) = REPLACEMENT.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    }
    let mut v = default_newvertex();
    v.inode = parse_pos(changes, &cap[4])?;
    v.flag = EdgeFlags::BLOCK;
    Ok(std::mem::replace(
    current,
    Some(Hunk::Replacement {
    change: Atom::NewVertex(v.clone()),
    replacement: Atom::NewVertex(v),
    local: Local {
    path: cap[2].to_string(),
    line: cap[3].parse().unwrap(),
    },
    encoding: encoding_from_label(cap),
    }),
    ))
    } else if let Some(cap) = FILE_DELETION.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    }
    let mut del = default_edgemap();
    del.inode = parse_pos(changes, &cap[3])?;
    Ok(std::mem::replace(
    current,
    Some(Hunk::FileDel {
    del: Atom::EdgeMap(del),
    contents: None,
    path: cap[2].to_string(),
    encoding: encoding_from_label(cap),
    }),
    ))
    } else if let Some(cap) = FILE_UNDELETION.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    }
    let mut undel = default_edgemap();
    undel.inode = parse_pos(changes, &cap[3])?;
    Ok(std::mem::replace(
    current,
    Some(Hunk::FileUndel {
    undel: Atom::EdgeMap(undel),
    contents: None,
    path: cap[2].to_string(),
    encoding: encoding_from_label(cap),
    [7.61463]
    [7.63130]
    contents: Some(Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&content_edges, changes)?,
    inode: from_printable_pos(changes, pos)?,
    })),
    path,
    encoding,
    }),
    PrintableHunk::FileUndel {
    path,
    pos,
    encoding,
    undel_edges,
    content_edges,
    contents: _,
    } => Ok(Hunk::FileUndel {
    undel: Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&undel_edges, changes)?,
    inode: from_printable_pos(changes, pos)?,
  • replacement in libpijul/src/change/text_changes.rs at line 749
    [7.63150][7.63150:63402](),[7.63402][7.346:401](),[7.401][7.63456:63557](),[7.63456][7.63456:63557](),[7.63557][7.9481:9532](),[7.9532][7.63610:63759](),[7.63610][7.63610:63759](),[7.63759][7.9533:9586](),[7.9586][7.63814:64101](),[7.63814][7.63814:64101]()
    ))
    } else if let Some(cap) = NAME_CONFLICT.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    }
    let mut name = default_edgemap();
    debug!("cap = {:?}", cap);
    name.inode = parse_pos(changes, &cap[6])?;
    Ok(std::mem::replace(
    current,
    if &cap[2] == "Solving" {
    Some(Hunk::SolveNameConflict {
    name: Atom::EdgeMap(name),
    path: cap[5].to_string(),
    })
    } else {
    Some(Hunk::UnsolveNameConflict {
    name: Atom::EdgeMap(name),
    path: cap[5].to_string(),
    })
    },
    ))
    } else if let Some(cap) = MOVE.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    [7.63150]
    [7.64101]
    contents: Some(Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&content_edges, changes)?,
    inode: from_printable_pos(changes, pos)?,
    })),
    path,
    encoding,
    }),
    PrintableHunk::Edit {
    path,
    line,
    pos,
    encoding,
    change,
    contents,
    } => {
    let inode = from_printable_pos(changes, pos)?;
    let change = match change {
    PrintableAtom::NewVertex(new_vertex) => {
    let mut x = default_newvertex();
    x.inode = inode;
    x.flag = EdgeFlags::BLOCK;
    x.up_context = from_printable_pos_vec(changes, &new_vertex.up_context)?;
    x.down_context = from_printable_pos_vec(changes, &new_vertex.down_context)?;
    x.start = ChangePosition(contents_.len().into());
    contents_.extend(&contents);
    x.end = ChangePosition(contents_.len().into());
    Atom::NewVertex(x)
    }
    PrintableAtom::Edges(edges) => Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&edges, changes)?,
    inode: inode,
    }),
    };
    Ok(Hunk::Edit {
    change,
    local: Local { path, line },
    encoding,
    })
  • replacement in libpijul/src/change/text_changes.rs at line 789
    [7.64115][7.64115:64162](),[7.64162][7.141200:141264](),[7.141264][7.64226:64287](),[7.64226][7.64226:64287](),[7.64287][7.2677:2745](),[7.2745][7.2937:2999](),[7.64345][7.2937:2999](),[7.2999][7.516:615](),[7.615][7.3043:3070](),[7.3043][7.3043:3070](),[7.3070][7.616:667](),[7.667][7.3120:3265](),[7.3120][7.3120:3265](),[7.3265][7.3217:3302](),[7.3302][7.2746:2779](),[7.2779][7.6078:6110](),[7.3334][7.6078:6110](),[7.6110][7.3334:3349](),[7.3334][7.3334:3349](),[7.3349][7.2270:2305](),[7.2305][7.141265:141327](),[7.3389][7.141265:141327](),[7.64686][7.141265:141327]()
    let mut add = default_newvertex();
    add.start = ChangePosition(contents_.len().into());
    add.flag = EdgeFlags::FOLDER | EdgeFlags::BLOCK;
    let name = unescape(cap.name("new").unwrap().as_str());
    let meta = if let Some(perm) = cap.name("perm") {
    debug!("perm = {:?}", perm.as_str());
    if perm.as_str() == "+dx " {
    0o1100
    } else if perm.as_str() == "+x " {
    0o100
    } else {
    0
    }
    } else {
    0
    };
    let meta = FileMetadata {
    metadata: InodeMetadata(meta),
    basename: &name,
    encoding: None,
    };
    meta.write(contents_);
    add.end = ChangePosition(contents_.len().into());
    [7.64115]
    [7.64748]
    PrintableHunk::Replace {
    path,
    line,
    pos,
    encoding,
    change,
    replacement,
    change_contents: _,
    replacement_contents,
    } => {
    let inode = from_printable_pos(changes, pos)?;
  • replacement in libpijul/src/change/text_changes.rs at line 801
    [7.64749][7.64749:64794](),[7.64794][7.402:484](),[7.484][7.64875:64934](),[7.64875][7.64875:64934](),[7.64934][7.9587:9625](),[7.9625][7.64974:65277](),[7.64974][7.64974:65277]()
    let mut del = default_edgemap();
    del.inode = parse_pos(changes, cap.name("inode").unwrap().as_str())?;
    Ok(std::mem::replace(
    current,
    Some(Hunk::FileMove {
    del: Atom::EdgeMap(del),
    add: Atom::NewVertex(add),
    path: cap[2].to_string(),
    }),
    ))
    } else if let Some(cap) = MOVE_.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    [7.64749]
    [7.65277]
    let replacement = {
    let mut x = default_newvertex();
    x.inode = inode;
    x.flag = EdgeFlags::BLOCK;
    x.up_context = from_printable_pos_vec(changes, &replacement.up_context)?;
    x.down_context = from_printable_pos_vec(changes, &replacement.down_context)?;
    x.start = ChangePosition(contents_.len().into());
    contents_.extend(&replacement_contents);
    x.end = ChangePosition(contents_.len().into());
    Atom::NewVertex(x)
    };
    Ok(Hunk::Replacement {
    change: Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&change, changes)?,
    inode: inode,
    }),
    replacement,
    local: Local { path, line },
    encoding,
    })
  • replacement in libpijul/src/change/text_changes.rs at line 823
    [7.65291][7.65291:65381](),[7.65381][7.485:539](),[7.539][7.65434:65528](),[7.65434][7.65434:65528](),[7.65528][7.9626:9664](),[7.9664][7.65568:65704](),[7.65568][7.65568:65704]()
    let mut add = default_edgemap();
    let mut del = default_edgemap();
    add.inode = parse_pos(changes, &cap[3])?;
    del.inode = add.inode;
    Ok(std::mem::replace(
    current,
    Some(Hunk::FileMove {
    del: Atom::EdgeMap(del),
    add: Atom::EdgeMap(add),
    path: cap[2].to_string(),
    [7.65291]
    [7.65704]
    PrintableHunk::SolveNameConflict {
    path,
    pos,
    names: _,
    edges,
    } => Ok(Hunk::SolveNameConflict {
    name: Atom::EdgeMap(EdgeMap {
    inode: from_printable_pos(changes, pos)?,
    edges: from_printable_edge_map(&edges, changes)?,
  • replacement in libpijul/src/change/text_changes.rs at line 833
    [7.65724][7.65724:65892]()
    ))
    } else if let Some(cap) = ORDER_CONFLICT.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    }
    [7.65724]
    [7.65892]
    path,
    }),
    PrintableHunk::UnsolveNameConflict {
    path,
    pos,
    names: _,
    edges,
    } => Ok(Hunk::UnsolveNameConflict {
    name: Atom::EdgeMap(EdgeMap {
    inode: from_printable_pos(changes, pos)?,
    edges: from_printable_edge_map(&edges, changes)?,
    }),
    path,
    }),
    PrintableHunk::SolveOrderConflict {
    path,
    line,
    pos,
    encoding: _,
    change,
    contents,
    } => {
    // TODO: this code block looks suspect. Check the correctness.
    let mut c = default_newvertex();
    c.inode = from_printable_pos(changes, pos)?;
    c.up_context = from_printable_pos_vec(changes, &change.up_context)?;
    c.down_context = from_printable_pos_vec(changes, &change.down_context)?;
    // TODO: this maths is probably unnecessarily complicated
    c.start = ChangePosition(contents_.len().into());
    c.end = ChangePosition((contents_.len() as u64 + change.end - change.start).into());
    offsets.insert(change.end, c.end);
    c.start = ChangePosition(contents_.len().into());
    contents_.extend(&contents);
    c.end = ChangePosition(contents_.len().into());
  • replacement in libpijul/src/change/text_changes.rs at line 868
    [7.65893][7.65893:66052](),[7.66052][7.540:600](),[7.600][7.9665:9712](),[7.9712][7.66160:66489](),[7.66160][7.66160:66489](),[7.66489][7.601:661](),[7.661][7.9713:9762](),[7.9762][7.66599:67016](),[7.66599][7.66599:67016]()
    Ok(std::mem::replace(
    current,
    Some(if &cap[2] == "Solving" {
    let mut v = default_newvertex();
    v.inode = parse_pos(changes, &cap[7])?;
    Hunk::SolveOrderConflict {
    change: Atom::NewVertex(v),
    local: Local {
    path: cap[5].to_string(),
    line: cap[6].parse().unwrap(),
    },
    }
    } else {
    let mut v = default_edgemap();
    v.inode = parse_pos(changes, &cap[7])?;
    Hunk::UnsolveOrderConflict {
    change: Atom::EdgeMap(v),
    local: Local {
    path: cap[5].to_string(),
    line: cap[6].parse().unwrap(),
    },
    }
    }),
    ))
    } else if let Some(cap) = ZOMBIE.captures(h) {
    if has_newvertices(current) {
    contents_.push(0)
    [7.65893]
    [7.67016]
    Ok(Hunk::SolveOrderConflict {
    change: Atom::NewVertex(c),
    local: Local { path, line },
    })
  • replacement in libpijul/src/change/text_changes.rs at line 873
    [7.67030][7.67030:67073](),[7.67073][7.662:743](),[7.743][7.67153:67212](),[7.67153][7.67153:67212](),[7.67212][7.9763:9809](),[7.9809][7.67260:67530](),[7.67260][7.67260:67530](),[7.67530][7.4542:4598]()
    let mut v = default_edgemap();
    v.inode = parse_pos(changes, &cap.name("inode").unwrap().as_str())?;
    Ok(std::mem::replace(
    current,
    Some(Hunk::ResurrectZombies {
    change: Atom::EdgeMap(v),
    local: Local {
    path: cap.name("path").unwrap().as_str().parse().unwrap(),
    line: cap.name("line").unwrap().as_str().parse().unwrap(),
    },
    encoding: encoding_from_label(cap),
    [7.67030]
    [7.67530]
    PrintableHunk::UnsolveOrderConflict {
    path,
    line,
    pos,
    encoding: _,
    change,
    contents: _,
    } => Ok(Hunk::UnsolveOrderConflict {
    change: Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&change, changes)?,
    inode: from_printable_pos(changes, pos)?,
  • replacement in libpijul/src/change/text_changes.rs at line 885
    [7.67550][7.67550:67610](),[7.67610][7.9810:9847](),[7.9847][7.67649:67725](),[7.67649][7.67649:67725](),[7.67725][7.152:182](),[7.182][7.67725:67925](),[7.67725][7.67725:67925](),[7.67925][7.141328:141466](),[7.141466][7.67925:68022](),[7.67925][7.67925:68022](),[7.68022][7.141467:141550](),[7.141550][7.68103:68233](),[7.68103][7.68103:68233](),[7.68233][7.17146:17201](),[7.17201][7.141551:141629](),[7.141629][7.68311:68539](),[7.68311][7.68311:68539](),[7.68539][7.183:278](),[7.278][7.68624:68732](),[7.68624][7.68624:68732](),[7.68732][7.87:216](),[7.216][7.21240:21312](),[7.21312][7.141703:141780](),[7.141703][7.141703:141780](),[7.141780][7.378:584](),[7.378][7.378:584](),[7.584][7.69085:69999](),[7.69085][7.69085:69999](),[7.69999][7.9848:9885](),[7.9885][7.70038:70156](),[7.70038][7.70038:70156](),[7.70156][7.744:812](),[7.812][7.70223:70904](),[7.70223][7.70223:70904](),[7.70904][7.9886:9925](),[7.9925][7.70945:71065](),[7.70945][7.70945:71065](),[7.71065][7.813:881](),[7.881][7.71132:71815](),[7.71132][7.71132:71815](),[7.71815][7.9926:9964](),[7.9964][7.71855:71968](),[7.71855][7.71855:71968](),[7.71968][7.882:950](),[7.950][7.72035:73304](),[7.72035][7.72035:73304](),[7.73304][7.279:426](),[7.426][7.73367:73590](),[7.10026][7.73367:73590](),[7.73367][7.73367:73590](),[7.73590][7.141781:141868](),[7.141868][7.73677:73707](),[7.73677][7.73677:73707](),[7.73707][7.427:516](),[7.516][7.73786:73938](),[7.73786][7.73786:73938](),[7.73938][7.21313:21413](),[7.21413][7.313:343](),[7.141944][7.313:343](),[7.313][7.313:343](),[7.343][7.141945:142023](),[7.142023][7.74101:74748](),[7.74101][7.74101:74748](),[7.74748][7.951:1026](),[7.1026][7.74822:75063](),[7.74822][7.74822:75063](),[7.75063][7.10027:10068](),[7.10068][7.75106:75183](),[7.75106][7.75106:75183](),[7.75183][7.517:547](),[7.547][7.75183:75408](),[7.75183][7.75183:75408](),[7.75408][7.142024:142109](),[7.142109][7.75493:75523](),[7.75493][7.75493:75523](),[7.75523][7.548:635](),[7.635][7.75600:75755](),[7.75600][7.75600:75755](),[7.75755][7.21414:21510](),[7.21510][7.142205:142281](),[7.474][7.142205:142281](),[7.142281][7.75914:76569](),[7.75914][7.75914:76569](),[7.76569][7.1027:1102](),[7.1102][7.76643:76884](),[7.76643][7.76643:76884](),[7.76884][7.10069:10212](),[7.10212][7.1103:1171](),[7.77031][7.1103:1171](),[7.1171][7.77098:77334](),[7.77098][7.77098:77334](),[7.77334][7.10213:10288](),[7.10288][7.77411:77590](),[7.77411][7.77411:77590](),[7.77590][7.142282:142369](),[7.142369][7.77677:77707](),[7.77677][7.77677:77707](),[7.77707][7.636:767](),[7.767][7.77786:78601](),[7.77786][7.77786:78601](),[7.78601][7.142370:142457](),[7.142457][7.21511:21698](),[7.21698][7.78831:79025](),[7.142565][7.78831:79025](),[7.78831][7.78831:79025](),[7.79025][7.10289:10366](),[7.10366][7.1172:1240](),[7.79104][7.1172:1240](),[7.1240][7.79171:79387](),[7.79171][7.79171:79387](),[7.79387][7.10367:10440](),[7.10440][7.1241:1309](),[7.79462][7.1241:1309](),[7.1309][7.79529:79931](),[7.79529][7.79529:79931]()
    ))
    } else {
    match current {
    Some(Hunk::FileAdd {
    ref mut contents,
    ref mut add_name,
    encoding,
    ..
    }) => {
    if h.starts_with('+') {
    if contents.is_none() {
    let mut v = default_newvertex();
    // The `-1` here comes from the extra 0
    // padding bytes pushed onto `contents_`.
    let inode = Position {
    change: None,
    pos: ChangePosition((contents_.len() - 1).into()),
    };
    v.up_context.push(inode);
    v.inode = inode;
    v.flag = EdgeFlags::BLOCK;
    v.start = ChangePosition(contents_.len().into());
    *contents = Some(Atom::NewVertex(v));
    }
    if let Some(Atom::NewVertex(ref mut contents)) = contents {
    if h.starts_with('+') {
    text_changes::parse_line_add(h, contents, contents_, encoding)
    }
    }
    } else if h.starts_with('\\') {
    if let Some(Atom::NewVertex(mut c)) = contents.take() {
    if c.end > c.start {
    if contents_[c.end.us() - 1] == b'\n' {
    assert_eq!(c.end.us(), contents_.len());
    contents_.pop();
    c.end.0 -= 1;
    }
    *contents = Some(Atom::NewVertex(c))
    }
    }
    } else if let Some(cap) = CONTEXT.captures(h) {
    if let Atom::NewVertex(ref mut name) = add_name {
    name.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
    if let (Some(new_start), Some(new_end)) = (cap.get(4), cap.get(5)) {
    offsets.insert(new_start.as_str().parse().unwrap(), name.start);
    offsets.insert(new_end.as_str().parse().unwrap(), name.end);
    offsets.insert(
    new_end.as_str().parse::<u64>().unwrap() + 1,
    name.end + 1,
    );
    }
    }
    }
    Ok(None)
    }
    Some(Hunk::FileDel {
    ref mut del,
    ref mut contents,
    ..
    }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    if let Atom::EdgeMap(ref mut e) = del {
    if edges[0].flag.contains(EdgeFlags::FOLDER) {
    *e = EdgeMap {
    inode: e.inode,
    edges,
    }
    } else {
    *contents = Some(Atom::EdgeMap(EdgeMap {
    inode: e.inode,
    edges,
    }))
    }
    }
    }
    Ok(None)
    }
    Some(Hunk::FileUndel {
    ref mut undel,
    ref mut contents,
    ..
    }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    if let Atom::EdgeMap(ref mut e) = undel {
    if edges[0].flag.contains(EdgeFlags::FOLDER) {
    *e = EdgeMap {
    inode: e.inode,
    edges,
    }
    } else {
    *contents = Some(Atom::EdgeMap(EdgeMap {
    inode: e.inode,
    edges,
    }))
    }
    }
    }
    Ok(None)
    }
    Some(Hunk::FileMove {
    ref mut del,
    ref mut add,
    ..
    }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    if edges[0].flag.contains(EdgeFlags::DELETED) {
    *del = Atom::EdgeMap(EdgeMap {
    inode: del.inode(),
    edges,
    });
    return Ok(None);
    } else if let Atom::EdgeMap(ref mut add) = add {
    if add.edges.is_empty() {
    *add = EdgeMap {
    inode: add.inode,
    edges,
    };
    return Ok(None);
    }
    }
    } else if let Some(cap) = CONTEXT.captures(h) {
    if let Atom::NewVertex(ref mut c) = add {
    debug!("cap = {:?}", cap);
    c.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
    if let Some(cap) = cap.get(7) {
    c.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
    }
    }
    }
    Ok(None)
    }
    Some(Hunk::Edit {
    ref mut change,
    encoding,
    ..
    }) => {
    debug!("edit {:?}", h);
    if h.starts_with("+ ") {
    if let Atom::NewVertex(ref mut change) = change {
    if change.start == change.end {
    change.start = ChangePosition(contents_.len().into());
    }
    text_changes::parse_line_add(h, change, contents_, encoding)
    }
    } else if h.starts_with('\\') {
    if let Atom::NewVertex(ref mut change) = change {
    if change.end > change.start && contents_[change.end.us() - 1] == b'\n'
    {
    assert_eq!(change.end.us(), contents_.len());
    contents_.pop();
    change.end.0 -= 1;
    }
    }
    } else if let Some(cap) = CONTEXT.captures(h) {
    if let Atom::NewVertex(ref mut c) = change {
    debug!("cap = {:?}", cap);
    c.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
    if let Some(cap) = cap.get(7) {
    c.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
    }
    }
    } else if let Some(edges) = parse_edges(changes, h)? {
    *change = Atom::EdgeMap(EdgeMap {
    inode: change.inode(),
    edges,
    });
    }
    Ok(None)
    }
    Some(Hunk::Replacement {
    ref mut change,
    ref mut replacement,
    encoding,
    ..
    }) => {
    if h.starts_with("+ ") {
    if let Atom::NewVertex(ref mut repl) = replacement {
    if repl.start == repl.end {
    repl.start = ChangePosition(contents_.len().into());
    }
    text_changes::parse_line_add(h, repl, contents_, encoding)
    }
    } else if h.starts_with('\\') {
    if let Atom::NewVertex(ref mut repl) = replacement {
    if repl.end > repl.start && contents_[repl.end.us() - 1] == b'\n' {
    assert_eq!(repl.end.us(), contents_.len());
    contents_.pop();
    repl.end.0 -= 1;
    }
    }
    } else if let Some(cap) = CONTEXT.captures(h) {
    debug!("cap = {:?}", cap);
    if let Atom::NewVertex(ref mut repl) = replacement {
    repl.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
    if let Some(cap) = cap.get(7) {
    repl.down_context = parse_pos_vec(changes, offsets, cap.as_str())?;
    }
    }
    } else if let Some(edges) = parse_edges(changes, h)? {
    *change = Atom::EdgeMap(EdgeMap {
    inode: change.inode(),
    edges,
    });
    }
    Ok(None)
    }
    Some(Hunk::SolveNameConflict { ref mut name, .. })
    | Some(Hunk::UnsolveNameConflict { ref mut name, .. }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    *name = Atom::EdgeMap(EdgeMap {
    edges,
    inode: name.inode(),
    })
    }
    Ok(None)
    }
    Some(Hunk::SolveOrderConflict { ref mut change, .. }) => {
    if h.starts_with("+ ") {
    if let Atom::NewVertex(ref mut change) = change {
    if change.start == change.end {
    change.start = ChangePosition(contents_.len().into());
    }
    // TODO encoding
    text_changes::parse_line_add(h, change, contents_, &None)
    }
    } else if let Some(cap) = CONTEXT.captures(h) {
    debug!("cap = {:?}", cap);
    if let Atom::NewVertex(ref mut change) = change {
    change.up_context = parse_pos_vec(changes, offsets, &cap[1])?;
    if let Some(cap) = cap.get(7) {
    change.down_context =
    parse_pos_vec(changes, offsets, cap.as_str())?;
    }
    if let (Some(new_start), Some(new_end)) = (cap.get(4), cap.get(5)) {
    let new_start = new_start.as_str().parse::<u64>().unwrap();
    let new_end = new_end.as_str().parse::<u64>().unwrap();
    change.start = ChangePosition(contents_.len().into());
    change.end = ChangePosition(
    (contents_.len() as u64 + new_end - new_start).into(),
    );
    offsets.insert(new_end, change.end);
    }
    }
    }
    Ok(None)
    }
    Some(Hunk::UnsolveOrderConflict { ref mut change, .. }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    if let Atom::EdgeMap(ref mut change) = change {
    change.edges = edges
    }
    }
    Ok(None)
    }
    Some(Hunk::ResurrectZombies { ref mut change, .. }) => {
    if let Some(edges) = parse_edges(changes, h)? {
    if let Atom::EdgeMap(ref mut change) = change {
    change.edges = edges
    }
    }
    Ok(None)
    }
    None => {
    debug!("current = {:#?}", current);
    debug!("h = {:?}", h);
    Ok(None)
    }
    }
    [7.67550]
    [7.79931]
    local: Local { path, line },
    }),
    PrintableHunk::ResurrectZombies {
    path,
    line,
    pos,
    encoding,
    change,
    contents: _,
    } => Ok(Hunk::ResurrectZombies {
    change: Atom::EdgeMap(EdgeMap {
    edges: from_printable_edge_map(&change, changes)?,
    inode: from_printable_pos(changes, pos)?,
    }),
    local: Local { path, line },
    encoding,
    }),
  • edit in libpijul/src/change/text_changes.rs at line 906
    [7.4608][7.4608:4849](),[7.4849][7.79941:80257](),[7.79941][7.79941:80257]()
    fn encoding_from_label(cap: Captures) -> Option<Encoding> {
    let encoding_label = cap.name("encoding").unwrap().as_str();
    if encoding_label != BINARY_LABEL {
    Some(Encoding::for_label(encoding_label))
    } else {
    None
    }
    }
    lazy_static! {
    static ref POS: regex::Regex = regex::Regex::new(r#"(\d+)\.(\d+)"#).unwrap();
    static ref EDGE: regex::Regex =
    regex::Regex::new(r#"\s*(?P<prev>[BFD]*):(?P<flag>[BFD]*)\s+(?P<up_c>\d+)\.(?P<up_l>\d+)\s*->\s*(?P<c>\d+)\.(?P<l0>\d+):(?P<l1>\d+)/(?P<intro>\d+)\s*"#).unwrap();
    }
  • edit in libpijul/src/change/text_changes.rs at line 913
    [7.80495][7.80495:80559](),[7.80559][7.142643:142684](),[7.142684][7.80595:80708](),[7.80595][7.80595:80708]()
    inode: Position {
    change: Some(Hash::None),
    pos: ChangePosition(L64(0)),
    },
    }
    }
    pub fn default_edgemap() -> EdgeMap<Option<Hash>> {
    EdgeMap {
    edges: Vec::new(),
  • replacement in libpijul/src/change/text_changes.rs at line 920
    [7.80828][7.10441:10518](),[7.10518][7.80907:80927](),[7.80907][7.80907:80927](),[7.80927][7.10519:10587](),[7.10587][7.80997:81102](),[7.80997][7.80997:81102]()
    pub fn has_newvertices<L>(current: &Option<Hunk<Option<Hash>, L>>) -> bool {
    match current {
    Some(Hunk::FileAdd { contents: None, .. }) | None => false,
    Some(rec) => rec.iter().any(|e| matches!(e, Atom::NewVertex(_))),
    }
    }
    pub fn parse_pos_vec(
    [7.80828]
    [7.81102]
    // TODO: rename
    pub fn from_printable_pos_vec_offsets(
  • replacement in libpijul/src/change/text_changes.rs at line 924
    [7.81182][7.81182:81195]()
    s: &str,
    [7.81182]
    [7.81195]
    s: &[PrintablePos],
  • replacement in libpijul/src/change/text_changes.rs at line 927
    [7.81279][7.81279:81459]()
    for pos in POS.captures_iter(s) {
    let change: usize = (&pos[1]).parse().unwrap();
    let pos: u64 = (&pos[2]).parse().unwrap();
    let pos = if change == 0 {
    [7.81279]
    [7.81459]
    for PrintablePos(change, pos) in s {
    let pos = if *change == 0 {
  • replacement in libpijul/src/change/text_changes.rs at line 933
    [7.81622][7.1310:1373]()
    return Err(TextDeError::MissingPosition(pos));
    [7.81622]
    [7.81686]
    return Err(TextDeError::MissingPosition(*pos));
  • replacement in libpijul/src/change/text_changes.rs at line 939
    [7.81786][7.1374:1424]()
    change: change_ref(changes, change)?,
    [7.81786]
    [7.81835]
    change: change_ref(changes, *change)?,
  • replacement in libpijul/src/change/text_changes.rs at line 959
    [7.82168][7.1695:1713]()
    pub fn parse_pos(
    [7.82168]
    [7.1713]
    pub fn from_printable_pos(
  • replacement in libpijul/src/change/text_changes.rs at line 961
    [7.1749][7.1749:1762]()
    s: &str,
    [7.1749]
    [7.1762]
    pos: PrintablePos,
  • edit in libpijul/src/change/text_changes.rs at line 963
    [7.1813][7.82254:82393](),[7.82254][7.82254:82393]()
    let pos = POS.captures(s).unwrap();
    let change: usize = (&pos[1]).parse().unwrap();
    let pos: u64 = (&pos[2]).parse().unwrap();
  • replacement in libpijul/src/change/text_changes.rs at line 964
    [7.1832][7.1832:1878](),[7.1878][7.142773:142820]()
    change: change_ref(changes, change)?,
    pos: ChangePosition(L64(pos.to_le())),
    [7.1832]
    [7.1879]
    change: change_ref(changes, pos.0)?,
    pos: ChangePosition(L64(pos.1.to_le())),
  • replacement in libpijul/src/change/text_changes.rs at line 969
    [7.82496][7.1887:1907]()
    pub fn parse_edges(
    [7.82496]
    [7.1907]
    pub fn from_printable_pos_vec(
  • replacement in libpijul/src/change/text_changes.rs at line 971
    [7.1943][7.1943:2019](),[7.2019][7.82596:83547](),[7.82596][7.82596:83547](),[7.83547][7.2020:2079](),[7.2079][7.142821:142881](),[7.142881][7.83652:83700](),[7.83652][7.83652:83700](),[7.83700][7.2080:2139](),[7.2139][7.142882:143006](),[7.143006][7.83856:83875](),[7.83856][7.83856:83875](),[7.83875][7.2140:2208](),[7.2208][7.83942:84008](),[7.83942][7.83942:84008](),[7.84008][7.2209:2238](),[7.2238][7.84033:84043](),[7.84033][7.84033:84043]()
    s: &str,
    ) -> Result<Option<Vec<NewEdge<Option<Hash>>>>, TextDeError> {
    debug!("parse_edges {:?}", s);
    let mut result = Vec::new();
    for edge in s.split(',') {
    debug!("parse edge {:?}", edge);
    if let Some(cap) = EDGE.captures(edge) {
    let previous = read_flag(cap.name("prev").unwrap().as_str());
    let flag = read_flag(cap.name("flag").unwrap().as_str());
    let change0: usize = cap.name("up_c").unwrap().as_str().parse().unwrap();
    let pos0: u64 = cap.name("up_l").unwrap().as_str().parse().unwrap();
    let change1: usize = cap.name("c").unwrap().as_str().parse().unwrap();
    let start1: u64 = cap.name("l0").unwrap().as_str().parse().unwrap();
    let end1: u64 = cap.name("l1").unwrap().as_str().parse().unwrap();
    let introduced_by: usize = cap.name("intro").unwrap().as_str().parse().unwrap();
    result.push(NewEdge {
    previous,
    flag,
    from: Position {
    change: change_ref(changes, change0)?,
    pos: ChangePosition(L64(pos0.to_le())),
    },
    to: Vertex {
    change: change_ref(changes, change1)?,
    start: ChangePosition(L64(start1.to_le())),
    end: ChangePosition(L64(end1.to_le())),
    },
    introduced_by: change_ref(changes, introduced_by)?,
    })
    } else {
    debug!("not parsed");
    return Ok(None);
    }
    [7.1943]
    [7.84043]
    pos: &[PrintablePos],
    ) -> Result<Vec<Position<Option<Hash>>>, TextDeError> {
    let mut buf = Vec::new();
    for p in pos {
    buf.push(from_printable_pos(changes, *p)?);
  • replacement in libpijul/src/change/text_changes.rs at line 977
    [7.84049][7.2239:2260]()
    Ok(Some(result))
    [7.84049]
    [7.84066]
    Ok(buf)
  • edit in libpijul/src/change/text_changes.rs at line 980
    [7.84069][7.768:987](),[7.987][7.1204:1262](),[7.1262][7.1033:1040](),[7.1033][7.1033:1040](),[7.1040][7.84191:84540](),[7.84191][7.84191:84540](),[7.84540][7.143007:143064](),[7.143064][7.84597:84600](),[7.84597][7.84597:84600]()
    pub fn parse_line_add(
    h: &str,
    change: &mut NewVertex<Option<Hash>>,
    contents_: &mut Vec<u8>,
    encoding: &Option<Encoding>,
    ) {
    let h = match encoding {
    Some(encoding) => encoding.encode(h),
    None => std::borrow::Cow::Borrowed(h.as_bytes()),
    };
    debug!("parse_line_add {:?} {:?}", change.end, change.start);
    debug!("parse_line_add {:?}", h);
    if h.len() > 2 {
    let h = &h[2..h.len()];
    contents_.extend(h);
    } else if h.len() > 1 {
    contents_.push(b'\n');
    }
    debug!("contents_.len() = {:?}", contents_.len());
    trace!("contents_ = {:?}", contents_);
    change.end = ChangePosition(contents_.len().into());
    }
  • replacement in libpijul/src/change/text_changes.rs at line 989
    [7.1668][7.3699:3775](),[7.3699][7.3699:3775]()
    write!(self, "{}b{}", pref, data_encoding::BASE64.encode(contents))
    [7.1668]
    [7.3775]
    writeln!(self, "{}b{}", pref, data_encoding::BASE64.encode(contents))
  • edit in libpijul/src/change/text_changes.rs at line 996
    [7.143163][7.3825:3869](),[7.3825][7.3825:3869](),[7.3869][7.84642:84694](),[7.84642][7.84642:84694](),[7.84694][7.449:482](),[7.482][7.84694:84728](),[7.84694][7.84694:84728](),[7.84728][7.483:522](),[7.522][7.1263:1466](),[7.1466][7.1236:1278](),[7.6169][7.1236:1278](),[7.591][7.1236:1278](),[7.1278][7.85088:85206](),[7.3962][7.85088:85206](),[7.5848][7.85088:85206](),[7.85088][7.85088:85206]()
    pub fn print_contents<W: WriteChangeLine>(
    w: &mut W,
    pref: &str,
    contents: &[u8],
    encoding: &Option<Encoding>,
    ) -> Result<(), std::io::Error> {
    if let Some(encoding) = encoding {
    let dec = encoding.decode(&contents);
    let dec = if dec.ends_with("\n") {
    &dec[..dec.len() - 1]
    } else {
    &dec
    };
    for a in dec.split('\n') {
    w.write_change_line(pref, a)?
    }
    } else {
    writeln!(w, "{}b{}", pref, data_encoding::BASE64.encode(contents))?
    }
    Ok(())
    }
  • replacement in libpijul/src/change/text_changes.rs at line 997
    [7.85207][7.3963:4029](),[7.4029][7.85272:85287](),[7.85272][7.85272:85287]()
    pub fn print_change_contents<W: WriteChangeLine, C: ChangeStore>(
    w: &mut W,
    [7.85207]
    [7.85287]
    pub fn get_change_contents<C: ChangeStore>(
  • replacement in libpijul/src/change/text_changes.rs at line 1001
    [7.85365][7.4850:4883](),[7.4883][7.85365:85407](),[7.85365][7.85365:85407](),[7.85407][7.6170:6220]()
    encoding: &Option<Encoding>,
    ) -> Result<(), TextSerError<C::Error>> {
    debug!("print_change_contents {:?}", change);
    [7.85365]
    [7.85407]
    ) -> Result<Vec<u8>, TextSerError<C::Error>> {
    debug!("get_change_contents {:?}", change);
  • replacement in libpijul/src/change/text_changes.rs at line 1004
    [7.85426][7.85426:85462](),[7.85462][7.143164:143228](),[7.143228][7.4884:4934](),[7.639][7.85578:85615](),[7.4934][7.85578:85615](),[7.85578][7.85578:85615](),[7.85615][7.6221:6278](),[7.6278][7.169:206](),[7.85615][7.169:206](),[7.206][7.85650:85693](),[7.85650][7.85650:85693](),[7.85693][7.528:623]()
    Atom::NewVertex(ref n) => {
    let c = &change_contents[n.start.us()..n.end.us()];
    print_contents(w, "+", c, encoding)?;
    if !c.ends_with(b"\n") {
    debug!("print_change_contents {:?}", c);
    writeln!(w, "\n\\")?
    }
    Ok(())
    }
    Atom::EdgeMap(ref n) if n.edges.is_empty() => return Err(TextSerError::InvalidChange),
    [7.85426]
    [7.85693]
    Atom::NewVertex(ref n) => Ok(change_contents[n.start.us()..n.end.us()].to_vec()),
    Atom::EdgeMap(ref n) if n.edges.is_empty() => Err(TextSerError::InvalidChange),
  • edit in libpijul/src/change/text_changes.rs at line 1007
    [7.85775]
    [7.85775]
    // TODO: get rid of `tmp` and/or `buf`
  • edit in libpijul/src/change/text_changes.rs at line 1009
    [7.85813]
    [7.85813]
    let mut tmp = Vec::new();
  • replacement in libpijul/src/change/text_changes.rs at line 1015
    [7.85978][7.85978:86007]()
    buf.clear();
    [7.85978]
    [7.86007]
    tmp.clear();
  • replacement in libpijul/src/change/text_changes.rs at line 1017
    [7.86031][7.86031:86085]()
    .get_contents_ext(e.to, &mut buf)
    [7.86031]
    [7.86085]
    .get_contents_ext(e.to, &mut tmp)
  • replacement in libpijul/src/change/text_changes.rs at line 1019
    [7.86133][7.4935:4997](),[7.443][7.0:43](),[7.4997][7.0:43](),[7.86184][7.0:43](),[7.43][7.6279:6342](),[7.6342][7.43:95](),[7.43][7.43:95]()
    print_contents(w, "-", &buf[..], &encoding)?;
    if !buf.ends_with(b"\n") {
    debug!("print_change_contents {:?}", buf);
    writeln!(w)?;
    }
    [7.86133]
    [7.86184]
    buf.extend_from_slice(&tmp);
  • replacement in libpijul/src/change/text_changes.rs at line 1022
    [7.86235][7.86235:86254]()
    Ok(())
    [7.86235]
    [7.86254]
    Ok(buf)
  • replacement in libpijul/src/change/text_changes.rs at line 1024
    [7.86264][7.86264:86285]()
    _ => Ok(()),
    [7.86264]
    [7.86285]
    _ => Ok(Vec::new()),
  • replacement in libpijul/src/change/text_changes.rs at line 1028
    [7.86294][7.86294:86372]()
    pub fn write_deleted_names<W: std::io::Write, C: ChangeStore>(
    w: &mut W,
    [7.86294]
    [7.86372]
    pub fn get_deleted_names<C: ChangeStore>(
  • replacement in libpijul/src/change/text_changes.rs at line 1031
    [7.86419][7.86419:86461]()
    ) -> Result<(), TextSerError<C::Error>> {
    [7.86419]
    [7.86461]
    ) -> Result<Vec<String>, TextSerError<C::Error>> {
    let mut res = Vec::new();
  • replacement in libpijul/src/change/text_changes.rs at line 1034
    [7.86501][7.86501:86568]()
    let mut buf = Vec::new();
    let mut is_first = true;
    [7.86501]
    [7.86568]
    let mut tmp = Vec::new();
  • replacement in libpijul/src/change/text_changes.rs at line 1036
    [7.86602][7.86602:86627]()
    buf.clear();
    [7.86602]
    [7.86627]
    tmp.clear();
  • replacement in libpijul/src/change/text_changes.rs at line 1038
    [7.86647][7.86647:86697]()
    .get_contents_ext(d.to, &mut buf)
    [7.86647]
    [7.86697]
    .get_contents_ext(d.to, &mut tmp)
  • replacement in libpijul/src/change/text_changes.rs at line 1040
    [7.86741][7.86741:86774](),[7.86774][7.3390:3474](),[7.3474][7.86850:86962](),[7.86850][7.86850:86962]()
    if !buf.is_empty() {
    let FileMetadata { basename: name, .. } = FileMetadata::read(&buf);
    write!(w, "{}{:?}", if is_first { "" } else { ", " }, name)?;
    is_first = false;
    [7.86741]
    [7.86962]
    if !tmp.is_empty() {
    let FileMetadata { basename: name, .. } = FileMetadata::read(&tmp);
    res.push(name.to_string());
  • replacement in libpijul/src/change/text_changes.rs at line 1046
    [7.86992][7.86992:87003]()
    Ok(())
    [7.86992]
    [7.87003]
    Ok(res)
  • replacement in libpijul/src/change/text_changes.rs at line 1049
    [7.87006][7.87006:87812]()
    pub fn write_flag<W: std::io::Write>(mut w: W, flag: EdgeFlags) -> Result<(), std::io::Error> {
    if flag.contains(EdgeFlags::BLOCK) {
    w.write_all(b"B")?;
    }
    if flag.contains(EdgeFlags::FOLDER) {
    w.write_all(b"F")?;
    }
    if flag.contains(EdgeFlags::DELETED) {
    w.write_all(b"D")?;
    }
    assert!(!flag.contains(EdgeFlags::PARENT));
    assert!(!flag.contains(EdgeFlags::PSEUDO));
    Ok(())
    }
    pub fn read_flag(s: &str) -> EdgeFlags {
    let mut f = EdgeFlags::empty();
    for i in s.chars() {
    match i {
    'B' => f |= EdgeFlags::BLOCK,
    'F' => f |= EdgeFlags::FOLDER,
    'D' => f |= EdgeFlags::DELETED,
    c => panic!("read_flag: {:?}", c),
    }
    }
    f
    }
    pub fn write_pos<W: std::io::Write>(
    mut w: W,
    [7.87006]
    [7.87812]
    pub fn to_printable_pos(
  • replacement in libpijul/src/change/text_changes.rs at line 1052
    [7.87880][7.87880:87914]()
    ) -> Result<(), std::io::Error> {
    [7.87880]
    [7.87914]
    ) -> PrintablePos {
  • replacement in libpijul/src/change/text_changes.rs at line 1060
    [7.88087][7.88087:88142]()
    write!(w, "{}.{}", change, pos.pos.0)?;
    Ok(())
    [7.88087]
    [7.88142]
    PrintablePos(change, pos.pos.0 .0)
  • replacement in libpijul/src/change/text_changes.rs at line 1063
    [7.88145][7.88145:88198]()
    pub fn write_atom<W: std::io::Write>(
    w: &mut W,
    [7.88145]
    [7.88198]
    pub fn to_printable_pos_vec(
  • replacement in libpijul/src/change/text_changes.rs at line 1065
    [7.88233][7.88233:88447]()
    atom: &Atom<Option<Hash>>,
    ) -> Result<(), std::io::Error> {
    match atom {
    Atom::NewVertex(ref n) => write_newvertex(w, hashes, n),
    Atom::EdgeMap(ref n) => write_edgemap(w, hashes, n),
    }
    [7.88233]
    [7.88447]
    pos: &[Position<Option<Hash>>],
    ) -> Vec<PrintablePos> {
    pos.iter().map(|c| to_printable_pos(hashes, *c)).collect()
  • edit in libpijul/src/change/text_changes.rs at line 1069
    [7.88449][7.88449:89960](),[7.89960][7.10588:10689](),[7.10689][7.90065:90121](),[7.90065][7.90065:90121]()
    pub fn write_newvertex<W: std::io::Write>(
    mut w: W,
    hashes: &HashMap<Hash, usize>,
    n: &NewVertex<Option<Hash>>,
    ) -> Result<(), std::io::Error> {
    write!(w, " up")?;
    for c in n.up_context.iter() {
    write!(w, " ")?;
    write_pos(&mut w, hashes, *c)?
    }
    write!(w, ", new {}:{}", n.start.0, n.end.0)?;
    if !n.down_context.is_empty() {
    write!(w, ", down")?;
    for c in n.down_context.iter() {
    write!(w, " ")?;
    write_pos(&mut w, hashes, *c)?
    }
    }
    w.write_all(b"\n")?;
    Ok(())
    }
    pub fn write_edgemap<W: std::io::Write>(
    mut w: W,
    hashes: &HashMap<Hash, usize>,
    n: &EdgeMap<Option<Hash>>,
    ) -> Result<(), std::io::Error> {
    let mut is_first = true;
    for c in n.edges.iter() {
    if !is_first {
    write!(w, ", ")?;
    }
    is_first = false;
    write_flag(&mut w, c.previous)?;
    write!(w, ":")?;
    write_flag(&mut w, c.flag)?;
    write!(w, " ")?;
    write_pos(&mut w, hashes, c.from)?;
    write!(w, " -> ")?;
    write_pos(&mut w, hashes, c.to.start_pos())?;
    let h = if let Some(h) = hashes.get(c.introduced_by.as_ref().unwrap()) {
    h
    } else {
    panic!("introduced_by = {:?}, not found", c.introduced_by);
    };
    write!(w, ":{}/{}", c.to.end.0, h)?;
    }
    writeln!(w)?;
    Ok(())
    }
    #[derive(Debug, Clone, PartialEq, Eq)]
    pub enum Section {
    Header(String),
    Deps,
    Changes {
    changes: Vec<Hunk<Option<Hash>, Local>>,
    current: Option<Hunk<Option<Hash>, Local>>,
    offsets: HashMap<u64, ChangePosition>,
    },
    }
  • file addition: printable.rs (----------)
    [49.931000]
    use std::fmt;
    #[cfg(test)]
    use quickcheck::{Arbitrary, Gen};
    #[cfg(test)]
    use PrintableHunk::*;
    use super::*;
    #[derive(Debug, Clone, Copy, PartialEq, Eq)]
    pub enum StringFragment<'a> {
    Literal(&'a str),
    EscapedChar(char),
    }
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    pub enum PrintablePerms {
    IsDir,
    IsExecutable,
    IsFile,
    }
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    pub struct PrintableEdgeFlags {
    pub block: bool,
    pub folder: bool,
    pub deleted: bool,
    }
    #[derive(Clone, Debug, Eq, PartialEq)]
    pub struct PrintableEdge {
    pub previous: PrintableEdgeFlags,
    pub flag: PrintableEdgeFlags,
    pub from: PrintablePos,
    pub to_start: PrintablePos,
    pub to_end: u64,
    pub introduced_by: usize,
    }
    #[derive(Clone, Debug, Eq, PartialEq)]
    pub struct PrintableNewVertex {
    pub up_context: Vec<PrintablePos>,
    pub start: u64,
    pub end: u64,
    pub down_context: Vec<PrintablePos>,
    }
    #[derive(Clone, Debug, Eq, PartialEq)]
    pub enum PrintableAtom {
    NewVertex(PrintableNewVertex),
    Edges(Vec<PrintableEdge>),
    }
    #[derive(Copy, Clone, Debug, Eq, PartialEq)]
    pub struct PrintablePos(pub usize, pub u64);
    // TODO: Some of these are untested: I didn't know how to create them from the
    // command line
    // NOTE: contents must be valid under the chosen encoding
    #[derive(PartialEq, Eq, Clone, Debug)]
    pub enum PrintableHunk {
    FileMoveV {
    path: String,
    name: String,
    perms: PrintablePerms,
    pos: PrintablePos,
    up_context: Vec<PrintablePos>,
    down_context: Vec<PrintablePos>,
    del: Vec<PrintableEdge>,
    },
    FileMoveE {
    path: String,
    pos: PrintablePos,
    add: Vec<PrintableEdge>,
    del: Vec<PrintableEdge>,
    },
    FileAddition {
    name: String,
    parent: String,
    perms: PrintablePerms,
    encoding: Option<Encoding>,
    up_context: Vec<PrintablePos>,
    start: u64,
    end: u64,
    contents: Vec<u8>,
    },
    FileDel {
    path: String,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    del_edges: Vec<PrintableEdge>,
    content_edges: Vec<PrintableEdge>,
    contents: Vec<u8>,
    },
    FileUndel {
    path: String,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    undel_edges: Vec<PrintableEdge>,
    content_edges: Vec<PrintableEdge>,
    contents: Vec<u8>,
    },
    Edit {
    path: String,
    line: usize,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    change: PrintableAtom,
    contents: Vec<u8>,
    },
    Replace {
    path: String,
    line: usize,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    change: Vec<PrintableEdge>,
    replacement: PrintableNewVertex,
    change_contents: Vec<u8>,
    replacement_contents: Vec<u8>,
    },
    SolveNameConflict {
    path: String,
    pos: PrintablePos,
    names: Vec<String>,
    edges: Vec<PrintableEdge>,
    },
    UnsolveNameConflict {
    path: String,
    pos: PrintablePos,
    names: Vec<String>,
    edges: Vec<PrintableEdge>,
    },
    SolveOrderConflict {
    path: String,
    line: usize,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    change: PrintableNewVertex,
    contents: Vec<u8>,
    },
    UnsolveOrderConflict {
    path: String,
    line: usize,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    change: Vec<PrintableEdge>,
    contents: Vec<u8>,
    },
    ResurrectZombies {
    path: String,
    line: usize,
    pos: PrintablePos,
    encoding: Option<Encoding>,
    change: Vec<PrintableEdge>,
    contents: Vec<u8>,
    },
    }
    #[derive(PartialEq, Eq, Clone, Debug)]
    pub struct PrintableDep {
    pub type_: DepType,
    pub hash: String,
    }
    // TODO: make names more precise. I don't know what these should be named.
    #[derive(PartialEq, Eq, Clone, Debug)]
    pub enum DepType {
    Numbered(usize, bool), // (number, plus-sign)
    ExtraKnown,
    ExtraUnknown,
    }
    pub struct Escaped<'a>(pub &'a str);
    impl<'a> fmt::Display for Escaped<'a> {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(fmt, "\"")?;
    for c in self.0.chars() {
    if c == '\n' {
    write!(fmt, "\\n")?
    } else if c == '\r' {
    write!(fmt, "\\r")?
    } else if c == '\t' {
    write!(fmt, "\\t")?
    } else if c == '\u{08}' {
    write!(fmt, "\\b")?
    } else if c == '\u{0C}' {
    write!(fmt, "\\f")?
    } else if c == '\\' {
    write!(fmt, "\\\\")?
    } else if c == '"' {
    write!(fmt, "\\\"")?
    } else {
    write!(fmt, "{}", c)?
    }
    }
    write!(fmt, "\"")?;
    Ok(())
    }
    }
    impl PrintablePerms {
    pub fn from_metadata(perms: InodeMetadata) -> Self {
    if perms.0 & 0o1000 == 0o1000 {
    PrintablePerms::IsDir
    } else if perms.0 & 0o100 == 0o100 {
    PrintablePerms::IsExecutable
    } else {
    PrintablePerms::IsFile
    }
    }
    pub fn to_metadata(self) -> InodeMetadata {
    InodeMetadata(match self {
    PrintablePerms::IsDir => 0o1100,
    PrintablePerms::IsExecutable => 0o100,
    PrintablePerms::IsFile => 0o0,
    })
    }
    }
    impl fmt::Display for PrintablePos {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(fmt, "{}.{}", self.0, self.1)
    }
    }
    impl fmt::Display for PrintablePerms {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(
    fmt,
    "{}",
    match self {
    PrintablePerms::IsDir => " +dx",
    PrintablePerms::IsExecutable => " +x",
    PrintablePerms::IsFile => "",
    }
    )
    }
    }
    impl PrintableEdgeFlags {
    pub fn from(ef: EdgeFlags) -> Self {
    assert!(!ef.contains(EdgeFlags::PARENT));
    assert!(!ef.contains(EdgeFlags::PSEUDO));
    Self {
    block: ef.contains(EdgeFlags::BLOCK),
    folder: ef.contains(EdgeFlags::FOLDER),
    deleted: ef.contains(EdgeFlags::DELETED),
    }
    }
    // TODO: make this nicer
    pub fn to(self) -> EdgeFlags {
    let mut f = EdgeFlags::empty();
    if self.block {
    f |= EdgeFlags::BLOCK;
    }
    if self.folder {
    f |= EdgeFlags::FOLDER;
    }
    if self.deleted {
    f |= EdgeFlags::DELETED;
    }
    f
    }
    }
    impl fmt::Display for PrintableEdgeFlags {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    if self.block {
    write!(fmt, "B")?;
    }
    if self.folder {
    write!(fmt, "F")?;
    }
    if self.deleted {
    write!(fmt, "D")?;
    }
    Ok(())
    }
    }
    impl fmt::Display for PrintableEdge {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(
    fmt,
    "{}:{} {} -> {}:{}/{}",
    self.previous, self.flag, self.from, self.to_start, self.to_end, self.introduced_by
    )
    }
    }
    impl fmt::Display for PrintableNewVertex {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    write!(fmt, " up")?;
    for c in self.up_context.iter() {
    write!(fmt, " {}", c)?
    }
    write!(fmt, ", new {}:{}, down", self.start, self.end)?;
    for c in self.down_context.iter() {
    write!(fmt, " {}", c)?;
    }
    Ok(())
    }
    }
    impl fmt::Display for PrintableAtom {
    fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
    match self {
    PrintableAtom::NewVertex(x) => write!(fmt, "{}", x),
    PrintableAtom::Edges(x) => {
    for (i, edge) in x.iter().enumerate() {
    if i > 0 {
    write!(fmt, ", ")?;
    }
    write!(fmt, "{}", edge)?;
    }
    Ok(())
    }
    }
    }
    }
    impl PrintableHunk {
    pub fn write<W: WriteChangeLine>(&self, w: &mut W) -> Result<(), std::io::Error> {
    use PrintableHunk::*;
    match self {
    FileMoveV {
    path,
    name,
    perms,
    pos,
    up_context,
    down_context,
    del,
    } => {
    writeln!(
    w,
    "Moved: {} {} {} {}",
    Escaped(path),
    Escaped(name),
    perms,
    pos,
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(del.to_vec()))?;
    write!(w, "up")?;
    for c in up_context.iter() {
    write!(w, " {}", c)?
    }
    write!(w, ", down")?;
    for c in down_context.iter() {
    write!(w, " {}", c)?
    }
    writeln!(w)?;
    }
    FileMoveE {
    path,
    pos,
    add,
    del,
    } => {
    writeln!(w, "Moved: {} {}", Escaped(path), pos)?;
    writeln!(w, "{}", PrintableAtom::Edges(add.to_vec()))?;
    writeln!(w, "{}", PrintableAtom::Edges(del.to_vec()))?;
    }
    FileAddition {
    name,
    parent,
    perms,
    encoding,
    up_context,
    start,
    end,
    contents,
    } => {
    write!(
    w,
    "File addition: {} in {}{} {}\n up",
    Escaped(name),
    Escaped(parent),
    perms,
    Escaped(encoding_label(encoding)),
    )?;
    for c in up_context.iter() {
    write!(w, " {}", c)?
    }
    writeln!(w, ", new {}:{}", start, end)?;
    print_contents(w, "+", contents, encoding)?;
    }
    FileDel {
    path,
    pos,
    encoding,
    del_edges,
    content_edges,
    contents,
    } => {
    writeln!(
    w,
    "File deletion: {} {} {}",
    Escaped(path),
    pos,
    Escaped(encoding_label(encoding)),
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(del_edges.to_vec()))?;
    writeln!(w, "{}", PrintableAtom::Edges(content_edges.to_vec()))?;
    print_contents(w, "-", contents, encoding)?;
    }
    FileUndel {
    path,
    pos,
    encoding,
    undel_edges,
    content_edges,
    contents,
    } => {
    writeln!(
    w,
    "File un-deletion: {} {} {}",
    Escaped(path),
    pos,
    Escaped(encoding_label(encoding)),
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(undel_edges.to_vec()))?;
    writeln!(w, "{}", PrintableAtom::Edges(content_edges.to_vec()))?;
    print_contents(w, "+", contents, encoding)?;
    }
    Edit {
    path,
    line,
    pos,
    encoding,
    change,
    contents,
    } => {
    writeln!(
    w,
    "Edit in {}:{} {} {}",
    Escaped(&path),
    line,
    pos,
    Escaped(encoding_label(encoding))
    )?;
    writeln!(w, "{}", change)?;
    print_contents(w, "+", contents, encoding)?;
    }
    Replace {
    path,
    line,
    pos,
    encoding,
    change,
    replacement,
    change_contents,
    replacement_contents,
    } => {
    writeln!(
    w,
    "Replacement in {}:{} {} {}",
    Escaped(&path),
    line,
    pos,
    Escaped(encoding_label(encoding))
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
    writeln!(w, "{}", PrintableAtom::NewVertex(replacement.clone()))?;
    print_contents(w, "-", change_contents, encoding)?;
    print_contents(w, "+", replacement_contents, encoding)?;
    }
    SolveNameConflict {
    path,
    pos,
    names,
    edges,
    } => {
    write!(w, "Solving a name conflict in {} {}: ", Escaped(path), pos,)?;
    write_names(w, names)?;
    writeln!(w)?;
    writeln!(w, "{}", PrintableAtom::Edges(edges.clone()))?;
    }
    UnsolveNameConflict {
    path,
    pos,
    names,
    edges,
    } => {
    write!(
    w,
    "Un-solving a name conflict in {} {}: ",
    Escaped(path),
    pos,
    )?;
    write_names(w, names)?;
    writeln!(w)?;
    writeln!(w, "{}", PrintableAtom::Edges(edges.clone()))?;
    }
    SolveOrderConflict {
    path,
    line,
    pos,
    encoding,
    change,
    contents,
    } => {
    writeln!(
    w,
    "Solving an order conflict in {}:{} {} {}",
    Escaped(path),
    line,
    pos,
    Escaped(encoding_label(encoding)),
    )?;
    writeln!(w, "{}", change)?;
    print_contents(w, "+", contents, encoding)?;
    }
    UnsolveOrderConflict {
    path,
    line,
    pos,
    encoding,
    change,
    contents,
    } => {
    writeln!(
    w,
    "Un-solving an order conflict in {}:{} {} {}",
    Escaped(path),
    line,
    pos,
    Escaped(encoding_label(encoding))
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
    print_contents(w, "-", contents, encoding)?;
    }
    ResurrectZombies {
    path,
    line,
    pos,
    encoding,
    change,
    contents,
    } => {
    writeln!(
    w,
    "Resurrecting zombie lines in {}:{} {} {}",
    Escaped(path),
    line,
    pos,
    Escaped(encoding_label(encoding))
    )?;
    writeln!(w, "{}", PrintableAtom::Edges(change.clone()))?;
    print_contents(w, "+", contents, encoding)?;
    }
    };
    Ok(())
    }
    }
    pub fn write_names<W: std::io::Write>(w: &mut W, names: &[String]) -> Result<(), std::io::Error> {
    for (i, name) in names.iter().enumerate() {
    if i > 0 {
    write!(w, ", ")?;
    }
    write!(w, "{}", Escaped(name))?;
    }
    Ok(())
    }
    pub fn get_encoding(contents: &[u8]) -> Option<Encoding> {
    let mut detector = crate::chardetng::EncodingDetector::new();
    detector.feed(contents, true);
    if let Some(e) = detector.get_valid(None, true, &contents) {
    Some(Encoding(e))
    } else {
    None
    }
    // let (encoding_guess, may_be_right) = detector.guess_assess(None, true);
    // if may_be_right {
    // Some(Encoding(encoding_guess))
    // } else {
    // None
    // }
    }
    fn print_contents<W: WriteChangeLine>(
    w: &mut W,
    prefix: &str,
    contents: &[u8],
    encoding: &Option<Encoding>,
    ) -> Result<(), std::io::Error> {
    if let Some(encoding) = encoding {
    let dec = encoding.decode(&contents);
    let ends_with_newline = dec.ends_with("\n");
    let dec = if ends_with_newline {
    &dec[..dec.len() - 1]
    } else {
    &dec
    };
    for a in dec.split('\n') {
    writeln!(w, "{} {}", prefix, a)?;
    }
    if !ends_with_newline {
    writeln!(w, "\\")?;
    }
    Ok(())
    } else {
    writeln!(w, "{}b{}", prefix, data_encoding::BASE64.encode(contents))
    }
    }
    // QuickCheck instances
    #[cfg(test)]
    #[rustfmt::skip]
    // This may be nicer if it was generated by a macro
    impl Arbitrary for PrintableHunk {
    fn arbitrary(g: &mut Gen) -> Self {
    fn f<A: Arbitrary>(g: &mut Gen) -> A {
    Arbitrary::arbitrary(g)
    }
    fix_encoding(Gen::new(g.size()).choose(&[
    FileMoveV {
    path: f(g), name: f(g), perms: f(g), pos: f(g), up_context: f(g), down_context: f(g), del: f(g),
    },
    FileMoveE {
    path: f(g), pos: f(g), add: f(g), del: f(g),
    },
    FileAddition {
    name: f(g), parent: f(g), perms: f(g), encoding: f(g), up_context: f(g), start: f(g), end: f(g), contents: f(g),
    },
    FileDel {
    path: f(g), pos: f(g), encoding: f(g), del_edges: f(g), content_edges: f(g), contents: f(g),
    },
    FileUndel {
    path: f(g), pos: f(g), encoding: f(g), undel_edges: f(g), content_edges: f(g), contents: f(g),
    },
    Edit {
    path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
    },
    Replace {
    path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), replacement: f(g), change_contents: f(g), replacement_contents: f(g),
    },
    SolveNameConflict {
    path: f(g), pos: f(g), names: f(g), edges: f(g),
    },
    UnsolveNameConflict {
    path: f(g), pos: f(g), names: f(g), edges: f(g),
    },
    SolveOrderConflict {
    path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
    },
    UnsolveOrderConflict {
    path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
    },
    ResurrectZombies {
    path: f(g), line: f(g), pos: f(g), encoding: f(g), change: f(g), contents: f(g),
    },
    ])
    .unwrap().clone())
    }
    // Shrinking frequently blows stack. Investigate how to fix it.
    // You can disable shrinking by commenting out this function.
    // This may be best solved by switching to proptest crate
    /*
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    match self.clone() {
    FileMoveV { path, name, perms, pos, up_context, down_context, del } =>
    Box::new((path, name, perms, pos, up_context, down_context, del)
    .shrink().map(|(path, name, perms, pos, up_context, down_context, del)|
    fix_encoding(FileMoveV { path, name, perms, pos, up_context, down_context, del }))),
    FileMoveE { path, pos, add, del } =>
    Box::new((path, pos, add, del)
    .shrink().map(|(path, pos, add, del)|
    fix_encoding(FileMoveE { path, pos, add, del }))),
    FileAddition { name, parent, perms, encoding, up_context, start, end, contents } =>
    Box::new((name, parent, perms, encoding, up_context, start, end, contents)
    .shrink().map(|(name, parent, perms, encoding, up_context, start, end, contents)|
    fix_encoding(FileAddition { name, parent, perms, encoding, up_context, start, end, contents }))),
    FileDel { path, pos, encoding, del_edges, content_edges, contents } =>
    Box::new((path, pos, encoding, del_edges, content_edges, contents)
    .shrink().map(|(path, pos, encoding, del_edges, content_edges, contents)|
    fix_encoding(FileDel { path, pos, encoding, del_edges, content_edges, contents }))),
    FileUndel { path, pos, encoding, undel_edges, content_edges, contents } =>
    Box::new((path, pos, encoding, undel_edges, content_edges, contents)
    .shrink().map(|(path, pos, encoding, undel_edges, content_edges, contents)|
    fix_encoding(FileUndel { path, pos, encoding, undel_edges, content_edges, contents }))),
    Edit { path, line, pos, encoding, change, contents } =>
    Box::new((path, line, pos, encoding, change, contents)
    .shrink().map(|(path, line, pos, encoding, change, contents)|
    fix_encoding(Edit { path, line, pos, encoding, change, contents }))),
    Replace { path, line, pos, encoding, change, replacement, change_contents, replacement_contents } =>
    Box::new((path, line, pos, encoding, change, replacement, change_contents, replacement_contents)
    .shrink().map(|(path, line, pos, encoding, change, replacement, change_contents, replacement_contents)|
    fix_encoding(Replace { path, line, pos, encoding, change, replacement, change_contents, replacement_contents }))),
    SolveNameConflict { path, pos, names, edges } =>
    Box::new((path, pos, names, edges)
    .shrink().map(|(path, pos, names, edges)|
    fix_encoding(SolveNameConflict { path, pos, names, edges }))),
    UnsolveNameConflict { path, pos, names, edges } =>
    Box::new((path, pos, names, edges)
    .shrink().map(|(path, pos, names, edges)|
    fix_encoding(UnsolveNameConflict { path, pos, names, edges }))),
    SolveOrderConflict { path, line, pos, encoding, change, contents } =>
    Box::new((path, line, pos, encoding, change, contents)
    .shrink().map(|(path, line, pos, encoding, change, contents)|
    fix_encoding(SolveOrderConflict { path, line, pos, encoding, change, contents }))),
    UnsolveOrderConflict { path, line, pos, encoding, change, contents } =>
    Box::new((path, line, pos, encoding, change, contents)
    .shrink().map(|(path, line, pos, encoding, change, contents)|
    fix_encoding(UnsolveOrderConflict { path, line, pos, encoding, change, contents }))),
    ResurrectZombies { path, line, pos, encoding, change, contents } =>
    Box::new((path, line, pos, encoding, change, contents)
    .shrink().map(|(path, line, pos, encoding, change, contents)|
    fix_encoding(ResurrectZombies { path, line, pos, encoding, change, contents }))),
    }
    }
    */
    }
    #[cfg(test)]
    /// This is the one thing that is not normalized on PrintableHunk: encoding
    /// content must be valid, given the encoding. This function ensures that.
    fn fix_encoding(mut hunk: PrintableHunk) -> PrintableHunk {
    // let mut h = hunk.clone();
    match &mut hunk {
    FileAddition {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    FileDel {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    FileUndel {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    Edit {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    Replace { encoding, .. } => *encoding = None,
    SolveOrderConflict {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    UnsolveOrderConflict {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    ResurrectZombies {
    encoding, contents, ..
    } => *encoding = get_encoding(contents),
    _ => (),
    };
    hunk
    }
    #[cfg(test)]
    impl Arbitrary for PrintablePerms {
    fn arbitrary(g: &mut Gen) -> Self {
    *g.choose(&[
    PrintablePerms::IsDir,
    PrintablePerms::IsExecutable,
    PrintablePerms::IsFile,
    ])
    .unwrap()
    }
    }
    #[cfg(test)]
    impl Arbitrary for PrintablePos {
    fn arbitrary(g: &mut Gen) -> Self {
    PrintablePos(usize::arbitrary(g), u64::arbitrary(g))
    }
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    Box::new(
    self.0
    .shrink()
    .zip(self.1.shrink())
    .map(|(a, b)| PrintablePos(a, b)),
    )
    }
    }
    #[cfg(test)]
    impl Arbitrary for PrintableEdge {
    fn arbitrary(g: &mut Gen) -> Self {
    Self {
    previous: Arbitrary::arbitrary(g),
    flag: Arbitrary::arbitrary(g),
    from: Arbitrary::arbitrary(g),
    to_start: Arbitrary::arbitrary(g),
    to_end: Arbitrary::arbitrary(g),
    introduced_by: Arbitrary::arbitrary(g),
    }
    }
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    let Self {
    previous,
    flag,
    from,
    to_start,
    to_end,
    introduced_by,
    } = self.clone();
    Box::new(
    (previous, flag, from, to_start, to_end, introduced_by)
    .shrink()
    .map(
    |(previous, flag, from, to_start, to_end, introduced_by)| Self {
    previous,
    flag,
    from,
    to_start,
    to_end,
    introduced_by,
    },
    ),
    )
    }
    }
    #[cfg(test)]
    impl Arbitrary for PrintableNewVertex {
    fn arbitrary(g: &mut Gen) -> Self {
    Self {
    up_context: Arbitrary::arbitrary(g),
    start: Arbitrary::arbitrary(g),
    end: Arbitrary::arbitrary(g),
    down_context: Arbitrary::arbitrary(g),
    }
    }
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    let Self {
    up_context,
    start,
    end,
    down_context,
    } = self.clone();
    Box::new((up_context, start, end, down_context).shrink().map(
    |(up_context, start, end, down_context)| Self {
    up_context,
    start,
    end,
    down_context,
    },
    ))
    }
    }
    #[cfg(test)]
    impl Arbitrary for PrintableAtom {
    fn arbitrary(g: &mut Gen) -> Self {
    Gen::new(g.size())
    .choose(&[
    PrintableAtom::NewVertex(Arbitrary::arbitrary(g)),
    PrintableAtom::Edges(Arbitrary::arbitrary(g)),
    ])
    .unwrap()
    .clone()
    }
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    match self {
    PrintableAtom::NewVertex(x) => {
    Box::new(x.shrink().map(|x| PrintableAtom::NewVertex(x)))
    }
    PrintableAtom::Edges(x) => Box::new(x.shrink().map(|x| PrintableAtom::Edges(x))),
    }
    }
    }
    #[cfg(test)]
    impl Arbitrary for PrintableEdgeFlags {
    fn arbitrary(g: &mut Gen) -> Self {
    Self {
    block: Arbitrary::arbitrary(g),
    folder: Arbitrary::arbitrary(g),
    deleted: Arbitrary::arbitrary(g),
    }
    }
    fn shrink(&self) -> Box<dyn Iterator<Item = Self>> {
    Box::new((self.block, self.folder, self.deleted).shrink().map(
    |(block, folder, deleted)| Self {
    block,
    folder,
    deleted,
    },
    ))
    }
    }
  • file addition: parse.rs (----------)
    [49.931000]
    use nom::branch::alt;
    use nom::bytes::complete::*;
    use nom::character::complete::*;
    use nom::combinator::*;
    use nom::error::ParseError;
    use nom::multi::*;
    use nom::sequence::*;
    use nom::*;
    use crate::change::printable::*;
    use PrintableHunk::*;
    use super::*;
    fn parse_file_move_v_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
    let (i, name) = preceded(space0, parse_string)(i)?;
    let (i, perms) = preceded(space0, parse_perms)(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, del) = parse_edges(i)?;
    let (i, up_context) = preceded(pair(space0, tag("up")), parse_context)(i)?;
    let (i, down_context) = preceded(pair(space0, tag(", down")), parse_context)(i)?;
    let (i, _) = newline(i)?;
    Ok((
    i,
    FileMoveV {
    path,
    name,
    perms,
    pos,
    up_context,
    down_context,
    del,
    },
    ))
    }
    fn parse_file_move_e_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(delimited(space0, tag("Moved:"), space0), parse_string)(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, add) = parse_edges(i)?;
    let (i, del) = parse_edges(i)?;
    Ok((
    i,
    FileMoveE {
    path,
    pos,
    add,
    del,
    },
    ))
    }
    fn parse_file_del_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("File deletion:"), space0),
    parse_string,
    )(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, del_edges) = parse_edges(i)?;
    let (i, content_edges) = parse_edges(i)?;
    let (i, contents) = parse_contents('-', encoding.clone(), i)?;
    Ok((
    i,
    FileDel {
    path,
    pos,
    encoding,
    del_edges,
    content_edges,
    contents,
    },
    ))
    }
    fn parse_file_undel_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("File un-deletion:"), space0),
    parse_string,
    )(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, undel_edges) = parse_edges(i)?;
    let (i, content_edges) = parse_edges(i)?;
    let (i, contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    FileUndel {
    path,
    pos,
    encoding,
    undel_edges,
    content_edges,
    contents,
    },
    ))
    }
    fn parse_file_addition_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, name) = preceded(
    delimited(space0, tag("File addition:"), space0),
    parse_string,
    )(i)?;
    let (i, parent) = preceded(delimited(space1, tag("in"), space1), parse_string)(i)?;
    let (i, perms) = preceded(space0, parse_perms)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline, multispace0))(i)?;
    let (i, up_context) = preceded(tag("up"), parse_context)(i)?;
    let (i, (start, end)) = delimited(space0, parse_start_end, pair(space0, newline))(i)?;
    let (i, contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    FileAddition {
    name,
    parent,
    perms,
    encoding,
    up_context,
    start,
    end,
    contents,
    },
    ))
    }
    /// Parse a hunk header string
    fn parse_edit_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(delimited(space0, tag("Edit in"), space0), parse_string)(i)?;
    let (i, line) = preceded(char(':'), u64)(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, change) = parse_atom(i)?;
    let (i, contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    Edit {
    path,
    line: line as usize,
    pos,
    encoding,
    change,
    contents,
    },
    ))
    }
    fn parse_replace_hunk(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Replacement in"), space0),
    parse_string,
    )(i)?;
    let (i, line) = preceded(char(':'), u64)(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    // TODO: allow newlines in between these lines
    let (i, change) = parse_edges(i)?;
    let (i, replacement) = parse_new_vertex(i)?;
    let (i, change_contents) = parse_contents('-', encoding.clone(), i)?;
    let (i, replacement_contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    Replace {
    path,
    line: line as usize,
    pos,
    encoding,
    change,
    replacement,
    change_contents,
    replacement_contents,
    },
    ))
    }
    fn parse_solve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Solving a name conflict in"), space0),
    parse_string,
    )(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, _) = tuple((space0, char(':'), space0))(i)?;
    let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, edges) = parse_edges(i)?;
    Ok((
    i,
    SolveNameConflict {
    path,
    pos,
    names,
    edges,
    },
    ))
    }
    fn parse_unsolve_name_conflict(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Un-solving a name conflict in"), space0),
    parse_string,
    )(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, _) = tuple((space0, char(':'), space0))(i)?;
    let (i, names) = separated_list0(tuple((space0, char(','), space0)), parse_string)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, edges) = parse_edges(i)?;
    Ok((
    i,
    UnsolveNameConflict {
    path,
    pos,
    names,
    edges,
    },
    ))
    }
    fn parse_solve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Solving an order conflict in"), space0),
    parse_string,
    )(i)?;
    let (i, line) = preceded(char(':'), u64)(i)?;
    let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, change) = parse_new_vertex(i)?;
    let (i, contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    SolveOrderConflict {
    path,
    line: line as usize,
    pos,
    encoding,
    change,
    contents,
    },
    ))
    }
    fn parse_unsolve_order_conflict(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Un-solving an order conflict in"), space0),
    parse_string,
    )(i)?;
    let (i, line) = preceded(char(':'), u64)(i)?;
    let (i, pos) = preceded(space1, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, change) = parse_edges(i)?;
    let (i, contents) = parse_contents('-', encoding.clone(), i)?;
    Ok((
    i,
    UnsolveOrderConflict {
    path,
    line: line as usize,
    pos,
    encoding,
    change,
    contents,
    },
    ))
    }
    fn parse_resurrect_zombies(i: &str) -> IResult<&str, PrintableHunk> {
    let (i, path) = preceded(
    delimited(space0, tag("Resurrecting zombie lines in"), space0),
    parse_string,
    )(i)?;
    let (i, line) = preceded(char(':'), u64)(i)?;
    let (i, pos) = preceded(space0, parse_printable_pos)(i)?;
    let (i, encoding) = preceded(space0, parse_encoding)(i)?;
    let (i, _) = tuple((space0, newline))(i)?;
    let (i, change) = parse_edges(i)?;
    let (i, contents) = parse_contents('+', encoding.clone(), i)?;
    Ok((
    i,
    ResurrectZombies {
    path,
    line: line as usize,
    pos,
    encoding,
    change,
    contents,
    },
    ))
    }
    fn parse_content_line(leading_char: char, input: &str) -> IResult<&str, String> {
    preceded(
    char(leading_char),
    alt((
    map(
    delimited(tag(" "), take_till(|c| c == '\n'), newline),
    |s: &str| s.to_string() + "\n",
    ),
    map(
    delimited(tag("b"), take_till(|c| c == '\n'), newline),
    |s: &str| s.to_string(),
    ),
    )),
    )(input)
    }
    // TODO: better error handling
    fn parse_contents(
    leading_char: char,
    encoding: Option<Encoding>,
    i: &str,
    ) -> IResult<&str, Vec<u8>> {
    let (i, res) = fold_many0(
    complete(|i| parse_content_line(leading_char, i)),
    String::new,
    |s, r| s + r.as_str(),
    )(i)?;
    let (i, backslash) = opt(complete(tag("\\\n")))(i)?;
    if let Ok(mut vec) = encode(encoding, &res) {
    if backslash.is_some() && vec[vec.len() - 1] == b'\n' {
    vec.pop();
    }
    Ok((i, vec))
    } else {
    Err(nom::Err::Error(nom::error::Error::new(
    i,
    nom::error::ErrorKind::Verify,
    )))
    }
    }
    fn encode(encoding: Option<Encoding>, contents: &str) -> Result<Vec<u8>, String> {
    if let Some(encoding) = encoding {
    Ok(encoding.encode(contents).to_vec())
    } else {
    data_encoding::BASE64
    .decode(contents.as_bytes())
    .map_err(|e| e.to_string())
    }
    }
    fn parse_encoding(input: &str) -> IResult<&str, Option<Encoding>> {
    map(parse_string, |e| {
    if e != BINARY_LABEL {
    Some(Encoding::for_label(&e))
    } else {
    None
    }
    })(input)
    }
    pub fn parse_numbered_hunk(input: &str) -> IResult<&str, (u64, PrintableHunk)> {
    tuple((terminated(u64, char('.')), parse_hunk))(input)
    }
    pub fn parse_hunk(input: &str) -> IResult<&str, PrintableHunk> {
    alt((
    parse_file_move_v_hunk,
    parse_file_move_e_hunk,
    parse_file_del_hunk,
    parse_file_undel_hunk,
    parse_file_addition_hunk,
    parse_edit_hunk,
    parse_replace_hunk,
    parse_solve_name_conflict,
    parse_unsolve_name_conflict,
    parse_solve_order_conflict,
    parse_unsolve_order_conflict,
    parse_resurrect_zombies,
    ))(input)
    }
    pub fn parse_hunks(input: &str) -> IResult<&str, Vec<(u64, PrintableHunk)>> {
    preceded(
    tuple((tag("# Hunks"), space0, newline, multispace0)),
    many0(complete(terminated(parse_numbered_hunk, multispace0))),
    )(input)
    }
    pub const BINARY_LABEL: &str = "binary";
    pub fn encoding_label(encoding: &Option<Encoding>) -> &str {
    match encoding {
    Some(encoding) => encoding.label(),
    _ => BINARY_LABEL,
    }
    }
    /// Parse an escaped character: \n, \t, \r, etc.
    fn parse_escaped_char(input: &str) -> IResult<&str, char> {
    preceded(
    char('\\'),
    alt((
    value('\n', char('n')),
    value('\r', char('r')),
    value('\t', char('t')),
    value('\u{08}', char('b')),
    value('\u{0C}', char('f')),
    value('\\', char('\\')),
    value('"', char('"')),
    )),
    )(input)
    }
    /// Parse a non-empty block of text that doesn't include \ or "
    fn parse_literal<'a, E: ParseError<&'a str>>(input: &'a str) -> IResult<&'a str, &'a str, E> {
    let not_quote_slash = is_not("\"\\");
    verify(not_quote_slash, |s: &str| !s.is_empty())(input)
    }
    /// Combine parse_literal and parse_escaped_char into a StringFragment.
    fn parse_fragment(input: &str) -> IResult<&str, StringFragment> {
    alt((
    map(parse_literal, StringFragment::Literal),
    map(parse_escaped_char, StringFragment::EscapedChar),
    ))(input)
    }
    /// Parse a string. Use a loop of parse_fragment and push all of the fragments
    /// into an output string.
    pub fn parse_string(input: &str) -> IResult<&str, String> {
    let build_string = fold_many0(parse_fragment, String::new, |mut string, fragment| {
    match fragment {
    StringFragment::Literal(s) => string.push_str(s),
    StringFragment::EscapedChar(c) => string.push(c),
    }
    string
    });
    delimited(char('"'), build_string, char('"'))(input)
    }
    fn parse_perms(input: &str) -> IResult<&str, PrintablePerms> {
    alt((
    value(PrintablePerms::IsDir, tag("+dx")),
    value(PrintablePerms::IsExecutable, tag("+x")),
    value(PrintablePerms::IsFile, tag("")),
    ))(input)
    }
    fn parse_printable_pos(input: &str) -> IResult<&str, PrintablePos> {
    map(separated_pair(u64, char('.'), u64), |(a, b)| {
    PrintablePos(a as usize, b)
    })(input)
    }
    fn parse_context(input: &str) -> IResult<&str, Vec<PrintablePos>> {
    delimited(space0, separated_list0(space1, parse_printable_pos), space0)(input)
    }
    fn parse_start_end(input: &str) -> IResult<&str, (u64, u64)> {
    preceded(
    pair(tag(", new"), space1),
    separated_pair(u64, char(':'), u64),
    )(input)
    }
    fn parse_edge_flags(i: &str) -> IResult<&str, PrintableEdgeFlags> {
    let (i, block) = map(opt(char('B')), |x| x.is_some())(i)?;
    let (i, folder) = map(opt(char('F')), |x| x.is_some())(i)?;
    let (i, deleted) = map(opt(char('D')), |x| x.is_some())(i)?;
    Ok((
    i,
    PrintableEdgeFlags {
    block,
    folder,
    deleted,
    },
    ))
    }
    fn parse_new_vertex(i: &str) -> IResult<&str, PrintableNewVertex> {
    map(
    tuple((
    space0,
    preceded(tag("up"), parse_context),
    terminated(tag(", new"), space0),
    terminated(u64, char(':')),
    terminated(u64, space0),
    preceded(tag(", down"), parse_context),
    newline,
    )),
    |(_, up_context, _, start, end, down_context, _)| PrintableNewVertex {
    up_context,
    start,
    end,
    down_context,
    },
    )(i)
    }
    fn parse_edge(i: &str) -> IResult<&str, PrintableEdge> {
    map(
    tuple((
    terminated(parse_edge_flags, char(':')),
    terminated(parse_edge_flags, char(' ')),
    terminated(parse_printable_pos, tag(" -> ")),
    terminated(parse_printable_pos, tag(":")),
    terminated(u64, tag("/")),
    terminated(u64, space0),
    )),
    |(previous, flag, from, to_start, to_end, introduced_by)| PrintableEdge {
    previous,
    flag,
    from,
    to_start,
    to_end,
    introduced_by: introduced_by as usize,
    },
    )(i)
    }
    fn parse_atom(i: &str) -> IResult<&str, PrintableAtom> {
    alt((
    map(parse_new_vertex, PrintableAtom::NewVertex),
    map(parse_edges, PrintableAtom::Edges),
    ))(i)
    }
    fn parse_edges(input: &str) -> IResult<&str, Vec<PrintableEdge>> {
    terminated(
    separated_list0(delimited(space0, char(','), space0), complete(parse_edge)),
    pair(space0, newline),
    )(input)
    }
    pub fn parse_header(input: &str) -> IResult<&str, Result<ChangeHeader, toml::de::Error>> {
    map(
    alt((take_until("# Dependencies"), take_until("# Hunks"))),
    |s| toml::de::from_str(s),
    )(input)
    }
    pub fn parse_dependency(i: &str) -> IResult<&str, PrintableDep> {
    let (i, mut type_) = delimited(
    char('['),
    alt((
    map(u64, |n| DepType::Numbered(n as usize, false)),
    value(DepType::ExtraKnown, char('*')),
    value(DepType::ExtraUnknown, take_till(|c| c != ']')),
    )),
    char(']'),
    )(i)?;
    let (i, plus) = terminated(
    alt((value(true, char('+')), value(false, char(' ')))),
    space0,
    )(i)?;
    // TODO: get rid of this confusing mutation
    type_ = if let DepType::Numbered(n, _) = type_ {
    DepType::Numbered(n, plus)
    } else {
    type_
    };
    let (i, hash) = delimited(
    space0,
    take_while(|c: char| c.is_ascii_alphanumeric()),
    pair(space0, char('\n')),
    )(i)?;
    Ok((
    i,
    PrintableDep {
    type_,
    hash: hash.to_string(),
    },
    ))
    }
    pub fn parse_dependencies(input: &str) -> IResult<&str, Vec<PrintableDep>> {
    alt((
    preceded(
    tuple((tag("# Dependencies"), space0, char('\n'), multispace0)),
    many0(terminated(parse_dependency, multispace0)),
    ),
    value(Vec::new(), multispace0),
    ))(input)
    }
  • edit in libpijul/Cargo.toml at line 118
    [52.216]
    [7.109302]
    nom = "7"
  • edit in libpijul/Cargo.toml at line 154
    [4.3715]
    quickcheck = "1"
    quickcheck_macros = "1"
  • edit in Cargo.lock at line 1093
    [53.338]
    [4.3947]
    "nom",
  • edit in Cargo.lock at line 1098
    [54.23364]
    [4.3964]
    "quickcheck",
    "quickcheck_macros",
  • edit in Cargo.lock at line 1233
    [54.26734]
    [54.26734]
    name = "minimal-lexical"
    version = "0.1.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "0c835948974f68e0bd58636fc6c5b1fbff7b297e3046f11b3b3c18bbac012c6d"
    [[package]]
  • edit in Cargo.lock at line 1302
    [54.28286]
    [54.28286]
    name = "nom"
    version = "7.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "7ffd9d26838a953b4af82cbeb9f1592c6798916983959be223a7124e992742c1"
    dependencies = [
    "memchr",
    "minimal-lexical",
    "version_check",
    ]
    [[package]]
  • edit in Cargo.lock at line 1626
    [54.36174]
    [54.36174]
    ]
    [[package]]
    name = "quickcheck"
    version = "1.0.3"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6"
    dependencies = [
    "env_logger",
    "log",
    "rand 0.8.4",
  • edit in Cargo.lock at line 1640
    [54.36189]
    [54.36189]
    name = "quickcheck_macros"
    version = "1.0.0"
    source = "registry+https://github.com/rust-lang/crates.io-index"
    checksum = "b22a693222d716a9587786f37ac3f6b4faedb5b80c23914e7303ff5a1d8016e9"
    dependencies = [
    "proc-macro2",
    "quote",
    "syn",
    ]
    [[package]]