mirror of
https://github.com/git/git.git
synced 2026-08-08 17:11:48 +00:00
Merge branch 'lo/mv-missing-dest-dir-check' into jch
'git mv' has been updated to check for a missing destination leading directory during the checking phase, allowing 'git mv -n' to report the failure. The error message when the rename(2) syscall fails has also been improved to name both the source and the destination. * lo/mv-missing-dest-dir-check: mv: reject a destination whose leading path is missing or a symlink mv: name both source and destination when rename fails
This commit is contained in:
47
builtin/mv.c
47
builtin/mv.c
@@ -22,6 +22,7 @@
|
||||
#include "string-list.h"
|
||||
#include "parse-options.h"
|
||||
#include "read-cache-ll.h"
|
||||
#include "symlinks.h"
|
||||
|
||||
#include "setup.h"
|
||||
#include "strvec.h"
|
||||
@@ -48,6 +49,12 @@ enum update_mode {
|
||||
MOVE_VIA_PARENT_DIR = (1 << 5),
|
||||
};
|
||||
|
||||
static int needs_worktree_rename(enum update_mode mode, enum update_mode dst_mode)
|
||||
{
|
||||
return !(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
|
||||
!(dst_mode & (SKIP_WORKTREE_DIR | SPARSE));
|
||||
}
|
||||
|
||||
#define DUP_BASENAME 1
|
||||
#define KEEP_TRAILING_SLASH 2
|
||||
|
||||
@@ -443,6 +450,41 @@ dir_check:
|
||||
bad = _("destination directory does not exist");
|
||||
goto act_on_entry;
|
||||
}
|
||||
if (has_symlink_leading_path(dst, strlen(dst))) {
|
||||
bad = _("destination is beyond a symbolic link");
|
||||
goto act_on_entry;
|
||||
}
|
||||
|
||||
/*
|
||||
* If we are going to move SRC to DST on disk, DST's leading
|
||||
* directories must already exist.
|
||||
*/
|
||||
if (needs_worktree_rename(modes[i], dst_mode)) {
|
||||
const char *slash_ = strrchr(dst, '/');
|
||||
|
||||
if (slash_) {
|
||||
struct stat dir_st;
|
||||
char *dst_dir = xstrdup(dst);
|
||||
char *slash = &dst_dir[slash_ - dst];
|
||||
|
||||
*slash = '\0';
|
||||
if (lstat(dst_dir, &dir_st) < 0) {
|
||||
/*
|
||||
* other errors fall through to rename(),
|
||||
* which reports them
|
||||
*/
|
||||
if (errno == ENOENT || errno == ENOTDIR)
|
||||
bad = _("destination directory does not exist");
|
||||
} else if (!S_ISDIR(dir_st.st_mode)) {
|
||||
bad = _("destination is not a directory");
|
||||
}
|
||||
|
||||
free(dst_dir);
|
||||
}
|
||||
|
||||
if (bad)
|
||||
goto act_on_entry;
|
||||
}
|
||||
|
||||
if (ignore_sparse &&
|
||||
(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) &&
|
||||
@@ -544,12 +586,11 @@ remove_entry:
|
||||
printf(_("Renaming %s to %s\n"), src, dst);
|
||||
if (show_only)
|
||||
continue;
|
||||
if (!(mode & (INDEX | SPARSE | SKIP_WORKTREE_DIR)) &&
|
||||
!(dst_mode & (SKIP_WORKTREE_DIR | SPARSE)) &&
|
||||
if (needs_worktree_rename(mode, dst_mode) &&
|
||||
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))
|
||||
|
||||
102
t/t7001-mv.sh
102
t/t7001-mv.sh
@@ -114,6 +114,108 @@ test_expect_success 'clean up' '
|
||||
git reset --hard
|
||||
'
|
||||
|
||||
test_expect_success 'moving file to directory without trailing slash' '
|
||||
git reset --hard HEAD &&
|
||||
rm -rf file.txt target && mkdir target &&
|
||||
echo content > file.txt &&
|
||||
git add file.txt &&
|
||||
git mv file.txt target &&
|
||||
test_path_is_file target/file.txt
|
||||
'
|
||||
|
||||
test_expect_success 'moving file to a bare filename in the cwd' '
|
||||
git reset --hard &&
|
||||
rm -rf from dest.txt &&
|
||||
mkdir from &&
|
||||
echo content >from/file &&
|
||||
git add from/file &&
|
||||
git mv from/file dest.txt &&
|
||||
test_path_is_file dest.txt
|
||||
'
|
||||
|
||||
test_expect_success 'moving to a non-existent directory' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir 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 'moving to a destination with a file as a leading path component' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir from &&
|
||||
echo contents >from/file &&
|
||||
echo blocker >not-dir &&
|
||||
git add from/file &&
|
||||
test_must_fail git mv from/file not-dir/file 2>actual &&
|
||||
test_grep "destination is not a directory" actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'moving to a destination beyond a symlink' '
|
||||
git reset --hard &&
|
||||
rm -rf from regular-dir link-to-dir &&
|
||||
mkdir from regular-dir &&
|
||||
echo contents >from/file &&
|
||||
ln -s regular-dir link-to-dir &&
|
||||
git add from/file &&
|
||||
test_must_fail git mv from/file link-to-dir/file 2>actual &&
|
||||
test_grep "destination is beyond a symbolic link" actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'moving to a destination with a symlink as an intermediate component' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir -p from/real/inner &&
|
||||
echo contents >from/file &&
|
||||
ln -s real from/link &&
|
||||
git add from/file from/link &&
|
||||
test_must_fail git mv from/file from/link/inner/dst 2>actual &&
|
||||
test_grep "destination is beyond a symbolic link" actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'refuses to overwrite a symlink at the destination' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir from &&
|
||||
echo contents >from/file &&
|
||||
ln -s target from/link &&
|
||||
git add from/file from/link &&
|
||||
test_must_fail git mv from/file from/link 2>actual &&
|
||||
test_grep "destination exists" actual
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'mv through a symlinked leading path does not touch the index' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir from &&
|
||||
echo contents >from/src &&
|
||||
ln -s . from/link &&
|
||||
git add from/src from/link &&
|
||||
git commit -m "setup symlink case" &&
|
||||
git ls-files --stage >expect.index &&
|
||||
test_must_fail git mv from/src from/link/real/dst 2>actual &&
|
||||
test_grep "destination is beyond a symbolic link" actual &&
|
||||
git ls-files --stage >actual.index &&
|
||||
test_cmp expect.index actual.index
|
||||
'
|
||||
|
||||
test_expect_success SYMLINKS 'mv -f does not follow a symlinked leading path' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir from &&
|
||||
echo contents >from/src &&
|
||||
ln -s file from/link &&
|
||||
git add from/src from/link &&
|
||||
test_must_fail git mv -f from/src from/link/dst 2>actual &&
|
||||
test_grep "destination is beyond a symbolic link" actual
|
||||
'
|
||||
|
||||
test_expect_success 'mv --dry-run detects non-existent destination parent directory' '
|
||||
git reset --hard &&
|
||||
rm -rf from && mkdir from &&
|
||||
echo contents >from/file &&
|
||||
git add from/file &&
|
||||
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/ &&
|
||||
|
||||
Reference in New Issue
Block a user