Merge pull request #12 from msfjarvis/clap-v3

Switch to clap v3
This commit is contained in:
Harsh Shandilya 2020-11-09 17:51:56 +05:30 committed by GitHub
commit 90cba9587f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 255 additions and 150 deletions

129
Cargo.lock generated
View file

@ -1,14 +1,5 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
[[package]]
name = "ansi_term"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b"
dependencies = [
"winapi",
]
[[package]]
name = "anyhow"
version = "1.0.33"
@ -55,16 +46,34 @@ checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]]
name = "clap"
version = "2.33.3"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "37e58ac78573c40708d45522f0d80fa2f01cc4f9b4e2bf749807255454312002"
checksum = "4bd1061998a501ee7d4b6d449020df3266ca3124b941ec56cf2005c3779ca142"
dependencies = [
"ansi_term",
"atty",
"bitflags",
"clap_derive",
"indexmap",
"lazy_static",
"os_str_bytes",
"strsim",
"termcolor",
"textwrap",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap_derive"
version = "3.0.0-beta.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "370f715b81112975b1b69db93e0b56ea4cd4e5002ac43b2da8474106a54096a1"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
@ -95,6 +104,21 @@ dependencies = [
"walkdir",
]
[[package]]
name = "hashbrown"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d7afe4a420e3fe79967a00898cc1f4db7c8a49a9333a29f8a4bd76a253d5cd04"
[[package]]
name = "heck"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "hermit-abi"
version = "0.1.17"
@ -115,6 +139,16 @@ dependencies = [
"unicode-normalization",
]
[[package]]
name = "indexmap"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "55e2e4c765aa53a0424761bf9f41aa7a6ac1efa87238f59560640e27fca028f2"
dependencies = [
"autocfg",
"hashbrown",
]
[[package]]
name = "jobserver"
version = "0.1.21"
@ -124,6 +158,12 @@ dependencies = [
"libc",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.79"
@ -204,6 +244,12 @@ dependencies = [
"vcpkg",
]
[[package]]
name = "os_str_bytes"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "afb2e1c3ee07430c2cf76151675e583e0f19985fa6efae47d6848a3e2c824f85"
[[package]]
name = "percent-encoding"
version = "2.1.0"
@ -216,6 +262,30 @@ version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3831453b3449ceb48b6d9c7ad7c96d5ea673e9b470a1dc578c2ce6521230884c"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.24"
@ -265,9 +335,9 @@ dependencies = [
[[package]]
name = "strsim"
version = "0.8.0"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
@ -280,11 +350,20 @@ dependencies = [
"unicode-xid",
]
[[package]]
name = "termcolor"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb6bfa289a4d7c5766392812c0a1f4c1ba45afa1ad47803c11e1f407d846d75f"
dependencies = [
"winapi-util",
]
[[package]]
name = "textwrap"
version = "0.11.0"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
checksum = "203008d98caf094106cfaba70acfed15e18ed3ddb7d94e49baec153a2b462789"
dependencies = [
"unicode-width",
]
@ -322,6 +401,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0"
[[package]]
name = "unicode-width"
version = "0.1.8"
@ -351,6 +436,18 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
[[package]]
name = "walkdir"
version = "2.3.1"

View file

@ -14,7 +14,7 @@ readme = "README.md"
[dependencies]
anyhow = "1.0.32"
clap = { version = "2.33.3", default-features = false, features = ["suggestions", "color"] }
clap = "3.0.0-beta.2"
git2 = "0.13.11"
serde = {version = "1.0.116", default-features = false, features = ["derive"] }
serde_derive = "1.0.116"

39
src/cli.rs Normal file
View file

@ -0,0 +1,39 @@
use clap::{crate_authors, crate_version, AppSettings, Clap};
#[derive(Clap)]
#[clap(
version = crate_version!(),
author = crate_authors!(),
setting = AppSettings::ColoredHelp,
setting = AppSettings::DeriveDisplayOrder,
setting = AppSettings::SubcommandRequiredElseHelp,
)]
pub(crate) struct Opts {
#[clap(subcommand)]
pub(crate) subcommand: SubCommand,
}
#[derive(Clap)]
pub(crate) enum SubCommand {
Freeze(Freeze),
Thaw(Thaw),
}
/// recursively find git repos and record their states into a lockfile
#[derive(Clap)]
#[clap(setting = AppSettings::ColoredHelp)]
pub(crate) struct Freeze {
/// directory to search and freeze repos from.
pub(crate) directory: String,
}
/// takes the given
#[derive(Clap)]
#[clap(setting = AppSettings::ColoredHelp)]
pub(crate) struct Thaw {
/// directory to put cloned repos into.
pub(crate) directory: String,
/// the lockfile to restore repositories from.
#[clap(default_value = "gitice.lock")]
pub(crate) lockfile: String,
}

83
src/git.rs Normal file
View file

@ -0,0 +1,83 @@
use crate::model::PersistableRepo;
use git2::Repository;
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
process::Command,
};
use walkdir::WalkDir;
pub(crate) fn freeze_repos(dir: &str) -> anyhow::Result<()> {
let mut repos: HashMap<String, PersistableRepo> = HashMap::new();
for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_dir() {
let path = format!("{}/.git", entry.path().display());
let git_dir = Path::new(&path);
if git_dir.exists() {
let repo = Repository::open(git_dir)?;
if repo.is_empty()? {
continue;
}
let head = repo.head()?;
if let Some(head) = head.name() {
if let Ok(upstream) = repo.branch_upstream_name(head) {
if let Ok(remote) = repo.find_remote(
// This is a rather ugly hack, but not sure how else to get the required name
// doesn't seem to work with the full name such as `refs/remotes/origin/master`
upstream.as_str().unwrap().split('/').collect::<Vec<&str>>()[2],
) {
let path = entry
.path()
.strip_prefix(Path::new(dir))?
.to_str()
.unwrap()
.to_string();
repos.insert(
path,
PersistableRepo {
remote_url: remote.url().unwrap_or("None").to_owned(),
head: head.to_owned(),
},
);
}
}
};
}
};
}
fs::write("gitice.lock", toml::to_string(&repos)?).expect("could not write to lockfile!");
println!(
"Successfully generated lockfile with {} repos",
&repos.len()
);
Ok(())
}
pub(crate) fn thaw_repos(dir: &str, lockfile: &str) -> anyhow::Result<()> {
let lockfile = fs::read_to_string(lockfile)
.unwrap_or_else(|_| panic!("unable to read lockfile from {}", lockfile));
let repos: HashMap<String, PersistableRepo> = toml::from_str(&lockfile)?;
for (name, repo) in repos {
println!("Cloning {} from {}", &name, &repo.remote_url);
let output = Command::new("git")
.args(&[
"clone",
&repo.remote_url,
PathBuf::from(&dir).join(&name).to_str().unwrap(),
])
.output()
.expect("Failed to run `git clone`. Perhaps git is not installed?");
if output.status.success() {
println!("Thawed {} successfully.", name)
} else {
println!("{}", std::str::from_utf8(&output.stderr)?)
}
}
Ok(())
}

