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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
use std::collections::HashMap;

use bstr::BString;
use smartstring::alias::String as SmolString;
use std::hash::Hash;
use std::{fmt, slice};

/// A wrapper for a repository of the crates.io index.
pub struct Index {
    /// The name and path of the reference used to keep track of the last seen state of the
    /// crates.io repository. The default value is `refs/heads/crates-index-diff_last-seen`.
    pub seen_ref_name: &'static str,
    /// The name of the branch to fetch. This value also affects the tracking branch.
    pub branch_name: &'static str,
    /// The name of the symbolic name of the remote to fetch from.
    /// If `None`, obtain the remote name from the configuration of the currently checked-out branch.
    pub remote_name: Option<BString>,
    /// The git repository to use for diffing
    pub(crate) repo: gix::Repository,
}

/// Identify a kind of change that occurred to a crate
#[derive(Clone, Eq, PartialEq, Debug)]
pub enum Change {
    /// A crate version was added.
    Added(CrateVersion),
    /// A crate version was unyanked.
    Unyanked(CrateVersion),
    /// A crate version was added in a yanked state.
    ///
    /// This can happen if we don't see the commit that added them, so it appears to pop into existence yanked.
    /// Knowing this should help to trigger the correct action, as simply `Yanked` crates would be treated quite differently.
    AddedAndYanked(CrateVersion),
    /// A crate version was yanked.
    Yanked(CrateVersion),
    /// The name of the crate whose file was deleted, which implies all versions were deleted as well.
    CrateDeleted {
        /// The name of the deleted crate.
        name: String,
        /// All of its versions that were deleted along with the file.
        versions: Vec<CrateVersion>,
    },
    /// A crate version was deleted.
    ///
    /// Note that this is equivalent to deleting a line from a crates version file.
    /// Should more than one lines be removed per commit, the order of these changes is nondeterministic.
    VersionDeleted(CrateVersion),
}

impl Change {
    /// Return the added crate, if this is this kind of change.
    pub fn added(&self) -> Option<&CrateVersion> {
        match self {
            Change::Added(v) | Change::AddedAndYanked(v) => Some(v),
            _ => None,
        }
    }

    /// Return the yanked crate, if this is this kind of change.
    pub fn yanked(&self) -> Option<&CrateVersion> {
        match self {
            Change::Yanked(v) | Change::AddedAndYanked(v) => Some(v),
            _ => None,
        }
    }

    /// Return the unyanked crate, if this is this kind of change.
    pub fn unyanked(&self) -> Option<&CrateVersion> {
        match self {
            Change::Unyanked(v) => Some(v),
            _ => None,
        }
    }

    /// Return the deleted crate, if this is this kind of change.
    pub fn crate_deleted(&self) -> Option<(&str, &[CrateVersion])> {
        match self {
            Change::CrateDeleted { name, versions } => Some((name.as_str(), versions)),
            _ => None,
        }
    }

    /// Return the deleted version crate, if this is this kind of change.
    pub fn version_deleted(&self) -> Option<&CrateVersion> {
        match self {
            Change::VersionDeleted(v) => Some(v),
            _ => None,
        }
    }

    /// Returns all versions affected by this change.
    ///
    /// The returned slice usually has length 1.
    /// However, if a crate was purged from the index by an admin,
    /// all versions of the purged crate are returned.
    pub fn versions(&self) -> &[CrateVersion] {
        match self {
            Change::Added(v)
            | Change::Unyanked(v)
            | Change::AddedAndYanked(v)
            | Change::Yanked(v)
            | Change::VersionDeleted(v) => slice::from_ref(v),
            Change::CrateDeleted { versions, .. } => versions,
        }
    }
}

impl fmt::Display for Change {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(
            f,
            "{}",
            match *self {
                Change::Added(_) => "added",
                Change::Yanked(_) => "yanked",
                Change::CrateDeleted { .. } => "crate deleted",
                Change::VersionDeleted(_) => "version deleted",
                Change::Unyanked(_) => "unyanked",
                Change::AddedAndYanked(_) => "added and yanked",
            }
        )
    }
}

/// Section in which a dependency was defined in.
#[derive(
    Debug, Copy, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Hash, Ord, PartialOrd,
)]
#[serde(rename_all = "lowercase")]
pub enum DependencyKind {
    /// Used for production builds.
    Normal,
    /// Used only for tests and examples.
    Dev,
    /// Used in build scripts.
    Build,
}

/// Pack all information we know about a change made to a version of a crate.
#[derive(Default, Clone, serde::Serialize, serde::Deserialize, Eq, PartialEq, Debug)]
pub struct CrateVersion {
    /// The crate name, i.e. `clap`.
    pub name: SmolString,
    /// is the release yanked?
    pub yanked: bool,
    /// The semantic version of the crate.
    #[serde(rename = "vers")]
    pub version: SmolString,
    /// The checksum over the crate archive
    #[serde(rename = "cksum", with = "hex")]
    pub checksum: [u8; 32],
    /// All cargo features
    pub features: HashMap<String, Vec<String>>,
    /// All crate dependencies
    #[serde(rename = "deps")]
    pub dependencies: Vec<Dependency>,
}

/// A single dependency of a specific crate version
#[derive(
    Clone, serde::Serialize, serde::Deserialize, Ord, PartialOrd, Eq, PartialEq, Debug, Hash,
)]
pub struct Dependency {
    /// The crate name
    pub name: SmolString,
    /// The version the parent crate requires of this dependency
    #[serde(rename = "req")]
    pub required_version: SmolString,
    /// All cargo features configured by the parent crate
    pub features: Vec<String>,
    /// True if this is an optional dependency
    pub optional: bool,
    /// True if default features are enabled
    pub default_features: bool,
    /// The name of the build target
    #[serde(skip_serializing_if = "Option::is_none")]
    pub target: Option<SmolString>,
    /// The kind of dependency, usually 'normal' or 'dev'
    #[serde(skip_serializing_if = "Option::is_none")]
    pub kind: Option<DependencyKind>,
    /// The package this crate is contained in
    #[serde(skip_serializing_if = "Option::is_none")]
    pub package: Option<SmolString>,
}