Avoid refreshing Git repo twice (#350)

This was a bug in the Git code (that I wrote, not from Cargo) -- when we
`precise` the reference, we should store the resolved commit.
This commit is contained in:
Charlie Marsh 2023-11-06 18:52:15 -08:00 committed by GitHub
parent 243549876c
commit 620afc3caf
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 42 additions and 21 deletions

View file

@ -32,17 +32,26 @@ pub(crate) enum GitReference {
Tag(String), Tag(String),
/// From a reference that's ambiguously a branch or tag. /// From a reference that's ambiguously a branch or tag.
BranchOrTag(String), BranchOrTag(String),
/// From a specific revision. Can be a commit hash (either short or full), /// From a specific revision, using a full 40-character commit hash.
/// or a named reference like `refs/pull/493/head`. FullCommit(String),
Rev(String), /// From a truncated revision.
ShortCommit(String),
/// From a named reference, like `refs/pull/493/head`.
Ref(String),
/// The default branch of the repository, the reference named `HEAD`. /// The default branch of the repository, the reference named `HEAD`.
DefaultBranch, DefaultBranch,
} }
impl GitReference { impl GitReference {
pub(crate) fn from_rev(rev: &str) -> Self { pub(crate) fn from_rev(rev: &str) -> Self {
if looks_like_commit_hash(rev) || rev.starts_with("refs/") { if rev.starts_with("refs/") {
Self::Rev(rev.to_owned()) Self::Ref(rev.to_owned())
} else if looks_like_commit_hash(rev) {
if rev.len() == 40 {
Self::FullCommit(rev.to_owned())
} else {
Self::ShortCommit(rev.to_owned())
}
} else { } else {
Self::BranchOrTag(rev.to_owned()) Self::BranchOrTag(rev.to_owned())
} }
@ -123,7 +132,7 @@ impl GitRemote {
strategy: FetchStrategy, strategy: FetchStrategy,
client: &Client, client: &Client,
) -> Result<(GitDatabase, git2::Oid)> { ) -> Result<(GitDatabase, git2::Oid)> {
let locked_ref = locked_rev.map(|oid| GitReference::Rev(oid.to_string())); let locked_ref = locked_rev.map(|oid| GitReference::FullCommit(oid.to_string()));
let reference = locked_ref.as_ref().unwrap_or(reference); let reference = locked_ref.as_ref().unwrap_or(reference);
if let Some(mut db) = db { if let Some(mut db) = db {
fetch(&mut db.repo, self.url.as_str(), reference, strategy, client) fetch(&mut db.repo, self.url.as_str(), reference, strategy, client)
@ -262,7 +271,7 @@ impl GitReference {
head.peel(ObjectType::Commit)?.id() head.peel(ObjectType::Commit)?.id()
} }
GitReference::Rev(s) => { GitReference::FullCommit(s) | GitReference::ShortCommit(s) | GitReference::Ref(s) => {
let obj = repo.revparse_single(s)?; let obj = repo.revparse_single(s)?;
match obj.as_tag() { match obj.as_tag() {
Some(tag) => tag.target_id(), Some(tag) => tag.target_id(),
@ -483,7 +492,7 @@ impl<'a> GitCheckout<'a> {
// Fetch data from origin and reset to the head commit // Fetch data from origin and reset to the head commit
debug!("Updating Git submodule: {}", child_remote_url); debug!("Updating Git submodule: {}", child_remote_url);
let reference = GitReference::Rev(head.to_string()); let reference = GitReference::FullCommit(head.to_string());
fetch(&mut repo, &child_remote_url, &reference, strategy, client).with_context( fetch(&mut repo, &child_remote_url, &reference, strategy, client).with_context(
|| { || {
format!( format!(
@ -917,14 +926,14 @@ pub(crate) fn fetch(
refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD")); refspecs.push(String::from("+HEAD:refs/remotes/origin/HEAD"));
} }
GitReference::Rev(rev) => { GitReference::Ref(rev) => {
if rev.starts_with("refs/") { refspecs.push(format!("+{rev}:{rev}"));
refspecs.push(format!("+{rev}:{rev}")); }
} else if let Some(oid_to_fetch) = oid_to_fetch {
GitReference::FullCommit(rev) => {
if let Some(oid_to_fetch) = oid_to_fetch {
refspecs.push(format!("+{oid_to_fetch}:refs/commit/{oid_to_fetch}")); refspecs.push(format!("+{oid_to_fetch}:refs/commit/{oid_to_fetch}"));
} else if rev.parse::<git2::Oid>().is_ok() } else {
&& (rev.len() == 40 || rev.starts_with("refs/"))
{
// There is a specific commit to fetch and we will do so in shallow-mode only // There is a specific commit to fetch and we will do so in shallow-mode only
// to not disturb the previous logic. // to not disturb the previous logic.
// Note that with typical settings for shallowing, we will just fetch a single `rev` // Note that with typical settings for shallowing, we will just fetch a single `rev`
@ -932,6 +941,12 @@ pub(crate) fn fetch(
// The reason we write to `refs/remotes/origin/HEAD` is that it's of special significance // The reason we write to `refs/remotes/origin/HEAD` is that it's of special significance
// when during `GitReference::resolve()`, but otherwise it shouldn't matter. // when during `GitReference::resolve()`, but otherwise it shouldn't matter.
refspecs.push(format!("+{rev}:refs/remotes/origin/HEAD")); refspecs.push(format!("+{rev}:refs/remotes/origin/HEAD"));
}
}
GitReference::ShortCommit(_) => {
if let Some(oid_to_fetch) = oid_to_fetch {
refspecs.push(format!("+{oid_to_fetch}:refs/commit/{oid_to_fetch}"));
} else { } else {
// We don't know what the rev will point to. To handle this // We don't know what the rev will point to. To handle this
// situation we fetch all branches and tags, and then we pray // situation we fetch all branches and tags, and then we pray
@ -1216,10 +1231,9 @@ fn github_fast_path(
GitReference::Tag(tag) => tag, GitReference::Tag(tag) => tag,
GitReference::BranchOrTag(branch_or_tag) => branch_or_tag, GitReference::BranchOrTag(branch_or_tag) => branch_or_tag,
GitReference::DefaultBranch => "HEAD", GitReference::DefaultBranch => "HEAD",
GitReference::Rev(rev) => { GitReference::Ref(rev) => rev,
if rev.starts_with("refs/") { GitReference::FullCommit(rev) | GitReference::ShortCommit(rev) => {
rev if looks_like_commit_hash(rev) {
} else if looks_like_commit_hash(rev) {
// `revparse_single` (used by `resolve`) is the only way to turn // `revparse_single` (used by `resolve`) is the only way to turn
// short hash -> long hash, but it also parses other things, // short hash -> long hash, but it also parses other things,
// like branch and tag names, which might coincidentally be // like branch and tag names, which might coincidentally be

View file

@ -42,11 +42,16 @@ impl TryFrom<Url> for Git {
reference = GitReference::from_rev(rev); reference = GitReference::from_rev(rev);
url = Url::parse(prefix)?; url = Url::parse(prefix)?;
} }
let precise = if let GitReference::FullCommit(rev) = &reference {
Some(git2::Oid::from_str(rev)?)
} else {
None
};
Ok(Self { Ok(Self {
url, url,
reference, reference,
precise: None, precise,
}) })
} }
} }
@ -64,7 +69,9 @@ impl From<Git> for Url {
GitReference::Branch(rev) GitReference::Branch(rev)
| GitReference::Tag(rev) | GitReference::Tag(rev)
| GitReference::BranchOrTag(rev) | GitReference::BranchOrTag(rev)
| GitReference::Rev(rev) => { | GitReference::Ref(rev)
| GitReference::FullCommit(rev)
| GitReference::ShortCommit(rev) => {
url.set_path(&format!("{}@{}", url.path(), rev)); url.set_path(&format!("{}@{}", url.path(), rev));
} }
GitReference::DefaultBranch => {} GitReference::DefaultBranch => {}