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}