write.c 15.7 KB
Newer Older
Ben Straub committed
1 2 3 4 5 6 7 8 9 10 11
#include "clar_libgit2.h"

#include "tree.h"

static const char *blob_oid = "fa49b077972391ad58037050f2a75f74e3671e92";
static const char *first_tree  = "181037049a54a1eb5fab404658a3a250b44335d7";
static const char *second_tree = "f60079018b664e4e79329a7ef9559c8d9e0378d1";
static const char *third_tree = "eb86d8b81d6adbd5290a935d6c9976882de98488";

static git_repository *g_repo;

12
/* Fixture setup and teardown */
Ben Straub committed
13 14 15 16 17 18 19 20
void test_object_tree_write__initialize(void)
{
   g_repo = cl_git_sandbox_init("testrepo");
}

void test_object_tree_write__cleanup(void)
{
   cl_git_sandbox_cleanup();
21

22
	cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 1));
Ben Straub committed
23 24 25 26
}

void test_object_tree_write__from_memory(void)
{
27
	/* write a tree from a memory */
Ben Straub committed
28 29 30 31 32 33 34 35
	git_treebuilder *builder;
	git_tree *tree;
	git_oid id, bid, rid, id2;

	git_oid_fromstr(&id, first_tree);
	git_oid_fromstr(&id2, second_tree);
	git_oid_fromstr(&bid, blob_oid);

36 37 38
	/* create a second tree from first tree using `git_treebuilder_insert`
	 * on REPOSITORY_FOLDER.
	 */
Ben Straub committed
39
	cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
40
	cl_git_pass(git_treebuilder_new(&builder, g_repo, tree));
Ben Straub committed
41

nulltoken committed
42 43 44 45
	cl_git_fail(git_treebuilder_insert(NULL, builder, "",
		&bid, GIT_FILEMODE_BLOB));
	cl_git_fail(git_treebuilder_insert(NULL, builder, "/",
		&bid, GIT_FILEMODE_BLOB));
46 47 48 49 50 51
	cl_git_fail(git_treebuilder_insert(NULL, builder, ".git",
		&bid, GIT_FILEMODE_BLOB));
	cl_git_fail(git_treebuilder_insert(NULL, builder, "..",
		&bid, GIT_FILEMODE_BLOB));
	cl_git_fail(git_treebuilder_insert(NULL, builder, ".",
		&bid, GIT_FILEMODE_BLOB));
nulltoken committed
52 53 54 55 56
	cl_git_fail(git_treebuilder_insert(NULL, builder, "folder/new.txt",
		&bid, GIT_FILEMODE_BLOB));

	cl_git_pass(git_treebuilder_insert(
		NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB));
Ben Straub committed
57

58
	cl_git_pass(git_treebuilder_write(&rid, builder));
Ben Straub committed
59 60 61 62 63 64 65 66 67

	cl_assert(git_oid_cmp(&rid, &id2) == 0);

	git_treebuilder_free(builder);
	git_tree_free(tree);
}

void test_object_tree_write__subtree(void)
{
68
	/* write a hierarchical tree from a memory */
Ben Straub committed
69 70 71 72 73 74 75 76 77 78
	git_treebuilder *builder;
	git_tree *tree;
	git_oid id, bid, subtree_id, id2, id3;
	git_oid id_hiearar;

	git_oid_fromstr(&id, first_tree);
	git_oid_fromstr(&id2, second_tree);
	git_oid_fromstr(&id3, third_tree);
	git_oid_fromstr(&bid, blob_oid);

79
	/* create subtree */
80
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
nulltoken committed
81
	cl_git_pass(git_treebuilder_insert(
82
		NULL, builder, "new.txt", &bid, GIT_FILEMODE_BLOB)); /* -V536 */
83
	cl_git_pass(git_treebuilder_write(&subtree_id, builder));
Ben Straub committed
84 85
	git_treebuilder_free(builder);

86
	/* create parent tree */
Ben Straub committed
87
	cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
88
	cl_git_pass(git_treebuilder_new(&builder, g_repo, tree));
nulltoken committed
89
	cl_git_pass(git_treebuilder_insert(
90
		NULL, builder, "new", &subtree_id, GIT_FILEMODE_TREE)); /* -V536 */
91
	cl_git_pass(git_treebuilder_write(&id_hiearar, builder));
Ben Straub committed
92 93 94 95 96
	git_treebuilder_free(builder);
	git_tree_free(tree);

	cl_assert(git_oid_cmp(&id_hiearar, &id3) == 0);

97
	/* check data is correct */
Ben Straub committed
98 99 100 101
	cl_git_pass(git_tree_lookup(&tree, g_repo, &id_hiearar));
	cl_assert(2 == git_tree_entrycount(tree));
	git_tree_free(tree);
}
102 103 104 105 106 107 108

