diff --git a/Documentation/gitattributes.txt b/Documentation/gitattributes.txt
index 564586b..b110082 100644
--- a/Documentation/gitattributes.txt
+++ b/Documentation/gitattributes.txt
@@ -317,6 +317,18 @@ command is "cat").
smudge = cat
------------------------
+For best results, `clean` and `smudge` commands should produce output
+that is not dependent on the corresponding command having been run.
+That is, `clean` should produce identical output whether its input has
+been run through `smudge` or not, and `smudge` should not rely on its
+input having been run through `clean`. See the section on merging
+below for a rationale.
+
+The example "indent" filter is well-behaved in this regard: it will
+accept input that is already correctly indented without modifying it.
+In this case, the lack of a smudge filter means that the clean filter
+_must_ accept its own output without modifying it.
+
Interaction between checkin/checkout attributes
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
@@ -331,6 +343,21 @@ In the check-out codepath, the blob content is first converted
with `text`, and then `ident` and fed to `filter`.
+Merging branches with differing checkin/checkout attributes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+To prevent unnecessary merge conflicts, git runs a virtual check-out
+and check-in of all three stages of a file when resolving a three-way
+merge. This prevents changes caused by check-in conversion from
+causing spurious merge conflicts when a converted file is merged with
+an unconverted file.
+
+This strategy will break down if a `smudge` filter relies on its input
+having been processed by the corresponding `clean` filter or vice
+versa. Such filters may otherwise work well, but will prevent
+automatic merging.
+
+
Generating diff text
~~~~~~~~~~~~~~~~~~~~
diff --git a/cache.h b/cache.h
index ff4a7c2..5db89f9 100644
--- a/cache.h
+++ b/cache.h
@@ -1043,6 +1043,7 @@ extern void trace_argv_printf(const char **argv, const char *format, ...);
extern int convert_to_git(const char *path, const char *src, size_t len,
struct strbuf *dst, enum safe_crlf checksafe);
extern int convert_to_working_tree(const char *path, const char *src, size_t len, struct strbuf *dst);
+extern int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst);
/* add */
/*diff --git a/convert.c b/convert.c
index e41a31e..0203be8 100644
--- a/convert.c
+++ b/convert.c
@@ -93,7 +93,8 @@ static int is_binary(unsigned long size, struct text_stat *stats)
return 0;
}
-static enum eol determine_output_conversion(enum action action) {
+static enum eol determine_output_conversion(enum action action)
+{
switch (action) {
case CRLF_BINARY:
return EOL_UNSET;@@ -693,7 +694,8 @@ static int git_path_check_ident(const char *path, struct git_attr_check *check)
return !!ATTR_TRUE(value);
}
-enum action determine_action(enum action text_attr, enum eol eol_attr) {
+static enum action determine_action(enum action text_attr, enum eol eol_attr)
+{
if (text_attr == CRLF_BINARY)
return CRLF_BINARY;
if (eol_attr == EOL_LF)@@ -773,3 +775,13 @@ int convert_to_working_tree(const char *path, const char *src, size_t len, struc
}
return ret | apply_filter(path, src, len, dst, filter);
}
+
+int renormalize_buffer(const char *path, const char *src, size_t len, struct strbuf *dst)
+{
+ int ret = convert_to_working_tree(path, src, len, dst);
+ if (ret) {
+ src = dst->buf;
+ len = dst->len;
+ }
+ return ret | convert_to_git(path, src, len, dst, 0);
+}diff --git a/ll-merge.c b/ll-merge.c
index 3764a1a..28c6f54 100644
--- a/ll-merge.c
+++ b/ll-merge.c
@@ -321,6 +321,16 @@ static int git_path_check_merge(const char *path, struct git_attr_check check[2]
return git_checkattr(path, 2, check);
}
+static void normalize_file(mmfile_t *mm, const char *path)
+{
+ struct strbuf strbuf = STRBUF_INIT;
+ if (renormalize_buffer(path, mm->ptr, mm->size, &strbuf)) {
+ free(mm->ptr);
+ mm->size = strbuf.len;
+ mm->ptr = strbuf_detach(&strbuf, NULL);
+ }
+}
+
int ll_merge(mmbuffer_t *result_buf,
const char *path,
mmfile_t *ancestor, const char *ancestor_label,@@ -334,6 +344,9 @@ int ll_merge(mmbuffer_t *result_buf,
const struct ll_merge_driver *driver;
int virtual_ancestor = flag & 01;
+ normalize_file(ancestor, path);
+ normalize_file(ours, path);
+ normalize_file(theirs, path);
if (!git_path_check_merge(path, check)) {
ll_driver_name = check[0].value;
if (check[1].value) {diff --git a/t/t6038-merge-text-auto.sh b/t/t6038-merge-text-auto.sh
new file mode 100755
index 0000000..44e6003
--- /dev/null
+++ b/t/t6038-merge-text-auto.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+
+test_description='CRLF merge conflict across text=auto change'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+ git config core.autocrlf false &&
+ echo first line | append_cr >file &&
+ git add file &&
+ git commit -m "Initial" &&
+ git tag initial &&
+ git branch side &&
+ echo "* text=auto" >.gitattributes &&
+ touch file &&
+ git add .gitattributes file &&
+ git commit -m "normalize file" &&
+ echo same line | append_cr >>file &&
+ git add file &&
+ git commit -m "add line from a" &&
+ git tag a &&
+ git rm .gitattributes &&
+ rm file &&
+ git checkout file &&
+ git commit -m "remove .gitattributes" &&
+ git tag c &&
+ git checkout side &&
+ echo same line | append_cr >>file &&
+ git commit -m "add line from b" file &&
+ git tag b &&
+ git checkout master
+'
+
+test_expect_success 'Check merging after setting text=auto' '
+ git reset --hard a &&
+ git merge b &&
+ cat file | remove_cr >file.temp &&
+ test_cmp file file.temp
+'
+
+test_expect_success 'Check merging addition of text=auto' '
+ git reset --hard b &&
+ git merge a &&
+ cat file | remove_cr >file.temp &&
+ test_cmp file file.temp
+'
+
+test_expect_failure 'Test delete/normalize conflict' '
+ git checkout side &&
+ git reset --hard initial &&
+ git rm file &&
+ git commit -m "remove file" &&
+ git checkout master &&
+ git reset --hard a^ &&
+ git merge side
+'
+
+test_done
--
1.7.1.575.g383de