1 #include "clar_libgit2.h"
2 #include "buffer.h"
3 #include "path.h"
4 #include "posix.h"
5 #include "status_helpers.h"
6 #include "util.h"
7 #include "status.h"
8 
9 static git_repository *g_repo = NULL;
10 
test_status_renames__initialize(void)11 void test_status_renames__initialize(void)
12 {
13 	g_repo = cl_git_sandbox_init("renames");
14 
15 	cl_repo_set_bool(g_repo, "core.autocrlf", false);
16 }
17 
test_status_renames__cleanup(void)18 void test_status_renames__cleanup(void)
19 {
20 	cl_git_sandbox_cleanup();
21 }
22 
_rename_helper(git_repository * repo,const char * from,const char * to,const char * extra)23 static void _rename_helper(
24 	git_repository *repo, const char *from, const char *to, const char *extra)
25 {
26 	git_buf oldpath = GIT_BUF_INIT, newpath = GIT_BUF_INIT;
27 
28 	cl_git_pass(git_buf_joinpath(
29 		&oldpath, git_repository_workdir(repo), from));
30 	cl_git_pass(git_buf_joinpath(
31 		&newpath, git_repository_workdir(repo), to));
32 
33 	cl_git_pass(p_rename(oldpath.ptr, newpath.ptr));
34 
35 	if (extra)
36 		cl_git_append2file(newpath.ptr, extra);
37 
38 	git_buf_dispose(&oldpath);
39 	git_buf_dispose(&newpath);
40 }
41 
42 #define rename_file(R,O,N) _rename_helper((R), (O), (N), NULL)
43 #define rename_and_edit_file(R,O,N) \
44 	_rename_helper((R), (O), (N), "Added at the end to keep similarity!")
45 
46 struct status_entry {
47 	git_status_t status;
48 	const char *oldname;
49 	const char *newname;
50 };
51 
check_status(git_status_list * status_list,struct status_entry * expected_list,size_t expected_len)52 static void check_status(
53 	git_status_list *status_list,
54 	struct status_entry *expected_list,
55 	size_t expected_len)
56 {
57 	const git_status_entry *actual;
58 	const struct status_entry *expected;
59 	const char *oldname, *newname;
60 	size_t i, files_in_status = git_status_list_entrycount(status_list);
61 
62 	cl_assert_equal_sz(expected_len, files_in_status);
63 
64 	for (i = 0; i < expected_len; i++) {
65 		actual = git_status_byindex(status_list, i);
66 		expected = &expected_list[i];
67 
68 		oldname = actual->head_to_index ? actual->head_to_index->old_file.path :
69 			actual->index_to_workdir ? actual->index_to_workdir->old_file.path : NULL;
70 
71 		newname = actual->index_to_workdir ? actual->index_to_workdir->new_file.path :
72 			actual->head_to_index ? actual->head_to_index->new_file.path : NULL;
73 
74 		cl_assert_equal_i_fmt(expected->status, actual->status, "%04x");
75 
76 		if (expected->oldname) {
77 			cl_assert(oldname != NULL);
78 			cl_assert_equal_s(oldname, expected->oldname);
79 		} else {
80 			cl_assert(oldname == NULL);
81 		}
82 
83 		if (actual->status & (GIT_STATUS_INDEX_RENAMED|GIT_STATUS_WT_RENAMED)) {
84 			if (expected->newname) {
85 				cl_assert(newname != NULL);
86 				cl_assert_equal_s(newname, expected->newname);
87 			} else {
88 				cl_assert(newname == NULL);
89 			}
90 		}
91 	}
92 }
93 
test_status_renames__head2index_one(void)94 void test_status_renames__head2index_one(void)
95 {
96 	git_index *index;
97 	git_status_list *statuslist;
98 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
99 	struct status_entry expected[] = {
100 		{ GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "newname.txt" },
101 	};
102 
103 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
104 
105 	cl_git_pass(git_repository_index(&index, g_repo));
106 
107 	rename_file(g_repo, "ikeepsix.txt", "newname.txt");
108 
109 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
110 	cl_git_pass(git_index_add_bypath(index, "newname.txt"));
111 	cl_git_pass(git_index_write(index));
112 
113 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
114 	check_status(statuslist, expected, 1);
115 	git_status_list_free(statuslist);
116 
117 	git_index_free(index);
118 }
119 
test_status_renames__head2index_two(void)120 void test_status_renames__head2index_two(void)
121 {
122 	git_index *index;
123 	git_status_list *statuslist;
124 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
125 	struct status_entry expected[] = {
126 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
127 		  "sixserving.txt", "aaa.txt" },
128 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
129 		  "untimely.txt", "bbb.txt" },
130 		{ GIT_STATUS_INDEX_RENAMED, "songof7cities.txt", "ccc.txt" },
131 		{ GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "ddd.txt" },
132 	};
133 
134 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
135 
136 	cl_git_pass(git_repository_index(&index, g_repo));
137 
138 	rename_file(g_repo, "ikeepsix.txt", "ddd.txt");
139 	rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt");
140 	rename_file(g_repo, "songof7cities.txt", "ccc.txt");
141 	rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt");
142 
143 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
144 	cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
145 	cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
146 	cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
147 	cl_git_pass(git_index_add_bypath(index, "ddd.txt"));
148 	cl_git_pass(git_index_add_bypath(index, "aaa.txt"));
149 	cl_git_pass(git_index_add_bypath(index, "ccc.txt"));
150 	cl_git_pass(git_index_add_bypath(index, "bbb.txt"));
151 	cl_git_pass(git_index_write(index));
152 
153 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
154 	check_status(statuslist, expected, 4);
155 	git_status_list_free(statuslist);
156 
157 	git_index_free(index);
158 }
159 
test_status_renames__head2index_no_rename_from_rewrite(void)160 void test_status_renames__head2index_no_rename_from_rewrite(void)
161 {
162 	git_index *index;
163 	git_status_list *statuslist;
164 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
165 	struct status_entry expected[] = {
166 		{ GIT_STATUS_INDEX_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" },
167 		{ GIT_STATUS_INDEX_MODIFIED, "sixserving.txt", "sixserving.txt" },
168 	};
169 
170 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
171 
172 	cl_git_pass(git_repository_index(&index, g_repo));
173 
174 	rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
175 	rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
176 	rename_file(g_repo, "_temp_.txt", "sixserving.txt");
177 
178 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
179 	cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
180 	cl_git_pass(git_index_write(index));
181 
182 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
183 	check_status(statuslist, expected, 2);
184 	git_status_list_free(statuslist);
185 
186 	git_index_free(index);
187 }
188 
test_status_renames__head2index_rename_from_rewrite(void)189 void test_status_renames__head2index_rename_from_rewrite(void)
190 {
191 	git_index *index;
192 	git_status_list *statuslist;
193 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
194 	struct status_entry expected[] = {
195 		{ GIT_STATUS_INDEX_RENAMED, "sixserving.txt", "ikeepsix.txt" },
196 		{ GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", "sixserving.txt" },
197 	};
198 
199 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
200 	opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
201 
202 	cl_git_pass(git_repository_index(&index, g_repo));
203 
204 	rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
205 	rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
206 	rename_file(g_repo, "_temp_.txt", "sixserving.txt");
207 
208 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
209 	cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
210 	cl_git_pass(git_index_write(index));
211 
212 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
213 	check_status(statuslist, expected, 2);
214 	git_status_list_free(statuslist);
215 
216 	git_index_free(index);
217 }
218 
test_status_renames__index2workdir_one(void)219 void test_status_renames__index2workdir_one(void)
220 {
221 	git_status_list *statuslist;
222 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
223 	struct status_entry expected[] = {
224 		{ GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "newname.txt" },
225 	};
226 
227 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
228 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
229 
230 	rename_file(g_repo, "ikeepsix.txt", "newname.txt");
231 
232 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
233 	check_status(statuslist, expected, 1);
234 	git_status_list_free(statuslist);
235 }
236 
test_status_renames__index2workdir_two(void)237 void test_status_renames__index2workdir_two(void)
238 {
239 	git_status_list *statuslist;
240 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
241 	struct status_entry expected[] = {
242 		{ GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
243 		  "sixserving.txt", "aaa.txt" },
244 		{ GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
245 		  "untimely.txt", "bbb.txt" },
246 		{ GIT_STATUS_WT_RENAMED, "songof7cities.txt", "ccc.txt" },
247 		{ GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "ddd.txt" },
248 	};
249 
250 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
251 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
252 
253 	rename_file(g_repo, "ikeepsix.txt", "ddd.txt");
254 	rename_and_edit_file(g_repo, "sixserving.txt", "aaa.txt");
255 	rename_file(g_repo, "songof7cities.txt", "ccc.txt");
256 	rename_and_edit_file(g_repo, "untimely.txt", "bbb.txt");
257 
258 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
259 	check_status(statuslist, expected, 4);
260 	git_status_list_free(statuslist);
261 }
262 
test_status_renames__index2workdir_rename_from_rewrite(void)263 void test_status_renames__index2workdir_rename_from_rewrite(void)
264 {
265 	git_index *index;
266 	git_status_list *statuslist;
267 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
268 	struct status_entry expected[] = {
269 		{ GIT_STATUS_WT_RENAMED, "sixserving.txt", "ikeepsix.txt" },
270 		{ GIT_STATUS_WT_RENAMED, "ikeepsix.txt", "sixserving.txt" },
271 	};
272 
273 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
274 	opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
275 
276 	cl_git_pass(git_repository_index(&index, g_repo));
277 
278 	rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
279 	rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
280 	rename_file(g_repo, "_temp_.txt", "sixserving.txt");
281 
282 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
283 	check_status(statuslist, expected, 2);
284 	git_status_list_free(statuslist);
285 
286 	git_index_free(index);
287 }
288 
test_status_renames__both_one(void)289 void test_status_renames__both_one(void)
290 {
291 	git_index *index;
292 	git_status_list *statuslist;
293 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
294 	struct status_entry expected[] = {
295 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
296 		  "ikeepsix.txt", "newname-workdir.txt" },
297 	};
298 
299 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
300 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
301 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
302 
303 	cl_git_pass(git_repository_index(&index, g_repo));
304 
305 	rename_file(g_repo, "ikeepsix.txt", "newname-index.txt");
306 
307 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
308 	cl_git_pass(git_index_add_bypath(index, "newname-index.txt"));
309 	cl_git_pass(git_index_write(index));
310 
311 	rename_file(g_repo, "newname-index.txt", "newname-workdir.txt");
312 
313 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
314 	check_status(statuslist, expected, 1);
315 	git_status_list_free(statuslist);
316 
317 	git_index_free(index);
318 }
319 
test_status_renames__both_two(void)320 void test_status_renames__both_two(void)
321 {
322 	git_index *index;
323 	git_status_list *statuslist;
324 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
325 	struct status_entry expected[] = {
326 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
327 		  GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
328 		  "ikeepsix.txt", "ikeepsix-both.txt" },
329 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED,
330 		  "sixserving.txt", "sixserving-index.txt" },
331 		{ GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
332 		  "songof7cities.txt", "songof7cities-workdir.txt" },
333 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
334 		  "untimely.txt", "untimely-both.txt" },
335 	};
336 
337 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
338 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
339 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
340 
341 	cl_git_pass(git_repository_index(&index, g_repo));
342 
343 	rename_and_edit_file(g_repo, "ikeepsix.txt", "ikeepsix-index.txt");
344 	rename_and_edit_file(g_repo, "sixserving.txt", "sixserving-index.txt");
345 	rename_file(g_repo, "untimely.txt", "untimely-index.txt");
346 
347 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
348 	cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
349 	cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
350 	cl_git_pass(git_index_add_bypath(index, "ikeepsix-index.txt"));
351 	cl_git_pass(git_index_add_bypath(index, "sixserving-index.txt"));
352 	cl_git_pass(git_index_add_bypath(index, "untimely-index.txt"));
353 	cl_git_pass(git_index_write(index));
354 
355 	rename_and_edit_file(g_repo, "ikeepsix-index.txt", "ikeepsix-both.txt");
356 	rename_and_edit_file(g_repo, "songof7cities.txt", "songof7cities-workdir.txt");
357 	rename_file(g_repo, "untimely-index.txt", "untimely-both.txt");
358 
359 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
360 	check_status(statuslist, expected, 4);
361 	git_status_list_free(statuslist);
362 
363 	git_index_free(index);
364 }
365 
366 
test_status_renames__both_rename_from_rewrite(void)367 void test_status_renames__both_rename_from_rewrite(void)
368 {
369 	git_index *index;
370 	git_status_list *statuslist;
371 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
372 	struct status_entry expected[] = {
373 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
374 		  "songof7cities.txt", "ikeepsix.txt" },
375 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
376 		  "ikeepsix.txt", "sixserving.txt" },
377 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
378 		  "sixserving.txt", "songof7cities.txt" },
379 	};
380 
381 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
382 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
383 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
384 	opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
385 
386 	cl_git_pass(git_repository_index(&index, g_repo));
387 
388 	rename_file(g_repo, "ikeepsix.txt", "_temp_.txt");
389 	rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
390 	rename_file(g_repo, "songof7cities.txt", "sixserving.txt");
391 	rename_file(g_repo, "_temp_.txt", "songof7cities.txt");
392 
393 	cl_git_pass(git_index_add_bypath(index, "ikeepsix.txt"));
394 	cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
395 	cl_git_pass(git_index_add_bypath(index, "songof7cities.txt"));
396 	cl_git_pass(git_index_write(index));
397 
398 	rename_file(g_repo, "songof7cities.txt", "_temp_.txt");
399 	rename_file(g_repo, "ikeepsix.txt", "songof7cities.txt");
400 	rename_file(g_repo, "sixserving.txt", "ikeepsix.txt");
401 	rename_file(g_repo, "_temp_.txt", "sixserving.txt");
402 
403 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
404 	check_status(statuslist, expected, 3);
405 	git_status_list_free(statuslist);
406 
407 	git_index_free(index);
408 }
409 
test_status_renames__rewrites_only_for_renames(void)410 void test_status_renames__rewrites_only_for_renames(void)
411 {
412 	git_index *index;
413 	git_status_list *statuslist;
414 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
415 	struct status_entry expected[] = {
416 		{ GIT_STATUS_WT_MODIFIED, "ikeepsix.txt", "ikeepsix.txt" },
417 	};
418 
419 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
420 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
421 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
422 	opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES;
423 
424 	cl_git_pass(git_repository_index(&index, g_repo));
425 
426 	cl_git_rewritefile("renames/ikeepsix.txt",
427 		"This is enough content for the file to be rewritten.\n" \
428 		"This is enough content for the file to be rewritten.\n" \
429 		"This is enough content for the file to be rewritten.\n" \
430 		"This is enough content for the file to be rewritten.\n" \
431 		"This is enough content for the file to be rewritten.\n" \
432 		"This is enough content for the file to be rewritten.\n" \
433 		"This is enough content for the file to be rewritten.\n" \
434 		"This is enough content for the file to be rewritten.\n" \
435 		"This is enough content for the file to be rewritten.\n" \
436 		"This is enough content for the file to be rewritten.\n" \
437 		"This is enough content for the file to be rewritten.\n" \
438 		"This is enough content for the file to be rewritten.\n" \
439 		"This is enough content for the file to be rewritten.\n" \
440 		"This is enough content for the file to be rewritten.\n" \
441 		"This is enough content for the file to be rewritten.\n" \
442 		"This is enough content for the file to be rewritten.\n");
443 
444 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
445 	check_status(statuslist, expected, 1);
446 	git_status_list_free(statuslist);
447 
448 	git_index_free(index);
449 }
450 
test_status_renames__both_casechange_one(void)451 void test_status_renames__both_casechange_one(void)
452 {
453 	git_index *index;
454 	git_status_list *statuslist;
455 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
456 	int index_caps;
457 	struct status_entry expected_icase[] = {
458 		{ GIT_STATUS_INDEX_RENAMED,
459 		  "ikeepsix.txt", "IKeepSix.txt" },
460 	};
461 	struct status_entry expected_case[] = {
462 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
463 		  "ikeepsix.txt", "IKEEPSIX.txt" },
464 	};
465 
466 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
467 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
468 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
469 
470 	cl_git_pass(git_repository_index(&index, g_repo));
471 	index_caps = git_index_caps(index);
472 
473 	rename_file(g_repo, "ikeepsix.txt", "IKeepSix.txt");
474 
475 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
476 	cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt"));
477 	cl_git_pass(git_index_write(index));
478 
479 	/* on a case-insensitive file system, this change won't matter.
480 	 * on a case-sensitive one, it will.
481 	 */
482 	rename_file(g_repo, "IKeepSix.txt", "IKEEPSIX.txt");
483 
484 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
485 
486 	check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ?
487 		expected_icase : expected_case, 1);
488 
489 	git_status_list_free(statuslist);
490 
491 	git_index_free(index);
492 }
493 
test_status_renames__both_casechange_two(void)494 void test_status_renames__both_casechange_two(void)
495 {
496 	git_index *index;
497 	git_status_list *statuslist;
498 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
499 	int index_caps;
500 	struct status_entry expected_icase[] = {
501 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
502 		  GIT_STATUS_WT_MODIFIED,
503 		  "ikeepsix.txt", "IKeepSix.txt" },
504 		{ GIT_STATUS_INDEX_MODIFIED,
505 		  "sixserving.txt", "sixserving.txt" },
506 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_MODIFIED,
507 		  "songof7cities.txt", "songof7.txt" },
508 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
509 		  "untimely.txt", "untimeliest.txt" }
510 	};
511 	struct status_entry expected_case[] = {
512 		{ GIT_STATUS_INDEX_RENAMED |
513 		  GIT_STATUS_WT_MODIFIED | GIT_STATUS_WT_RENAMED,
514 		  "songof7cities.txt", "SONGOF7.txt" },
515 		{ GIT_STATUS_INDEX_MODIFIED | GIT_STATUS_WT_RENAMED,
516 		  "sixserving.txt", "SixServing.txt" },
517 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_INDEX_MODIFIED |
518 		  GIT_STATUS_WT_RENAMED | GIT_STATUS_WT_MODIFIED,
519 		  "ikeepsix.txt", "ikeepsix.txt" },
520 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED,
521 		  "untimely.txt", "untimeliest.txt" }
522 	};
523 
524 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
525 	opts.flags |= GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX;
526 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
527 
528 	cl_git_pass(git_repository_index(&index, g_repo));
529 	index_caps = git_index_caps(index);
530 
531 	rename_and_edit_file(g_repo, "ikeepsix.txt", "IKeepSix.txt");
532 	rename_and_edit_file(g_repo, "sixserving.txt", "sixserving.txt");
533 	rename_file(g_repo, "songof7cities.txt", "songof7.txt");
534 	rename_file(g_repo, "untimely.txt", "untimelier.txt");
535 
536 	cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
537 	cl_git_pass(git_index_remove_bypath(index, "sixserving.txt"));
538 	cl_git_pass(git_index_remove_bypath(index, "songof7cities.txt"));
539 	cl_git_pass(git_index_remove_bypath(index, "untimely.txt"));
540 	cl_git_pass(git_index_add_bypath(index, "IKeepSix.txt"));
541 	cl_git_pass(git_index_add_bypath(index, "sixserving.txt"));
542 	cl_git_pass(git_index_add_bypath(index, "songof7.txt"));
543 	cl_git_pass(git_index_add_bypath(index, "untimelier.txt"));
544 	cl_git_pass(git_index_write(index));
545 
546 	rename_and_edit_file(g_repo, "IKeepSix.txt", "ikeepsix.txt");
547 	rename_file(g_repo, "sixserving.txt", "SixServing.txt");
548 	rename_and_edit_file(g_repo, "songof7.txt", "SONGOF7.txt");
549 	rename_file(g_repo, "untimelier.txt", "untimeliest.txt");
550 
551 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
552 
553 	check_status(statuslist, (index_caps & GIT_INDEX_CAPABILITY_IGNORE_CASE) ?
554 		expected_icase : expected_case, 4);
555 
556 	git_status_list_free(statuslist);
557 
558 	git_index_free(index);
559 }
560 
test_status_renames__zero_byte_file_does_not_fail(void)561 void test_status_renames__zero_byte_file_does_not_fail(void)
562 {
563 	git_status_list *statuslist;
564 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
565 
566 	struct status_entry expected[] = {
567 		{ GIT_STATUS_WT_DELETED, "ikeepsix.txt", "ikeepsix.txt" },
568 		{ GIT_STATUS_WT_NEW, "zerobyte.txt", "zerobyte.txt" },
569 	};
570 
571 	opts.flags |= GIT_STATUS_OPT_RENAMES_FROM_REWRITES |
572 		GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
573 		GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR |
574 		GIT_STATUS_OPT_INCLUDE_IGNORED |
575 		GIT_STATUS_OPT_INCLUDE_UNTRACKED |
576 		GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS |
577 		GIT_STATUS_SHOW_INDEX_AND_WORKDIR |
578 		GIT_STATUS_OPT_RECURSE_IGNORED_DIRS;
579 
580 	p_unlink("renames/ikeepsix.txt");
581 	cl_git_mkfile("renames/zerobyte.txt", "");
582 
583 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
584 	check_status(statuslist, expected, 2);
585 	git_status_list_free(statuslist);
586 }
587 
588 #ifdef GIT_USE_ICONV
589 static char *nfc = "\xC3\x85\x73\x74\x72\xC3\xB6\x6D";
590 static char *nfd = "\x41\xCC\x8A\x73\x74\x72\x6F\xCC\x88\x6D";
591 #endif
592 
593 /*
594  * Create a file in NFD (canonically decomposed) format.  Ensure
595  * that when core.precomposeunicode is false that we return paths
596  * in NFD, but when core.precomposeunicode is true, then we
597  * return paths precomposed (in NFC).
598  */
test_status_renames__precomposed_unicode_rename(void)599 void test_status_renames__precomposed_unicode_rename(void)
600 {
601 #ifdef GIT_USE_ICONV
602 	git_status_list *statuslist;
603 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
604 	struct status_entry expected0[] = {
605 		{ GIT_STATUS_WT_NEW, nfd, NULL },
606 		{ GIT_STATUS_WT_DELETED, "sixserving.txt", NULL },
607 	};
608 	struct status_entry expected1[] = {
609 		{ GIT_STATUS_WT_RENAMED, "sixserving.txt", nfd },
610 	};
611 	struct status_entry expected2[] = {
612 		{ GIT_STATUS_WT_DELETED, "sixserving.txt", NULL },
613 		{ GIT_STATUS_WT_NEW, nfc, NULL },
614 	};
615 	struct status_entry expected3[] = {
616 		{ GIT_STATUS_WT_RENAMED, "sixserving.txt", nfc },
617 	};
618 
619 	rename_file(g_repo, "sixserving.txt", nfd);
620 
621 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED;
622 
623 	cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
624 
625 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
626 	check_status(statuslist, expected0, ARRAY_SIZE(expected0));
627 	git_status_list_free(statuslist);
628 
629 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
630 
631 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
632 	check_status(statuslist, expected1, ARRAY_SIZE(expected1));
633 	git_status_list_free(statuslist);
634 
635 	cl_repo_set_bool(g_repo, "core.precomposeunicode", true);
636 
637 	opts.flags &= ~GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
638 
639 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
640 	check_status(statuslist, expected2, ARRAY_SIZE(expected2));
641 	git_status_list_free(statuslist);
642 
643 	opts.flags |= GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
644 
645 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
646 	check_status(statuslist, expected3, ARRAY_SIZE(expected3));
647 	git_status_list_free(statuslist);
648 #endif
649 }
650 
test_status_renames__precomposed_unicode_toggle_is_rename(void)651 void test_status_renames__precomposed_unicode_toggle_is_rename(void)
652 {
653 #ifdef GIT_USE_ICONV
654 	git_status_list *statuslist;
655 	git_status_options opts = GIT_STATUS_OPTIONS_INIT;
656 	struct status_entry expected0[] = {
657 		{ GIT_STATUS_INDEX_RENAMED, "ikeepsix.txt", nfd },
658 	};
659 	struct status_entry expected1[] = {
660 		{ GIT_STATUS_WT_RENAMED, nfd, nfc },
661 	};
662 	struct status_entry expected2[] = {
663 		{ GIT_STATUS_INDEX_RENAMED, nfd, nfc },
664 	};
665 	struct status_entry expected3[] = {
666 		{ GIT_STATUS_INDEX_RENAMED | GIT_STATUS_WT_RENAMED, nfd, nfd },
667 	};
668 
669 	cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
670 	rename_file(g_repo, "ikeepsix.txt", nfd);
671 
672 	{
673 		git_index *index;
674 		cl_git_pass(git_repository_index(&index, g_repo));
675 		cl_git_pass(git_index_remove_bypath(index, "ikeepsix.txt"));
676 		cl_git_pass(git_index_add_bypath(index, nfd));
677 		cl_git_pass(git_index_write(index));
678 		git_index_free(index);
679 	}
680 
681 	opts.flags |= GIT_STATUS_OPT_INCLUDE_UNTRACKED |
682 		GIT_STATUS_OPT_RENAMES_HEAD_TO_INDEX |
683 		GIT_STATUS_OPT_RENAMES_INDEX_TO_WORKDIR;
684 
685 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
686 	check_status(statuslist, expected0, ARRAY_SIZE(expected0));
687 	git_status_list_free(statuslist);
688 
689 	cl_repo_commit_from_index(NULL, g_repo, NULL, 0, "commit nfd");
690 
691 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
692 	cl_assert_equal_sz(0, git_status_list_entrycount(statuslist));
693 	git_status_list_free(statuslist);
694 
695 	cl_repo_set_bool(g_repo, "core.precomposeunicode", true);
696 
697 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
698 	check_status(statuslist, expected1, ARRAY_SIZE(expected1));
699 	git_status_list_free(statuslist);
700 
701 	{
702 		git_index *index;
703 		cl_git_pass(git_repository_index(&index, g_repo));
704 		cl_git_pass(git_index_remove_bypath(index, nfd));
705 		cl_git_pass(git_index_add_bypath(index, nfc));
706 		cl_git_pass(git_index_write(index));
707 		git_index_free(index);
708 	}
709 
710 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
711 	check_status(statuslist, expected2, ARRAY_SIZE(expected2));
712 	git_status_list_free(statuslist);
713 
714 	cl_repo_set_bool(g_repo, "core.precomposeunicode", false);
715 
716 	cl_git_pass(git_status_list_new(&statuslist, g_repo, &opts));
717 	check_status(statuslist, expected3, ARRAY_SIZE(expected3));
718 	git_status_list_free(statuslist);
719 #endif
720 }
721 
722