/*
 * And the Lord said: Is this tree properly sorted?
 */
void test_object_tree_write__sorted_subtrees(void)
{
	git_treebuilder *builder;
109
	git_tree *tree;
Vicent Marti committed
110 111
	unsigned int i;
	int position_c = -1, position_cake = -1, position_config = -1;
112 113 114 115 116

	struct {
		unsigned int attr;
		const char *filename;
	} entries[] = {
nulltoken committed
117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133
		{ GIT_FILEMODE_BLOB, ".gitattributes" },
	  	{ GIT_FILEMODE_BLOB, ".gitignore" },
	  	{ GIT_FILEMODE_BLOB, ".htaccess" },
	  	{ GIT_FILEMODE_BLOB, "Capfile" },
	  	{ GIT_FILEMODE_BLOB, "Makefile"},
	  	{ GIT_FILEMODE_BLOB, "README"},
	  	{ GIT_FILEMODE_TREE, "app"},
	  	{ GIT_FILEMODE_TREE, "cake"},
	  	{ GIT_FILEMODE_TREE, "config"},
	  	{ GIT_FILEMODE_BLOB, "c"},
	  	{ GIT_FILEMODE_BLOB, "git_test.txt"},
	  	{ GIT_FILEMODE_BLOB, "htaccess.htaccess"},
	  	{ GIT_FILEMODE_BLOB, "index.php"},
	  	{ GIT_FILEMODE_TREE, "plugins"},
	  	{ GIT_FILEMODE_TREE, "schemas"},
	  	{ GIT_FILEMODE_TREE, "ssl-certs"},
	  	{ GIT_FILEMODE_TREE, "vendors"}
134 135
	};

136
	git_oid bid, tid, tree_oid;
137

138 139
	cl_git_pass(git_oid_fromstr(&bid, blob_oid));
	cl_git_pass(git_oid_fromstr(&tid, first_tree));
140

141
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
142 143

	for (i = 0; i < ARRAY_SIZE(entries); ++i) {
144 145
		git_oid *id = entries[i].attr == GIT_FILEMODE_TREE ?  &tid : &bid; 

146
		cl_git_pass(git_treebuilder_insert(NULL,
147
			builder, entries[i].filename, id, entries[i].attr));
148 149
	}

150
	cl_git_pass(git_treebuilder_write(&tree_oid, builder));
151

152 153 154
	cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));
	for (i = 0; i < git_tree_entrycount(tree); i++) {
		const git_tree_entry *entry = git_tree_entry_byindex(tree, i);
155 156 157 158 159 160 161 162 163 164 165

		if (strcmp(entry->filename, "c") == 0)
			position_c = i;

		if (strcmp(entry->filename, "cake") == 0)
			position_cake = i;

		if (strcmp(entry->filename, "config") == 0)
			position_config = i;
	}

166 167
	git_tree_free(tree);

Vicent Marti committed
168 169 170 171
	cl_assert(position_c != -1);
	cl_assert(position_cake != -1);
	cl_assert(position_config != -1);

172 173 174 175 176
	cl_assert(position_c < position_cake);
	cl_assert(position_cake < position_config);

	git_treebuilder_free(builder);
}
177

178 179 180 181 182 183 184 185 186 187 188 189 190
static struct {
	unsigned int attr;
	const char *filename;
} _entries[] = {
	{ GIT_FILEMODE_BLOB, "aardvark" },
	{ GIT_FILEMODE_BLOB, ".first" },
	{ GIT_FILEMODE_BLOB, "apple" },
	{ GIT_FILEMODE_BLOB, "last"},
	{ GIT_FILEMODE_BLOB, "apple_after"},
	{ GIT_FILEMODE_BLOB, "after_aardvark"},
	{ 0, NULL },
};

