tree.c 28.3 KB
Newer Older
1
#include "clar_libgit2.h"
2
#include "checkout_helpers.h"
3 4 5

#include "git2/checkout.h"
#include "repository.h"
6 7
#include "buffer.h"
#include "fileops.h"
8 9

static git_repository *g_repo;
10
static git_checkout_options g_opts;
11
static git_object *g_object;
12 13 14 15

void test_checkout_tree__initialize(void)
{
	g_repo = cl_git_sandbox_init("testrepo");
16

17
	GIT_INIT_STRUCTURE(&g_opts, GIT_CHECKOUT_OPTIONS_VERSION);
18
	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
19 20 21 22
}

void test_checkout_tree__cleanup(void)
{
23
	git_object_free(g_object);
24
	g_object = NULL;
25

26
	cl_git_sandbox_cleanup();
27 28 29

	if (git_path_isdir("alternative"))
		git_futils_rmdir_r("alternative", NULL, GIT_RMDIR_REMOVE_FILES);
30 31 32 33
}

void test_checkout_tree__cannot_checkout_a_non_treeish(void)
{
34
	/* blob */
35
	cl_git_pass(git_revparse_single(&g_object, g_repo, "a71586c1dfe8a71c6cbf6c129f404c5642ff31bd"));
36
	cl_git_fail(git_checkout_tree(g_repo, g_object, NULL));
37 38 39 40 41 42
}

void test_checkout_tree__can_checkout_a_subdirectory_from_a_commit(void)
{
	char *entries[] = { "ab/de/" };

43 44
	g_opts.paths.strings = entries;
	g_opts.paths.count = 1;
45

46
	cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
47 48 49

	cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));

50
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
51 52 53 54 55

	cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt"));
	cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));
}

56 57 58 59
void test_checkout_tree__can_checkout_and_remove_directory(void)
{
	cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));

60 61 62
	/* Checkout brach "subtrees" and update HEAD, so that HEAD matches the
	 * current working tree
	 */
63
	cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees"));
64
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
65

66
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
67

68 69 70 71
	cl_assert_equal_i(true, git_path_isdir("./testrepo/ab/"));
	cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/2.txt"));
	cl_assert_equal_i(true, git_path_isfile("./testrepo/ab/de/fgh/1.txt"));

nulltoken committed
72 73 74
	git_object_free(g_object);
	g_object = NULL;

75 76 77
	/* Checkout brach "master" and update HEAD, so that HEAD matches the
	 * current working tree
	 */
78
	cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
79 80
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

81
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master", NULL, NULL));
82 83

	/* This directory should no longer exist */
84 85 86
	cl_assert_equal_i(false, git_path_isdir("./testrepo/ab/"));
}

87 88 89 90
void test_checkout_tree__can_checkout_a_subdirectory_from_a_subtree(void)
{
	char *entries[] = { "de/" };

91 92
	g_opts.paths.strings = entries;
	g_opts.paths.count = 1;
93

94
	cl_git_pass(git_revparse_single(&g_object, g_repo, "subtrees:ab"));
95

96
	cl_assert_equal_i(false, git_path_isdir("./testrepo/de/"));
97

98
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
99

100 101
	cl_assert_equal_i(true, git_path_isfile("./testrepo/de/2.txt"));
	cl_assert_equal_i(true, git_path_isfile("./testrepo/de/fgh/1.txt"));
102
}
103

104
static void progress(const char *path, size_t cur, size_t tot, void *payload)
105
{
Ben Straub committed
106
	bool *was_called = (bool*)payload;
Ben Straub committed
107
	GIT_UNUSED(path); GIT_UNUSED(cur); GIT_UNUSED(tot);
Ben Straub committed
108
	*was_called = true;
109 110 111 112
}

void test_checkout_tree__calls_progress_callback(void)
{
Ben Straub committed
113
	bool was_called = 0;
114

115
	g_opts.progress_cb = progress;
Ben Straub committed
116
	g_opts.progress_payload = &was_called;
117

118
	cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
119

120
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
121

Ben Straub committed
122
	cl_assert_equal_i(was_called, true);
123
}
124 125 126

