Skip to content

Commit 4c962c1

Browse files
committed
auto merge of #1074 : alexcrichton/cargo/issue-1069, r=brson
This commit unifies the notion of a "git revision" between a SourceId and the GitSource. This pushes the request of a branch, tag, or revision all the way down into a GitSource so special care can be taken for each case. This primarily was discovered by #1069 where a git tag's id is different from the commit that it points at, and we need to push the knowledge of whether it's a tag or not all the way down to the point where we resolve what revision we want (and perform appropriate operations to find the commit we want). Closes #1069
2 parents 26ac282 + aa256ff commit 4c962c1

File tree

8 files changed

+133
-107
lines changed

8 files changed

+133
-107
lines changed

src/bin/git_checkout.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use cargo::core::MultiShell;
2-
use cargo::core::source::{Source, SourceId};
2+
use cargo::core::source::{Source, SourceId, GitReference};
33
use cargo::sources::git::{GitSource};
44
use cargo::util::{Config, CliResult, CliError, human, ToUrl};
55

@@ -30,7 +30,8 @@ pub fn execute(options: Options, shell: &mut MultiShell) -> CliResult<Option<()>
3030
})
3131
.map_err(|e| CliError::from_boxed(e, 1)));
3232

33-
let source_id = SourceId::for_git(&url, reference.as_slice());
33+
let reference = GitReference::Branch(reference.to_string());
34+
let source_id = SourceId::for_git(&url, reference);
3435

