addall.c 15.3 KB
Newer Older
1 2 3
#include "clar_libgit2.h"
#include "../status/status_helpers.h"
#include "posix.h"
Russell Belfer committed
4
#include "fileops.h"
5

6
static git_repository *g_repo = NULL;
7
#define TEST_DIR "addall"
8 9 10 11 12 13 14

void test_index_addall__initialize(void)
{
}

void test_index_addall__cleanup(void)
{
15
	cl_git_sandbox_cleanup();
16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
}

#define STATUS_INDEX_FLAGS \
	(GIT_STATUS_INDEX_NEW | GIT_STATUS_INDEX_MODIFIED | \
	 GIT_STATUS_INDEX_DELETED | GIT_STATUS_INDEX_RENAMED | \
	 GIT_STATUS_INDEX_TYPECHANGE)

#define STATUS_WT_FLAGS \
	(GIT_STATUS_WT_NEW | GIT_STATUS_WT_MODIFIED | \
	 GIT_STATUS_WT_DELETED | GIT_STATUS_WT_TYPECHANGE | \
	 GIT_STATUS_WT_RENAMED)

typedef struct {
	size_t index_adds;
	size_t index_dels;
	size_t index_mods;
	size_t wt_adds;
	size_t wt_dels;
	size_t wt_mods;
	size_t ignores;
36
	size_t conflicts;
37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67
} index_status_counts;

static int index_status_cb(
	const char *path, unsigned int status_flags, void *payload)
{
	index_status_counts *vals = payload;

	/* cb_status__print(path, status_flags, NULL); */

	GIT_UNUSED(path);

	if (status_flags & GIT_STATUS_INDEX_NEW)
		vals->index_adds++;
	if (status_flags & GIT_STATUS_INDEX_MODIFIED)
		vals->index_mods++;
	if (status_flags & GIT_STATUS_INDEX_DELETED)
		vals->index_dels++;
	if (status_flags & GIT_STATUS_INDEX_TYPECHANGE)
		vals->index_mods++;

	if (status_flags & GIT_STATUS_WT_NEW)
		vals->wt_adds++;
	if (status_flags & GIT_STATUS_WT_MODIFIED)
		vals->wt_mods++;
	if (status_flags & GIT_STATUS_WT_DELETED)
		vals->wt_dels++;
	if (status_flags & GIT_STATUS_WT_TYPECHANGE)
		vals->wt_mods++;

	if (status_flags & GIT_STATUS_IGNORED)
		vals->ignores++;
68 69
	if (status_flags & GIT_STATUS_CONFLICTED)
		vals->conflicts++;
70 71 72 73

	return 0;
}

74
static void check_status_at_line(
75 76
	git_repository *repo,
	size_t index_adds, size_t index_dels, size_t index_mods,
77
	size_t wt_adds, size_t wt_dels, size_t wt_mods, size_t ignores,
78
	size_t conflicts, const char *file, int line)
79 80 81 82 83 84 85
{
	index_status_counts vals;

	memset(&vals, 0, sizeof(vals));

	cl_git_pass(git_status_foreach(repo, index_status_cb, &vals));

86 87 88 89 90 91 92 93 94 95 96 97 98 99
	clar__assert_equal(
		file,line,"wrong index adds", 1, "%"PRIuZ, index_adds, vals.index_adds);
	clar__assert_equal(
		file,line,"wrong index dels", 1, "%"PRIuZ, index_dels, vals.index_dels);
	clar__assert_equal(
		file,line,"wrong index mods", 1, "%"PRIuZ, index_mods, vals.index_mods);
	clar__assert_equal(
		file,line,"wrong workdir adds", 1, "%"PRIuZ, wt_adds, vals.wt_adds);
	clar__assert_equal(
		file,line,"wrong workdir dels", 1, "%"PRIuZ, wt_dels, vals.wt_dels);
	clar__assert_equal(
		file,line,"wrong workdir mods", 1, "%"PRIuZ, wt_mods, vals.wt_mods);
	clar__assert_equal(
		file,line,"wrong ignores", 1, "%"PRIuZ, ignores, vals.ignores);
100 101
	clar__assert_equal(
		file,line,"wrong conflicts", 1, "%"PRIuZ, conflicts, vals.conflicts);
102 103
}

