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