@@ -8,7 +8,6 @@ use std::{
8
8
} ;
9
9
10
10
use anyhow:: { anyhow, Result } ;
11
- use bstr:: ByteSlice ;
12
11
use clap:: { Arg , ArgGroup , ArgMatches , ValueHint } ;
13
12
use indexmap:: IndexSet ;
14
13
@@ -19,10 +18,12 @@ use crate::{
19
18
index:: TemporaryIndex ,
20
19
patchedit,
21
20
patchname:: PatchName ,
22
- pathspec,
23
21
signature:: SignatureExtended ,
24
22
stack:: { Error , Stack , StackStateAccess } ,
25
- stupid:: Stupid ,
23
+ stupid:: {
24
+ status:: { Status , StatusEntryKind , StatusOptions , Statuses } ,
25
+ Stupid , StupidContext ,
26
+ } ,
26
27
} ;
27
28
28
29
pub ( super ) fn get_command ( ) -> ( & ' static str , super :: StGitCommand ) {
@@ -338,82 +339,36 @@ fn run(matches: &ArgMatches) -> Result<()> {
338
339
}
339
340
340
341
fn determine_refresh_paths (
341
- repo : & git2 :: Repository ,
342
- pathspecs : Option < clap :: parser :: ValuesRef < PathBuf > > ,
342
+ stupid : & StupidContext ,
343
+ statuses : & Statuses ,
343
344
patch_commit : Option < & git2:: Commit > ,
344
- use_submodules : bool ,
345
345
force : bool ,
346
346
) -> Result < IndexSet < PathBuf > > {
347
- let mut status_opts = git2:: StatusOptions :: new ( ) ;
348
- status_opts. show ( git2:: StatusShow :: IndexAndWorkdir ) ;
349
- status_opts. exclude_submodules ( !use_submodules) ;
350
-
351
- if let Some ( pathspecs) = pathspecs {
352
- let workdir = repo. workdir ( ) . expect ( "not a bare repository" ) ;
353
- let curdir = std:: env:: current_dir ( ) ?;
354
-
355
- for pathspec in pathspecs {
356
- let norm_pathspec =
357
- pathspec:: normalize_pathspec ( workdir, & curdir, Path :: new ( pathspec) ) ?;
358
- status_opts. pathspec ( norm_pathspec) ;
359
- }
360
- }
361
-
362
- let mut refresh_paths: IndexSet < PathBuf > = repo
363
- . statuses ( Some ( & mut status_opts) ) ?
364
- . iter ( )
365
- . map ( |entry| PathBuf :: from ( path_from_bytes ( entry. path_bytes ( ) ) ) )
366
- . collect ( ) ;
367
-
368
- if let Some ( patch_commit) = patch_commit {
347
+ let refresh_paths: IndexSet < & Path > = if let Some ( patch_commit) = patch_commit {
369
348
// Restrict update to the paths that were already part of the patch.
370
- let patch_tree = patch_commit. tree ( ) ?;
371
- let parent_tree = patch_commit. parent ( 0 ) ?. tree ( ) ?;
372
- let mut diff_opts = git2:: DiffOptions :: new ( ) ;
373
- diff_opts. ignore_submodules ( !use_submodules) ;
374
- diff_opts. force_binary ( true ) ; // Less expensive(?)
375
-
376
- let mut patch_paths: IndexSet < PathBuf > = IndexSet :: new ( ) ;
377
-
378
- repo. diff_tree_to_tree ( Some ( & parent_tree) , Some ( & patch_tree) , Some ( & mut diff_opts) ) ?
379
- . foreach (
380
- & mut |delta, _| {
381
- if let Some ( old_path) = delta. old_file ( ) . path ( ) {
382
- patch_paths. insert ( old_path. to_owned ( ) ) ;
383
- }
384
- if let Some ( new_path) = delta. new_file ( ) . path ( ) {
385
- patch_paths. insert ( new_path. to_owned ( ) ) ;
386
- }
387
- true
388
- } ,
389
- None ,
390
- None ,
391
- None ,
392
- ) ?;
393
-
394
- // Set intersection to determine final subset of paths.
395
- refresh_paths. retain ( |path| patch_paths. contains ( path) ) ;
396
- }
349
+ let parent_tree_id = patch_commit. parent ( 0 ) ?. tree_id ( ) ;
350
+ let diff_files = stupid. diff_tree_files ( parent_tree_id, patch_commit. tree_id ( ) ) ?;
351
+ let patch_paths = diff_files. iter ( ) . collect :: < IndexSet < _ > > ( ) ;
352
+
353
+ statuses
354
+ . iter ( )
355
+ . filter_map ( |entry| {
356
+ let path = entry. path ( ) ;
357
+ if patch_paths. contains ( path) {
358
+ Some ( path)
359
+ } else {
360
+ None
361
+ }
362
+ } )
363
+ . collect ( )
364
+ } else {
365
+ statuses. iter ( ) . map ( |entry| entry. path ( ) ) . collect ( )
366
+ } ;
397
367
398
368
// Ensure no conflicts in the files to be refreshed.
399
- if repo
400
- . index ( ) ?
401
- . conflicts ( ) ?
402
- . filter_map ( |maybe_entry| maybe_entry. ok ( ) )
403
- . any ( |conflict| {
404
- if let ( Some ( our) , Some ( their) ) = ( & conflict. our , & conflict. their ) {
405
- refresh_paths. contains ( path_from_bytes ( & our. path ) )
406
- || ( their. path != our. path
407
- && refresh_paths. contains ( path_from_bytes ( & their. path ) ) )
408
- } else if let Some ( our) = conflict. our {
409
- refresh_paths. contains ( path_from_bytes ( & our. path ) )
410
- } else if let Some ( their) = conflict. their {
411
- refresh_paths. contains ( path_from_bytes ( & their. path ) )
412
- } else {
413
- false
414
- }
415
- } )
416
- {
369
+ if statuses. iter ( ) . any ( |entry| {
370
+ matches ! ( entry. kind( ) , StatusEntryKind :: Unmerged ) && refresh_paths. contains ( entry. path ( ) )
371
+ } ) {
417
372
return Err ( Error :: OutstandingConflicts . into ( ) ) ;
418
373
}
419
374
@@ -422,100 +377,108 @@ fn determine_refresh_paths(
422
377
// If not forcing, all changes must be either in the index or worktree,
423
378
// but not both.
424
379
if !force {
425
- let mut status_opts = git2:: StatusOptions :: new ( ) ;
426
- status_opts. show ( git2:: StatusShow :: Index ) ;
427
- status_opts. exclude_submodules ( !use_submodules) ;
428
- let is_index_clean = repo. statuses ( Some ( & mut status_opts) ) ?. is_empty ( ) ;
429
-
430
- if !is_index_clean {
431
- let mut status_opts = git2:: StatusOptions :: new ( ) ;
432
- status_opts. show ( git2:: StatusShow :: Workdir ) ;
433
- status_opts. exclude_submodules ( !use_submodules) ;
434
- let is_worktree_clean = repo. statuses ( Some ( & mut status_opts) ) ?. is_empty ( ) ;
435
-
436
- if !is_worktree_clean {
380
+ let mut is_index_clean = true ;
381
+ let mut is_worktree_clean = true ;
382
+ for entry in statuses. iter ( ) {
383
+ if !matches ! ( entry. index_status( ) , Status :: Unmodified ) {
384
+ is_index_clean = false ;
385
+ }
386
+ if !matches ! ( entry. worktree_status( ) , Status :: Unmodified ) {
387
+ is_worktree_clean = false ;
388
+ }
389
+ if !is_index_clean && !is_worktree_clean {
437
390
return Err ( anyhow ! (
438
391
"The index is dirty; consider using `--index` or `--force`" ,
439
392
) ) ;
440
393
}
441
394
}
442
395
}
443
396
397
+ // TODO: interrogate status once and avoid allocating PathBufs
398
+ let refresh_paths = refresh_paths
399
+ . iter ( )
400
+ . map ( |path| path. to_path_buf ( ) )
401
+ . collect ( ) ;
444
402
Ok ( refresh_paths)
445
403
}
446
404
405
+ fn write_tree (
406
+ stack : & Stack ,
407
+ refresh_paths : & IndexSet < PathBuf > ,
408
+ is_path_limiting : bool ,
409
+ ) -> Result < git2:: Oid > {
410
+ // N.B. using temp index is necessary for the cases where there are conflicts in the
411
+ // default index. I.e. by using a temp index, a subset of paths without conflicts
412
+ // may be formed into a coherent tree while leaving the default index as-is.
413
+ let stupid = stack. repo . stupid ( ) ;
414
+ if is_path_limiting {
415
+ let stupid_temp = stupid. get_temp_index_context ( ) ;
416
+ let stupid_temp = stupid_temp. context ( ) ;
417
+ let tree_id_result = {
418
+ stupid_temp. read_tree ( stack. branch_head . tree_id ( ) ) ?;
419
+ stupid_temp. update_index ( Some ( refresh_paths) ) ?;
420
+ stupid_temp. write_tree ( )
421
+ } ;
422
+ stupid. update_index ( Some ( refresh_paths) ) ?;
423
+ tree_id_result
424
+ } else {
425
+ if !refresh_paths. is_empty ( ) {
426
+ stupid. update_index ( Some ( refresh_paths) ) ?;
427
+ }
428
+ stupid. write_tree ( )
429
+ }
430
+ }
431
+
447
432
pub ( crate ) fn assemble_refresh_tree (
448
433
stack : & Stack ,
449
434
config : & git2:: Config ,
450
435
matches : & ArgMatches ,
451
436
limit_to_patchname : Option < & PatchName > ,
452
437
) -> Result < git2:: Oid > {
453
- let repo = stack. repo ;
454
- let opt_submodules = matches. contains_id ( "submodules" ) ;
455
- let opt_nosubmodules = matches. contains_id ( "no-submodules" ) ;
456
- let use_submodules = if !opt_submodules && !opt_nosubmodules {
457
- config. get_bool ( "stgit.refreshsubmodules" ) . unwrap_or ( false )
458
- } else {
459
- opt_submodules
460
- } ;
438
+ let stupid = stack. repo . stupid ( ) ;
461
439
let opt_pathspecs = matches. get_many :: < PathBuf > ( "pathspecs" ) ;
462
440
let is_path_limiting = limit_to_patchname. is_some ( ) || opt_pathspecs. is_some ( ) ;
441
+ let statuses;
463
442
464
443
let refresh_paths = if matches. contains_id ( "index" ) {
465
444
// When refreshing from the index, no path limiting may be used.
466
445
assert ! ( !is_path_limiting) ;
467
446
IndexSet :: new ( )
468
447
} else {
469
448
let maybe_patch_commit = limit_to_patchname. map ( |pn| stack. get_patch_commit ( pn) ) ;
449
+ let opt_submodules = matches. contains_id ( "submodules" ) ;
450
+ let opt_nosubmodules = matches. contains_id ( "no-submodules" ) ;
451
+ let use_submodules = if !opt_submodules && !opt_nosubmodules {
452
+ config. get_bool ( "stgit.refreshsubmodules" ) . unwrap_or ( false )
453
+ } else {
454
+ opt_submodules
455
+ } ;
456
+ let mut status_opts = StatusOptions :: default ( ) ;
457
+ status_opts. include_submodules ( use_submodules) ;
458
+ if let Some ( pathspecs) = opt_pathspecs {
459
+ status_opts. pathspecs ( pathspecs) ;
460
+ }
461
+ statuses = stupid. statuses ( Some ( & status_opts) ) ?;
462
+
470
463
determine_refresh_paths (
471
- repo ,
472
- opt_pathspecs ,
464
+ & stupid ,
465
+ & statuses ,
473
466
maybe_patch_commit,
474
- use_submodules,
475
467
matches. contains_id ( "force" ) ,
476
468
) ?
477
469
} ;
478
470
479
- let tree_id = {
480
- let paths: & IndexSet < PathBuf > = & refresh_paths;
481
- let mut default_index = stack. repo . index ( ) ?;
482
-
483
- // N.B. using temp index is necessary for the cases where there are conflicts in the
484
- // default index. I.e. by using a temp index, a subset of paths without conflicts
485
- // may be formed into a coherent tree while leaving the default index as-is.
486
- let tree_id_result = if is_path_limiting {
487
- let head_tree = stack. branch_head . tree ( ) ?;
488
- let tree_id_result = stack. repo . with_temp_index ( |temp_index| {
489
- temp_index. read_tree ( & head_tree) ?;
490
- temp_index. add_all ( paths, git2:: IndexAddOption :: DEFAULT , None ) ?;
491
- Ok ( temp_index. write_tree ( ) ?)
492
- } ) ;
493
-
494
- default_index. update_all ( paths, None ) ?;
495
- tree_id_result
496
- } else {
497
- if !paths. is_empty ( ) {
498
- default_index. update_all ( paths, None ) ?;
499
- }
500
- Ok ( default_index. write_tree ( ) ?)
501
- } ;
502
- default_index. write ( ) ?;
503
- tree_id_result
504
- } ?;
471
+ let tree_id = write_tree ( stack, & refresh_paths, is_path_limiting) ?;
505
472
506
- let tree_id = if matches. contains_id ( "no-verify" ) {
473
+ let tree_id = if matches. contains_id ( "no-verify" )
474
+ || !run_pre_commit_hook ( stack. repo , matches. contains_id ( "edit" ) ) ?
475
+ || stupid. diff_index_quiet ( tree_id) ?
476
+ {
507
477
tree_id
508
478
} else {
509
- run_pre_commit_hook ( repo, matches. contains_id ( "edit" ) ) ?;
510
- // Re-read index from filesystem because pre-commit hook may have modified it
511
- let mut index = repo. index ( ) ?;
512
- index. read ( false ) ?;
513
- index. write_tree ( ) ?
479
+ // Update index and rewrite tree if hook updated files in index
480
+ write_tree ( stack, & refresh_paths, is_path_limiting) ?
514
481
} ;
515
482
516
483
Ok ( tree_id)
517
484
}
518
-
519
- fn path_from_bytes ( b : & [ u8 ] ) -> & Path {
520
- b. to_path ( ) . expect ( "paths on Windows must be utf8" )
521
- }
0 commit comments