void test_checkout_tree__doesnt_write_unrequested_files_to_worktree(void)
{
nulltoken committed
127 128 129 130
	git_oid master_oid;
	git_oid chomped_oid;
	git_commit* p_master_commit;
	git_commit* p_chomped_commit;
131
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
nulltoken committed
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146

	git_oid_fromstr(&master_oid, "a65fedf39aefe402d3bb6e24df4d4f5fe4547750");
	git_oid_fromstr(&chomped_oid, "e90810b8df3e80c413d903f631643c716887138d");
	cl_git_pass(git_commit_lookup(&p_master_commit, g_repo, &master_oid));
	cl_git_pass(git_commit_lookup(&p_chomped_commit, g_repo, &chomped_oid));

	/* GIT_CHECKOUT_NONE should not add any file to the working tree from the
	 * index as it is supposed to be a dry run.
	 */
	opts.checkout_strategy = GIT_CHECKOUT_NONE;
	git_checkout_tree(g_repo, (git_object*)p_chomped_commit, &opts);
	cl_assert_equal_i(false, git_path_isfile("testrepo/readme.txt"));

	git_commit_free(p_master_commit);
	git_commit_free(p_chomped_commit);
147
}
148 149 150

void test_checkout_tree__can_switch_branches(void)
{
151
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
152 153 154 155 156 157 158 159 160 161 162 163 164 165
	git_oid oid;
	git_object *obj = NULL;

	assert_on_branch(g_repo, "master");

	/* do first checkout with FORCE because we don't know if testrepo
	 * base data is clean for a checkout or not
	 */
	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
166
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185

	cl_assert(git_path_isfile("testrepo/README"));
	cl_assert(git_path_isfile("testrepo/branch_file.txt"));
	cl_assert(git_path_isfile("testrepo/new.txt"));
	cl_assert(git_path_isfile("testrepo/a/b.txt"));

	cl_assert(!git_path_isdir("testrepo/ab"));

	assert_on_branch(g_repo, "dir");

	git_object_free(obj);

	/* do second checkout safe because we should be clean after first */
	opts.checkout_strategy = GIT_CHECKOUT_SAFE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
186
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204

	cl_assert(git_path_isfile("testrepo/README"));
	cl_assert(git_path_isfile("testrepo/branch_file.txt"));
	cl_assert(git_path_isfile("testrepo/new.txt"));
	cl_assert(git_path_isfile("testrepo/ab/4.txt"));
	cl_assert(git_path_isfile("testrepo/ab/c/3.txt"));
	cl_assert(git_path_isfile("testrepo/ab/de/2.txt"));
	cl_assert(git_path_isfile("testrepo/ab/de/fgh/1.txt"));

	cl_assert(!git_path_isdir("testrepo/a"));

	assert_on_branch(g_repo, "subtrees");

	git_object_free(obj);
}

void test_checkout_tree__can_remove_untracked(void)
{
205
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
206 207 208 209 210 211 212 213 214 215 216 217 218

	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_UNTRACKED;

	cl_git_mkfile("testrepo/untracked_file", "as you wish");
	cl_assert(git_path_isfile("testrepo/untracked_file"));

	cl_git_pass(git_checkout_head(g_repo, &opts));

	cl_assert(!git_path_isfile("testrepo/untracked_file"));
}

void test_checkout_tree__can_remove_ignored(void)
{
219
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237
	int ignored = 0;

	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_REMOVE_IGNORED;

	cl_git_mkfile("testrepo/ignored_file", "as you wish");

	cl_git_pass(git_ignore_add_rule(g_repo, "ignored_file\n"));

	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ignored_file"));
	cl_assert_equal_i(1, ignored);

	cl_assert(git_path_isfile("testrepo/ignored_file"));

	cl_git_pass(git_checkout_head(g_repo, &opts));

	cl_assert(!git_path_isfile("testrepo/ignored_file"));
}

