@@ -10,6 +10,8 @@ use serde::Serialize;
10
10
use tracing:: debug;
11
11
12
12
use crate :: core:: Package ;
13
+ use crate :: core:: Workspace ;
14
+ use crate :: ops:: lockfile:: LOCKFILE_NAME ;
13
15
use crate :: sources:: PathEntry ;
14
16
use crate :: CargoResult ;
15
17
use crate :: GlobalContext ;
@@ -44,12 +46,16 @@ pub struct GitVcsInfo {
44
46
pub fn check_repo_state (
45
47
p : & Package ,
46
48
src_files : & [ PathEntry ] ,
47
- gctx : & GlobalContext ,
49
+ ws : & Workspace < ' _ > ,
48
50
opts : & PackageOpts < ' _ > ,
49
51
) -> CargoResult < Option < VcsInfo > > {
52
+ let gctx = ws. gctx ( ) ;
50
53
let Ok ( repo) = git2:: Repository :: discover ( p. root ( ) ) else {
51
54
gctx. shell ( ) . verbose ( |shell| {
52
- shell. warn ( format ! ( "no (git) VCS found for `{}`" , p. root( ) . display( ) ) )
55
+ shell. warn ( format_args ! (
56
+ "no (git) VCS found for `{}`" ,
57
+ p. root( ) . display( )
58
+ ) )
53
59
} ) ?;
54
60
// No Git repo found. Have to assume it is clean.
55
61
return Ok ( None ) ;
@@ -69,7 +75,7 @@ pub fn check_repo_state(
69
75
let path = paths:: strip_prefix_canonical ( path, workdir) . unwrap_or_else ( |_| path. to_path_buf ( ) ) ;
70
76
let Ok ( status) = repo. status_file ( & path) else {
71
77
gctx. shell ( ) . verbose ( |shell| {
72
- shell. warn ( format ! (
78
+ shell. warn ( format_args ! (
73
79
"no (git) Cargo.toml found at `{}` in workdir `{}`" ,
74
80
path. display( ) ,
75
81
workdir. display( )
@@ -82,7 +88,7 @@ pub fn check_repo_state(
82
88
83
89
if !( status & git2:: Status :: IGNORED ) . is_empty ( ) {
84
90
gctx. shell ( ) . verbose ( |shell| {
85
- shell. warn ( format ! (
91
+ shell. warn ( format_args ! (
86
92
"found (git) Cargo.toml ignored at `{}` in workdir `{}`" ,
87
93
path. display( ) ,
88
94
workdir. display( )
@@ -100,16 +106,17 @@ pub fn check_repo_state(
100
106
path. display( ) ,
101
107
workdir. display( ) ,
102
108
) ;
109
+ let Some ( git) = git ( ws, p, src_files, & repo, & opts) ? else {
110
+ // If the git repo lacks essensial field like `sha1`, and since this field exists from the beginning,
111
+ // then don't generate the corresponding file in order to maintain consistency with past behavior.
112
+ return Ok ( None ) ;
113
+ } ;
114
+
103
115
let path_in_vcs = path
104
116
. parent ( )
105
117
. and_then ( |p| p. to_str ( ) )
106
118
. unwrap_or ( "" )
107
119
. replace ( "\\ " , "/" ) ;
108
- let Some ( git) = git ( p, gctx, src_files, & repo, & opts) ? else {
109
- // If the git repo lacks essensial field like `sha1`, and since this field exists from the beginning,
110
- // then don't generate the corresponding file in order to maintain consistency with past behavior.
111
- return Ok ( None ) ;
112
- } ;
113
120
114
121
return Ok ( Some ( VcsInfo { git, path_in_vcs } ) ) ;
115
122
}
@@ -162,8 +169,8 @@ fn warn_symlink_checked_out_as_plain_text_file(
162
169
163
170
/// The real git status check starts from here.
164
171
fn git (
172
+ ws : & Workspace < ' _ > ,
165
173
pkg : & Package ,
166
- gctx : & GlobalContext ,
167
174
src_files : & [ PathEntry ] ,
168
175
repo : & git2:: Repository ,
169
176
opts : & PackageOpts < ' _ > ,
@@ -184,12 +191,12 @@ fn git(
184
191
// Find the intersection of dirty in git, and the src_files that would
185
192
// be packaged. This is a lazy n^2 check, but seems fine with
186
193
// thousands of files.
187
- let cwd = gctx. cwd ( ) ;
194
+ let cwd = ws . gctx ( ) . cwd ( ) ;
188
195
let mut dirty_src_files: Vec < _ > = src_files
189
196
. iter ( )
190
197
. filter ( |src_file| dirty_files. iter ( ) . any ( |path| src_file. starts_with ( path) ) )
191
198
. map ( |p| p. as_ref ( ) )
192
- . chain ( dirty_files_outside_pkg_root ( pkg, repo, src_files) ?. iter ( ) )
199
+ . chain ( dirty_files_outside_pkg_root ( ws , pkg, repo, src_files) ?. iter ( ) )
193
200
. map ( |path| {
194
201
pathdiff:: diff_paths ( path, cwd)
195
202
. as_ref ( )
@@ -228,41 +235,79 @@ fn git(
228
235
///
229
236
/// * `package.readme` and `package.license-file` pointing to paths outside package root
230
237
/// * symlinks targets reside outside package root
238
+ /// * Any change in the root workspace manifest, regardless of what has changed.
239
+ /// * Changes in the lockfile [^1].
231
240
///
232
241
/// This is required because those paths may link to a file outside the
233
242
/// current package root, but still under the git workdir, affecting the
234
243
/// final packaged `.crate` file.
244
+ ///
245
+ /// [^1]: Lockfile might be re-generated if it is too out of sync with the manifest.
246
+ /// Therefore, even you have a modified lockfile,
247
+ /// you might still get a new fresh one that matches what is in git index.
235
248
fn dirty_files_outside_pkg_root (
249
+ ws : & Workspace < ' _ > ,
236
250
pkg : & Package ,
237
251
repo : & git2:: Repository ,
238
252
src_files : & [ PathEntry ] ,
239
253
) -> CargoResult < HashSet < PathBuf > > {
240
254
let pkg_root = pkg. root ( ) ;
241
255
let workdir = repo. workdir ( ) . unwrap ( ) ;
242
256
257
+ let mut dirty_files = HashSet :: new ( ) ;
258
+
243
259
let meta = pkg. manifest ( ) . metadata ( ) ;
244
260
let metadata_paths: Vec < _ > = [ & meta. license_file , & meta. readme ]
245
261
. into_iter ( )
246
262
. filter_map ( |p| p. as_deref ( ) )
247
263
. map ( |path| paths:: normalize_path ( & pkg_root. join ( path) ) )
248
264
. collect ( ) ;
249
265
250
- let mut dirty_symlinks = HashSet :: new ( ) ;
266
+ // Unlike other files, lockfile is allowed to be missing,
267
+ // and can be generated during packaging.
268
+ // We skip checking when it is missing in both workdir and git index,
269
+ // otherwise cargo will fail with git2 not found error.
270
+ let lockfile_path = ws. lock_root ( ) . as_path_unlocked ( ) . join ( LOCKFILE_NAME ) ;
271
+ let lockfile_path = if lockfile_path. exists ( ) {
272
+ Some ( lockfile_path)
273
+ } else if let Ok ( rel_path) = paths:: normalize_path ( & lockfile_path) . strip_prefix ( workdir) {
274
+ // We don't canonicalize here because non-existing path can't be canonicalized.
275
+ match repo. status_file ( & rel_path) {
276
+ Ok ( s) if s != git2:: Status :: CURRENT => {
277
+ dirty_files. insert ( lockfile_path) ;
278
+ }
279
+ // Unmodified
280
+ Ok ( _) => { }
281
+ Err ( e) => {
282
+ debug ! (
283
+ "check git status failed for `{}` in workdir `{}`: {e}" ,
284
+ rel_path. display( ) ,
285
+ workdir. display( ) ,
286
+ ) ;
287
+ }
288
+ }
289
+ None
290
+ } else {
291
+ None
292
+ } ;
293
+
251
294
for rel_path in src_files
252
295
. iter ( )
253
296
. filter ( |p| p. is_symlink_or_under_symlink ( ) )
254
- . map ( |p| p. as_ref ( ) )
255
- . chain ( metadata_paths. iter ( ) )
297
+ . map ( |p| p. as_ref ( ) . as_path ( ) )
298
+ . chain ( metadata_paths. iter ( ) . map ( AsRef :: as_ref) )
299
+ . chain ( [ ws. root_manifest ( ) ] )
300
+ . chain ( lockfile_path. as_deref ( ) . into_iter ( ) )
256
301
// If inside package root. Don't bother checking git status.
257
302
. filter ( |p| paths:: strip_prefix_canonical ( p, pkg_root) . is_err ( ) )
258
303
// Handle files outside package root but under git workdir,
259
304
. filter_map ( |p| paths:: strip_prefix_canonical ( p, workdir) . ok ( ) )
260
305
{
261
306
if repo. status_file ( & rel_path) ? != git2:: Status :: CURRENT {
262
- dirty_symlinks . insert ( workdir. join ( rel_path) ) ;
307
+ dirty_files . insert ( workdir. join ( rel_path) ) ;
263
308
}
264
309
}
265
- Ok ( dirty_symlinks )
310
+ Ok ( dirty_files )
266
311
}
267
312
268
313
/// Helper to collect dirty statuses for a single repo.
0 commit comments