@@ -39,6 +39,13 @@ enum update_mode {
3939 INDEX = (1 << 2 ),
4040 SPARSE = (1 << 3 ),
4141 SKIP_WORKTREE_DIR = (1 << 4 ),
42+ /*
43+ * A file gets moved implicitly via a move of one of its parent
44+ * directories. This flag causes us to skip the check that we don't try
45+ * to move a file and any of its parent directories at the same point
46+ * in time.
47+ */
48+ MOVE_VIA_PARENT_DIR = (1 << 5 ),
4249};
4350
4451#define DUP_BASENAME 1
@@ -183,6 +190,21 @@ static void remove_empty_src_dirs(const char **src_dir, size_t src_dir_nr)
183190 strbuf_release (& a_src_dir );
184191}
185192
193+ struct pathmap_entry {
194+ struct hashmap_entry ent ;
195+ const char * path ;
196+ };
197+
198+ static int pathmap_cmp (const void * cmp_data UNUSED ,
199+ const struct hashmap_entry * a ,
200+ const struct hashmap_entry * b ,
201+ const void * key UNUSED )
202+ {
203+ const struct pathmap_entry * e1 = container_of (a , struct pathmap_entry , ent );
204+ const struct pathmap_entry * e2 = container_of (b , struct pathmap_entry , ent );
205+ return fspathcmp (e1 -> path , e2 -> path );
206+ }
207+
186208int cmd_mv (int argc ,
187209 const char * * argv ,
188210 const char * prefix ,
@@ -213,6 +235,8 @@ int cmd_mv(int argc,
213235 struct cache_entry * ce ;
214236 struct string_list only_match_skip_worktree = STRING_LIST_INIT_DUP ;
215237 struct string_list dirty_paths = STRING_LIST_INIT_DUP ;
238+ struct hashmap moved_dirs = HASHMAP_INIT (pathmap_cmp , NULL );
239+ struct strbuf pathbuf = STRBUF_INIT ;
216240 int ret ;
217241
218242 git_config (git_default_config , NULL );
@@ -331,6 +355,7 @@ int cmd_mv(int argc,
331355
332356dir_check :
333357 if (S_ISDIR (st .st_mode )) {
358+ struct pathmap_entry * entry ;
334359 char * dst_with_slash ;
335360 size_t dst_with_slash_len ;
336361 int j , n ;
@@ -348,6 +373,11 @@ int cmd_mv(int argc,
348373 goto act_on_entry ;
349374 }
350375
376+ entry = xmalloc (sizeof (* entry ));
377+ entry -> path = src ;
378+ hashmap_entry_init (& entry -> ent , fspathhash (src ));
379+ hashmap_add (& moved_dirs , & entry -> ent );
380+
351381 /* last - first >= 1 */
352382 modes [i ] |= WORKING_DIRECTORY ;
353383
@@ -368,8 +398,7 @@ int cmd_mv(int argc,
368398 strvec_push (& sources , path );
369399 strvec_push (& destinations , prefixed_path );
370400
371- memset (modes + argc + j , 0 , sizeof (enum update_mode ));
372- modes [argc + j ] |= ce_skip_worktree (ce ) ? SPARSE : INDEX ;
401+ modes [argc + j ] = MOVE_VIA_PARENT_DIR | (ce_skip_worktree (ce ) ? SPARSE : INDEX );
373402 submodule_gitfiles [argc + j ] = NULL ;
374403
375404 free (prefixed_path );
@@ -465,6 +494,32 @@ int cmd_mv(int argc,
465494 }
466495 }
467496
497+ for (i = 0 ; i < argc ; i ++ ) {
498+ const char * slash_pos ;
499+
500+ if (modes [i ] & MOVE_VIA_PARENT_DIR )
501+ continue ;
502+
503+ strbuf_reset (& pathbuf );
504+ strbuf_addstr (& pathbuf , sources .v [i ]);
505+
506+ slash_pos = strrchr (pathbuf .buf , '/' );
507+ while (slash_pos > pathbuf .buf ) {
508+ struct pathmap_entry needle ;
509+
510+ strbuf_setlen (& pathbuf , slash_pos - pathbuf .buf );
511+
512+ needle .path = pathbuf .buf ;
513+ hashmap_entry_init (& needle .ent , fspathhash (pathbuf .buf ));
514+
515+ if (hashmap_get_entry (& moved_dirs , & needle , ent , NULL ))
516+ die (_ ("cannot move both '%s' and its parent directory '%s'" ),
517+ sources .v [i ], pathbuf .buf );
518+
519+ slash_pos = strrchr (pathbuf .buf , '/' );
520+ }
521+ }
522+
468523 if (only_match_skip_worktree .nr ) {
469524 advise_on_updating_sparse_paths (& only_match_skip_worktree );
470525 if (!ignore_errors ) {
@@ -507,7 +562,8 @@ int cmd_mv(int argc,
507562 continue ;
508563
509564 pos = index_name_pos (the_repository -> index , src , strlen (src ));
510- assert (pos >= 0 );
565+ if (pos < 0 )
566+ BUG ("could not find source in index: '%s'" , src );
511567 if (!(mode & SPARSE ) && !lstat (src , & st ))
512568 sparse_and_dirty = ie_modified (the_repository -> index ,
513569 the_repository -> index -> cache [pos ],
@@ -589,6 +645,8 @@ int cmd_mv(int argc,
589645 strvec_clear (& dest_paths );
590646 strvec_clear (& destinations );
591647 strvec_clear (& submodule_gitfiles_to_free );
648+ hashmap_clear_and_free (& moved_dirs , struct pathmap_entry , ent );
649+ strbuf_release (& pathbuf );
592650 free (submodule_gitfiles );
593651 free (modes );
594652 return ret ;
0 commit comments