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.
This commit is contained in:
Russell Belfer 2013-03-25 14:20:07 -07:00
parent 050ab9950d
commit 3658e81e34
9 changed files with 194 additions and 64 deletions

View File

@ -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)
{

View File

@ -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;

View File

@ -55,6 +55,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
*

View File

@ -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 */

View File

@ -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 (!workdir_ending)
return -1;
if (!strcmp("\n", workdir_ending)) /* do nothing for \n 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));
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);
}

View File

@ -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)
{

View File

@ -9,7 +9,6 @@
#include "common.h"
#include "buffer.h"
#include "buf_text.h"
#include "git2/odb.h"
#include "git2/repository.h"

View File

@ -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);
}

View File

@ -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