submodules.c 21 KB
Newer Older
1 2
#include "clar_libgit2.h"
#include "repository.h"
3
#include "posix.h"
4
#include "diff_helpers.h"
5 6 7 8 9 10 11 12 13 14
#include "../submodule/submodule_helpers.h"

static git_repository *g_repo = NULL;

void test_diff_submodules__initialize(void)
{
}

void test_diff_submodules__cleanup(void)
{
15
	cl_git_sandbox_cleanup();
16 17
}

Nicolas Hake committed
18 19
#define get_buf_ptr(buf) ((buf)->asize ? (buf)->ptr : NULL)

20
static void check_diff_patches_at_line(
21
	git_diff *diff, const char **expected, const char *file, int line)
22 23
{
	const git_diff_delta *delta;
24
	git_patch *patch = NULL;
25
	size_t d, num_d = git_diff_num_deltas(diff);
Nicolas Hake committed
26
	git_buf buf = GIT_BUF_INIT;
27

28
	for (d = 0; d < num_d; ++d, git_patch_free(patch)) {
Russell Belfer committed
29 30
		cl_git_pass(git_patch_from_diff(&patch, diff, d));
		cl_assert((delta = git_patch_get_delta(patch)) != NULL);
31

32
		if (delta->status == GIT_DELTA_UNMODIFIED) {
33
			cl_assert_at_line(expected[d] == NULL, file, line);
34
			continue;
35
		}
36 37 38

		if (expected[d] && !strcmp(expected[d], "<SKIP>"))
			continue;
39 40 41 42
		if (expected[d] && !strcmp(expected[d], "<UNTRACKED>")) {
			cl_assert_at_line(delta->status == GIT_DELTA_UNTRACKED, file, line);
			continue;
		}
43
		if (expected[d] && !strcmp(expected[d], "<END>")) {
Nicolas Hake committed
44
			cl_git_pass(git_patch_to_buf(&buf, patch));
45
			cl_assert_at_line(!strcmp(expected[d], "<END>"), file, line);
46
		}
47

Nicolas Hake committed
48
		cl_git_pass(git_patch_to_buf(&buf, patch));
49

Russell Belfer committed
50 51
		clar__assert_equal(
			file, line, "expected diff did not match actual diff", 1,
Nicolas Hake committed
52
			"%s", expected[d], get_buf_ptr(&buf));
53
		git_buf_dispose(&buf);
54 55
	}

56
	cl_assert_at_line(expected[d] && !strcmp(expected[d], "<END>"), file, line);
57 58
}

59 60 61
#define check_diff_patches(diff, exp) \
	check_diff_patches_at_line(diff, exp, __FILE__, __LINE__)

62 63 64
void test_diff_submodules__unmodified_submodule(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
65
	git_diff *diff = NULL;
66 67 68 69 70 71 72 73 74 75 76
	static const char *expected[] = {
		"<SKIP>", /* .gitmodules */
		NULL, /* added */
		NULL, /* ignored */
		"diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */
		NULL, /* testrepo.git */
		NULL, /* unmodified */
		NULL, /* untracked */
		"<END>"
	};

77
	g_repo = setup_fixture_submodules();
78 79 80 81

	opts.flags = GIT_DIFF_INCLUDE_IGNORED |
		GIT_DIFF_INCLUDE_UNTRACKED |
		GIT_DIFF_INCLUDE_UNMODIFIED;
82
	opts.old_prefix = "a"; opts.new_prefix = "b";
83 84 85

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected);
86
	git_diff_free(diff);
87 88 89 90 91
}