191 192 193
void test_object_tree_write__removing_and_re_adding_in_treebuilder(void)
{
	git_treebuilder *builder;
194
	int i, aardvark_i, apple_i, apple_after_i, apple_extra_i, last_i;
195
	git_oid entry_oid, tree_oid;
196 197
	git_tree *tree;

198
	cl_git_pass(git_oid_fromstr(&entry_oid, blob_oid));
199

200
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
201 202 203

	cl_assert_equal_i(0, (int)git_treebuilder_entrycount(builder));

204
	for (i = 0; _entries[i].filename; ++i)
205
		cl_git_pass(git_treebuilder_insert(NULL,
206
			builder, _entries[i].filename, &entry_oid, _entries[i].attr));
207 208 209 210 211 212 213 214 215 216

	cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));

	cl_git_pass(git_treebuilder_remove(builder, "apple"));
	cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));

	cl_git_pass(git_treebuilder_remove(builder, "apple_after"));
	cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder));

	cl_git_pass(git_treebuilder_insert(
217
		NULL, builder, "before_last", &entry_oid, GIT_FILEMODE_BLOB));
218 219 220 221
	cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));

	/* reinsert apple_after */
	cl_git_pass(git_treebuilder_insert(
222
		NULL, builder, "apple_after", &entry_oid, GIT_FILEMODE_BLOB));
223 224 225 226 227 228 229
	cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));

	cl_git_pass(git_treebuilder_remove(builder, "last"));
	cl_assert_equal_i(5, (int)git_treebuilder_entrycount(builder));

	/* reinsert last */
	cl_git_pass(git_treebuilder_insert(
230
		NULL, builder, "last", &entry_oid, GIT_FILEMODE_BLOB));
231 232 233
	cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));

	cl_git_pass(git_treebuilder_insert(
234
		NULL, builder, "apple_extra", &entry_oid, GIT_FILEMODE_BLOB));
235 236
	cl_assert_equal_i(7, (int)git_treebuilder_entrycount(builder));

237
	cl_git_pass(git_treebuilder_write(&tree_oid, builder));
238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274

	git_treebuilder_free(builder);

	cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));

	cl_assert_equal_i(7, (int)git_tree_entrycount(tree));

	cl_assert(git_tree_entry_byname(tree, ".first") != NULL);
	cl_assert(git_tree_entry_byname(tree, "apple") == NULL);
	cl_assert(git_tree_entry_byname(tree, "apple_after") != NULL);
	cl_assert(git_tree_entry_byname(tree, "apple_extra") != NULL);
	cl_assert(git_tree_entry_byname(tree, "last") != NULL);

	aardvark_i = apple_i = apple_after_i = apple_extra_i = last_i = -1;

	for (i = 0; i < 7; ++i) {
		const git_tree_entry *entry = git_tree_entry_byindex(tree, i);

		if (!strcmp(entry->filename, "aardvark"))
			aardvark_i = i;
		else if (!strcmp(entry->filename, "apple"))
			apple_i = i;
		else if (!strcmp(entry->filename, "apple_after"))
			apple_after_i = i;
		else if (!strcmp(entry->filename, "apple_extra"))
			apple_extra_i = i;
		else if (!strcmp(entry->filename, "last"))
			last_i = i;
	}

	cl_assert_equal_i(-1, apple_i);
	cl_assert_equal_i(6, last_i);
	cl_assert(aardvark_i < apple_after_i);
	cl_assert(apple_after_i < apple_extra_i);

	git_tree_free(tree);
}
275 276 277 278 279 280 281 282 283 284 285

static int treebuilder_filter_prefixed(
	const git_tree_entry *entry, void *payload)
{
	return !git__prefixcmp(git_tree_entry_name(entry), payload);
}

