git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH/RFC] File commited with CRLF should roundtrip diff and apply

From
tboegi@web.de <tboegi@web.de>
Date
Aug 12, 2017, 14:56 UTC
Message-ID
<20170812145625.31560-1-tboegi@web.de>
In-Reply-To
<xmqq4ltpsn42.fsf@gitster.mtv.corp.google.com>
From: Torsten Bögershausen <tboegi@web.de>

When a file had been commited with CRLF and core.autocrlf is true, the following does not roundtrip, `git apply` fails:

printf "Added line\r\n" >>file && git diff >patch && git checkout -- . && git apply patch

Before applying the patch, the file from working tree is converted into the index format (clean filter, CRLF conversion, ...) Here, when commited with CRLF, the line endings should not be converted.

Analyze the patch if there is any context line with CRLF, or if any line with CRLF is to be removed.

If yes, the new flag has_crlf is set in "struct patch", and two things
will happen:
- read_old_data() will not convert CRLF into LF by calling
  convert_to_git(..., SAFE_CRLF_KEEP_CRLF);
- The WS_CR_AT_EOL bit is set in the "white space rule",
  CRLF are no longer treated as white space.
Thanks to Junio C Hamano, his input became the base for t4140.
Reported-by: Anthony Sottile <asottile@umich.edu>
Signed-off-by: Torsten Bögershausen <tboegi@web.de>
---
 apply.c               | 37 ++++++++++++++++++++++++++++---------
 apply.h               |  4 ++++
 t/t4140-apply-CRLF.sh | 46 ++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 78 insertions(+), 9 deletions(-)
 create mode 100755 t/t4140-apply-CRLF.sh