238
static int checkout_tree_with_blob_ignored_in_workdir(int strategy, bool isdir)
239 240 241
{
	git_oid oid;
	git_object *obj = NULL;
242
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
243 244 245 246 247 248 249 250 251 252 253 254 255
	int ignored = 0, error;

	assert_on_branch(g_repo, "master");

	/* do first checkout with FORCE because we don't know if testrepo
	 * base data is clean for a checkout or not
	 */
	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
256
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
257 258 259 260 261 262 263 264 265 266 267 268 269 270

	cl_assert(git_path_isfile("testrepo/README"));
	cl_assert(git_path_isfile("testrepo/branch_file.txt"));
	cl_assert(git_path_isfile("testrepo/new.txt"));
	cl_assert(git_path_isfile("testrepo/a/b.txt"));

	cl_assert(!git_path_isdir("testrepo/ab"));

	assert_on_branch(g_repo, "dir");

	git_object_free(obj);

	opts.checkout_strategy = strategy;

271 272 273 274 275 276 277 278 279 280 281 282 283 284 285
	if (isdir) {
		cl_must_pass(p_mkdir("testrepo/ab", 0777));
		cl_must_pass(p_mkdir("testrepo/ab/4.txt", 0777));

		cl_git_mkfile("testrepo/ab/4.txt/file1.txt", "as you wish");
		cl_git_mkfile("testrepo/ab/4.txt/file2.txt", "foo bar foo");
		cl_git_mkfile("testrepo/ab/4.txt/file3.txt", "inky blinky pinky clyde");

		cl_assert(git_path_isdir("testrepo/ab/4.txt"));
	} else {
		cl_must_pass(p_mkdir("testrepo/ab", 0777));
		cl_git_mkfile("testrepo/ab/4.txt", "as you wish");

		cl_assert(git_path_isfile("testrepo/ab/4.txt"));
	}
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306

	cl_git_pass(git_ignore_add_rule(g_repo, "ab/4.txt\n"));

	cl_git_pass(git_ignore_path_is_ignored(&ignored, g_repo, "ab/4.txt"));
	cl_assert_equal_i(1, ignored);

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/subtrees"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	error = git_checkout_tree(g_repo, obj, &opts);

	git_object_free(obj);

	return error;
}

void test_checkout_tree__conflict_on_ignored_when_not_overwriting(void)
{
	int error;

	cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
307
		GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, false));
308 309 310 311 312 313

	cl_assert_equal_i(GIT_EMERGECONFLICT, error);
}

void test_checkout_tree__can_overwrite_ignored_by_default(void)
{
314
	cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, false));
315

316
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
317 318 319 320 321 322

	cl_assert(git_path_isfile("testrepo/ab/4.txt"));

	assert_on_branch(g_repo, "subtrees");
}

323 324 325 326 327 328 329 330 331 332 333 334 335 336
void test_checkout_tree__conflict_on_ignored_folder_when_not_overwriting(void)
{
	int error;

	cl_git_fail(error = checkout_tree_with_blob_ignored_in_workdir(
		GIT_CHECKOUT_SAFE | GIT_CHECKOUT_DONT_OVERWRITE_IGNORED, true));

	cl_assert_equal_i(GIT_EMERGECONFLICT, error);
}

void test_checkout_tree__can_overwrite_ignored_folder_by_default(void)
{
	cl_git_pass(checkout_tree_with_blob_ignored_in_workdir(GIT_CHECKOUT_SAFE, true));

337
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/subtrees", NULL, NULL));
338 339 340 341 342 343 344

	cl_assert(git_path_isfile("testrepo/ab/4.txt"));

	assert_on_branch(g_repo, "subtrees");

}

345 346
void test_checkout_tree__can_update_only(void)
{
347
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
348 349 350 351 352 353 354 355 356 357 358 359
	git_oid oid;
	git_object *obj = NULL;

	/* first let's get things into a known state - by checkout out the HEAD */

	assert_on_branch(g_repo, "master");

	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
	cl_git_pass(git_checkout_head(g_repo, &opts));

	cl_assert(!git_path_isdir("testrepo/a"));

360
	check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n");
361 362 363 364 365 366 367 368 369

	/* now checkout branch but with update only */

	opts.checkout_strategy = GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_ONLY;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));
