@@ -68,11 +68,10 @@ use url::Url;
6868pub struct GitSource < ' cfg > {
6969 /// The git remote which we're going to fetch from.
7070 remote : GitRemote ,
71- /// The Git reference from the manifest file.
72- manifest_reference : GitReference ,
7371 /// The revision which a git source is locked to.
74- /// This is expected to be set after the Git repository is fetched.
75- locked_rev : Option < git2:: Oid > ,
72+ ///
73+ /// Expected to always be [`Revision::Locked`] after the Git repository is fetched.
74+ locked_rev : Revision ,
7675 /// The unique identifier of this source.
7776 source_id : SourceId ,
7877 /// The underlying path source to discover packages inside the Git repository.
@@ -102,8 +101,12 @@ impl<'cfg> GitSource<'cfg> {
102101 assert ! ( source_id. is_git( ) , "id is not git, id={}" , source_id) ;
103102
104103 let remote = GitRemote :: new ( source_id. url ( ) ) ;
105- let manifest_reference = source_id. git_reference ( ) . unwrap ( ) . clone ( ) ;
106- let locked_rev = source_id. precise_git_oid ( ) ?;
104+ // Fallback to git ref from mainfest if there is no locked revision.
105+ let locked_rev = source_id
106+ . precise_git_fragment ( )
107+ . map ( |s| Revision :: new ( s. into ( ) ) )
108+ . unwrap_or_else ( || source_id. git_reference ( ) . unwrap ( ) . clone ( ) . into ( ) ) ;
109+
107110 let ident = ident_shallow (
108111 & source_id,
109112 config
@@ -114,7 +117,6 @@ impl<'cfg> GitSource<'cfg> {
114117
115118 let source = GitSource {
116119 remote,
117- manifest_reference,
118120 locked_rev,
119121 source_id,
120122 path_source : None ,
@@ -155,6 +157,48 @@ impl<'cfg> GitSource<'cfg> {
155157 }
156158}
157159
160+ /// Indicates a [Git revision] that might be locked or deferred to be resolved.
161+ ///
162+ /// [Git revision]: https://git-scm.com/docs/revisions
163+ #[ derive( Clone , Debug ) ]
164+ enum Revision {
165+ /// A [Git reference] that would trigger extra fetches when being resolved.
166+ ///
167+ /// [Git reference]: https://git-scm.com/book/en/v2/Git-Internals-Git-References
168+ Deferred ( GitReference ) ,
169+ /// A locked revision of the actual Git commit object ID.
170+ Locked ( git2:: Oid ) ,
171+ }
172+
173+ impl Revision {
174+ fn new ( rev : & str ) -> Revision {
175+ let oid = git2:: Oid :: from_str ( rev) . ok ( ) ;
176+ match oid {
177+ // Git object ID is supposed to be a hex string of 20 (SHA1) or 32 (SHA256) bytes.
178+ // Its length must be double to the underlying bytes (40 or 64),
179+ // otherwise libgit2 would happily zero-pad the returned oid.
180+ // See rust-lang/cargo#13188
181+ Some ( oid) if oid. as_bytes ( ) . len ( ) * 2 == rev. len ( ) => Revision :: Locked ( oid) ,
182+ _ => Revision :: Deferred ( GitReference :: Rev ( rev. to_string ( ) ) ) ,
183+ }
184+ }
185+ }
186+
187+ impl From < GitReference > for Revision {
188+ fn from ( value : GitReference ) -> Self {
189+ Revision :: Deferred ( value)
190+ }
191+ }
192+
193+ impl From < Revision > for GitReference {
194+ fn from ( value : Revision ) -> Self {
195+ match value {
196+ Revision :: Deferred ( git_ref) => git_ref,
197+ Revision :: Locked ( oid) => GitReference :: Rev ( oid. to_string ( ) ) ,
198+ }
199+ }
200+ }
201+
158202/// Create an identifier from a URL,
159203/// essentially turning `proto://host/path/repo` into `repo-<hash-of-url>`.
160204fn ident ( id : & SourceId ) -> String {
@@ -191,9 +235,12 @@ impl<'cfg> Debug for GitSource<'cfg> {
191235 // TODO(-Znext-lockfile-bump): set it to true when stabilizing
192236 // lockfile v4, because we want Source ID serialization to be
193237 // consistent with lockfile.
194- match self . manifest_reference . pretty_ref ( false ) {
195- Some ( s) => write ! ( f, " ({})" , s) ,
196- None => Ok ( ( ) ) ,
238+ match & self . locked_rev {
239+ Revision :: Deferred ( git_ref) => match git_ref. pretty_ref ( false ) {
240+ Some ( s) => write ! ( f, " ({})" , s) ,
241+ None => Ok ( ( ) ) ,
242+ } ,
243+ Revision :: Locked ( oid) => write ! ( f, " ({oid})" ) ,
197244 }
198245 }
199246}
@@ -252,16 +299,17 @@ impl<'cfg> Source for GitSource<'cfg> {
252299 let db_path = db_path. into_path_unlocked ( ) ;
253300
254301 let db = self . remote . db_at ( & db_path) . ok ( ) ;
255- let ( db, actual_rev) = match ( self . locked_rev , db) {
302+
303+ let ( db, actual_rev) = match ( & self . locked_rev , db) {
256304 // If we have a locked revision, and we have a preexisting database
257305 // which has that revision, then no update needs to happen.
258- ( Some ( rev ) , Some ( db) ) if db. contains ( rev ) => ( db, rev ) ,
306+ ( Revision :: Locked ( oid ) , Some ( db) ) if db. contains ( * oid ) => ( db, * oid ) ,
259307
260308 // If we're in offline mode, we're not locked, and we have a
261309 // database, then try to resolve our reference with the preexisting
262310 // repository.
263- ( None , Some ( db) ) if self . config . offline ( ) => {
264- let rev = db. resolve ( & self . manifest_reference ) . with_context ( || {
311+ ( Revision :: Deferred ( git_ref ) , Some ( db) ) if self . config . offline ( ) => {
312+ let rev = db. resolve ( & git_ref ) . with_context ( || {
265313 "failed to lookup reference in preexisting repository, and \
266314 can't check for updates in offline mode (--offline)"
267315 } ) ?;
@@ -279,6 +327,7 @@ impl<'cfg> Source for GitSource<'cfg> {
279327 self . remote. url( )
280328 ) ;
281329 }
330+
282331 if !self . quiet {
283332 self . config . shell ( ) . status (
284333 "Updating" ,
@@ -288,13 +337,9 @@ impl<'cfg> Source for GitSource<'cfg> {
288337
289338 trace ! ( "updating git source `{:?}`" , self . remote) ;
290339
291- self . remote . checkout (
292- & db_path,
293- db,
294- & self . manifest_reference ,
295- locked_rev,
296- self . config ,
297- ) ?
340+ let locked_rev = locked_rev. clone ( ) . into ( ) ;
341+ self . remote
342+ . checkout ( & db_path, db, & locked_rev, self . config ) ?
298343 }
299344 } ;
300345
@@ -321,7 +366,7 @@ impl<'cfg> Source for GitSource<'cfg> {
321366
322367 self . path_source = Some ( path_source) ;
323368 self . short_id = Some ( short_id. as_str ( ) . into ( ) ) ;
324- self . locked_rev = Some ( actual_rev) ;
369+ self . locked_rev = Revision :: Locked ( actual_rev) ;
325370 self . path_source . as_mut ( ) . unwrap ( ) . update ( ) ?;
326371
327372 // Hopefully this shouldn't incur too much of a performance hit since
@@ -350,7 +395,10 @@ impl<'cfg> Source for GitSource<'cfg> {
350395 }
351396
352397 fn fingerprint ( & self , _pkg : & Package ) -> CargoResult < String > {
353- Ok ( self . locked_rev . as_ref ( ) . unwrap ( ) . to_string ( ) )
398+ match & self . locked_rev {
399+ Revision :: Locked ( oid) => Ok ( oid. to_string ( ) ) ,
400+ _ => unreachable ! ( "locked_rev must be resolved when computing fingerprint" ) ,
401+ }
354402 }
355403
356404 fn describe ( & self ) -> String {
0 commit comments