@@ -15,7 +15,9 @@ static const char * const worktree_usage[] = {
15
15
N_ ("git worktree add [<options>] <path> [<branch>]" ),
16
16
N_ ("git worktree list [<options>]" ),
17
17
N_ ("git worktree lock [<options>] <path>" ),
18
+ N_ ("git worktree move <worktree> <new-path>" ),
18
19
N_ ("git worktree prune [<options>]" ),
20
+ N_ ("git worktree remove [<options>] <worktree>" ),
19
21
N_ ("git worktree unlock <path>" ),
20
22
NULL
21
23
};
@@ -524,6 +526,162 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
524
526
return ret ;
525
527
}
526
528
529
+ static void validate_no_submodules (const struct worktree * wt )
530
+ {
531
+ struct index_state istate = {0 };
532
+ int i , found_submodules = 0 ;
533
+
534
+ if (read_index_from (& istate , worktree_git_path (wt , "index" )) > 0 ) {
535
+ for (i = 0 ; i < istate .cache_nr ; i ++ ) {
536
+ struct cache_entry * ce = istate .cache [i ];
537
+
538
+ if (S_ISGITLINK (ce -> ce_mode )) {
539
+ found_submodules = 1 ;
540
+ break ;
541
+ }
542
+ }
543
+ }
544
+ discard_index (& istate );
545
+
546
+ if (found_submodules )
547
+ die (_ ("This working tree contains submodules and cannot be moved yet" ));
548
+ }
549
+
550
+ static int move_worktree (int ac , const char * * av , const char * prefix )
551
+ {
552
+ struct option options [] = {
553
+ OPT_END ()
554
+ };
555
+ struct worktree * * worktrees , * wt ;
556
+ struct strbuf dst = STRBUF_INIT ;
557
+ const char * reason ;
558
+
559
+ ac = parse_options (ac , av , prefix , options , worktree_usage , 0 );
560
+ if (ac != 2 )
561
+ usage_with_options (worktree_usage , options );
562
+
563
+ strbuf_addstr (& dst , prefix_filename (prefix ,
564
+ strlen (prefix ),
565
+ av [1 ]));
566
+ if (is_directory (dst .buf ))
567
+ /*
568
+ * keep going, dst will be appended after we get the
569
+ * source's absolute path
570
+ */
571
+ ;
572
+ else if (file_exists (dst .buf ))
573
+ die (_ ("target '%s' already exists" ), av [1 ]);
574
+
575
+ worktrees = get_worktrees (0 );
576
+ wt = find_worktree (worktrees , prefix , av [0 ]);
577
+ if (!wt )
578
+ die (_ ("'%s' is not a working directory" ), av [0 ]);
579
+ if (is_main_worktree (wt ))
580
+ die (_ ("'%s' is a main working directory" ), av [0 ]);
581
+ reason = is_worktree_locked (wt );
582
+ if (reason ) {
583
+ if (* reason )
584
+ die (_ ("already locked, reason: %s" ), reason );
585
+ die (_ ("already locked, no reason" ));
586
+ }
587
+ if (validate_worktree (wt , 0 ))
588
+ return -1 ;
589
+
590
+ validate_no_submodules (wt );
591
+
592
+ if (is_directory (dst .buf )) {
593
+ const char * sep = find_last_dir_sep (wt -> path );
594
+
595
+ if (!sep )
596
+ die (_ ("could not figure out destination name from '%s'" ),
597
+ wt -> path );
598
+ strbuf_addstr (& dst , sep );
599
+ if (file_exists (dst .buf ))
600
+ die (_ ("target '%s' already exists" ), dst .buf );
601
+ }
602
+
603
+ if (rename (wt -> path , dst .buf ) == -1 )
604
+ die_errno (_ ("failed to move '%s' to '%s'" ), wt -> path , dst .buf );
605
+
606
+ return update_worktree_location (wt , dst .buf );
607
+ }
608
+
609
+ static int remove_worktree (int ac , const char * * av , const char * prefix )
610
+ {
611
+ int force = 0 ;
612
+ struct option options [] = {
613
+ OPT_BOOL (0 , "force" , & force ,
614
+ N_ ("force removing even if the worktree is dirty" )),
615
+ OPT_END ()
616
+ };
617
+ struct worktree * * worktrees , * wt ;
618
+ struct strbuf sb = STRBUF_INIT ;
619
+ const char * reason ;
620
+ int ret = 0 ;
621
+
622
+ ac = parse_options (ac , av , prefix , options , worktree_usage , 0 );
623
+ if (ac != 1 )
624
+ usage_with_options (worktree_usage , options );
625
+
626
+ worktrees = get_worktrees (0 );
627
+ wt = find_worktree (worktrees , prefix , av [0 ]);
628
+ if (!wt )
629
+ die (_ ("'%s' is not a working directory" ), av [0 ]);
630
+ if (is_main_worktree (wt ))
631
+ die (_ ("'%s' is a main working directory" ), av [0 ]);
632
+ reason = is_worktree_locked (wt );
633
+ if (reason ) {
634
+ if (* reason )
635
+ die (_ ("already locked, reason: %s" ), reason );
636
+ die (_ ("already locked, no reason" ));
637
+ }
638
+ if (validate_worktree (wt , 0 ))
639
+ return -1 ;
640
+
641
+ if (!force ) {
642
+ struct argv_array child_env = ARGV_ARRAY_INIT ;
643
+ struct child_process cp ;
644
+ char buf [1 ];
645
+
646
+ argv_array_pushf (& child_env , "%s=%s/.git" ,
647
+ GIT_DIR_ENVIRONMENT , wt -> path );
648
+ argv_array_pushf (& child_env , "%s=%s" ,
649
+ GIT_WORK_TREE_ENVIRONMENT , wt -> path );
650
+ memset (& cp , 0 , sizeof (cp ));
651
+ argv_array_pushl (& cp .args , "status" , "--porcelain" , NULL );
652
+ cp .env = child_env .argv ;
653
+ cp .git_cmd = 1 ;
654
+ cp .dir = wt -> path ;
655
+ cp .out = -1 ;
656
+ ret = start_command (& cp );
657
+ if (ret )
658
+ die_errno (_ ("failed to run git-status on '%s', code %d" ),
659
+ av [0 ], ret );
660
+ ret = xread (cp .out , buf , sizeof (buf ));
661
+ if (ret )
662
+ die (_ ("'%s' is dirty, use --force to delete it" ), av [0 ]);
663
+ close (cp .out );
664
+ ret = finish_command (& cp );
665
+ if (ret )
666
+ die_errno (_ ("failed to run git-status on '%s', code %d" ),
667
+ av [0 ], ret );
668
+ }
669
+ strbuf_addstr (& sb , wt -> path );
670
+ if (remove_dir_recursively (& sb , 0 )) {
671
+ error_errno (_ ("failed to delete '%s'" ), sb .buf );
672
+ ret = -1 ;
673
+ }
674
+ strbuf_reset (& sb );
675
+ strbuf_addstr (& sb , git_common_path ("worktrees/%s" , wt -> id ));
676
+ if (remove_dir_recursively (& sb , 0 )) {
677
+ error_errno (_ ("failed to delete '%s'" ), sb .buf );
678
+ ret = -1 ;
679
+ }
680
+ strbuf_release (& sb );
681
+ free_worktrees (worktrees );
682
+ return ret ;
683
+ }
684
+
527
685
int cmd_worktree (int ac , const char * * av , const char * prefix )
528
686
{
529
687
struct option options [] = {
@@ -546,5 +704,9 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
546
704
return lock_worktree (ac - 1 , av + 1 , prefix );
547
705
if (!strcmp (av [1 ], "unlock" ))
548
706
return unlock_worktree (ac - 1 , av + 1 , prefix );
707
+ if (!strcmp (av [1 ], "move" ))
708
+ return move_worktree (ac - 1 , av + 1 , prefix );
709
+ if (!strcmp (av [1 ], "remove" ))
710
+ return remove_worktree (ac - 1 , av + 1 , prefix );
549
711
usage_with_options (worktree_usage , options );
550
712
}
0 commit comments