Skip to content

Commit a66aa0b

Browse files
authored
Merge pull request #10 from thecasualcoder/clone
[Ninan] Add cloen command
2 parents c7a2482 + 7d38b26 commit a66aa0b

File tree

5 files changed

+123
-30
lines changed

5 files changed

+123
-30
lines changed

src/clone.rs

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
use std::path::Path;
2+
3+
use clap::{App, Arg, SubCommand};
4+
use colored::*;
5+
use git2::build::RepoBuilder;
6+
use git2::Error as GitError;
7+
use serde::{Deserialize, Serialize};
8+
9+
use crate::git::GitAction;
10+
use crate::input_args::InputArgs;
11+
12+
#[derive(Debug, Serialize, Deserialize)]
13+
pub struct GitRepo {
14+
#[serde(alias = "remoteURL")]
15+
#[serde(rename = "remoteURL")]
16+
remote_url: String,
17+
#[serde(alias = "localPath")]
18+
#[serde(rename = "localPath")]
19+
local_path: String,
20+
}
21+
22+
pub fn sub_command<'a, 'b>() -> App<'a, 'b> {
23+
SubCommand::with_name("clone")
24+
.arg(Arg::with_name("local_path")
25+
.short("l")
26+
.takes_value(true)
27+
.default_value(".")
28+
.help("path at which to create the local repo. Defaults to '.'"))
29+
.arg(Arg::with_name("repo_url")
30+
.short("r")
31+
.takes_value(true)
32+
.multiple(true)
33+
.help("the remote git repo url"))
34+
}
35+
36+
// gg clone -r url1 -r url2 -l local_root_path
37+
// In addition to arguments passed, also check conf file. If 2 entries conflict from file and passed entry, use the passed entry to resolve the conflict.
38+
// Arguments can have only one local path at which to clone. If user wishes multiple paths, they have to use a config file.
39+
// Todo: test conflicting entries from arguments and file.
40+
pub fn clone(args: InputArgs, mut clone_repos: Vec<GitRepo>) {
41+
let matches = args.get_matches();
42+
43+
let remote_urls: Vec<&str> = matches.values_of("repo_url").expect("failed getting remote_urls from user").collect();
44+
let local_path = matches.value_of("local_path").expect("failed parsing local path from arguments");
45+
46+
let mut remotes_from_args: Vec<GitRepo> = vec![];
47+
for remote in remote_urls {
48+
let repo = GitRepo {
49+
remote_url: remote.to_string(),
50+
local_path: local_path.to_string(),
51+
};
52+
remotes_from_args.push(repo);
53+
}
54+
55+
remotes_from_args.append(&mut clone_repos);
56+
57+
for remote in remotes_from_args {
58+
let mut clone = GitClone { remote_url: remote.remote_url.as_str(), local_path: Path::new(remote.local_path.as_str()) };
59+
clone.git_action().expect(format!("Failed to clone repo {}, ", remote.remote_url).as_str());
60+
}
61+
}
62+
63+
pub struct GitClone<'a> {
64+
pub remote_url: &'a str,
65+
pub local_path: &'a Path,
66+
}
67+
68+
// Todo: Add spinner to show progress.
69+
impl<'a> GitAction for GitClone<'a> {
70+
fn git_action(&mut self) -> Result<(), GitError> {
71+
RepoBuilder::new()
72+
.clone(self.remote_url, self.local_path)?;
73+
println!("{} - {} {} {:#?}", "Remote repo".green(), self.remote_url.blue(), "cloned locally at".green(),
74+
self.local_path.as_os_str());
75+
76+
Ok(())
77+
}
78+
}

src/conf.rs

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,54 @@
11
use std::error::Error;
22
use std::fs;
33
use std::path::Path;
4-
use serde::{Deserialize, Serialize};
54

65
use regex::Regex;
6+
use serde::{Deserialize, Serialize};
77

8+
use crate::clone::GitRepo;
9+
10+
// Todo: This will never be serialized. Try removing Serialize.
811
#[derive(Debug, Serialize, Deserialize)]
912
pub struct GGConf {
1013
#[serde(alias = "skipDirectories")]
1114
#[serde(rename = "skipDirectories")]
1215
filter_list: Vec<String>,
1316
#[serde(skip)]
1417
pub filter_list_regex: Vec<Regex>,
18+
#[serde(alias = "cloneRepos")]
19+
#[serde(rename = "cloneRepos")]
20+
// Todo: Add validations on this field. It should not allow empty key/values.
21+
pub clone_repos: Vec<GitRepo>,
22+
}
23+
24+
impl GGConf {
25+
pub fn default() -> GGConf {
26+
GGConf {
27+
filter_list: vec![],
28+
filter_list_regex: vec![],
29+
clone_repos: vec![],
30+
}
31+
}
1532
}
1633