370
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/dir", NULL, NULL));
371 372 373 374 375 376 377 378 379 380

	assert_on_branch(g_repo, "dir");

	/* this normally would have been created (which was tested separately in
	 * the test_checkout_tree__can_switch_branches test), but with
	 * UPDATE_ONLY it will not have been created.
	 */
	cl_assert(!git_path_isdir("testrepo/a"));

	/* but this file still should have been updated */
381
	check_file_contents_nocr("testrepo/branch_file.txt", "hi\n");
382 383 384

	git_object_free(obj);
}
385 386 387 388 389 390 391 392 393 394

void test_checkout_tree__can_checkout_with_pattern(void)
{
	char *entries[] = { "[l-z]*.txt" };

	/* reset to beginning of history (i.e. just a README file) */

	g_opts.checkout_strategy =
		GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;

395
	cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479"));
396 397 398

	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
	cl_git_pass(
399
		git_repository_set_head_detached(g_repo, git_object_id(g_object), NULL, NULL));
400 401 402 403 404 405 406 407 408 409 410 411 412 413 414

	git_object_free(g_object);
	g_object = NULL;

	cl_assert(git_path_exists("testrepo/README"));
	cl_assert(!git_path_exists("testrepo/branch_file.txt"));
	cl_assert(!git_path_exists("testrepo/link_to_new.txt"));
	cl_assert(!git_path_exists("testrepo/new.txt"));

	/* now to a narrow patterned checkout */

	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;
	g_opts.paths.strings = entries;
	g_opts.paths.count = 1;

415
	cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master"));
416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433

	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

	cl_assert(git_path_exists("testrepo/README"));
	cl_assert(!git_path_exists("testrepo/branch_file.txt"));
	cl_assert(git_path_exists("testrepo/link_to_new.txt"));
	cl_assert(git_path_exists("testrepo/new.txt"));
}

void test_checkout_tree__can_disable_pattern_match(void)
{
	char *entries[] = { "b*.txt" };

	/* reset to beginning of history (i.e. just a README file) */

	g_opts.checkout_strategy =
		GIT_CHECKOUT_FORCE | GIT_CHECKOUT_REMOVE_UNTRACKED;

434
	cl_git_pass(git_revparse_single(&g_object, g_repo, "8496071c1b46c854b31185ea97743be6a8774479"));
435 436 437

	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
	cl_git_pass(
438
		git_repository_set_head_detached(g_repo, git_object_id(g_object), NULL, NULL));
439 440 441 442 443 444 445 446 447 448 449 450 451

	git_object_free(g_object);
	g_object = NULL;

	cl_assert(!git_path_isfile("testrepo/branch_file.txt"));

	/* now to a narrow patterned checkout, but disable pattern */

	g_opts.checkout_strategy =
		GIT_CHECKOUT_SAFE_CREATE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH;
	g_opts.paths.strings = entries;
	g_opts.paths.count = 1;

452
	cl_git_pass(git_revparse_single(&g_object, g_repo, "refs/heads/master"));
453 454 455 456 457 458 459 460 461 462 463 464 465

	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

	cl_assert(!git_path_isfile("testrepo/branch_file.txt"));

	/* let's try that again, but allow the pattern match */

	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;

	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

	cl_assert(git_path_isfile("testrepo/branch_file.txt"));
}
466 467 468 469 470 471 472 473 474 475