void test_diff_submodules__dirty_submodule(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
92
	git_diff *diff = NULL;
93 94 95 96 97 98 99 100 101 102 103
	static const char *expected[] = {
		"<SKIP>", /* .gitmodules */
		NULL, /* added */
		NULL, /* ignored */
		"diff --git a/modified b/modified\nindex 092bfb9..452216e 100644\n--- a/modified\n+++ b/modified\n@@ -1 +1,2 @@\n-yo\n+changed\n+\n", /* modified */
		"diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */
		NULL, /* unmodified */
		NULL, /* untracked */
		"<END>"
	};

104
	g_repo = setup_fixture_submodules();
105 106 107 108 109 110 111

	cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
	cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");

	opts.flags = GIT_DIFF_INCLUDE_IGNORED |
		GIT_DIFF_INCLUDE_UNTRACKED |
		GIT_DIFF_INCLUDE_UNMODIFIED;
112
	opts.old_prefix = "a"; opts.new_prefix = "b";
113 114 115

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected);
116
	git_diff_free(diff);
117 118
}

119 120 121
void test_diff_submodules__dirty_submodule_2(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
122
	git_diff *diff = NULL, *diff2 = NULL;
123
	char *smpath = "testrepo";
124 125 126
	static const char *expected_none[] = {
		"<END>"
	};
127 128 129 130 131
	static const char *expected_dirty[] = {
		"diff --git a/testrepo b/testrepo\nindex a65fedf..a65fedf 160000\n--- a/testrepo\n+++ b/testrepo\n@@ -1 +1 @@\n-Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750\n+Subproject commit a65fedf39aefe402d3bb6e24df4d4f5fe4547750-dirty\n", /* testrepo.git */
		"<END>"
	};

132
	g_repo = setup_fixture_submodules();
133

134
	opts.flags = GIT_DIFF_INCLUDE_UNTRACKED |
Russell Belfer committed
135
		GIT_DIFF_SHOW_UNTRACKED_CONTENT |
136 137 138
		GIT_DIFF_RECURSE_UNTRACKED_DIRS |
		GIT_DIFF_DISABLE_PATHSPEC_MATCH;
	opts.old_prefix = "a"; opts.new_prefix = "b";
139 140 141 142 143
	opts.pathspec.count = 1;
	opts.pathspec.strings = &smpath;

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_none);
144
	git_diff_free(diff);
145 146 147 148 149 150

	cl_git_rewritefile("submodules/testrepo/README", "heyheyhey");
	cl_git_mkfile("submodules/testrepo/all_new.txt", "never seen before");

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
151 152 153 154 155 156 157

	{
		git_tree *head;

		cl_git_pass(git_repository_head_tree(&head, g_repo));
		cl_git_pass(git_diff_tree_to_index(&diff2, g_repo, head, NULL, &opts));
		cl_git_pass(git_diff_merge(diff, diff2));
158
		git_diff_free(diff2);
Russell Belfer committed
159
		git_tree_free(head);
160 161 162 163

		check_diff_patches(diff, expected_dirty);
	}

164
	git_diff_free(diff);
165 166 167

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
168
	git_diff_free(diff);
169 170
}

171 172 173
void test_diff_submodules__submod2_index_to_wd(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
174
	git_diff *diff = NULL;
175 176
	static const char *expected[] = {
		"<SKIP>", /* .gitmodules */
177 178
		"<UNTRACKED>", /* not-submodule */
		"<UNTRACKED>", /* not */
179 180
		"diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
181 182
		"<UNTRACKED>", /* sm_changed_head- */
		"<UNTRACKED>", /* sm_changed_head_ */
183 184 185 186 187 188
		"diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
		"diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */
		"diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
		"<END>"
	};

189
	g_repo = setup_fixture_submod2();
190

191 192 193 194
	/* bracket existing submodule with similarly named items */
	cl_git_mkfile("submod2/sm_changed_head-", "hello");
	cl_git_mkfile("submod2/sm_changed_head_", "hello");

195
	opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
196
	opts.old_prefix = "a"; opts.new_prefix = "b";
197 198 199

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected);
200
	git_diff_free(diff);
201 202 203 204 205 206
}

