1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
// Conserve backup system.
// Copyright 2015, 2016, 2017, 2018, 2019, 2020, 2021 Martin Pool.

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

//! Diff two trees: for example a live tree against a stored tree.
//!
//! See also [conserve::show_diff] to format the diff as text.

use std::fmt;

use readahead_iterator::IntoReadahead;

use crate::*;

use DiffKind::*;
use Kind::*;
use MergedEntryKind::*;

#[derive(Default, Debug)]
pub struct DiffOptions {
    pub excludes: Option<GlobSet>,
    pub include_unchanged: bool,
}

/// The overall state of change of an entry.
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
pub enum DiffKind {
    Unchanged,
    New,
    Deleted,
    Changed,
}

impl DiffKind {
    pub fn as_sigil(self) -> char {
        match self {
            Unchanged => '.',
            New => '+',
            Deleted => '-',
            Changed => '*',
        }
    }
}

#[derive(Debug, Eq, PartialEq)]
pub struct DiffEntry {
    pub apath: Apath,
    pub kind: DiffKind,
}

impl fmt::Display for DiffEntry {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}\t{}", self.kind.as_sigil(), self.apath)
    }
}

/// Generate an iter of per-entry diffs between two trees.
pub fn diff(
    st: &StoredTree,
    lt: &LiveTree,
    options: &DiffOptions,
) -> Result<impl Iterator<Item = DiffEntry>> {
    let readahead = 1000;
    let include_unchanged: bool = options.include_unchanged;
    let ait = st
        .iter_entries(None, options.excludes.clone())?
        .readahead(readahead);
    let bit = lt
        .iter_entries(None, options.excludes.clone())?
        .filter(|le| le.kind() != Unknown)
        .readahead(readahead);
    Ok(MergeTrees::new(ait, bit)
        .map(diff_merged_entry)
        .filter(move |de: &DiffEntry| include_unchanged || de.kind != DiffKind::Unchanged))
}

fn diff_merged_entry<AE, BE>(me: merge::MergedEntry<AE, BE>) -> DiffEntry
where
    AE: Entry,
    BE: Entry,
{
    let apath = me.apath;
    match me.kind {
        Both(ae, be) => diff_common_entry(ae, be, apath),
        LeftOnly(_) => DiffEntry {
            kind: Deleted,
            apath,
        },
        RightOnly(_) => DiffEntry { kind: New, apath },
    }
}

fn diff_common_entry<AE, BE>(ae: AE, be: BE, apath: Apath) -> DiffEntry
where
    AE: Entry,
    BE: Entry,
{
    // TODO: Actually compare content, if requested.
    // TODO: Skip Kind::Unknown.
    let ak = ae.kind();
    if ak != be.kind()
        || (ak == File && (ae.mtime() != be.mtime() || ae.size() != be.size()))
        || (ak == Symlink && (ae.symlink_target() != be.symlink_target()))
    {
        DiffEntry {
            kind: Changed,
            apath,
        }
    } else {
        DiffEntry {
            kind: Unchanged,
            apath,
        }
    }
}