1use std::path::{Path, PathBuf};
2
3use crate::commit::CommitObject;
4use crate::error::{GitError, Result};
5use crate::hash::GitHash;
6use crate::loose;
7use crate::object::{ObjectKind, RawObject};
8use crate::pack;
9use crate::reflog::{self, ReflogEntry};
10use crate::refs;
11use crate::tree::TreeObject;
12
13pub struct GitRepo {
14 git_dir: PathBuf,
16 objects_dir: PathBuf,
18}
19
20impl GitRepo {
21 pub fn open(path: &Path) -> Result<Self> {
24 let git_dir = if path.join("HEAD").exists() {
25 path.to_owned()
26 } else if path.join(".git").join("HEAD").exists() {
27 path.join(".git")
28 } else {
29 return Err(GitError::InvalidObject(format!(
30 "not a git repository: {}",
31 path.display()
32 )));
33 };
34
35 let objects_dir = git_dir.join("objects");
36 Ok(Self {
37 git_dir,
38 objects_dir,
39 })
40 }
41
42 pub fn head(&self) -> Result<GitHash> {
44 refs::resolve_ref(&self.git_dir, "HEAD")
45 }
46
47 pub fn resolve_ref(&self, name: &str) -> Result<GitHash> {
49 refs::resolve_ref(&self.git_dir, name)
50 }
51
52 pub fn read_object(&self, hash: &GitHash) -> Result<RawObject> {
60 match loose::read_loose(&self.objects_dir, hash) {
61 Err(GitError::ObjectNotFound(h)) => match pack::read_packed(&self.objects_dir, hash)? {
62 Some(obj) => Ok(obj),
63 None => Err(GitError::ObjectNotFound(h)),
64 },
65 other => other,
66 }
67 }
68
69 pub fn read_commit(&self, hash: &GitHash) -> Result<CommitObject> {
71 let obj = self.read_object(hash)?;
72 if obj.kind != ObjectKind::Commit {
73 return Err(GitError::InvalidObject(format!(
74 "{hash} is a {:?}, not a commit",
75 obj.kind
76 )));
77 }
78 CommitObject::parse(*hash, &obj.data)
79 }
80
81 pub fn read_tree(&self, hash: &GitHash) -> Result<TreeObject> {
83 let obj = self.read_object(hash)?;
84 if obj.kind != ObjectKind::Tree {
85 return Err(GitError::InvalidObject(format!(
86 "{hash} is a {:?}, not a tree",
87 obj.kind
88 )));
89 }
90 TreeObject::parse(*hash, &obj.data)
91 }
92
93 pub fn read_blob(&self, hash: &GitHash) -> Result<Vec<u8>> {
95 let obj = self.read_object(hash)?;
96 if obj.kind != ObjectKind::Blob {
97 return Err(GitError::InvalidObject(format!(
98 "{hash} is a {:?}, not a blob",
99 obj.kind
100 )));
101 }
102 Ok(obj.data)
103 }
104
105 pub fn walk_commits(&self, from: GitHash) -> impl Iterator<Item = Result<CommitObject>> + '_ {
107 CommitWalker {
108 repo: self,
109 next: Some(from),
110 }
111 }
112
113 pub fn reflog(&self, refname: &str) -> Result<Vec<ReflogEntry>> {
121 reflog::read_reflog(&self.git_dir, refname)
122 }
123
124 pub fn all_objects(&self) -> Result<Vec<GitHash>> {
130 let mut seen = std::collections::HashSet::new();
131 let mut out = Vec::new();
132 for hash in loose::list_loose(&self.objects_dir)
133 .into_iter()
134 .chain(pack::list_packed(&self.objects_dir)?)
135 {
136 if seen.insert(hash) {
137 out.push(hash);
138 }
139 }
140 Ok(out)
141 }
142
143 #[must_use]
146 pub fn all_refs(&self) -> Vec<(String, GitHash)> {
147 refs::list_refs(&self.git_dir)
148 }
149}
150
151struct CommitWalker<'a> {
152 repo: &'a GitRepo,
153 next: Option<GitHash>,
154}
155
156impl Iterator for CommitWalker<'_> {
157 type Item = Result<CommitObject>;
158
159 fn next(&mut self) -> Option<Self::Item> {
160 let hash = self.next.take()?;
161 match self.repo.read_commit(&hash) {
162 Ok(commit) => {
163 self.next = commit.parents.first().copied();
164 Some(Ok(commit))
165 }
166 Err(e) => Some(Err(e)),
167 }
168 }
169}