void test_diff_submodules__submod2_head_to_index(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
	git_tree *head;
207
	git_diff *diff = NULL;
208 209 210 211 212 213
	static const char *expected[] = {
		"<SKIP>", /* .gitmodules */
		"diff --git a/sm_added_and_uncommited b/sm_added_and_uncommited\nnew file mode 160000\nindex 0000000..4800958\n--- /dev/null\n+++ b/sm_added_and_uncommited\n@@ -0,0 +1 @@\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n", /* sm_added_and_uncommited */
		"<END>"
	};

214
	g_repo = setup_fixture_submod2();
215 216 217 218

	cl_git_pass(git_repository_head_tree(&head, g_repo));

	opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
219
	opts.old_prefix = "a"; opts.new_prefix = "b";
220 221 222

	cl_git_pass(git_diff_tree_to_index(&diff, g_repo, head, NULL, &opts));
	check_diff_patches(diff, expected);
223
	git_diff_free(diff);
224 225 226

	git_tree_free(head);
}
227 228 229 230

void test_diff_submodules__invalid_cache(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
231
	git_diff *diff = NULL;
232 233 234 235 236 237 238 239 240 241 242 243 244 245
	git_submodule *sm;
	char *smpath = "sm_changed_head";
	git_repository *smrepo;
	git_index *smindex;
	static const char *expected_baseline[] = {
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
		"<END>"
	};
	static const char *expected_unchanged[] = { "<END>" };
	static const char *expected_dirty[] = {
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247-dirty\n",
		"<END>"
	};
	static const char *expected_moved[] = {
246
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae\n",
247 248 249
		"<END>"
	};
	static const char *expected_moved_dirty[] = {
250
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 3d9386c..7002348 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n+Subproject commit 700234833f6ccc20d744b238612646be071acaae-dirty\n",
251 252 253
		"<END>"
	};

254
	g_repo = setup_fixture_submod2();
255 256 257 258 259 260 261 262 263

	opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
	opts.old_prefix = "a"; opts.new_prefix = "b";
	opts.pathspec.count = 1;
	opts.pathspec.strings = &smpath;

	/* baseline */
	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_baseline);
264
	git_diff_free(diff);
265 266 267 268 269 270 271

	/* update index with new HEAD */
	cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath));
	cl_git_pass(git_submodule_add_to_index(sm, 1));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_unchanged);
272
	git_diff_free(diff);
273 274 275

	/* create untracked file in submodule working directory */
	cl_git_mkfile("submod2/sm_changed_head/new_around_here", "hello");
276
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE);
277 278 279

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
280
	git_diff_free(diff);
281

282
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED);
283 284 285

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_unchanged);
286
	git_diff_free(diff);
287 288 289 290 291 292 293

	/* modify tracked file in submodule working directory */
	cl_git_append2file(
		"submod2/sm_changed_head/file_to_modify", "\nmore stuff\n");

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
294
	git_diff_free(diff);
295

296 297
	git_submodule_free(sm);

298 299 300 301
	cl_git_pass(git_submodule_lookup(&sm, g_repo, smpath));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
302
	git_diff_free(diff);
303

304
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY);
305 306 307

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_unchanged);
308
	git_diff_free(diff);
309 310 311 312 313 314

	/* add file to index in submodule */
	cl_git_pass(git_submodule_open(&smrepo, sm));
	cl_git_pass(git_repository_index(&smindex, smrepo));
	cl_git_pass(git_index_add_bypath(smindex, "file_to_modify"));

315
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_UNTRACKED);
316 317 318

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_dirty);
319
	git_diff_free(diff);
320

321
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY);
322 323 324

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_unchanged);
325
	git_diff_free(diff);
326 327

	/* commit changed index of submodule */
328
	cl_repo_commit_from_index(NULL, smrepo, NULL, 1372350000, "Move it");
329

330
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_DIRTY);
331 332 333

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_moved);
334
	git_diff_free(diff);
335

336
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_ALL);
337 338 339

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_unchanged);
340
	git_diff_free(diff);
341

342
	git_submodule_set_ignore(g_repo, git_submodule_name(sm), GIT_SUBMODULE_IGNORE_NONE);
343 344 345

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_moved_dirty);
346
	git_diff_free(diff);
