addall.c 14.6 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 126 127
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);
		cl_assert(st.st_uid  == entry->uid);
		cl_assert(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
	check_stat_data(index, TEST_DIR "/file.bar", true);
177
	check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
178

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

183 184 185
	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");
186
	check_status(g_repo, 1, 0, 0, 4, 0, 1, 1, 0);
187 188

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

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

196
	cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "first commit");
197
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
198

199 200 201 202
	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));
203
		check_status(g_repo, 0, 0, 1, 3, 0, 0, 1, 0);
204 205 206 207

		/* 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));
208
		check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
209 210 211
	}


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

	/* 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);
221
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 1, 0);
222 223 224 225

	/* add with force - should allow */
	cl_git_pass(git_index_add_all(
		index, &paths, GIT_INDEX_ADD_FORCE, NULL, NULL));
226
	check_stat_data(index, TEST_DIR "/file.foo", true);
227
	check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
228 229

	/* now it's in the index, so regular add should work */
230 231
	cl_git_rewritefile(TEST_DIR "/file.foo", "new content for file");
	check_stat_data(index, TEST_DIR "/file.foo", false);
232
	check_status(g_repo, 1, 0, 0, 3, 0, 1, 0, 0);
233 234

	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
235
	check_stat_data(index, TEST_DIR "/file.foo", true);
236
	check_status(g_repo, 1, 0, 0, 3, 0, 0, 0, 0);
237 238

	cl_git_pass(git_index_add_bypath(index, "more.zzz"));
239
	check_stat_data(index, TEST_DIR "/more.zzz", true);
240
	check_status(g_repo, 2, 0, 0, 2, 0, 0, 0, 0);
241

242
	cl_git_rewritefile(TEST_DIR "/file.zzz", "new content for file");
243
	check_status(g_repo, 2, 0, 0, 2, 0, 1, 0, 0);
244 245

	cl_git_pass(git_index_add_bypath(index, "file.zzz"));
246
	check_stat_data(index, TEST_DIR "/file.zzz", true);
247
	check_status(g_repo, 2, 0, 1, 2, 0, 0, 0, 0);
248 249 250

	strs[0] = "*.zzz";
	cl_git_pass(git_index_remove_all(index, &paths, NULL, NULL));
251
	check_status(g_repo, 1, 1, 0, 4, 0, 0, 0, 0);
252 253

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

256
	cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "second commit");
257
	check_status(g_repo, 0, 0, 0, 3, 0, 0, 0, 0);
258

259
	cl_must_pass(p_unlink(TEST_DIR "/file.zzz"));
260
	check_status(g_repo, 0, 0, 0, 3, 1, 0, 0, 0);
261 262 263

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

266 267
	strs[0] = "*";
	cl_git_pass(git_index_add_all(index, &paths, 0, NULL, NULL));
268
	check_status(g_repo, 3, 1, 0, 0, 0, 0, 0, 0);
269 270

	/* must be able to remove at any position while still updating other files */
271 272 273
	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");
274
	check_status(g_repo, 3, 1, 0, 1, 1, 1, 0, 0);
275 276

	cl_git_pass(git_index_update_all(index, NULL, NULL, NULL));
277
	check_status(g_repo, 2, 1, 0, 1, 0, 0, 0, 0);
278 279 280 281 282 283 284
	/* 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
	 */

285 286
	git_index_free(index);
}
287

288 289 290 291 292 293 294 295 296 297
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));
	check_stat_data(index, TEST_DIR "/file.bar", true);
298
	check_status(g_repo, 2, 0, 0, 0, 0, 0, 1, 0);
299 300 301

	cl_must_pass(p_mkdir(TEST_DIR "/subdir", 0777));
	cl_git_mkfile(TEST_DIR "/subdir/file", "hello!\n");
302
	check_status(g_repo, 2, 0, 0, 1, 0, 0, 1, 0);
303 304

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
305
	check_status(g_repo, 3, 0, 0, 0, 0, 0, 1, 0);
306 307 308 309

	git_index_free(index);
}

310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344
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));
	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));
	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
}

345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376
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."));
	check_stat_data(index, TEST_DIR "/file.bar", true);
377
	check_status(g_repo, 1, 0, 0, 1, 0, 0, 1, 0);
378 379 380 381 382 383 384

	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);
385
	check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
386 387 388 389

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_prefix, "other"));
	check_stat_data(index, TEST_DIR "/other.zzz", true);
390
	check_status(g_repo, 2, 0, 0, 3, 0, 0, 1, 0);
391 392 393

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
394
	check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0);
395 396 397

	cl_git_pass(
		git_index_remove_all(index, NULL, addall_match_suffix, ".zzz"));
398
	check_status(g_repo, 1, 0, 0, 4, 0, 0, 1, 0);
399 400 401

	cl_git_fail_with(
		git_index_add_all(index, NULL, 0, addall_cancel_at, "more.zzz"), -123);
402
	check_status(g_repo, 3, 0, 0, 2, 0, 0, 1, 0);
403 404 405

	cl_git_fail_with(
		git_index_add_all(index, NULL, 0, addall_cancel_at, "other.zzz"), -123);
406
	check_status(g_repo, 4, 0, 0, 1, 0, 0, 1, 0);
407 408 409

	cl_git_pass(
		git_index_add_all(index, NULL, 0, addall_match_suffix, ".zzz"));
410
	check_status(g_repo, 5, 0, 0, 0, 0, 0, 1, 0);
411 412 413 414 415 416 417 418 419

	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) */
420
	check_status(g_repo, 4, 0, 0, 0, 2, 0, 1, 0);
421 422 423 424 425

	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) */
426
	check_status(g_repo, 3, 0, 0, 0, 1, 0, 1, 0);
427 428 429

	git_index_free(index);
}
430 431 432 433 434 435 436 437 438 439 440 441 442 443 444

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));

445
	cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL));
446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469
	check_status(g_repo, 0, 1, 2, 0, 0, 0, 0, 1);

	cl_git_pass(git_index_add_all(index, NULL, 0, NULL, NULL));
	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));

470
	cl_git_pass(git_merge(g_repo, (const git_annotated_commit**)&annotated, 1, NULL, NULL));
471 472 473 474 475 476 477 478 479 480
	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));
	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);
481
}