1use crate::command::{
37 GitCommand, add::AddCommand, bisect::BisectCommand, branch::BranchCommand,
38 checkout::CheckoutCommand, cherry_pick::CherryPickCommand, clone::CloneCommand,
39 commit::CommitCommand, config::ConfigCommand, describe::DescribeCommand, diff::DiffCommand,
40 fetch::FetchCommand, grep::GrepCommand, init::InitCommand, log::LogCommand,
41 ls_files::LsFilesCommand, ls_tree::LsTreeCommand, merge::MergeCommand, mv::MvCommand,
42 notes::NotesCommand, pull::PullCommand, push::PushCommand, rebase::RebaseCommand,
43 reflog::ReflogCommand, remote::RemoteCommand, reset::ResetCommand, restore::RestoreCommand,
44 rev_parse::RevParseCommand, rm::RmCommand, show::ShowCommand, show_ref::ShowRefCommand,
45 stash::StashCommand, status::StatusCommand, submodule::SubmoduleCommand, switch::SwitchCommand,
46 symbolic_ref::SymbolicRefCommand, tag::TagCommand, worktree::WorktreeCommand,
47};
48use crate::error::{Error, Result};
49use std::path::{Path, PathBuf};
50
51#[derive(Debug, Clone)]
56pub struct Repository {
57 path: PathBuf,
58}
59
60impl Repository {
61 pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
65 let path = path.into();
66 let dotgit = path.join(".git");
67 if !dotgit.exists() {
68 return Err(Error::not_a_repository(path.display().to_string()));
69 }
70 Ok(Self { path })
71 }
72
73 #[must_use]
77 pub fn new_unchecked(path: impl Into<PathBuf>) -> Self {
78 Self { path: path.into() }
79 }
80
81 #[must_use]
83 pub fn path(&self) -> &Path {
84 &self.path
85 }
86
87 #[must_use]
89 pub fn git_dir(&self) -> PathBuf {
90 self.path.join(".git")
91 }
92
93 pub async fn init(path: impl Into<PathBuf>) -> Result<Self> {
97 let path = path.into();
98 if let Some(parent) = path.parent() {
99 if !parent.as_os_str().is_empty() && !parent.exists() {
100 std::fs::create_dir_all(parent).map_err(Error::from)?;
101 }
102 }
103 if !path.exists() {
104 std::fs::create_dir_all(&path).map_err(Error::from)?;
105 }
106 InitCommand::in_directory(path).execute().await
107 }
108
109 pub async fn clone(url: impl Into<String>, path: impl Into<PathBuf>) -> Result<Self> {
111 let mut cmd = CloneCommand::new(url);
112 cmd.directory(path);
113 cmd.execute().await
114 }
115
116 #[must_use]
118 pub fn add(&self) -> AddCommand {
119 let mut c = AddCommand::new();
120 c.current_dir(&self.path);
121 c
122 }
123
124 #[must_use]
126 pub fn commit(&self) -> CommitCommand {
127 let mut c = CommitCommand::new();
128 c.current_dir(&self.path);
129 c
130 }
131
132 #[must_use]
134 pub fn status(&self) -> StatusCommand {
135 let mut c = StatusCommand::new();
136 c.current_dir(&self.path);
137 c
138 }
139
140 #[must_use]
142 pub fn log(&self) -> LogCommand {
143 let mut c = LogCommand::new();
144 c.current_dir(&self.path);
145 c
146 }
147
148 #[must_use]
150 pub fn diff(&self) -> DiffCommand {
151 let mut c = DiffCommand::new();
152 c.current_dir(&self.path);
153 c
154 }
155
156 #[must_use]
158 pub fn show(&self) -> ShowCommand {
159 let mut c = ShowCommand::new();
160 c.current_dir(&self.path);
161 c
162 }
163
164 #[must_use]
166 pub fn branch(&self) -> BranchCommand {
167 let mut c = BranchCommand::new();
168 c.current_dir(&self.path);
169 c
170 }
171
172 #[must_use]
174 pub fn checkout(&self) -> CheckoutCommand {
175 let mut c = CheckoutCommand::new();
176 c.current_dir(&self.path);
177 c
178 }
179
180 #[must_use]
182 pub fn switch(&self) -> SwitchCommand {
183 let mut c = SwitchCommand::new();
184 c.current_dir(&self.path);
185 c
186 }
187
188 #[must_use]
190 pub fn merge(&self) -> MergeCommand {
191 let mut c = MergeCommand::new();
192 c.current_dir(&self.path);
193 c
194 }
195
196 #[must_use]
198 pub fn rebase(&self) -> RebaseCommand {
199 let mut c = RebaseCommand::new();
200 c.current_dir(&self.path);
201 c
202 }
203
204 #[must_use]
206 pub fn pull(&self) -> PullCommand {
207 let mut c = PullCommand::new();
208 c.current_dir(&self.path);
209 c
210 }
211
212 #[must_use]
214 pub fn push(&self) -> PushCommand {
215 let mut c = PushCommand::new();
216 c.current_dir(&self.path);
217 c
218 }
219
220 #[must_use]
222 pub fn fetch(&self) -> FetchCommand {
223 let mut c = FetchCommand::new();
224 c.current_dir(&self.path);
225 c
226 }
227
228 #[must_use]
230 pub fn remote(&self, action: RemoteCommand) -> RemoteCommand {
231 let mut c = action;
232 c.current_dir(&self.path);
233 c
234 }
235
236 #[must_use]
238 pub fn tag(&self) -> TagCommand {
239 let mut c = TagCommand::new();
240 c.current_dir(&self.path);
241 c
242 }
243
244 #[must_use]
250 pub fn notes(&self, action: NotesCommand) -> NotesCommand {
251 let mut c = action;
252 c.current_dir(&self.path);
253 c
254 }
255
256 #[must_use]
258 pub fn stash(&self, action: StashCommand) -> StashCommand {
259 let mut c = action;
260 c.current_dir(&self.path);
261 c
262 }
263
264 #[must_use]
266 pub fn reset(&self) -> ResetCommand {
267 let mut c = ResetCommand::new();
268 c.current_dir(&self.path);
269 c
270 }
271
272 #[must_use]
274 pub fn restore(&self) -> RestoreCommand {
275 let mut c = RestoreCommand::new();
276 c.current_dir(&self.path);
277 c
278 }
279
280 #[must_use]
282 pub fn rm(&self) -> RmCommand {
283 let mut c = RmCommand::new();
284 c.current_dir(&self.path);
285 c
286 }
287
288 pub fn mv(&self, src: impl Into<String>, dst: impl Into<String>) -> MvCommand {
290 let mut c = MvCommand::new(src, dst);
291 c.current_dir(&self.path);
292 c
293 }
294
295 #[must_use]
297 pub fn cherry_pick(&self) -> CherryPickCommand {
298 let mut c = CherryPickCommand::new();
299 c.current_dir(&self.path);
300 c
301 }
302
303 pub fn grep(&self, pattern: impl Into<String>) -> GrepCommand {
305 let mut c = GrepCommand::new(pattern);
306 c.current_dir(&self.path);
307 c
308 }
309
310 #[must_use]
312 pub fn config(&self, action: ConfigCommand) -> ConfigCommand {
313 let mut c = action;
314 c.current_dir(&self.path);
315 c
316 }
317
318 #[must_use]
320 pub fn reflog(&self, action: ReflogCommand) -> ReflogCommand {
321 let mut c = action;
322 c.current_dir(&self.path);
323 c
324 }
325
326 #[must_use]
328 pub fn worktree(&self, action: WorktreeCommand) -> WorktreeCommand {
329 let mut c = action;
330 c.current_dir(&self.path);
331 c
332 }
333
334 #[must_use]
336 pub fn submodule(&self, action: SubmoduleCommand) -> SubmoduleCommand {
337 let mut c = action;
338 c.current_dir(&self.path);
339 c
340 }
341
342 #[must_use]
344 pub fn bisect(&self, action: BisectCommand) -> BisectCommand {
345 let mut c = action;
346 c.current_dir(&self.path);
347 c
348 }
349
350 #[must_use]
352 pub fn rev_parse(&self) -> RevParseCommand {
353 let mut c = RevParseCommand::new();
354 c.current_dir(&self.path);
355 c
356 }
357
358 #[must_use]
360 pub fn describe(&self) -> DescribeCommand {
361 let mut c = DescribeCommand::new();
362 c.current_dir(&self.path);
363 c
364 }
365
366 #[must_use]
368 pub fn ls_files(&self) -> LsFilesCommand {
369 let mut c = LsFilesCommand::new();
370 c.current_dir(&self.path);
371 c
372 }
373
374 pub fn ls_tree(&self, tree: impl Into<String>) -> LsTreeCommand {
376 let mut c = LsTreeCommand::new(tree);
377 c.current_dir(&self.path);
378 c
379 }
380
381 #[must_use]
383 pub fn show_ref(&self) -> ShowRefCommand {
384 let mut c = ShowRefCommand::new();
385 c.current_dir(&self.path);
386 c
387 }
388
389 #[must_use]
394 pub fn symbolic_ref(&self, action: SymbolicRefCommand) -> SymbolicRefCommand {
395 let mut c = action;
396 c.current_dir(&self.path);
397 c
398 }
399}
400
401#[cfg(test)]
402mod tests {
403 use super::*;
404
405 #[test]
406 fn open_missing_repo_errors() {
407 let tmp = tempfile::tempdir().unwrap();
408 let err = Repository::open(tmp.path()).unwrap_err();
409 assert!(matches!(err, Error::NotARepository { .. }));
410 }
411
412 #[test]
413 fn new_unchecked_does_not_check() {
414 let repo = Repository::new_unchecked("/definitely/not/here");
415 assert_eq!(repo.path(), Path::new("/definitely/not/here"));
416 }
417}