347 348 349

	p_unlink("submod2/sm_changed_head/new_around_here");

350 351
	git_submodule_free(sm);

352 353
	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_moved);
354
	git_diff_free(diff);
355 356 357 358

	git_index_free(smindex);
	git_repository_free(smrepo);
}
359 360 361 362

void test_diff_submodules__diff_ignore_options(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
363
	git_diff *diff = NULL;
364
	git_config *cfg;
365 366
	static const char *expected_normal[] = {
		"<SKIP>", /* .gitmodules */
367 368
		"<UNTRACKED>", /* not-submodule */
		"<UNTRACKED>", /* not */
369 370 371 372 373 374 375 376 377
		"diff --git a/sm_changed_file b/sm_changed_file\nindex 4800958..4800958 160000\n--- a/sm_changed_file\n+++ b/sm_changed_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_file */
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
		"diff --git a/sm_changed_index b/sm_changed_index\nindex 4800958..4800958 160000\n--- a/sm_changed_index\n+++ b/sm_changed_index\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_index */
		"diff --git a/sm_changed_untracked_file b/sm_changed_untracked_file\nindex 4800958..4800958 160000\n--- a/sm_changed_untracked_file\n+++ b/sm_changed_untracked_file\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0-dirty\n", /* sm_changed_untracked_file */
		"diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
		"<END>"
	};
	static const char *expected_ignore_all[] = {
		"<SKIP>", /* .gitmodules */
378 379
		"<UNTRACKED>", /* not-submodule */
		"<UNTRACKED>", /* not */
380 381 382 383
		"<END>"
	};
	static const char *expected_ignore_dirty[] = {
		"<SKIP>", /* .gitmodules */
384 385
		"<UNTRACKED>", /* not-submodule */
		"<UNTRACKED>", /* not */
386 387 388 389 390
		"diff --git a/sm_changed_head b/sm_changed_head\nindex 4800958..3d9386c 160000\n--- a/sm_changed_head\n+++ b/sm_changed_head\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 3d9386c507f6b093471a3e324085657a3c2b4247\n", /* sm_changed_head */
		"diff --git a/sm_missing_commits b/sm_missing_commits\nindex 4800958..5e49635 160000\n--- a/sm_missing_commits\n+++ b/sm_missing_commits\n@@ -1 +1 @@\n-Subproject commit 480095882d281ed676fe5b863569520e54a7d5c0\n+Subproject commit 5e4963595a9774b90524d35a807169049de8ccad\n", /* sm_missing_commits */
		"<END>"
	};

391
	g_repo = setup_fixture_submod2();
392 393 394 395 396 397

	opts.flags = GIT_DIFF_INCLUDE_UNTRACKED;
	opts.old_prefix = "a"; opts.new_prefix = "b";

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_normal);
398
	git_diff_free(diff);
399 400 401 402 403

	opts.flags |= GIT_DIFF_IGNORE_SUBMODULES;

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_ignore_all);
404
	git_diff_free(diff);
405 406 407 408 409 410

	opts.flags &= ~GIT_DIFF_IGNORE_SUBMODULES;
	opts.ignore_submodules = GIT_SUBMODULE_IGNORE_ALL;

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_ignore_all);
411
	git_diff_free(diff);
412 413 414 415 416

	opts.ignore_submodules = GIT_SUBMODULE_IGNORE_DIRTY;

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_ignore_dirty);
417
	git_diff_free(diff);
418 419 420 421 422 423 424

	opts.ignore_submodules = 0;
	cl_git_pass(git_repository_config(&cfg, g_repo));
	cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", false));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_normal);
425
	git_diff_free(diff);
426 427 428 429 430

	cl_git_pass(git_config_set_bool(cfg, "diff.ignoreSubmodules", true));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_ignore_all);
431
	git_diff_free(diff);
432 433 434 435 436

	cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "none"));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_normal);
437
	git_diff_free(diff);
438 439 440 441 442

	cl_git_pass(git_config_set_string(cfg, "diff.ignoreSubmodules", "dirty"));

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	check_diff_patches(diff, expected_ignore_dirty);
443
	git_diff_free(diff);