void test_object_tree_write__filtering(void)
{
	git_treebuilder *builder;
	int i;
286
	git_oid entry_oid, tree_oid;
287 288
	git_tree *tree;

289
	git_oid_fromstr(&entry_oid, blob_oid);
290

291
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
292 293 294

	for (i = 0; _entries[i].filename; ++i)
		cl_git_pass(git_treebuilder_insert(NULL,
295
			builder, _entries[i].filename, &entry_oid, _entries[i].attr));
296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317

	cl_assert_equal_i(6, (int)git_treebuilder_entrycount(builder));

	cl_assert(git_treebuilder_get(builder, "apple") != NULL);
	cl_assert(git_treebuilder_get(builder, "aardvark") != NULL);
	cl_assert(git_treebuilder_get(builder, "last") != NULL);

	git_treebuilder_filter(builder, treebuilder_filter_prefixed, "apple");

	cl_assert_equal_i(4, (int)git_treebuilder_entrycount(builder));

	cl_assert(git_treebuilder_get(builder, "apple") == NULL);
	cl_assert(git_treebuilder_get(builder, "aardvark") != NULL);
	cl_assert(git_treebuilder_get(builder, "last") != NULL);

	git_treebuilder_filter(builder, treebuilder_filter_prefixed, "a");

	cl_assert_equal_i(2, (int)git_treebuilder_entrycount(builder));

	cl_assert(git_treebuilder_get(builder, "aardvark") == NULL);
	cl_assert(git_treebuilder_get(builder, "last") != NULL);

318
	cl_git_pass(git_treebuilder_write(&tree_oid, builder));
319 320 321 322 323 324 325 326 327

	git_treebuilder_free(builder);

	cl_git_pass(git_tree_lookup(&tree, g_repo, &tree_oid));

	cl_assert_equal_i(2, (int)git_tree_entrycount(tree));

	git_tree_free(tree);
}
328 329 330 331 332 333 334 335

void test_object_tree_write__cruel_paths(void)
{
	static const char *the_paths[] = {
		"C:\\",
		" : * ? \" \n < > |",
		"a\\b",
		"\\\\b\a",
336 337 338
		":\\",
		"COM1",
		"foo.aux",
339 340
		REP1024("1234"), /* 4096 char string */
		REP1024("12345678"), /* 8192 char string */
341
		"\xC5\xAA\x6E\xC4\xAD\x63\xC5\x8D\x64\x65\xCC\xBD", /* Ūnĭcōde̽ */
342 343 344 345
		NULL
	};
	git_treebuilder *builder;
	git_tree *tree;
346
	git_oid id, bid, subid;
347
	const char **scan;
348 349
	int count = 0, i, j;
	git_tree_entry *te;
350 351 352 353

	git_oid_fromstr(&bid, blob_oid);

	/* create tree */
354
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
355 356 357 358 359
	for (scan = the_paths; *scan; ++scan) {
		cl_git_pass(git_treebuilder_insert(
			NULL, builder, *scan, &bid, GIT_FILEMODE_BLOB));
		count++;
	}
360
	cl_git_pass(git_treebuilder_write(&id, builder));
361 362 363 364
	git_treebuilder_free(builder);

	/* check data is correct */
	cl_git_pass(git_tree_lookup(&tree, g_repo, &id));
365

366
	cl_assert_equal_i(count, git_tree_entrycount(tree));
367

368
	for (scan = the_paths; *scan; ++scan) {
369 370 371
		const git_tree_entry *cte = git_tree_entry_byname(tree, *scan);
		cl_assert(cte != NULL);
		cl_assert_equal_s(*scan, git_tree_entry_name(cte));
372 373 374 375 376 377
	}
	for (scan = the_paths; *scan; ++scan) {
		cl_git_pass(git_tree_entry_bypath(&te, tree, *scan));
		cl_assert_equal_s(*scan, git_tree_entry_name(te));
		git_tree_entry_free(te);
	}
378 379 380 381

	git_tree_free(tree);

	/* let's try longer paths */
382
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
383 384 385 386
	for (scan = the_paths; *scan; ++scan) {
		cl_git_pass(git_treebuilder_insert(
			NULL, builder, *scan, &id, GIT_FILEMODE_TREE));
	}
387
	cl_git_pass(git_treebuilder_write(&subid, builder));
388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
	git_treebuilder_free(builder);

	/* check data is correct */
	cl_git_pass(git_tree_lookup(&tree, g_repo, &subid));

	cl_assert_equal_i(count, git_tree_entrycount(tree));

	for (i = 0; i < count; ++i) {
		for (j = 0; j < count; ++j) {
			git_buf b = GIT_BUF_INIT;
			cl_git_pass(git_buf_joinpath(&b, the_paths[i], the_paths[j]));
			cl_git_pass(git_tree_entry_bypath(&te, tree, b.ptr));
			cl_assert_equal_s(the_paths[j], git_tree_entry_name(te));
			git_tree_entry_free(te);
			git_buf_free(&b);
		}
	}

406 407
	git_tree_free(tree);
}
408 409 410 411 412 413

void test_object_tree_write__protect_filesystems(void)
{
	git_treebuilder *builder;
	git_oid bid;

414 415
	cl_git_pass(git_oid_fromstr(&bid, "fa49b077972391ad58037050f2a75f74e3671e92"));

416 417 418
	/* Ensure that (by default) we can write objects with funny names on
	 * platforms that are not affected.
	 */
419
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439

#ifndef GIT_WIN32
	cl_git_pass(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB));
	cl_git_pass(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB));
