From ba5639f364c688c6d8b67efb4e7229e247a90e07 Mon Sep 17 00:00:00 2001 From: Yehuda Katz Date: Thu, 29 May 2014 17:49:53 -0700 Subject: [PATCH] Update GitSource --- src/bin/cargo-git-checkout.rs | 12 +- src/cargo/sources/git.rs | 234 ++++++++++++++++++++-------------- 2 files changed, 142 insertions(+), 104 deletions(-) diff --git a/src/bin/cargo-git-checkout.rs b/src/bin/cargo-git-checkout.rs index a62d1874a..38eabe728 100644 --- a/src/bin/cargo-git-checkout.rs +++ b/src/bin/cargo-git-checkout.rs @@ -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> { +fn execute(options: Options) -> CLIResult> { 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)) } diff --git a/src/cargo/sources/git.rs b/src/cargo/sources/git.rs index fa2a2c794..740b934d1 100644 --- a/src/cargo/sources/git.rs +++ b/src/cargo/sources/git.rs @@ -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(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> { - 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> { - 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> Encodable 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> Encodable 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> Encodable for GitConfig { +#[deriving(Encodable)] +pub struct EncodableGitCheckout { + database: GitDatabase, + location: String, + reference: String, + revision: String +} + +impl> Encodable 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 { - if self.config.path.exists() { + pub fn checkout(&self, into: &Path) -> CargoResult { + 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> { - let checkout = try!(GitCheckout::clone(dest, self)); + pub fn copy_to(&self, reference: S, dest: &Path) -> CargoResult { + 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(&self, reference: S) -> CargoResult { + 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> { - let checkout = GitCheckout { location: into.clone(), repo: repo }; +impl GitCheckout { + fn clone_into(into: &Path, database: GitDatabase, reference: GitReference, verbose: bool) -> CargoResult { + 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(&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 { - Ok(git_output!(config.path, config.verbose, "rev-parse {}", config.reference)) -} - -#[allow(dead_code)] -fn has_rev(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()); -- 2.30.2