104 105
#define check_status(R,IA,ID,IM,WA,WD,WM,IG,C) \
	check_status_at_line(R,IA,ID,IM,WA,WD,WM,IG,C,__FILE__,__LINE__)
106

107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125
static void check_stat_data(git_index *index, const char *path, bool match)
{
	const git_index_entry *entry;
	struct stat st;

	cl_must_pass(p_lstat(path, &st));

	/* skip repo base dir name */
	while (*path != '/')
		++path;
	++path;

	entry = git_index_get_bypath(index, path, 0);
	cl_assert(entry);

	if (match) {
		cl_assert(st.st_ctime == entry->ctime.seconds);
		cl_assert(st.st_mtime == entry->mtime.seconds);
		cl_assert(st.st_size == entry->file_size);
lhchavez committed
126 127
		cl_assert((uint32_t)st.st_uid  == entry->uid);
		cl_assert((uint32_t)st.st_gid  == entry->gid);
Russell Belfer committed
128 129
		cl_assert_equal_i_fmt(
			GIT_MODE_TYPE(st.st_mode), GIT_MODE_TYPE(entry->mode), "%07o");
130 131 132
		if (cl_is_chmod_supported())
			cl_assert_equal_b(
				GIT_PERMS_IS_EXEC(st.st_mode), GIT_PERMS_IS_EXEC(entry->mode));
133 134 135 136 137 138 139
	} else {
		/* most things will still match */
		cl_assert(st.st_size != entry->file_size);
		/* would check mtime, but with second resolution it won't work :( */
	}
}

140 141
static void addall_create_test_repo(bool check_every_step)
{
142 143
	g_repo = cl_git_sandbox_init_new(TEST_DIR);

144
	if (check_every_step)
145
		check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0);
146 147 148

	cl_git_mkfile(TEST_DIR "/file.foo", "a file");
	if (check_every_step)
149
		check_status(g_repo, 0, 0, 0, 1, 0, 0, 0, 0);
150 151 152

	cl_git_mkfile(TEST_DIR "/.gitignore", "*.foo\n");
	if (check_every_step)
153
		check_status(g_repo, 0, 0, 0, 1, 0, 0, 1, 0);
154 155 156

	cl_git_mkfile(TEST_DIR "/file.bar", "another file");
	if (check_every_step)
157
		check_status(g_repo, 0, 0, 0, 2, 0, 0, 1, 0);
158 159
}

160 161 162 163 164 165 166
void test_index_addall__repo_lifecycle(void)
{
	int error;
	git_index *index;
	git_strarray paths = { NULL, 0 };
	char *strs[1];

167
	addall_create_test_repo(true);
168 169 170 171 172 173 174 175

	cl_git_pass(git_repository_index(&index, g_repo));

	strs[0] = "file.*";
	paths.strings = strs;
	paths.count   = 1;

	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
176
	cl_git_pass(git_index_write(index));
177
	check_stat_data(index, TEST_DIR "/file.bar", true);
178
	check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
179

180 181
	cl_git_rewritefile(TEST_DIR "/file.bar", "new content for file");
	check_stat_data(index, TEST_DIR "/file.bar", false);
182
	check_status(g_repo, 1, 0, 0, 1, 0, 1, 1, 0);
183

184 185 186
	cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
187
	check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0);
188 189

	cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
190
	check_stat_data(index, TEST_DIR "/file.bar", true);
191
	check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
192 193

	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
194
	cl_git_pass(git_index_write(index));
195
	check_stat_data(index, TEST_DIR "/file.zzz", true);
196
	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
197

198
	cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit");
199
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
200

201 202 203 204
	if (cl_repo_get_bool(g_repo, "core.filemode")) {
		cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
		cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0777));
		cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
205
		check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0);
206 207 208 209

		/* go back to what we had before */
		cl_must_pass(p_chmod(TEST_DIR "/file.zzz", 0666));
		cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
210
		check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
211 212 213
	}


214 215 216
	/* attempt to add an ignored file - does nothing */
	strs[0] = "file.foo";
	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
217
	cl_git_pass(git_index_write(index));
218
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
219 220 221 222 223

	/* add with check - should generate error */
	error = git_index_add_all(
		index, &paths, GIT_INDEX_ADD_CHECK_PATHSPEC, NULL, NULL);
	cl_assert_equal_i(GIT_EINVALIDSPEC, error);
224
	cl_git_pass(git_index_write(index));
