Commit Diff


commit - 46cee7a3e76f2e44d2e0c5919de76d228b9c79af
commit + 4a1ddfc2ae992bd73e34ce89b7f309df643aeba5
blob - 45ace73306c72d723185ef208bb2d4b51fd09546
blob + f51b680fd50600a7dde8e98ca7bb6f718b81089c
--- lib/worktree.c
+++ lib/worktree.c
@@ -507,6 +507,40 @@ lock_worktree(struct got_worktree *worktree, int opera
 }
 
 static const struct got_error *
+add_dir_on_disk(struct got_worktree *worktree, const char *path)
+{
+	const struct got_error *err = NULL;
+	char *abspath;
+
+	if (asprintf(&abspath, "%s/%s", worktree->root_path, path) == -1)
+		return got_error_from_errno();
+
+	/* XXX queue work rather than editing disk directly? */
+	if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) {
+		struct stat sb;
+
+		if (errno != EEXIST) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		if (lstat(abspath, &sb) == -1) {
+			err = got_error_from_errno();
+			goto done;
+		}
+
+		if (!S_ISDIR(sb.st_mode)) {
+			/* TODO directory is obstructed; do something */
+			return got_error(GOT_ERR_FILE_OBSTRUCTED);
+		}
+	}
+
+done:
+	free(abspath);
+	return err;
+}
+
+static const struct got_error *
 install_blob(struct got_worktree *worktree, struct got_fileindex *fileindex,
    struct got_fileindex_entry *entry, const char *path,
    struct got_blob_object *blob,
@@ -526,7 +560,19 @@ install_blob(struct got_worktree *worktree, struct got
 	fd = open(ondisk_path, O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
 	    GOT_DEFAULT_FILE_MODE);
 	if (fd == -1) {
-		if (errno == EEXIST) {
+		if (errno == ENOENT) {
+			char *parent = dirname(path);
+			if (parent == NULL)
+				return got_error_from_errno();
+			err = add_dir_on_disk(worktree, parent);
+			if (err)
+				return err;
+			fd = open(ondisk_path,
+			    O_RDWR | O_CREAT | O_EXCL | O_NOFOLLOW,
+			    GOT_DEFAULT_FILE_MODE);
+			if (fd == -1)
+				return got_error_from_errno();
+		} else if (errno == EEXIST) {
 			struct stat sb;
 			if (lstat(ondisk_path, &sb) == -1) {
 				err = got_error_from_errno();
@@ -595,40 +641,6 @@ done:
 		close(fd);
 	free(ondisk_path);
 	free(tmppath);
-	return err;
-}
-
-static const struct got_error *
-add_dir_on_disk(struct got_worktree *worktree, const char *path)
-{
-	const struct got_error *err = NULL;
-	char *abspath;
-
-	if (asprintf(&abspath, "%s/%s", worktree->root_path, path) == -1)
-		return got_error_from_errno();
-
-	/* XXX queue work rather than editing disk directly? */
-	if (mkdir(abspath, GOT_DEFAULT_DIR_MODE) == -1) {
-		struct stat sb;
-
-		if (errno != EEXIST) {
-			err = got_error_from_errno();
-			goto done;
-		}
-
-		if (lstat(abspath, &sb) == -1) {
-			err = got_error_from_errno();
-			goto done;
-		}
-
-		if (!S_ISDIR(sb.st_mode)) {
-			/* TODO directory is obstructed; do something */
-			return got_error(GOT_ERR_FILE_OBSTRUCTED);
-		}
-	}
-
-done:
-	free(abspath);
 	return err;
 }
 
blob - a8a3ea579510a1fb52d1335755b07681391a398d
blob + a7e8448f37599141ef907df5d2159eaac83edf3e
--- regress/cmdline/common.sh
+++ regress/cmdline/common.sh
@@ -55,6 +55,7 @@ function make_test_tree
 function test_init
 {
 	local testname="$1"
+	local no_tree="$2"
 	if [ -z "$testname" ]; then
 		echo "No test name provided" >&2
 		return 1
@@ -62,8 +63,10 @@ function test_init
 	local testroot=`mktemp -p /tmp -d got-test-$testname-XXXXXXXX`
 	mkdir $testroot/repo
 	git_init $testroot/repo
-	make_test_tree $testroot/repo
-	git_commit $testroot/repo -m "adding the test tree"
+	if [ -z "$no_tree" ]; then
+		make_test_tree $testroot/repo
+		git_commit $testroot/repo -m "adding the test tree"
+	fi
 	echo "$testroot"
 }
 
blob - 29675054ce70a9414adddb3bf89fd6f602397973
blob + b7d9d15172b1bc6145e019ed80dba37c3dea057e
--- regress/cmdline/update.sh
+++ regress/cmdline/update.sh
@@ -458,6 +458,59 @@ function test_update_moves_files_to_new_dir {
 	if [ -e $testroot/wt/epsilon/psi/mu ]; then
 		echo "removed file epsilon/psi/mu still exists on disk" >&2
 		test_done "$testroot" "1"
+		return 1
+	fi
+
+	test_done "$testroot" "0"
+}
+
+function test_update_creates_missing_parent {
+	local testroot=`test_init update_creates_missing_parent no_tree`
+
+	touch $testroot/repo/Makefile
+	touch $testroot/repo/snake.6
+	touch $testroot/repo/snake.c
+	(cd $testroot/repo && git add .)
+	git_commit $testroot/repo -m "adding initial snake tree"
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	if [ "$?" != "0" ]; then
+		test_done "$testroot" "$?"
+		return 1
+	fi
+
+	mkdir -p $testroot/repo/snake
+	(cd $testroot/repo && git mv Makefile snake.6 snake.c snake)
+	touch $testroot/repo/snake/move.c
+	touch $testroot/repo/snake/pathnames.h
+	touch $testroot/repo/snake/snake.h
+	mkdir -p $testroot/repo/snscore
+	touch $testroot/repo/snscore/Makefile
+	touch $testroot/repo/snscore/snscore.c
+	(cd $testroot/repo && git add .)
+	git_commit $testroot/repo -m "restructuring snake tree"
+
+	echo "D  Makefile" > $testroot/stdout.expected
+	echo "A  snake/Makefile" >> $testroot/stdout.expected
+	echo "A  snake/move.c" >> $testroot/stdout.expected
+	echo "A  snake/pathnames.h" >> $testroot/stdout.expected
+	echo "A  snake/snake.6" >> $testroot/stdout.expected
+	echo "A  snake/snake.c" >> $testroot/stdout.expected
+	echo "A  snake/snake.h" >> $testroot/stdout.expected
+	echo "D  snake.6" >> $testroot/stdout.expected
+	echo "D  snake.c" >> $testroot/stdout.expected
+	echo "A  snscore/Makefile" >> $testroot/stdout.expected
+	echo "A  snscore/snscore.c" >> $testroot/stdout.expected
+	echo -n "Updated to commit " >> $testroot/stdout.expected
+	git_show_head $testroot/repo >> $testroot/stdout.expected
+	echo >> $testroot/stdout.expected
+
+	(cd $testroot/wt && got update > $testroot/stdout)
+
+	cmp $testroot/stdout.expected $testroot/stdout
+	if [ "$?" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$?"
 		return 1
 	fi
 
@@ -474,3 +527,4 @@ run_test test_update_sibling_dirs_with_common_prefix
 run_test test_update_dir_with_dot_sibling
 run_test test_update_moves_files_upwards
 #run_test test_update_moves_files_to_new_dir  # test is failing
+run_test test_update_creates_missing_parent