1use crate::command::{
37 GitCommand, add::AddCommand, bisect::BisectCommand, branch::BranchCommand,
38 checkout::CheckoutCommand, cherry_pick::CherryPickCommand, clone::CloneCommand,
39 commit::CommitCommand, config::ConfigCommand, diff::DiffCommand, fetch::FetchCommand,
40 grep::GrepCommand, init::InitCommand, log::LogCommand, merge::MergeCommand, mv::MvCommand,
41 pull::PullCommand, push::PushCommand, rebase::RebaseCommand, reflog::ReflogCommand,
42 remote::RemoteCommand, reset::ResetCommand, restore::RestoreCommand, rm::RmCommand,
43 show::ShowCommand, stash::StashCommand, status::StatusCommand, submodule::SubmoduleCommand,
44 switch::SwitchCommand, tag::TagCommand, worktree::WorktreeCommand,
45};
46use crate::error::{Error, Result};
47use std::path::{Path, PathBuf};
48
49#[derive(Debug, Clone)]
54pub struct Repository {
55 path: PathBuf,
56}
57
58impl Repository {
59 pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
63 let path = path.into();
64 let dotgit = path.join(".git");
65 if !dotgit.exists() {
66 return Err(Error::not_a_repository(path.display().to_string()));
67 }
68 Ok(Self { path })
69 }
70
71 #[must_use]
75 pub fn new_unchecked(path: impl Into<PathBuf>) -> Self {
76 Self { path: path.into() }
77 }
78
79 #[must_use]
81 pub fn path(&self) -> &Path {
82 &self.path
83 }
84
85 #[must_use]
87 pub fn git_dir(&self) -> PathBuf {
88 self.path.join(".git")
89 }
90
91 pub async fn init(path: impl Into<PathBuf>) -> Result<Self> {
95 let path = path.into();
96 if let Some(parent) = path.parent() {
97 if !parent.as_os_str().is_empty() && !parent.exists() {
98 std::fs::create_dir_all(parent).map_err(Error::from)?;
99 }
100 }
101 if !path.exists() {
102 std::fs::create_dir_all(&path).map_err(Error::from)?;
103 }
104 InitCommand::in_directory(path).execute().await
105 }
106
107 pub async fn clone(url: impl Into<String>, path: impl Into<PathBuf>) -> Result<Self> {
109 let mut cmd = CloneCommand::new(url);
110 cmd.directory(path);
111 cmd.execute().await
112 }
113
114 #[must_use]
116 pub fn add(&self) -> AddCommand {
117 let mut c = AddCommand::new();
118 c.current_dir(&self.path);
119 c
120 }
121
122 #[must_use]
124 pub fn commit(&self) -> CommitCommand {
125 let mut c = CommitCommand::new();
126 c.current_dir(&self.path);
127 c
128 }
129
130 #[must_use]
132 pub fn status(&self) -> StatusCommand {
133 let mut c = StatusCommand::new();
134 c.current_dir(&self.path);
135 c
136 }
137
138 #[must_use]
140 pub fn log(&self) -> LogCommand {
141 let mut c = LogCommand::new();
142 c.current_dir(&self.path);
143 c
144 }
145
146 #[must_use]
148 pub fn diff(&self) -> DiffCommand {
149 let mut c = DiffCommand::new();
150 c.current_dir(&self.path);
151 c
152 }
153
154 #[must_use]
156 pub fn show(&self) -> ShowCommand {
157 let mut c = ShowCommand::new();
158 c.current_dir(&self.path);
159 c
160 }
161
162 #[must_use]
164 pub fn branch(&self) -> BranchCommand {
165 let mut c = BranchCommand::new();
166 c.current_dir(&self.path);
167 c
168 }
169
170 #[must_use]
172 pub fn checkout(&self) -> CheckoutCommand {
173 let mut c = CheckoutCommand::new();
174 c.current_dir(&self.path);
175 c
176 }
177
178 #[must_use]
180 pub fn switch(&self) -> SwitchCommand {
181 let mut c = SwitchCommand::new();
182 c.current_dir(&self.path);
183 c
184 }
185
186 #[must_use]
188 pub fn merge(&self) -> MergeCommand {
189 let mut c = MergeCommand::new();
190 c.current_dir(&self.path);
191 c
192 }
193
194 #[must_use]
196 pub fn rebase(&self) -> RebaseCommand {
197 let mut c = RebaseCommand::new();
198 c.current_dir(&self.path);
199 c
200 }
201
202 #[must_use]
204 pub fn pull(&self) -> PullCommand {
205 let mut c = PullCommand::new();
206 c.current_dir(&self.path);
207 c
208 }
209
210 #[must_use]
212 pub fn push(&self) -> PushCommand {
213 let mut c = PushCommand::new();
214 c.current_dir(&self.path);
215 c
216 }
217
218 #[must_use]
220 pub fn fetch(&self) -> FetchCommand {
221 let mut c = FetchCommand::new();
222 c.current_dir(&self.path);
223 c
224 }
225
226 #[must_use]
228 pub fn remote(&self, action: RemoteCommand) -> RemoteCommand {
229 let mut c = action;
230 c.current_dir(&self.path);
231 c
232 }
233
234 #[must_use]
236 pub fn tag(&self) -> TagCommand {
237 let mut c = TagCommand::new();
238 c.current_dir(&self.path);
239 c
240 }
241
242 #[must_use]
244 pub fn stash(&self, action: StashCommand) -> StashCommand {
245 let mut c = action;
246 c.current_dir(&self.path);
247 c
248 }
249
250 #[must_use]
252 pub fn reset(&self) -> ResetCommand {
253 let mut c = ResetCommand::new();
254 c.current_dir(&self.path);
255 c
256 }
257
258 #[must_use]
260 pub fn restore(&self) -> RestoreCommand {
261 let mut c = RestoreCommand::new();
262 c.current_dir(&self.path);
263 c
264 }
265
266 #[must_use]
268 pub fn rm(&self) -> RmCommand {
269 let mut c = RmCommand::new();
270 c.current_dir(&self.path);
271 c
272 }
273
274 pub fn mv(&self, src: impl Into<String>, dst: impl Into<String>) -> MvCommand {
276 let mut c = MvCommand::new(src, dst);
277 c.current_dir(&self.path);
278 c
279 }
280
281 #[must_use]
283 pub fn cherry_pick(&self) -> CherryPickCommand {
284 let mut c = CherryPickCommand::new();
285 c.current_dir(&self.path);
286 c
287 }
288
289 pub fn grep(&self, pattern: impl Into<String>) -> GrepCommand {
291 let mut c = GrepCommand::new(pattern);
292 c.current_dir(&self.path);
293 c
294 }
295
296 #[must_use]
298 pub fn config(&self, action: ConfigCommand) -> ConfigCommand {
299 let mut c = action;
300 c.current_dir(&self.path);
301 c
302 }
303
304 #[must_use]
306 pub fn reflog(&self, action: ReflogCommand) -> ReflogCommand {
307 let mut c = action;
308 c.current_dir(&self.path);
309 c
310 }
311
312 #[must_use]
314 pub fn worktree(&self, action: WorktreeCommand) -> WorktreeCommand {
315 let mut c = action;
316 c.current_dir(&self.path);
317 c
318 }
319
320 #[must_use]
322 pub fn submodule(&self, action: SubmoduleCommand) -> SubmoduleCommand {
323 let mut c = action;
324 c.current_dir(&self.path);
325 c
326 }
327
328 #[must_use]
330 pub fn bisect(&self, action: BisectCommand) -> BisectCommand {
331 let mut c = action;
332 c.current_dir(&self.path);
333 c
334 }
335}
336
337#[cfg(test)]
338mod tests {
339 use super::*;
340
341 #[test]
342 fn open_missing_repo_errors() {
343 let tmp = tempfile::tempdir().unwrap();
344 let err = Repository::open(tmp.path()).unwrap_err();
345 assert!(matches!(err, Error::NotARepository { .. }));
346 }
347
348 #[test]
349 fn new_unchecked_does_not_check() {
350 let repo = Repository::new_unchecked("/definitely/not/here");
351 assert_eq!(repo.path(), Path::new("/definitely/not/here"));
352 }
353}