1
1
use std:: fs:: { self , File } ;
2
- use std:: io:: SeekFrom ;
3
2
use std:: io:: prelude:: * ;
4
- use std:: path:: { self , Path } ;
3
+ use std:: io:: SeekFrom ;
4
+ use std:: path:: { self , Path , PathBuf } ;
5
5
use std:: sync:: Arc ;
6
6
7
7
use flate2:: read:: GzDecoder ;
8
8
use flate2:: { Compression , GzBuilder } ;
9
9
use git2;
10
+ use serde_json;
10
11
use tar:: { Archive , Builder , EntryType , Header } ;
11
12
12
13
use core:: { Package , Source , SourceId , Workspace } ;
@@ -28,6 +29,8 @@ pub struct PackageOpts<'cfg> {
28
29
pub registry : Option < String > ,
29
30
}
30
31
32
+ static VCS_INFO_FILE : & ' static str = ".cargo_vcs_info.json" ;
33
+
31
34
pub fn package ( ws : & Workspace , opts : & PackageOpts ) -> CargoResult < Option < FileLock > > {
32
35
ops:: resolve_ws ( ws) ?;
33
36
let pkg = ws. current ( ) ?;
@@ -42,6 +45,19 @@ pub fn package(ws: &Workspace, opts: &PackageOpts) -> CargoResult<Option<FileLoc
42
45
43
46
verify_dependencies ( pkg) ?;
44
47
48
+ // `list_files` outputs warnings as a side effect, so only do it once.
49
+ let src_files = src. list_files ( pkg) ?;
50
+
51
+ // Make sure a VCS info file is not included in source, regardless of if
52
+ // we produced the file above, and in particular if we did not.
53
+ check_vcs_file_collision ( pkg, & src_files) ?;
54
+
55
+ // Check (git) repository state, getting the current commit hash if not
56
+ // dirty. This will `bail!` if dirty, unless allow_dirty. Produce json
57
+ // info for any sha1 (HEAD revision) returned.
58
+ let vcs_info = check_repo_state ( pkg, & src_files, & config, opts. allow_dirty ) ?
59
+ . map ( |h| json ! ( { "git" : { "sha1" : h} } ) ) ;
60
+
45
61
if opts. list {
46
62
let root = pkg. root ( ) ;
47
63
let mut list: Vec < _ > = src. list_files ( pkg) ?
@@ -51,17 +67,16 @@ pub fn package(ws: &Workspace, opts: &PackageOpts) -> CargoResult<Option<FileLoc
51
67
if include_lockfile ( pkg) {
52
68
list. push ( "Cargo.lock" . into ( ) ) ;
53
69
}
70
+ if vcs_info. is_some ( ) {
71
+ list. push ( Path :: new ( VCS_INFO_FILE ) . to_path_buf ( ) ) ;
72
+ }
54
73
list. sort_unstable ( ) ;
55
74
for file in list. iter ( ) {
56
75
println ! ( "{}" , file. display( ) ) ;
57
76
}
58
77
return Ok ( None ) ;
59
78
}
60
79
61
- if !opts. allow_dirty {
62
- check_not_dirty ( pkg, & src, & config) ?;
63
- }
64
-
65
80
let filename = format ! ( "{}-{}.crate" , pkg. name( ) , pkg. version( ) ) ;
66
81
let dir = ws. target_dir ( ) . join ( "package" ) ;
67
82
let mut dst = {
@@ -77,7 +92,7 @@ pub fn package(ws: &Workspace, opts: &PackageOpts) -> CargoResult<Option<FileLoc
77
92
. shell ( )
78
93
. status ( "Packaging" , pkg. package_id ( ) . to_string ( ) ) ?;
79
94
dst. file ( ) . set_len ( 0 ) ?;
80
- tar ( ws, & src , dst. file ( ) , & filename)
95
+ tar ( ws, & src_files , vcs_info . as_ref ( ) , dst. file ( ) , & filename)
81
96
. chain_err ( || format_err ! ( "failed to prepare local package for uploading" ) ) ?;
82
97
if opts. verify {
83
98
dst. seek ( SeekFrom :: Start ( 0 ) ) ?;
@@ -152,7 +167,16 @@ fn verify_dependencies(pkg: &Package) -> CargoResult<()> {
152
167
Ok ( ( ) )
153
168
}
154
169
155
- fn check_not_dirty ( p : & Package , src : & PathSource , config : & Config ) -> CargoResult < ( ) > {
170
+ // Check if the package source is in a *git* DVCS repository. If *git*, and
171
+ // the source is *dirty* (e.g. has uncommited changes) and not `allow_dirty`
172
+ // then `bail!` with an informative message. Otherwise return the sha1 hash of
173
+ // the current *HEAD* commit, or `None` if *dirty*.
174
+ fn check_repo_state (
175
+ p : & Package ,
176
+ src_files : & [ PathBuf ] ,
177
+ config : & Config ,
178
+ allow_dirty : bool
179
+ ) -> CargoResult < Option < String > > {
156
180
if let Ok ( repo) = git2:: Repository :: discover ( p. root ( ) ) {
157
181
if let Some ( workdir) = repo. workdir ( ) {
158
182
debug ! ( "found a git repo at {:?}" , workdir) ;
@@ -164,7 +188,7 @@ fn check_not_dirty(p: &Package, src: &PathSource, config: &Config) -> CargoResul
164
188
"found (git) Cargo.toml at {:?} in workdir {:?}" ,
165
189
path, workdir
166
190
) ;
167
- return git ( p, src , & repo) ;
191
+ return git ( p, src_files , & repo, allow_dirty ) ;
168
192
}
169
193
}
170
194
config. shell ( ) . verbose ( |shell| {
@@ -182,11 +206,16 @@ fn check_not_dirty(p: &Package, src: &PathSource, config: &Config) -> CargoResul
182
206
183
207
// No VCS with a checked in Cargo.toml found. so we don't know if the
184
208
// directory is dirty or not, so we have to assume that it's clean.
185
- return Ok ( ( ) ) ;
186
-
187
- fn git ( p : & Package , src : & PathSource , repo : & git2:: Repository ) -> CargoResult < ( ) > {
209
+ return Ok ( None ) ;
210
+
211
+ fn git (
212
+ p : & Package ,
213
+ src_files : & [ PathBuf ] ,
214
+ repo : & git2:: Repository ,
215
+ allow_dirty : bool
216
+ ) -> CargoResult < Option < String > > {
188
217
let workdir = repo. workdir ( ) . unwrap ( ) ;
189
- let dirty = src . list_files ( p ) ?
218
+ let dirty = src_files
190
219
. iter ( )
191
220
. filter ( |file| {
192
221
let relative = file. strip_prefix ( workdir) . unwrap ( ) ;
@@ -204,20 +233,46 @@ fn check_not_dirty(p: &Package, src: &PathSource, config: &Config) -> CargoResul
204
233
} )
205
234
. collect :: < Vec < _ > > ( ) ;
206
235
if dirty. is_empty ( ) {
207
- Ok ( ( ) )
236
+ let rev_obj = repo. revparse_single ( "HEAD" ) ?;
237
+ Ok ( Some ( rev_obj. id ( ) . to_string ( ) ) )
208
238
} else {
209
- bail ! (
210
- "{} files in the working directory contain changes that were \
211
- not yet committed into git:\n \n {}\n \n \
212
- to proceed despite this, pass the `--allow-dirty` flag",
213
- dirty. len( ) ,
214
- dirty. join( "\n " )
215
- )
239
+ if !allow_dirty {
240
+ bail ! (
241
+ "{} files in the working directory contain changes that were \
242
+ not yet committed into git:\n \n {}\n \n \
243
+ to proceed despite this, pass the `--allow-dirty` flag",
244
+ dirty. len( ) ,
245
+ dirty. join( "\n " )
246
+ )
247
+ }
248
+ Ok ( None )
216
249
}
217
250
}
218
251
}
219
252
220
- fn tar ( ws : & Workspace , src : & PathSource , dst : & File , filename : & str ) -> CargoResult < ( ) > {
253
+ // Check for and `bail!` if a source file matches ROOT/VCS_INFO_FILE, since
254
+ // this is now a cargo reserved file name, and we don't want to allow
255
+ // forgery.
256
+ fn check_vcs_file_collision ( pkg : & Package , src_files : & [ PathBuf ] ) -> CargoResult < ( ) > {
257
+ let root = pkg. root ( ) ;
258
+ let vcs_info_path = Path :: new ( VCS_INFO_FILE ) ;
259
+ let collision = src_files. iter ( ) . find ( |& p| {
260
+ util:: without_prefix ( & p, root) . unwrap ( ) == vcs_info_path
261
+ } ) ;
262
+ if collision. is_some ( ) {
263
+ bail ! ( "Invalid inclusion of reserved file name \
264
+ {} in package source", VCS_INFO_FILE ) ;
265
+ }
266
+ Ok ( ( ) )
267
+ }
268
+
269
+ fn tar (
270
+ ws : & Workspace ,
271
+ src_files : & [ PathBuf ] ,
272
+ vcs_info : Option < & serde_json:: Value > ,
273
+ dst : & File ,
274
+ filename : & str
275
+ ) -> CargoResult < ( ) > {
221
276
// Prepare the encoder and its header
222
277
let filename = Path :: new ( filename) ;
223
278
let encoder = GzBuilder :: new ( )
@@ -229,7 +284,7 @@ fn tar(ws: &Workspace, src: &PathSource, dst: &File, filename: &str) -> CargoRes
229
284
let pkg = ws. current ( ) ?;
230
285
let config = ws. config ( ) ;
231
286
let root = pkg. root ( ) ;
232
- for file in src . list_files ( pkg ) ? . iter ( ) {
287
+ for file in src_files . iter ( ) {
233
288
let relative = util:: without_prefix ( file, root) . unwrap ( ) ;
234
289
check_filename ( relative) ?;
235
290
let relative = relative. to_str ( ) . ok_or_else ( || {
@@ -297,6 +352,36 @@ fn tar(ws: &Workspace, src: &PathSource, dst: &File, filename: &str) -> CargoRes
297
352
}
298
353
}
299
354
355
+ if let Some ( ref json) = vcs_info {
356
+ let filename: PathBuf = Path :: new ( VCS_INFO_FILE ) . into ( ) ;
357
+ debug_assert ! ( check_filename( & filename) . is_ok( ) ) ;
358
+ let fnd = filename. display ( ) ;
359
+ config
360
+ . shell ( )
361
+ . verbose ( |shell| shell. status ( "Archiving" , & fnd) ) ?;
362
+ let path = format ! (
363
+ "{}-{}{}{}" ,
364
+ pkg. name( ) ,
365
+ pkg. version( ) ,
366
+ path:: MAIN_SEPARATOR ,
367
+ fnd
368
+ ) ;
369
+ let mut header = Header :: new_ustar ( ) ;
370
+ header. set_path ( & path) . chain_err ( || {
371
+ format ! ( "failed to add to archive: `{}`" , fnd)
372
+ } ) ?;
373
+ let json = format ! ( "{}\n " , serde_json:: to_string_pretty( json) ?) ;
374
+ let mut header = Header :: new_ustar ( ) ;
375
+ header. set_path ( & path) ?;
376
+ header. set_entry_type ( EntryType :: file ( ) ) ;
377
+ header. set_mode ( 0o644 ) ;
378
+ header. set_size ( json. len ( ) as u64 ) ;
379
+ header. set_cksum ( ) ;
380
+ ar. append ( & header, json. as_bytes ( ) ) . chain_err ( || {
381
+ internal ( format ! ( "could not archive source file `{}`" , fnd) )
382
+ } ) ?;
383
+ }
384
+
300
385
if include_lockfile ( pkg) {
301
386
let toml = paths:: read ( & ws. root ( ) . join ( "Cargo.lock" ) ) ?;
302
387
let path = format ! (
0 commit comments