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
use std::{
    borrow::{Borrow, Cow},
    collections::HashSet,
    iter::FromIterator,
};

/// Convenient [`From`] implementations for `Cow<'_, str>` based newtypes.
macro_rules! impl_str_from {
    ($typename:ident) => {
        impl<'a> From<&'a str> for $typename<'a> {
            fn from(s: &'a str) -> Self {
                Self(Cow::from(s))
            }
        }

        impl<'a> From<String> for $typename<'a> {
            fn from(s: String) -> Self {
                Self(Cow::from(s))
            }
        }
    };
}

macro_rules! impl_str_possibly_clone {
    ($typename:ident) => {
        /// Takes ownership of non-`'static` borrowed data, possibly allocating a
        /// `String` to do so.
        ///
        /// Convenient representation for types that want to stake ownership of the newtype without
        /// exposing a generic lifetime of their own.
        impl $typename<'_> {
            pub fn possibly_clone(self) -> $typename<'static> {
                let owned = self.0.into_owned();
                $typename(Cow::Owned(owned))
            }
        }
    };
}

macro_rules! impl_str_always_borrow {
    ($typename:ident) => {
        /// Returns a copy of itself while guaranteeing zero allocations.
        ///
        /// Useful for standard containers that use the `Borrow + Hash + Eq` sleight of hand to
        /// permit zero allocation lookups while still owning the underlying data.
        impl<'a> $typename<'a> {
            pub fn always_borrow(&'a self) -> Self {
                let y: &str = self.0.borrow();
                Self::from(y)
            }
        }
    };
}

/// A remote git repository identified by name, like `origin`.
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Remote<'a>(pub Cow<'a, str>);
impl_str_from!(Remote);

#[cfg(test)]
impl_str_always_borrow!(Remote);

/// The branch name part of a ref. `refs/head/master` would be `Branch::from("master")`.
#[derive(Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Branch<'a>(pub Cow<'a, str>);
impl_str_from!(Branch);
impl_str_possibly_clone!(Branch);
impl_str_always_borrow!(Branch);

/// Represents "who" a given branch belongs to. This value should be shared by multiple git
/// clones that belong to the same user.
///
/// This string is used when pushing branches to the remote so that multiple users can use
/// nomad on that remote without overwriting each others refs.
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct User<'a>(pub Cow<'a, str>);
impl_str_from!(User);
impl_str_possibly_clone!(User);
impl_str_always_borrow!(User);

/// Represents "where" a given branch comes from. This value should be unique for every git
/// clone belonging to a specific user.
///
/// This string is used when pushing branches to the remote so that multiple hosts belonging to
/// the same user can co-exist (i.e. the whole point of nomad).
///
/// This string is also used when pulling branches for all hosts of the current user
/// and for detecting when branches have been deleted.
#[derive(Clone, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
pub struct Host<'a>(pub Cow<'a, str>);
impl_str_from!(Host);
impl_str_possibly_clone!(Host);
impl_str_always_borrow!(Host);

/// A ref representing a branch managed by nomad.
#[derive(Debug, PartialEq, Eq, Hash)]
pub struct NomadRef<'a, Ref> {
    /// The user this branch belongs to.
    pub user: User<'a>,
    /// The host this branch comes from.
    pub host: Host<'a>,
    /// The branch name.
    pub branch: Branch<'a>,
    /// Any additional internal data representing the underlying git ref.
    pub ref_: Ref,
}

/// A specialized container to represent nomad managed refs that a remote knows about.
pub struct RemoteNomadRefSet {
    set: HashSet<(User<'static>, Host<'static>, Branch<'static>)>,
}

impl RemoteNomadRefSet {
    /// Check whether the remote knows about a given [`NomadRef`].
    ///
    /// Note that the `Ref` part of `NomadRef<Ref>` is completely ignored, since we don't care
    /// about the intrinsic git ref being pointed to, merely that the remote is still tracking a
    /// nomad ref with the given user/host/branch.
    pub fn contains<Ref>(&self, nomad_ref: &NomadRef<Ref>) -> bool {
        // Performs a lookup without allocating.
        //
        // https://users.rust-lang.org/t/using-hashset-contains-with-tuple-types-without-takeing-ownership-of-the-values/65455
        // https://stackoverflow.com/questions/45786717/how-to-implement-hashmap-with-two-keys/45795699#45795699
        self.set.contains(&(
            nomad_ref.user.always_borrow(),
            nomad_ref.host.always_borrow(),
            nomad_ref.branch.always_borrow(),
        ))
    }
}

impl<'a> FromIterator<(User<'a>, Host<'a>, Branch<'a>)> for RemoteNomadRefSet {
    fn from_iter<T: IntoIterator<Item = (User<'a>, Host<'a>, Branch<'a>)>>(iter: T) -> Self {
        let set = HashSet::from_iter(iter.into_iter().map(|(user, host, branch)| {
            (
                user.possibly_clone(),
                host.possibly_clone(),
                branch.possibly_clone(),
            )
        }));
        RemoteNomadRefSet { set }
    }
}

impl<'a, Ref> FromIterator<NomadRef<'a, Ref>> for RemoteNomadRefSet {
    fn from_iter<T: IntoIterator<Item = NomadRef<'a, Ref>>>(iter: T) -> Self {
        Self::from_iter(iter.into_iter().map(|nomad_ref| {
            let NomadRef {
                user, host, branch, ..
            } = nomad_ref;
            (user, host, branch)
        }))
    }
}