--- /dev/null
+#![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<Option<GitRepo>> {
+ 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))
+}
--- /dev/null
+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<E, S: Encoder<E>> Encodable<S, E> 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<GitRepo> {
+ 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<StrBuf> {
+ 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::<Vec<&str>>().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<StrBuf> {
+ 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))
+}
}
}
-pub fn human_error(desc: StrBuf, detail: StrBuf, cause: CargoError) -> CargoError {
+pub fn human_error<T: ToStr, U: ToStr>(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<T: Show>(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),