Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 1a79cd9

Browse files
author
Edward Thomson
committed
patch: show copy information for identical copies
When showing copy information because we are duplicating contents, for example, when performing a `diff --find-copies-harder -M100 -B100`, then show copy from/to lines in a patch, and do not show context. Ensure that we can also parse such patches.
1 parent 9eb1938 commit 1a79cd9

File tree

6 files changed

+147
-24
lines changed

6 files changed

+147
-24
lines changed

src/diff_print.c

Lines changed: 33 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -331,11 +331,12 @@ static int diff_delta_format_with_paths(
331331
return git_buf_printf(out, template, oldpath, newpath);
332332
}
333333

334-
int diff_delta_format_rename_header(
334+
int diff_delta_format_similarity_header(
335335
git_buf *out,
336336
const git_diff_delta *delta)
337337
{
338338
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
339+
const char *type;
339340
int error = 0;
340341

341342
if (delta->similarity > 100) {
@@ -344,6 +345,13 @@ int diff_delta_format_rename_header(
344345
goto done;
345346
}
346347

348+
if (delta->status == GIT_DELTA_RENAMED)
349+
type = "rename";
350+
else if (delta->status == GIT_DELTA_COPIED)
351+
type = "copy";
352+
else
353+
abort();
354+
347355
if ((error = git_buf_puts(&old_path, delta->old_file.path)) < 0 ||
348356
(error = git_buf_puts(&new_path, delta->new_file.path)) < 0 ||
349357
(error = git_buf_quote(&old_path)) < 0 ||
@@ -352,11 +360,11 @@ int diff_delta_format_rename_header(
352360

353361
git_buf_printf(out,
354362
"similarity index %d%%\n"
355-
"rename from %s\n"
356-
"rename to %s\n",
363+
"%s from %s\n"
364+
"%s to %s\n",
357365
delta->similarity,
358-
old_path.ptr,
359-
new_path.ptr);
366+
type, old_path.ptr,
367+
type, new_path.ptr);
360368

361369
if (git_buf_oom(out))
362370
error = -1;
@@ -368,6 +376,22 @@ int diff_delta_format_rename_header(
368376
return error;
369377
}
370378

379+
static bool delta_is_unchanged(const git_diff_delta *delta)
380+
{
381+
if (git_oid_iszero(&delta->old_file.id) &&
382+
git_oid_iszero(&delta->new_file.id))
383+
return true;
384+
385+
if (delta->old_file.mode == GIT_FILEMODE_COMMIT ||
386+
delta->new_file.mode == GIT_FILEMODE_COMMIT)
387+
return false;
388+
389+
if (git_oid_equal(&delta->old_file.id, &delta->new_file.id))
390+
return true;
391+
392+
return false;
393+
}
394+
371395
int git_diff_delta__format_file_header(
372396
git_buf *out,
373397
const git_diff_delta *delta,
@@ -376,7 +400,7 @@ int git_diff_delta__format_file_header(
376400
int id_strlen)
377401
{
378402
git_buf old_path = GIT_BUF_INIT, new_path = GIT_BUF_INIT;
379-
bool unchanged;
403+
bool unchanged = delta_is_unchanged(delta);
380404
int error = 0;
381405

382406
if (!oldpfx)
@@ -397,14 +421,12 @@ int git_diff_delta__format_file_header(
397421
git_buf_printf(out, "diff --git %s %s\n",
398422
old_path.ptr, new_path.ptr);
399423

400-
if (delta->status == GIT_DELTA_RENAMED) {
401-
if ((error = diff_delta_format_rename_header(out, delta)) < 0)
424+
if (delta->status == GIT_DELTA_RENAMED ||
425+
(delta->status == GIT_DELTA_COPIED && unchanged)) {
426+
if ((error = diff_delta_format_similarity_header(out, delta)) < 0)
402427
goto done;
403428
}
404429

405-
unchanged = (git_oid_iszero(&delta->old_file.id) &&
406-
git_oid_iszero(&delta->new_file.id));
407-
408430
if (!unchanged) {
409431
if ((error = diff_print_oid_range(out, delta, id_strlen)) < 0)
410432
goto done;

src/patch_parse.c

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -310,6 +310,20 @@ static int parse_header_renameto(
310310
return parse_header_rename(&patch->rename_new_path, ctx);
311311
}
312312

313+
static int parse_header_copyfrom(
314+
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
315+
{
316+
patch->base.delta->status = GIT_DELTA_COPIED;
317+
return parse_header_rename(&patch->rename_old_path, ctx);
318+
}
319+
320+
static int parse_header_copyto(
321+
git_patch_parsed *patch, git_patch_parse_ctx *ctx)
322+
{
323+
patch->base.delta->status = GIT_DELTA_COPIED;
324+
return parse_header_rename(&patch->rename_new_path, ctx);
325+
}
326+
313327
static int parse_header_percent(uint16_t *out, git_patch_parse_ctx *ctx)
314328
{
315329
int32_t val;
@@ -375,6 +389,8 @@ static const header_git_op header_git_ops[] = {
375389
{ "rename to ", parse_header_renameto },
376390
{ "rename old ", parse_header_renamefrom },
377391
{ "rename new ", parse_header_renameto },
392+
{ "copy from ", parse_header_copyfrom },
393+
{ "copy to ", parse_header_copyto },
378394
{ "similarity index ", parse_header_similarity },
379395
{ "dissimilarity index ", parse_header_dissimilarity },
380396
};

tests/diff/diff_helpers.c

Lines changed: 47 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -242,18 +242,44 @@ void diff_print_raw(FILE *fp, git_diff *diff)
242242
git_diff_print_callback__to_file_handle, fp ? fp : stderr));
243243
}
244244

245+
static size_t num_modified_deltas(git_diff *diff)
246+
{
247+
const git_diff_delta *delta;
248+
size_t i, cnt = 0;
249+
250+
for (i = 0; i < git_diff_num_deltas(diff); i++) {
251+
delta = git_diff_get_delta(diff, i);
252+
253+
if (delta->status != GIT_DELTA_UNMODIFIED)
254+
cnt++;
255+
}
256+
257+
return cnt;
258+
}
259+
245260
void diff_assert_equal(git_diff *a, git_diff *b)
246261
{
247262
const git_diff_delta *ad, *bd;
248-
size_t i;
263+
size_t i, j;
249264

250265
assert(a && b);
251266

252-
cl_assert_equal_i(git_diff_num_deltas(a), git_diff_num_deltas(b));
267+
cl_assert_equal_i(num_modified_deltas(a), num_modified_deltas(b));
268+
269+
for (i = 0, j = 0;
270+
i < git_diff_num_deltas(a) && j < git_diff_num_deltas(b); ) {
253271

254-
for (i = 0; i < git_diff_num_deltas(a); i++) {
255272
ad = git_diff_get_delta(a, i);
256-
bd = git_diff_get_delta(b, i);
273+
bd = git_diff_get_delta(b, j);
274+
275+
if (ad->status == GIT_DELTA_UNMODIFIED) {
276+
i++;
277+
continue;
278+
}
279+
if (bd->status == GIT_DELTA_UNMODIFIED) {
280+
j++;
281+
continue;
282+
}
257283

258284
cl_assert_equal_i(ad->status, bd->status);
259285
cl_assert_equal_i(ad->flags, bd->flags);
@@ -265,15 +291,26 @@ void diff_assert_equal(git_diff *a, git_diff *b)
265291
* computed deltas will have flags of `VALID_ID` and
266292
* `EXISTS` (parsed deltas will not query the ODB.)
267293
*/
268-
cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id);
269-
cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev);
294+
295+
/* an empty id indicates that it wasn't presented, because
296+
* the diff was identical. (eg, pure rename, mode change only, etc)
297+
*/
298+
if (ad->old_file.id_abbrev && bd->old_file.id_abbrev) {
299+
cl_assert_equal_i(ad->old_file.id_abbrev, bd->old_file.id_abbrev);
300+
cl_assert_equal_oid(&ad->old_file.id, &bd->old_file.id);
301+
cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode);
302+
}
270303
cl_assert_equal_s(ad->old_file.path, bd->old_file.path);
271-
cl_assert_equal_i(ad->old_file.mode, bd->old_file.mode);
272304

273-
cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id);
274-
cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev);
305+
if (ad->new_file.id_abbrev && bd->new_file.id_abbrev) {
306+
cl_assert_equal_oid(&ad->new_file.id, &bd->new_file.id);
307+
cl_assert_equal_i(ad->new_file.id_abbrev, bd->new_file.id_abbrev);
308+
cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode);
309+
}
275310
cl_assert_equal_s(ad->new_file.path, bd->new_file.path);
276-
cl_assert_equal_i(ad->new_file.mode, bd->new_file.mode);
311+
312+
i++;
313+
j++;
277314
}
278315
}
279316

tests/diff/format_email.c

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -351,9 +351,6 @@ void test_diff_format_email__mode_change(void)
351351
"diff --git a/file1.txt.renamed b/file1.txt.renamed\n" \
352352
"old mode 100644\n" \
353353
"new mode 100755\n" \
354-
"index a97157a..a97157a\n" \
355-
"--- a/file1.txt.renamed\n" \
356-
"+++ b/file1.txt.renamed\n" \
357354
"--\n" \
358355
"libgit2 " LIBGIT2_VERSION "\n" \
359356
"\n";

tests/diff/parse.c

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,11 @@ void test_diff_parse__can_parse_generated_diff(void)
139139
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
140140
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
141141
0, GIT_DIFF_FIND_RENAMES);
142+
test_tree_to_tree_computed_to_parsed("renames",
143+
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
144+
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",
145+
GIT_DIFF_INCLUDE_UNMODIFIED,
146+
0);
142147
test_tree_to_tree_computed_to_parsed("renames",
143148
"31e47d8c1fa36d7f8d537b96158e3f024de0a9f2",
144149
"2bc7f351d20b53f1c72c16c4b036e491c478c49a",

tests/diff/rename.c

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1702,3 +1702,49 @@ void test_diff_rename__blank_files_not_renamed_when_not_ignoring_whitespace(void
17021702
expect_files_not_renamed("", "\n\n\n\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
17031703
expect_files_not_renamed("\n\n\n\n", "\r\n\r\n\r\n", GIT_DIFF_FIND_DONT_IGNORE_WHITESPACE);
17041704
}
1705+
1706+
/* test that 100% renames and copies emit the correct patch file
1707+
* git diff --find-copies-harder -M100 -B100 \
1708+
* 31e47d8c1fa36d7f8d537b96158e3f024de0a9f2 \
1709+
* 2bc7f351d20b53f1c72c16c4b036e491c478c49a
1710+
*/
1711+
void test_diff_rename__identical(void)
1712+
{
1713+
const char *old_sha = "31e47d8c1fa36d7f8d537b96158e3f024de0a9f2";
1714+
const char *new_sha = "2bc7f351d20b53f1c72c16c4b036e491c478c49a";
1715+
git_tree *old_tree, *new_tree;
1716+
git_diff *diff;
1717+
git_diff_options diff_opts = GIT_DIFF_OPTIONS_INIT;
1718+
git_diff_find_options find_opts = GIT_DIFF_FIND_OPTIONS_INIT;
1719+
git_buf diff_buf = GIT_BUF_INIT;
1720+
const char *expected =
1721+
"diff --git a/serving.txt b/sixserving.txt\n"
1722+
"similarity index 100%\n"
1723+
"rename from serving.txt\n"
1724+
"rename to sixserving.txt\n"
1725+
"diff --git a/sevencities.txt b/songofseven.txt\n"
1726+
"similarity index 100%\n"
1727+
"copy from sevencities.txt\n"
1728+
"copy to songofseven.txt\n";
1729+
1730+
old_tree = resolve_commit_oid_to_tree(g_repo, old_sha);
1731+
new_tree = resolve_commit_oid_to_tree(g_repo, new_sha);
1732+
1733+
diff_opts.flags |= GIT_DIFF_INCLUDE_UNMODIFIED;
1734+
find_opts.flags = GIT_DIFF_FIND_COPIES_FROM_UNMODIFIED |
1735+
GIT_DIFF_FIND_EXACT_MATCH_ONLY;
1736+
1737+
cl_git_pass(git_diff_tree_to_tree(&diff,
1738+
g_repo, old_tree, new_tree, &diff_opts));
1739+
cl_git_pass(git_diff_find_similar(diff, &find_opts));
1740+
1741+
cl_git_pass(git_diff_to_buf(&diff_buf, diff, GIT_DIFF_FORMAT_PATCH));
1742+
1743+
cl_assert_equal_s(expected, diff_buf.ptr);
1744+
1745+
git_buf_free(&diff_buf);
1746+
git_diff_free(diff);
1747+
git_tree_free(old_tree);
1748+
git_tree_free(new_tree);
1749+
}
1750+

0 commit comments

Comments
 (0)