commit 07fa936579660934e2b84e747df1d21810278e11 from: Stefan Sperling date: Fri Mar 10 20:29:05 2023 UTC handle files changing into directories during 'got update' problem found by naddy@ commit - 5add7f42e1397d136860680e1f0411db17b4f22c commit + 07fa936579660934e2b84e747df1d21810278e11 blob - 59440ec12abde110db7e832a12e05f5bd4bfc613 blob + c924075eacdb3d7e5ebe3fc11b3c59262e7e120f --- got/got.1 +++ got/got.1 @@ -628,6 +628,7 @@ Show the status of each affected file, using the follo .It G Ta file was updated and local changes were merged cleanly .It C Ta file was updated and conflicts occurred during merge .It D Ta file was deleted +.It d Ta file's deletion was prevented by local modifications .It A Ta new file was added .It \(a~ Ta versioned file is obstructed by a non-regular file .It ! Ta a missing versioned file was restored blob - ce1ab2adbfadcbf6d57d031d9390a6d8a3940fb8 blob + 9e87414b038cb6aa0ef965f162999f1108c5d82a --- lib/worktree.c +++ lib/worktree.c @@ -1408,12 +1408,14 @@ install_blob(struct got_worktree *worktree, const char fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW | O_CLOEXEC, mode); if (fd == -1) { - if (errno == ENOENT) { + if (errno == ENOENT || errno == ENOTDIR) { char *parent; err = got_path_dirname(&parent, path); if (err) return err; err = add_dir_on_disk(worktree, parent); + if (err && err->code == GOT_ERR_FILE_OBSTRUCTED) + err = got_error_path(path, err->code); free(parent); if (err) return err; @@ -1883,6 +1885,8 @@ sync_timestamps(int wt_fd, const char *path, unsigned return NULL; } +static const struct got_error *remove_ondisk_file(const char *, const char *); + static const struct got_error * update_blob(struct got_worktree *worktree, struct got_fileindex *fileindex, struct got_fileindex_entry *ie, @@ -1913,7 +1917,7 @@ update_blob(struct got_worktree *worktree, sb.st_mode = got_fileindex_perms_to_st(ie); } else { if (stat(ondisk_path, &sb) == -1) { - if (errno != ENOENT) { + if (errno != ENOENT && errno != ENOTDIR) { err = got_error_from_errno2("stat", ondisk_path); goto done; @@ -1940,6 +1944,32 @@ update_blob(struct got_worktree *worktree, err = (*progress_cb)(progress_arg, GOT_STATUS_CANNOT_UPDATE, path); goto done; + } + + if (S_ISDIR(te->mode)) { /* file changing into a directory */ + if (status == GOT_STATUS_UNVERSIONED) { + err = (*progress_cb)(progress_arg, status, path); + } else if (status != GOT_STATUS_NO_CHANGE && + status != GOT_STATUS_DELETE && + status != GOT_STATUS_NONEXISTENT && + status != GOT_STATUS_MISSING) { + err = (*progress_cb)(progress_arg, + GOT_STATUS_CANNOT_DELETE, path); + } else if (ie) { + if (status != GOT_STATUS_DELETE && + status != GOT_STATUS_NONEXISTENT && + status != GOT_STATUS_MISSING) { + err = remove_ondisk_file(worktree->root_path, + ie->path); + if (err && !(err->code == GOT_ERR_ERRNO && + errno == ENOENT)) + goto done; + } + got_fileindex_entry_remove(fileindex, ie); + err = (*progress_cb)(progress_arg, GOT_STATUS_DELETE, + ie->path); + } + goto done; /* nothing else to do */ } if (ie && status != GOT_STATUS_MISSING && S_ISREG(sb.st_mode) && blob - 4aa5a095404e1e99181588149852bfe3df333a9c blob + c2b798a572a28b05ce1a6dd4ea570a7728b1c23f --- regress/cmdline/update.sh +++ regress/cmdline/update.sh @@ -655,11 +655,69 @@ test_update_changes_file_to_dir() { (cd $testroot/wt && got update > $testroot/stdout 2> $testroot/stderr) ret=$? if [ $ret -ne 0 ]; then - ret="xfail change file into directory" + echo "update failed unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "D alpha" > $testroot/stdout.expected + echo "A alpha/eta" >> $testroot/stdout.expected + echo -n "Updated to refs/heads/master: " >> $testroot/stdout.expected + git_show_head $testroot/repo >> $testroot/stdout.expected + echo >> $testroot/stdout.expected + + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout fi test_done "$testroot" "$ret" } +test_update_changes_modified_file_to_dir() { + local testroot=`test_init update_changes_modified_file_to_dir` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done "$testroot" "$ret" + return 1 + fi + + git_rm $testroot/repo alpha + mkdir $testroot/repo/alpha + echo eta > $testroot/repo/alpha/eta + (cd $testroot/repo && git add alpha/eta) + git_commit $testroot/repo -m "changed alpha into directory" + + echo "modified alpha" >> $testroot/wt/alpha + cp $testroot/wt/alpha $testroot/wt/content.expected + (cd $testroot/wt && got update > $testroot/stdout 2> $testroot/stderr) + ret=$? + if [ $ret -eq 0 ]; then + echo "update succeeded unexpectedly" >&2 + test_done "$testroot" "1" + return 1 + fi + + echo "d alpha" > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done "$testroot" "$ret" + return 1 + fi + + echo "got: alpha/eta: file is obstructed" > $testroot/stderr.expected + cmp -s $testroot/stderr.expected $testroot/stderr + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stderr.expected $testroot/stderr + fi + test_done "$testroot" "$ret" +} + test_update_merges_file_edits() { local testroot=`test_init update_merges_file_edits` @@ -3120,6 +3178,7 @@ run_test test_update_creates_missing_parent run_test test_update_creates_missing_parent_with_subdir run_test test_update_file_in_subsubdir run_test test_update_changes_file_to_dir +run_test test_update_changes_modified_file_to_dir run_test test_update_merges_file_edits run_test test_update_keeps_xbit run_test test_update_clears_xbit