@@ -15,6 +15,7 @@ use crate::error::*;
15
15
use crate :: git:: Git ;
16
16
use crate :: sess:: { DependencySource , Session } ;
17
17
use glob:: Pattern ;
18
+ use std:: collections:: HashSet ;
18
19
use std:: path:: Path ;
19
20
use std:: path:: PathBuf ;
20
21
use tempfile:: TempDir ;
@@ -28,6 +29,8 @@ pub struct PatchLink {
28
29
pub from_prefix : PathBuf ,
29
30
/// prefix for local
30
31
pub to_prefix : PathBuf ,
32
+ /// subdirs and files to exclude
33
+ pub exclude : Vec < PathBuf > ,
31
34
}
32
35
33
36
/// Assemble the `vendor` subcommand.
@@ -128,6 +131,7 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
128
131
patch_dir : link. patch_dir ,
129
132
from_prefix : link. from ,
130
133
to_prefix : link. to ,
134
+ exclude : vec ! [ ] ,
131
135
} )
132
136
}
133
137
@@ -138,25 +142,59 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
138
142
patch_dir: vendor_package. patch_dir. clone( ) ,
139
143
from_prefix: PathBuf :: from( "" ) ,
140
144
to_prefix: PathBuf :: from( "" ) ,
145
+ exclude: vec![ ] ,
141
146
} ] ,
142
147
_ => patch_links,
143
148
}
144
149
} ;
145
150
151
+ // sort patch_links so more specific links have priority
152
+ // 1. file links over directory links eg 'a/file -> c/file' before 'b/ -> c/'
153
+ // 2. subdirs (deeper paths) first eg 'a/aa/ -> c/aa' before 'a/ab -> c/'
154
+ let mut sorted_links: Vec < _ > = patch_links. clone ( ) ;
155
+ sorted_links. sort_by ( |a, b| {
156
+ let a_is_file = a. to_prefix . is_file ( ) ;
157
+ let b_is_file = b. to_prefix . is_file ( ) ;
158
+
159
+ if a_is_file != b_is_file {
160
+ return b_is_file. cmp ( & a_is_file) ;
161
+ }
162
+
163
+ let a_depth = a. to_prefix . iter ( ) . count ( ) ;
164
+ let b_depth = b. to_prefix . iter ( ) . count ( ) ;
165
+
166
+ b_depth. cmp ( & a_depth)
167
+ } ) ;
168
+
169
+ // Add all subdirs and files to the exclude list of above dirs
170
+ // avoids duplicate handling of the same changes
171
+ let mut seen_paths: HashSet < PathBuf > = HashSet :: new ( ) ;
172
+ for patch_link in sorted_links. iter_mut ( ) {
173
+ patch_link. exclude = seen_paths
174
+ . iter ( )
175
+ . filter ( |path| path. starts_with ( & patch_link. to_prefix ) ) // subdir?
176
+ . cloned ( )
177
+ . collect ( ) ;
178
+
179
+ seen_paths. insert ( patch_link. to_prefix . clone ( ) ) ;
180
+ }
146
181
let git = Git :: new ( tmp_path, & sess. config . git ) ;
147
182
148
183
match matches. subcommand ( ) {
149
184
Some ( ( "diff" , matches) ) => {
150
185
// Apply patches
151
- patch_links. clone ( ) . into_iter ( ) . try_for_each ( |patch_link| {
152
- apply_patches ( & rt, git, vendor_package. name . clone ( ) , patch_link) . map ( |_| ( ) )
153
- } ) ?;
186
+ sorted_links
187
+ . clone ( )
188
+ . into_iter ( )
189
+ . try_for_each ( |patch_link| {
190
+ apply_patches ( & rt, git, vendor_package. name . clone ( ) , patch_link) . map ( |_| ( ) )
191
+ } ) ?;
154
192
155
193
// Stage applied patches to clean working tree
156
194
rt. block_on ( git. add_all ( ) ) ?;
157
195
158
196
// Print diff for each link
159
- patch_links . into_iter ( ) . try_for_each ( |patch_link| {
197
+ sorted_links . into_iter ( ) . try_for_each ( |patch_link| {
160
198
let get_diff = diff ( & rt, git, vendor_package, patch_link, dep_path. clone ( ) )
161
199
. map_err ( |cause| Error :: chain ( "Failed to get diff." , cause) ) ?;
162
200
if !get_diff. is_empty ( ) {
@@ -176,7 +214,7 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
176
214
}
177
215
178
216
Some ( ( "init" , matches) ) => {
179
- patch_links . clone ( ) . into_iter ( ) . try_for_each ( |patch_link| {
217
+ sorted_links . into_iter ( ) . rev ( ) . try_for_each ( |patch_link| {
180
218
stageln ! ( "Copying" , "{} files from upstream" , vendor_package. name) ;
181
219
// Remove existing directories before importing them again
182
220
let target_path = patch_link
@@ -209,7 +247,7 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
209
247
Some ( ( "patch" , matches) ) => {
210
248
// Apply patches
211
249
let mut num_patches = 0 ;
212
- patch_links
250
+ sorted_links
213
251
. clone ( )
214
252
. into_iter ( )
215
253
. try_for_each ( |patch_link| {
@@ -225,7 +263,7 @@ pub fn run(sess: &Session, matches: &ArgMatches) -> Result<()> {
225
263
}
226
264
227
265
// Generate patch
228
- patch_links . clone ( ) . into_iter ( ) . try_for_each ( |patch_link| {
266
+ sorted_links . into_iter ( ) . try_for_each ( |patch_link| {
229
267
match patch_link. patch_dir . clone ( ) {
230
268
Some ( patch_dir) => {
231
269
if matches. get_flag ( "plain" ) {
@@ -285,7 +323,7 @@ pub fn init(
285
323
286
324
// Check if includes exist
287
325
for path in vendor_package. include_from_upstream . clone ( ) {
288
- if !PathBuf :: from ( extend_paths ( & [ path. clone ( ) ] , dep_path) ?[ 0 ] . clone ( ) ) . exists ( ) {
326
+ if !PathBuf :: from ( extend_paths ( & [ path. clone ( ) ] , dep_path, true ) ?[ 0 ] . clone ( ) ) . exists ( ) {
289
327
warnln ! ( "{} not found in upstream, continuing." , path) ;
290
328
}
291
329
}
@@ -295,7 +333,7 @@ pub fn init(
295
333
true => copy_recursively (
296
334
& link_from,
297
335
& link_to,
298
- & extend_paths ( & vendor_package. include_from_upstream , dep_path) ?,
336
+ & extend_paths ( & vendor_package. include_from_upstream , dep_path, false ) ?,
299
337
& vendor_package
300
338
. exclude_from_upstream
301
339
. clone ( )
@@ -364,24 +402,32 @@ pub fn apply_patches(
364
402
} )
365
403
. and_then ( |_| {
366
404
git. spawn_with ( |c| {
367
- let current_patch_target = if ! patch_link
405
+ let is_file = patch_link
368
406
. from_prefix
369
407
. clone ( )
370
408
. prefix_paths ( git. path )
371
409
. unwrap ( )
372
- . is_file ( )
373
- {
374
- patch_link. from_prefix . as_path ( )
410
+ . is_file ( ) ;
411
+
412
+ let current_patch_target = if is_file {
413
+ patch_link. from_prefix . parent ( ) . unwrap ( ) . to_str ( ) . unwrap ( )
375
414
} else {
376
- patch_link. from_prefix . parent ( ) . unwrap ( )
377
- }
378
- . to_str ( )
379
- . unwrap ( ) ;
415
+ patch_link. from_prefix . as_path ( ) . to_str ( ) . unwrap ( )
416
+ } ;
417
+
380
418
c. arg ( "apply" )
381
419
. arg ( "--directory" )
382
420
. arg ( current_patch_target)
383
421
. arg ( "-p1" )
384
- . arg ( & patch)
422
+ . arg ( & patch) ;
423
+
424
+ // limit to specific file for file links
425
+ if is_file {
426
+ let file_path = patch_link. from_prefix . to_str ( ) . unwrap ( ) ;
427
+ c. arg ( "--include" ) . arg ( file_path) ;
428
+ }
429
+
430
+ c
385
431
} )
386
432
} )
387
433
. await
@@ -428,6 +474,7 @@ pub fn diff(
428
474
& extend_paths (
429
475
& vendor_package. include_from_upstream ,
430
476
& vendor_package. target_dir ,
477
+ false ,
431
478
) ?,
432
479
& vendor_package
433
480
. exclude_from_upstream
@@ -559,30 +606,47 @@ pub fn gen_format_patch(
559
606
// We assume that patch_dir matches Some() was checked outside this function.
560
607
let patch_dir = patch_link. patch_dir . clone ( ) . unwrap ( ) ;
561
608
609
+ // If the patch link maps a file, we operate in the file's parent directory
610
+ // Therefore, only get the diff for that file.
611
+ let include_pathspec = if !to_path. is_dir ( ) {
612
+ patch_link
613
+ . to_prefix
614
+ . file_name ( )
615
+ . unwrap ( )
616
+ . to_str ( )
617
+ . unwrap ( )
618
+ . to_string ( )
619
+ } else {
620
+ "." . to_string ( )
621
+ } ;
622
+
623
+ // Build the exclude pathspec to diff only the applicable files
624
+ let exclude_pathspecs: Vec < String > = patch_link
625
+ . exclude
626
+ . iter ( )
627
+ . map ( |path| format ! ( ":!{}" , path. to_str( ) . unwrap( ) ) )
628
+ . collect ( ) ;
629
+
630
+ let mut diff_args = vec ! [
631
+ "diff" . to_string( ) ,
632
+ "--relative" . to_string( ) ,
633
+ "--cached" . to_string( ) ,
634
+ ] ;
635
+
636
+ diff_args. push ( include_pathspec) ;
637
+ for exclude_path in exclude_pathspecs {
638
+ diff_args. push ( exclude_path) ;
639
+ }
640
+
562
641
// Get staged changes in dependency
563
642
let get_diff_cached = rt
564
- . block_on ( async {
565
- git_parent
566
- . spawn_with ( |c| {
567
- c. arg ( "diff" )
568
- . arg ( "--relative" )
569
- . arg ( "--cached" )
570
- . arg ( if !to_path. is_dir ( ) {
571
- // If the patch link maps a file, we operate in the file's parent
572
- // directory. Therefore, only get the diff for that file.
573
- patch_link. to_prefix . file_name ( ) . unwrap ( ) . to_str ( ) . unwrap ( )
574
- } else {
575
- "."
576
- } )
577
- } )
578
- . await
579
- } )
643
+ . block_on ( async { git_parent. spawn_with ( |c| c. args ( & diff_args) ) . await } )
580
644
. map_err ( |cause| Error :: chain ( "Failed to generate diff" , cause) ) ?;
581
645
582
646
if !get_diff_cached. is_empty ( ) {
583
647
// Write diff into new temp dir. TODO: pipe directly to "git apply"
584
648
let tmp_format_dir = TempDir :: new ( ) ?;
585
- let tmp_format_path = tmp_format_dir. path ( ) ;
649
+ let tmp_format_path = tmp_format_dir. into_path ( ) ;
586
650
let diff_cached_path = tmp_format_path. join ( "staged.diff" ) ;
587
651
std:: fs:: write ( diff_cached_path. clone ( ) , get_diff_cached) ?;
588
652
@@ -729,12 +793,13 @@ pub fn copy_recursively(
729
793
pub fn extend_paths (
730
794
include_from_upstream : & [ String ] ,
731
795
prefix : impl AsRef < Path > ,
796
+ dir_only : bool ,
732
797
) -> Result < Vec < String > > {
733
798
include_from_upstream
734
799
. iter ( )
735
800
. map ( |pattern| {
736
801
let pattern_long = PathBuf :: from ( pattern) . prefix_paths ( prefix. as_ref ( ) ) ?;
737
- if pattern_long. is_dir ( ) {
802
+ if pattern_long. is_dir ( ) && !dir_only {
738
803
Ok ( String :: from ( pattern_long. join ( "**" ) . to_str ( ) . unwrap ( ) ) )
739
804
} else {
740
805
Ok ( String :: from ( pattern_long. to_str ( ) . unwrap ( ) ) )
0 commit comments