@@ -17,7 +17,9 @@ static const char * const worktree_usage[] = {
17
17
N_ ("git worktree add [<options>] <path> [<commit-ish>]" ),
18
18
N_ ("git worktree list [<options>]" ),
19
19
N_ ("git worktree lock [<options>] <path>" ),
20
+ N_ ("git worktree move <worktree> <new-path>" ),
20
21
N_ ("git worktree prune [<options>]" ),
22
+ N_ ("git worktree remove [<options>] <worktree>" ),
21
23
N_ ("git worktree unlock <path>" ),
22
24
NULL
23
25
};
@@ -619,6 +621,220 @@ static int unlock_worktree(int ac, const char **av, const char *prefix)
619
621
return ret ;
620
622
}
621
623
624
+ static void validate_no_submodules (const struct worktree * wt )
625
+ {
626
+ struct index_state istate = { NULL };
627
+ int i , found_submodules = 0 ;
628
+
629
+ if (read_index_from (& istate , worktree_git_path (wt , "index" ),
630
+ get_worktree_git_dir (wt )) > 0 ) {
631
+ for (i = 0 ; i < istate .cache_nr ; i ++ ) {
632
+ struct cache_entry * ce = istate .cache [i ];
633
+
634
+ if (S_ISGITLINK (ce -> ce_mode )) {
635
+ found_submodules = 1 ;
636
+ break ;
637
+ }
638
+ }
639
+ }
640
+ discard_index (& istate );
641
+
642
+ if (found_submodules )
643
+ die (_ ("working trees containing submodules cannot be moved or removed" ));
644
+ }
645
+
646
+ static int move_worktree (int ac , const char * * av , const char * prefix )
647
+ {
648
+ struct option options [] = {
649
+ OPT_END ()
650
+ };
651
+ struct worktree * * worktrees , * wt ;
652
+ struct strbuf dst = STRBUF_INIT ;
653
+ struct strbuf errmsg = STRBUF_INIT ;
654
+ const char * reason ;
655
+ char * path ;
656
+
657
+ ac = parse_options (ac , av , prefix , options , worktree_usage , 0 );
658
+ if (ac != 2 )
659
+ usage_with_options (worktree_usage , options );
660
+
661
+ path = prefix_filename (prefix , av [1 ]);
662
+ strbuf_addstr (& dst , path );
663
+ free (path );
664
+
665
+ worktrees = get_worktrees (0 );
666
+ wt = find_worktree (worktrees , prefix , av [0 ]);
667
+ if (!wt )
668
+ die (_ ("'%s' is not a working tree" ), av [0 ]);
669
+ if (is_main_worktree (wt ))
670
+ die (_ ("'%s' is a main working tree" ), av [0 ]);
671
+ if (is_directory (dst .buf )) {
672
+ const char * sep = find_last_dir_sep (wt -> path );
673
+
674
+ if (!sep )
675
+ die (_ ("could not figure out destination name from '%s'" ),
676
+ wt -> path );
677
+ strbuf_trim_trailing_dir_sep (& dst );
678
+ strbuf_addstr (& dst , sep );
679
+ }
680
+ if (file_exists (dst .buf ))
681
+ die (_ ("target '%s' already exists" ), dst .buf );
682
+
683
+ validate_no_submodules (wt );
684
+
685
+ reason = is_worktree_locked (wt );
686
+ if (reason ) {
687
+ if (* reason )
688
+ die (_ ("cannot move a locked working tree, lock reason: %s" ),
689
+ reason );
690
+ die (_ ("cannot move a locked working tree" ));
691
+ }
692
+ if (validate_worktree (wt , & errmsg , 0 ))
693
+ die (_ ("validation failed, cannot move working tree: %s" ),
694
+ errmsg .buf );
695
+ strbuf_release (& errmsg );
696
+
697
+ if (rename (wt -> path , dst .buf ) == -1 )
698
+ die_errno (_ ("failed to move '%s' to '%s'" ), wt -> path , dst .buf );
699
+
700
+ update_worktree_location (wt , dst .buf );
701
+
702
+ strbuf_release (& dst );
703
+ free_worktrees (worktrees );
704
+ return 0 ;
705
+ }
706
+
707
+ /*
708
+ * Note, "git status --porcelain" is used to determine if it's safe to
709
+ * delete a whole worktree. "git status" does not ignore user
710
+ * configuration, so if a normal "git status" shows "clean" for the
711
+ * user, then it's ok to remove it.
712
+ *
713
+ * This assumption may be a bad one. We may want to ignore
714
+ * (potentially bad) user settings and only delete a worktree when
715
+ * it's absolutely safe to do so from _our_ point of view because we
716
+ * know better.
717
+ */
718
+ static void check_clean_worktree (struct worktree * wt ,
719
+ const char * original_path )
720
+ {
721
+ struct argv_array child_env = ARGV_ARRAY_INIT ;
722
+ struct child_process cp ;
723
+ char buf [1 ];
724
+ int ret ;
725
+
726
+ /*
727
+ * Until we sort this out, all submodules are "dirty" and
728
+ * will abort this function.
729
+ */
730
+ validate_no_submodules (wt );
731
+
732
+ argv_array_pushf (& child_env , "%s=%s/.git" ,
733
+ GIT_DIR_ENVIRONMENT , wt -> path );
734
+ argv_array_pushf (& child_env , "%s=%s" ,
735
+ GIT_WORK_TREE_ENVIRONMENT , wt -> path );
736
+ memset (& cp , 0 , sizeof (cp ));
737
+ argv_array_pushl (& cp .args , "status" ,
738
+ "--porcelain" , "--ignore-submodules=none" ,
739
+ NULL );
740
+ cp .env = child_env .argv ;
741
+ cp .git_cmd = 1 ;
742
+ cp .dir = wt -> path ;
743
+ cp .out = -1 ;
744
+ ret = start_command (& cp );
745
+ if (ret )
746
+ die_errno (_ ("failed to run 'git status' on '%s'" ),
747
+ original_path );
748
+ ret = xread (cp .out , buf , sizeof (buf ));
749
+ if (ret )
750
+ die (_ ("'%s' is dirty, use --force to delete it" ),
751
+ original_path );
752
+ close (cp .out );
753
+ ret = finish_command (& cp );
754
+ if (ret )
755
+ die_errno (_ ("failed to run 'git status' on '%s', code %d" ),
756
+ original_path , ret );
757
+ }
758
+
759
+ static int delete_git_work_tree (struct worktree * wt )
760
+ {
761
+ struct strbuf sb = STRBUF_INIT ;
762
+ int ret = 0 ;
763
+
764
+ strbuf_addstr (& sb , wt -> path );
765
+ if (remove_dir_recursively (& sb , 0 )) {
766
+ error_errno (_ ("failed to delete '%s'" ), sb .buf );
767
+ ret = -1 ;
768
+ }
769
+ strbuf_release (& sb );
770
+ return ret ;
771
+ }
772
+
773
+ static int delete_git_dir (struct worktree * wt )
774
+ {
775
+ struct strbuf sb = STRBUF_INIT ;
776
+ int ret = 0 ;
777
+
778
+ strbuf_addstr (& sb , git_common_path ("worktrees/%s" , wt -> id ));
779
+ if (remove_dir_recursively (& sb , 0 )) {
780
+ error_errno (_ ("failed to delete '%s'" ), sb .buf );
781
+ ret = -1 ;
782
+ }
783
+ strbuf_release (& sb );
784
+ return ret ;
785
+ }
786
+
787
+ static int remove_worktree (int ac , const char * * av , const char * prefix )
788
+ {
789
+ int force = 0 ;
790
+ struct option options [] = {
791
+ OPT_BOOL (0 , "force" , & force ,
792
+ N_ ("force removing even if the worktree is dirty" )),
793
+ OPT_END ()
794
+ };
795
+ struct worktree * * worktrees , * wt ;
796
+ struct strbuf errmsg = STRBUF_INIT ;
797
+ const char * reason ;
798
+ int ret = 0 ;
799
+
800
+ ac = parse_options (ac , av , prefix , options , worktree_usage , 0 );
801
+ if (ac != 1 )
802
+ usage_with_options (worktree_usage , options );
803
+
804
+ worktrees = get_worktrees (0 );
805
+ wt = find_worktree (worktrees , prefix , av [0 ]);
806
+ if (!wt )
807
+ die (_ ("'%s' is not a working tree" ), av [0 ]);
808
+ if (is_main_worktree (wt ))
809
+ die (_ ("'%s' is a main working tree" ), av [0 ]);
810
+ reason = is_worktree_locked (wt );
811
+ if (reason ) {
812
+ if (* reason )
813
+ die (_ ("cannot remove a locked working tree, lock reason: %s" ),
814
+ reason );
815
+ die (_ ("cannot remove a locked working tree" ));
816
+ }
817
+ if (validate_worktree (wt , & errmsg , WT_VALIDATE_WORKTREE_MISSING_OK ))
818
+ die (_ ("validation failed, cannot remove working tree: %s" ),
819
+ errmsg .buf );
820
+ strbuf_release (& errmsg );
821
+
822
+ if (file_exists (wt -> path )) {
823
+ if (!force )
824
+ check_clean_worktree (wt , av [0 ]);
825
+
826
+ ret |= delete_git_work_tree (wt );
827
+ }
828
+ /*
829
+ * continue on even if ret is non-zero, there's no going back
830
+ * from here.
831
+ */
832
+ ret |= delete_git_dir (wt );
833
+
834
+ free_worktrees (worktrees );
835
+ return ret ;
836
+ }
837
+
622
838
int cmd_worktree (int ac , const char * * av , const char * prefix )
623
839
{
624
840
struct option options [] = {
@@ -641,5 +857,9 @@ int cmd_worktree(int ac, const char **av, const char *prefix)
641
857
return lock_worktree (ac - 1 , av + 1 , prefix );
642
858
if (!strcmp (av [1 ], "unlock" ))
643
859
return unlock_worktree (ac - 1 , av + 1 , prefix );
860
+ if (!strcmp (av [1 ], "move" ))
861
+ return move_worktree (ac - 1 , av + 1 , prefix );
862
+ if (!strcmp (av [1 ], "remove" ))
863
+ return remove_worktree (ac - 1 , av + 1 , prefix );
644
864
usage_with_options (worktree_usage , options );
645
865
}
0 commit comments