Commit 3658e81e3499f874dc9f323d4d9127df7e219e12

Russell Belfer 2013-03-25T14:20:07

Move crlf conversion into buf_text This adds crlf/lf conversion functions into buf_text with more efficient implementations that bypass the high level buffer functions. They attempt to minimize the number of reallocations done and they directly write the buffer data as needed if they know that there is enough memory allocated to memcpy data. Tests are added for these new functions. The crlf.c code is updated to use the new functions. Removed the include of buf_text.h from filter.h and just include it more narrowly in the places that need it.

diff --git a/src/blob.c b/src/blob.c
index 3ff141c..c0514fc 100644
--- a/src/blob.c
+++ b/src/blob.c
@@ -12,6 +12,7 @@
 #include "common.h"
 #include "blob.h"
 #include "filter.h"
+#include "buf_text.h"
 
 const void *git_blob_rawcontent(const git_blob *blob)
 {
diff --git a/src/buf_text.c b/src/buf_text.c
index 3a8f442..443454b 100644
--- a/src/buf_text.c
+++ b/src/buf_text.c
@@ -60,6 +60,83 @@ void git_buf_text_unescape(git_buf *buf)
 	buf->size = git__unescape(buf->ptr);
 }
 
+int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src)
+{
+	const char *scan = src->ptr;
+	const char *scan_end = src->ptr + src->size;
+	const char *next = memchr(scan, '\r', src->size);
+	char *out;
+
+	assert(tgt != src);
+
+	if (!next)
+		return GIT_ENOTFOUND;
+
+	/* reduce reallocs while in the loop */
+	if (git_buf_grow(tgt, src->size) < 0)
+		return -1;
+	out = tgt->ptr;
+	tgt->size = 0;
+
+	/* Find the next \r and copy whole chunk up to there to tgt */
+	for (; next; scan = next + 1, next = memchr(scan, '\r', scan_end - scan)) {
+		if (next > scan) {
+			size_t copylen = next - scan;
+			memcpy(out, scan, copylen);
+			out += copylen;
+		}
+
+		/* Do not drop \r unless it is followed by \n */
+		if (next[1] != '\n')
+			*out++ = '\r';
+	}
+
+	/* Copy remaining input into dest */
+	memcpy(out, scan, scan_end - scan + 1); /* +1 for NUL byte */
+	out += (scan_end - scan);
+	tgt->size = out - tgt->ptr;
+
+	return 0;
+}
+
+int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src)
+{
+	const char *start = src->ptr;
+	const char *end = start + src->size;
+	const char *scan = start;
+	const char *next = memchr(scan, '\n', src->size);
+
+	assert(tgt != src);
+
+	if (!next)
+		return GIT_ENOTFOUND;
+
+	/* attempt to reduce reallocs while in the loop */
+	if (git_buf_grow(tgt, src->size + (src->size >> 4) + 1) < 0)
+		return -1;
+	tgt->size = 0;
+
+	for (; next; scan = next + 1, next = memchr(scan, '\n', end - scan)) {
+		size_t copylen = next - scan;
+		/* don't convert existing \r\n to \r\r\n */
+		size_t extralen = (next > start && next[-1] == '\r') ? 1 : 2;
+		size_t needsize = tgt->size + copylen + extralen + 1;
+
+		if (tgt->asize < needsize && git_buf_grow(tgt, needsize) < 0)
+			return -1;
+
+		if (next > scan) {
+			memcpy(tgt->ptr + tgt->size, scan, copylen);
+			tgt->size += copylen;
+		}
+		if (extralen == 2)
+			tgt->ptr[tgt->size++] = '\r';
+		tgt->ptr[tgt->size++] = '\n';
+	}
+
+	return git_buf_put(tgt, scan, end - scan);
+}
+
 int git_buf_text_common_prefix(git_buf *buf, const git_strarray *strings)
 {
 	size_t i;
diff --git a/src/buf_text.h b/src/buf_text.h
index 458ee33..58e4e26 100644
--- a/src/buf_text.h
+++ b/src/buf_text.h
@@ -56,6 +56,20 @@ GIT_INLINE(int) git_buf_text_puts_escape_regex(git_buf *buf, const char *string)
 extern void git_buf_text_unescape(git_buf *buf);
 
 /**
+ * Replace all \r\n with \n (or do nothing if no \r\n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \r\n, -1 on memory error
+ */
+extern int git_buf_text_crlf_to_lf(git_buf *tgt, const git_buf *src);
+
+/**
+ * Replace all \n with \r\n (or do nothing if no \n are found)
+ *
+ * @return 0 on success, GIT_ENOTFOUND if no \n, -1 on memory error
+ */
+extern int git_buf_text_lf_to_crlf(git_buf *tgt, const git_buf *src);
+
+/**
  * Fill buffer with the common prefix of a array of strings
  *
  * Buffer will be set to empty if there is no common prefix
diff --git a/src/checkout.c b/src/checkout.c
index e52649a..5a2698e 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -23,6 +23,7 @@
 #include "blob.h"
 #include "diff.h"
 #include "pathspec.h"
+#include "buf_text.h"
 
 /* See docs/checkout-internals.md for more information */
 
diff --git a/src/crlf.c b/src/crlf.c
index cd6d982..81268da 100644
--- a/src/crlf.c
+++ b/src/crlf.c
@@ -9,6 +9,7 @@
 #include "fileops.h"
 #include "hash.h"
 #include "filter.h"
+#include "buf_text.h"
 #include "repository.h"
 #include "git2/attr.h"
 #include "git2/blob.h"
@@ -105,35 +106,6 @@ static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, con
 	return -1;
 }
 
