blob: 65039f9ccf765052d133d8ca952199e8831de5d2 [file] [log] [blame]
/*
* Copyright (C) the libgit2 contributors. All rights reserved.
*
* This file is part of libgit2, distributed under the GNU GPL v2 with
* a Linking Exception. For full terms see the included COPYING file.
*/
#include "git2/attr.h"
#include "git2/blob.h"
#include "git2/index.h"
#include "common.h"
#include "fileops.h"
#include "hash.h"
#include "filter.h"
#include "buf_text.h"
#include "repository.h"
struct crlf_attrs {
int crlf_action;
int eol;
};
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)
{
if (GIT_ATTR_TRUE(value))
return GIT_CRLF_TEXT;
if (GIT_ATTR_FALSE(value))
return GIT_CRLF_BINARY;
if (GIT_ATTR_UNSPECIFIED(value))
return GIT_CRLF_GUESS;
if (strcmp(value, "input") == 0)
return GIT_CRLF_INPUT;
if (strcmp(value, "auto") == 0)
return GIT_CRLF_AUTO;
return GIT_CRLF_GUESS;
}
static int check_eol(const char *value)
{
if (GIT_ATTR_UNSPECIFIED(value))
return GIT_EOL_UNSET;
if (strcmp(value, "lf") == 0)
return GIT_EOL_LF;
if (strcmp(value, "crlf") == 0)
return GIT_EOL_CRLF;
return GIT_EOL_UNSET;
}
static int crlf_input_action(struct crlf_attrs *ca)
{
if (ca->crlf_action == GIT_CRLF_BINARY)
return GIT_CRLF_BINARY;
if (ca->eol == GIT_EOL_LF)
return GIT_CRLF_INPUT;
if (ca->eol == GIT_EOL_CRLF)
return GIT_CRLF_CRLF;
return ca->crlf_action;
}
static int crlf_load_attributes(struct crlf_attrs *ca, git_repository *repo, const char *path)
{
#define NUM_CONV_ATTRS 3
static const char *attr_names[NUM_CONV_ATTRS] = {
"crlf", "eol", "text",
};
const char *attr_vals[NUM_CONV_ATTRS];
int error;
error = git_attr_get_many(attr_vals,
repo, 0, path, NUM_CONV_ATTRS, attr_names);
if (error == GIT_ENOTFOUND) {
ca->crlf_action = GIT_CRLF_GUESS;
ca->eol = GIT_EOL_UNSET;
return 0;
}
if (error == 0) {
ca->crlf_action = check_crlf(attr_vals[2]); /* text */
if (ca->crlf_action == GIT_CRLF_GUESS)
ca->crlf_action = check_crlf(attr_vals[0]); /* clrf */
ca->eol = check_eol(attr_vals[1]); /* eol */
return 0;
}
return -1;
}
static int has_cr_in_index(git_filter *self)
{
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;
if (git_repository_index__weakptr(&index, filter->repo) < 0) {
giterr_clear();
return false;
}
if (!(entry = git_index_get_bypath(index, filter->path, 0)) &&
!(entry = git_index_get_bypath(index, filter->path, 1)))
return false;
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)
{
struct crlf_filter *filter = (struct crlf_filter *)self;
assert(self && dest && source);
/* Empty file? Nothing to do */
if (git_buf_len(source) == 0)
return 0;
/* Heuristics to see if we can skip the conversion.
* Straight from Core Git.
*/
if (filter->attrs.crlf_action == GIT_CRLF_AUTO ||
filter->attrs.crlf_action == GIT_CRLF_GUESS) {
git_buf_text_stats stats;
/* Check heuristics for binary vs text... */
if (git_buf_text_gather_stats(&stats, source, false))
return -1;
/*
* We're currently not going to even try to convert stuff
* that has bare CR characters. Does anybody do that crazy
* stuff?
*/
if (stats.cr != stats.crlf)
return -1;
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(self))
return -1;
}
if (!stats.cr)
return -1;
}
/* Actually drop the carriage returns */
return git_buf_text_crlf_to_lf(dest, source);
}
static const char *line_ending(struct crlf_filter *filter)
{
switch (filter->attrs.crlf_action) {
case GIT_CRLF_BINARY:
case GIT_CRLF_INPUT:
return "\n";
case GIT_CRLF_CRLF:
return "\r\n";
case GIT_CRLF_AUTO:
case GIT_CRLF_TEXT:
case GIT_CRLF_GUESS:
break;
default:
goto line_ending_error;
}
switch (filter->attrs.eol) {
case GIT_EOL_UNSET:
return GIT_EOL_NATIVE == GIT_EOL_CRLF
? "\r\n"
: "\n";
case GIT_EOL_CRLF:
return "\r\n";
case GIT_EOL_LF:
return "\n";
default:
goto line_ending_error;
}
line_ending_error:
giterr_set(GITERR_INVALID, "Invalid input to line ending filter");
return NULL;
}
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);
/* Empty file? Nothing to do. */
if (git_buf_len(source) == 0)
return -1;
/* Determine proper line ending */
workdir_ending = line_ending(filter);
if (!workdir_ending)
return -1;
if (!strcmp("\n", workdir_ending)) /* do nothing for \n ending */
return -1;
/* 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(
git_vector *filters, git_repository *repo, const char *path,
int (*apply)(struct git_filter *self, git_buf *dest, const git_buf *source))
{
struct crlf_attrs ca;
struct crlf_filter *filter;
size_t pathlen;
int error;
/* Load gitattributes for the path */
if ((error = crlf_load_attributes(&ca, repo, path)) < 0)
return error;
/*
* Use the core Git logic to see if we should perform CRLF for this file
* based on its attributes & the value of `core.autocrlf`
*/
ca.crlf_action = crlf_input_action(&ca);
if (ca.crlf_action == GIT_CRLF_BINARY)
return 0;
if (ca.crlf_action == GIT_CRLF_GUESS) {
int auto_crlf;
if ((error = git_repository__cvar(&auto_crlf, repo, GIT_CVAR_AUTO_CRLF)) < 0)
return error;
if (auto_crlf == GIT_AUTO_CRLF_FALSE)
return 0;
}
/* If we're good, we create a new filter object and push it
* into the filters array */
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)
{
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)
{
return find_and_add_filter(filters, repo, path, &crlf_apply_to_workdir);
}