Merge remote-tracking branch 'origin/development' into jss/fix-ignore-pop
diff --git a/src/diff.c b/src/diff.c
index 484273f..e62f45c 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -876,7 +876,7 @@
 			 DIFF_FLAG_IS_SET(diff, GIT_DIFF_RECURSE_IGNORED_DIRS));
 
 		/* do not advance into directories that contain a .git file */
-		if (recurse_into_dir) {
+		if (recurse_into_dir && !contains_oitem) {
 			git_buf *full = NULL;
 			if (git_iterator_current_workdir_path(&full, info->new_iter) < 0)
 				return -1;
@@ -969,6 +969,16 @@
 		if (git_submodule_lookup(NULL, info->repo, nitem->path) != 0) {
 			giterr_clear();
 			delta_type = GIT_DELTA_IGNORED;
+
+			/* if this contains a tracked item, treat as normal TREE */
+			if (contains_oitem) {
+				error = git_iterator_advance_into(&info->nitem, info->new_iter);
+				if (error != GIT_ENOTFOUND)
+					return error;
+
+				giterr_clear();
+				return git_iterator_advance(&info->nitem, info->new_iter);
+			}
 		}
 	}
 
diff --git a/src/repository.c b/src/repository.c
index 49d1bc6..6b2705b 100644
--- a/src/repository.c
+++ b/src/repository.c
@@ -1955,24 +1955,32 @@
 	return state;
 }
 
-int git_repository__cleanup_files(git_repository *repo, const char *files[], size_t files_len)
+int git_repository__cleanup_files(
+	git_repository *repo, const char *files[], size_t files_len)
 {
-	git_buf path = GIT_BUF_INIT;
+	git_buf buf = GIT_BUF_INIT;
 	size_t i;
-	int error = 0;
+	int error;
 
-	for (i = 0; i < files_len; ++i) {
-		git_buf_clear(&path);
+	for (error = 0, i = 0; !error && i < files_len; ++i) {
+		const char *path;
 
-		if ((error = git_buf_joinpath(&path, repo->path_repository, files[i])) < 0 ||
-			(git_path_isfile(git_buf_cstr(&path)) &&
-			(error = p_unlink(git_buf_cstr(&path))) < 0))
-			goto done;
+		if (git_buf_joinpath(&buf, repo->path_repository, files[i]) < 0)
+			return -1;
+
+		path = git_buf_cstr(&buf);
+
+		if (git_path_isfile(path)) {
+			error = p_unlink(path);
+		} else if (git_path_isdir(path)) {
+			error = git_futils_rmdir_r(path, NULL,
+				GIT_RMDIR_REMOVE_FILES | GIT_RMDIR_REMOVE_BLOCKERS);
+		}
+			
+		git_buf_clear(&buf);
 	}
 
-done:
-	git_buf_free(&path);
-
+	git_buf_free(&buf);
 	return error;
 }
 
@@ -1982,6 +1990,9 @@
 	GIT_MERGE_MSG_FILE,
 	GIT_REVERT_HEAD_FILE,
 	GIT_CHERRY_PICK_HEAD_FILE,
+	GIT_BISECT_LOG_FILE,
+	GIT_REBASE_MERGE_DIR,
+	GIT_REBASE_APPLY_DIR,
 };
 
 int git_repository_state_cleanup(git_repository *repo)