-static int drop_crlf(git_buf *dest, const git_buf *source)
-{
-	const char *scan = source->ptr, *next;
-	const char *scan_end = git_buf_cstr(source) + git_buf_len(source);
-
-	/* Main scan loop.  Find the next carriage return and copy the
-	 * whole chunk up to that point to the destination buffer.
-	 */
-	while ((next = memchr(scan, '\r', scan_end - scan)) != NULL) {
-		/* copy input up to \r */
-		if (next > scan)
-			git_buf_put(dest, scan, next - scan);
-
-		/* Do not drop \r unless it is followed by \n */
-		if (*(next + 1) != '\n')
-			git_buf_putc(dest, '\r');
-
-		scan = next + 1;
-	}
-
-	/* If there was no \r, then tell the library to skip this filter */
-	if (scan == source->ptr)
-		return -1;
-
-	/* Copy remaining input into dest */
-	git_buf_put(dest, scan, scan_end - scan);
-	return 0;
-}
-
 static int has_cr_in_index(git_filter *self)
 {
 	struct crlf_filter *filter = (struct crlf_filter *)self;
@@ -217,29 +189,7 @@ static int crlf_apply_to_odb(
 	}
 
 	/* Actually drop the carriage returns */
-	return drop_crlf(dest, source);
-}
-
-static int convert_line_endings(git_buf *dest, const git_buf *source, const char *ending)
-{
-	const char *scan = git_buf_cstr(source),
-		*next,
-		*line_end,
-		*scan_end = git_buf_cstr(source) + git_buf_len(source);
-
-	while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) {
-		if (next > scan) {
-			line_end = *(next - 1) == '\r' ? next - 1 : next;
-			git_buf_put(dest, scan, line_end - scan);
-			scan = next + 1;
-		}
-
-		git_buf_puts(dest, ending);
-		scan = next + 1;
-	}
-
-	git_buf_put(dest, scan, scan_end - scan);
-	return 0;
+	return git_buf_text_crlf_to_lf(dest, source);
 }
 
 static const char *line_ending(struct crlf_filter *filter)
@@ -282,26 +232,28 @@ line_ending_error:
 	return NULL;
 }
 