225
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
226 227 228 229

	/* add with force - should allow */
	cl_git_pass(git_index_add_all(
		index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL));
230
	cl_git_pass(git_index_write(index));
231
	check_stat_data(index, TEST_DIR "/file.foo", true);
232
	check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
233 234

	/* now it's in the index, so regular add should work */
235 236
	cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file");
	check_stat_data(index, TEST_DIR "/file.foo", false);
237
	check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0);
238 239

	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
240
	cl_git_pass(git_index_write(index));
241
	check_stat_data(index, TEST_DIR "/file.foo", true);
242
	check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
243 244

	cl_git_pass(git_index_add_bypath(index, "more.zzz"));
245
	check_stat_data(index, TEST_DIR "/more.zzz", true);
246
	check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0);
247

248
	cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file");
249
	check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0);
250 251

	cl_git_pass(git_index_add_bypath(index, "file.zzz"));
252
	check_stat_data(index, TEST_DIR "/file.zzz", true);
253
	check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0);
254 255 256

	strs[0] = "*.zzz";
	cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL));
257
	check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0);
258 259

	cl_git_pass(git_index_add_bypath(index, "file.zzz"));
260
	check_status(g_repo, 1, 0, 1, 3, 0, 0, 0, 0);
261

262
	cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit");
263
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0);
264

265
	cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
266
	check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0);
267 268 269

	/* update_all should be able to remove entries */
	cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
270
	check_status(g_repo, 0, 1, 0, 3, 0, 0, 0, 0);
271

272 273
	strs[0] = "*";
	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
274
	cl_git_pass(git_index_write(index));
275
	check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0);
276 277

	/* must be able to remove at any position while still updating other files */
278 279 280
	cl_must_pass(p_unlink(TEST_DIR "/.gitignore"));
	cl_git_rewritefile(TEST_DIR "/file.zzz", "reconstructed file");
	cl_git_rewritefile(TEST_DIR "/more.zzz", "altered file reality");
281
	check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0);
282 283

	cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
284
	check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0);
285 286 287 288 289 290 291
	/* this behavior actually matches 'git add -u' where "file.zzz" has
	 * been removed from the index, so when you go to update, even though
	 * it exists in the HEAD, it is not re-added to the index, leaving it
	 * as a DELETE when comparing HEAD to index and as an ADD comparing
	 * index to worktree
	 */

292 293
	git_index_free(index);
}
294

295 296 297 298 299 300 301 302 303
void test_index_addall__files_in_folders(void)
{
	git_index *index;

	addall_create_test_repo(true);

	cl_git_pass(git_repository_index(&index, g_repo));

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
304
	cl_git_pass(git_index_write(index));
305
	check_stat_data(index, TEST_DIR "/file.bar", true);
306
	check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);
307 308 309

	cl_must_pass(p_mkdir(TEST_DIR "/subdir", 0777));
	cl_git_mkfile(TEST_DIR "/subdir/file", "hello!\n");
310
	check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0);
311 312

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
313
	cl_git_pass(git_index_write(index));
314
	check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0);
315 316 317 318

	git_index_free(index);
}

319 320 321 322 323 324 325 326 327 328 329 330
void test_index_addall__hidden_files(void)
{
	git_index *index;

	GIT_UNUSED(index);

#ifdef GIT_WIN32
	addall_create_test_repo(true);

	cl_git_pass(git_repository_index(&index, g_repo));

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
331
	cl_git_pass(git_index_write(index));
332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
	check_stat_data(index, TEST_DIR "/file.bar", true);
	check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);

	cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");

	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);

	cl_git_pass(git_win32__set_hidden(TEST_DIR "/file.zzz", true));
	cl_git_pass(git_win32__set_hidden(TEST_DIR "/more.zzz", true));
	cl_git_pass(git_win32__set_hidden(TEST_DIR "/other.zzz", true));

	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
348
	cl_git_pass(git_index_write(index));
349 350 351 352 353 354 355
	check_stat_data(index, TEST_DIR "/file.bar", true);
	check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);

	git_index_free(index);
#endif
}

356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386
static int addall_match_prefix(
	const char *path, const char *matched_pathspec, void *payload)
{
	GIT_UNUSED(matched_pathspec);
	return !git__prefixcmp(path, payload) ? 0 : 1;
}

static int addall_match_suffix(
	const char *path, const char *matched_pathspec, void *payload)
{
	GIT_UNUSED(matched_pathspec);
	return !git__suffixcmp(path, payload) ? 0 : 1;
}