#endif

#ifndef __APPLE__
	cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB));
	cl_git_pass(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB));
#endif

	git_treebuilder_free(builder);

	/* Now turn on core.protectHFS and core.protectNTFS and validate that these
	 * paths are rejected.
	 */

	cl_repo_set_bool(g_repo, "core.protectHFS", true);
	cl_repo_set_bool(g_repo, "core.protectNTFS", true);

440
	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));
441 442 443 444 445 446 447 448 449

	cl_git_fail(git_treebuilder_insert(NULL, builder, ".git.", &bid, GIT_FILEMODE_BLOB));
	cl_git_fail(git_treebuilder_insert(NULL, builder, "git~1", &bid, GIT_FILEMODE_BLOB));

	cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xef\xbb\xbf", &bid, GIT_FILEMODE_BLOB));
	cl_git_fail(git_treebuilder_insert(NULL, builder, ".git\xe2\x80\xad", &bid, GIT_FILEMODE_BLOB));

	git_treebuilder_free(builder);
}
450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491 492

static void test_invalid_objects(bool should_allow_invalid)
{
	git_treebuilder *builder;
	git_oid valid_blob_id, invalid_blob_id, valid_tree_id, invalid_tree_id;

#define assert_allowed(expr) \
	clar__assert(!(expr) == should_allow_invalid, __FILE__, __LINE__, \
		(should_allow_invalid ? \
		 "Expected function call to succeed: " #expr : \
		 "Expected function call to fail: " #expr), \
		NULL, 1)

	cl_git_pass(git_oid_fromstr(&valid_blob_id, blob_oid));
	cl_git_pass(git_oid_fromstr(&invalid_blob_id,
		"1234567890123456789012345678901234567890"));
	cl_git_pass(git_oid_fromstr(&valid_tree_id, first_tree));
	cl_git_pass(git_oid_fromstr(&invalid_tree_id,
		"0000000000111111111122222222223333333333"));

	cl_git_pass(git_treebuilder_new(&builder, g_repo, NULL));

	/* test valid blobs and trees (these should always pass) */
	cl_git_pass(git_treebuilder_insert(NULL, builder, "file.txt", &valid_blob_id, GIT_FILEMODE_BLOB));
	cl_git_pass(git_treebuilder_insert(NULL, builder, "folder", &valid_tree_id, GIT_FILEMODE_TREE));

	/* replace valid files and folders with invalid ones */
	assert_allowed(git_treebuilder_insert(NULL, builder, "file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB));
	assert_allowed(git_treebuilder_insert(NULL, builder, "folder", &invalid_blob_id, GIT_FILEMODE_BLOB));

	/* insert new invalid files and folders */
	assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_file.txt", &invalid_blob_id, GIT_FILEMODE_BLOB));
	assert_allowed(git_treebuilder_insert(NULL, builder, "invalid_folder", &invalid_blob_id, GIT_FILEMODE_BLOB));

	/* insert valid blobs as trees and trees as blobs */
	assert_allowed(git_treebuilder_insert(NULL, builder, "file_as_folder", &valid_blob_id, GIT_FILEMODE_TREE));
	assert_allowed(git_treebuilder_insert(NULL, builder, "folder_as_file.txt", &valid_tree_id, GIT_FILEMODE_BLOB));

#undef assert_allowed

	git_treebuilder_free(builder);
}

493 494 495 496 497 498 499 500 501 502
static void test_inserting_submodule(void)
{
	git_treebuilder *bld;
	git_oid sm_id;

	cl_git_pass(git_treebuilder_new(&bld, g_repo, NULL));
	cl_git_pass(git_treebuilder_insert(NULL, bld, "sm", &sm_id, GIT_FILEMODE_COMMIT));
	git_treebuilder_free(bld);
}

503 504
void test_object_tree_write__object_validity(void)
{
505
	/* Ensure that we cannot add invalid objects by default */
506
	test_invalid_objects(false);
507
	test_inserting_submodule();
508 509 510 511

	/* Ensure that we can turn off validation */
	cl_git_pass(git_libgit2_opts(GIT_OPT_ENABLE_STRICT_OBJECT_CREATION, 0));
	test_invalid_objects(true);
512
	test_inserting_submodule();
513 514
}