void assert_conflict(
	const char *entry_path,
	const char *new_content,
	const char *parent_sha,
	const char *commit_sha)
{
	git_index *index;
	git_object *hack_tree;
	git_reference *branch, *head;
nulltoken committed
476
	git_buf file_path = GIT_BUF_INIT;
477 478 479 480

	cl_git_pass(git_repository_index(&index, g_repo));

	/* Create a branch pointing at the parent */
481
	cl_git_pass(git_revparse_single(&g_object, g_repo, parent_sha));
482
	cl_git_pass(git_branch_create(&branch, g_repo,
483
		"potential_conflict", (git_commit *)g_object, 0, NULL, NULL));
484 485 486

	/* Make HEAD point to this branch */
	cl_git_pass(git_reference_symbolic_create(
487
		&head, g_repo, "HEAD", git_reference_name(branch), 1, NULL, NULL));
Carlos Martín Nieto committed
488 489
	git_reference_free(head);
	git_reference_free(branch);
490 491

	/* Checkout the parent */
492
	g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
493 494 495 496 497 498 499 500 501 502 503 504 505 506 507 508 509
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

	/* Hack-ishy workaound to ensure *all* the index entries
	 * match the content of the tree
	 */
	cl_git_pass(git_object_peel(&hack_tree, g_object, GIT_OBJ_TREE));
	cl_git_pass(git_index_read_tree(index, (git_tree *)hack_tree));
	git_object_free(hack_tree);
	git_object_free(g_object);
	g_object = NULL;

	/* Create a conflicting file */
	cl_git_pass(git_buf_joinpath(&file_path, "./testrepo", entry_path));
	cl_git_mkfile(git_buf_cstr(&file_path), new_content);
	git_buf_free(&file_path);

	/* Trying to checkout the original commit */
510
	cl_git_pass(git_revparse_single(&g_object, g_repo, commit_sha));
511

512
	g_opts.checkout_strategy = GIT_CHECKOUT_SAFE;
513 514 515 516
	cl_assert_equal_i(
		GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));

	/* Stage the conflicting change */
517
	cl_git_pass(git_index_add_bypath(index, entry_path));
518
	cl_git_pass(git_index_write(index));
Carlos Martín Nieto committed
519
	git_index_free(index);
520 521 522 523 524 525 526 527 528 529 530 531 532 533 534 535 536 537 538 539 540 541 542 543 544 545 546 547 548 549 550 551 552 553

	cl_assert_equal_i(
		GIT_EMERGECONFLICT, git_checkout_tree(g_repo, g_object, &g_opts));
}

void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT(void)
{
	/*
	 * 099faba adds a symlink named 'link_to_new.txt'
	 * a65fedf is the parent of 099faba
	 */

	assert_conflict("link_to_new.txt", "old.txt", "a65fedf", "099faba");
}

void test_checkout_tree__checking_out_a_conflicting_type_change_returns_EMERGECONFLICT_2(void)
{
	/*
	 * cf80f8d adds a directory named 'a/'
	 * a4a7dce is the parent of cf80f8d
	 */

	assert_conflict("a", "hello\n", "a4a7dce", "cf80f8d");
}

void test_checkout_tree__checking_out_a_conflicting_content_change_returns_EMERGECONFLICT(void)
{
	/*
	 * c47800c adds a symlink named 'branch_file.txt'
	 * 5b5b025 is the parent of 763d71a
	 */

	assert_conflict("branch_file.txt", "hello\n", "5b5b025", "c47800c");
}
554

555 556
void test_checkout_tree__donot_update_deleted_file_by_default(void)
{
557
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
558 559 560
	git_oid old_id, new_id;
	git_commit *old_commit = NULL, *new_commit = NULL;
	git_index *index = NULL;
561
	checkout_counts ct;
562 563 564

	opts.checkout_strategy = GIT_CHECKOUT_SAFE;

565 566 567 568 569
	memset(&ct, 0, sizeof(ct));
	opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
	opts.notify_cb = checkout_count_callback;
	opts.notify_payload = &ct;

570 571 572 573
	cl_git_pass(git_repository_index(&index, g_repo));

	cl_git_pass(git_oid_fromstr(&old_id, "be3563ae3f795b2b4353bcce3a527ad0a4f7f644"));
	cl_git_pass(git_commit_lookup(&old_commit, g_repo, &old_id));
574
	cl_git_pass(git_reset(g_repo, (git_object *)old_commit, GIT_RESET_HARD, NULL, NULL));
575 576 577 578 579 580 581 582 583

	cl_git_pass(p_unlink("testrepo/branch_file.txt"));
	cl_git_pass(git_index_remove_bypath(index ,"branch_file.txt"));
	cl_git_pass(git_index_write(index));

	cl_assert(!git_path_exists("testrepo/branch_file.txt"));

	cl_git_pass(git_oid_fromstr(&new_id, "099fabac3a9ea935598528c27f866e34089c2eff"));
	cl_git_pass(git_commit_lookup(&new_commit, g_repo, &new_id));
584 585


586 587
	cl_git_fail(git_checkout_tree(g_repo, (git_object *)new_commit, &opts));

588 589 590
	cl_assert_equal_i(1, ct.n_conflicts);
	cl_assert_equal_i(1, ct.n_updates);

591 592 593 594 595
	git_commit_free(old_commit);
	git_commit_free(new_commit);
	git_index_free(index);
}