-static int crlf_apply_to_workdir(git_filter *self, git_buf *dest, const git_buf *source)
+static int crlf_apply_to_workdir(
+	git_filter *self, git_buf *dest, const git_buf *source)
 {
 	struct crlf_filter *filter = (struct crlf_filter *)self;
 	const char *workdir_ending = NULL;
 
-	assert (self && dest && source);
+	assert(self && dest && source);
 
 	/* Empty file? Nothing to do. */
 	if (git_buf_len(source) == 0)
-		return 0;
+		return -1;
 
 	/* Determine proper line ending */
 	workdir_ending = line_ending(filter);
-	if (!workdir_ending) return -1;
-
-	/* If the line ending is '\n', just copy the input */
-	if (!strcmp(workdir_ending, "\n"))
-		return git_buf_puts(dest, git_buf_cstr(source));
+	if (!workdir_ending)
+		return -1;
+	if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */
+		return -1;
 
-	return convert_line_endings(dest, source, workdir_ending);
+	/* for now, only lf->crlf conversion is supported here */
+	assert(!strcmp("\r\n", workdir_ending));
+	return git_buf_text_lf_to_crlf(dest, source);
 }
 
 static int find_and_add_filter(
@@ -351,12 +303,14 @@ static int find_and_add_filter(
 	return git_vector_insert(filters, filter);
 }
 
-int git_filter_add__crlf_to_odb(git_vector *filters, git_repository *repo, const char *path)
+int git_filter_add__crlf_to_odb(
+	git_vector *filters, git_repository *repo, const char *path)
 {
 	return find_and_add_filter(filters, repo, path, &crlf_apply_to_odb);
 }
 
-int git_filter_add__crlf_to_workdir(git_vector *filters, git_repository *repo, const char *path)
+int git_filter_add__crlf_to_workdir(
+	git_vector *filters, git_repository *repo, const char *path)
 {
 	return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
 }
diff --git a/src/diff_output.c b/src/diff_output.c
index fba6129..e8dd5b3 100644
--- a/src/diff_output.c
+++ b/src/diff_output.c
@@ -12,6 +12,7 @@
 #include <ctype.h>
 #include "fileops.h"
 #include "filter.h"
+#include "buf_text.h"
 
 static int read_next_int(const char **str, int *value)
 {
diff --git a/src/filter.h b/src/filter.h
index 0ca7165..42a44eb 100644
--- a/src/filter.h
+++ b/src/filter.h
@@ -9,7 +9,6 @@
 
 #include "common.h"
 #include "buffer.h"
-#include "buf_text.h"
 #include "git2/odb.h"
 #include "git2/repository.h"
 
diff --git a/tests-clar/core/buffer.c b/tests-clar/core/buffer.c
index cb5f016..3d8221e 100644
--- a/tests-clar/core/buffer.c
+++ b/tests-clar/core/buffer.c
@@ -904,3 +904,85 @@ void test_core_buffer__similarity_metric_whitespace(void)
 
 	git_buf_free(&buf);
 }
+
+#define check_buf(expected,buf) do { \
+	cl_assert_equal_s(expected, buf.ptr); \
+	cl_assert_equal_sz(strlen(expected), buf.size); } while (0)
+
+void test_core_buffer__lf_and_crlf_conversions(void)
+{
+	git_buf src = GIT_BUF_INIT, tgt = GIT_BUF_INIT;
+
+	/* LF source */
+
+	git_buf_sets(&src, "lf\nlf\nlf\nlf\n");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("lf\r\nlf\r\nlf\r\nlf\r\n", tgt);
+
+	cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src));
+	/* no conversion needed if all LFs already */
+
+	git_buf_sets(&src, "\nlf\nlf\nlf\nlf\nlf");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("\r\nlf\r\nlf\r\nlf\r\nlf\r\nlf", tgt);
+
+	cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_crlf_to_lf(&tgt, &src));
+	/* no conversion needed if all LFs already */
+
+	/* CRLF source */
+
+	git_buf_sets(&src, "crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("crlf\r\ncrlf\r\ncrlf\r\ncrlf\r\n", tgt);
+	check_buf(src.ptr, tgt);
+
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("crlf\ncrlf\ncrlf\ncrlf\n", tgt);
+
+	git_buf_sets(&src, "\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf\r\ncrlf", tgt);
+	check_buf(src.ptr, tgt);
+
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("\ncrlf\ncrlf\ncrlf\ncrlf\ncrlf", tgt);
+
+	/* CRLF in LF text */
+
+	git_buf_sets(&src, "\nlf\nlf\ncrlf\r\nlf\nlf\ncrlf\r\n");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("\r\nlf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\ncrlf\r\n", tgt);
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("\nlf\nlf\ncrlf\nlf\nlf\ncrlf\n", tgt);
+
+	/* LF in CRLF text */
+
+	git_buf_sets(&src, "\ncrlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("\r\ncrlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf", tgt);
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("\ncrlf\ncrlf\nlf\ncrlf\ncrlf", tgt);
+
+	/* bare CR test */
+
+	git_buf_sets(&src, "\rcrlf\r\nlf\nlf\ncr\rcrlf\r\nlf\ncr\r");
+
+	cl_git_pass(git_buf_text_lf_to_crlf(&tgt, &src));
+	check_buf("\rcrlf\r\nlf\r\nlf\r\ncr\rcrlf\r\nlf\r\ncr\r", tgt);
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("\rcrlf\nlf\nlf\ncr\rcrlf\nlf\ncr\r", tgt);
+
+	git_buf_sets(&src, "\rcr\r");
+	cl_assert_equal_i(GIT_ENOTFOUND, git_buf_text_lf_to_crlf(&tgt, &src));
+	cl_git_pass(git_buf_text_crlf_to_lf(&tgt, &src));
+	check_buf("\rcr\r", tgt);
+
+	git_buf_free(&src);
+	git_buf_free(&tgt);
+}
diff --git a/tests-clar/object/blob/filter.c b/tests-clar/object/blob/filter.c
index b9bbfff..042bdda 100644
--- a/tests-clar/object/blob/filter.c
+++ b/tests-clar/object/blob/filter.c
@@ -2,6 +2,7 @@
 #include "posix.h"
 #include "blob.h"
 #include "filter.h"
+#include "buf_text.h"
 
 static git_repository *g_repo = NULL;
 #define NUM_TEST_OBJECTS 8