diff --git a/src/blob.c b/src/blob.c index bcb6ac96b..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) { @@ -221,7 +222,9 @@ int git_blob_create_fromworkdir(git_oid *oid, git_repository *repo, const char * return -1; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + error = blob_create_internal( + oid, repo, git_buf_cstr(&full_path), + git_buf_cstr(&full_path) + strlen(workdir), true); git_buf_free(&full_path); return error; @@ -231,13 +234,21 @@ int git_blob_create_fromdisk(git_oid *oid, git_repository *repo, const char *pat { int error; git_buf full_path = GIT_BUF_INIT; + const char *workdir, *hintpath; if ((error = git_path_prettify(&full_path, path, NULL)) < 0) { git_buf_free(&full_path); return error; } - error = blob_create_internal(oid, repo, git_buf_cstr(&full_path), git_buf_cstr(&full_path), true); + hintpath = git_buf_cstr(&full_path); + workdir = git_repository_workdir(repo); + + if (workdir && !git__prefixcmp(hintpath, workdir)) + hintpath += strlen(workdir); + + error = blob_create_internal( + oid, repo, git_buf_cstr(&full_path), hintpath, true); git_buf_free(&full_path); return error; 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 @@ -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 * 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 060d39d37..81268da83 100644 --- a/src/crlf.c +++ b/src/crlf.c @@ -9,9 +9,10 @@ #include "fileops.h" #include "hash.h" #include "filter.h" +#include "buf_text.h" #include "repository.h" - #include "git2/attr.h" +#include "git2/blob.h" struct crlf_attrs { int crlf_action; @@ -21,6 +22,8 @@ struct crlf_attrs { struct crlf_filter { git_filter f; struct crlf_attrs attrs; + git_repository *repo; + char path[GIT_FLEX_ARRAY]; }; static int check_crlf(const char *value) @@ -103,36 +106,46 @@ 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) +static int has_cr_in_index(git_filter *self) { - const char *scan = source->ptr, *next; - const char *scan_end = git_buf_cstr(source) + git_buf_len(source); + struct crlf_filter *filter = (struct crlf_filter *)self; + git_index *index; + const git_index_entry *entry; + git_blob *blob; + const void *blobcontent; + git_off_t blobsize; + bool found_cr; - /* 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 (git_repository_index__weakptr(&index, filter->repo) < 0) { + giterr_clear(); + return false; } - /* If there was no \r, then tell the library to skip this filter */ - if (scan == source->ptr) - return -1; + if (!(entry = git_index_get_bypath(index, filter->path, 0)) && + !(entry = git_index_get_bypath(index, filter->path, 1))) + return false; - /* Copy remaining input into dest */ - git_buf_put(dest, scan, scan_end - scan); - return 0; + if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */ + return true; + + if (git_blob_lookup(&blob, filter->repo, &entry->oid) < 0) + return false; + + blobcontent = git_blob_rawcontent(blob); + blobsize = git_blob_rawsize(blob); + if (!git__is_sizet(blobsize)) + blobsize = (size_t)-1; + + found_cr = (blobcontent != NULL && + blobsize > 0 && + memchr(blobcontent, '\r', (size_t)blobsize) != NULL); + + git_blob_free(blob); + return found_cr; } -static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *source) +static int crlf_apply_to_odb( + git_filter *self, git_buf *dest, const git_buf *source) { struct crlf_filter *filter = (struct crlf_filter *)self; @@ -162,40 +175,21 @@ static int crlf_apply_to_odb(git_filter *self, git_buf *dest, const git_buf *sou if (stats.cr != stats.crlf) return -1; -#if 0 - if (crlf_action == CRLF_GUESS) { + if (filter->attrs.crlf_action == GIT_CRLF_GUESS) { /* * If the file in the index has any CR in it, do not convert. * This is the new safer autocrlf handling. */ - if (has_cr_in_index(path)) - return 0; + if (has_cr_in_index(self)) + return -1; } -#endif if (!stats.cr) return -1; } /* 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, - *scan_end = git_buf_cstr(source) + git_buf_len(source); - - while ((next = memchr(scan, '\n', scan_end - scan)) != NULL) { - if (next > scan) - git_buf_put(dest, scan, next-scan); - 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) @@ -238,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( @@ -266,6 +262,7 @@ static int find_and_add_filter( { struct crlf_attrs ca; struct crlf_filter *filter; + size_t pathlen; int error; /* Load gitattributes for the path */ @@ -293,22 +290,27 @@ static int find_and_add_filter( /* If we're good, we create a new filter object and push it * into the filters array */ - filter = git__malloc(sizeof(struct crlf_filter)); + pathlen = strlen(path); + filter = git__malloc(sizeof(struct crlf_filter) + pathlen + 1); GITERR_CHECK_ALLOC(filter); filter->f.apply = apply; filter->f.do_free = NULL; memcpy(&filter->attrs, &ca, sizeof(struct crlf_attrs)); + filter->repo = repo; + memcpy(filter->path, path, pathlen + 1); 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 #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/checkout/checkout_helpers.c b/tests-clar/checkout/checkout_helpers.c index 79e80c13a..ab93a89bd 100644 --- a/tests-clar/checkout/checkout_helpers.c +++ b/tests-clar/checkout/checkout_helpers.c @@ -50,35 +50,44 @@ void reset_index_to_treeish(git_object *treeish) git_index_free(index); } -static void test_file_contents_internal( - const char *path, const char *expectedcontents, bool strip_cr) +static void check_file_contents_internal( + const char *path, + const char *expected_content, + bool strip_cr, + const char *file, + int line, + const char *msg) { int fd; char data[1024] = {0}; git_buf buf = GIT_BUF_INIT; - size_t expectedlen = strlen(expectedcontents); + size_t expected_len = expected_content ? strlen(expected_content) : 0; fd = p_open(path, O_RDONLY); cl_assert(fd >= 0); buf.ptr = data; - buf.size = p_read(fd, buf.ptr, 1024); + buf.size = p_read(fd, buf.ptr, sizeof(data)); cl_git_pass(p_close(fd)); if (strip_cr) strip_cr_from_buf(&buf); - cl_assert_equal_i((int)expectedlen, (int)buf.size); - cl_assert_equal_s(expectedcontents, buf.ptr); + clar__assert_equal_i((int)expected_len, (int)buf.size, file, line, "strlen(expected_content) != strlen(actual_content)", 1); + clar__assert_equal_s(expected_content, buf.ptr, file, line, msg, 1); } -void test_file_contents(const char *path, const char *expected) +void check_file_contents_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg) { - test_file_contents_internal(path, expected, false); + check_file_contents_internal(path, expected, false, file, line, msg); } -void test_file_contents_nocr(const char *path, const char *expected) +void check_file_contents_nocr_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg) { - test_file_contents_internal(path, expected, true); + check_file_contents_internal(path, expected, true, file, line, msg); } diff --git a/tests-clar/checkout/checkout_helpers.h b/tests-clar/checkout/checkout_helpers.h index 2c3a4b5bb..34053809d 100644 --- a/tests-clar/checkout/checkout_helpers.h +++ b/tests-clar/checkout/checkout_helpers.h @@ -5,5 +5,17 @@ extern void strip_cr_from_buf(git_buf *buf); extern void assert_on_branch(git_repository *repo, const char *branch); extern void reset_index_to_treeish(git_object *treeish); -extern void test_file_contents(const char *path, const char *expected); -extern void test_file_contents_nocr(const char *path, const char *expected); + +extern void check_file_contents_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg); + +extern void check_file_contents_nocr_at_line( + const char *path, const char *expected, + const char *file, int line, const char *msg); + +#define check_file_contents(PATH,EXP) \ + check_file_contents_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) + +#define check_file_contents_nocr(PATH,EXP) \ + check_file_contents_nocr_at_line(PATH,EXP,__FILE__,__LINE__,"String mismatch: " #EXP " != " #PATH) diff --git a/tests-clar/checkout/crlf.c b/tests-clar/checkout/crlf.c index 74da27652..285b1f272 100644 --- a/tests-clar/checkout/crlf.c +++ b/tests-clar/checkout/crlf.c @@ -10,7 +10,9 @@ #define MORE_CRLF_TEXT_RAW "crlf\r\ncrlf\r\nlf\ncrlf\r\ncrlf\r\n" #define MORE_LF_TEXT_RAW "lf\nlf\ncrlf\r\nlf\nlf\n" -#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define ALL_LF_TEXT_AS_CRLF "lf\r\nlf\r\nlf\r\nlf\r\nlf\r\n" +#define MORE_CRLF_TEXT_AS_CRLF "crlf\r\ncrlf\r\nlf\r\ncrlf\r\ncrlf\r\n" +#define MORE_LF_TEXT_AS_CRLF "lf\r\nlf\r\ncrlf\r\nlf\r\nlf\r\n" static git_repository *g_repo; @@ -26,7 +28,6 @@ void test_checkout_crlf__cleanup(void) void test_checkout_crlf__detect_crlf_autocrlf_false(void) { -#ifdef GIT_WIN32 git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; @@ -34,13 +35,12 @@ void test_checkout_crlf__detect_crlf_autocrlf_false(void) git_checkout_head(g_repo, &opts); - test_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); -#endif + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); } void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) { -#ifdef GIT_WIN32 git_index *index; const git_index_entry *entry; git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; @@ -55,13 +55,14 @@ void test_checkout_crlf__autocrlf_false_index_size_is_unfiltered_size(void) cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); cl_assert(entry->file_size == strlen(ALL_LF_TEXT_RAW)); + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert(entry->file_size == strlen(ALL_CRLF_TEXT_RAW)); + git_index_free(index); -#endif } void test_checkout_crlf__detect_crlf_autocrlf_true(void) { -#ifdef GIT_WIN32 git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; @@ -69,13 +70,58 @@ void test_checkout_crlf__detect_crlf_autocrlf_true(void) git_checkout_head(g_repo, &opts); - test_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); -#endif + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_RAW); + else + check_file_contents("./crlf/all-lf", ALL_LF_TEXT_AS_CRLF); + + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); +} + +void test_checkout_crlf__more_lf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/more-lf", MORE_LF_TEXT_RAW); + else + check_file_contents("./crlf/more-lf", MORE_LF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__more_crlf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_RAW); + else + check_file_contents("./crlf/more-crlf", MORE_CRLF_TEXT_AS_CRLF); +} + +void test_checkout_crlf__all_crlf_autocrlf_true(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + opts.checkout_strategy = GIT_CHECKOUT_SAFE_CREATE; + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + git_checkout_head(g_repo, &opts); + + check_file_contents("./crlf/all-crlf", ALL_CRLF_TEXT_RAW); } void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) { -#ifdef GIT_WIN32 git_index *index; const git_index_entry *entry; git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; @@ -88,8 +134,14 @@ void test_checkout_crlf__autocrlf_true_index_size_is_filtered_size(void) git_repository_index(&index, g_repo); cl_assert((entry = git_index_get_bypath(index, "all-lf", 0)) != NULL); - cl_assert(entry->file_size == strlen(ALL_LF_TEXT_AS_CRLF)); + + if (GIT_EOL_NATIVE == GIT_EOL_LF) + cl_assert_equal_sz(strlen(ALL_LF_TEXT_RAW), entry->file_size); + else + cl_assert_equal_sz(strlen(ALL_LF_TEXT_AS_CRLF), entry->file_size); + + cl_assert((entry = git_index_get_bypath(index, "all-crlf", 0)) != NULL); + cl_assert_equal_sz(strlen(ALL_CRLF_TEXT_RAW), entry->file_size); git_index_free(index); -#endif } diff --git a/tests-clar/checkout/index.c b/tests-clar/checkout/index.c index 3976dd20e..78ff5ac62 100644 --- a/tests-clar/checkout/index.c +++ b/tests-clar/checkout/index.c @@ -48,9 +48,9 @@ void test_checkout_index__can_create_missing_files(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); } void test_checkout_index__can_remove_untracked_files(void) @@ -88,8 +88,8 @@ void test_checkout_index__honor_the_specified_pathspecs(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); cl_assert_equal_i(false, git_path_isfile("./testrepo/README")); - test_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); } void test_checkout_index__honor_the_gitattributes_directives(void) @@ -106,9 +106,9 @@ void test_checkout_index__honor_the_gitattributes_directives(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/README", "hey there\n"); - test_file_contents("./testrepo/new.txt", "my new file\n"); - test_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); + check_file_contents("./testrepo/README", "hey there\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/branch_file.txt", "hi\r\nbye!\r\n"); } void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) @@ -124,7 +124,7 @@ void test_checkout_index__honor_coreautocrlf_setting_set_to_true(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/README", expected_readme_text); + check_file_contents("./testrepo/README", expected_readme_text); #endif } @@ -139,7 +139,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); #ifdef GIT_WIN32 - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); + check_file_contents("./testrepo/link_to_new.txt", "new.txt"); #else { char link_data[1024]; @@ -149,7 +149,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_true(void) link_data[link_size] = '\0'; cl_assert_equal_i(link_size, strlen("new.txt")); cl_assert_equal_s(link_data, "new.txt"); - test_file_contents("./testrepo/link_to_new.txt", "my new file\n"); + check_file_contents("./testrepo/link_to_new.txt", "my new file\n"); } #endif } @@ -164,7 +164,7 @@ void test_checkout_index__honor_coresymlinks_setting_set_to_false(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/link_to_new.txt", "new.txt"); + check_file_contents("./testrepo/link_to_new.txt", "new.txt"); } void test_checkout_index__donot_overwrite_modified_file_by_default(void) @@ -180,7 +180,7 @@ void test_checkout_index__donot_overwrite_modified_file_by_default(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/new.txt", "This isn't what's stored!"); + check_file_contents("./testrepo/new.txt", "This isn't what's stored!"); } void test_checkout_index__can_overwrite_modified_file(void) @@ -193,7 +193,7 @@ void test_checkout_index__can_overwrite_modified_file(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); } void test_checkout_index__options_disable_filters(void) @@ -207,14 +207,14 @@ void test_checkout_index__options_disable_filters(void) cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/new.txt", "my new file\r\n"); + check_file_contents("./testrepo/new.txt", "my new file\r\n"); p_unlink("./testrepo/new.txt"); opts.disable_filters = true; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/new.txt", "my new file\n"); + check_file_contents("./testrepo/new.txt", "my new file\n"); } void test_checkout_index__options_dir_modes(void) @@ -274,7 +274,7 @@ void test_checkout_index__options_open_flags(void) opts.checkout_strategy = GIT_CHECKOUT_FORCE; cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); - test_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); + check_file_contents("./testrepo/new.txt", "hi\nmy new file\n"); } struct notify_data { @@ -469,9 +469,9 @@ void test_checkout_index__can_update_prefixed_files(void) /* remove untracked will remove the .gitattributes file before the blobs * were created, so they will have had crlf filtering applied on Windows */ - test_file_contents_nocr("./testrepo/README", "hey there\n"); - test_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n"); - test_file_contents_nocr("./testrepo/new.txt", "my new file\n"); + check_file_contents_nocr("./testrepo/README", "hey there\n"); + check_file_contents_nocr("./testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("./testrepo/new.txt", "my new file\n"); cl_assert(!git_path_exists("testrepo/READ")); cl_assert(!git_path_exists("testrepo/README.after")); @@ -488,3 +488,20 @@ void test_checkout_index__can_checkout_a_newly_initialized_repository(void) cl_git_pass(git_checkout_index(g_repo, NULL, NULL)); } + +void test_checkout_index__issue_1397(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + + test_checkout_index__cleanup(); + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_index(g_repo, NULL, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); +} diff --git a/tests-clar/checkout/tree.c b/tests-clar/checkout/tree.c index 8309cd721..5a2eacea1 100644 --- a/tests-clar/checkout/tree.c +++ b/tests-clar/checkout/tree.c @@ -248,7 +248,7 @@ void test_checkout_tree__can_update_only(void) cl_assert(!git_path_isdir("testrepo/a")); - test_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); + check_file_contents_nocr("testrepo/branch_file.txt", "hi\nbye!\n"); /* now checkout branch but with update only */ @@ -269,7 +269,7 @@ void test_checkout_tree__can_update_only(void) cl_assert(!git_path_isdir("testrepo/a")); /* but this file still should have been updated */ - test_file_contents_nocr("testrepo/branch_file.txt", "hi\n"); + check_file_contents_nocr("testrepo/branch_file.txt", "hi\n"); git_object_free(obj); } @@ -481,3 +481,26 @@ void test_checkout_tree__can_checkout_with_last_workdir_item_missing(void) git_commit_free(commit); git_index_free(index); } + +void test_checkout_tree__issue_1397(void) +{ + git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + const char *partial_oid = "8a7ef04"; + git_object *tree = NULL; + + test_checkout_tree__cleanup(); /* cleanup default checkout */ + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_git_pass(git_revparse_single(&tree, g_repo, partial_oid)); + + opts.checkout_strategy = GIT_CHECKOUT_FORCE; + + cl_git_pass(git_checkout_tree(g_repo, tree, &opts)); + + check_file_contents("./issue_1397/crlf_file.txt", "first line\r\nsecond line\r\nboth with crlf"); + + git_object_free(tree); +} 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/diff/tree.c b/tests-clar/diff/tree.c index e86f8d538..850feefde 100644 --- a/tests-clar/diff/tree.c +++ b/tests-clar/diff/tree.c @@ -431,3 +431,26 @@ void test_diff_tree__regular_blob_mode_changed_to_executable_file(void) cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); } + +void test_diff_tree__issue_1397(void) +{ + /* this test shows that it is not needed */ + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + cl_assert((a = resolve_commit_oid_to_tree(g_repo, "8a7ef04")) != NULL); + cl_assert((b = resolve_commit_oid_to_tree(g_repo, "7f483a7")) != NULL); + + cl_git_pass(git_diff_tree_to_tree(&diff, g_repo, a, b, &opts)); + + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &expect)); + + cl_assert_equal_i(1, expect.files); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_DELETED]); + cl_assert_equal_i(1, expect.file_status[GIT_DELTA_MODIFIED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_ADDED]); + cl_assert_equal_i(0, expect.file_status[GIT_DELTA_TYPECHANGE]); +} diff --git a/tests-clar/diff/workdir.c b/tests-clar/diff/workdir.c index 1ac56311c..f67f09ae8 100644 --- a/tests-clar/diff/workdir.c +++ b/tests-clar/diff/workdir.c @@ -1084,3 +1084,99 @@ void test_diff_workdir__can_diff_empty_file(void) git_diff_patch_free(patch); git_diff_list_free(diff); } + +void test_diff_workdir__to_index_issue_1397(void) +{ + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_list_free(diff); + diff = NULL; + + cl_git_rewritefile("issue_1397/crlf_file.txt", + "first line\r\nsecond line modified\r\nboth with crlf"); + + cl_git_pass(git_diff_index_to_workdir(&diff, g_repo, NULL, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(1, exp.files); + cl_assert_equal_i(1, exp.file_status[GIT_DELTA_MODIFIED]); + + cl_assert_equal_i(1, exp.hunks); + + cl_assert_equal_i(5, exp.lines); + cl_assert_equal_i(3, exp.line_ctxt); + cl_assert_equal_i(1, exp.line_adds); + cl_assert_equal_i(1, exp.line_dels); + + git_diff_list_free(diff); +} + +void test_diff_workdir__to_tree_issue_1397(void) +{ + const char *a_commit = "7f483a738"; /* the current HEAD */ + git_tree *a; + git_diff_options opts = GIT_DIFF_OPTIONS_INIT; + git_diff_list *diff = NULL; + git_diff_list *diff2 = NULL; + diff_expects exp; + + g_repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(g_repo, "core.autocrlf", true); + + a = resolve_commit_oid_to_tree(g_repo, a_commit); + + opts.context_lines = 3; + opts.interhunk_lines = 1; + + cl_git_pass(git_diff_tree_to_workdir(&diff, g_repo, a, &opts)); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_list_free(diff); + diff = NULL; + + cl_git_pass(git_diff_tree_to_index(&diff, g_repo, a, NULL, &opts)); + cl_git_pass(git_diff_index_to_workdir(&diff2, g_repo, NULL, &opts)); + cl_git_pass(git_diff_merge(diff, diff2)); + git_diff_list_free(diff2); + + memset(&exp, 0, sizeof(exp)); + cl_git_pass(git_diff_foreach( + diff, diff_file_cb, diff_hunk_cb, diff_line_cb, &exp)); + + cl_assert_equal_i(0, exp.files); + cl_assert_equal_i(0, exp.hunks); + cl_assert_equal_i(0, exp.lines); + + git_diff_list_free(diff); + git_tree_free(a); +} diff --git a/tests-clar/index/tests.c b/tests-clar/index/tests.c index 64f547ead..88e374e6e 100644 --- a/tests-clar/index/tests.c +++ b/tests-clar/index/tests.c @@ -251,6 +251,48 @@ void test_index_tests__add(void) git_repository_free(repo); } +static void cleanup_1397(void *opaque) +{ + GIT_UNUSED(opaque); + cl_git_sandbox_cleanup(); +} + +void test_index_tests__add_issue_1397(void) +{ + git_index *index; + git_repository *repo; + const git_index_entry *entry; + git_oid id1; + + cl_set_cleanup(&cleanup_1397, NULL); + + repo = cl_git_sandbox_init("issue_1397"); + + cl_repo_set_bool(repo, "core.autocrlf", true); + + /* Ensure we're the only guy in the room */ + cl_git_pass(git_repository_index(&index, repo)); + + /* Store the expected hash of the file/blob + * This has been generated by executing the following + * $ git hash-object crlf_file.txt + */ + cl_git_pass(git_oid_fromstr(&id1, "8312e0889a9cbab77c732b6bc39b51a683e3a318")); + + /* Make sure the initial SHA-1 is correct */ + cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); + cl_assert_(git_oid_cmp(&id1, &entry->oid) == 0, "first oid check"); + + /* Update the index */ + cl_git_pass(git_index_add_bypath(index, "crlf_file.txt")); + + /* Check the new SHA-1 */ + cl_assert((entry = git_index_get_bypath(index, "crlf_file.txt", 0)) != NULL); + cl_assert_(git_oid_cmp(&id1, &entry->oid) == 0, "second oid check"); + + git_index_free(index); +} + void test_index_tests__add_bypath_to_a_bare_repository_returns_EBAREPO(void) { git_repository *bare_repo; 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 diff --git a/tests-clar/resources/issue_1397/.gitted/HEAD b/tests-clar/resources/issue_1397/.gitted/HEAD new file mode 100644 index 000000000..cb089cd89 --- /dev/null +++ b/tests-clar/resources/issue_1397/.gitted/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/tests-clar/resources/issue_1397/.gitted/config b/tests-clar/resources/issue_1397/.gitted/config new file mode 100644 index 000000000..ba5bbde24 --- /dev/null +++ b/tests-clar/resources/issue_1397/.gitted/config @@ -0,0 +1,6 @@ +[core] + bare = false + repositoryformatversion = 0 + filemode = false + logallrefupdates = true + ignorecase = true diff --git a/tests-clar/resources/issue_1397/.gitted/index b/tests-clar/resources/issue_1397/.gitted/index new file mode 100644 index 000000000..fa0f541d6 Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/index differ diff --git a/tests-clar/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 b/tests-clar/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 new file mode 100644 index 000000000..63bcb5d76 --- /dev/null +++ b/tests-clar/resources/issue_1397/.gitted/objects/7f/483a738f867e5b21c8f377d70311f011eb48b5 @@ -0,0 +1,3 @@ +xQN0 as +!'nTBC+e\vӯǾofPPL8FΆ%_ċb4IܗtkULdeId<3/|0j1֣\Gcwe]~ߊS +)GxEqsUڇ8mk~yI \ No newline at end of file diff --git a/tests-clar/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 b/tests-clar/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 new file mode 100644 index 000000000..06b59fede Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/objects/83/12e0889a9cbab77c732b6bc39b51a683e3a318 differ diff --git a/tests-clar/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 b/tests-clar/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 new file mode 100644 index 000000000..19cfbeae2 Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/objects/8a/7ef047fc933edb62e84e7977b0612ec3f6f283 differ diff --git a/tests-clar/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac b/tests-clar/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac new file mode 100644 index 000000000..f5c776b17 Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/objects/8e/8f80088a9274fd23584992f587083ca1bcbbac differ diff --git a/tests-clar/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a b/tests-clar/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a new file mode 100644 index 000000000..f932f3618 Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/objects/f2/c62dea0372a0578e053697d5c1ba1ac05e774a differ diff --git a/tests-clar/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 b/tests-clar/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 new file mode 100644 index 000000000..fbd731727 Binary files /dev/null and b/tests-clar/resources/issue_1397/.gitted/objects/ff/3578d64d199d5b48d92bbb569e0a273e411741 differ diff --git a/tests-clar/resources/issue_1397/.gitted/refs/heads/master b/tests-clar/resources/issue_1397/.gitted/refs/heads/master new file mode 100644 index 000000000..285bc08ff --- /dev/null +++ b/tests-clar/resources/issue_1397/.gitted/refs/heads/master @@ -0,0 +1 @@ +7f483a738f867e5b21c8f377d70311f011eb48b5 diff --git a/tests-clar/resources/issue_1397/crlf_file.txt b/tests-clar/resources/issue_1397/crlf_file.txt new file mode 100644 index 000000000..8312e0889 --- /dev/null +++ b/tests-clar/resources/issue_1397/crlf_file.txt @@ -0,0 +1,3 @@ +first line +second line +both with crlf \ No newline at end of file diff --git a/tests-clar/resources/issue_1397/some_other_crlf_file.txt b/tests-clar/resources/issue_1397/some_other_crlf_file.txt new file mode 100644 index 000000000..8e8f80088 --- /dev/null +++ b/tests-clar/resources/issue_1397/some_other_crlf_file.txt @@ -0,0 +1,3 @@ +first line +second line with some change +both with crlf \ No newline at end of file diff --git a/tests-clar/status/worktree.c b/tests-clar/status/worktree.c index 5b11d1f82..a9b8a12ed 100644 --- a/tests-clar/status/worktree.c +++ b/tests-clar/status/worktree.c @@ -541,6 +541,18 @@ void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf(void cl_assert_equal_i(GIT_STATUS_CURRENT, status); } +void test_status_worktree__line_endings_dont_count_as_changes_with_autocrlf_issue_1397(void) +{ + git_repository *repo = cl_git_sandbox_init("issue_1397"); + unsigned int status; + + cl_repo_set_bool(repo, "core.autocrlf", true); + + cl_git_pass(git_status_file(&status, repo, "crlf_file.txt")); + + cl_assert_equal_i(GIT_STATUS_CURRENT, status); +} + void test_status_worktree__conflicted_item(void) { git_repository *repo = cl_git_sandbox_init("status");