596 597 598 599 600 601 602 603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622 623 624
struct checkout_cancel_at {
	const char *filename;
	int error;
	int count;
};

static int checkout_cancel_cb(
	git_checkout_notify_t why,
	const char *path,
	const git_diff_file *b,
	const git_diff_file *t,
	const git_diff_file *w,
	void *payload)
{
	struct checkout_cancel_at *ca = payload;

	GIT_UNUSED(why); GIT_UNUSED(b); GIT_UNUSED(t); GIT_UNUSED(w);

	ca->count++;

	if (!strcmp(path, ca->filename))
		return ca->error;

	return 0;
}

void test_checkout_tree__can_cancel_checkout_from_notify(void)
{
	struct checkout_cancel_at ca;
625
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
626 627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650 651
	git_oid oid;
	git_object *obj = NULL;

	assert_on_branch(g_repo, "master");

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	ca.filename = "new.txt";
	ca.error = -5555;
	ca.count = 0;

	opts.notify_flags = GIT_CHECKOUT_NOTIFY_UPDATED;
	opts.notify_cb = checkout_cancel_cb;
	opts.notify_payload = &ca;
	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_assert(!git_path_exists("testrepo/new.txt"));

	cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), -5555);

	cl_assert(!git_path_exists("testrepo/new.txt"));

	/* on case-insensitive FS = a/b.txt, branch_file.txt, new.txt */
	/* on case-sensitive FS   = README, then above */

652
	if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */
653 654 655 656 657 658 659 660 661 662 663 664 665
		cl_assert_equal_i(3, ca.count);
	else
		cl_assert_equal_i(4, ca.count);

	/* and again with a different stopping point and return code */
	ca.filename = "README";
	ca.error = 123;
	ca.count = 0;

	cl_git_fail_with(git_checkout_tree(g_repo, obj, &opts), 123);

	cl_assert(!git_path_exists("testrepo/new.txt"));

666
	if (git_path_exists("testrepo/.git/CoNfIg")) /* case insensitive */
667 668 669 670 671 672 673
		cl_assert_equal_i(4, ca.count);
	else
		cl_assert_equal_i(1, ca.count);

	git_object_free(obj);
}

674 675 676
void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void)
{
	git_index *index = NULL;
677
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
678 679 680
	git_oid tree_id, commit_id;
	git_tree *tree = NULL;
	git_commit *commit = NULL;
681

682
	git_repository_index(&index, g_repo);
683

684
	opts.checkout_strategy = GIT_CHECKOUT_FORCE;
685

686 687
	cl_git_pass(git_reference_name_to_id(&commit_id, g_repo, "refs/heads/master"));
	cl_git_pass(git_commit_lookup(&commit, g_repo, &commit_id));
688

689
	cl_git_pass(git_checkout_tree(g_repo, (git_object *)commit, &opts));
690
	cl_git_pass(git_repository_set_head(g_repo, "refs/heads/master", NULL, NULL));
691

692 693
	cl_git_pass(p_mkdir("./testrepo/this-is-dir", 0777));
	cl_git_mkfile("./testrepo/this-is-dir/contained_file", "content\n");
694

695 696 697
	cl_git_pass(git_index_add_bypath(index, "this-is-dir/contained_file"));
	git_index_write_tree(&tree_id, index);
	cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_id));
698

699
	cl_git_pass(p_unlink("./testrepo/this-is-dir/contained_file"));
700

701
	opts.checkout_strategy = GIT_CHECKOUT_SAFE;
