Skip to content

Commit

Permalink
feat(forge): Add alias on install (#1315)
Browse files Browse the repository at this point in the history
* Add alias on install

* fix needless borrow
  • Loading branch information
pyk committed Apr 15, 2022
1 parent 0b85a3e commit 761a2aa
Show file tree
Hide file tree
Showing 3 changed files with 103 additions and 23 deletions.
32 changes: 21 additions & 11 deletions cli/src/cmd/forge/install.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,24 @@ use std::{

/// Command to install dependencies
#[derive(Debug, Clone, Parser)]
#[clap(override_usage = "forge install [OPTIONS] [DEPENDENCIES]...
forge install [OPTIONS] <github username>/<github project>@<tag>...
forge install [OPTIONS] <alias>=<github username>/<github project>@<tag>...
forge install [OPTIONS] <https:// git url>...")]
pub struct InstallArgs {
/// The dependencies to install.
///
/// A dependency can be a raw URL, or the path to a GitHub repository.
///
/// Additionally, a ref can be provided by adding @ to the dependency path.
///
/// A ref can be:
/// A ref can be:
/// - A branch: master
/// - A tag: v1.2.3
/// - A commit: 8e8128
///
/// Target installation directory can be addded via `<alias>=` suffix.
/// The dependency will installed to `lib/<alias>`.
dependencies: Vec<Dependency>,
#[clap(flatten)]
opts: DependencyInstallOpts,
Expand Down Expand Up @@ -80,8 +87,9 @@ pub(crate) fn install(
std::fs::create_dir_all(&libs)?;

for dep in dependencies {
let target_dir = if let Some(alias) = &dep.alias { alias } else { &dep.name };
let DependencyInstallOpts { no_git, no_commit, quiet } = opts;
p_println!(!quiet => "Installing {} in {:?}, (url: {}, tag: {:?})", dep.name, &libs.join(&dep.name), dep.url, dep.tag);
p_println!(!quiet => "Installing {} in {:?}, (url: {}, tag: {:?})", dep.name, &libs.join(&target_dir), dep.url, dep.tag);
if no_git {
install_as_folder(&dep, &libs)?;
} else {
Expand All @@ -95,8 +103,9 @@ pub(crate) fn install(

/// installs the dependency as an ordinary folder instead of a submodule
fn install_as_folder(dep: &Dependency, libs: &Path) -> eyre::Result<()> {
let target_dir = if let Some(alias) = &dep.alias { alias } else { &dep.name };
let output = Command::new("git")
.args(&["clone", &dep.url, &dep.name])
.args(&["clone", &dep.url, target_dir])
.current_dir(&libs)
.stdout(Stdio::piped())
.output()?;
Expand All @@ -117,24 +126,25 @@ fn install_as_folder(dep: &Dependency, libs: &Path) -> eyre::Result<()> {
if let Some(ref tag) = dep.tag {
Command::new("git")
.args(&["checkout", tag])
.current_dir(&libs.join(&dep.name))
.current_dir(&libs.join(&target_dir))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
.wait()?;
}

// rm git artifacts
std::fs::remove_dir_all(libs.join(&dep.name).join(".git"))?;
std::fs::remove_dir_all(libs.join(&target_dir).join(".git"))?;

Ok(())
}

/// installs the dependency as new submodule
fn install_as_submodule(dep: &Dependency, libs: &Path, no_commit: bool) -> eyre::Result<()> {
// install the dep
let target_dir = if let Some(alias) = &dep.alias { alias } else { &dep.name };
let output = Command::new("git")
.args(&["submodule", "add", &dep.url, &dep.name])
.args(&["submodule", "add", &dep.url, target_dir])
.current_dir(&libs)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
Expand All @@ -147,7 +157,7 @@ fn install_as_submodule(dep: &Dependency, libs: &Path, no_commit: bool) -> eyre:
} else if stderr.contains("already exists in the index") {
eyre::bail!(
"\"lib/{}\" already exists in the index, you can update it using forge update.",
&dep.name
&target_dir
)
} else if stderr.contains("not a git repository") {
eyre::bail!("\"{}\" is not a git repository", &dep.url)
Expand All @@ -161,7 +171,7 @@ fn install_as_submodule(dep: &Dependency, libs: &Path, no_commit: bool) -> eyre:

// call update on it
Command::new("git")
.args(&["submodule", "update", "--init", "--recursive", &dep.name])
.args(&["submodule", "update", "--init", "--recursive", target_dir])
.current_dir(&libs)
.stdout(Stdio::piped())
.stderr(Stdio::piped())
Expand All @@ -172,7 +182,7 @@ fn install_as_submodule(dep: &Dependency, libs: &Path, no_commit: bool) -> eyre:
let message = if let Some(ref tag) = dep.tag {
Command::new("git")
.args(&["checkout", "--recurse-submodules", tag])
.current_dir(&libs.join(&dep.name))
.current_dir(&libs.join(&target_dir))
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()?
Expand All @@ -181,9 +191,9 @@ fn install_as_submodule(dep: &Dependency, libs: &Path, no_commit: bool) -> eyre:
if !no_commit {
Command::new("git").args(&["add", &libs.display().to_string()]).spawn()?.wait()?;
}
format!("forge install: {}\n\n{}", dep.name, tag)
format!("forge install: {}\n\n{}", target_dir, tag)
} else {
format!("forge install: {}", dep.name)
format!("forge install: {}", target_dir)
};

if !no_commit {
Expand Down
5 changes: 3 additions & 2 deletions cli/src/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,8 +111,9 @@ fn remove(root: impl AsRef<std::path::Path>, dependencies: Vec<Dependency>) -> e
let git_mod_libs = std::path::Path::new(".git/modules/lib");

dependencies.iter().try_for_each(|dep| -> eyre::Result<_> {
let path = libs.join(&dep.name);
let git_mod_path = git_mod_libs.join(&dep.name);
let target_dir = if let Some(alias) = &dep.alias { alias } else { &dep.name };
let path = libs.join(&target_dir);
let git_mod_path = git_mod_libs.join(&target_dir);
println!("Removing {} in {:?}, (url: {}, tag: {:?})", dep.name, path, dep.url, dep.tag);

// remove submodule entry from .git/config
Expand Down
89 changes: 79 additions & 10 deletions cli/src/opts/forge.rs
Original file line number Diff line number Diff line change
Expand Up @@ -239,14 +239,24 @@ pub struct Dependency {
pub url: String,
/// Optional tag corresponding to a Git SHA, tag, or branch.
pub tag: Option<String>,
/// Optional alias of the dependency
pub alias: Option<String>,
}

const GITHUB: &str = "github.com";
const VERSION_SEPARATOR: char = '@';
const ALIAS_SEPARATOR: char = '=';

impl FromStr for Dependency {
type Err = eyre::Error;
fn from_str(dependency: &str) -> Result<Self, Self::Err> {
// everything before "=" should be considered the alias
let (alias, dependency) = if let Some(split) = dependency.split_once(ALIAS_SEPARATOR) {
(Some(String::from(split.0)), split.1)
} else {
(None, dependency)
};

let url_with_version = if let Some(captures) = GH_REPO_PREFIX_REGEX.captures(dependency) {
let brand = captures.get(5).unwrap().as_str();
let tld = captures.get(6).unwrap().as_str();
Expand All @@ -270,7 +280,7 @@ impl FromStr for Dependency {
.to_string();
let tag = split.next().map(ToString::to_string);

Ok(Dependency { name, url, tag })
Ok(Dependency { name, url, tag, alias })
}
}

Expand All @@ -281,36 +291,95 @@ mod tests {
#[test]
fn parses_dependencies() {
[
("gakonst/lootloose", "https://github.com/gakonst/lootloose", None),
("github.com/gakonst/lootloose", "https://github.com/gakonst/lootloose", None),
("https://github.com/gakonst/lootloose", "https://github.com/gakonst/lootloose", None),
("gakonst/lootloose", "https://github.com/gakonst/lootloose", None, None),
("github.com/gakonst/lootloose", "https://github.com/gakonst/lootloose", None, None),
(
"https://github.com/gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
None,
),
(
"git+https://github.com/gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
None,
),
(
"[email protected]:gakonst/lootloose@v1",
"https://github.com/gakonst/lootloose",
Some("v1"),
None,
),
(
"[email protected]:gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
None,
),
(
"https://gitlab.com/gakonst/lootloose",
"https://gitlab.com/gakonst/lootloose",
None,
None,
),
(
"https://github.xyz/gakonst/lootloose",
"https://github.xyz/gakonst/lootloose",
None,
None,
),
(
"gakonst/[email protected]",
"https://github.com/gakonst/lootloose",
Some("0.1.0"),
None,
),
(
"gakonst/lootloose@develop",
"https://github.com/gakonst/lootloose",
Some("develop"),
None,
),
("[email protected]:gakonst/lootloose", "https://github.com/gakonst/lootloose", None),
("https://gitlab.com/gakonst/lootloose", "https://gitlab.com/gakonst/lootloose", None),
("https://github.xyz/gakonst/lootloose", "https://github.xyz/gakonst/lootloose", None),
("gakonst/[email protected]", "https://github.com/gakonst/lootloose", Some("0.1.0")),
("gakonst/lootloose@develop", "https://github.com/gakonst/lootloose", Some("develop")),
(
"gakonst/lootloose@98369d0edc900c71d0ec33a01dfba1d92111deed",
"https://github.com/gakonst/lootloose",
Some("98369d0edc900c71d0ec33a01dfba1d92111deed"),
None,
),
("loot=gakonst/lootloose", "https://github.com/gakonst/lootloose", None, Some("loot")),
(
"loot=github.com/gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
Some("loot"),
),
(
"loot=https://github.com/gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
Some("loot"),
),
(
"loot=git+https://github.com/gakonst/lootloose",
"https://github.com/gakonst/lootloose",
None,
Some("loot"),
),
(
"[email protected]:gakonst/lootloose@v1",
"https://github.com/gakonst/lootloose",
Some("v1"),
Some("loot"),
),
]
.iter()
.for_each(|(input, expected_path, expected_tag)| {
.for_each(|(input, expected_path, expected_tag, expected_alias)| {
let dep = Dependency::from_str(input).unwrap();
assert_eq!(dep.url, expected_path.to_string());
assert_eq!(dep.tag, expected_tag.map(ToString::to_string));
assert_eq!(dep.name, "lootloose");
assert_eq!(dep.alias, expected_alias.map(ToString::to_string));
});
}

Expand Down

0 comments on commit 761a2aa

Please sign in to comment.