3536
let mut config = try!(Config::new(shell, None, None).map_err(|e| {
3637
CliError::from_boxed(e, 1)

src/cargo/core/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ pub use self::package_id_spec::PackageIdSpec;
66
pub use self::registry::Registry;
77
pub use self::resolver::Resolve;
88
pub use self::shell::{Shell, MultiShell, ShellConfig};
9-
pub use self::source::{Source, SourceId, SourceMap, SourceSet};
9+
pub use self::source::{Source, SourceId, SourceMap, SourceSet, GitReference};
1010
pub use self::summary::Summary;
1111

1212
pub mod source;

src/cargo/core/source.rs

Lines changed: 54 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -42,16 +42,23 @@ pub trait Source: Registry {
4242
fn fingerprint(&self, pkg: &Package) -> CargoResult<String>;
4343
}
4444

45-
#[deriving(RustcEncodable, RustcDecodable, Show, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
45+
#[deriving(Show, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
4646
enum Kind {
4747
/// Kind::Git(<git reference>) represents a git repository
48-
Git(String),
48+
Git(GitReference),
4949
/// represents a local path
5050
Path,
5151
/// represents the central registry
5252
Registry,
5353
}
5454

55+
#[deriving(Show, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
56+
pub enum GitReference {
57+
Tag(String),
58+
Branch(String),
59+
Rev(String),
60+
}
61+
5562
type Error = Box<CargoError + Send>;
5663

5764
/// Unique identifier for a source of packages.
@@ -97,16 +104,22 @@ impl SourceId {
97104
match kind {
98105
"git" => {
99106
let mut url = url.to_url().unwrap();
100-
let mut reference = "master".to_string();
107+
let mut reference = GitReference::Branch("master".to_string());
101108
let pairs = url.query_pairs().unwrap_or(Vec::new());
102109
for &(ref k, ref v) in pairs.iter() {
103-
if k.as_slice() == "ref" {
104-
reference = v.clone();
110+
match k.as_slice() {
111+
// map older 'ref' to branch
112+
"branch" |
113+
"ref" => reference = GitReference::Branch(v.clone()),
114+
115+
"rev" => reference = GitReference::Rev(v.clone()),
116+
"tag" => reference = GitReference::Tag(v.clone()),
117+
_ => {}
105118
}
106119
}
107120
url.query = None;
108121
let precise = mem::replace(&mut url.fragment, None);
109-
SourceId::for_git(&url, reference.as_slice())
122+
SourceId::for_git(&url, reference)
110123
.with_precise(precise)
111124
},
112125
"registry" => {
@@ -128,11 +141,7 @@ impl SourceId {
128141
SourceIdInner {
129142
kind: Kind::Git(ref reference), ref url, ref precise, ..
130143
} => {
131-
let ref_str = if reference.as_slice() != "master" {
132-
format!("?ref={}", reference)
133-
} else {
134-
"".to_string()
135-
};
144+
let ref_str = url_ref(reference);
136145

137146
let precise_str = if precise.is_some() {
138147
format!("#{}", precise.as_ref().unwrap())
@@ -154,8 +163,8 @@ impl SourceId {
154163
Ok(SourceId::new(Kind::Path, url))
155164
}
156165

157-
pub fn for_git(url: &Url, reference: &str) -> SourceId {
158-
SourceId::new(Kind::Git(reference.to_string()), url.clone())
166+
pub fn for_git(url: &Url, reference: GitReference) -> SourceId {
167+
SourceId::new(Kind::Git(reference), url.clone())
159168
}
160169

161170
pub fn for_registry(url: &Url) -> SourceId {
@@ -203,9 +212,9 @@ impl SourceId {
203212
self.inner.precise.as_ref().map(|s| s.as_slice())
204213
}
205214

206-
pub fn git_reference(&self) -> Option<&str> {
215+
pub fn git_reference(&self) -> Option<&GitReference> {
207216
match self.inner.kind {
208-
Kind::Git(ref s) => Some(s.as_slice()),
217+
Kind::Git(ref s) => Some(s),
209218
_ => None,
210219
}
211220
}
@@ -269,10 +278,7 @@ impl Show for SourceId {
269278
SourceIdInner { kind: Kind::Path, ref url, .. } => url.fmt(f),
270279
SourceIdInner { kind: Kind::Git(ref reference), ref url,
271280
ref precise, .. } => {
272-
try!(write!(f, "{}", url));
273-
if reference.as_slice() != "master" {
274-
try!(write!(f, "?ref={}", reference));
275-
}
281+
try!(write!(f, "{}{}", url, url_ref(reference)));
276282

277283
match *precise {
278284
Some(ref s) => {
@@ -319,6 +325,29 @@ impl<S: hash::Writer> hash::Hash<S> for SourceId {
319325
}
320326
}
321327

328+
fn url_ref(r: &GitReference) -> String {
329+
match r.to_ref_string() {
330+
None => "".to_string(),
331+
Some(s) => format!("?{}", s),
332+
}
333+
}
334+
335+
impl GitReference {
336+
pub fn to_ref_string(&self) -> Option<String> {
337+
match *self {
338+
GitReference::Branch(ref s) => {
339+
if s.as_slice() == "master" {
340+
None
341+
} else {
342+
Some(format!("branch={}", s))
343+
}
344+
}
345+
GitReference::Tag(ref s) => Some(format!("tag={}", s)),
346+
GitReference::Rev(ref s) => Some(format!("rev={}", s)),
347+
}
348+
}
349+
}
350+
322351
pub struct SourceMap<'src> {
323352
map: HashMap<SourceId, Box<Source+'src>>
324353
}
@@ -446,20 +475,22 @@ impl<'src> Source for SourceSet<'src> {
446475

447476
#[cfg(test)]
448477
mod tests {
449-
use super::{SourceId, Kind};
478+
use super::{SourceId, Kind, GitReference};
450479
use util::ToUrl;
451480

452481
#[test]
453482
fn github_sources_equal() {
454483
let loc = "https://github.com/foo/bar".to_url().unwrap();
455-
let s1 = SourceId::new(Kind::Git("master".to_string()), loc);
484+
let master = Kind::Git(GitReference::Branch("master".to_string()));
485+
let s1 = SourceId::new(master.clone(), loc);
456486

457487
let loc = "git://github.com/foo/bar".to_url().unwrap();
458-
let s2 = SourceId::new(Kind::Git("master".to_string()), loc.clone());
488+
let s2 = SourceId::new(master, loc.clone());
459489

460490
assert_eq!(s1, s2);
461491

462-
let s3 = SourceId::new(Kind::Git("foo".to_string()), loc);
492+
let foo = Kind::Git(GitReference::Branch("foo".to_string()));
493+
let s3 = SourceId::new(foo, loc);
463494
assert!(s1 != s3);
464495
}
465496
}

src/cargo/sources/git/source.rs

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use std::mem;
55
use url::{mod, Url};
66

77
use core::source::{Source, SourceId};
8+
use core::GitReference;
89
use core::{Package, PackageId, Summary, Registry, Dependency};
910
use util::{CargoResult, Config, to_hex};
1011
use sources::PathSource;
11-
use sources::git::utils::{GitReference, GitRemote, GitRevision};
12+
use sources::git::utils::{GitRemote, GitRevision};
1213

1314
/* TODO: Refactor GitSource to delegate to a PathSource
1415
*/
@@ -39,17 +40,23 @@ impl<'a, 'b> GitSource<'a, 'b> {
3940
let db_path = config.git_db_path()
4041
.join(ident.as_slice());
4142

43+
let reference_path = match *reference {
44+
GitReference::Branch(ref s) |
45+
GitReference::Tag(ref s) |
46+
GitReference::Rev(ref s) => s.to_string(),
47+
};
4248
let checkout_path = config.git_checkout_path()
43-
.join(ident.as_slice()).join(reference.as_slice());
49+
.join(ident)
50+
.join(reference_path);
4451

4552
let reference = match source_id.get_precise() {
46-
Some(s) => s,
47-
None => reference.as_slice(),
53+
Some(s) => GitReference::Rev(s.to_string()),
54+
None => source_id.git_reference().unwrap().clone(),
4855
};
4956

5057
GitSource {
5158
remote: remote,
52-
reference: GitReference::for_str(reference.as_slice()),
59+
reference: reference,
5360
db_path: db_path,
5461
checkout_path: checkout_path,
5562
source_id: source_id.clone(),
@@ -140,9 +147,9 @@ impl<'a, 'b> Show for GitSource<'a, 'b> {
140147
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
141148
try!(write!(f, "git repo at {}", self.remote.get_url()));
142149

143-
match self.reference {
144-
GitReference::Master => Ok(()),
145-
GitReference::Other(ref reference) => write!(f, " ({})", reference)
150+
match self.reference.to_ref_string() {
151+
Some(s) => write!(f, " ({})", s),
152+
None => Ok(())
146153
}
147154
}
148155
}
@@ -157,8 +164,7 @@ impl<'a, 'b> Registry for GitSource<'a, 'b> {
157164

158165
impl<'a, 'b> Source for GitSource<'a, 'b> {
159166
fn update(&mut self) -> CargoResult<()> {
160-
let actual_rev = self.remote.rev_for(&self.db_path,
161-
self.reference.as_slice());
167+
let actual_rev = self.remote.rev_for(&self.db_path, &self.reference);
162168
let should_update = actual_rev.is_err() ||
163169
self.source_id.get_precise().is_none();
164170

@@ -168,7 +174,7 @@ impl<'a, 'b> Source for GitSource<'a, 'b> {
168174

169175
log!(5, "updating git source `{}`", self.remote);
170176
let repo = try!(self.remote.checkout(&self.db_path));
171-
let rev = try!(repo.rev_for(self.reference.as_slice()));
177+
let rev = try!(repo.rev_for(&self.reference));
172178
(repo, rev)
173179
} else {
174180
(try!(self.remote.db_at(&self.db_path)), actual_rev.unwrap())

src/cargo/sources/git/utils.rs

Lines changed: 39 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -5,52 +5,16 @@ use rustc_serialize::{Encodable, Encoder};
55
use url::Url;
66
use git2;
77

8+
use core::GitReference;
89
use util::{CargoResult, ChainError, human, ToUrl, internal, Require};
910

10-
#[deriving(PartialEq,Clone,RustcEncodable)]
11-
pub enum GitReference {
12-
Master,
13-
Other(String)
14-
}
15-
16-
#[deriving(PartialEq,Clone,RustcEncodable)]
17-
pub struct GitRevision(String);
18-
19-
impl GitReference {
20-
pub fn for_str<S: Str>(string: S) -> GitReference {
21-
if string.as_slice() == "master" {
22-
GitReference::Master
23-
} else {
24-
GitReference::Other(string.as_slice().to_string())
25-
}
26-
}
27-
}
28-
29-
impl Str for GitReference {
30-
fn as_slice(&self) -> &str {
31-
match *self {
32-
GitReference::Master => "master",
33-
GitReference::Other(ref string) => string.as_slice()
34-
}
35-
}
36-
}
37-
38-
impl Show for GitReference {
39-
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
40-
self.as_slice().fmt(f)
41-
}
42-
}
43-
44-
impl Str for GitRevision {
45-
fn as_slice(&self) -> &str {
46-
let GitRevision(ref me) = *self;
47-
me.as_slice()
48-
}
49-
}
11+
#[deriving(PartialEq, Clone)]
12+
#[allow(missing_copy_implementations)]
13+
pub struct GitRevision(git2::Oid);
5014

5115
impl Show for GitRevision {
5216
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
53-
self.as_slice().fmt(f)
17+
self.0.fmt(f)
5418
}
5519
}
5620

@@ -138,8 +102,8 @@ impl GitRemote {
138102
&self.url
139103
}
140104

141-
pub fn rev_for<S: Str>(&self, path: &Path, reference: S)
142-
-> CargoResult<GitRevision> {
105+
pub fn rev_for(&self, path: &Path, reference: &GitReference)
106+
-> CargoResult<GitRevision> {
143107
let db = try!(self.db_at(path));
144108
db.rev_for(reference)
145109
}
@@ -215,9 +179,36 @@ impl GitDatabase {
215179
Ok(checkout)
216180
}
217181

218-
pub fn rev_for<S: Str>(&self, reference: S) -> CargoResult<GitRevision> {
219-
let rev = try!(self.repo.revparse_single(reference.as_slice()));
220-
Ok(GitRevision(rev.id().to_string()))
182+
pub fn rev_for(&self, reference: &GitReference) -> CargoResult<GitRevision> {
183+
let id = match *reference {
184+
GitReference::Tag(ref s) => {
185+
try!((|| {
186+
let refname = format!("refs/tags/{}", s);
187+
let id = try!(self.repo.refname_to_id(refname.as_slice()));
188+
let tag = try!(self.repo.find_tag(id));
189+
let obj = try!(tag.peel());
190+
Ok(obj.id())
191+
}).chain_error(|| {
192+
human(format!("failed to find tag `{}`", s))
193+
}))
194+
}
195+
GitReference::Branch(ref s) => {
196+
try!((|| {
197+
let b = try!(self.repo.find_branch(s.as_slice(),
198+
git2::BranchType::Local));
199+
b.get().target().require(|| {
200+
human(format!("branch `{}` did not have a target", s))
201+
})
202+
}).chain_error(|| {
203+
human(format!("failed to find branch `{}`", s))
204+
}))
205+
}
206+
GitReference::Rev(ref s) => {
207+
let obj = try!(self.repo.revparse_single(s.as_slice()));
208+
obj.id()
209+
}
210+
};
211+
Ok(GitRevision(id))
221212
}
222213

223214
pub fn has_ref<S: Str>(&self, reference: S) -> CargoResult<()> {
@@ -249,10 +240,6 @@ impl<'a> GitCheckout<'a> {
249240
Ok(checkout)
250241
}
251242

252-
pub fn get_rev(&self) -> &str {
253-
self.revision.as_slice()
254-
}
255-
256243
fn clone_repo(source: &Path, into: &Path) -> CargoResult<git2::Repository> {
257244
let dirname = into.dir_path();
258245

@@ -293,10 +280,8 @@ impl<'a> GitCheckout<'a> {
293280
}
294281

295282
fn reset(&self) -> CargoResult<()> {
296-
info!("reset {} to {}", self.repo.path().display(),
297-
self.revision.as_slice());
298-
let oid = try!(git2::Oid::from_str(self.revision.as_slice()));
299-
let object = try!(self.repo.find_object(oid, None));
283+
info!("reset {} to {}", self.repo.path().display(), self.revision);
284+
let object = try!(self.repo.find_object(self.revision.0, None));
300285
try!(self.repo.reset(&object, git2::ResetType::Hard, None, None));
301286
Ok(())
302287
}

0 commit comments

Comments
 (0)