Initial work on the git source
authorYehuda Katz <wycats@gmail.com>
Thu, 22 May 2014 00:53:05 +0000 (17:53 -0700)
committerYehuda Katz <wycats@gmail.com>
Thu, 22 May 2014 00:53:05 +0000 (17:53 -0700)
Makefile
src/bin/cargo-git-checkout.rs [new file with mode: 0644]
src/cargo/lib.rs
src/cargo/sources/git.rs [new file with mode: 0644]
src/cargo/sources/mod.rs
src/cargo/util/mod.rs
src/cargo/util/process_builder.rs
src/cargo/util/result.rs

index 9db1a0b2d7334888c472ad44d1d4fd5284edcdaa..ea0042cecab1549f225dd58251525ee47131a5be 100644 (file)
--- 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 (file)
index 0000000..dbb1768
--- /dev/null
@@ -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<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))
+}
index 76fd076364e9835cb6e8f5f68978746d0c1eb6a9..93ada512ff9a01f55fd94b8ea078a92c2f9deda4 100644 (file)
@@ -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 (file)
index 0000000..d09ebb2
--- /dev/null
@@ -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<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))
+}
index 4da9789237c64bf81f91e18bdc6ef97af1732e9b..dc14a54baa330cd945d720b53dee28cd37692dc5 100644 (file)
@@ -1 +1,2 @@
 pub mod path;
+pub mod git;
index fd80bfb6dd12f685e7ccabf793eb56e3cdf33060..98d127efe3e5a2dae232e95965928d4e55126522 100644 (file)
@@ -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;
index 54116aba5852c1472d367e9a353162de2684a10f..37b4da7a260012a999de89fe5f1daa8737d86cac 100644 (file)
@@ -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<T: Show>(mut self, arguments: &[T]) -> ProcessBuilder {
+        self.args = arguments.iter().map(|a| format_strbuf!("{}", a)).collect();
         self
     }
 
index a2600fbf3f8e3117fab497e96dd7f75e8c02bafe..f263b7ff18f4c8f22322c6d0feaae217453505c0 100644 (file)
@@ -42,15 +42,24 @@ pub fn process_error(detail: StrBuf, exit: ProcessExit, output: Option<ProcessOu
     }
 }
 
-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),