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
use std::fmt::{self, Display};

use semver::{Op, Version, VersionReq};

use crate::util_semver::VersionExt as _;

#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub enum OptVersionReq {
    Any,
    Req(VersionReq),
    /// The exact locked version and the original version requirement.
    Locked(Version, VersionReq),
    /// The exact requested version and the original version requirement.
    UpdatePrecise(Version, VersionReq),
}

impl OptVersionReq {
    pub fn exact(version: &Version) -> Self {
        OptVersionReq::Req(version.to_exact_req())
    }

    // Since some registries have allowed crate versions to differ only by build metadata,
    // A query using OptVersionReq::exact return nondeterministic results.
    // So we `lock_to` the exact version were interested in.
    pub fn lock_to_exact(version: &Version) -> Self {
        OptVersionReq::Locked(version.clone(), version.to_exact_req())
    }

    pub fn is_exact(&self) -> bool {
        match self {
            OptVersionReq::Any => false,
            OptVersionReq::Req(req) | OptVersionReq::UpdatePrecise(_, req) => {
                req.comparators.len() == 1 && {
                    let cmp = &req.comparators[0];
                    cmp.op == Op::Exact && cmp.minor.is_some() && cmp.patch.is_some()
                }
            }
            OptVersionReq::Locked(..) => true,
        }
    }

    pub fn lock_to(&mut self, version: &Version) {
        assert!(self.matches(version), "cannot lock {} to {}", self, version);
        use OptVersionReq::*;
        let version = version.clone();
        *self = match self {
            Any => Locked(version, VersionReq::STAR),
            Req(req) | Locked(_, req) | UpdatePrecise(_, req) => Locked(version, req.clone()),
        };
    }

    pub fn update_precise(&mut self, version: &Version) {
        use OptVersionReq::*;
        let version = version.clone();
        *self = match self {
            Any => UpdatePrecise(version, VersionReq::STAR),
            Req(req) | Locked(_, req) | UpdatePrecise(_, req) => {
                UpdatePrecise(version, req.clone())
            }
        };
    }

    pub fn is_locked(&self) -> bool {
        matches!(self, OptVersionReq::Locked(..))
    }

    /// Gets the version to which this req is locked, if any.
    pub fn locked_version(&self) -> Option<&Version> {
        match self {
            OptVersionReq::Locked(version, _) => Some(version),
            _ => None,
        }
    }

    pub fn matches(&self, version: &Version) -> bool {
        match self {
            OptVersionReq::Any => true,
            OptVersionReq::Req(req) => req.matches(version),
            OptVersionReq::Locked(v, _) => {
                // Generally, cargo is of the opinion that semver metadata should be ignored.
                // If your registry has two versions that only differing metadata you get the bugs you deserve.
                // We also believe that lock files should ensure reproducibility
                // and protect against mutations from the registry.
                // In this circumstance these two goals are in conflict, and we pick reproducibility.
                // If the lock file tells us that there is a version called `1.0.0+bar` then
                // we should not silently use `1.0.0+foo` even though they have the same version.
                v == version
            }
            OptVersionReq::UpdatePrecise(v, _) => {
                // This is used for the `--precise` field of cargo update.
                //
                // Unfortunately crates.io allowed versions to differ only
                // by build metadata. This shouldn't be allowed, but since
                // it is, this will honor it if requested.
                //
                // In that context we treat a requirement that does not have
                // build metadata as allowing any metadata. But, if a requirement
                // has build metadata, then we only allow it to match the exact
                // metadata.
                v.major == version.major
                    && v.minor == version.minor
                    && v.patch == version.patch
                    && v.pre == version.pre
                    && (v.build == version.build || v.build.is_empty())
            }
        }
    }
}

impl Display for OptVersionReq {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            OptVersionReq::Any => f.write_str("*"),
            OptVersionReq::Req(req)
            | OptVersionReq::Locked(_, req)
            | OptVersionReq::UpdatePrecise(_, req) => Display::fmt(req, f),
        }
    }
}

impl From<VersionReq> for OptVersionReq {
    fn from(req: VersionReq) -> Self {
        OptVersionReq::Req(req)
    }
}