View file

@ -1,140 +1,19 @@
use anyhow::anyhow;
use clap::{crate_version, App, AppSettings, Arg};
use git2::Repository;
use serde::{Deserialize, Serialize};
use std::{
collections::HashMap,
fs,
path::{Path, PathBuf},
process::Command,
};
use walkdir::WalkDir;
pub(crate) mod cli;
pub(crate) mod git;
pub(crate) mod model;
#[derive(Debug, Serialize, Deserialize)]
struct PersistableRepo {
pub(crate) remote_url: String,
pub(crate) head: String,
}
use clap::Clap;
use cli::Opts;
use cli::SubCommand;
use git::freeze_repos;
use git::thaw_repos;
fn main() -> anyhow::Result<()> {
let matches = App::new("gitice")
.about("Command-line tool for backing up and restoring multiple Git repositories from a directory")
.version(crate_version!())
.setting(AppSettings::ColoredHelp)
.setting(AppSettings::DeriveDisplayOrder)
.setting(AppSettings::SubcommandRequiredElseHelp)
.subcommand(
App::new("freeze")
.about("Generate a gitice.lock file with all the repositories in the given directory")
.setting(AppSettings::ColoredHelp)
.args(&[Arg::with_name("directory")
.help("Directory to look for Git repos in")
.required(true)
.index(1)]),
)
.subcommand(
App::new("thaw")
.about("Given a gitice.lock and a directory, clones back all the repositories from the lockfile in the directory")
.setting(AppSettings::ColoredHelp)
.args(&[
Arg::with_name("directory")
.help("Directory to restore repositories in")
.required(true)
.index(1),
Arg::with_name("lockfile")
.help("The lockfile to restore repositories from")
.short("l")
.long("lockfile")
.required(false)
.default_value("gitice.lock")
]),
)
.get_matches();
let opts = Opts::parse();
match matches.subcommand() {
("freeze", m) => freeze_repos(m.unwrap().value_of("directory").unwrap())?,
("thaw", m) => {
let m = m.unwrap();
thaw_repos(
m.value_of("directory").unwrap(),
m.value_of("lockfile").unwrap(),
)?
}
(cmd, _) => return Err(anyhow!("unknown subcommand: {}", cmd)),
}
Ok(())
}
fn freeze_repos(dir: &str) -> anyhow::Result<()> {
let mut repos: HashMap<String, PersistableRepo> = HashMap::new();
for entry in WalkDir::new(dir).into_iter().filter_map(|e| e.ok()) {
if entry.file_type().is_dir() {
let path = format!("{}/.git", entry.path().display());
let git_dir = Path::new(&path);
if git_dir.exists() {
let repo = Repository::open(git_dir)?;
if repo.is_empty()? {
continue;
}
let head = repo.head()?;
if let Some(head) = head.name() {
if let Ok(upstream) = repo.branch_upstream_name(head) {
if let Ok(remote) = repo.find_remote(
// This is a rather ugly hack, but not sure how else to get the required name
// doesn't seem to work with the full name such as `refs/remotes/origin/master`
upstream.as_str().unwrap().split('/').collect::<Vec<&str>>()[2],
) {
let path = entry
.path()
.strip_prefix(Path::new(dir))?
.to_str()
.unwrap()
.to_string();
repos.insert(
path,
PersistableRepo {
remote_url: remote.url().unwrap_or("None").to_owned(),
head: head.to_owned(),
},
);
}
}
};
}
};
}
fs::write("gitice.lock", toml::to_string(&repos)?).expect("could not write to lockfile!");
println!(
"Successfully generated lockfile with {} repos",
&repos.len()
);
Ok(())
}
fn thaw_repos(dir: &str, lockfile: &str) -> anyhow::Result<()> {
let lockfile = fs::read_to_string(lockfile)
.unwrap_or_else(|_| panic!("unable to read lockfile from {}", lockfile));
let repos: HashMap<String, PersistableRepo> = toml::from_str(&lockfile)?;
for (name, repo) in repos {
println!("Cloning {} from {}", &name, &repo.remote_url);
let output = Command::new("git")
.args(&[
"clone",
&repo.remote_url,
PathBuf::from(&dir).join(&name).to_str().unwrap(),
])
.output()
.expect("Failed to run `git clone`. Perhaps git is not installed?");
if output.status.success() {
println!("Thawed {} successfully.", name)
} else {
println!("{}", std::str::from_utf8(&output.stderr)?)
}
match opts.subcommand {
SubCommand::Freeze(p) => freeze_repos(&p.directory)?,
SubCommand::Thaw(p) => thaw_repos(&p.directory, &p.lockfile)?,
}
Ok(())

7
src/model.rs Normal file
View file

@ -0,0 +1,7 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub(crate) struct PersistableRepo {
pub(crate) remote_url: String,
pub(crate) head: String,
}