@@ -9,8 +9,7 @@ use cargo_util::{paths, ProcessBuilder};
9
9
use curl:: easy:: List ;
10
10
use git2:: { self , ErrorClass , ObjectType , Oid } ;
11
11
use log:: { debug, info} ;
12
- use serde:: ser;
13
- use serde:: Serialize ;
12
+ use serde:: { ser, Deserialize , Serialize } ;
14
13
use std:: borrow:: Cow ;
15
14
use std:: env;
16
15
use std:: fmt;
@@ -79,7 +78,7 @@ impl GitRemote {
79
78
}
80
79
81
80
pub fn rev_for ( & self , path : & Path , reference : & GitReference ) -> CargoResult < git2:: Oid > {
82
- reference. resolve ( & self . db_at ( path) ?. repo )
81
+ reference. resolve ( & self . db_at ( path) ?. repo , true )
83
82
}
84
83
85
84
pub fn checkout (
@@ -104,7 +103,7 @@ impl GitRemote {
104
103
}
105
104
}
106
105
None => {
107
- if let Ok ( rev) = reference. resolve ( & db. repo ) {
106
+ if let Ok ( rev) = reference. resolve ( & db. repo , true ) {
108
107
return Ok ( ( db, rev) ) ;
109
108
}
110
109
}
@@ -123,7 +122,7 @@ impl GitRemote {
123
122
. context ( format ! ( "failed to clone into: {}" , into. display( ) ) ) ?;
124
123
let rev = match locked_rev {
125
124
Some ( rev) => rev,
126
- None => reference. resolve ( & repo) ?,
125
+ None => reference. resolve ( & repo, true ) ?,
127
126
} ;
128
127
129
128
Ok ( (
@@ -180,12 +179,48 @@ impl GitDatabase {
180
179
}
181
180
182
181
pub fn resolve ( & self , r : & GitReference ) -> CargoResult < git2:: Oid > {
183
- r. resolve ( & self . repo )
182
+ r. resolve ( & self . repo , true )
184
183
}
185
184
}
186
185
186
+ #[ derive( Debug , Clone , PartialEq , Eq , PartialOrd , Ord , Hash , Serialize , Deserialize ) ]
187
+ struct ShalowDataBlob < ' a > {
188
+ tree : & ' a str ,
189
+ etag : & ' a str ,
190
+ }
191
+
192
+ #[ test]
193
+ fn check_with_git_hub ( ) {
194
+ panic ! ( r#"nonstandard shallow clone may be worse than a full check out.
195
+ This test is here to make sure we do not merge until we have official signoff from GitHub"# )
196
+ }
197
+
187
198
impl GitReference {
188
- pub fn resolve ( & self , repo : & git2:: Repository ) -> CargoResult < git2:: Oid > {
199
+ pub fn resolve ( & self , repo : & git2:: Repository , tree : bool ) -> CargoResult < git2:: Oid > {
200
+ // Check if Cargo has done a nonstandard shallow clone
201
+ if let Some ( reference) = repo
202
+ . find_reference (
203
+ & ( format ! (
204
+ "refs/cargo-{}" ,
205
+ serde_json:: to_string( self ) . expect( "why cant we make json of this" )
206
+ ) ) ,
207
+ )
208
+ . ok ( )
209
+ . and_then ( |re| {
210
+ let blob = re. peel_to_blob ( ) . ok ( ) ?;
211
+ let shalow_data: ShalowDataBlob < ' _ > =
212
+ serde_json:: from_slice ( blob. content ( ) ) . ok ( ) ?;
213
+ let id = if tree {
214
+ shalow_data. tree
215
+ } else {
216
+ shalow_data. etag
217
+ } ;
218
+ Some ( id. parse :: < Oid > ( ) . ok ( ) ?)
219
+ } )
220
+ {
221
+ return Ok ( reference) ;
222
+ }
223
+
189
224
let id = match self {
190
225
// Note that we resolve the named tag here in sync with where it's
191
226
// fetched into via `fetch` below.
@@ -707,6 +742,12 @@ fn reset(repo: &git2::Repository, obj: &git2::Object<'_>, config: &Config) -> Ca
707
742
opts. progress ( |_, cur, max| {
708
743
drop ( pb. tick ( cur, max, "" ) ) ;
709
744
} ) ;
745
+ if obj. as_tree ( ) . is_some ( ) {
746
+ debug ! ( "doing reset for Cargo nonstandard shallow clone" ) ;
747
+ repo. checkout_tree ( obj, Some ( & mut opts) ) ?;
748
+ debug ! ( "reset done" ) ;
749
+ return Ok ( ( ) ) ;
750
+ }
710
751
debug ! ( "doing reset" ) ;
711
752
repo. reset ( obj, git2:: ResetType :: Hard , Some ( & mut opts) ) ?;
712
753
debug ! ( "reset done" ) ;
@@ -819,32 +860,44 @@ pub fn fetch(
819
860
// The `+` symbol on the refspec means to allow a forced (fast-forward)
820
861
// update which is needed if there is ever a force push that requires a
821
862
// fast-forward.
822
- match reference {
823
- // For branches and tags we can fetch simply one reference and copy it
824
- // locally, no need to fetch other branches/tags.
825
- GitReference :: Branch ( b) => {
826
- refspecs. push ( format ! ( "+refs/heads/{0}:refs/remotes/origin/{0}" , b) ) ;
827
- }
828
- GitReference :: Tag ( t) => {
829
- refspecs. push ( format ! ( "+refs/tags/{0}:refs/remotes/origin/tags/{0}" , t) ) ;
863
+ if let Some ( oid_to_fetch) = oid_to_fetch {
864
+ // GitHub told us exactly the min needed to fetch. So we can go ahead and do a Cargo nonstandard shallow clone.
865
+ refspecs. push ( format ! ( "+{0}" , oid_to_fetch) ) ;
866
+ } else {
867
+ // In some cases we have Cargo nonstandard shallow cloned this repo before, but cannot do it now.
868
+ // Mostly if GitHub is now rate limiting us. If so, remove the info about the shallow clone.
869
+ if let Ok ( mut refe) = repo. find_reference ( & format ! (
870
+ "refs/cargo-{}" ,
871
+ serde_json:: to_string( reference) . expect( "why cant we make json of this" )
872
+ ) ) {
873
+ let _ = refe. delete ( ) ;
830
874
}
831
875
832
- GitReference :: DefaultBranch => {
833
- refspecs. push ( String :: from ( "+HEAD:refs/remotes/origin/HEAD" ) ) ;
834
- }
876
+ match reference {
877
+ // For branches and tags we can fetch simply one reference and copy it
878
+ // locally, no need to fetch other branches/tags.
879
+ GitReference :: Branch ( b) => {
880
+ refspecs. push ( format ! ( "+refs/heads/{0}:refs/remotes/origin/{0}" , b) ) ;
881
+ }
882
+ GitReference :: Tag ( t) => {
883
+ refspecs. push ( format ! ( "+refs/tags/{0}:refs/remotes/origin/tags/{0}" , t) ) ;
884
+ }
835
885
836
- GitReference :: Rev ( rev) => {
837
- if rev. starts_with ( "refs/" ) {
838
- refspecs. push ( format ! ( "+{0}:{0}" , rev) ) ;
839
- } else if let Some ( oid_to_fetch) = oid_to_fetch {
840
- refspecs. push ( format ! ( "+{0}:refs/commit/{0}" , oid_to_fetch) ) ;
841
- } else {
842
- // We don't know what the rev will point to. To handle this
843
- // situation we fetch all branches and tags, and then we pray
844
- // it's somewhere in there.
845
- refspecs. push ( String :: from ( "+refs/heads/*:refs/remotes/origin/*" ) ) ;
886
+ GitReference :: DefaultBranch => {
846
887
refspecs. push ( String :: from ( "+HEAD:refs/remotes/origin/HEAD" ) ) ;
847
- tags = true ;
888
+ }
889
+
890
+ GitReference :: Rev ( rev) => {
891
+ if rev. starts_with ( "refs/" ) {
892
+ refspecs. push ( format ! ( "+{0}:{0}" , rev) ) ;
893
+ } else {
894
+ // We don't know what the rev will point to. To handle this
895
+ // situation we fetch all branches and tags, and then we pray
896
+ // it's somewhere in there.
897
+ refspecs. push ( String :: from ( "+refs/heads/*:refs/remotes/origin/*" ) ) ;
898
+ refspecs. push ( String :: from ( "+HEAD:refs/remotes/origin/HEAD" ) ) ;
899
+ tags = true ;
900
+ }
848
901
}
849
902
}
850
903
}
@@ -1046,6 +1099,22 @@ enum FastPathRev {
1046
1099
Indeterminate ,
1047
1100
}
1048
1101
1102
+ #[ derive( Debug , Deserialize ) ]
1103
+ struct GithubFastPathJsonResponse {
1104
+ sha : String ,
1105
+ commit : GithubCommitJsonResponse ,
1106
+ }
1107
+
1108
+ #[ derive( Debug , Deserialize ) ]
1109
+ struct GithubCommitJsonResponse {
1110
+ tree : GithubTreeJsonResponse ,
1111
+ }
1112
+
1113
+ #[ derive( Debug , Deserialize ) ]
1114
+ struct GithubTreeJsonResponse {
1115
+ sha : String ,
1116
+ }
1117
+
1049
1118
/// Updating the index is done pretty regularly so we want it to be as fast as
1050
1119
/// possible. For registries hosted on GitHub (like the crates.io index) there's
1051
1120
/// a fast path available to use [1] to tell us that there's no updates to be
@@ -1067,11 +1136,8 @@ fn github_fast_path(
1067
1136
config : & Config ,
1068
1137
) -> CargoResult < FastPathRev > {
1069
1138
let url = Url :: parse ( url) ?;
1070
- if !is_github ( & url) {
1071
- return Ok ( FastPathRev :: Indeterminate ) ;
1072
- }
1073
1139
1074
- let local_object = reference. resolve ( repo) . ok ( ) ;
1140
+ let local_object = reference. resolve ( repo, false ) . ok ( ) ;
1075
1141
1076
1142
let github_branch_name = match reference {
1077
1143
GitReference :: Branch ( branch) => branch,
@@ -1111,6 +1177,10 @@ fn github_fast_path(
1111
1177
}
1112
1178
} ;
1113
1179
1180
+ if !is_github ( & url) {
1181
+ return Ok ( FastPathRev :: Indeterminate ) ;
1182
+ }
1183
+
1114
1184
// This expects GitHub urls in the form `github.com/user/repo` and nothing
1115
1185
// else
1116
1186
let mut pieces = url
@@ -1141,7 +1211,7 @@ fn github_fast_path(
1141
1211
handle. useragent ( "cargo" ) ?;
1142
1212
handle. http_headers ( {
1143
1213
let mut headers = List :: new ( ) ;
1144
- headers. append ( "Accept: application/vnd.github.3.sha " ) ?;
1214
+ headers. append ( "Accept: application/vnd.github+json " ) ?;
1145
1215
if let Some ( local_object) = local_object {
1146
1216
headers. append ( & format ! ( "If-None-Match: \" {}\" " , local_object) ) ?;
1147
1217
}
@@ -1161,7 +1231,24 @@ fn github_fast_path(
1161
1231
if response_code == 304 {
1162
1232
Ok ( FastPathRev :: UpToDate )
1163
1233
} else if response_code == 200 {
1164
- let oid_to_fetch = str:: from_utf8 ( & response_body) ?. parse :: < Oid > ( ) ?;
1234
+ let data: GithubFastPathJsonResponse = serde_json:: from_slice ( & response_body) ?;
1235
+ // We can do a Cargo nonstandard shallow clone, so record the relevant information.
1236
+ let bytes = serde_json:: to_string ( & ShalowDataBlob {
1237
+ tree : & data. commit . tree . sha ,
1238
+ etag : & data. sha ,
1239
+ } )
1240
+ . expect ( "why cant we make json of this" ) ;
1241
+ let shallow_blob = repo. blob ( bytes. as_bytes ( ) ) ?;
1242
+ repo. reference (
1243
+ & format ! (
1244
+ "refs/cargo-{}" ,
1245
+ serde_json:: to_string( reference) . expect( "why cant we make json of this" )
1246
+ ) ,
1247
+ shallow_blob,
1248
+ true ,
1249
+ "" ,
1250
+ ) ?;
1251
+ let oid_to_fetch = str:: from_utf8 ( data. commit . tree . sha . as_bytes ( ) ) ?. parse :: < Oid > ( ) ?;
1165
1252
Ok ( FastPathRev :: NeedsFetch ( oid_to_fetch) )
1166
1253
} else {
1167
1254
// Usually response_code == 404 if the repository does not exist, and
0 commit comments