2 * Copyright (C) the libgit2 contributors. All rights reserved.
4 * This file is part of libgit2, distributed under the GNU GPL v2 with
5 * a Linking Exception. For full terms see the included COPYING file.
10 #include "git2/index.h"
11 #include "git2/sys/filter.h"
18 #include "repository.h"
31 static int check_crlf(const char *value
)
33 if (GIT_ATTR_TRUE(value
))
36 if (GIT_ATTR_FALSE(value
))
37 return GIT_CRLF_BINARY
;
39 if (GIT_ATTR_UNSPECIFIED(value
))
40 return GIT_CRLF_GUESS
;
42 if (strcmp(value
, "input") == 0)
43 return GIT_CRLF_INPUT
;
45 if (strcmp(value
, "auto") == 0)
48 return GIT_CRLF_GUESS
;
51 static int check_eol(const char *value
)
53 if (GIT_ATTR_UNSPECIFIED(value
))
56 if (strcmp(value
, "lf") == 0)
59 if (strcmp(value
, "crlf") == 0)
65 static int crlf_input_action(struct crlf_attrs
*ca
)
67 if (ca
->crlf_action
== GIT_CRLF_BINARY
)
68 return GIT_CRLF_BINARY
;
70 if (ca
->eol
== GIT_EOL_LF
)
71 return GIT_CRLF_INPUT
;
73 if (ca
->eol
== GIT_EOL_CRLF
)
76 return ca
->crlf_action
;
79 static int has_cr_in_index(const git_filter_source
*src
)
81 git_repository
*repo
= git_filter_source_repo(src
);
82 const char *path
= git_filter_source_path(src
);
84 const git_index_entry
*entry
;
86 const void *blobcontent
;
93 if (git_repository_index__weakptr(&index
, repo
) < 0) {
98 if (!(entry
= git_index_get_bypath(index
, path
, 0)) &&
99 !(entry
= git_index_get_bypath(index
, path
, 1)))
102 if (!S_ISREG(entry
->mode
)) /* don't crlf filter non-blobs */
105 if (git_blob_lookup(&blob
, repo
, &entry
->id
) < 0)
108 blobcontent
= git_blob_rawcontent(blob
);
109 blobsize
= git_blob_rawsize(blob
);
110 if (!git__is_sizet(blobsize
))
111 blobsize
= (size_t)-1;
113 found_cr
= (blobcontent
!= NULL
&&
115 memchr(blobcontent
, '\r', (size_t)blobsize
) != NULL
);
121 static int crlf_apply_to_odb(
122 struct crlf_attrs
*ca
,
125 const git_filter_source
*src
)
127 /* Empty file? Nothing to do */
128 if (!git_buf_len(from
))
131 /* Heuristics to see if we can skip the conversion.
132 * Straight from Core Git.
134 if (ca
->crlf_action
== GIT_CRLF_AUTO
|| ca
->crlf_action
== GIT_CRLF_GUESS
) {
135 git_buf_text_stats stats
;
137 /* Check heuristics for binary vs text - returns true if binary */
138 if (git_buf_text_gather_stats(&stats
, from
, false))
139 return GIT_PASSTHROUGH
;
141 /* If there are no CR characters to filter out, then just pass */
143 return GIT_PASSTHROUGH
;
145 /* If safecrlf is enabled, sanity-check the result. */
146 if (stats
.cr
!= stats
.crlf
|| stats
.lf
!= stats
.crlf
) {
147 switch (ca
->safe_crlf
) {
148 case GIT_SAFE_CRLF_FAIL
:
150 GITERR_FILTER
, "LF would be replaced by CRLF in '%s'",
151 git_filter_source_path(src
));
153 case GIT_SAFE_CRLF_WARN
:
154 /* TODO: issue warning when warning API is available */;
162 * We're currently not going to even try to convert stuff
163 * that has bare CR characters. Does anybody do that crazy
166 if (stats
.cr
!= stats
.crlf
)
167 return GIT_PASSTHROUGH
;
169 if (ca
->crlf_action
== GIT_CRLF_GUESS
) {
171 * If the file in the index has any CR in it, do not convert.
172 * This is the new safer autocrlf handling.
174 if (has_cr_in_index(src
))
175 return GIT_PASSTHROUGH
;
179 return GIT_PASSTHROUGH
;
182 /* Actually drop the carriage returns */
183 return git_buf_text_crlf_to_lf(to
, from
);
186 static const char *line_ending(struct crlf_attrs
*ca
)
188 switch (ca
->crlf_action
) {
189 case GIT_CRLF_BINARY
:
202 goto line_ending_error
;
207 return GIT_EOL_NATIVE
== GIT_EOL_CRLF
? "\r\n" : "\n";
216 goto line_ending_error
;
220 giterr_set(GITERR_INVALID
, "Invalid input to line ending filter");
224 static int crlf_apply_to_workdir(
225 struct crlf_attrs
*ca
, git_buf
*to
, const git_buf
*from
)
227 const char *workdir_ending
= NULL
;
229 /* Empty file? Nothing to do. */
230 if (git_buf_len(from
) == 0)
233 /* Don't filter binary files */
234 if (git_buf_text_is_binary(from
))
235 return GIT_PASSTHROUGH
;
237 /* Determine proper line ending */
238 workdir_ending
= line_ending(ca
);
242 /* only LF->CRLF conversion is supported, do nothing on LF platforms */
243 if (strcmp(workdir_ending
, "\r\n") != 0)
244 return GIT_PASSTHROUGH
;
246 return git_buf_text_lf_to_crlf(to
, from
);
249 static int crlf_check(
251 void **payload
, /* points to NULL ptr on entry, may be set */
252 const git_filter_source
*src
,
253 const char **attr_values
)
256 struct crlf_attrs ca
;
261 ca
.crlf_action
= GIT_CRLF_GUESS
;
262 ca
.eol
= GIT_EOL_UNSET
;
264 ca
.crlf_action
= check_crlf(attr_values
[2]); /* text */
265 if (ca
.crlf_action
== GIT_CRLF_GUESS
)
266 ca
.crlf_action
= check_crlf(attr_values
[0]); /* clrf */
267 ca
.eol
= check_eol(attr_values
[1]); /* eol */
269 ca
.auto_crlf
= GIT_AUTO_CRLF_DEFAULT
;
272 * Use the core Git logic to see if we should perform CRLF for this file
273 * based on its attributes & the value of `core.autocrlf`
275 ca
.crlf_action
= crlf_input_action(&ca
);
277 if (ca
.crlf_action
== GIT_CRLF_BINARY
)
278 return GIT_PASSTHROUGH
;
280 if (ca
.crlf_action
== GIT_CRLF_GUESS
||
281 (ca
.crlf_action
== GIT_CRLF_AUTO
&&
282 git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)) {
284 error
= git_repository__cvar(
285 &ca
.auto_crlf
, git_filter_source_repo(src
), GIT_CVAR_AUTO_CRLF
);
289 if (ca
.auto_crlf
== GIT_AUTO_CRLF_FALSE
)
290 return GIT_PASSTHROUGH
;
292 if (ca
.auto_crlf
== GIT_AUTO_CRLF_INPUT
&&
293 git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)
294 return GIT_PASSTHROUGH
;
297 if (git_filter_source_mode(src
) == GIT_FILTER_CLEAN
) {
298 error
= git_repository__cvar(
299 &ca
.safe_crlf
, git_filter_source_repo(src
), GIT_CVAR_SAFE_CRLF
);
303 /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
304 if ((git_filter_source_options(src
) & GIT_FILTER_OPT_ALLOW_UNSAFE
) &&
305 ca
.safe_crlf
== GIT_SAFE_CRLF_FAIL
)
306 ca
.safe_crlf
= GIT_SAFE_CRLF_WARN
;
309 *payload
= git__malloc(sizeof(ca
));
310 GITERR_CHECK_ALLOC(*payload
);
311 memcpy(*payload
, &ca
, sizeof(ca
));
316 static int crlf_apply(
318 void **payload
, /* may be read and/or set */
321 const git_filter_source
*src
)
323 /* initialize payload in case `check` was bypassed */
325 int error
= crlf_check(self
, payload
, src
, NULL
);
326 if (error
< 0 && error
!= GIT_PASSTHROUGH
)
330 if (git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)
331 return crlf_apply_to_workdir(*payload
, to
, from
);
333 return crlf_apply_to_odb(*payload
, to
, from
, src
);
336 static void crlf_cleanup(
344 git_filter
*git_crlf_filter_new(void)
346 struct crlf_filter
*f
= git__calloc(1, sizeof(struct crlf_filter
));
348 f
->f
.version
= GIT_FILTER_VERSION
;
349 f
->f
.attributes
= "crlf eol text";
350 f
->f
.initialize
= NULL
;
351 f
->f
.shutdown
= git_filter_free
;
352 f
->f
.check
= crlf_check
;
353 f
->f
.apply
= crlf_apply
;
354 f
->f
.cleanup
= crlf_cleanup
;
356 return (git_filter
*)f
;