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 pull::PullCommand, push::PushCommand, rebase::RebaseCommand, reflog::ReflogCommand,
43 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]
246 pub fn stash(&self, action: StashCommand) -> StashCommand {
247 let mut c = action;
248 c.current_dir(&self.path);
249 c
250 }
251
252 #[must_use]
254 pub fn reset(&self) -> ResetCommand {
255 let mut c = ResetCommand::new();
256 c.current_dir(&self.path);
257 c
258 }
259
260 #[must_use]
262 pub fn restore(&self) -> RestoreCommand {
263 let mut c = RestoreCommand::new();
264 c.current_dir(&self.path);
265 c
266 }
267
268 #[must_use]
270 pub fn rm(&self) -> RmCommand {
271 let mut c = RmCommand::new();
272 c.current_dir(&self.path);
273 c
274 }
275
276 pub fn mv(&self, src: impl Into<String>, dst: impl Into<String>) -> MvCommand {
278 let mut c = MvCommand::new(src, dst);
279 c.current_dir(&self.path);
280 c
281 }
282
283 #[must_use]
285 pub fn cherry_pick(&self) -> CherryPickCommand {
286 let mut c = CherryPickCommand::new();
287 c.current_dir(&self.path);
288 c
289 }
290
291 pub fn grep(&self, pattern: impl Into<String>) -> GrepCommand {
293 let mut c = GrepCommand::new(pattern);
294 c.current_dir(&self.path);
295 c
296 }
297
298 #[must_use]
300 pub fn config(&self, action: ConfigCommand) -> ConfigCommand {
301 let mut c = action;
302 c.current_dir(&self.path);
303 c
304 }
305
306 #[must_use]
308 pub fn reflog(&self, action: ReflogCommand) -> ReflogCommand {
309 let mut c = action;
310 c.current_dir(&self.path);
311 c
312 }
313
314 #[must_use]
316 pub fn worktree(&self, action: WorktreeCommand) -> WorktreeCommand {
317 let mut c = action;
318 c.current_dir(&self.path);
319 c
320 }
321
322 #[must_use]
324 pub fn submodule(&self, action: SubmoduleCommand) -> SubmoduleCommand {
325 let mut c = action;
326 c.current_dir(&self.path);
327 c
328 }
329
330 #[must_use]
332 pub fn bisect(&self, action: BisectCommand) -> BisectCommand {
333 let mut c = action;
334 c.current_dir(&self.path);
335 c
336 }
337
338 #[must_use]
340 pub fn rev_parse(&self) -> RevParseCommand {
341 let mut c = RevParseCommand::new();
342 c.current_dir(&self.path);
343 c
344 }
345
346 #[must_use]
348 pub fn describe(&self) -> DescribeCommand {
349 let mut c = DescribeCommand::new();
350 c.current_dir(&self.path);
351 c
352 }
353
354 #[must_use]
356 pub fn ls_files(&self) -> LsFilesCommand {
357 let mut c = LsFilesCommand::new();
358 c.current_dir(&self.path);
359 c
360 }
361
362 pub fn ls_tree(&self, tree: impl Into<String>) -> LsTreeCommand {
364 let mut c = LsTreeCommand::new(tree);
365 c.current_dir(&self.path);
366 c
367 }
368
369 #[must_use]
371 pub fn show_ref(&self) -> ShowRefCommand {
372 let mut c = ShowRefCommand::new();
373 c.current_dir(&self.path);
374 c
375 }
376
377 #[must_use]
382 pub fn symbolic_ref(&self, action: SymbolicRefCommand) -> SymbolicRefCommand {
383 let mut c = action;
384 c.current_dir(&self.path);
385 c
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392
393 #[test]
394 fn open_missing_repo_errors() {
395 let tmp = tempfile::tempdir().unwrap();
396 let err = Repository::open(tmp.path()).unwrap_err();
397 assert!(matches!(err, Error::NotARepository { .. }));
398 }
399
400 #[test]
401 fn new_unchecked_does_not_check() {
402 let repo = Repository::new_unchecked("/definitely/not/here");
403 assert_eq!(repo.path(), Path::new("/definitely/not/here"));
404 }
405}