gitopen/
lib.rs

1use std::{path::PathBuf, process};
2
3use anyhow::Result;
4use git2::Repository;
5
6mod providers;
7use providers::github::GitHub;
8use providers::Provider;
9
10pub struct GitOpen<'a> {
11    /// The Git repository with all its data.
12    repository: Repository,
13    /// The remote we are interested in. Usually `origin`.
14    remote_name: &'a str,
15
16    provider: Box<dyn Provider>,
17}
18
19pub enum Entity {
20    Repository,
21    Branch,
22    Commit,
23    PullRequest,
24}
25
26impl<'a> GitOpen<'a> {
27    pub fn new(path: &PathBuf, remote_name: &'a str) -> Self {
28        let mut cwd = path.clone();
29
30        let repository = loop {
31            match Repository::open(&cwd) {
32                Ok(r) => break r,
33                Err(_) => {
34                    if !cwd.pop() {
35                        panic!("Unable to open repository at path or parent: {:?}", path);
36                    }
37                }
38            }
39        };
40
41        // TODO(jsgv): Support additional providers.
42        let provider = GitHub {};
43
44        Self {
45            repository,
46            remote_name,
47            provider: Box::new(provider),
48        }
49    }
50
51    pub fn url(&self, entity: Entity) -> Result<String> {
52        let remote = self
53            .repository
54            .find_remote(self.remote_name)
55            .unwrap_or_else(|e| {
56                println!("Could not retrieve remote: {}", e);
57                process::exit(1);
58            });
59
60        let remote_url = remote.url().unwrap_or_else(|| {
61            println!("Could not retrieve remote url.");
62            process::exit(1);
63        });
64
65        let head = self.repository.head().unwrap_or_else(|e| {
66            println!("Could not retrieve repository head: {}", e);
67            process::exit(1);
68        });
69
70        let branch = head.shorthand().unwrap_or_else(|| {
71            println!("Could not retrieve branch name.");
72            process::exit(1);
73        });
74
75        let commit = head.target().unwrap_or_else(|| {
76            println!("Could not retrieve commit.");
77            process::exit(1);
78        });
79
80        match entity {
81            Entity::Repository => self.provider.web_url(remote_url),
82            Entity::Branch => self.provider.branch_url(remote_url, branch),
83            Entity::PullRequest => self.provider.pull_request_url(remote_url, branch),
84            Entity::Commit => self.provider.commit_url(remote_url, &commit.to_string()),
85        }
86    }
87}
88
89#[cfg(test)]
90mod tests {
91    use super::GitOpen;
92    use std::{env, path::PathBuf};
93
94    #[test]
95    fn can_be_created() {
96        let p = env::current_dir().unwrap();
97        GitOpen::new(&p, "origin");
98    }
99
100    #[test]
101    fn can_be_created_in_child_path() {
102        let mut p = env::current_dir().unwrap();
103        p.push("src");
104        GitOpen::new(&p, "origin");
105    }
106
107    #[test]
108    #[should_panic]
109    fn panics_correctly() {
110        let p = PathBuf::from("/tmp");
111        GitOpen::new(&p, "origin");
112    }
113}