diff --git a/src/userdiff.h b/src/userdiff.h
index 7eb0952..523f2f8 100644
--- a/src/userdiff.h
+++ b/src/userdiff.h
@@ -45,13 +45,13 @@
 static git_diff_driver_definition builtin_defs[] = {
 
 IPATTERN("ada",
-	 "!^(.*[ \t])?(is new|renames|is separate)([ \t].*)?$\n"
+	 "!^(.*[ \t])?(is[ \t]+new|renames|is[ \t]+separate)([ \t].*)?$\n"
 	 "!^[ \t]*with[ \t].*$\n"
 	 "^[ \t]*((procedure|function)[ \t]+.*)$\n"
 	 "^[ \t]*((package|protected|task)[ \t]+.*)$",
 	 /* -- */
 	 "[a-zA-Z][a-zA-Z0-9_]*"
-	 "|[0-9][-+0-9#_.eE]"
+	 "|[-+]?[0-9][0-9#_.aAbBcCdDeEfF]*([eE][+-]?[0-9_]+)?"
 	 "|=>|\\.\\.|\\*\\*|:=|/=|>=|<=|<<|>>|<>"),
 
 IPATTERN("fortran",
@@ -159,15 +159,13 @@
 
 PATTERNS("cpp",
 	 /* Jump targets or access declarations */
-	 "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:.*$\n"
-	 /* C/++ functions/methods at top level */
-	 "^([A-Za-z_][A-Za-z_0-9]*([ \t*]+[A-Za-z_][A-Za-z_0-9]*([ \t]*::[ \t]*[^[:space:]]+)?){1,}[ \t]*\\([^;]*)$\n"
-	 /* compound type at top level */
-	 "^((struct|class|enum)[^;]*)$",
+	 "!^[ \t]*[A-Za-z_][A-Za-z_0-9]*:[[:space:]]*($|/[/*])\n"
+	 /* functions/methods, variables, and compounds at top level */
+	 "^((::[[:space:]]*)?[A-Za-z_].*)$",
 	 /* -- */
 	 "[a-zA-Z_][a-zA-Z0-9_]*"
-	 "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lL]?"
-	 "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->"),
+	 "|[-+0-9.e]+[fFlL]?|0[xXbB]?[0-9a-fA-F]+[lLuU]*"
+	 "|[-+*/<>%&^|=!]=|--|\\+\\+|<<=?|>>=?|&&|\\|\\||::|->\\*?|\\.\\*"),
 
 PATTERNS("csharp",
 	 /* Keywords */
diff --git a/tests/repo/state.c b/tests/repo/state.c
index 5e72272..2d6c780 100644
--- a/tests/repo/state.c
+++ b/tests/repo/state.c
@@ -45,52 +45,70 @@
 {
 	setup_simple_state(GIT_MERGE_HEAD_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_MERGE);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__revert(void)
 {
 	setup_simple_state(GIT_REVERT_HEAD_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_REVERT);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__cherry_pick(void)
 {
 	setup_simple_state(GIT_CHERRY_PICK_HEAD_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_CHERRY_PICK);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__bisect(void)
 {
 	setup_simple_state(GIT_BISECT_LOG_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_BISECT);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__rebase_interactive(void)
 {
 	setup_simple_state(GIT_REBASE_MERGE_INTERACTIVE_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_REBASE_INTERACTIVE);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__rebase_merge(void)
 {
 	setup_simple_state(GIT_REBASE_MERGE_DIR "whatever");
 	assert_repo_state(GIT_REPOSITORY_STATE_REBASE_MERGE);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__rebase(void)
 {
 	setup_simple_state(GIT_REBASE_APPLY_REBASING_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_REBASE);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__apply_mailbox(void)
 {
 	setup_simple_state(GIT_REBASE_APPLY_APPLYING_FILE);
 	assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
 
 void test_repo_state__apply_mailbox_or_rebase(void)
 {
 	setup_simple_state(GIT_REBASE_APPLY_DIR "whatever");
 	assert_repo_state(GIT_REPOSITORY_STATE_APPLY_MAILBOX_OR_REBASE);
+	cl_git_pass(git_repository_state_cleanup(_repo));
+	assert_repo_state(GIT_REPOSITORY_STATE_NONE);
 }
diff --git a/tests/status/submodules.c b/tests/status/submodules.c
index 6d0d63a..63cf73f 100644
--- a/tests/status/submodules.c
+++ b/tests/status/submodules.c
@@ -1,7 +1,5 @@
 #include "clar_libgit2.h"
-#include "buffer.h"
-#include "path.h"
-#include "posix.h"
+#include "fileops.h"
 #include "status_helpers.h"
 #include "../submodule/submodule_helpers.h"
 
@@ -389,3 +387,92 @@
 		g_repo, &opts, cb_status__match, &counts));
 	cl_assert_equal_i(5, counts.entry_count);
 }
+
+void test_status_submodules__broken_stuff_that_git_allows(void)
+{
+	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
+	status_entry_counts counts;
+	git_repository *contained;
+	static const char *expected_files_with_broken[] = {
+		".gitmodules",
+		"added",
+		"broken/tracked",
+		"deleted",
+		"ignored",
+		"modified",
+		"untracked"
+	};
+	static unsigned int expected_status_with_broken[] = {
+		GIT_STATUS_WT_MODIFIED,
+		GIT_STATUS_INDEX_NEW,
+		GIT_STATUS_INDEX_NEW,
+		GIT_STATUS_INDEX_DELETED,
+		GIT_STATUS_IGNORED,
+		GIT_STATUS_WT_MODIFIED,
+		GIT_STATUS_WT_NEW,
+	};
+
+	g_repo = setup_fixture_submodules();
+
+	opts.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED |
+		GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
+		GIT_STATUS_OPT_INCLUDE_IGNORED;
+
+	/* make a directory and stick a tracked item into the index */
+	{
+		git_index *idx;
+		cl_must_pass(p_mkdir("submodules/broken", 0777));
+		cl_git_mkfile("submodules/broken/tracked", "tracked content");
+		cl_git_pass(git_repository_index(&idx, g_repo));
+		cl_git_pass(git_index_add_bypath(idx, "broken/tracked"));
+		cl_git_pass(git_index_write(idx));
+		git_index_free(idx);
+	}
+
+	status_counts_init(
+		counts, expected_files_with_broken, expected_status_with_broken);
+	cl_git_pass(git_status_foreach_ext(
+		g_repo, &opts, cb_status__match, &counts));
+	cl_assert_equal_i(7, counts.entry_count);
+
+	/* directory with tracked items that looks a little bit like a repo */
+
+	cl_must_pass(p_mkdir("submodules/broken/.git", 0777));
+	cl_must_pass(p_mkdir("submodules/broken/.git/info", 0777));
+	cl_git_mkfile("submodules/broken/.git/info/exclude", "# bogus");
+
+	status_counts_init(
+		counts, expected_files_with_broken, expected_status_with_broken);
+	cl_git_pass(git_status_foreach_ext(
+		g_repo, &opts, cb_status__match, &counts));
+	cl_assert_equal_i(7, counts.entry_count);
+
+	/* directory with tracked items that is a repo */
+
+	cl_git_pass(git_futils_rmdir_r(
+		"submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES));
+	cl_git_pass(git_repository_init(&contained, "submodules/broken", false));
+	git_repository_free(contained);
+
+	status_counts_init(
+		counts, expected_files_with_broken, expected_status_with_broken);
+	cl_git_pass(git_status_foreach_ext(
+		g_repo, &opts, cb_status__match, &counts));
+	cl_assert_equal_i(7, counts.entry_count);
+
+	/* directory with tracked items that claims to be a submodule but is not */
+
+	cl_git_pass(git_futils_rmdir_r(
+		"submodules/broken/.git", NULL, GIT_RMDIR_REMOVE_FILES));
+	cl_git_append2file("submodules/.gitmodules",
+		"\n[submodule \"broken\"]\n"
+		"\tpath = broken\n"
+		"\turl = https://github.com/not/used\n\n");
+
+	status_counts_init(
+		counts, expected_files_with_broken, expected_status_with_broken);
+	cl_git_pass(git_status_foreach_ext(
+		g_repo, &opts, cb_status__match, &counts));
+	cl_assert_equal_i(7, counts.entry_count);
+}
+