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
use std::borrow::Cow;

use crate::{
    bstr::{BStr, ByteSlice, ByteVec},
    remote, Reference,
};

/// The name of a remote, either interpreted as symbol like `origin` or as url as returned by [`Reference::remote_name()`].
#[derive(Debug, PartialEq, Eq, Clone)]
pub enum Name<'repo> {
    /// A symbolic name, like `origin`
    Symbol(Cow<'repo, str>),
    /// A url pointing to the remote host directly.
    Url(Cow<'repo, BStr>),
}

impl Name<'_> {
    /// Return this instance as a symbolic name, if it is one.
    pub fn as_symbol(&self) -> Option<&str> {
        match self {
            Name::Symbol(n) => n.as_ref().into(),
            Name::Url(_) => None,
        }
    }

    /// Return this instance as url, if it is one.
    pub fn as_url(&self) -> Option<&BStr> {
        match self {
            Name::Url(n) => n.as_ref().into(),
            Name::Symbol(_) => None,
        }
    }
}

/// Remotes
impl<'repo> Reference<'repo> {
    /// Find the name of our remote for `direction` as configured in `branch.<name>.remote|pushRemote` respectively.
    /// If `Some(<name>)` it can be used in [`Repository::find_remote(…)`][crate::Repository::find_remote()], or if `None` then
    /// [Repository::remote_default_name()][crate::Repository::remote_default_name()] could be used in its place.
    ///
    /// Return `None` if no remote is configured.
    ///
    /// # Note
    ///
    /// - it's recommended to use the [`remote(…)`][Self::remote()] method as it will configure the remote with additional
    ///   information.
    /// - `branch.<name>.pushRemote` falls back to `branch.<name>.remote`.
    pub fn remote_name(&self, direction: remote::Direction) -> Option<Name<'repo>> {
        let name = self.name().shorten().to_str().ok()?;
        let config = &self.repo.config.resolved;
        (direction == remote::Direction::Push)
            .then(|| {
                config
                    .string("branch", Some(name), "pushRemote")
                    .or_else(|| config.string("remote", None, "pushDefault"))
            })
            .flatten()
            .or_else(|| config.string("branch", Some(name), "remote"))
            .and_then(|name| {
                if name.contains(&b'/') {
                    Some(Name::Url(name))
                } else {
                    match name {
                        Cow::Borrowed(n) => n.to_str().ok().map(Cow::Borrowed),
                        Cow::Owned(n) => Vec::from(n).into_string().ok().map(Cow::Owned),
                    }
                    .map(Name::Symbol)
                }
            })
    }

    /// Like [`remote_name(…)`][Self::remote_name()], but configures the returned `Remote` with additional information like
    ///
    /// - `branch.<name>.merge` to know which branch on the remote side corresponds to this one for merging when pulling.
    ///
    /// It also handles if the remote is a configured URL, which has no name.
    pub fn remote(
        &self,
        direction: remote::Direction,
    ) -> Option<Result<crate::Remote<'repo>, remote::find::existing::Error>> {
        // TODO: use `branch.<name>.merge`
        self.remote_name(direction).map(|name| match name {
            Name::Symbol(name) => self.repo.find_remote(name.as_ref()).map_err(Into::into),
            Name::Url(url) => git_url::parse(url.as_ref()).map_err(Into::into).and_then(|url| {
                self.repo
                    .remote_at(url)
                    .map_err(|err| remote::find::existing::Error::Find(remote::find::Error::Init(err)))
            }),
        })
    }
}