@@ -21,7 +21,6 @@ static const char * const builtin_mv_usage[] = {
2121};
2222
2323enum update_mode {
24- BOTH = 0 ,
2524 WORKING_DIRECTORY = (1 << 1 ),
2625 INDEX = (1 << 2 ),
2726 SPARSE = (1 << 3 ),
@@ -72,7 +71,7 @@ static const char **internal_prefix_pathspec(const char *prefix,
7271static const char * add_slash (const char * path )
7372{
7473 size_t len = strlen (path );
75- if (path [len - 1 ] != '/' ) {
74+ if (len && path [len - 1 ] != '/' ) {
7675 char * with_slash = xmalloc (st_add (len , 2 ));
7776 memcpy (with_slash , path , len );
7877 with_slash [len ++ ] = '/' ;
@@ -125,16 +124,15 @@ static int index_range_of_same_dir(const char *src, int length,
125124}
126125
127126/*
128- * Check if an out-of-cone directory should be in the index. Imagine this case
129- * that all the files under a directory are marked with 'CE_SKIP_WORKTREE' bit
130- * and thus the directory is sparsified.
131- *
132- * Return 0 if such directory exist (i.e. with any of its contained files not
133- * marked with CE_SKIP_WORKTREE, the directory would be present in working tree).
134- * Return 1 otherwise.
127+ * Given the path of a directory that does not exist on-disk, check whether the
128+ * directory contains any entries in the index with the SKIP_WORKTREE flag
129+ * enabled.
130+ * Return 1 if such index entries exist.
131+ * Return 0 otherwise.
135132 */
136- static int check_dir_in_index (const char * name )
133+ static int empty_dir_has_sparse_contents (const char * name )
137134{
135+ int ret = 0 ;
138136 const char * with_slash = add_slash (name );
139137 int length = strlen (with_slash );
140138
@@ -144,14 +142,18 @@ static int check_dir_in_index(const char *name)
144142 if (pos < 0 ) {
145143 pos = - pos - 1 ;
146144 if (pos >= the_index .cache_nr )
147- return 1 ;
145+ goto free_return ;
148146 ce = active_cache [pos ];
149147 if (strncmp (with_slash , ce -> name , length ))
150- return 1 ;
148+ goto free_return ;
151149 if (ce_skip_worktree (ce ))
152- return 0 ;
150+ ret = 1 ;
153151 }
154- return 1 ;
152+
153+ free_return :
154+ if (with_slash != name )
155+ free ((char * )with_slash );
156+ return ret ;
155157}
156158
157159int cmd_mv (int argc , const char * * argv , const char * prefix )
@@ -168,12 +170,17 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
168170 OPT_END (),
169171 };
170172 const char * * source , * * destination , * * dest_path , * * submodule_gitfile ;
171- enum update_mode * modes ;
173+ const char * dst_w_slash ;
174+ const char * * src_dir = NULL ;
175+ int src_dir_nr = 0 , src_dir_alloc = 0 ;
176+ struct strbuf a_src_dir = STRBUF_INIT ;
177+ enum update_mode * modes , dst_mode = 0 ;
172178 struct stat st ;
173179 struct string_list src_for_dst = STRING_LIST_INIT_NODUP ;
174180 struct lock_file lock_file = LOCK_INIT ;
175181 struct cache_entry * ce ;
176182 struct string_list only_match_skip_worktree = STRING_LIST_INIT_NODUP ;
183+ struct string_list dirty_paths = STRING_LIST_INIT_NODUP ;
177184
178185 git_config (git_default_config , NULL );
179186
@@ -198,19 +205,39 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
198205 if (argc == 1 && is_directory (argv [0 ]) && !is_directory (argv [1 ]))
199206 flags = 0 ;
200207 dest_path = internal_prefix_pathspec (prefix , argv + argc , 1 , flags );
208+ dst_w_slash = add_slash (dest_path [0 ]);
201209 submodule_gitfile = xcalloc (argc , sizeof (char * ));
202210
203211 if (dest_path [0 ][0 ] == '\0' )
204212 /* special case: "." was normalized to "" */
205213 destination = internal_prefix_pathspec (dest_path [0 ], argv , argc , DUP_BASENAME );
206214 else if (!lstat (dest_path [0 ], & st ) &&
207215 S_ISDIR (st .st_mode )) {
208- dest_path [0 ] = add_slash (dest_path [0 ]);
209- destination = internal_prefix_pathspec (dest_path [0 ], argv , argc , DUP_BASENAME );
216+ destination = internal_prefix_pathspec (dst_w_slash , argv , argc , DUP_BASENAME );
210217 } else {
211- if (argc != 1 )
218+ if (!path_in_sparse_checkout (dst_w_slash , & the_index ) &&
219+ empty_dir_has_sparse_contents (dst_w_slash )) {
220+ destination = internal_prefix_pathspec (dst_w_slash , argv , argc , DUP_BASENAME );
221+ dst_mode = SKIP_WORKTREE_DIR ;
222+ } else if (argc != 1 ) {
212223 die (_ ("destination '%s' is not a directory" ), dest_path [0 ]);
213- destination = dest_path ;
224+ } else {
225+ destination = dest_path ;
226+ /*
227+ * <destination> is a file outside of sparse-checkout
228+ * cone. Insist on cone mode here for backward
229+ * compatibility. We don't want dst_mode to be assigned
230+ * for a file when the repo is using no-cone mode (which
231+ * is deprecated at this point) sparse-checkout. As
232+ * SPARSE here is only considering cone-mode situation.
233+ */
234+ if (!path_in_cone_mode_sparse_checkout (destination [0 ], & the_index ))
235+ dst_mode = SPARSE ;
236+ }
237+ }
238+ if (dst_w_slash != dest_path [0 ]) {
239+ free ((char * )dst_w_slash );
240+ dst_w_slash = NULL ;
214241 }
215242
216243 /* Checking */
@@ -232,7 +259,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
232259 if (pos < 0 ) {
233260 const char * src_w_slash = add_slash (src );
234261 if (!path_in_sparse_checkout (src_w_slash , & the_index ) &&
235- ! check_dir_in_index (src )) {
262+ empty_dir_has_sparse_contents (src )) {
236263 modes [i ] |= SKIP_WORKTREE_DIR ;
237264 goto dir_check ;
238265 }
@@ -290,6 +317,10 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
290317
291318 /* last - first >= 1 */
292319 modes [i ] |= WORKING_DIRECTORY ;
320+
321+ ALLOC_GROW (src_dir , src_dir_nr + 1 , src_dir_alloc );
322+ src_dir [src_dir_nr ++ ] = src ;
323+
293324 n = argc + last - first ;
294325 REALLOC_ARRAY (source , n );
295326 REALLOC_ARRAY (destination , n );
@@ -346,6 +377,18 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
346377 goto act_on_entry ;
347378 }
348379
380+ if (ignore_sparse &&
381+ (dst_mode & (SKIP_WORKTREE_DIR | SPARSE )) &&
382+ index_entry_exists (& the_index , dst , strlen (dst ))) {
383+ bad = _ ("destination exists in the index" );
384+ if (force ) {
385+ if (verbose )
386+ warning (_ ("overwriting '%s'" ), dst );
387+ bad = NULL ;
388+ } else {
389+ goto act_on_entry ;
390+ }
391+ }
349392 /*
350393 * We check if the paths are in the sparse-checkout
351394 * definition as a very final check, since that
@@ -396,6 +439,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
396439 const char * src = source [i ], * dst = destination [i ];
397440 enum update_mode mode = modes [i ];
398441 int pos ;
442+ int sparse_and_dirty = 0 ;
399443 struct checkout state = CHECKOUT_INIT ;
400444 state .istate = & the_index ;
401445
@@ -406,6 +450,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
406450 if (show_only )
407451 continue ;
408452 if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR )) &&
453+ !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE )) &&
409454 rename (src , dst ) < 0 ) {
410455 if (ignore_errors )
411456 continue ;
@@ -425,20 +470,81 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
425470
426471 pos = cache_name_pos (src , strlen (src ));
427472 assert (pos >= 0 );
473+ if (!(mode & SPARSE ) && !lstat (src , & st ))
474+ sparse_and_dirty = ce_modified (active_cache [pos ], & st , 0 );
428475 rename_cache_entry_at (pos , dst );
429476
430- if ((mode & SPARSE ) &&
431- (path_in_sparse_checkout (dst , & the_index ))) {
432- int dst_pos ;
477+ if (ignore_sparse &&
478+ core_apply_sparse_checkout &&
479+ core_sparse_checkout_cone ) {
480+ /*
481+ * NEEDSWORK: we are *not* paying attention to
482+ * "out-to-out" move (<source> is out-of-cone and
483+ * <destination> is out-of-cone) at this point. It
484+ * should be added in a future patch.
485+ */
486+ if ((mode & SPARSE ) &&
487+ path_in_sparse_checkout (dst , & the_index )) {
488+ /* from out-of-cone to in-cone */
489+ int dst_pos = cache_name_pos (dst , strlen (dst ));
490+ struct cache_entry * dst_ce = active_cache [dst_pos ];
491+
492+ dst_ce -> ce_flags &= ~CE_SKIP_WORKTREE ;
493+
494+ if (checkout_entry (dst_ce , & state , NULL , NULL ))
495+ die (_ ("cannot checkout %s" ), dst_ce -> name );
496+ } else if ((dst_mode & (SKIP_WORKTREE_DIR | SPARSE )) &&
497+ !(mode & SPARSE ) &&
498+ !path_in_sparse_checkout (dst , & the_index )) {
499+ /* from in-cone to out-of-cone */
500+ int dst_pos = cache_name_pos (dst , strlen (dst ));
501+ struct cache_entry * dst_ce = active_cache [dst_pos ];
433502
434- dst_pos = cache_name_pos (dst , strlen (dst ));
435- active_cache [dst_pos ]-> ce_flags &= ~CE_SKIP_WORKTREE ;
503+ /*
504+ * if src is clean, it will suffice to remove it
505+ */
506+ if (!sparse_and_dirty ) {
507+ dst_ce -> ce_flags |= CE_SKIP_WORKTREE ;
508+ unlink_or_warn (src );
509+ } else {
510+ /*
511+ * if src is dirty, move it to the
512+ * destination and create leading
513+ * dirs if necessary
514+ */
515+ char * dst_dup = xstrdup (dst );
516+ string_list_append (& dirty_paths , dst );
517+ safe_create_leading_directories (dst_dup );
518+ FREE_AND_NULL (dst_dup );
519+ rename (src , dst );
520+ }
521+ }
522+ }
523+ }
436524
437- if (checkout_entry (active_cache [dst_pos ], & state , NULL , NULL ))
438- die (_ ("cannot checkout %s" ), active_cache [dst_pos ]-> name );
525+ /*
526+ * cleanup the empty src_dirs
527+ */
528+ for (i = 0 ; i < src_dir_nr ; i ++ ) {
529+ int dummy ;
530+ strbuf_addstr (& a_src_dir , src_dir [i ]);
531+ /*
532+ * if entries under a_src_dir are all moved away,
533+ * recursively remove a_src_dir to cleanup
534+ */
535+ if (index_range_of_same_dir (a_src_dir .buf , a_src_dir .len ,
536+ & dummy , & dummy ) < 1 ) {
537+ remove_dir_recursively (& a_src_dir , 0 );
439538 }
539+ strbuf_reset (& a_src_dir );
440540 }
441541
542+ strbuf_release (& a_src_dir );
543+ free (src_dir );
544+
545+ if (dirty_paths .nr )
546+ advise_on_moving_dirty_path (& dirty_paths );
547+
442548 if (gitmodules_modified )
443549 stage_updated_gitmodules (& the_index );
444550
@@ -447,6 +553,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)
447553 die (_ ("Unable to write new index file" ));
448554
449555 string_list_clear (& src_for_dst , 0 );
556+ string_list_clear (& dirty_paths , 0 );
450557 UNLEAK (source );
451558 UNLEAK (dest_path );
452559 free (submodule_gitfile );
0 commit comments