From 26c88db05117aea52caedf0fd045bdf8a92dd783 Mon Sep 17 00:00:00 2001
From: Weihang Lo <me@weihanglo.tw>
Date: Mon, 27 May 2024 09:56:49 -0400
Subject: [PATCH] fix: check if rev is full commit sha for github fast path

---
 src/cargo/sources/git/source.rs | 12 ++++--------
 src/cargo/sources/git/utils.rs  | 18 +++++++++++++++++-
 2 files changed, 21 insertions(+), 9 deletions(-)

diff --git a/src/cargo/sources/git/source.rs b/src/cargo/sources/git/source.rs
index 9f92ab2c14e..8beacdd456d 100644
--- a/src/cargo/sources/git/source.rs
+++ b/src/cargo/sources/git/source.rs
@@ -4,6 +4,7 @@ use crate::core::global_cache_tracker;
 use crate::core::GitReference;
 use crate::core::SourceId;
 use crate::core::{Dependency, Package, PackageId};
+use crate::sources::git::utils::rev_to_oid;
 use crate::sources::git::utils::GitRemote;
 use crate::sources::source::MaybePackage;
 use crate::sources::source::QueryKind;
@@ -171,14 +172,9 @@ enum Revision {
 
 impl Revision {
     fn new(rev: &str) -> Revision {
-        let oid = git2::Oid::from_str(rev).ok();
-        match oid {
-            // Git object ID is supposed to be a hex string of 20 (SHA1) or 32 (SHA256) bytes.
-            // Its length must be double to the underlying bytes (40 or 64),
-            // otherwise libgit2 would happily zero-pad the returned oid.
-            // See rust-lang/cargo#13188
-            Some(oid) if oid.as_bytes().len() * 2 == rev.len() => Revision::Locked(oid),
-            _ => Revision::Deferred(GitReference::Rev(rev.to_string())),
+        match rev_to_oid(rev) {
+            Some(oid) => Revision::Locked(oid),
+            None => Revision::Deferred(GitReference::Rev(rev.to_string())),
         }
     }
 }
diff --git a/src/cargo/sources/git/utils.rs b/src/cargo/sources/git/utils.rs
index 66fb11a3079..6117266559a 100644
--- a/src/cargo/sources/git/utils.rs
+++ b/src/cargo/sources/git/utils.rs
@@ -1421,7 +1421,7 @@ fn github_fast_path(
                 // to is itself. Don't bother talking to GitHub in that case
                 // either. (This ensures that we always attempt to fetch the
                 // commit directly even if we can't reach the GitHub API.)
-                if let Ok(oid) = rev.parse() {
+                if let Some(oid) = rev_to_oid(rev) {
                     debug!("github fast path is already a full commit hash {rev}");
                     return Ok(FastPathRev::NeedsFetch(oid));
                 }
@@ -1614,3 +1614,19 @@ mod tests {
         }
     }
 }
+
+/// Turns a full commit hash revision into an oid.
+///
+/// Git object ID is supposed to be a hex string of 20 (SHA1) or 32 (SHA256) bytes.
+/// Its length must be double to the underlying bytes (40 or 64),
+/// otherwise libgit2 would happily zero-pad the returned oid.
+///
+/// See:
+///
+/// * <https://github.com/rust-lang/cargo/issues/13188>
+/// * <https://github.com/rust-lang/cargo/issues/13968>
+pub(super) fn rev_to_oid(rev: &str) -> Option<Oid> {
+    Oid::from_str(rev)
+        .ok()
+        .filter(|oid| oid.as_bytes().len() * 2 == rev.len())
+}