Commit Diff


commit - 2f63b34ce3d2db78052d19284337414845664b6f
commit + bd6aa3590530fb1684f2ca8ded49f431bf0e9a18
blob - 4f2a83243d33c26ba5cf655c2b3e82a61f8d5517
blob + 8d06a93d5428544c51f0757dc50b19a388cb67d8
--- lib/worktree.c
+++ lib/worktree.c
@@ -1025,7 +1025,8 @@ static const struct got_error *
 install_blob(struct got_worktree *worktree, const char *ondisk_path,
     const char *path, mode_t te_mode, mode_t st_mode,
     struct got_blob_object *blob, int restoring_missing_file,
-    int reverting_versioned_file, struct got_repository *repo,
+    int reverting_versioned_file, int installing_bad_symlink,
+    struct got_repository *repo,
     got_worktree_checkout_cb progress_cb, void *progress_arg);
 
 static const struct got_error *
@@ -1057,7 +1058,7 @@ install_symlink(struct got_worktree *worktree, const c
 			return install_blob(worktree, ondisk_path, path,
 			    GOT_DEFAULT_FILE_MODE, st_mode, blob,
 			    restoring_missing_file, reverting_versioned_file,
-			    repo, progress_cb, progress_arg);
+			    1, repo, progress_cb, progress_arg);
 		}
 		if (len > 0) {
 			/* Skip blob object header first time around. */
@@ -1106,7 +1107,7 @@ install_symlink(struct got_worktree *worktree, const c
 		got_object_blob_rewind(blob);
 		err = install_blob(worktree, ondisk_path, path,
 		    GOT_DEFAULT_FILE_MODE, st_mode, blob,
-		    restoring_missing_file, reverting_versioned_file,
+		    restoring_missing_file, reverting_versioned_file, 1,
 		    repo, progress_cb, progress_arg);
 		goto done;
 	}
@@ -1123,7 +1124,7 @@ install_symlink(struct got_worktree *worktree, const c
 		got_object_blob_rewind(blob);
 		err = install_blob(worktree, ondisk_path, path,
 		    GOT_DEFAULT_FILE_MODE, st_mode, blob,
-		    restoring_missing_file, reverting_versioned_file,
+		    restoring_missing_file, reverting_versioned_file, 1,
 		    repo, progress_cb, progress_arg);
 		goto done;
 	}
@@ -1158,8 +1159,10 @@ install_symlink(struct got_worktree *worktree, const c
 				    target_len);
 				if (err)
 					goto done;
-				err = (*progress_cb)(progress_arg,
-				    GOT_STATUS_UPDATE, path);
+				if (progress_cb) {
+					err = (*progress_cb)(progress_arg,
+					    GOT_STATUS_UPDATE, path);
+				}
 				goto done;
 			}
 		}
@@ -1190,7 +1193,7 @@ install_symlink(struct got_worktree *worktree, const c
 			got_object_blob_rewind(blob);
 			err = install_blob(worktree, ondisk_path, path,
 			    GOT_DEFAULT_FILE_MODE, st_mode, blob,
-			    restoring_missing_file, reverting_versioned_file,
+			    restoring_missing_file, reverting_versioned_file, 1,
 			    repo, progress_cb, progress_arg);
 		} else if (errno == ENOTDIR) {
 			err = got_error_path(ondisk_path,
@@ -1199,7 +1202,7 @@ install_symlink(struct got_worktree *worktree, const c
 			err = got_error_from_errno3("symlink",
 			    target_path, ondisk_path);
 		}
-	} else
+	} else if (progress_cb)
 		err = (*progress_cb)(progress_arg, GOT_STATUS_ADD, path);
 done:
 	free(resolved_path);