1734
pub fn read_conf_file(conf_file: &str) -> Result<GGConf, Box<dyn Error>> {
1835
if Path::new(conf_file).exists() {
1936
let file = fs::File::open(conf_file)?;
20-
let config: GGConf = serde_yaml::from_reader(file)?;
21-
let updated_conf = create_filter_list(config)?;
22-
return Ok(updated_conf);
37+
let mut config: GGConf = serde_yaml::from_reader(file)?;
38+
update_conf_file(&mut config)?;
39+
return Ok(config);
2340
}
24-
let default = GGConf { filter_list: vec![], filter_list_regex: vec![] };
25-
let updated_conf = create_filter_list(default)?;
26-
Ok(updated_conf)
41+
let mut default = GGConf::default();
42+
update_conf_file(&mut default)?;
43+
Ok(default)
44+
}
45+
46+
fn update_conf_file(conf: &mut GGConf) -> Result<&mut GGConf, Box<dyn Error>> {
47+
create_filter_list(conf)?;
48+
Ok(conf)
2749
}
2850

29-
fn create_filter_list(conf: GGConf) -> Result<GGConf, Box<dyn Error>> {
51+
fn create_filter_list(conf: &mut GGConf) -> Result<&mut GGConf, Box<dyn Error>> {
3052
let mut filter_list = Vec::new();
3153
let mut filters = conf.filter_list.clone();
3254
let defaults: Vec<String> = [".idea", ".DS_Store"].iter().map(|&s| s.into()).collect();
@@ -39,6 +61,7 @@ fn create_filter_list(conf: GGConf) -> Result<GGConf, Box<dyn Error>> {
3961
filter_list.push(re);
4062
});
4163

42-
let updated_conf = GGConf { filter_list: conf.filter_list, filter_list_regex: filter_list };
43-
Ok(updated_conf)
64+
conf.filter_list = filters;
65+
conf.filter_list_regex = filter_list;
66+
Ok(conf)
4467
}

src/create.rs

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,14 @@
11
use std::{env, process};
22
use std::collections::HashMap;
33
use std::error::Error;
4-
use std::path::Path;
54

65
use clap::{App, Arg, SubCommand};
76
use colored::*;
8-
use git2::build::RepoBuilder;
9-
use git2::Error as GitError;
107
use reqwest::{Client, RequestBuilder};
118

129
use crate::git::GitAction;
1310
use crate::input_args::InputArgs;
11+
use crate::clone::GitClone;
1412

1513
pub fn sub_command<'a, 'b>() -> App<'a, 'b> {
1614
SubCommand::with_name("create")
@@ -117,19 +115,3 @@ fn create_remote(token: String, platform: &str, repo_name: &str) -> Result<Strin
117115
};
118116
remote_repo.create()
119117
}
120-
121-
pub struct GitClone<'a> {
122-
remote_url: &'a str,
123-
local_path: &'a Path,
124-
}
125-
126-
impl<'a> GitAction for GitClone<'a> {
127-
fn git_action(&mut self) -> Result<(), GitError> {
128-
RepoBuilder::new()
129-
.clone(self.remote_url, self.local_path)?;
130-
println!("{} {} {} {:#?}", "Created repo at".green(), self.remote_url.green(), "and cloned locally at".green(),
131-
self.local_path.as_os_str());
132-
133-
Ok(())
134-
}
135-
}

src/input_args.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ pub enum InputCommand {
99
Status,
1010
Create,
1111
Fetch,
12+
Clone,
1213
Error,
1314
}
1415

@@ -18,6 +19,7 @@ impl InputCommand {
1819
InputCommand::Status => "status",
1920
InputCommand::Create => "create",
2021
InputCommand::Fetch => "fetch",
22+
InputCommand::Clone => "clone",
2123
_ => "unknown command"
2224
}
2325
}
@@ -47,6 +49,11 @@ impl<'a> InputArgs<'a> {
4749
input_command: InputCommand::Fetch,
4850
arg_matches: matches.to_owned(),
4951
}
52+
} else if subcommand_name == InputCommand::Clone.as_str() {
53+
InputArgs {
54+
input_command: InputCommand::Clone,
55+
arg_matches: matches.to_owned(),
56+
}
5057
} else {
5158
InputArgs {
5259
input_command: InputCommand::Error,

src/main.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ mod fetch;
1818
mod git;
1919
mod dir;
2020
mod conf;
21+
mod clone;
2122

2223
fn main() {
2324
let app = App::new("Git Governance")
@@ -30,7 +31,8 @@ fn main() {
3031
.help("config file to use. Defaults to .ggConf"))
3132
.subcommand(status::sub_command())
3233
.subcommand(create::sub_command())
33-
.subcommand(fetch::sub_command());
34+
.subcommand(fetch::sub_command())
35+
.subcommand(clone::sub_command());
3436

3537

3638
let global_matches = app.get_matches();
@@ -45,6 +47,7 @@ fn main() {
4547
match args.input_command() {
4648
input_args::InputCommand::Status => status::status(args, conf.filter_list_regex),
4749
input_args::InputCommand::Create => create::create(args),
50+
input_args::InputCommand::Clone => clone::clone(args, conf.clone_repos),
4851
input_args::InputCommand::Fetch => fetch::fetch(args, conf.filter_list_regex),
4952
input_args::InputCommand::Error => {}
5053
}

0 commit comments

Comments
 (0)