Initial commit
This commit is contained in:
51
src/cmd.rs
Normal file
51
src/cmd.rs
Normal file
@@ -0,0 +1,51 @@
|
||||
use clap::{AppSettings, Parser};
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap(
|
||||
name = clap::crate_name!(),
|
||||
version = clap::crate_version!(),
|
||||
author = clap::crate_authors!("\n"),
|
||||
about = clap::crate_description!(),
|
||||
long_version = clap::crate_version!(),
|
||||
license = clap::crate_license!(),
|
||||
setting = AppSettings::DeriveDisplayOrder,
|
||||
setting = AppSettings::PropagateVersion,
|
||||
setting = AppSettings::HelpRequired,
|
||||
)]
|
||||
pub struct Opts {
|
||||
#[clap(subcommand)]
|
||||
pub subcmd: SubCommand,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub enum SubCommand {
|
||||
#[clap(
|
||||
visible_alias = "run",
|
||||
about = "Synchronize the repositories to the configured values"
|
||||
)]
|
||||
Sync(Sync),
|
||||
#[clap(about = "Generate a repository configuration from an existing file tree")]
|
||||
Find(Find),
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
#[clap()]
|
||||
pub struct Sync {
|
||||
#[clap(
|
||||
short,
|
||||
long,
|
||||
default_value = "./config.toml",
|
||||
about = "Path to the configuration file"
|
||||
)]
|
||||
pub config: String,
|
||||
}
|
||||
|
||||
#[derive(Parser)]
|
||||
pub struct Find {
|
||||
#[clap(about = "The path to search through")]
|
||||
pub path: String,
|
||||
}
|
||||
|
||||
pub fn parse() -> Opts {
|
||||
Opts::parse()
|
||||
}
|
||||
40
src/config.rs
Normal file
40
src/config.rs
Normal file
@@ -0,0 +1,40 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use super::repo::Repo;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Config {
|
||||
pub trees: Vec<Tree>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Tree {
|
||||
pub root: Option<String>,
|
||||
pub repos: Option<Vec<Repo>>,
|
||||
}
|
||||
|
||||
pub fn read_config(path: &str) -> Result<Config, String> {
|
||||
let content = match std::fs::read_to_string(&path) {
|
||||
Ok(s) => s,
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"Error reading configuration file \"{}\": {}",
|
||||
path, e
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let config: Config = match toml::from_str(&content) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
return Err(format!(
|
||||
"Error parsing configuration file \"{}\": {}",
|
||||
path, e
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
395
src/lib.rs
Normal file
395
src/lib.rs
Normal file
@@ -0,0 +1,395 @@
|
||||
use std::fs;
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::process;
|
||||
|
||||
mod cmd;
|
||||
mod config;
|
||||
mod output;
|
||||
mod repo;
|
||||
|
||||
use config::{Config, Tree};
|
||||
use output::*;
|
||||
|
||||
use repo::{clone_repo, detect_remote_type, init_repo, open_repo, Remote, Repo};
|
||||
|
||||
fn path_as_string(path: &Path) -> String {
|
||||
path.to_path_buf().into_os_string().into_string().unwrap()
|
||||
}
|
||||
|
||||
fn env_home() -> PathBuf {
|
||||
match std::env::var("HOME") {
|
||||
Ok(path) => Path::new(&path).to_path_buf(),
|
||||
Err(e) => {
|
||||
print_error(&format!("Unable to read HOME: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn sync_trees(config: Config) {
|
||||
for tree in config.trees {
|
||||
let repos = tree.repos.unwrap_or_default();
|
||||
|
||||
let root_path = match &tree.root {
|
||||
Some(root) => {
|
||||
fn home_dir() -> Option<PathBuf> {
|
||||
Some(env_home())
|
||||
}
|
||||
|
||||
let expanded_path = match shellexpand::full_with_context(
|
||||
&root,
|
||||
home_dir,
|
||||
|name| -> Result<Option<String>, &'static str> {
|
||||
match name {
|
||||
"HOME" => Ok(Some(path_as_string(home_dir().unwrap().as_path()))),
|
||||
_ => Ok(None),
|
||||
}
|
||||
},
|
||||
) {
|
||||
Ok(std::borrow::Cow::Borrowed(path)) => path.to_owned(),
|
||||
Ok(std::borrow::Cow::Owned(path)) => path,
|
||||
Err(e) => {
|
||||
print_error(&format!("Unable to expand root: {}", e));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
Path::new(&expanded_path).to_path_buf()
|
||||
}
|
||||
None => std::env::current_dir().unwrap(),
|
||||
};
|
||||
|
||||
for repo in &repos {
|
||||
let name = &repo.name;
|
||||
|
||||
let repo_path = root_path.join(&repo.name);
|
||||
|
||||
let mut repo_handle = None;
|
||||
|
||||
if repo_path.exists() {
|
||||
repo_handle = Some(open_repo(&repo_path).unwrap_or_else(|error| {
|
||||
print_repo_error(name, &format!("Opening repository failed: {}", error));
|
||||
process::exit(1);
|
||||
}));
|
||||
} else {
|
||||
match &repo.remotes {
|
||||
None => {
|
||||
print_repo_action(
|
||||
name,
|
||||
"Repository does not have remotes configured, initializing new",
|
||||
);
|
||||
repo_handle = match init_repo(&repo_path) {
|
||||
Ok(r) => {
|
||||
print_repo_success(name, "Repository created");
|
||||
Some(r)
|
||||
}
|
||||
Err(e) => {
|
||||
print_repo_error(
|
||||
name,
|
||||
&format!("Repository failed during init: {}", e),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
Some(r) => {
|
||||
let first = match r.first() {
|
||||
Some(e) => e,
|
||||
None => {
|
||||
panic!("Repos is an empty array. This is a bug");
|
||||
}
|
||||
};
|
||||
|
||||
match clone_repo(first, &repo_path) {
|
||||
Ok(_) => {
|
||||
print_repo_success(name, "Repository successfully cloned");
|
||||
}
|
||||
Err(e) => {
|
||||
print_repo_error(
|
||||
name,
|
||||
&format!("Repository failed during clone: {}", e),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
if let Some(remotes) = &repo.remotes {
|
||||
let repo_handle = repo_handle
|
||||
.unwrap_or_else(|| open_repo(&repo_path).unwrap_or_else(|_| process::exit(1)));
|
||||
|
||||
let current_remotes: Vec<String> = match repo_handle.remotes() {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
print_repo_error(
|
||||
name,
|
||||
&format!("Repository failed during getting the remotes: {}", e),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
.iter()
|
||||
.flatten()
|
||||
.map(|r| r.to_owned())
|
||||
.collect();
|
||||
|
||||
for remote in remotes {
|
||||
if !current_remotes.iter().any(|r| *r == remote.name) {
|
||||
print_repo_action(
|
||||
name,
|
||||
&format!(
|
||||
"Setting up new remote \"{}\" to \"{}\"",
|
||||
&remote.name, &remote.url
|
||||
),
|
||||
);
|
||||
if let Err(e) = repo_handle.remote(&remote.name, &remote.url) {
|
||||
print_repo_error(
|
||||
name,
|
||||
&format!("Repository failed during setting the remotes: {}", e),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
} else {
|
||||
let current_remote = repo_handle.find_remote(&remote.name).unwrap();
|
||||
let current_url = match current_remote.url() {
|
||||
Some(url) => url,
|
||||
None => {
|
||||
print_repo_error(name, &format!("Repository failed during getting of the remote URL for remote \"{}\". This is most likely caused by a non-utf8 remote name", remote.name));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
if remote.url != current_url {
|
||||
if let Err(e) = repo_handle.remote_set_url(&remote.name, &remote.url) {
|
||||
print_repo_error(name, &format!("Repository failed during setting of the remote URL for remote \"{}\": {}", &remote.name, e));
|
||||
continue;
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for current_remote in ¤t_remotes {
|
||||
if !remotes.iter().any(|r| &r.name == current_remote) {
|
||||
print_repo_action(
|
||||
name,
|
||||
&format!("Deleting remote \"{}\"", ¤t_remote,),
|
||||
);
|
||||
if let Err(e) = repo_handle.remote_delete(current_remote) {
|
||||
print_repo_error(
|
||||
name,
|
||||
&format!(
|
||||
"Repository failed during deleting remote \"{}\": {}",
|
||||
¤t_remote, e
|
||||
),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
print_repo_success(&repo.name, "OK");
|
||||
}
|
||||
|
||||
let current_repos = find_repos_without_details(&root_path).unwrap();
|
||||
for repo in current_repos {
|
||||
let name = path_as_string(repo.strip_prefix(&root_path).unwrap());
|
||||
if !repos.iter().any(|r| r.name == name) {
|
||||
print_warning(&format!("Found unmanaged repository: {}", name));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn find_repos_without_details(path: &Path) -> Option<Vec<PathBuf>> {
|
||||
let mut repos: Vec<PathBuf> = Vec::new();
|
||||
|
||||
let git_dir = path.join(".git");
|
||||
if git_dir.exists() {
|
||||
repos.push(path.to_path_buf());
|
||||
} else {
|
||||
match fs::read_dir(path) {
|
||||
Ok(contents) => {
|
||||
for content in contents {
|
||||
match content {
|
||||
Ok(entry) => {
|
||||
let path = entry.path();
|
||||
if path.is_symlink() {
|
||||
continue;
|
||||
}
|
||||
if path.is_dir() {
|
||||
if let Some(mut r) = find_repos_without_details(&path) {
|
||||
repos.append(&mut r);
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(&format!("Error accessing directory: {}", e));
|
||||
continue;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
print_error(&format!("Failed to open \"{}\": {}", &path.display(), &e));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
Some(repos)
|
||||
}
|
||||
|
||||
fn find_repos(root: &Path, at_root: bool) -> Option<Vec<Repo>> {
|
||||
let mut repos: Vec<Repo> = Vec::new();
|
||||
|
||||
for path in find_repos_without_details(root).unwrap() {
|
||||
let repo = match open_repo(&path) {
|
||||
Ok(r) => r,
|
||||
Err(e) => {
|
||||
print_error(&format!("Error opening repo {}: {}", path.display(), e));
|
||||
return None;
|
||||
}
|
||||
};
|
||||
|
||||
let remotes = match repo.remotes() {
|
||||
Ok(remotes) => {
|
||||
let mut results: Vec<Remote> = Vec::new();
|
||||
for remote in remotes.iter() {
|
||||
match remote {
|
||||
Some(remote_name) => {
|
||||
match repo.find_remote(remote_name) {
|
||||
Ok(remote) => {
|
||||
let name = match remote.name() {
|
||||
Some(name) => name.to_string(),
|
||||
None => {
|
||||
print_repo_error(&path_as_string(&path), &format!("Falied getting name of remote \"{}\". This is most likely caused by a non-utf8 remote name", remote_name));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let url = match remote.url() {
|
||||
Some(url) => url.to_string(),
|
||||
None => {
|
||||
print_repo_error(&path_as_string(&path), &format!("Falied getting URL of remote \"{}\". This is most likely caused by a non-utf8 URL", name));
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
let remote_type = match detect_remote_type(&url) {
|
||||
Some(t) => t,
|
||||
None => {
|
||||
print_repo_error(
|
||||
&path_as_string(&path),
|
||||
&format!(
|
||||
"Could not detect remote type of \"{}\"",
|
||||
&url
|
||||
),
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
results.push(Remote {
|
||||
name,
|
||||
url,
|
||||
remote_type,
|
||||
});
|
||||
}
|
||||
Err(e) => {
|
||||
print_repo_error(
|
||||
&path_as_string(&path),
|
||||
&format!("Error getting remote {}: {}", remote_name, e),
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
None => {
|
||||
print_repo_error(&path_as_string(&path), "Error getting remote. This is most likely caused by a non-utf8 remote name");
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
}
|
||||
Some(results)
|
||||
}
|
||||
Err(e) => {
|
||||
print_repo_error(
|
||||
&path_as_string(&path),
|
||||
&format!("Error getting remotes: {}", e),
|
||||
);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
repos.push(Repo {
|
||||
name: match at_root {
|
||||
true => match &root.parent() {
|
||||
Some(parent) => path_as_string(path.strip_prefix(parent).unwrap()),
|
||||
None => {
|
||||
print_error("Getting name of the search root failed. Do you have a git repository in \"/\"?");
|
||||
process::exit(1);
|
||||
},
|
||||
}
|
||||
false => path_as_string(path.strip_prefix(&root).unwrap()),
|
||||
},
|
||||
remotes,
|
||||
});
|
||||
}
|
||||
Some(repos)
|
||||
}
|
||||
|
||||
fn find_in_tree(path: &Path) -> Option<Tree> {
|
||||
let repos: Vec<Repo> = match find_repos(path, true) {
|
||||
Some(vec) => vec,
|
||||
None => Vec::new(),
|
||||
};
|
||||
|
||||
let mut root = path.to_path_buf();
|
||||
let home = env_home();
|
||||
if root.starts_with(&home) {
|
||||
// The tilde is not handled differently, it's just a normal path component for `Path`.
|
||||
// Therefore we can treat it like that during **output**.
|
||||
root = Path::new("~").join(root.strip_prefix(&home).unwrap());
|
||||
}
|
||||
|
||||
Some(Tree {
|
||||
root: Some(root.into_os_string().into_string().unwrap()),
|
||||
repos: Some(repos),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn run() {
|
||||
let opts = cmd::parse();
|
||||
|
||||
match opts.subcmd {
|
||||
cmd::SubCommand::Sync(sync) => {
|
||||
let config = match config::read_config(&sync.config) {
|
||||
Ok(c) => c,
|
||||
Err(e) => {
|
||||
print_error(&e);
|
||||
process::exit(1);
|
||||
}
|
||||
};
|
||||
sync_trees(config);
|
||||
}
|
||||
cmd::SubCommand::Find(find) => {
|
||||
let path = Path::new(&find.path);
|
||||
if !path.exists() {
|
||||
print_error(&format!("Path \"{}\" does not exist", path.display()));
|
||||
process::exit(1);
|
||||
}
|
||||
let path = &path.canonicalize().unwrap();
|
||||
if !path.is_dir() {
|
||||
print_error(&format!("Path \"{}\" is not a directory", path.display()));
|
||||
process::exit(1);
|
||||
}
|
||||
|
||||
let config = Config {
|
||||
trees: vec![find_in_tree(path).unwrap()],
|
||||
};
|
||||
|
||||
let toml = toml::to_string(&config).unwrap();
|
||||
|
||||
print!("{}", toml);
|
||||
}
|
||||
}
|
||||
}
|
||||
5
src/main.rs
Normal file
5
src/main.rs
Normal file
@@ -0,0 +1,5 @@
|
||||
use grm::run;
|
||||
|
||||
fn main() {
|
||||
run();
|
||||
}
|
||||
58
src/output.rs
Normal file
58
src/output.rs
Normal file
@@ -0,0 +1,58 @@
|
||||
use console::{Style, Term};
|
||||
|
||||
pub fn print_repo_error(repo: &str, message: &str) {
|
||||
print_error(&format!("{}: {}", repo, message));
|
||||
}
|
||||
|
||||
pub fn print_error(message: &str) {
|
||||
let stderr = Term::stderr();
|
||||
let mut style = Style::new().red();
|
||||
if stderr.is_term() {
|
||||
style = style.force_styling(true);
|
||||
}
|
||||
stderr
|
||||
.write_line(&format!("[{}] {}", style.apply_to('\u{2718}'), &message))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn print_repo_action(repo: &str, message: &str) {
|
||||
print_action(&format!("{}: {}", repo, message));
|
||||
}
|
||||
|
||||
pub fn print_action(message: &str) {
|
||||
let stderr = Term::stderr();
|
||||
let mut style = Style::new().yellow();
|
||||
if stderr.is_term() {
|
||||
style = style.force_styling(true);
|
||||
}
|
||||
stderr
|
||||
.write_line(&format!("[{}] {}", style.apply_to('\u{2699}'), &message))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn print_warning(message: &str) {
|
||||
let stderr = Term::stderr();
|
||||
let mut style = Style::new().yellow();
|
||||
if stderr.is_term() {
|
||||
style = style.force_styling(true);
|
||||
}
|
||||
stderr
|
||||
.write_line(&format!("[{}] {}", style.apply_to('!'), &message))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn print_repo_success(repo: &str, message: &str) {
|
||||
print_success(&format!("{}: {}", repo, message));
|
||||
}
|
||||
|
||||
pub fn print_success(message: &str) {
|
||||
let stderr = Term::stderr();
|
||||
let mut style = Style::new().green();
|
||||
if stderr.is_term() {
|
||||
style = style.force_styling(true);
|
||||
}
|
||||
|
||||
stderr
|
||||
.write_line(&format!("[{}] {}", style.apply_to('\u{2714}'), &message))
|
||||
.unwrap();
|
||||
}
|
||||
150
src/repo.rs
Normal file
150
src/repo.rs
Normal file
@@ -0,0 +1,150 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::path::Path;
|
||||
|
||||
use git2::{Cred, RemoteCallbacks, Repository};
|
||||
|
||||
use crate::output::*;
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, PartialEq)]
|
||||
#[serde(rename_all = "snake_case")]
|
||||
pub enum RemoteType {
|
||||
Ssh,
|
||||
Https,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Remote {
|
||||
pub name: String,
|
||||
pub url: String,
|
||||
#[serde(alias = "type")]
|
||||
pub remote_type: RemoteType,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(deny_unknown_fields)]
|
||||
pub struct Repo {
|
||||
pub name: String,
|
||||
pub remotes: Option<Vec<Remote>>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn check_ssh_remote() {
|
||||
assert_eq!(
|
||||
detect_remote_type("ssh://git@example.com"),
|
||||
Some(RemoteType::Ssh)
|
||||
);
|
||||
assert_eq!(detect_remote_type("git@example.git"), Some(RemoteType::Ssh));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_https_remote() {
|
||||
assert_eq!(
|
||||
detect_remote_type("https://example.com"),
|
||||
Some(RemoteType::Https)
|
||||
);
|
||||
assert_eq!(
|
||||
detect_remote_type("https://example.com/test.git"),
|
||||
Some(RemoteType::Https)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_invalid_remotes() {
|
||||
assert_eq!(detect_remote_type("https//example.com"), None);
|
||||
assert_eq!(detect_remote_type("https:example.com"), None);
|
||||
assert_eq!(detect_remote_type("ssh//example.com"), None);
|
||||
assert_eq!(detect_remote_type("ssh:example.com"), None);
|
||||
assert_eq!(detect_remote_type("git@example.com"), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn check_unsupported_protocol_http() {
|
||||
detect_remote_type("http://example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn check_unsupported_protocol_git() {
|
||||
detect_remote_type("git://example.com");
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn check_unsupported_protocol_file() {
|
||||
detect_remote_type("file:///");
|
||||
}
|
||||
}
|
||||
|
||||
pub fn detect_remote_type(remote_url: &str) -> Option<RemoteType> {
|
||||
let git_regex = regex::Regex::new(r"^[a-zA-Z]+@.*$").unwrap();
|
||||
if remote_url.starts_with("ssh://") {
|
||||
return Some(RemoteType::Ssh);
|
||||
}
|
||||
if git_regex.is_match(remote_url) && remote_url.ends_with(".git") {
|
||||
return Some(RemoteType::Ssh);
|
||||
}
|
||||
if remote_url.starts_with("https://") {
|
||||
return Some(RemoteType::Https);
|
||||
}
|
||||
if remote_url.starts_with("http://") {
|
||||
unimplemented!("Remotes using HTTP protocol are not supported");
|
||||
}
|
||||
if remote_url.starts_with("git://") {
|
||||
unimplemented!("Remotes using git protocol are not supported");
|
||||
}
|
||||
if remote_url.starts_with("file://") || remote_url.starts_with('/') {
|
||||
unimplemented!("Remotes using local protocol are not supported");
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
pub fn open_repo(path: &Path) -> Result<Repository, Box<dyn std::error::Error>> {
|
||||
match Repository::open(path) {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn init_repo(path: &Path) -> Result<Repository, Box<dyn std::error::Error>> {
|
||||
match Repository::init(path) {
|
||||
Ok(r) => Ok(r),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clone_repo(remote: &Remote, path: &Path) -> Result<(), Box<dyn std::error::Error>> {
|
||||
print_action(&format!(
|
||||
"Cloning into \"{}\" from \"{}\"",
|
||||
&path.display(),
|
||||
&remote.url
|
||||
));
|
||||
match remote.remote_type {
|
||||
RemoteType::Https => match Repository::clone(&remote.url, &path) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
},
|
||||
RemoteType::Ssh => {
|
||||
let mut callbacks = RemoteCallbacks::new();
|
||||
callbacks.credentials(|_url, username_from_url, _allowed_types| {
|
||||
Cred::ssh_key_from_agent(username_from_url.unwrap())
|
||||
});
|
||||
|
||||
let mut fo = git2::FetchOptions::new();
|
||||
fo.remote_callbacks(callbacks);
|
||||
|
||||
let mut builder = git2::build::RepoBuilder::new();
|
||||
builder.fetch_options(fo);
|
||||
|
||||
match builder.clone(&remote.url, path) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(e) => Err(Box::new(e)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user