diff --git a/Makefile b/Makefile index 9db1a0b2d..ea0042cec 100644 --- a/Makefile +++ b/Makefile @@ -3,10 +3,11 @@ RUSTC_FLAGS ?= # Link flags to pull in dependencies BINS = cargo \ - cargo-compile \ + cargo-compile \ cargo-read-manifest \ cargo-rustc \ - cargo-verify-project + cargo-verify-project \ + cargo-git-checkout \ SRC = $(shell find src -name '*.rs') diff --git a/src/bin/cargo-git-checkout.rs b/src/bin/cargo-git-checkout.rs new file mode 100644 index 000000000..dbb17689f --- /dev/null +++ b/src/bin/cargo-git-checkout.rs @@ -0,0 +1,34 @@ +#![crate_id="cargo-git-checkout"] + +extern crate cargo; +extern crate serialize; +extern crate hammer; +extern crate url; + +use hammer::FlagConfig; +use cargo::{execute_main_without_stdin,CLIResult,CLIError,ToResult}; +use cargo::core::Package; +use cargo::util::{Require,ToCLI,simple_human}; +use cargo::sources::git::{GitCommand,GitRepo}; +use url::Url; + +#[deriving(Eq,Clone,Decodable)] +struct Options { + directory: StrBuf, + url: StrBuf, + reference: StrBuf +} + +impl FlagConfig for Options {} + +fn main() { + execute_main_without_stdin(execute); +} + +fn execute(options: Options) -> CLIResult> { + let url: Url = try!(from_str(options.url.as_slice()).to_result(|_| + CLIError::new(format!("The URL `{}` you passed was not a valid URL", options.url), None::<&str>, 1))); + + let cmd = GitCommand::new(Path::new(options.directory.clone()), url, options.reference); + cmd.checkout().to_cli(1).map(|repo| Some(repo)) +} diff --git a/src/cargo/lib.rs b/src/cargo/lib.rs index 76fd07636..93ada512f 100644 --- a/src/cargo/lib.rs +++ b/src/cargo/lib.rs @@ -5,6 +5,7 @@ #![feature(macro_rules)] extern crate collections; +extern crate url; extern crate hammer; extern crate serialize; extern crate semver; diff --git a/src/cargo/sources/git.rs b/src/cargo/sources/git.rs new file mode 100644 index 000000000..d09ebb21c --- /dev/null +++ b/src/cargo/sources/git.rs @@ -0,0 +1,103 @@ +use url::Url; +use util::{CargoResult,ProcessBuilder,io_error,human_error,process}; +use std::str; +use std::io::UserDir; +use std::io::fs::mkdir_recursive; +use serialize::{Encodable,Encoder}; + +macro_rules! git( + ($config:expr, $str:expr, $($rest:expr),*) => ( + try!(git_inherit($config, format_strbuf!($str, $($rest),*))) + ); +) + +macro_rules! git_output( + ($config:expr, $str:expr, $($rest:expr),*) => ( + try!(git_output($config, format_strbuf!($str, $($rest),*))) + ); +) + +#[deriving(Eq,Clone)] +struct GitConfig { + path: Path, + uri: Url, + reference: StrBuf +} + +#[deriving(Eq,Clone,Encodable)] +struct EncodableGitConfig { + path: StrBuf, + uri: StrBuf, + reference: StrBuf +} + +impl> Encodable for GitConfig { + fn encode(&self, s: &mut S) -> Result<(), E> { + EncodableGitConfig { + path: format_strbuf!("{}", self.path.display()), + uri: format_strbuf!("{}", self.uri), + reference: self.reference.clone() + }.encode(s) + } +} + +#[deriving(Eq,Clone)] +pub struct GitCommand { + config: GitConfig +} + +#[deriving(Eq,Clone,Encodable)] +pub struct GitRepo { + config: GitConfig, + revision: StrBuf +} + +impl GitCommand { + pub fn new(path: Path, uri: Url, reference: StrBuf) -> GitCommand { + GitCommand { config: GitConfig { path: path, uri: uri, reference: reference } } + } + + pub fn checkout(&self) -> CargoResult { + let config = &self.config; + + if config.path.exists() { + git!(config, "fetch --force --quiet --tags {} refs/heads/*:refs/heads/*", config.uri); + } else { + let dirname = Path::new(config.path.dirname()); + let mut checkout_config = self.config.clone(); + checkout_config.path = dirname; + + try!(mkdir_recursive(&checkout_config.path, UserDir).map_err(|err| + human_error(format_strbuf!("Couldn't recursively create `{}`", checkout_config.path.display()), format_strbuf!("path={}", checkout_config.path.display()), io_error(err)))); + + git!(&checkout_config, "clone {} {} --bare --no-hardlinks --quiet", config.uri, config.path.display()); + } + + Ok(GitRepo { config: config.clone(), revision: try!(rev_for(config)) }) + } +} + +fn rev_for(config: &GitConfig) -> CargoResult { + Ok(git_output!(config, "rev-parse {}", config.reference)) +} + +fn git(config: &GitConfig, str: &str) -> ProcessBuilder { + println!("Executing git {} @ {}", str, config.path.display()); + process("git").args(str.split(' ').collect::>().as_slice()).cwd(config.path.clone()) +} + +fn git_inherit(config: &GitConfig, str: StrBuf) -> CargoResult<()> { + git(config, str.as_slice()).exec().map_err(|err| + human_error(format_strbuf!("Couldn't execute `git {}`: {}", str, err), None::<&str>, err)) +} + +fn git_output(config: &GitConfig, str: StrBuf) -> CargoResult { + let output = try!(git(config, str.as_slice()).exec_with_output().map_err(|err| + human_error(format_strbuf!("Couldn't execute `git {}`", str), None::<&str>, err))); + + Ok(to_str(output.output.as_slice())) +} + +fn to_str(vec: &[u8]) -> StrBuf { + format_strbuf!("{}", str::from_utf8_lossy(vec)) +} diff --git a/src/cargo/sources/mod.rs b/src/cargo/sources/mod.rs index 4da978923..dc14a54ba 100644 --- a/src/cargo/sources/mod.rs +++ b/src/cargo/sources/mod.rs @@ -1 +1,2 @@ pub mod path; +pub mod git; diff --git a/src/cargo/util/mod.rs b/src/cargo/util/mod.rs index fd80bfb6d..98d127efe 100644 --- a/src/cargo/util/mod.rs +++ b/src/cargo/util/mod.rs @@ -1,5 +1,5 @@ pub use self::process_builder::{process,ProcessBuilder}; -pub use self::result::{CargoError,CargoResult,Wrap,Require,ToCLI,other_error,human_error,toml_error,io_error,process_error}; +pub use self::result::{CargoError,CargoResult,Wrap,Require,ToCLI,other_error,human_error,simple_human,toml_error,io_error,process_error}; pub mod graph; pub mod process_builder; diff --git a/src/cargo/util/process_builder.rs b/src/cargo/util/process_builder.rs index 54116aba5..37b4da7a2 100644 --- a/src/cargo/util/process_builder.rs +++ b/src/cargo/util/process_builder.rs @@ -31,8 +31,8 @@ impl Show for ProcessBuilder { static PATH_SEP : &'static str = ":"; impl ProcessBuilder { - pub fn args(mut self, arguments: &[StrBuf]) -> ProcessBuilder { - self.args = Vec::from_slice(arguments); + pub fn args(mut self, arguments: &[T]) -> ProcessBuilder { + self.args = arguments.iter().map(|a| format_strbuf!("{}", a)).collect(); self } diff --git a/src/cargo/util/result.rs b/src/cargo/util/result.rs index a2600fbf3..f263b7ff1 100644 --- a/src/cargo/util/result.rs +++ b/src/cargo/util/result.rs @@ -42,15 +42,24 @@ pub fn process_error(detail: StrBuf, exit: ProcessExit, output: Option CargoError { +pub fn human_error(desc: T, detail: U, cause: CargoError) -> CargoError { CargoError { kind: HumanReadableError, - desc: BoxedDescription(desc), - detail: Some(detail), + desc: BoxedDescription(desc.to_str().to_strbuf()), + detail: Some(detail.to_str().to_strbuf()), cause: Some(box cause) } } +pub fn simple_human(desc: T) -> CargoError { + CargoError { + kind: HumanReadableError, + desc: BoxedDescription(format_strbuf!("{}", desc)), + detail: None, + cause: None + } +} + pub fn toml_error(desc: &'static str, error: toml::Error) -> CargoError { CargoError { kind: TomlError(error),