444 445

	git_config_free(cfg);
446
}
447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467

void test_diff_submodules__skips_empty_includes_used(void)
{
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
	git_diff *diff = NULL;
	diff_expects exp;

	/* A side effect of of Git's handling of untracked directories and
	 * auto-ignoring of ".git" entries is that a newly initialized Git
	 * repo inside another repo will be skipped by diff, but one that
	 * actually has a commit it in will show as an untracked directory.
	 * Let's make sure that works.
	 */

	g_repo = cl_git_sandbox_init("empty_standard_repo");

	opts.flags |= GIT_DIFF_INCLUDE_IGNORED | GIT_DIFF_INCLUDE_UNTRACKED;

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	memset(&exp, 0, sizeof(exp));
	cl_git_pass(git_diff_foreach(
468
		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
469 470 471
	cl_assert_equal_i(0, exp.files);
	git_diff_free(diff);

472 473 474 475 476
	{
		git_repository *r2;
		cl_git_pass(git_repository_init(&r2, "empty_standard_repo/subrepo", 0));
		git_repository_free(r2);
	}
477 478 479 480

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	memset(&exp, 0, sizeof(exp));
	cl_git_pass(git_diff_foreach(
481
		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
482 483 484 485 486 487 488 489 490
	cl_assert_equal_i(1, exp.files);
	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_IGNORED]);
	git_diff_free(diff);

	cl_git_mkfile("empty_standard_repo/subrepo/README.txt", "hello\n");

	cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts));
	memset(&exp, 0, sizeof(exp));
	cl_git_pass(git_diff_foreach(
491
		diff, diff_file_cb, diff_binary_cb, diff_hunk_cb, diff_line_cb, &exp));
492 493 494 495
	cl_assert_equal_i(1, exp.files);
	cl_assert_equal_i(1, exp.file_status[GIT_DELTA_UNTRACKED]);
	git_diff_free(diff);
}
496 497 498 499 500 501 502 503 504 505 506 507 508 509 510 511 512 513 514 515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542

static void ensure_submodules_found(
	git_repository *repo,
	const char **paths,
	size_t cnt)
{
	git_diff *diff = NULL;
	git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
	const git_diff_delta *delta;
	size_t i, pathlen;

	opts.pathspec.strings = (char **)paths;
	opts.pathspec.count = cnt;

	git_diff_index_to_workdir(&diff, repo, NULL, &opts);

	cl_assert_equal_i(cnt, git_diff_num_deltas(diff));

	for (i = 0; i < cnt; i++) {
		delta = git_diff_get_delta(diff, i);

		/* ensure that the given path is returned w/o trailing slashes. */
		pathlen = strlen(opts.pathspec.strings[i]);

		while (pathlen && opts.pathspec.strings[i][pathlen - 1] == '/')
			pathlen--;

		cl_assert_equal_strn(opts.pathspec.strings[i], delta->new_file.path, pathlen);
	}

	git_diff_free(diff);
}

void test_diff_submodules__can_be_identified_by_trailing_slash_in_pathspec(void)
{
	const char *one_path_without_slash[] = { "sm_changed_head" };
	const char *one_path_with_slash[] = { "sm_changed_head/" };
	const char *many_paths_without_slashes[] = { "sm_changed_head", "sm_changed_index" };
	const char *many_paths_with_slashes[] = { "sm_changed_head/", "sm_changed_index/" };

	g_repo = setup_fixture_submod2();

	ensure_submodules_found(g_repo, one_path_without_slash, ARRAY_SIZE(one_path_without_slash));
	ensure_submodules_found(g_repo, one_path_with_slash, ARRAY_SIZE(one_path_with_slash));
	ensure_submodules_found(g_repo, many_paths_without_slashes, ARRAY_SIZE(many_paths_without_slashes));
	ensure_submodules_found(g_repo, many_paths_with_slashes, ARRAY_SIZE(many_paths_with_slashes));
}