@@ -1212,8 +1215,9 @@ static const struct got_error *
 install_blob(struct got_worktree *worktree, const char *ondisk_path,
     const char *path, mode_t te_mode, mode_t st_mode,
     struct got_blob_object *blob, int restoring_missing_file,
-    int reverting_versioned_file, struct got_repository *repo,
-    got_worktree_checkout_cb progress_cb, void *progress_arg)
+    int reverting_versioned_file, int installing_bad_symlink,
+    struct got_repository *repo, got_worktree_checkout_cb progress_cb,
+    void *progress_arg)
 {
 	const struct got_error *err = NULL;
 	int fd = -1;
@@ -1243,7 +1247,7 @@ install_blob(struct got_worktree *worktree, const char
 				return got_error_from_errno2("open",
 				    ondisk_path);
 		} else if (errno == EEXIST) {
-			if (!S_ISREG(st_mode)) {
+			if (!S_ISREG(st_mode) && !installing_bad_symlink) {
 				/* TODO file is obstructed; do something */
 				err = got_error_path(ondisk_path,
 				    GOT_ERR_FILE_OBSTRUCTED);
@@ -1259,15 +1263,19 @@ install_blob(struct got_worktree *worktree, const char
 			return got_error_from_errno2("open", ondisk_path);
 	}
 
-	if (restoring_missing_file)
-		err = (*progress_cb)(progress_arg, GOT_STATUS_MISSING, path);
-	else if (reverting_versioned_file)
-		err = (*progress_cb)(progress_arg, GOT_STATUS_REVERT, path);
-	else
-		err = (*progress_cb)(progress_arg,
-		    update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path);
-	if (err)
-		goto done;
+	if (progress_cb) {
+		if (restoring_missing_file)
+			err = (*progress_cb)(progress_arg, GOT_STATUS_MISSING,
+			    path);
+		else if (reverting_versioned_file)
+			err = (*progress_cb)(progress_arg, GOT_STATUS_REVERT,
+			    path);
+		else
+			err = (*progress_cb)(progress_arg,
+			    update ? GOT_STATUS_UPDATE : GOT_STATUS_ADD, path);
+		if (err)
+			goto done;
+	}
 
 	hdrlen = got_object_blob_get_hdrlen(blob);
 	do {
@@ -1727,7 +1735,7 @@ update_blob(struct got_worktree *worktree,
 			goto done;
 	} else {
 		err = install_blob(worktree, ondisk_path, path, te->mode,
-		    sb.st_mode, blob, status == GOT_STATUS_MISSING, 0,
+		    sb.st_mode, blob, status == GOT_STATUS_MISSING, 0, 0,
 		    repo, progress_cb, progress_arg);
 		if (err)
 			goto done;
@@ -2559,7 +2567,7 @@ merge_file_cb(void *arg, struct got_blob_object *blob1
 			err = install_blob(a->worktree, ondisk_path, path2,
 			    /* XXX get this from parent tree! */
 			    GOT_DEFAULT_FILE_MODE,
-			    sb.st_mode, blob2, 0, 0, repo,
+			    sb.st_mode, blob2, 0, 0, 0, repo,
 			    a->progress_cb, a->progress_arg);
 			if (err)
 				goto done;
@@ -4073,7 +4081,7 @@ revert_file(void *arg, unsigned char status, unsigned 
 		} else {
 			err = install_blob(a->worktree, ondisk_path, ie->path,
 			    te ? te->mode : GOT_DEFAULT_FILE_MODE,
-			    got_fileindex_perms_to_st(ie), blob, 0, 1,
+			    got_fileindex_perms_to_st(ie), blob, 0, 1, 0,
 			    a->repo, a->progress_cb, a->progress_arg);
 			if (err)
 				goto done;
@@ -4889,9 +4897,35 @@ commit_worktree(struct got_object_id **new_commit_id,
 			goto done;
 		}
 		err = got_object_blob_create(&ct->blob_id, ondisk_path, repo);
-		free(ondisk_path);
-		if (err)
+		if (err) {
+			free(ondisk_path);
 			goto done;
+		}
+
+		/*
+		 * When comitting a symlink we convert "bad" symlinks (those
+		 * which point outside the work tree or into .got) to regular
+		 * files. This way, the post-commit work tree state matches
+		 * a fresh checkout of the tree which was committed.
+		 */
+		if (S_ISLNK(get_ct_file_mode(ct))) {
+			struct got_blob_object *blob;
+			err = got_object_open_as_blob(&blob, repo, ct->blob_id,
+			    PATH_MAX); 
+			if (err) {
+				free(ondisk_path);
+				goto done;
+			}
+			err = install_symlink(worktree, ondisk_path, ct->path,
+			    get_ct_file_mode(ct), GOT_DEFAULT_FILE_MODE, blob,
+			    0, 0, repo, NULL, NULL);
+			got_object_blob_close(blob);
+			if (err) {
+				free(ondisk_path);
+				goto done;
+			}
+		}
+		free(ondisk_path);
 	}
 
 	/* Recursively write new tree objects. */
blob - 9bf3085d0b6f3078e98d58aaff1f4e4e4525bff9
blob + 45cd97ff9844d5541bd8f229053371132ae23913
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
@@ -902,101 +902,107 @@ function test_commit_with_unrelated_submodule {
 	test_done "$testroot" "$ret"
 }
 
-function test_commit_symlink {
-	local testroot=`test_init commit_symlink`
+function check_symlinks {
+	local wtpath="$1"
+	if ! [ -h $wtpath/alpha.link ]; then
+		echo "alpha.link is not a symlink"
+		return 1
+	fi
 
-	got checkout $testroot/repo $testroot/wt > /dev/null
+	readlink $wtpath/alpha.link > $testroot/stdout
+	echo "alpha" > $testroot/stdout.expected
+	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
-		test_done "$testroot" "$ret"
+		diff -u $testroot/stdout.expected $testroot/stdout
 		return 1
 	fi
 
-	(cd $testroot/wt && ln -s alpha alpha.link)
-	(cd $testroot/wt && ln -s epsilon epsilon.link)
-	(cd $testroot/wt && ln -s /etc/passwd passwd.link)
-	(cd $testroot/wt && ln -s ../beta epsilon/beta.link)
-	(cd $testroot/wt && ln -s nonexistent nonexistent.link)
-	(cd $testroot/wt && got add alpha.link epsilon.link passwd.link \
-		epsilon/beta.link nonexistent.link > /dev/null)
-
-	(cd $testroot/wt && got commit -m 'test commit_symlink' > $testroot/stdout)
-
-	local head_rev=`git_show_head $testroot/repo`
-	echo "A  alpha.link" > $testroot/stdout.expected
-	echo "A  epsilon.link" >> $testroot/stdout.expected
-	echo "A  nonexistent.link" >> $testroot/stdout.expected
-	echo "A  passwd.link" >> $testroot/stdout.expected
-	echo "A  epsilon/beta.link" >> $testroot/stdout.expected
-	echo "Created commit $head_rev" >> $testroot/stdout.expected
+	if ! [ -h $wtpath/epsilon.link ]; then
+		echo "epsilon.link is not a symlink"
+		return 1
+	fi
 
+	readlink $wtpath/epsilon.link > $testroot/stdout
+	echo "epsilon" > $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
-		test_done "$testroot" "$ret"
 		return 1
 	fi
 
-	got checkout $testroot/repo $testroot/wt2 > /dev/null
+	if [ -h $wtpath/passwd.link ]; then
+		echo -n "passwd.link is a symlink and points outside of work tree: " >&2
+		readlink $wtpath/passwd.link >&2
+		return 1
+	fi
+
+	echo -n "/etc/passwd" > $testroot/content.expected
+	cp $wtpath/passwd.link $testroot/content
 	ret="$?"
 	if [ "$ret" != "0" ]; then
-		test_done "$testroot" "$ret"
+		echo "cp command failed unexpectedly" >&2
 		return 1
 	fi
 
-	if ! [ -h $testroot/wt2/alpha.link ]; then
-		echo "alpha.link is not a symlink"
-		test_done "$testroot" "1"
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
 		return 1
 	fi
 
-	readlink $testroot/wt2/alpha.link > $testroot/stdout
-	echo "alpha" > $testroot/stdout.expected
+	readlink $wtpath/epsilon/beta.link > $testroot/stdout
+	echo "../beta" > $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
-		test_done "$testroot" "$ret"
 		return 1
 	fi
 
-	if ! [ -h $testroot/wt2/epsilon.link ]; then
-		echo "epsilon.link is not a symlink"
-		test_done "$testroot" "1"
-		return 1
-	fi
-
-	readlink $testroot/wt2/epsilon.link > $testroot/stdout
-	echo "epsilon" > $testroot/stdout.expected
+	readlink $wtpath/nonexistent.link > $testroot/stdout
+	echo "nonexistent" > $testroot/stdout.expected
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
-		test_done "$testroot" "$ret"
 		return 1
 	fi
 
-	if [ -h $testroot/wt2/passwd.link ]; then
-		echo -n "passwd.link symlink points outside of work tree: " >&2
-		readlink $testroot/wt2/passwd.link >&2
-		test_done "$testroot" "1"
-		return 1
-	fi
+	return 0
+}
 
-	echo -n "/etc/passwd" > $testroot/content.expected
-	cp $testroot/wt2/passwd.link $testroot/content
+function test_commit_symlink {
+	local testroot=`test_init commit_symlink`
 
-	cmp -s $testroot/content.expected $testroot/content
+	got checkout $testroot/repo $testroot/wt > /dev/null
 	ret="$?"
 	if [ "$ret" != "0" ]; then
-		diff -u $testroot/content.expected $testroot/content
 		test_done "$testroot" "$ret"
 		return 1
 	fi
 
-	readlink $testroot/wt2/epsilon/beta.link > $testroot/stdout
-	echo "../beta" > $testroot/stdout.expected
+	(cd $testroot/wt && ln -s alpha alpha.link)
+	(cd $testroot/wt && ln -s epsilon epsilon.link)
+	(cd $testroot/wt && ln -s /etc/passwd passwd.link)
+	(cd $testroot/wt && ln -s ../beta epsilon/beta.link)
+	(cd $testroot/wt && ln -s nonexistent nonexistent.link)
+	(cd $testroot/wt && got add alpha.link epsilon.link passwd.link \
+		epsilon/beta.link nonexistent.link > /dev/null)
+
+	(cd $testroot/wt && got commit -m 'test commit_symlink' > $testroot/stdout)
+	#(cd $testroot/wt && egdb --args got commit -m 'test commit_symlink')
+
+	local head_rev=`git_show_head $testroot/repo`
+	echo "A  alpha.link" > $testroot/stdout.expected
+	echo "A  epsilon.link" >> $testroot/stdout.expected
+	echo "A  nonexistent.link" >> $testroot/stdout.expected
+	echo "A  passwd.link" >> $testroot/stdout.expected
+	echo "A  epsilon/beta.link" >> $testroot/stdout.expected
+	echo "Created commit $head_rev" >> $testroot/stdout.expected
+
 	cmp -s $testroot/stdout.expected $testroot/stdout
 	ret="$?"
 	if [ "$ret" != "0" ]; then
@@ -1005,14 +1011,28 @@ function test_commit_symlink {
 		return 1
 	fi
 
-	readlink $testroot/wt2/nonexistent.link > $testroot/stdout
-	echo "nonexistent" > $testroot/stdout.expected
-	cmp -s $testroot/stdout.expected $testroot/stdout
+	# verify created in-repository tree
+	got checkout $testroot/repo $testroot/wt2 > /dev/null
 	ret="$?"
 	if [ "$ret" != "0" ]; then
-		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done "$testroot" "$ret"
+		return 1
 	fi
-	test_done "$testroot" "$ret"
+	check_symlinks $testroot/wt2
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	# verify post-commit work tree state matches a fresh checkout
+	check_symlinks $testroot/wt
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+	test_done "$testroot" "0"
 }
 
 run_test test_commit_basic