Commit Diff


commit - 2e9bd5cb1a3122326a3b347a6febc7da047b58ad
commit + 9f32321217acc7223f02908d236a292f3fe3f2af
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 - 7ff73929567851b4bd0ee8ea0d46d55d776a0928
blob + 0e0b0feb644f21aef2762bad36f5c42d5dfb5591
--- lib/worktree.c
+++ lib/worktree.c
@@ -1405,12 +1405,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;
@@ -1880,6 +1882,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,
@@ -1910,7 +1914,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;
@@ -1937,6 +1941,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