702

703 704
	opts.checkout_strategy = 1;
	git_checkout_tree(g_repo, (git_object *)tree, &opts);
705

706 707 708 709
	git_tree_free(tree);
	git_commit_free(commit);
	git_index_free(index);
}
710 711 712

void test_checkout_tree__issue_1397(void)
{
713
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
714
	const char *partial_oid = "8a7ef04";
Russell Belfer committed
715 716 717
	git_object *tree = NULL;

	test_checkout_tree__cleanup(); /* cleanup default checkout */
718 719 720

	g_repo = cl_git_sandbox_init("issue_1397");

Russell Belfer committed
721
	cl_repo_set_bool(g_repo, "core.autocrlf", true);
722

723
	cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid));
724 725 726

	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

Russell Belfer committed
727
	cl_git_pass(git_checkout_tree(g_repo, tree, &opts));
728

729
	check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf");
730

Russell Belfer committed
731
	git_object_free(tree);
732
}
733 734 735

void test_checkout_tree__can_write_to_empty_dirs(void)
{
736
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755 756 757
	git_oid oid;
	git_object *obj = NULL;

	assert_on_branch(g_repo, "master");

	cl_git_pass(p_mkdir("testrepo/a", 0777));

	/* do first checkout with FORCE because we don't know if testrepo
	 * base data is clean for a checkout or not
	 */
	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));

	cl_assert(git_path_isfile("testrepo/a/b.txt"));

	git_object_free(obj);
}
758 759 760 761

void test_checkout_tree__fails_when_dir_in_use(void)
{
#ifdef GIT_WIN32
762
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786
	git_oid oid;
	git_object *obj = NULL;

	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));

	cl_assert(git_path_isfile("testrepo/a/b.txt"));

	git_object_free(obj);

	cl_git_pass(p_chdir("testrepo/a"));

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_fail(git_checkout_tree(g_repo, obj, &opts));

	cl_git_pass(p_chdir("../.."));

	cl_assert(git_path_is_empty_dir("testrepo/a"));
787 788

	git_object_free(obj);
789 790 791 792 793 794
#endif
}

void test_checkout_tree__can_continue_when_dir_in_use(void)
{
#ifdef GIT_WIN32
795
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
796 797 798 799 800 801 802 803 804 805 806 807 808 809 810 811 812 813 814 815 816 817 818 819 820
	git_oid oid;
	git_object *obj = NULL;

	opts.checkout_strategy = GIT_CHECKOUT_FORCE |
		GIT_CHECKOUT_SKIP_LOCKED_DIRECTORIES;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/dir"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));

	cl_assert(git_path_isfile("testrepo/a/b.txt"));

	git_object_free(obj);

	cl_git_pass(p_chdir("testrepo/a"));

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "refs/heads/master"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_pass(git_checkout_tree(g_repo, obj, &opts));

	cl_git_pass(p_chdir("../.."));

	cl_assert(git_path_is_empty_dir("testrepo/a"));
821 822

	git_object_free(obj);
823 824
#endif
}
825 826 827

void test_checkout_tree__target_directory_from_bare(void)
{
828
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
829 830 831 832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847 848 849 850 851 852 853 854 855 856 857
	git_oid oid;
	checkout_counts cts;
	memset(&cts, 0, sizeof(cts));

	test_checkout_tree__cleanup(); /* cleanup default checkout */

	g_repo = cl_git_sandbox_init("testrepo.git");
	cl_assert(git_repository_is_bare(g_repo));

	opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE;

	opts.notify_flags = GIT_CHECKOUT_NOTIFY_ALL;
	opts.notify_cb = checkout_count_callback;
	opts.notify_payload = &cts;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
	cl_git_pass(git_object_lookup(&g_object, g_repo, &oid, GIT_OBJ_ANY));

	cl_git_fail(git_checkout_tree(g_repo, g_object, &opts));

	opts.target_directory = "alternative";
	cl_assert(!git_path_isdir("alternative"));

	cl_git_pass(git_checkout_tree(g_repo, g_object, &opts));

	cl_assert_equal_i(0, cts.n_untracked);
	cl_assert_equal_i(0, cts.n_ignored);
	cl_assert_equal_i(3, cts.n_updates);

858 859 860
	check_file_contents_nocr("./alternative/README", "hey there\n");
	check_file_contents_nocr("./alternative/branch_file.txt", "hi\nbye!\n");
	check_file_contents_nocr("./alternative/new.txt", "my new file\n");
861 862 863 864

	cl_git_pass(git_futils_rmdir_r(
		"alternative", NULL, GIT_RMDIR_REMOVE_FILES));
}
865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883

