diff options
-rw-r--r-- | src/blob.c | 1 | ||||
-rw-r--r-- | src/buf_text.c | 77 | ||||
-rw-r--r-- | src/buf_text.h | 14 | ||||
-rw-r--r-- | src/checkout.c | 1 | ||||
-rw-r--r-- | src/crlf.c | 80 | ||||
-rw-r--r-- | src/diff_output.c | 1 | ||||
-rw-r--r-- | src/filter.h | 1 | ||||
-rw-r--r-- | tests-clar/core/buffer.c | 82 | ||||
-rw-r--r-- | tests-clar/object/blob/filter.c | 1 |
9 files changed, 194 insertions, 64 deletions
diff --git a/src/blob.c b/src/blob.c index 3ff141c7f..c0514fc13 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 3a8f442b4..443454b5f 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 458ee33c9..58e4e26a7 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 e52649aec..5a2698e41 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 cd6d9825f..81268da83 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 fba6129b7..e8dd5b317 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 0ca71656b..42a44ebdb 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 cb5f0161b..3d8221e04 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 b9bbfff0c..042bddab7 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 |