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+
1109use crate :: core:: GitReference ;
2110use crate :: util:: errors:: { CargoResult , CargoResultExt } ;
3111use crate :: util:: paths;
@@ -74,7 +182,7 @@ impl GitRemote {
74182 }
75183
76184 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 )
78186 }
79187
80188 pub fn checkout (
@@ -99,7 +207,7 @@ impl GitRemote {
99207 }
100208 }
101209 None => {
102- if let Ok ( rev) = reference. resolve ( & db. repo ) {
210+ if let Ok ( rev) = reference. resolve ( & db. repo , Some ( ( & self . url , cargo_config ) ) ) {
103211 return Ok ( ( db, rev) ) ;
104212 }
105213 }
@@ -118,7 +226,7 @@ impl GitRemote {
118226 . context ( format ! ( "failed to clone into: {}" , into. display( ) ) ) ?;
119227 let rev = match locked_rev {
120228 Some ( rev) => rev,
121- None => reference. resolve ( & repo) ?,
229+ None => reference. resolve ( & repo, Some ( ( & self . url , cargo_config ) ) ) ?,
122230 } ;
123231
124232 Ok ( (
@@ -187,13 +295,21 @@ impl GitDatabase {
187295 self . repo . revparse_single ( & oid. to_string ( ) ) . is_ok ( )
188296 }
189297
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)
192304 }
193305}
194306
195307impl 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 > {
197313 let id = match self {
198314 // Note that we resolve the named tag here in sync with where it's
199315 // fetched into via `fetch` below.
@@ -227,6 +343,28 @@ impl GitReference {
227343 . get ( )
228344 . target ( )
229345 . 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+ }
230368 master
231369 }
232370
@@ -762,6 +900,7 @@ pub fn fetch(
762900 }
763901
764902 GitReference :: DefaultBranch => {
903+ // See the module docs for why we're fetching `master` here.
765904 refspecs. push ( format ! ( "refs/heads/master:refs/remotes/origin/master" ) ) ;
766905 refspecs. push ( String :: from ( "HEAD:refs/remotes/origin/HEAD" ) ) ;
767906 }
@@ -1032,7 +1171,10 @@ fn github_up_to_date(
10321171 handle. useragent ( "cargo" ) ?;
10331172 let mut headers = List :: new ( ) ;
10341173 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+ ) ) ?;
10361178 handle. http_headers ( headers) ?;
10371179 handle. perform ( ) ?;
10381180 Ok ( handle. response_code ( ) ? == 304 )
0 commit comments