hubcaps_ex/
git.rs

1//! Git interface
2
3// Third party
4use serde::Deserialize;
5
6// Ours
7use crate::{Future, Github};
8
9/// reference to git operations associated with a github repo
10pub struct Git {
11    github: Github,
12    owner: String,
13    repo: String,
14}
15
16impl Git {
17    #[doc(hidden)]
18    pub fn new<O, R>(github: Github, owner: O, repo: R) -> Self
19    where
20        O: Into<String>,
21        R: Into<String>,
22    {
23        Git {
24            github,
25            owner: owner.into(),
26            repo: repo.into(),
27        }
28    }
29
30    fn path(&self, more: &str) -> String {
31        format!("/repos/{}/{}/git{}", self.owner, self.repo, more)
32    }
33
34    /// list a git tree of files for this repo at a given sha
35    /// https://developer.github.com/v3/git/trees/#get-a-tree
36    /// https://developer.github.com/v3/git/trees/#get-a-tree-recursively
37    pub fn tree<S>(&self, sha: S, recursive: bool) -> Future<TreeData>
38    where
39        S: Into<String>,
40    {
41        self.github.get(&self.path(&format!(
42            "/trees/{}?recursive={}",
43            sha.into(),
44            if recursive { "1" } else { "0" }
45        )))
46    }
47
48    /// get the blob contents of a given sha
49    /// https://developer.github.com/v3/git/blobs/#get-a-blob
50    pub fn blob<S>(&self, sha: S) -> Future<Blob>
51    where
52        S: Into<String>,
53    {
54        self.github
55            .get(&self.path(&format!("/blobs/{}", sha.into())))
56    }
57
58    /// get the git reference data of a given ref
59    /// the specified reference must be formatted as as "heads/branch", not just "branch"
60    /// https://developer.github.com/v3/git/refs/#get-a-reference
61    pub fn reference<S>(&self, reference: S) -> Future<GetReferenceResponse>
62    where
63        S: Into<String>,
64    {
65        self.github
66            .get(&self.path(&format!("/refs/{}", reference.into())))
67    }
68
69    //// deletes a refish
70    /// branches should be in the format `heads/feature-a`
71    /// tags should be in the format `tags/v1.0`
72    /// https://developer.github.com/v3/git/refs/#delete-a-reference
73    pub fn delete_reference<S>(&self, reference: S) -> Future<()>
74    where
75        S: Into<String>,
76    {
77        self.github
78            .delete(&self.path(&format!("/refs/{}", reference.into())))
79    }
80}
81
82// representations
83
84#[derive(Debug, Deserialize)]
85pub struct TreeData {
86    pub sha: String,
87    pub url: String,
88    pub tree: Vec<GitFile>,
89    pub truncated: bool,
90}
91
92#[derive(Debug, Deserialize)]
93pub struct GitFile {
94    pub path: String,
95    pub mode: String,
96    /// typically tree or blob
97    #[serde(rename = "type")]
98    pub content_type: String,
99    /// size will be None for directories
100    pub size: Option<usize>,
101    pub sha: String,
102    /// url will be None for commits
103    pub url: Option<String>,
104}
105
106#[derive(Debug, Deserialize)]
107pub struct Blob {
108    pub content: String,
109    pub encoding: String,
110    pub url: String,
111    pub sha: String,
112    /// sizes will be None for directories
113    pub size: Option<usize>,
114}
115
116#[derive(Debug, Deserialize, PartialEq)]
117#[serde(untagged)]
118/// The response for getting a git reference
119pub enum GetReferenceResponse {
120    /// The reference data matching the specified reference
121    Exact(Reference),
122    /// If the reference doesn't exist in the repository
123    /// but existing refs start with ref they will be returned as an array.
124    /// For example, a call to get the data for a branch named feature,
125    /// which doesn't exist, would return head refs including featureA and featureB which do.
126    StartWith(Vec<Reference>),
127}
128
129#[derive(Debug, Deserialize, PartialEq)]
130pub struct Reference {
131    #[serde(rename = "ref")]
132    pub reference: String,
133    pub url: String,
134    pub object: Object,
135}
136
137#[derive(Debug, Deserialize, PartialEq)]
138pub struct Object {
139    #[serde(rename = "type")]
140    pub object_type: String,
141    pub sha: String,
142    pub url: String,
143}
144
145#[cfg(test)]
146mod tests {
147    use super::*;
148    use serde::Deserialize;
149    use std::fmt::Debug;
150
151    fn test_deserializing<'de, T>(payload: &'static str, expected: T)
152    where
153        T: Debug + PartialEq + Deserialize<'de>,
154    {
155        let incoming: T = serde_json::from_str(payload).unwrap();
156        assert_eq!(incoming, expected)
157    }
158
159    #[test]
160    fn deserialize_get_ref_exact() {
161        let payload = r#"{
162  "ref": "refs/heads/featureA",
163  "url": "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/featureA",
164  "object": {
165    "type": "commit",
166    "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
167    "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
168  }
169}"#;
170        let expected = GetReferenceResponse::Exact(Reference {
171            reference: "refs/heads/featureA".to_string(),
172            url: "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/featureA".to_string(),
173            object: Object {
174                object_type: "commit".to_string(),
175                sha: "aa218f56b14c9653891f9e74264a383fa43fefbd".to_string(),
176                url: "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd".to_string(),
177            },
178        });
179        test_deserializing(payload, expected)
180    }
181
182    #[test]
183    fn deserialize_get_ref_starts_with() {
184        let payload = r#"[
185  {
186    "ref": "refs/heads/feature-a",
187    "url": "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/feature-a",
188    "object": {
189      "type": "commit",
190      "sha": "aa218f56b14c9653891f9e74264a383fa43fefbd",
191      "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd"
192    }
193  },
194  {
195    "ref": "refs/heads/feature-b",
196    "url": "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/feature-b",
197    "object": {
198      "type": "commit",
199      "sha": "612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac",
200      "url": "https://api.github.com/repos/octocat/Hello-World/git/commits/612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac"
201    }
202  }
203]"#;
204        let expected = GetReferenceResponse::StartWith(vec![
205            Reference {
206                reference: "refs/heads/feature-a".to_string(),
207                url: "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/feature-a".to_string(),
208                object: Object {
209                    object_type: "commit".to_string(),
210                    sha: "aa218f56b14c9653891f9e74264a383fa43fefbd".to_string(),
211                    url: "https://api.github.com/repos/octocat/Hello-World/git/commits/aa218f56b14c9653891f9e74264a383fa43fefbd".to_string(),
212                },
213            },
214            Reference {
215                reference: "refs/heads/feature-b".to_string(),
216                url: "https://api.github.com/repos/octocat/Hello-World/git/refs/heads/feature-b".to_string(),
217                object: Object {
218                    object_type: "commit".to_string(),
219                    sha: "612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac".to_string(),
220                    url: "https://api.github.com/repos/octocat/Hello-World/git/commits/612077ae6dffb4d2fbd8ce0cccaa58893b07b5ac".to_string(),
221                },
222            },
223        ]);
224        test_deserializing(payload, expected)
225    }
226}