Update GitSource
authorYehuda Katz <wycats@gmail.com>
Fri, 30 May 2014 00:49:53 +0000 (17:49 -0700)
committerYehuda Katz <wycats@gmail.com>
Fri, 30 May 2014 00:49:53 +0000 (17:49 -0700)
src/bin/cargo-git-checkout.rs
src/cargo/sources/git.rs

index a62d1874a6c916d9e3abadfc0c5ed84b88202ec8..38eabe728b696f159f5f5dbcff0ea8a94acb59ac 100644 (file)
@@ -7,7 +7,7 @@ extern crate url;
 
 use hammer::FlagConfig;
 use cargo::{execute_main_without_stdin,CLIResult,CLIError,ToResult};
-use cargo::sources::git::{GitRemoteRepo,GitRepo};
+use cargo::sources::git::{GitRemote,GitCheckout};
 use url::Url;
 
 #[deriving(Eq,Clone,Decodable)]
@@ -25,18 +25,18 @@ fn main() {
     execute_main_without_stdin(execute);
 }
 
-fn execute(options: Options) -> CLIResult<Option<GitRepo>> {
+fn execute(options: Options) -> CLIResult<Option<GitCheckout>> {
     let Options { database_path, checkout_path, url, reference, verbose } = options;
 
     let url: Url = try!(from_str(url.as_slice()).to_result(|_|
         CLIError::new(format!("The URL `{}` you passed was not a valid URL", url), None::<&str>, 1)));
 
-    let repo = GitRemoteRepo::new(Path::new(database_path), url, reference, verbose);
-    let local = try!(repo.checkout().map_err(|e|
+    let repo = GitRemote::new(url, verbose);
+    let local = try!(repo.checkout(&Path::new(database_path)).map_err(|e|
         CLIError::new(format!("Couldn't check out repository: {}", e), None::<&str>, 1)));
 
-    try!(local.copy_to(&Path::new(checkout_path)).map_err(|e|
+    let checkout = try!(local.copy_to(reference, &Path::new(checkout_path)).map_err(|e|
         CLIError::new(format!("Couldn't copy repository: {}", e), None::<&str>, 1)));
 
-    Ok(Some(local))
+    Ok(Some(checkout))
 }
index fa2a2c794cfe5478cd05a148f70e241403218dab..740b934d1a8f9b74d1219cc21c314238d21a2dba 100644 (file)
@@ -12,41 +12,72 @@ use core::source::Source;
 use core::{NameVer,Package,Summary};
 use ops;
 
+#[deriving(Eq,Clone,Encodable)]
+enum GitReference {
+    Master,
+    Other(String)
+}
+
+impl GitReference {
+    pub fn for_str<S: Str>(string: S) -> GitReference {
+        if string.as_slice() == "master" {
+            Master
+        } else {
+            Other(string.as_slice().to_str())
+        }
+    }
+}
+
+impl Str for GitReference {
+    fn as_slice<'a>(&'a self) -> &'a str {
+        match *self {
+            Master => "master",
+            Other(ref string) => string.as_slice()
+        }
+    }
+}
+
+impl Show for GitReference {
+    fn fmt(&self, f: &mut Formatter) -> fmt::Result {
+        self.as_slice().fmt(f)
+    }
+}
+
 pub struct GitSource {
-    config: GitConfig,
-    dest: Path
+    remote: GitRemote,
+    reference: GitReference,
+    db_path: Path,
+    checkout_path: Path,
+    verbose: bool
 }
 
 impl GitSource {
-    pub fn new(config: GitConfig, dest: Path) -> GitSource {
-        GitSource { config: config, dest: dest }
+    pub fn new(remote: GitRemote, reference: String, db: Path, checkout: Path, verbose: bool) -> GitSource {
+        GitSource { remote: remote, reference: GitReference::for_str(reference), db_path: db, checkout_path: checkout, verbose: verbose }
     }
 }
 
 impl Show for GitSource {
     fn fmt(&self, f: &mut Formatter) -> fmt::Result {
-        try!(write!(f, "git repo at {}", self.config.url));
+        try!(write!(f, "git repo at {}", self.remote.url));
 
-        if self.config.reference.as_slice() != "master" {
-            try!(write!(f, " ({})", self.config.reference));
+        match self.reference {
+            Master => Ok(()),
+            Other(ref reference) => write!(f, " ({})", reference)
         }
-
-        Ok(())
     }
 }
 
 impl Source for GitSource {
     fn update(&self) -> CargoResult<()> {
-        let remote = GitRemoteRepo::from_config(self.config.clone());
-        let repo = try!(remote.checkout());
-
-        try!(repo.copy_to(&self.dest));
+        let repo = try!(self.remote.checkout(&self.db_path));
+        try!(repo.copy_to(self.reference.as_slice(), &self.checkout_path));
 
         Ok(())
     }
 
     fn list(&self) -> CargoResult<Vec<Summary>> {
-        let pkg = try!(read_manifest(&self.dest));
+        let pkg = try!(read_manifest(&self.checkout_path));
         Ok(vec!(pkg.get_summary().clone()))
     }
 
@@ -55,7 +86,7 @@ impl Source for GitSource {
     }
 
     fn get(&self, packages: &[NameVer]) -> CargoResult<Vec<Package>> {
-        let pkg = try!(read_manifest(&self.dest));
+        let pkg = try!(read_manifest(&self.checkout_path));
 
         if packages.iter().any(|nv| pkg.is_for_name_ver(nv)) {
             Ok(vec!(pkg))
@@ -90,129 +121,147 @@ macro_rules! errln(
 )
 
 /**
- * GitConfig represents the information about a git location for code determined from
- * a Cargo manifest, as well as a location to store the git database for a remote
- * repository.
+ * GitRemote represents a remote repository. It gets cloned into a local GitDatabase.
  */
 
 #[deriving(Eq,Clone)]
-pub struct GitConfig {
-    path: Path,
+pub struct GitRemote {
     url: Url,
-    reference: String,
     verbose: bool
 }
 
 #[deriving(Eq,Clone,Encodable)]
-struct EncodableGitConfig {
-    path: String,
-    url: String,
-    reference: String
+struct EncodableGitRemote {
+    url: String
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for GitRemote {
+    fn encode(&self, s: &mut S) -> Result<(), E> {
+        EncodableGitRemote {
+            url: self.url.to_str()
+        }.encode(s)
+    }
 }
 
 /**
- * GitRemoteRepo is responsible for taking a GitConfig and bringing the local database up
- * to date with the remote repository, returning a GitRepo.
- *
- * A GitRemoteRepo has a `reference` in its config, which may not resolve to a valid revision.
- * Its `checkout` method returns a `GitRepo` which is guaranteed to have a resolved
- * revision for the supplied reference.
+ * GitDatabase is a local clone of a remote repository's database. Multiple GitCheckouts
+ * can be cloned from this GitDatabase.
  */
 
 #[deriving(Eq,Clone)]
-pub struct GitRemoteRepo {
-    config: GitConfig
+pub struct GitDatabase {
+    remote: GitRemote,
+    path: Path,
+    verbose: bool
 }
 
-/**
- * GitRepo is a local clone of a remote repository's database. The supplied reference is
- * guaranteed to resolve to a valid `revision`, so all code run from this point forward
- * can assume that the requested code exists.
- */
+#[deriving(Encodable)]
+pub struct EncodableGitDatabase {
+    remote: GitRemote,
+    path: String
+}
 
-#[deriving(Eq,Clone,Encodable)]
-pub struct GitRepo {
-    config: GitConfig,
-    revision: String
+impl<E, S: Encoder<E>> Encodable<S, E> for GitDatabase {
+    fn encode(&self, s: &mut S) -> Result<(), E> {
+        EncodableGitDatabase {
+            remote: self.remote.clone(),
+            path: self.path.display().to_str()
+        }.encode(s)
+    }
 }
 
 /**
- * GitCheckout is a local checkout of a particular revision. A single GitRepo can
- * have multiple GitCheckouts.
+ * GitCheckout is a local checkout of a particular revision. Calling `clone_into` with
+ * a reference will resolve the reference into a revision, and return a CargoError
+ * if no revision for that reference was found.
  */
 
-pub struct GitCheckout<'a> {
+pub struct GitCheckout {
+    database: GitDatabase,
     location: Path,
-    repo: &'a GitRepo
+    reference: GitReference,
+    revision: String,
+    verbose: bool
 }
 
-impl<E, S: Encoder<E>> Encodable<S, E> for GitConfig {
+#[deriving(Encodable)]
+pub struct EncodableGitCheckout {
+    database: GitDatabase,
+    location: String,
+    reference: String,
+    revision: String
+}
+
+impl<E, S: Encoder<E>> Encodable<S, E> for GitCheckout {
     fn encode(&self, s: &mut S) -> Result<(), E> {
-        EncodableGitConfig {
-            path: self.path.display().to_str(),
-            url: self.url.to_str(),
-            reference: self.reference.clone()
+        EncodableGitCheckout {
+            database: self.database.clone(),
+            location: self.location.display().to_str(),
+            reference: self.reference.to_str(),
+            revision: self.revision.to_str()
         }.encode(s)
     }
 }
 
-impl GitRemoteRepo {
-    pub fn new(path: Path, url: Url, reference: String, verbose: bool) -> GitRemoteRepo {
-        GitRemoteRepo { config: GitConfig { path: path, url: url, reference: reference, verbose: verbose } }
-    }
-
-    pub fn from_config(config: GitConfig) -> GitRemoteRepo {
-        GitRemoteRepo { config: config }
-    }
+/**
+ * Implementations
+ */
 
-    pub fn get_cwd<'a>(&'a self) -> &'a Path {
-        &self.config.path
+impl GitRemote {
+    pub fn new(url: Url, verbose: bool) -> GitRemote {
+        GitRemote { url: url, verbose: verbose }
     }
 
-    pub fn checkout(&self) -> CargoResult<GitRepo> {
-        if self.config.path.exists() {
+    pub fn checkout(&self, into: &Path) -> CargoResult<GitDatabase> {
+        if into.exists() {
             // TODO: If the revision we have is a rev, avoid unnecessarily fetching if we have the rev already
-            try!(self.fetch());
+            try!(self.fetch_into(into));
         } else {
-            try!(self.clone());
+            try!(self.clone_into(into));
         }
 
-        Ok(GitRepo { config: self.config.clone(), revision: try!(rev_for(&self.config)) })
+        Ok(GitDatabase { remote: self.clone(), path: into.clone(), verbose: self.verbose })
     }
 
-    fn fetch(&self) -> CargoResult<()> {
-        Ok(git!(self.config.path, self.config.verbose, "fetch --force --quiet --tags {} refs/heads/*:refs/heads/*", self.config.url))
+    fn fetch_into(&self, path: &Path) -> CargoResult<()> {
+        Ok(git!(*path, self.verbose, "fetch --force --quiet --tags {} refs/heads/*:refs/heads/*", self.url))
     }
 
-    fn clone(&self) -> CargoResult<()> {
-        let dirname = Path::new(self.config.path.dirname());
+    fn clone_into(&self, path: &Path) -> CargoResult<()> {
+        let dirname = Path::new(path.dirname());
 
-        try!(mkdir_recursive(&self.config.path, UserDir).map_err(|err|
+        try!(mkdir_recursive(path, UserDir).map_err(|err|
             human_error(format!("Couldn't recursively create `{}`", dirname.display()), format!("path={}", dirname.display()), io_error(err))));
 
-        Ok(git!(dirname, self.config.verbose, "clone {} {} --bare --no-hardlinks --quiet", self.config.url, self.config.path.display()))
+        Ok(git!(dirname, self.verbose, "clone {} {} --bare --no-hardlinks --quiet", self.url, path.display()))
     }
 }
 
-impl GitRepo {
+impl GitDatabase {
     fn get_path<'a>(&'a self) -> &'a Path {
-        &self.config.path
+        &self.path
     }
 
-    pub fn copy_to<'a>(&'a self, dest: &Path) -> CargoResult<GitCheckout<'a>> {
-        let checkout = try!(GitCheckout::clone(dest, self));
+    pub fn copy_to<S: Str>(&self, reference: S, dest: &Path) -> CargoResult<GitCheckout> {
+        let verbose = self.verbose;
+        let checkout = try!(GitCheckout::clone_into(dest, self.clone(), GitReference::for_str(reference.as_slice()), verbose));
 
         try!(checkout.fetch());
-        try!(checkout.reset(self.revision.as_slice()));
         try!(checkout.update_submodules());
 
         Ok(checkout)
     }
+
+    pub fn rev_for<S: Str>(&self, reference: S) -> CargoResult<String> {
+        Ok(git_output!(self.path, self.verbose, "rev-parse {}", reference.as_slice()))
+    }
+
 }
 
-impl<'a> GitCheckout<'a> {
-    fn clone<'a>(into: &Path, repo: &'a GitRepo) -> CargoResult<GitCheckout<'a>> {
-        let checkout = GitCheckout { location: into.clone(), repo: repo };
+impl GitCheckout {
+    fn clone_into(into: &Path, database: GitDatabase, reference: GitReference, verbose: bool) -> CargoResult<GitCheckout> {
+        let revision = try!(database.rev_for(reference.as_slice()));
+        let checkout = GitCheckout { location: into.clone(), database: database, reference: reference, revision: revision, verbose: verbose };
 
         // If the git checkout already exists, we don't need to clone it again
         if !checkout.location.join(".git").exists() {
@@ -223,11 +272,7 @@ impl<'a> GitCheckout<'a> {
     }
 
     fn get_source<'a>(&'a self) -> &'a Path {
-        self.repo.get_path()
-    }
-
-    fn get_verbose(&self) -> bool {
-        self.repo.config.verbose
+        self.database.get_path()
     }
 
     fn clone_repo(&self) -> CargoResult<()> {
@@ -241,34 +286,27 @@ impl<'a> GitCheckout<'a> {
                 human_error(format!("Couldn't rmdir {}", Path::new(&self.location).display()), None::<&str>, io_error(e))));
         }
 
-        git!(dirname, self.get_verbose(), "clone --no-checkout --quiet {} {}", self.get_source().display(), self.location.display());
+        git!(dirname, self.verbose, "clone --no-checkout --quiet {} {}", self.get_source().display(), self.location.display());
         try!(chmod(&self.location, AllPermissions).map_err(io_error));
 
         Ok(())
     }
 
     fn fetch(&self) -> CargoResult<()> {
-        Ok(git!(self.location, self.get_verbose(), "fetch --force --quiet --tags {}", self.get_source().display()))
+        git!(self.location, self.verbose, "fetch --force --quiet --tags {}", self.get_source().display());
+        try!(self.reset(self.revision.as_slice()));
+        Ok(())
     }
 
     fn reset<T: Show>(&self, revision: T) -> CargoResult<()> {
-        Ok(git!(self.location, self.get_verbose(), "reset -q --hard {}", revision))
+        Ok(git!(self.location, self.verbose, "reset -q --hard {}", revision))
     }
 
     fn update_submodules(&self) -> CargoResult<()> {
-        Ok(git!(self.location, self.get_verbose(), "submodule update --init --recursive --quiet"))
+        Ok(git!(self.location, self.verbose, "submodule update --init --recursive --quiet"))
     }
 }
 
-fn rev_for(config: &GitConfig) -> CargoResult<String> {
-    Ok(git_output!(config.path, config.verbose, "rev-parse {}", config.reference))
-}
-
-#[allow(dead_code)]
-fn has_rev<T: Show>(path: &Path, rev: T) -> bool {
-    git_output(path, false, format!("cat-file -e {}", rev)).is_ok()
-}
-
 fn git(path: &Path, verbose: bool, str: &str) -> ProcessBuilder {
     if verbose {
         errln!("Executing git {} @ {}", str, path.display());