radicle_source/
revision.rs

1// This file is part of radicle-surf
2// <https://github.com/radicle-dev/radicle-surf>
3//
4// Copyright (C) 2019-2020 The Radicle Team <dev@radicle.xyz>
5//
6// This program is free software: you can redistribute it and/or modify
7// it under the terms of the GNU General Public License version 3 or
8// later as published by the Free Software Foundation.
9//
10// This program is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with this program. If not, see <https://www.gnu.org/licenses/>.
17
18use std::convert::TryFrom;
19
20use nonempty::NonEmpty;
21use serde::{Deserialize, Serialize};
22
23use radicle_surf::vcs::git::{self, Browser, RefScope, Rev};
24
25use crate::{
26    branch::{branches, Branch},
27    error::Error,
28    oid::Oid,
29    tag::{tags, Tag},
30};
31
32pub enum Category<P, U> {
33    Local { peer_id: P, user: U },
34    Remote { peer_id: P, user: U },
35}
36
37/// A revision selector for a `Browser`.
38#[derive(Debug, Clone, Serialize, Deserialize)]
39#[serde(rename_all = "camelCase", tag = "type")]
40pub enum Revision<P> {
41    /// Select a tag under the name provided.
42    #[serde(rename_all = "camelCase")]
43    Tag {
44        /// Name of the tag.
45        name: String,
46    },
47    /// Select a branch under the name provided.
48    #[serde(rename_all = "camelCase")]
49    Branch {
50        /// Name of the branch.
51        name: String,
52        /// The remote peer, if specified.
53        peer_id: Option<P>,
54    },
55    /// Select a SHA1 under the name provided.
56    #[serde(rename_all = "camelCase")]
57    Sha {
58        /// The SHA1 value.
59        sha: Oid,
60    },
61}
62
63impl<P> TryFrom<Revision<P>> for Rev
64where
65    P: ToString,
66{
67    type Error = Error;
68
69    fn try_from(other: Revision<P>) -> Result<Self, Self::Error> {
70        match other {
71            Revision::Tag { name } => Ok(git::TagName::new(&name).into()),
72            Revision::Branch { name, peer_id } => Ok(match peer_id {
73                Some(peer) => {
74                    git::Branch::remote(&format!("heads/{}", name), &peer.to_string()).into()
75                },
76                None => git::Branch::local(&name).into(),
77            }),
78            Revision::Sha { sha } => {
79                let oid: git2::Oid = sha.into();
80                Ok(oid.into())
81            },
82        }
83    }
84}
85
86/// Bundled response to retrieve both [`Branch`]es and [`Tag`]s for a user's
87/// repo.
88#[derive(Clone, Debug, PartialEq, Eq)]
89pub struct Revisions<P, U> {
90    /// The peer peer_id for the user.
91    pub peer_id: P,
92    /// The user who owns these revisions.
93    pub user: U,
94    /// List of [`git::Branch`].
95    pub branches: NonEmpty<Branch>,
96    /// List of [`git::Tag`].
97    pub tags: Vec<Tag>,
98}
99
100/// Provide the [`Revisions`] for the given `peer_id`, looking for the
101/// branches as [`RefScope::Remote`].
102///
103/// If there are no branches then this returns `None`.
104///
105/// # Errors
106///
107///   * If we cannot get the branches from the `Browser`
108pub fn remote<P, U>(
109    browser: &Browser,
110    peer_id: P,
111    user: U,
112) -> Result<Option<Revisions<P, U>>, Error>
113where
114    P: Clone + ToString,
115{
116    let remote_branches = branches(browser, Some(peer_id.clone()).into())?;
117    Ok(
118        NonEmpty::from_vec(remote_branches).map(|branches| Revisions {
119            peer_id,
120            user,
121            branches,
122            // TODO(rudolfs): implement remote peer tags once we decide how
123            // https://radicle.community/t/git-tags/214
124            tags: vec![],
125        }),
126    )
127}
128
129/// Provide the [`Revisions`] for the given `peer_id`, looking for the
130/// branches as [`RefScope::Local`].
131///
132/// If there are no branches then this returns `None`.
133///
134/// # Errors
135///
136///   * If we cannot get the branches from the `Browser`
137pub fn local<P, U>(browser: &Browser, peer_id: P, user: U) -> Result<Option<Revisions<P, U>>, Error>
138where
139    P: Clone + ToString,
140{
141    let local_branches = branches(browser, RefScope::Local)?;
142    let tags = tags(browser)?;
143    Ok(
144        NonEmpty::from_vec(local_branches).map(|branches| Revisions {
145            peer_id,
146            user,
147            branches,
148            tags,
149        }),
150    )
151}
152
153/// Provide the [`Revisions`] of a peer.
154///
155/// If the peer is [`Category::Local`], meaning that is the current person doing
156/// the browsing and no remote is set for the reference.
157///
158/// Othewise, the peer is [`Category::Remote`], meaning that we are looking into
159/// a remote part of a reference.
160///
161/// # Errors
162///
163///   * If we cannot get the branches from the `Browser`
164pub fn revisions<P, U>(
165    browser: &Browser,
166    peer: Category<P, U>,
167) -> Result<Option<Revisions<P, U>>, Error>
168where
169    P: Clone + ToString,
170{
171    match peer {
172        Category::Local { peer_id, user } => local(browser, peer_id, user),
173        Category::Remote { peer_id, user } => remote(browser, peer_id, user),
174    }
175}