1use crate::*;
2
3use std::fmt::{self, Debug, Formatter};
4use std::ffi::OsStr;
5use std::io::{self, Read};
6use std::path::{Path, PathBuf};
7use std::process::{Command, Stdio};
8use std::sync::Arc;
9
10
11
12#[derive(Clone)]
14pub struct Repository {
15 dot_git: Arc<PathBuf>,
16}
17
18impl Repository {
19 pub fn from_bare_repository(dir: impl Into<PathBuf>) -> io::Result<Self> {
25 let dir = dir.into();
27 if dir.join(".git").exists() { return Err(io::Error::new(io::ErrorKind::InvalidData, "not a bare repository")); }
28 Ok(Self { dot_git: Arc::new(dir) })
29 }
30
31 pub fn from_regular_repository(dir: impl AsRef<Path>) -> io::Result<Self> {
37 let dir = dir.as_ref();
39 let dot_git = dir.join(".git");
40 if !dot_git.exists() { return Err(io::Error::new(io::ErrorKind::InvalidData, "not a regular repository")); }
41 Ok(Self { dot_git: Arc::new(dot_git) })
42 }
43
44
45 pub fn from_path(dir: impl AsRef<Path>) -> io::Result<Self> {
52 let dir = dir.as_ref();
53 let dot_git = dir.join(".git");
54 if dot_git.exists() {
55 Ok(Self { dot_git: Arc::new(dot_git) })
56 } else {
57 Self::from_bare_repository(dir)
58 }
59 }
60
61 pub fn local_branches(&self) -> io::Result<impl Iterator<Item = io::Result<Branch>>> {
76 let mut branches = Default::default();
77 gather_branches(OsStr::new(""), &self.dot_git.join("refs/heads"), &mut branches)?;
78 Ok(branches.into_iter().map(|(name, commit)| Ok(Branch { name, commit })))
79 }
80
81 pub fn remote_branches(&self) -> io::Result<impl Iterator<Item = io::Result<Branch>>> {
96 let mut branches = Default::default();
97 gather_branches(OsStr::new(""), &self.dot_git.join("refs/remotes"), &mut branches)?;
98 Ok(branches.into_iter().map(|(name, commit)| Ok(Branch { name, commit })))
99 }
100
101 pub fn cat_file_size(&self, hash: &blob::Hash) -> io::Result<u64> {
103 let hash = HashTempStr::new(hash);
104 let git = self.git().args(&["cat-file", "-s", hash.as_str()]).output()?;
105 match git.status.code() {
106 Some(0) => {},
107 Some(_) => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -s ... exited non-zero")),
108 None => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -s ... died by signal")),
109 }
110 Ok(String::from_utf8(git.stdout)
111 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -s ... returned non-utf8 size"))?
112 .trim()
113 .parse()
114 .map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -s ... returned non-u64 file size"))?
115 )
116 }
117
118 pub fn cat_file_type(&self, hash: &unknown::Hash) -> io::Result<FileType> {
120 let hash = HashTempStr::new(hash);
121 let git = self.git().args(&["cat-file", "-t", hash.as_str()]).output()?;
122 match git.status.code() {
123 Some(0) => {},
124 Some(_) => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -t ... exited non-zero")),
125 None => return Err(io::Error::new(io::ErrorKind::Other, "git cat-file -t ... died by signal")),
126 }
127 Ok(String::from_utf8(git.stdout).map_err(|_| io::Error::new(io::ErrorKind::InvalidData, "git cat-file -t ... returned non-utf8 type"))?.trim().into())
128 }
129
130 pub fn cat_file_commit (&self, hash: &commit::Hash) -> io::Result<impl Read> { self.cat_file("commit", hash) }
132
133 pub fn cat_file_tree (&self, hash: &tree::Hash) -> io::Result<impl Read> { self.cat_file("tree", hash) }
135
136 pub fn cat_file_blob (&self, hash: &blob::Hash) -> io::Result<impl Read> { self.cat_file("blob", hash) }
138
139 fn git(&self) -> Command {
140 let mut c = Command::new("git");
141 c.current_dir(&*self.dot_git);
142 c
143 }
144
145 fn cat_file<T>(&self, ty: &str, hash: &generic::Hash<T>) -> io::Result<impl Read> {
146 let hash = HashTempStr::new(hash);
147 let mut git = self.git()
148 .args(&["cat-file", ty, hash.as_str()])
149 .stdin (Stdio::null())
150 .stderr(Stdio::null())
151 .stdout(Stdio::piped())
152 .spawn()?;
153 Ok(CatFileReader { stdout: git.stdout.take().unwrap(), child: git })
154 }
155}
156
157impl Debug for Repository {
158 fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
159 fmt.debug_struct("Repository")
160 .field("dot_git", &self.dot_git)
161 .field("local_branches", &self.local_branches().map(|b| b.collect::<Vec<_>>()))
162 .field("remote_branches", &self.remote_branches().map(|r| r.collect::<Vec<_>>()))
163 .finish()
164 }
165}