@@ -40,6 +40,14 @@ pub static GIT: LazyLock<Result<PathBuf, GitError>> = LazyLock::new(|| {
4040 } )
4141} ) ;
4242
43+ /// Strategy when fetching refspecs for a [`GitReference`]
44+ enum RefspecStrategy {
45+ /// All refspecs should be fetched, if any fail then the fetch will fail.
46+ All ,
47+ /// Stop after the first successful fetch, if none succeed then the fetch will fail.
48+ First ,
49+ }
50+
4351/// A reference to commit or commit-ish.
4452#[ derive( Debug , Clone , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
4553pub enum GitReference {
@@ -49,24 +57,14 @@ pub enum GitReference {
4957 Tag ( String ) ,
5058 /// From a reference that's ambiguously a branch or tag.
5159 BranchOrTag ( String ) ,
52- /// From a reference that's ambiguously a short commit, a branch, or a tag.
60+ /// From a reference that's ambiguously a commit, branch, or tag.
5361 BranchOrTagOrCommit ( String ) ,
5462 /// From a named reference, like `refs/pull/493/head`.
5563 NamedRef ( String ) ,
56- /// From a specific revision, using a full 40-character commit hash.
57- FullCommit ( GitOid ) ,
5864 /// The default branch of the repository, the reference named `HEAD`.
5965 DefaultBranch ,
6066}
6167
62- /// Strategy when fetching refspecs for a [`GitReference`]
63- enum RefspecStrategy {
64- // All refspecs should be fetched, if any fail then the fetch will fail
65- All ,
66- // Stop after the first successful fetch, if none succeed then the fetch will fail
67- First ,
68- }
69-
7068impl GitReference {
7169 /// Creates a [`GitReference`] from an arbitrary revision string, which could represent a
7270 /// branch, tag, commit, or named ref.
@@ -87,7 +85,6 @@ impl GitReference {
8785 Self :: Branch ( rev) => Some ( rev) ,
8886 Self :: BranchOrTag ( rev) => Some ( rev) ,
8987 Self :: BranchOrTagOrCommit ( rev) => Some ( rev) ,
90- Self :: FullCommit ( rev) => Some ( rev. as_str ( ) ) ,
9188 Self :: NamedRef ( rev) => Some ( rev) ,
9289 Self :: DefaultBranch => None ,
9390 }
@@ -100,7 +97,6 @@ impl GitReference {
10097 Self :: Branch ( rev) => rev,
10198 Self :: BranchOrTag ( rev) => rev,
10299 Self :: BranchOrTagOrCommit ( rev) => rev,
103- Self :: FullCommit ( rev) => rev. as_str ( ) ,
104100 Self :: NamedRef ( rev) => rev,
105101 Self :: DefaultBranch => "HEAD" ,
106102 }
@@ -112,26 +108,93 @@ impl GitReference {
112108 Self :: Branch ( _) => "branch" ,
113109 Self :: Tag ( _) => "tag" ,
114110 Self :: BranchOrTag ( _) => "branch or tag" ,
115- Self :: FullCommit ( _) => "commit" ,
116111 Self :: BranchOrTagOrCommit ( _) => "branch, tag, or commit" ,
117112 Self :: NamedRef ( _) => "ref" ,
118113 Self :: DefaultBranch => "default branch" ,
119114 }
120115 }
116+ }
121117
122- /// Returns the precise [`GitOid`] of this reference, if it's a full commit.
123- pub ( crate ) fn as_sha ( & self ) -> Option < GitOid > {
124- if let Self :: FullCommit ( rev) = self {
125- Some ( * rev)
126- } else {
127- None
118+ impl Display for GitReference {
119+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
120+ write ! ( f, "{}" , self . as_str( ) . unwrap_or( "HEAD" ) )
121+ }
122+ }
123+
124+ /// A Git reference (like a tag or branch) or a specific commit.
125+ #[ derive( Debug , Copy , Clone , PartialEq , Eq , Hash , PartialOrd , Ord ) ]
126+ enum ReferenceOrOid < ' reference > {
127+ /// A Git reference, like a tag or branch.
128+ Reference ( & ' reference GitReference ) ,
129+ /// A specific commit.
130+ Oid ( GitOid ) ,
131+ }
132+
133+ impl ReferenceOrOid < ' _ > {
134+ /// Resolves the [`ReferenceOrOid`] to an object ID with objects the `repo` currently has.
135+ fn resolve ( & self , repo : & GitRepository ) -> Result < GitOid > {
136+ let refkind = self . kind_str ( ) ;
137+ let result = match self {
138+ // Resolve the commit pointed to by the tag.
139+ //
140+ // `^0` recursively peels away from the revision to the underlying commit object.
141+ // This also verifies that the tag indeed refers to a commit.
142+ Self :: Reference ( GitReference :: Tag ( s) ) => {
143+ repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) )
144+ }
145+
146+ // Resolve the commit pointed to by the branch.
147+ Self :: Reference ( GitReference :: Branch ( s) ) => repo. rev_parse ( & format ! ( "origin/{s}^0" ) ) ,
148+
149+ // Attempt to resolve the branch, then the tag.
150+ Self :: Reference ( GitReference :: BranchOrTag ( s) ) => repo
151+ . rev_parse ( & format ! ( "origin/{s}^0" ) )
152+ . or_else ( |_| repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) ) ) ,
153+
154+ // Attempt to resolve the branch, then the tag, then the commit.
155+ Self :: Reference ( GitReference :: BranchOrTagOrCommit ( s) ) => repo
156+ . rev_parse ( & format ! ( "origin/{s}^0" ) )
157+ . or_else ( |_| repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) ) )
158+ . or_else ( |_| repo. rev_parse ( & format ! ( "{s}^0" ) ) ) ,
159+
160+ // We'll be using the HEAD commit.
161+ Self :: Reference ( GitReference :: DefaultBranch ) => {
162+ repo. rev_parse ( "refs/remotes/origin/HEAD" )
163+ }
164+
165+ // Resolve a named reference.
166+ Self :: Reference ( GitReference :: NamedRef ( s) ) => repo. rev_parse ( & format ! ( "{s}^0" ) ) ,
167+
168+ // Resolve a specific commit.
169+ Self :: Oid ( s) => repo. rev_parse ( & format ! ( "{s}^0" ) ) ,
170+ } ;
171+
172+ result. with_context ( || anyhow:: format_err!( "failed to find {refkind} `{self}`" ) )
173+ }
174+
175+ /// Returns the kind of this [`ReferenceOrOid`].
176+ fn kind_str ( & self ) -> & str {
177+ match self {
178+ Self :: Reference ( reference) => reference. kind_str ( ) ,
179+ Self :: Oid ( _) => "commit" ,
180+ }
181+ }
182+
183+ /// Converts the [`ReferenceOrOid`] to a `str` that can be used as a revision.
184+ fn as_rev ( & self ) -> & str {
185+ match self {
186+ Self :: Reference ( r) => r. as_rev ( ) ,
187+ Self :: Oid ( rev) => rev. as_str ( ) ,
128188 }
129189 }
130190}
131191
132- impl Display for GitReference {
192+ impl Display for ReferenceOrOid < ' _ > {
133193 fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
134- write ! ( f, "{}" , self . as_str( ) . unwrap_or( "HEAD" ) )
194+ match self {
195+ Self :: Reference ( reference) => write ! ( f, "{reference}" ) ,
196+ Self :: Oid ( oid) => write ! ( f, "{oid}" ) ,
197+ }
135198 }
136199}
137200
@@ -241,8 +304,9 @@ impl GitRemote {
241304 locked_rev : Option < GitOid > ,
242305 client : & ClientWithMiddleware ,
243306 ) -> Result < ( GitDatabase , GitOid ) > {
244- let locked_ref = locked_rev. map ( GitReference :: FullCommit ) ;
245- let reference = locked_ref. as_ref ( ) . unwrap_or ( reference) ;
307+ let reference = locked_rev
308+ . map ( ReferenceOrOid :: Oid )
309+ . unwrap_or ( ReferenceOrOid :: Reference ( reference) ) ;
246310 let enable_lfs_fetch = env:: var ( EnvVars :: UV_GIT_LFS ) . is_ok ( ) ;
247311
248312 if let Some ( mut db) = db {
@@ -334,45 +398,6 @@ impl GitDatabase {
334398 }
335399}
336400
337- impl GitReference {
338- /// Resolves self to an object ID with objects the `repo` currently has.
339- pub ( crate ) fn resolve ( & self , repo : & GitRepository ) -> Result < GitOid > {
340- let refkind = self . kind_str ( ) ;
341- let result = match self {
342- // Resolve the commit pointed to by the tag.
343- //
344- // `^0` recursively peels away from the revision to the underlying commit object.
345- // This also verifies that the tag indeed refers to a commit.
346- Self :: Tag ( s) => repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) ) ,
347-
348- // Resolve the commit pointed to by the branch.
349- Self :: Branch ( s) => repo. rev_parse ( & format ! ( "origin/{s}^0" ) ) ,
350-
351- // Attempt to resolve the branch, then the tag.
352- Self :: BranchOrTag ( s) => repo
353- . rev_parse ( & format ! ( "origin/{s}^0" ) )
354- . or_else ( |_| repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) ) ) ,
355-
356- // Attempt to resolve the branch, then the tag, then the commit.
357- Self :: BranchOrTagOrCommit ( s) => repo
358- . rev_parse ( & format ! ( "origin/{s}^0" ) )
359- . or_else ( |_| repo. rev_parse ( & format ! ( "refs/remotes/origin/tags/{s}^0" ) ) )
360- . or_else ( |_| repo. rev_parse ( & format ! ( "{s}^0" ) ) ) ,
361-
362- // We'll be using the HEAD commit.
363- Self :: DefaultBranch => repo. rev_parse ( "refs/remotes/origin/HEAD" ) ,
364-
365- // Resolve a named reference.
366- Self :: NamedRef ( s) => repo. rev_parse ( & format ! ( "{s}^0" ) ) ,
367-
368- // Resolve a direct commit reference.
369- Self :: FullCommit ( s) => repo. rev_parse ( & format ! ( "{s}^0" ) ) ,
370- } ;
371-
372- result. with_context ( || anyhow:: format_err!( "failed to find {refkind} `{self}`" ) )
373- }
374- }
375-
376401impl GitCheckout {
377402 /// Creates an instance of [`GitCheckout`]. This doesn't imply the checkout
378403 /// is done. Use [`GitCheckout::is_fresh`] to check.
@@ -472,10 +497,10 @@ impl GitCheckout {
472497/// * Dispatches `git fetch` using the git CLI.
473498///
474499/// The `remote_url` argument is the git remote URL where we want to fetch from.
475- pub ( crate ) fn fetch (
500+ fn fetch (
476501 repo : & mut GitRepository ,
477502 remote_url : & Url ,
478- reference : & GitReference ,
503+ reference : ReferenceOrOid < ' _ > ,
479504 client : & ClientWithMiddleware ,
480505) -> Result < ( ) > {
481506 let oid_to_fetch = match github_fast_path ( repo, remote_url, reference, client) {
@@ -499,15 +524,15 @@ pub(crate) fn fetch(
499524 match reference {
500525 // For branches and tags we can fetch simply one reference and copy it
501526 // locally, no need to fetch other branches/tags.
502- GitReference :: Branch ( branch) => {
527+ ReferenceOrOid :: Reference ( GitReference :: Branch ( branch) ) => {
503528 refspecs. push ( format ! ( "+refs/heads/{branch}:refs/remotes/origin/{branch}" ) ) ;
504529 }
505530
506- GitReference :: Tag ( tag) => {
531+ ReferenceOrOid :: Reference ( GitReference :: Tag ( tag) ) => {
507532 refspecs. push ( format ! ( "+refs/tags/{tag}:refs/remotes/origin/tags/{tag}" ) ) ;
508533 }
509534
510- GitReference :: BranchOrTag ( branch_or_tag) => {
535+ ReferenceOrOid :: Reference ( GitReference :: BranchOrTag ( branch_or_tag) ) => {
511536 refspecs. push ( format ! (
512537 "+refs/heads/{branch_or_tag}:refs/remotes/origin/{branch_or_tag}"
513538 ) ) ;
@@ -519,7 +544,7 @@ pub(crate) fn fetch(
519544
520545 // For ambiguous references, we can fetch the exact commit (if known); otherwise,
521546 // we fetch all branches and tags.
522- GitReference :: BranchOrTagOrCommit ( branch_or_tag_or_commit) => {
547+ ReferenceOrOid :: Reference ( GitReference :: BranchOrTagOrCommit ( branch_or_tag_or_commit) ) => {
523548 // The `oid_to_fetch` is the exact commit we want to fetch. But it could be the exact
524549 // commit of a branch or tag. We should only fetch it directly if it's the exact commit
525550 // of a short commit hash.
@@ -537,15 +562,15 @@ pub(crate) fn fetch(
537562 }
538563 }
539564
540- GitReference :: DefaultBranch => {
565+ ReferenceOrOid :: Reference ( GitReference :: DefaultBranch ) => {
541566 refspecs. push ( String :: from ( "+HEAD:refs/remotes/origin/HEAD" ) ) ;
542567 }
543568
544- GitReference :: NamedRef ( rev) => {
569+ ReferenceOrOid :: Reference ( GitReference :: NamedRef ( rev) ) => {
545570 refspecs. push ( format ! ( "+{rev}:{rev}" ) ) ;
546571 }
547572
548- GitReference :: FullCommit ( rev) => {
573+ ReferenceOrOid :: Oid ( rev) => {
549574 refspecs. push ( format ! ( "+{rev}:refs/commit/{rev}" ) ) ;
550575 }
551576 }
@@ -587,7 +612,7 @@ pub(crate) fn fetch(
587612 } ;
588613 match reference {
589614 // With the default branch, adding context is confusing
590- GitReference :: DefaultBranch => result,
615+ ReferenceOrOid :: Reference ( GitReference :: DefaultBranch ) => result,
591616 _ => result. with_context ( || {
592617 format ! (
593618 "failed to fetch {} `{}`" ,
@@ -703,7 +728,7 @@ enum FastPathRev {
703728fn github_fast_path (
704729 git : & mut GitRepository ,
705730 url : & Url ,
706- reference : & GitReference ,
731+ reference : ReferenceOrOid < ' _ > ,
707732 client : & ClientWithMiddleware ,
708733) -> Result < FastPathRev > {
709734 let Some ( GitHubRepository { owner, repo } ) = GitHubRepository :: parse ( url) else {
@@ -713,12 +738,12 @@ fn github_fast_path(
713738 let local_object = reference. resolve ( git) . ok ( ) ;
714739
715740 let github_branch_name = match reference {
716- GitReference :: Branch ( branch ) => branch ,
717- GitReference :: Tag ( tag ) => tag ,
718- GitReference :: BranchOrTag ( branch_or_tag ) => branch_or_tag ,
719- GitReference :: DefaultBranch => "HEAD" ,
720- GitReference :: NamedRef ( rev) => rev,
721- GitReference :: BranchOrTagOrCommit ( rev) => {
741+ ReferenceOrOid :: Reference ( GitReference :: DefaultBranch ) => "HEAD" ,
742+ ReferenceOrOid :: Reference ( GitReference :: Branch ( branch ) ) => branch ,
743+ ReferenceOrOid :: Reference ( GitReference :: Tag ( tag ) ) => tag ,
744+ ReferenceOrOid :: Reference ( GitReference :: BranchOrTag ( branch_or_tag ) ) => branch_or_tag ,
745+ ReferenceOrOid :: Reference ( GitReference :: NamedRef ( rev) ) => rev,
746+ ReferenceOrOid :: Reference ( GitReference :: BranchOrTagOrCommit ( rev) ) => {
722747 // `revparse_single` (used by `resolve`) is the only way to turn
723748 // short hash -> long hash, but it also parses other things,
724749 // like branch and tag names, which might coincidentally be
@@ -743,18 +768,18 @@ fn github_fast_path(
743768 }
744769 rev
745770 }
746- GitReference :: FullCommit ( rev) => {
771+ ReferenceOrOid :: Oid ( rev) => {
747772 debug ! ( "Skipping GitHub fast path; full commit hash provided: {rev}" ) ;
748773
749- if let Some ( ref local_object) = local_object {
774+ if let Some ( local_object) = local_object {
750775 if rev == local_object {
751776 return Ok ( FastPathRev :: UpToDate ) ;
752777 }
753778 }
754779
755780 // If we know the reference is a full commit hash, we can just return it without
756781 // querying GitHub.
757- return Ok ( FastPathRev :: NeedsFetch ( * rev) ) ;
782+ return Ok ( FastPathRev :: NeedsFetch ( rev) ) ;
758783 }
759784 } ;
760785
0 commit comments