1use std::path::PathBuf;
2
3use git2::{build::RepoBuilder, FetchOptions, Repository};
4use log::debug;
5
6use crate::error::Result;
7use crate::{convert_to_bare, get_default_branch_name, get_remote_callbacks};
8
9pub struct CloneOptions {
11 pub on_transfer_progress: Box<dyn FnMut(usize, usize, usize)>,
13}
14
15impl Default for CloneOptions {
16 fn default() -> Self {
17 Self {
18 on_transfer_progress: Box::new(|_, _, _| {}),
19 }
20 }
21}
22
23pub fn clone(path: PathBuf, url: &str, options: CloneOptions) -> Result<Repository> {
32 let CloneOptions {
33 mut on_transfer_progress,
34 } = options;
35 debug!("path {}", path.display());
36 let path = if path.ends_with(".bare") {
37 debug!("ended with .bare!");
38 path
39 } else {
40 debug!("didn't end with .bare!");
41 path.join(".bare")
42 };
43
44 debug!("final path {}", path.display());
45
46 let mut callbacks = get_remote_callbacks()?;
47 callbacks.transfer_progress(move |progress| {
48 on_transfer_progress(
49 progress.received_objects(),
50 progress.total_objects(),
51 progress.received_bytes(),
52 );
53 true
54 });
55
56 let mut fetch_options = FetchOptions::new();
57 fetch_options.remote_callbacks(callbacks);
58
59 let mut builder = RepoBuilder::new();
60 builder.bare(true);
61 builder.fetch_options(fetch_options);
62 builder.remote_create(|repo, name, url| {
63 debug!("Creating remote {} at {}", name, url);
64 let remote = repo.remote(name, url)?;
65
66 match get_default_branch_name(repo, Some(remote)) {
67 Ok(default_branch) => {
68 debug!("Default branch: {}", default_branch);
69 repo.remote_add_fetch(
70 name,
71 format!(
72 "+refs/heads/{default_branch}:refs/remotes/origin/{default_branch}",
73 default_branch = default_branch
74 )
75 .as_str(),
76 )?;
77 repo.find_remote(name)
78 }
79 Err(_) => {
80 debug!("No default branch found");
81 repo.remote(name, url)
82 }
83 }
84 });
85
86 debug!("Cloning {} into {}", url, path.display());
87
88 let repo = builder.clone(url, &path)?;
90 convert_to_bare(repo)
93}