#include "git2/attr.h"
#include "git2/blob.h"
#include "git2/index.h"
#include "git2/sys/filter.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;
int
auto_crlf;
int
safe_crlf;
};
struct
crlf_filter {
git_filter f;
};
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
has_cr_in_index(
const
git_filter_source *src)
{
git_repository *repo = git_filter_source_repo(src);
const
char
*path = git_filter_source_path(src);
git_index *index;
const
git_index_entry *entry;
git_blob *blob;
const
void
*blobcontent;
git_off_t blobsize;
bool
found_cr;
if
(!path)
return
false
;
if
(git_repository_index__weakptr(&index, repo) < 0) {
giterr_clear();
return
false
;
}
if
(!(entry = git_index_get_bypath(index, path, 0)) &&
!(entry = git_index_get_bypath(index, path, 1)))
return
false
;
if
(!S_ISREG(entry->mode))
return
true
;
if
(git_blob_lookup(&blob, repo, &entry->id) < 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(
struct
crlf_attrs *ca,
git_buf *to,
const
git_buf *from,
const
git_filter_source *src)
{
if
(!git_buf_len(from))
return
0;
if
(ca->crlf_action == GIT_CRLF_AUTO || ca->crlf_action == GIT_CRLF_GUESS) {
git_buf_text_stats stats;
if
(git_buf_text_gather_stats(&stats, from,
false
))
return
GIT_PASSTHROUGH;
if
(!stats.cr)
return
GIT_PASSTHROUGH;
if
(stats.cr != stats.crlf || stats.lf != stats.crlf) {
switch
(ca->safe_crlf) {
case
GIT_SAFE_CRLF_FAIL:
giterr_set(
GITERR_FILTER,
"LF would be replaced by CRLF in '%s'"
,
git_filter_source_path(src));
return
-1;
case
GIT_SAFE_CRLF_WARN:
;
break
;
default
:
break
;
}
}
if
(stats.cr != stats.crlf)
return
GIT_PASSTHROUGH;
if
(ca->crlf_action == GIT_CRLF_GUESS) {
if
(has_cr_in_index(src))
return
GIT_PASSTHROUGH;
}
if
(!stats.cr)
return
GIT_PASSTHROUGH;
}
return
git_buf_text_crlf_to_lf(to, from);
}
static
const
char
*line_ending(
struct
crlf_attrs *ca)
{
switch
(ca->crlf_action) {
case
GIT_CRLF_BINARY:
case
GIT_CRLF_INPUT:
return
"\n"
;
case
GIT_CRLF_CRLF:
return
"\r\n"
;
case
GIT_CRLF_GUESS:
if
(ca->auto_crlf == GIT_AUTO_CRLF_FALSE)
return
"\n"
;
break
;
case
GIT_CRLF_AUTO:
case
GIT_CRLF_TEXT:
break
;
default
:
goto
line_ending_error;
}
if
(ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
return
"\r\n"
;
else
if
(ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
return
"\n"
;
else
if
(ca->eol == GIT_EOL_UNSET)
return
GIT_EOL_NATIVE == GIT_EOL_CRLF ?
"\r\n"
:
"\n"
;
else
if
(ca->eol == GIT_EOL_LF)
return
"\n"
;
else
if
(ca->eol == GIT_EOL_CRLF)
return
"\r\n"
;
line_ending_error:
giterr_set(GITERR_INVALID,
"Invalid input to line ending filter"
);
return
NULL;
}
static
int
crlf_apply_to_workdir(
struct
crlf_attrs *ca, git_buf *to,
const
git_buf *from)
{
git_buf_text_stats stats;
const
char
*workdir_ending = NULL;
bool
is_binary;
if
(git_buf_len(from) == 0)
return
0;
workdir_ending = line_ending(ca);
if
(!workdir_ending)
return
-1;
if
(
strcmp
(workdir_ending,
"\r\n"
) != 0)
return
GIT_PASSTHROUGH;
is_binary = git_buf_text_gather_stats(&stats, from,
false
);
if
(stats.lf == 0 || stats.lf == stats.crlf)
return
GIT_PASSTHROUGH;
if
(ca->crlf_action == GIT_CRLF_AUTO ||
ca->crlf_action == GIT_CRLF_GUESS) {
if
(ca->crlf_action == GIT_CRLF_GUESS &&
stats.cr > 0 && stats.crlf > 0)
return
GIT_PASSTHROUGH;
if
(stats.cr != stats.crlf)
return
GIT_PASSTHROUGH;
if
(is_binary)
return
GIT_PASSTHROUGH;
}
return
git_buf_text_lf_to_crlf(to, from);
}
static
int
crlf_check(
git_filter *self,
void
**payload,
const
git_filter_source *src,
const
char
**attr_values)
{
int
error;
struct
crlf_attrs ca;
GIT_UNUSED(self);
if
(!attr_values) {
ca.crlf_action = GIT_CRLF_GUESS;
ca.eol = GIT_EOL_UNSET;
}
else
{
ca.crlf_action = check_crlf(attr_values[2]);
if
(ca.crlf_action == GIT_CRLF_GUESS)
ca.crlf_action = check_crlf(attr_values[0]);
ca.eol = check_eol(attr_values[1]);
}
ca.auto_crlf = GIT_AUTO_CRLF_DEFAULT;
ca.crlf_action = crlf_input_action(&ca);
if
(ca.crlf_action == GIT_CRLF_BINARY)
return
GIT_PASSTHROUGH;
if
(ca.crlf_action == GIT_CRLF_GUESS ||
((ca.crlf_action == GIT_CRLF_AUTO || ca.crlf_action == GIT_CRLF_TEXT) &&
git_filter_source_mode(src) == GIT_FILTER_SMUDGE)) {
error = git_repository__cvar(
&ca.auto_crlf, git_filter_source_repo(src), GIT_CVAR_AUTO_CRLF);
if
(error < 0)
return
error;
if
(ca.crlf_action == GIT_CRLF_GUESS &&
ca.auto_crlf == GIT_AUTO_CRLF_FALSE)
return
GIT_PASSTHROUGH;
if
(ca.auto_crlf == GIT_AUTO_CRLF_INPUT &&
git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
return
GIT_PASSTHROUGH;
}
if
(git_filter_source_mode(src) == GIT_FILTER_CLEAN) {
error = git_repository__cvar(
&ca.safe_crlf, git_filter_source_repo(src), GIT_CVAR_SAFE_CRLF);
if
(error < 0)
return
error;
if
((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) &&
ca.safe_crlf == GIT_SAFE_CRLF_FAIL)
ca.safe_crlf = GIT_SAFE_CRLF_WARN;
}
*payload = git__malloc(
sizeof
(ca));
GITERR_CHECK_ALLOC(*payload);
memcpy
(*payload, &ca,
sizeof
(ca));
return
0;
}
static
int
crlf_apply(
git_filter *self,
void
**payload,
git_buf *to,
const
git_buf *from,
const
git_filter_source *src)
{
if
(!*payload) {
int
error = crlf_check(self, payload, src, NULL);
if
(error < 0 && error != GIT_PASSTHROUGH)
return
error;
}
if
(git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
return
crlf_apply_to_workdir(*payload, to, from);
else
return
crlf_apply_to_odb(*payload, to, from, src);
}
static
void
crlf_cleanup(
git_filter *self,
void
*payload)
{
GIT_UNUSED(self);
git__free(payload);
}
git_filter *git_crlf_filter_new(
void
)
{
struct
crlf_filter *f = git__calloc(1,
sizeof
(
struct
crlf_filter));
if
(f == NULL)
return
NULL;
f->f.version = GIT_FILTER_VERSION;
f->f.attributes =
"crlf eol text"
;
f->f.initialize = NULL;
f->f.shutdown = git_filter_free;
f->f.check = crlf_check;
f->f.apply = crlf_apply;
f->f.cleanup = crlf_cleanup;
return
(git_filter *)f;
}