{"thread":{"id":"24132","subject":"[PATCH/RFC v2] ll-merge: Normalize files before merging","startedAt":"2010-06-16T21:18:35Z","lastAt":"2010-06-16T21:18:35Z","messageCount":1,"participants":["Eyvind Bernhardsen"],"isPatch":true,"patchVersion":2,"patchTotal":null},"messages":[{"id":"143835","messageId":"1276723115-2869-1-git-send-email-eyvind.bernhardsen@gmail.com","threadId":"24132","inReplyTo":null,"subject":"[PATCH/RFC v2] ll-merge: Normalize files before merging","fromName":"Eyvind Bernhardsen","fromEmail":"eyvind.bernhardsen@gmail.com","sentAt":"2010-06-16T21:18:35Z","receivedAt":"2010-06-16T21:18:35Z","isPatch":true,"sender":{"key":"eyvind.bernhardsen@gmail.com","avatar":"https://avatars.githubusercontent.com/u/106762?v=4"},"body":"Currently, merging across changes in line ending normalization is\npainful since all lines containing CRLF will conflict.\n\nFix ll-merge so that the \"base\", \"theirs\" and \"ours\" files are passed\nthrough convert_to_worktree() and convert_to_git() before a three-way\nmerge.\n\nThis prevents differences that can be normalized away from blocking an\nautomatic merge, and makes real conflicts show up instead of being lost\nin the noise.\n\nSigned-off-by: Eyvind Bernhardsen <eyvind.bernhardsen@gmail.com>\n---\nSecond stab at having ll-merge automatically handle merges across text\nconversion boundaries.  The previous implementation ran convert_to_git()\non already normalized data, which is potentially dangerous.  This\nversion runs convert_to_worktree() first.\n\nAs promised, I did some benchmarks this time.  My repository has 55617\nfiles, all normalized with \"* text=auto\".  The branch to be merged\nchanges 16157 of them and is based on an old, pre-normalization commit.\n\nAn uncompleted merge doesn't make for a fair performance comparison, so\nI ended up rebasing the topic branch on top of the first normalized\ncommit on master to compare performance.  I ran each merge 10 times and\naveraged the user time for each run.\n\nWith the simplified merge there was no measurable difference between\nrunning with or without normalization.  Adding core.eol=crlf when\nnormalizing cost about 5%.\n\nA remaining problem is that delete/modify conflicts where the \"modify\"\nis just normalization have to be resolved manually.  This could be fixed\nby normalizing files when there is a d/m conflict and then comparing the\nnormalized sha1s.\n\n- Eyvind\n\n ll-merge.c                 |   17 +++++++++++++\n t/t6038-merge-text-auto.sh |   54 ++++++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 71 insertions(+), 0 deletions(-)\n create mode 100755 t/t6038-merge-text-auto.sh\n\ndiff --git a/ll-merge.c b/ll-merge.c\nindex f9b3d85..264337e 100644\n--- a/ll-merge.c\n+++ b/ll-merge.c\n@@ -321,6 +321,20 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2]\n \treturn git_checkattr(path, 2, check);\n }\n \n+static void normalize_file(mmfile_t *mm, const char *path) {\n+\tstruct strbuf strbuf = STRBUF_INIT;\n+\tif (convert_to_working_tree(path, mm->ptr, mm->size, &strbuf)) {\n+\t\tfree(mm->ptr);\n+\t\tmm->size = strbuf.len;\n+\t\tmm->ptr = strbuf_detach(&strbuf, NULL);\n+\t}\n+\tif (convert_to_git(path, mm->ptr, mm->size, &strbuf, 0)) {\n+\t\tfree(mm->ptr);\n+\t\tmm->size = strbuf.len;\n+\t\tmm->ptr = strbuf_detach(&strbuf, NULL);\n+\t}\n+}\n+\n int ll_merge(mmbuffer_t *result_buf,\n \t     const char *path,\n \t     mmfile_t *ancestor, const char *ancestor_label,\n@@ -334,6 +348,9 @@ int ll_merge(mmbuffer_t *result_buf,\n \tconst struct ll_merge_driver *driver;\n \tint virtual_ancestor = flag & 01;\n \n+\tnormalize_file(ancestor, path);\n+\tnormalize_file(ours, path);\n+\tnormalize_file(theirs, path);\n \tif (!git_path_check_merge(path, check)) {\n \t\tll_driver_name = check[0].value;\n \t\tif (check[1].value) {\ndiff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh\nnew file mode 100755\nindex 0000000..6af2c41\n--- /dev/null\n+++ b/t/t6038-merge-text-auto.sh\n@@ -0,0 +1,54 @@\n+#!/bin/sh\n+\n+test_description='CRLF merge conflict across text=auto change'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\tgit config core.autocrlf false &&\n+\techo first line | append_cr >file &&\n+\tgit add file &&\n+\tgit commit -m \"Initial\" &&\n+\tgit tag initial &&\n+\tgit branch side &&\n+\techo \"* text=auto\" >.gitattributes &&\n+\tgit add .gitattributes &&\n+\techo same line | append_cr >>file &&\n+\tgit add file &&\n+\tgit commit -m \"add line from a\" &&\n+\tgit tag a &&\n+\tgit rm .gitattributes &&\n+\trm file &&\n+\tgit checkout file &&\n+\tgit commit -m \"remove .gitattributes\" &&\n+\tgit tag c &&\n+\tgit checkout side &&\n+\techo same line | append_cr >>file &&\n+\tgit commit -m \"add line from b\" file &&\n+\tgit tag b &&\n+\tgit checkout master\n+'\n+\n+test_expect_success 'Check merging after setting text=auto' '\n+\tgit reset --hard a &&\n+\tgit merge b &&\n+\tcat file | remove_cr >file.temp &&\n+\ttest_cmp file file.temp\n+'\n+\n+test_expect_success 'Check merging addition of text=auto' '\n+\tgit reset --hard b &&\n+\tgit merge a &&\n+\tcat file | remove_cr >file.temp &&\n+\ttest_cmp file file.temp\n+'\n+\n+# Not sure if this deserves to be fixed\n+test_expect_failure 'Check merging removal of text=auto' '\n+\tgit reset --hard b &&\n+\tgit merge c &&\n+\tcat file | remove_cr >file.temp &&\n+\ttest_cmp file file.temp\n+'\n+\n+test_done\n-- \n1.7.1.5.g0ed10.dirty\n"}]}