1
+ //! Utilities for handling git repositories, mainly around
2
+ //! authentication/cloning.
3
+ //!
4
+ //! # `DefaultBranch` vs `Branch("master")`
5
+ //!
6
+ //! Long ago in a repository not so far away, an author (*cough* me *cough*)
7
+ //! didn't understand how branches work in Git. This led the author to
8
+ //! interpret these two dependency declarations the exact same way with the
9
+ //! former literally internally desugaring to the latter:
10
+ //!
11
+ //! ```toml
12
+ //! [dependencies]
13
+ //! foo = { git = "https://example.org/foo" }
14
+ //! foo = { git = "https://example.org/foo", branch = "master" }
15
+ //! ```
16
+ //!
17
+ //! It turns out there's this things called `HEAD` in git remotes which points
18
+ //! to the "main branch" of a repository, and the main branch is not always
19
+ //! literally called master. What Cargo would like to do is to differentiate
20
+ //! these two dependency directives, with the first meaning "depend on `HEAD`".
21
+ //!
22
+ //! Unfortunately implementing this is a breaking change. This was first
23
+ //! attempted in #8364 but resulted in #8468 which has two independent bugs
24
+ //! listed on that issue. Despite this breakage we would still like to roll out
25
+ //! this change in Cargo, but we're now going to take it very slow and try to
26
+ //! break as few people as possible along the way. These comments are intended
27
+ //! to log the current progress and what wonkiness you might see within Cargo
28
+ //! when handling `DefaultBranch` vs `Branch("master")`
29
+ //!
30
+ //! ### Repositories with `master` and a default branch
31
+ //!
32
+ //! This is one of the most obvious sources of breakage. If our `foo` example
33
+ //! in above had two branches, one called `master` and another which was
34
+ //! actually the main branch, then Cargo's change will always be a breaking
35
+ //! change. This is because what's downloaded is an entirely different branch
36
+ //! if we change the meaning of the dependency directive.
37
+ //!
38
+ //! It's expected this is quite rare, but to handle this case nonetheless when
39
+ //! Cargo fetches from a git remote and the dependency specification is
40
+ //! `DefaultBranch` then it will issue a warning if the `HEAD` reference
41
+ //! doesn't match `master`. It's expected in this situation that authors will
42
+ //! fix builds locally by specifying `branch = 'master'`.
43
+ //!
44
+ //! ### Differences in `cargo vendor` configuration
45
+ //!
46
+ //! When executing `cargo vendor` it will print out configuration which can
47
+ //! then be used to configure Cargo to use the `vendor` directory. Historically
48
+ //! this configuration looked like:
49
+ //!
50
+ //! ```toml
51
+ //! [source."https://example.org/foo"]
52
+ //! git = "https://example.org/foo"
53
+ //! branch = "master"
54
+ //! replace-with = "vendored-sources"
55
+ //! ```
56
+ //!
57
+ //! We would like to, however, transition this to not include the `branch =
58
+ //! "master"` unless the dependency directive actually mentions a branch.
59
+ //! Conveniently older Cargo implementations all interpret a missing `branch`
60
+ //! as `branch = "master"` so it's a backwards-compatible change to remove the
61
+ //! `branch = "master"` directive. As a result, `cargo vendor` will no longer
62
+ //! emit a `branch` if the git reference is `DefaultBranch`
63
+ //!
64
+ //! ### Differences in lock file formats
65
+ //!
66
+ //! Another issue pointed out in #8364 was that `Cargo.lock` files were no
67
+ //! longer compatible on stable and nightly with each other. The underlying
68
+ //! issue is that Cargo was serializing `branch = "master"` *differently* on
69
+ //! nightly than it was on stable. Historical implementations of Cargo
70
+ //! desugared `branch = "master"` to having not dependency directives in
71
+ //! `Cargo.lock`, which means when reading `Cargo.lock` we can't differentiate
72
+ //! what's for the default branch and what's for the `master` branch.
73
+ //!
74
+ //! To handle this difference in encoding of `Cargo.lock` we'll be employing
75
+ //! the standard scheme to change `Cargo.lock`:
76
+ //!
77
+ //! * Add support in Cargo for a future format, don't turn it on.
78
+ //! * Wait a long time
79
+ //! * Turn on the future format
80
+ //!
81
+ //! Here the "future format" is `branch=master` shows up if you have a `branch`
82
+ //! in `Cargo.toml`, and otherwise nothing shows up in URLs. Due to the effect
83
+ //! on crate graph resolution, however, this flows into the next point..
84
+ //!
85
+ //! ### Unification in the Cargo dependency graph
86
+ //!
87
+ //! Today dependencies with `branch = "master"` will unify with dependencies
88
+ //! that say nothing. (that's because the latter simply desugars). This means
89
+ //! the two `foo` directives above will resolve to the same dependency.
90
+ //!
91
+ //! The best idea I've got to fix this is to basically get everyone (if anyone)
92
+ //! to stop doing this today. The crate graph resolver will start to warn if it
93
+ //! detects that multiple `Cargo.toml` directives are detected and mixed. The
94
+ //! thinking is that when we turn on the new lock file format it'll also be
95
+ //! hard breaking change for any project which still has dependencies to
96
+ //! both the `master` branch and not.
97
+ //!
98
+ //! ### What we're doing today
99
+ //!
100
+ //! The general goal of Cargo today is to internally distinguish
101
+ //! `DefaultBranch` and `Branch("master")`, but for the time being they should
102
+ //! be functionally equivalent in terms of builds. The hope is that we'll let
103
+ //! all these warnings and such bake for a good long time, and eventually we'll
104
+ //! flip some switches if your build has no warnings it'll work before and
105
+ //! after.
106
+ //!
107
+ //! That's the dream at least, we'll see how this plays out.
108
+
1
109
use crate :: core:: GitReference ;
2
110
use crate :: util:: errors:: { CargoResult , CargoResultExt } ;
3
111
use crate :: util:: paths;
@@ -74,7 +182,7 @@ impl GitRemote {
74
182
}
75
183
76
184
pub fn rev_for ( & self , path : & Path , reference : & GitReference ) -> CargoResult < git2:: Oid > {
77
- reference. resolve ( & self . db_at ( path) ?. repo )
185
+ reference. resolve ( & self . db_at ( path) ?. repo , None )
78
186
}
79
187
80
188
pub fn checkout (
@@ -99,7 +207,7 @@ impl GitRemote {
99
207
}
100
208
}
101
209
None => {
102
- if let Ok ( rev) = reference. resolve ( & db. repo ) {
210
+ if let Ok ( rev) = reference. resolve ( & db. repo , Some ( ( & self . url , cargo_config ) ) ) {
103
211
return Ok ( ( db, rev) ) ;
104
212
}
105
213
}
@@ -118,7 +226,7 @@ impl GitRemote {
118
226
. context ( format ! ( "failed to clone into: {}" , into. display( ) ) ) ?;
119
227
let rev = match locked_rev {
120
228
Some ( rev) => rev,
121
- None => reference. resolve ( & repo) ?,
229
+ None => reference. resolve ( & repo, Some ( ( & self . url , cargo_config ) ) ) ?,
122
230
} ;
123
231
124
232
Ok ( (
@@ -187,13 +295,21 @@ impl GitDatabase {
187
295
self . repo . revparse_single ( & oid. to_string ( ) ) . is_ok ( )
188
296
}
189
297
190
- pub fn resolve ( & self , r : & GitReference ) -> CargoResult < git2:: Oid > {
191
- r. resolve ( & self . repo )
298
+ pub fn resolve (
299
+ & self ,
300
+ r : & GitReference ,
301
+ remote_and_config : Option < ( & Url , & Config ) > ,
302
+ ) -> CargoResult < git2:: Oid > {
303
+ r. resolve ( & self . repo , remote_and_config)
192
304
}
193
305
}
194
306
195
307
impl GitReference {
196
- pub fn resolve ( & self , repo : & git2:: Repository ) -> CargoResult < git2:: Oid > {
308
+ pub fn resolve (
309
+ & self ,
310
+ repo : & git2:: Repository ,
311
+ remote_and_config : Option < ( & Url , & Config ) > ,
312
+ ) -> CargoResult < git2:: Oid > {
197
313
let id = match self {
198
314
// Note that we resolve the named tag here in sync with where it's
199
315
// fetched into via `fetch` below.
@@ -227,6 +343,28 @@ impl GitReference {
227
343
. get ( )
228
344
. target ( )
229
345
. ok_or_else ( || anyhow:: format_err!( "branch `master` did not have a target" ) ) ?;
346
+
347
+ if let Some ( ( remote, config) ) = remote_and_config {
348
+ let head_id = repo. refname_to_id ( "refs/remotes/origin/HEAD" ) ?;
349
+ let head = repo. find_object ( head_id, None ) ?;
350
+ let head = head. peel ( ObjectType :: Commit ) ?. id ( ) ;
351
+
352
+ if head != master {
353
+ config. shell ( ) . warn ( & format ! (
354
+ "\
355
+ fetching `master` branch from `{}` but the `HEAD` \
356
+ reference for this repository is not the \
357
+ `master` branch. This behavior will change \
358
+ in Cargo in the future and your build may \
359
+ break, so it's recommended to place \
360
+ `branch = \" master\" ` in Cargo.toml when \
361
+ depending on this git repository to ensure \
362
+ that your build will continue to work.\
363
+ ",
364
+ remote,
365
+ ) ) ?;
366
+ }
367
+ }
230
368
master
231
369
}
232
370
@@ -762,6 +900,7 @@ pub fn fetch(
762
900
}
763
901
764
902
GitReference :: DefaultBranch => {
903
+ // See the module docs for why we're fetching `master` here.
765
904
refspecs. push ( format ! ( "refs/heads/master:refs/remotes/origin/master" ) ) ;
766
905
refspecs. push ( String :: from ( "HEAD:refs/remotes/origin/HEAD" ) ) ;
767
906
}
@@ -1032,7 +1171,10 @@ fn github_up_to_date(
1032
1171
handle. useragent ( "cargo" ) ?;
1033
1172
let mut headers = List :: new ( ) ;
1034
1173
headers. append ( "Accept: application/vnd.github.3.sha" ) ?;
1035
- headers. append ( & format ! ( "If-None-Match: \" {}\" " , reference. resolve( repo) ?) ) ?;
1174
+ headers. append ( & format ! (
1175
+ "If-None-Match: \" {}\" " ,
1176
+ reference. resolve( repo, None ) ?
1177
+ ) ) ?;
1036
1178
handle. http_headers ( headers) ?;
1037
1179
handle. perform ( ) ?;
1038
1180
Ok ( handle. response_code ( ) ? == 304 )
0 commit comments