static int addall_cancel_at(
	const char *path, const char *matched_pathspec, void *payload)
{
	GIT_UNUSED(matched_pathspec);
	return !strcmp(path, payload) ? -123 : 0;
}

void test_index_addall__callback_filtering(void)
{
	git_index *index;

	addall_create_test_repo(false);

	cl_git_pass(git_repository_index(&index, g_repo));

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_prefix, "file."));
387
	cl_git_pass(git_index_write(index));
388
	check_stat_data(index, TEST_DIR "/file.bar", true);
389
	check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
390 391 392 393 394 395 396

	cl_git_mkfile(TEST_DIR "/file.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/more.zzz", "yet another one");
	cl_git_mkfile(TEST_DIR "/other.zzz", "yet another one");

	cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
	check_stat_data(index, TEST_DIR "/file.bar", true);
397
	check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
398 399 400

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_prefix, "other"));
401
	cl_git_pass(git_index_write(index));
402
	check_stat_data(index, TEST_DIR "/other.zzz", true);
403
	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
404 405 406

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
407
	cl_git_pass(git_index_write(index));
408
	check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0);
409 410 411

	cl_git_pass(
		git_index_remove_all(index, NULL, addall_match_suffix, ".zzz"));
412
	check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
413 414 415

	cl_git_fail_with(
		git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123);
416
	check_status(g_repo, 3, 0, 0, 2, 0, 0, 1, 0);
417 418 419

	cl_git_fail_with(
		git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123);
420
	check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0);
421 422 423

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
424
	cl_git_pass(git_index_write(index));
425
	check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);
426 427 428 429 430 431 432 433 434

	cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
	cl_must_pass(p_unlink(TEST_DIR "/more.zzz"));
	cl_must_pass(p_unlink(TEST_DIR "/other.zzz"));

	cl_git_fail_with(
		git_index_update_all(index, NULL, addall_cancel_at, "more.zzz"), -123);
	/* file.zzz removed from index (so Index Adds 5 -> 4) and
	 * more.zzz + other.zzz removed (so Worktree Dels 0 -> 2) */
435
	check_status(g_repo, 4, 0, 0, 0, 2, 0, 1, 0);
436 437 438 439 440

	cl_git_fail_with(
		git_index_update_all(index, NULL, addall_cancel_at, "other.zzz"), -123);
	/* more.zzz removed from index (so Index Adds 4 -> 3) and
	 * Just other.zzz removed (so Worktree Dels 2 -> 1) */
441
	check_status(g_repo, 3, 0, 0, 0, 1, 0, 1, 0);
442 443 444

	git_index_free(index);
}
445 446 447 448 449 450 451 452 453 454 455 456 457 458 459

void test_index_addall__adds_conflicts(void)
{
	git_index *index;
	git_reference *ref;
	git_annotated_commit *annotated;

	g_repo = cl_git_sandbox_init("merge-resolve");
	cl_git_pass(git_repository_index(&index, g_repo));

	check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0);

	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch"));
	cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref));

460
	cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL));
461 462 463
	check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1);

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
464
	cl_git_pass(git_index_write(index));
465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485
	check_status(g_repo, 0, 1, 3, 0, 0, 0, 0, 0);

	git_annotated_commit_free(annotated);
	git_reference_free(ref);
	git_index_free(index);
}

void test_index_addall__removes_deleted_conflicted_files(void)
{
	git_index *index;
	git_reference *ref;
	git_annotated_commit *annotated;

	g_repo = cl_git_sandbox_init("merge-resolve");
	cl_git_pass(git_repository_index(&index, g_repo));

	check_status(g_repo, 0, 0, 0, 0, 0, 0, 0, 0);

	cl_git_pass(git_reference_lookup(&ref, g_repo, "refs/heads/branch"));
	cl_git_pass(git_annotated_commit_from_ref(&annotated, g_repo, ref));

486
	cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL));
487 488 489 490 491
	check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1);

	cl_git_rmfile("merge-resolve/conflicting.txt");

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
492
	cl_git_pass(git_index_write(index));
493 494 495 496 497
	check_status(g_repo, 0, 2, 2, 0, 0, 0, 0, 0);

	git_annotated_commit_free(annotated);
	git_reference_free(ref);
	git_index_free(index);
498
}