Revert "Use libgit2 for driving git instead of the CLI"

This reverts commit 3cd82ed7a1.

Conflicts:
	src/cargo/sources/git/utils.rs
This commit is contained in:
Alex Crichton 2014-08-17 00:10:23 -07:00
parent b0516e0fe9
commit 04440bb035
11 changed files with 165 additions and 207 deletions

22
Cargo.lock generated
View file

@ -4,7 +4,6 @@ version = "0.0.1-pre"
dependencies = [
"docopt 0.6.0 (git+https://github.com/burntsushi/docopt.rs#fd2377d1c36b2671136cd36566aad5d54c2fb17e)",
"docopt_macros 0.6.0 (git+https://github.com/burntsushi/docopt.rs#fd2377d1c36b2671136cd36566aad5d54c2fb17e)",
"git2 0.0.1 (git+https://github.com/alexcrichton/git2-rs#66ddeb423f5534817ec254b54eaeb50e506f87ec)",
"hamcrest 0.1.0 (git+https://github.com/carllerche/hamcrest-rust.git#f0fd1546b0a7a278a12658ab8602b5c827cc3a42)",
"semver 0.0.1 (git+https://github.com/rust-lang/semver#e17191f51d543529a6f07e6731802b77977fcef8)",
"toml 0.1.0 (git+https://github.com/alexcrichton/toml-rs#e7c7bba846fea549fe4e93de4c9d21e851a9882f)",
@ -29,32 +28,11 @@ name = "encoding"
version = "0.1.0"
source = "git+https://github.com/lifthrasiir/rust-encoding#b82ad2104b2d079620bd227fb9328b2ff8c20ca9"
[[package]]
name = "git2"
version = "0.0.1"
source = "git+https://github.com/alexcrichton/git2-rs#66ddeb423f5534817ec254b54eaeb50e506f87ec"
dependencies = [
"libgit2 0.0.1 (git+https://github.com/alexcrichton/git2-rs#66ddeb423f5534817ec254b54eaeb50e506f87ec)",
]
[[package]]
name = "hamcrest"
version = "0.1.0"
source = "git+https://github.com/carllerche/hamcrest-rust.git#f0fd1546b0a7a278a12658ab8602b5c827cc3a42"
[[package]]
name = "libgit2"
version = "0.0.1"
source = "git+https://github.com/alexcrichton/git2-rs#66ddeb423f5534817ec254b54eaeb50e506f87ec"
dependencies = [
"link-config 0.0.1 (git+https://github.com/alexcrichton/link-config#f08103ea7d2e2d3369c2c5e66b0220c8d16b92c9)",
]
[[package]]
name = "link-config"
version = "0.0.1"
source = "git+https://github.com/alexcrichton/link-config#f08103ea7d2e2d3369c2c5e66b0220c8d16b92c9"
[[package]]
name = "semver"
version = "0.0.1"

View file

@ -19,7 +19,7 @@ git = "https://github.com/burntsushi/docopt.rs"
[dependencies.toml]
git = "https://github.com/alexcrichton/toml-rs"
[dev-dependencies.hamcrest]
[dependencies.hamcrest]
git = "https://github.com/carllerche/hamcrest-rust.git"
[dependencies.url]
@ -28,9 +28,6 @@ git = "https://github.com/servo/rust-url"
[dependencies.semver]
git = "https://github.com/rust-lang/semver"
[dependencies.git2]
git = "https://github.com/alexcrichton/git2-rs"
[[bin]]
name = "cargo"
test = false

2
configure vendored
View file

@ -262,8 +262,6 @@ need_cmd date
need_cmd tr
need_cmd sed
need_cmd file
need_cmd cmake
need_cmd pkg-config
CFG_SRC_DIR="$(cd $(dirname $0) && pwd)/"
CFG_BUILD_DIR="$(pwd)/"

View file

@ -2,8 +2,7 @@ use std::collections::HashMap;
use std::fmt;
use serialize::{Encodable, Encoder, Decodable, Decoder};
use util::profile;
use util::graph::{Nodes, Edges};
use util::graph::{Nodes,Edges};
use core::{
Dependency,
@ -239,7 +238,6 @@ impl<'a, R: Registry> Context<'a, R> {
pub fn resolve<R: Registry>(root: &PackageId, deps: &[Dependency],
registry: &mut R) -> CargoResult<Resolve> {
log!(5, "resolve; deps={}", deps);
let _p = profile::start(format!("resolving: {}", root));
let mut context = Context::new(registry, root.clone());
try!(resolve_deps(root, deps, &mut context));

View file

@ -212,7 +212,9 @@ impl SourceId {
// Pass absolute path
pub fn for_path(path: &Path) -> CargoResult<SourceId> {
let url = try!(path.to_url().map_err(human));
let url = try!(Url::from_file_path(path).map_err(|()| {
human(format!("not a valid path for a URL: {}", path.display()))
}));
Ok(SourceId::new(PathKind, url))
}

View file

@ -18,7 +18,6 @@ extern crate url;
#[phase(plugin, link)] extern crate log;
extern crate docopt;
extern crate git2;
extern crate toml;
#[cfg(test)] extern crate hamcrest;

View file

@ -2,11 +2,13 @@ use std::os;
use std::io;
use std::io::{fs, File};
use git2::{Repository, Config};
use util::{CargoResult, human, ChainError};
use util::{CargoResult, human, ChainError, process};
use core::shell::MultiShell;
macro_rules! git( ($($a:expr),*) => ({
process("git") $(.arg($a))* .exec_with_output()
}) )
pub struct NewOptions<'a> {
pub git: bool,
pub bin: bool,
@ -29,7 +31,7 @@ pub fn new(opts: NewOptions, _shell: &mut MultiShell) -> CargoResult<()> {
fn mk(path: &Path, name: &str, opts: &NewOptions) -> CargoResult<()> {
if opts.git {
try!(Repository::init(path));
try!(git!("init", path));
let mut gitignore = "/target\n".to_string();
if !opts.bin {
gitignore.push_str("/Cargo.lock\n");
@ -68,17 +70,19 @@ fn it_works() {
}
fn discover_author() -> CargoResult<String> {
let git_config = Config::open_default().ok();
let git_config = git_config.as_ref();
let name = git_config.and_then(|g| g.get_str("user.name").ok())
.map(|s| s.to_string())
.or_else(|| os::getenv("USER"));
let name = match name {
Some(name) => name,
None => return Err(human("could not determine the current user, \
please set $USER"))
let name = match git!("config", "user.name") {
Ok(out) => String::from_utf8_lossy(out.output.as_slice()).into_string(),
Err(..) => match os::getenv("USER") {
Some(user) => user,
None => return Err(human("could not determine the current user, \
please set $USER"))
}
};
let email = match git!("config", "user.email") {
Ok(out) => Some(String::from_utf8_lossy(out.output.as_slice()).into_string()),
Err(..) => None,
};
let email = git_config.and_then(|g| g.get_str("user.email").ok());
let name = name.as_slice().trim().to_string();
let email = email.map(|s| s.as_slice().trim().to_string());

View file

@ -171,7 +171,7 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
let rev = try!(repo.rev_for(self.reference.as_slice()));
(repo, rev)
} else {
(try!(self.remote.db_at(&self.db_path)), actual_rev.unwrap())
(self.remote.db_at(&self.db_path), actual_rev.unwrap())
};
try!(repo.copy_to(actual_rev.clone(), &self.checkout_path));

View file

@ -4,9 +4,8 @@ use std::io::{UserDir};
use std::io::fs::{mkdir_recursive,rmdir_recursive};
use serialize::{Encodable,Encoder};
use url::Url;
use git2;
use util::{CargoResult, ChainError, human, ToUrl, internal, Require};
use util::{CargoResult, ChainError, ProcessBuilder, process, human};
#[deriving(PartialEq,Clone,Encodable)]
pub enum GitReference {
@ -55,6 +54,22 @@ impl Show for GitRevision {
}
}
macro_rules! git(
($config:expr, $($arg:expr),+) => (
try!(git_inherit(&$config, process("git")$(.arg($arg))*))
)
)
macro_rules! git_output(
($config:expr, $($arg:expr),*) => ({
try!(git_output(&$config, process("git")$(.arg($arg))*))
})
)
macro_rules! errln(
($($arg:tt)*) => (let _ = writeln!(::std::io::stdio::stderr(), $($arg)*))
)
/// GitRemote represents a remote repository. It gets cloned into a local
/// GitDatabase.
#[deriving(PartialEq,Clone,Show)]
@ -77,10 +92,10 @@ impl<E, S: Encoder<E>> Encodable<S, E> for GitRemote {
/// GitDatabase is a local clone of a remote repository's database. Multiple
/// GitCheckouts can be cloned from this GitDatabase.
#[deriving(PartialEq,Clone)]
pub struct GitDatabase {
remote: GitRemote,
path: Path,
repo: git2::Repository,
}
#[deriving(Encodable)]
@ -101,29 +116,25 @@ impl<E, S: Encoder<E>> Encodable<S, E> for GitDatabase {
/// 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> {
database: &'a GitDatabase,
pub struct GitCheckout {
database: GitDatabase,
location: Path,
revision: GitRevision,
repo: git2::Repository,
}
#[deriving(Encodable)]
pub struct EncodableGitCheckout {
database: EncodableGitDatabase,
database: GitDatabase,
location: String,
revision: String,
}
impl<'a, E, S: Encoder<E>> Encodable<S, E> for GitCheckout<'a> {
impl<E, S: Encoder<E>> Encodable<S, E> for GitCheckout {
fn encode(&self, s: &mut S) -> Result<(), E> {
EncodableGitCheckout {
database: self.database.clone(),
location: self.location.display().to_string(),
revision: self.revision.to_string(),
database: EncodableGitDatabase {
remote: self.database.remote.clone(),
path: self.database.path.display().to_string(),
},
revision: self.revision.to_string()
}.encode(s)
}
}
@ -141,53 +152,50 @@ impl GitRemote {
pub fn rev_for<S: Str>(&self, path: &Path, reference: S)
-> CargoResult<GitRevision> {
let db = try!(self.db_at(path));
db.rev_for(reference)
// We simultaneously want to transform the reference into a resolved
// revision as well as verify that the reference itself is inside the
// repository. Sadly for a 40-character SHA1 the call to `rev-parse`
// will *always* return the same string with a 0 exit status, regardless
// of whether it's present in the database.
//
// Later versions of git introduced a syntax for this query via
// `$sha1^{object}`, but older versions of git do not support this. To
// get around this limitation, we chop 40-character sha revisions to 39
// characters to get an error'd exit status if the revision is indeed
// not present.
let mut reference = reference.as_slice();
if reference.len() == 40 {
reference = reference.slice_to(39);
}
Ok(GitRevision(git_output!(*path, "rev-parse", reference)))
}
pub fn checkout(&self, into: &Path) -> CargoResult<GitDatabase> {
let repo = if into.exists() {
let r = try!(git2::Repository::open(into));
try!(self.fetch_into(&r).chain_error(|| {
internal(format!("failed to fetch into {}", into.display()))
}));
r
if into.exists() {
try!(self.fetch_into(into));
} else {
try!(self.clone_into(into).chain_error(|| {
internal(format!("failed to clone into: {}", into.display()))
}))
};
try!(self.clone_into(into));
}
Ok(GitDatabase { remote: self.clone(), path: into.clone(), repo: repo })
Ok(GitDatabase { remote: self.clone(), path: into.clone() })
}
pub fn db_at(&self, db_path: &Path) -> CargoResult<GitDatabase> {
let repo = try!(git2::Repository::open(db_path));
Ok(GitDatabase {
remote: self.clone(),
path: db_path.clone(),
repo: repo,
})
pub fn db_at(&self, db_path: &Path) -> GitDatabase {
GitDatabase { remote: self.clone(), path: db_path.clone() }
}
fn fetch_into(&self, dst: &git2::Repository) -> CargoResult<()> {
let url = self.url.to_string();
let refspec = "refs/heads/*:refs/heads/*";
let mut remote = try!(dst.remote_create_anonymous(url.as_slice(),
refspec));
try!(remote.add_fetch("refs/tags/*:refs/tags/*"));
let sig = try!(git2::Signature::default(dst));
try!(remote.fetch(&sig, None));
Ok(())
fn fetch_into(&self, path: &Path) -> CargoResult<()> {
Ok(git!(*path, "fetch", "--force", "--quiet", "--tags",
self.url.to_string(), "refs/heads/*:refs/heads/*"))
}
fn clone_into(&self, dst: &Path) -> CargoResult<git2::Repository> {
let url = self.url.to_string();
try!(mkdir_recursive(dst, UserDir));
let repo = try!(git2::build::RepoBuilder::new().bare(true)
.hardlinks(false)
.clone(url.as_slice(), dst));
Ok(repo)
fn clone_into(&self, path: &Path) -> CargoResult<()> {
let dirname = Path::new(path.dirname());
try!(mkdir_recursive(path, UserDir));
Ok(git!(dirname, "clone", self.url.to_string(), path, "--bare",
"--no-hardlinks", "--quiet"))
}
}
@ -198,7 +206,8 @@ impl GitDatabase {
pub fn copy_to(&self, rev: GitRevision, dest: &Path)
-> CargoResult<GitCheckout> {
let checkout = try!(GitCheckout::clone_into(dest, self, rev.clone()));
let checkout = try!(GitCheckout::clone_into(dest, self.clone(),
rev.clone()));
match self.remote.rev_for(dest, "HEAD") {
Ok(ref head) if rev == *head => {}
@ -211,133 +220,121 @@ impl GitDatabase {
}
pub fn rev_for<S: Str>(&self, reference: S) -> CargoResult<GitRevision> {
let rev = try!(self.repo.revparse_single(reference.as_slice()));
Ok(GitRevision(rev.id().to_string()))
self.remote.rev_for(&self.path, reference)
}
pub fn has_ref<S: Str>(&self, reference: S) -> CargoResult<()> {
try!(self.repo.revparse_single(reference.as_slice()));
git_output!(self.path, "rev-parse", "--verify", reference.as_slice());
Ok(())
}
}
impl<'a> GitCheckout<'a> {
fn clone_into<'a>(into: &Path, database: &'a GitDatabase,
revision: GitRevision) -> CargoResult<GitCheckout<'a>> {
// If the git checkout already exists, we don't need to clone it again
let repo = match git2::Repository::open(into) {
Ok(repo) => repo,
Err(..) => {
try!(mkdir_recursive(&into.dir_path(), UserDir));
try!(GitCheckout::clone_repo(database.get_path(), into))
}
};
Ok(GitCheckout {
impl GitCheckout {
fn clone_into(into: &Path, database: GitDatabase,
revision: GitRevision) -> CargoResult<GitCheckout> {
let checkout = GitCheckout {
location: into.clone(),
database: database,
revision: revision,
repo: repo,
})
};
// If the git checkout already exists, we don't need to clone it again
if !checkout.location.join(".git").exists() {
try!(checkout.clone_repo());
}
Ok(checkout)
}
fn get_source(&self) -> &Path {
self.database.get_path()
}
pub fn get_rev(&self) -> &str {
self.revision.as_slice()
}
fn clone_repo(source: &Path, into: &Path) -> CargoResult<git2::Repository> {
let dirname = into.dir_path();
fn clone_repo(&self) -> CargoResult<()> {
let dirname = Path::new(self.location.dirname());
try!(mkdir_recursive(&dirname, UserDir).chain_error(|| {
human(format!("Couldn't mkdir {}", dirname.display()))
human(format!("Couldn't mkdir {}",
Path::new(self.location.dirname()).display()))
}));
if into.exists() {
try!(rmdir_recursive(into).chain_error(|| {
human(format!("Couldn't rmdir {}", into.display()))
if self.location.exists() {
try!(rmdir_recursive(&self.location).chain_error(|| {
human(format!("Couldn't rmdir {}",
Path::new(&self.location).display()))
}));
}
let url = try!(source.to_url().map_err(human));
let url = url.to_string();
let repo = try!(git2::Repository::clone(url.as_slice(),
into).chain_error(|| {
internal(format!("failed to clone {} into {}", source.display(),
into.display()))
}));
Ok(repo)
git!(dirname, "clone", "--no-checkout", "--quiet",
self.get_source(), &self.location);
try!(self.reset());
Ok(())
}
fn fetch(&self) -> CargoResult<()> {
info!("fetch {}", self.repo.path().display());
let mut remote = try!(self.repo.remote_load("origin"));
try!(remote.add_fetch("refs/tags/*:refs/tags/*"));
let sig = try!(git2::Signature::default(&self.repo));
try!(remote.fetch(&sig, None));
// In git 1.8, apparently --tags explicitly *only* fetches tags, it does
// not fetch anything else. In git 1.9, however, git apparently fetches
// everything when --tags is passed.
//
// This means that if we want to fetch everything we need to execute
// both with and without --tags on 1.8 (apparently), and only with
// --tags on 1.9. For simplicity, we execute with and without --tags for
// all gits.
//
// FIXME: This is suspicious. I have been informed that, for example,
// bundler does not do this, yet bundler appears to work!
//
// And to continue the fun, git before 1.7.3 had the fun bug that if a
// branch was tracking a remote, then `git fetch $url` doesn't work!
//
// For details, see
// https://www.kernel.org/pub/software/scm/git/docs/RelNotes-1.7.3.txt
//
// In this case we just use `origin` here instead of the database path.
git!(self.location, "fetch", "--force", "--quiet", "origin");
git!(self.location, "fetch", "--force", "--quiet", "--tags", "origin");
try!(self.reset());
Ok(())
}
fn reset(&self) -> CargoResult<()> {
info!("reset {} to {}", self.repo.path().display(),
self.revision.as_slice());
let sig = try!(git2::Signature::default(&self.repo));
let oid = try!(git2::Oid::from_str(self.revision.as_slice()));
let object = try!(git2::Object::lookup(&self.repo, oid, None));
try!(self.repo.reset(&object, git2::Hard, &sig, None));
Ok(())
Ok(git!(self.location, "reset", "-q", "--hard",
self.revision.as_slice()))
}
fn update_submodules(&self) -> CargoResult<()> {
let sig = try!(git2::Signature::default(&self.repo));
return update_submodules(&self.repo, &sig);
fn update_submodules(repo: &git2::Repository,
sig: &git2::Signature) -> CargoResult<()> {
info!("update submodules for: {}", repo.path().display());
for mut child in try!(repo.submodules()).move_iter() {
try!(child.init(false));
// A submodule which is listed in .gitmodules but not actually
// checked out will not have a head id, so we should ignore it.
let head = match child.head_id() {
Some(head) => head,
None => continue,
};
// If the submodule hasn't been checked out yet, we need to
// clone it. If it has been checked out and the head is the same
// as the submodule's head, then we can bail out and go to the
// next submodule.
let repo = match child.open() {
Ok(repo) => {
if child.head_id() == try!(repo.head()).target() {
continue
}
repo
}
Err(..) => {
let path = repo.path().dir_path().join(child.path());
let url = try!(child.url().require(|| {
internal("invalid submodule url")
}));
try!(git2::Repository::clone(url, &path))
}
};
// Fetch data from origin and reset to the head commit
let url = try!(child.url().require(|| {
internal("repo with non-utf8 url")
}));
let refspec = "refs/heads/*:refs/heads/*";
let mut remote = try!(repo.remote_create_anonymous(url, refspec));
try!(remote.fetch(sig, None));
let obj = try!(git2::Object::lookup(&repo, head, None));
try!(repo.reset(&obj, git2::Hard, sig, None));
try!(update_submodules(&repo, sig));
}
Ok(())
}
Ok(git!(self.location, "submodule", "update", "--init",
"--recursive", "--quiet"))
}
}
fn git(path: &Path, cmd: ProcessBuilder) -> ProcessBuilder {
debug!("Executing {} @ {}", cmd, path.display());
cmd.cwd(path.clone())
}
fn git_inherit(path: &Path, cmd: ProcessBuilder) -> CargoResult<()> {
let cmd = git(path, cmd);
cmd.exec().chain_error(|| {
human(format!("Executing {} failed", cmd))
})
}
fn git_output(path: &Path, cmd: ProcessBuilder) -> CargoResult<String> {
let cmd = git(path, cmd);
let output = try!(cmd.exec_with_output().chain_error(||
human(format!("Executing {} failed", cmd))));
Ok(to_str(output.output.as_slice()).as_slice().trim_right().to_string())
}
fn to_str(vec: &[u8]) -> String {
String::from_utf8_lossy(vec).into_string()
}

View file

@ -7,7 +7,6 @@ use std::str;
use docopt;
use toml::Error as TomlError;
use url;
use git2;
pub trait CargoError: Send {
fn description(&self) -> String;
@ -295,12 +294,6 @@ impl CargoError for url::ParseError {
from_error!(url::ParseError)
impl CargoError for git2::Error {
fn description(&self) -> String { self.to_string() }
}
from_error!(git2::Error)
impl CliError {
pub fn new<S: Str>(error: S, code: uint) -> CliError {
let error = human(error.as_slice().to_string());

View file

@ -25,14 +25,6 @@ impl<'a> ToUrl for &'a str {
}
}
impl<'a> ToUrl for &'a Path {
fn to_url(self) -> Result<Url, String> {
Url::from_file_path(self).map_err(|()| {
format!("invalid path url `{}`", self.display())
})
}
}
fn mapper(s: &str) -> url::SchemeType {
match s {
"git" => url::RelativeScheme("9418"),