diff --git a/builtin/mv.c b/builtin/mv.c index a82fc97a19..eb59fe0f31 100644 --- a/builtin/mv.c +++ b/builtin/mv.c @@ -444,6 +444,27 @@ dir_check: goto act_on_entry; } + /* + * If we are going to move SRC to DST on disk, DST's leading + * directories must already exist. + */ + if (!(modes[i] & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) && + !(dst_mode & (SKIP_WORKTREE_DIR | SPARSE))) { + char *dst_dir = xstrdup(dst); + char *slash = strrchr(dst_dir, '/'); + + if (slash) { + struct stat dir_st; + *slash = '\0'; + if (lstat(dst_dir, &dir_st) < 0 && errno == ENOENT) { + free(dst_dir); + bad = _("destination directory does not exist"); + goto act_on_entry; + } + } + free(dst_dir); + } + if (ignore_sparse && (dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) && index_entry_exists(the_repository->index, dst, strlen(dst))) { @@ -549,7 +570,7 @@ remove_entry: rename(src, dst) < 0) { if (ignore_errors) continue; - die_errno(_("renaming '%s' failed"), src); + die_errno(_("renaming '%s' to '%s' failed"), src, dst); } if (submodule_gitfiles[i]) { if (!update_path_in_gitmodules(src, dst)) diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 7cf4aa5ba1..2d8a98d8b0 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -114,6 +114,20 @@ test_expect_success 'clean up' ' git reset --hard ' +test_expect_success 'moving to non-existent destination parent directory' ' + git reset --hard && + mkdir -p from && + echo content >from/file && + git add from/file && + test_must_fail git mv from/file no-such-dir/file 2>actual && + test_grep "destination directory does not exist" actual +' + +test_expect_success 'mv --dry-run detects non-existent destination parent directory' ' + test_must_fail git mv -n from/file no-such-dir/file 2>actual && + test_grep "destination directory does not exist" actual +' + test_expect_success 'moving to existing untracked target with trailing slash' ' mkdir path1 && git mv path0/ path1/ &&