diff --git a/apply.c b/apply.c
index f2d599141d..63455cd65f 100644
--- a/apply.c
+++ b/apply.c
@@ -220,6 +220,7 @@ struct patch {
 	unsigned int recount:1;
 	unsigned int conflicted_threeway:1;
 	unsigned int direct_to_threeway:1;
+	unsigned int has_crlf:1;
 	struct fragment *fragments;
 	char *result;
 	size_t resultsize;
@@ -1662,6 +1663,17 @@ static void check_whitespace(struct apply_state *state,
 	record_ws_error(state, result, line + 1, len - 2, state->linenr);
 }
 
+/* Check if the patch has context lines with CRLF or
+   the patch wants to remove lines with CRLF */
+static void check_old_for_crlf(struct patch *patch, const char *line, int len)
+{
+	if (len >= 2 && line[len-1] == '\n' && line[len-2] == '\r') {
+		patch->ws_rule |= WS_CR_AT_EOL;
+		patch->has_crlf = 1;
+	}
+}
+
+
 /*
  * Parse a unified diff. Note that this really needs to parse each
  * fragment separately, since the only way to know the difference
@@ -1712,11 +1724,13 @@ static int parse_fragment(struct apply_state *state,
 			if (!deleted && !added)
 				leading++;
 			trailing++;
+			check_old_for_crlf(patch, line, len);
 			if (!state->apply_in_reverse &&
 			    state->ws_error_action == correct_ws_error)
 				check_whitespace(state, line, len, patch->ws_rule);
 			break;
 		case '-':
+			check_old_for_crlf(patch, line, len);
 			if (state->apply_in_reverse &&
 			    state->ws_error_action != nowarn_ws_error)
 				check_whitespace(state, line, len, patch->ws_rule);
@@ -2268,8 +2282,10 @@ static void show_stats(struct apply_state *state, struct patch *patch)
 		add, pluses, del, minuses);
 }
 
-static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
+static int read_old_data(struct stat *st, const char *path, struct strbuf *buf, int flags)
 {
+	enum safe_crlf safe_crlf = flags & APPLY_FLAGS_CR_AT_EOL ?
+		SAFE_CRLF_KEEP_CRLF : SAFE_CRLF_FALSE;
 	switch (st->st_mode & S_IFMT) {
 	case S_IFLNK:
 		if (strbuf_readlink(buf, path, st->st_size) < 0)
@@ -2278,7 +2294,7 @@ static int read_old_data(struct stat *st, const char *path, struct strbuf *buf)
 	case S_IFREG:
 		if (strbuf_read_file(buf, path, st->st_size) != st->st_size)
 			return error(_("unable to open or read %s"), path);
-		convert_to_git(&the_index, path, buf->buf, buf->len, buf, 0);
+		convert_to_git(&the_index, path, buf->buf, buf->len, buf, safe_crlf);
 		return 0;
 	default:
 		return -1;
@@ -3385,7 +3401,8 @@ static int load_patch_target(struct apply_state *state,
 			     const struct cache_entry *ce,
 			     struct stat *st,
 			     const char *name,
-			     unsigned expected_mode)
+			     unsigned expected_mode,
+			     int flags)
 {
 	if (state->cached || state->check_index) {
 		if (read_file_or_gitlink(ce, buf))
@@ -3399,7 +3416,7 @@ static int load_patch_target(struct apply_state *state,
 		} else if (has_symlink_leading_path(name, strlen(name))) {
 			return error(_("reading from '%s' beyond a symbolic link"), name);
 		} else {
-			if (read_old_data(st, name, buf))
+			if (read_old_data(st, name, buf, flags))
 				return error(_("failed to read %s"), name);
 		}
 	}
@@ -3423,6 +3440,7 @@ static int load_preimage(struct apply_state *state,
 	char *img;
 	struct patch *previous;
 	int status;
+	int flags = patch->has_crlf ? APPLY_FLAGS_CR_AT_EOL : 0;
 
 	previous = previous_patch(state, patch, &status);
 	if (status)
@@ -3433,7 +3451,7 @@ static int load_preimage(struct apply_state *state,
 		strbuf_add(&buf, previous->result, previous->resultsize);
 	} else {
 		status = load_patch_target(state, &buf, ce, st,
-					   patch->old_name, patch->old_mode);
+					   patch->old_name, patch->old_mode, flags);
 		if (status < 0)
 			return status;
 		else if (status == SUBMODULE_PATCH_WITHOUT_INDEX) {
@@ -3493,7 +3511,8 @@ static int three_way_merge(struct image *image,
  */
 static int load_current(struct apply_state *state,
 			struct image *image,
-			struct patch *patch)
+			struct patch *patch,
+			int flags)
 {
 	struct strbuf buf = STRBUF_INIT;
 	int status, pos;
@@ -3520,7 +3539,7 @@ static int load_current(struct apply_state *state,
 	if (verify_index_match(ce, &st))
 		return error(_("%s: does not match index"), name);
 
-	status = load_patch_target(state, &buf, ce, &st, name, mode);
+	status = load_patch_target(state, &buf, ce, &st, name, mode, flags);
 	if (status < 0)
 		return status;
 	else if (status)
@@ -3571,7 +3590,8 @@ static int try_threeway(struct apply_state *state,
 
 	/* our_oid is ours */
 	if (patch->is_new) {
-		if (load_current(state, &tmp_image, patch))
+		int flags = 0;
+		if (load_current(state, &tmp_image, patch, flags))
 			return error(_("cannot read the current contents of '%s'"),
 				     patch->new_name);
 	} else {
@@ -3617,7 +3637,6 @@ static int apply_data(struct apply_state *state, struct patch *patch,
 		      struct stat *st, const struct cache_entry *ce)
 {
 	struct image image;
-
 	if (load_preimage(state, &image, patch, st, ce) < 0)
 		return -1;
 
diff --git a/apply.h b/apply.h
index b3d6783d55..192140280f 100644
--- a/apply.h
+++ b/apply.h
@@ -33,9 +33,13 @@ enum apply_verbosity {
 #define APPLY_SYMLINK_GOES_AWAY 01
 #define APPLY_SYMLINK_IN_RESULT 02
 
+
+#define APPLY_FLAGS_CR_AT_EOL   (1<<0)
+
 struct apply_state {
 	const char *prefix;
 	int prefix_length;
+	int flags;
 
 	/* These are lock_file related */
 	struct lock_file *lock_file;
diff --git a/t/t4140-apply-CRLF.sh b/t/t4140-apply-CRLF.sh
new file mode 100755
index 0000000000..fd528daabd
--- /dev/null
+++ b/t/t4140-apply-CRLF.sh
@@ -0,0 +1,46 @@
+#!/bin/sh
+
+test_description='CRLF diff and apply'
+
+. ./test-lib.sh
+
+test_expect_success setup '
+	mkdir upstream &&
+	(
+		cd upstream &&
+		git init &&
+		git config core.autocrlf false &&
+		>.gitignore &&
+		git add . &&
+		git commit -m gitignore &&
+		printf "F1\r\n" >FileCRLF &&
+		git add . &&
+		git commit -m initial &&
+		git diff HEAD^1 HEAD -- >../patch1
+	) &&
+	git config core.autocrlf true
+'
+
+test_expect_success 'apply patches Replace lines' '
+	(
+		cd upstream &&
+		printf "F11\r\nF12\r\n" >FileCRLF &&
+		git diff  >../patch2Replace
+	) &&
+	git apply patch1 &&
+	git apply patch2Replace
+'
+
+test_expect_success 'apply patches Add lines' '
+	(
+		cd upstream &&
+		git checkout FileCRLF &&
+		printf "F2\r\n" >>FileCRLF &&
+		git diff  >../patch2Add
+	) &&
+	rm -f FileCRLF &&
+	git apply patch1 &&
+	git apply patch2Add
+'
+
+test_done
-- 
2.14.1.145.gb3622a4ee9
Previous: tboegi@web.deNext: Junio C Hamano
Message 14 of 36 in “core.autocrlf=true causes `git apply` to fail on patch generated with `git diff-index HEAD --patch`”
  1. Anthony SottileAug 1, 2017
  2. Torsten BögershausenAug 1, 2017
  3. Anthony SottileAug 1, 2017
  4. Torsten BögershausenAug 2, 2017
  5. 1/1 correct apply for files commited with CRLFtboegi@web.de, Aug 2, 2017
  6. Junio C HamanoAug 2, 2017
  7. Torsten BögershausenAug 4, 2017
  8. Junio C HamanoAug 4, 2017
  9. Junio C HamanoAug 4, 2017
  10. Junio C HamanoAug 2, 2017
  11. Torsten BögershausenAug 12, 2017
  12. Torsten BögershausenAug 12, 2017
  13. convert: Add SAFE_CRLF_KEEP_CRLFtboegi@web.de, Aug 12, 2017
  14. File commited with CRLF should roundtrip diff and applytboegi@web.de, Aug 12, 2017
  15. Junio C HamanoAug 14, 2017
  16. 1/2 convert: Add SAFE_CRLF_KEEP_CRLFtboegi@web.de, Aug 17, 2017
  17. 2/2 File commited with CRLF should roundtrip diff and applytboegi@web.de, Aug 17, 2017
  18. Junio C HamanoAug 17, 2017
  19. 1/2 convert: Add SAFE_CRLF_KEEP_CRLFtboegi@web.de, Aug 17, 2017
  20. 2/2 File commited with CRLF should roundtrip diff and applytboegi@web.de, Aug 17, 2017
  21. Junio C HamanoAug 17, 2017
  22. Junio C HamanoAug 17, 2017
  23. 1/2 convert: Add SAFE_CRLF_KEEP_CRLFtboegi@web.de, Aug 19, 2017
  24. 2/2 File commited with CRLF should roundtrip diff and applytboegi@web.de, Aug 19, 2017
  25. 1/2 convert: Add SAFE_CRLF_KEEP_CRLFtboegi@web.de, Aug 13, 2017
  26. 2/2 File commited with CRLF should roundtrip diff and applytboegi@web.de, Aug 13, 2017
  27. 3/2 apply: remove unused member apply_state.flagsJunio C Hamano, Aug 16, 2017
  28. Junio C HamanoAug 16, 2017
  29. 4/2 apply: only pay attention to CRLF in the preimageJunio C Hamano, Aug 16, 2017
  30. 5/2 apply: localize the CRLF business to read_old_data()Junio C Hamano, Aug 16, 2017
  31. 6/2 apply: clarify read_old_data() is about no-index caseJunio C Hamano, Aug 16, 2017
  32. Torsten BögershausenAug 17, 2017
  33. Junio C HamanoAug 17, 2017
  34. Junio C HamanoAug 17, 2017
  35. Torsten BögershausenAug 17, 2017
  36. Junio C HamanoAug 17, 2017

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.