Commit 63c5ca5de411be54e75480b0efec04014ffab46e

Stefan Sperling 2019-08-24T20:56:15

detect and ignore Git submodules

diff --git a/got/got.1 b/got/got.1
index 45c8c5c..ffc4d07 100644
--- a/got/got.1
+++ b/got/got.1
@@ -405,6 +405,7 @@ annotations:
 .It @ Ta entry is a symbolic link
 .It / Ta entry is a directory
 .It * Ta entry is an executable file
+.It $ Ta entry is a Git submodule
 .El
 .Pp
 If no
diff --git a/got/got.c b/got/got.c
index 34e0719..77942a8 100644
--- a/got/got.c
+++ b/got/got.c
@@ -2511,7 +2511,9 @@ print_entry(struct got_tree_entry *te, const char *id, const char *path,
 	while (path[0] == '/')
 		path++;
 
-	if (S_ISLNK(te->mode))
+	if (got_object_tree_entry_is_submodule(te))
+		modestr = "$";
+	else if (S_ISLNK(te->mode))
 		modestr = "@";
 	else if (S_ISDIR(te->mode))
 		modestr = "/";
diff --git a/include/got_object.h b/include/got_object.h
index 93db9af..8063afe 100644
--- a/include/got_object.h
+++ b/include/got_object.h
@@ -186,6 +186,9 @@ const struct got_tree_entries *got_object_tree_get_entries(
 const struct got_tree_entry *got_object_tree_find_entry(
     struct got_tree_object *, const char *);
 
+/* Return non-zero if the specified tree entry is a Git submodule. */
+int got_object_tree_entry_is_submodule(const struct got_tree_entry *);
+
 /*
  * Compare two trees and indicate whether the entry at the specified path
  * differs between them. The path must not be the root path "/"; the function
diff --git a/lib/diff.c b/lib/diff.c
index 3c3c339..e269983 100644
--- a/lib/diff.c
+++ b/lib/diff.c
@@ -498,6 +498,9 @@ diff_entry_old_new(const struct got_tree_entry *te1,
 	const struct got_error *err = NULL;
 	int id_match;
 
+	if (got_object_tree_entry_is_submodule(te1))
+		return NULL;
+
 	if (te2 == NULL) {
 		if (S_ISDIR(te1->mode))
 			err = diff_deleted_tree(te1->id, label1, repo,
@@ -511,7 +514,8 @@ diff_entry_old_new(const struct got_tree_entry *te1,
 				    label1, NULL, repo);
 		}
 		return err;
-	}
+	} else if (got_object_tree_entry_is_submodule(te2))
+		return NULL;
 
 	id_match = (got_object_id_cmp(te1->id, te2->id) == 0);
 	if (S_ISDIR(te1->mode) && S_ISDIR(te2->mode)) {
@@ -545,6 +549,9 @@ diff_entry_new_old(const struct got_tree_entry *te2,
 	if (te1 != NULL) /* handled by diff_entry_old_new() */
 		return NULL;
 
+	if (got_object_tree_entry_is_submodule(te2))
+		return NULL;
+
 	if (S_ISDIR(te2->mode))
 		return diff_added_tree(te2->id, label2, repo, cb, cb_arg,
 		    diff_content);
diff --git a/lib/fileindex.c b/lib/fileindex.c
index 1f0d5de..696ab23 100644
--- a/lib/fileindex.c
+++ b/lib/fileindex.c
@@ -704,7 +704,7 @@ walk_tree(struct got_tree_entry **next, struct got_fileindex *fileindex,
 {
 	const struct got_error *err = NULL;
 
-	if (S_ISDIR(te->mode)) {
+	if (!got_object_tree_entry_is_submodule(te) && S_ISDIR(te->mode)) {
 		char *subpath;
 		struct got_tree_object *subtree;
 
@@ -756,7 +756,9 @@ diff_fileindex_tree(struct got_fileindex *fileindex,
 			free(te_path);
 			if (cmp == 0) {
 				if (got_path_is_child((*ie)->path, path,
-				    path_len) && (entry_name == NULL ||
+				    path_len) &&
+				    !got_object_tree_entry_is_submodule(te) &&
+				    (entry_name == NULL ||
 				    strcmp(te->name, entry_name) == 0)) {
 					err = cb->diff_old_new(cb_arg, *ie, te,
 					    path);
@@ -799,8 +801,9 @@ diff_fileindex_tree(struct got_fileindex *fileindex,
 			}
 			*ie = next;
 		} else if (te) {
-			if (entry_name == NULL ||
-			    strcmp(te->name, entry_name) == 0) {
+			if (!got_object_tree_entry_is_submodule(te) &&
+			    (entry_name == NULL ||
+			    strcmp(te->name, entry_name) == 0)) {
 				err = cb->diff_new(cb_arg, te, path);
 				if (err || entry_name)
 					break;
diff --git a/lib/object.c b/lib/object.c
index 72be8b6..e5c043c 100644
--- a/lib/object.c
+++ b/lib/object.c
@@ -1677,3 +1677,9 @@ done:
 	}
 	return err;
 }
+
+int
+got_object_tree_entry_is_submodule(const struct got_tree_entry *te)
+{
+	return (te->mode & S_IFMT) == (S_IFDIR | S_IFLNK);
+}
diff --git a/lib/worktree.c b/lib/worktree.c
index 9a7c693..8921d44 100644
--- a/lib/worktree.c
+++ b/lib/worktree.c
@@ -1426,6 +1426,9 @@ diff_new(void *arg, struct got_tree_entry *te, const char *parent_path)
 	if (a->cancel_cb && a->cancel_cb(a->cancel_arg))
 		return got_error(GOT_ERR_CANCELLED);
 
+	if (got_object_tree_entry_is_submodule(te))
+		return NULL;
+
 	if (asprintf(&path, "%s%s%s", parent_path,
 	    parent_path[0] ? "/" : "", te->name)
 	    == -1)
@@ -3835,6 +3838,17 @@ write_tree(struct got_object_id **new_tree_id,
 		SIMPLEQ_FOREACH(te, &base_entries->head, entry) {
 			struct got_commitable *ct = NULL;
 
+			if (got_object_tree_entry_is_submodule(te)) {
+				/* Entry is a submodule; just copy it. */
+				err = got_object_tree_entry_dup(&new_te, te);
+				if (err)
+					goto done;
+				err = insert_tree_entry(new_te, &paths);
+				if (err)
+					goto done;
+				continue;
+			}
+
 			if (S_ISDIR(te->mode)) {
 				int modified;
 				err = got_object_tree_entry_dup(&new_te, te);
diff --git a/regress/cmdline/checkout.sh b/regress/cmdline/checkout.sh
index b967040..76ad000 100755
--- a/regress/cmdline/checkout.sh
+++ b/regress/cmdline/checkout.sh
@@ -252,9 +252,54 @@ function test_checkout_tag {
 	test_done "$testroot" "$ret"
 }
 
+function test_checkout_ignores_submodules {
+	local testroot=`test_init checkout_ignores_submodules`
+
+	(cd $testroot && git clone -q repo repo2 >/dev/null)
+	(cd $testroot/repo && git submodule -q add ../repo2)
+	(cd $testroot/repo && git commit -q -m 'adding submodule')
+
+	echo "A  $testroot/wt/.gitmodules" > $testroot/stdout.expected
+	echo "A  $testroot/wt/alpha" >> $testroot/stdout.expected
+	echo "A  $testroot/wt/beta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt/epsilon/zeta" >> $testroot/stdout.expected
+	echo "A  $testroot/wt/gamma/delta" >> $testroot/stdout.expected
+	echo "Now shut up and hack" >> $testroot/stdout.expected
+
+	got checkout $testroot/repo $testroot/wt > $testroot/stdout
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		test_done "$testroot" "$ret"
+		return 1
+	fi
+
+	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
+
+	echo "alpha" > $testroot/content.expected
+	echo "beta" >> $testroot/content.expected
+	echo "zeta" >> $testroot/content.expected
+	echo "delta" >> $testroot/content.expected
+	cat $testroot/wt/alpha $testroot/wt/beta $testroot/wt/epsilon/zeta \
+	    $testroot/wt/gamma/delta > $testroot/content
+
+	cmp -s $testroot/content.expected $testroot/content
+	ret="$?"
+	if [ "$ret" != "0" ]; then
+		diff -u $testroot/content.expected $testroot/content
+	fi
+	test_done "$testroot" "$ret"
+}
+
 run_test test_checkout_basic
 run_test test_checkout_dir_exists
 run_test test_checkout_dir_not_empty
 run_test test_checkout_sets_xbit
 run_test test_checkout_commit_from_wrong_branch
 run_test test_checkout_tag
+run_test test_checkout_ignores_submodules
diff --git a/tog/tog.1 b/tog/tog.1
index 46ebc3a..4af5ca7 100644
--- a/tog/tog.1
+++ b/tog/tog.1
@@ -247,6 +247,7 @@ Displayed tree entries may carry one of the following trailing annotations:
 .It @ Ta entry is a symbolic link
 .It / Ta entry is a directory
 .It * Ta entry is an executable file
+.It $ Ta entry is a Git submodule
 .El
 .Pp
 The key bindings for
diff --git a/tog/tog.c b/tog/tog.c
index d5a10ac..fbc6df4 100644
--- a/tog/tog.c
+++ b/tog/tog.c
@@ -3935,7 +3935,9 @@ draw_tree_entries(struct tog_view *view,
 				return got_error_from_errno(
 				    "got_object_id_str");
 		}
-		if (S_ISLNK(te->mode))
+		if (got_object_tree_entry_is_submodule(te))
+			modestr = "$";
+		else if (S_ISLNK(te->mode))
 			modestr = "@";
 		else if (S_ISDIR(te->mode))
 			modestr = "/";