void test_checkout_tree__extremely_long_file_name(void)
{
	// A utf-8 string with 83 characters, but 249 bytes.
	const char *longname = "\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97\xe5\x8f\x97";
	char path[1024];

	g_opts.checkout_strategy = GIT_CHECKOUT_FORCE;
	cl_git_pass(git_revparse_single(&g_object, g_repo, "long-file-name"));
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));

	sprintf(path, "testrepo/%s.txt", longname);
	cl_assert(git_path_exists(path));

	git_object_free(g_object);
	cl_git_pass(git_revparse_single(&g_object, g_repo, "master"));
	cl_git_pass(git_checkout_tree(g_repo, g_object, &g_opts));
	cl_assert(!git_path_exists(path));
}
884 885 886 887 888 889 890 891 892 893 894

static void create_conflict(void)
{
	git_index *index;
	git_index_entry entry;

	cl_git_pass(git_repository_index(&index, g_repo));

	memset(&entry, 0x0, sizeof(git_index_entry));
	entry.mode = 0100644;
	entry.flags = 1 << GIT_IDXENTRY_STAGESHIFT;
895
	git_oid_fromstr(&entry.id, "d427e0b2e138501a3d15cc376077a3631e15bd46");
896 897 898 899
	entry.path = "conflicts.txt";
	cl_git_pass(git_index_add(index, &entry));

	entry.flags = 2 << GIT_IDXENTRY_STAGESHIFT;
900
	git_oid_fromstr(&entry.id, "ee3fa1b8c00aff7fe02065fdb50864bb0d932ccf");
901 902 903
	cl_git_pass(git_index_add(index, &entry));

	entry.flags = 3 << GIT_IDXENTRY_STAGESHIFT;
904
	git_oid_fromstr(&entry.id, "2bd0a343aeef7a2cf0d158478966a6e587ff3863");
905 906 907 908 909 910 911 912
	cl_git_pass(git_index_add(index, &entry));

	git_index_write(index);
	git_index_free(index);
}

void test_checkout_tree__fails_when_conflicts_exist_in_index(void)
{
913
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
914 915 916 917 918 919 920 921 922 923 924 925 926 927
	git_oid oid;
	git_object *obj = NULL;

	opts.checkout_strategy = GIT_CHECKOUT_SAFE;

	cl_git_pass(git_reference_name_to_id(&oid, g_repo, "HEAD"));
	cl_git_pass(git_object_lookup(&obj, g_repo, &oid, GIT_OBJ_ANY));

	create_conflict();

	cl_git_fail(git_checkout_tree(g_repo, obj, &opts));

	git_object_free(obj);
}
928 929 930 931 932

void test_checkout_tree__filemode_preserved_in_index(void)
{
	git_oid executable_oid;
	git_commit *commit;
933
	git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT;
934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950
	git_index *index;
	const git_index_entry *entry;

	cl_git_pass(git_repository_index(&index, g_repo));

	cl_git_pass(git_oid_fromstr(&executable_oid, "afe4393b2b2a965f06acf2ca9658eaa01e0cd6b6"));
	cl_git_pass(git_commit_lookup(&commit, g_repo, &executable_oid));

	opts.checkout_strategy = GIT_CHECKOUT_FORCE;

	cl_git_pass(git_checkout_tree(g_repo, (const git_object *)commit, &opts));
	cl_assert(entry = git_index_get_bypath(index, "executable.txt", 0));
	cl_assert_equal_i(0100755, entry->mode);

	git_commit_free(commit);
	git_index_free(index);
}