Commit Diff


commit - af57b12ab516c7fa5ecc8bd00db5637240411ed7
commit + 3d9a4ec407702ad2b932c522001f1b88a36571de
blob - fa3eec221ff24961d74441b2e25a3a6186877ecd
blob + cf8a72840c0b31087b00b5d1bfe09ed1eb4479ad
--- lib/object_create.c
+++ lib/object_create.c
@@ -128,10 +128,15 @@ got_object_blob_create(struct got_object_id **id, cons
 	SHA1Init(&sha1_ctx);
 
 	fd = open(ondisk_path, O_RDONLY | O_NOFOLLOW);
-	if (fd == -1)
-		return got_error_from_errno2("open", ondisk_path);
+	if (fd == -1) {
+		if (errno != ELOOP)
+			return got_error_from_errno2("open", ondisk_path);
 
-	if (fstat(fd, &sb) == -1) {
+		if (lstat(ondisk_path, &sb) == -1) {
+			err = got_error_from_errno2("lstat", ondisk_path);
+			goto done;
+		}
+	} else if (fstat(fd, &sb) == -1) {
 		err = got_error_from_errno2("fstat", ondisk_path);
 		goto done;
 	}
@@ -156,13 +161,21 @@ got_object_blob_create(struct got_object_id **id, cons
 		goto done;
 	}
 	for (;;) {
-		char buf[8192];
+		char buf[PATH_MAX];
 		ssize_t inlen;
 
-		inlen = read(fd, buf, sizeof(buf));
-		if (inlen == -1) {
-			err = got_error_from_errno("read");
-			goto done;
+		if (S_ISLNK(sb.st_mode)) {
+			inlen = readlink(ondisk_path, buf, sizeof(buf));
+			if (inlen == -1) {
+				err = got_error_from_errno("readlink");
+				goto done;
+			}
+		} else {
+			inlen = read(fd, buf, sizeof(buf));
+			if (inlen == -1) {
+				err = got_error_from_errno("read");
+				goto done;
+			}
 		}
 		if (inlen == 0)
 			break; /* EOF */
@@ -172,6 +185,8 @@ got_object_blob_create(struct got_object_id **id, cons
 			err = got_ferror(blobfile, GOT_ERR_IO);
 			goto done;
 		}
+		if (S_ISLNK(sb.st_mode))
+			break;
 	}
 
 	*id = malloc(sizeof(**id));
@@ -218,6 +233,8 @@ te_mode2str(char *buf, size_t len, struct got_tree_ent
 			mode |= S_IXUSR | S_IXGRP | S_IXOTH;
 	} else if (got_object_tree_entry_is_submodule(te))
 		mode = S_IFDIR | S_IFLNK;
+	else if (S_ISLNK(te->mode))
+		mode = S_IFLNK; /* Git leaves all the other bits unset. */
 	else if (S_ISDIR(te->mode))
 		mode = S_IFDIR; /* Git leaves all the other bits unset. */
 	else
blob - 18b7915d9fcf0ce6358373186138b917bbf2f1ba
blob + 4f2a83243d33c26ba5cf655c2b3e82a61f8d5517
--- lib/worktree.c
+++ lib/worktree.c
@@ -4343,6 +4343,9 @@ match_ct_parent_path(int *match, struct got_commitable
 static mode_t
 get_ct_file_mode(struct got_commitable *ct)
 {
+	if (S_ISLNK(ct->mode))
+		return S_IFLNK;
+
 	return S_IFREG | (ct->mode & ((S_IRWXU | S_IRWXG | S_IRWXO)));
 }
 
blob - 190d9e7cedd9ad2b17f54f971ebcf4cc3acd7c3f
blob + 9bf3085d0b6f3078e98d58aaff1f4e4e4525bff9
--- regress/cmdline/commit.sh
+++ regress/cmdline/commit.sh
@@ -893,12 +893,125 @@ function test_commit_with_unrelated_submodule {
 	local head_rev=`git_show_head $testroot/repo`
 	echo "M  alpha" > $testroot/stdout.expected
 	echo "Created commit $head_rev" >> $testroot/stdout.expected
+
+	cmp -s $testroot/stdout.expected $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/stdout.expected $testroot/stdout
+	fi
+	test_done "$testroot" "$ret"
+}
+
+function test_commit_symlink {
+	local testroot=`test_init commit_symlink`
+
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		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
+
+	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
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	if ! [ -h $testroot/wt2/alpha.link ]; then
+		echo "alpha.link is not a symlink"
+		test_done "$testroot" "1"
+		return 1
+	fi
+
+	readlink $testroot/wt2/alpha.link > $testroot/stdout
+	echo "alpha" > $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
 	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
+
+	echo -n "/etc/passwd" > $testroot/content.expected
+	cp $testroot/wt2/passwd.link $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	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
+	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
+
+	readlink $testroot/wt2/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
+	fi
 	test_done "$testroot" "$ret"
 }
 
@@ -922,3 +1035,4 @@ run_test test_commit_gitconfig_author
 run_test test_commit_xbit_change
 run_test test_commit_normalizes_filemodes
 run_test test_commit_with_unrelated_submodule
+run_test test_commit_symlink