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 safecrlf is enabled, sanity-check the result. */
142 if (stats
.cr
!= stats
.crlf
|| stats
.lf
!= stats
.crlf
) {
143 switch (ca
->safe_crlf
) {
144 case GIT_SAFE_CRLF_FAIL
:
146 GITERR_FILTER
, "LF would be replaced by CRLF in '%s'",
147 git_filter_source_path(src
));
149 case GIT_SAFE_CRLF_WARN
:
150 /* TODO: issue warning when warning API is available */;
158 * We're currently not going to even try to convert stuff
159 * that has bare CR characters. Does anybody do that crazy
162 if (stats
.cr
!= stats
.crlf
)
163 return GIT_PASSTHROUGH
;
165 if (ca
->crlf_action
== GIT_CRLF_GUESS
) {
167 * If the file in the index has any CR in it, do not convert.
168 * This is the new safer autocrlf handling.
170 if (has_cr_in_index(src
))
171 return GIT_PASSTHROUGH
;
175 return GIT_PASSTHROUGH
;
178 /* Actually drop the carriage returns */
179 return git_buf_text_crlf_to_lf(to
, from
);
182 static const char *line_ending(struct crlf_attrs
*ca
)
184 switch (ca
->crlf_action
) {
185 case GIT_CRLF_BINARY
:
198 goto line_ending_error
;
203 return GIT_EOL_NATIVE
== GIT_EOL_CRLF
? "\r\n" : "\n";
212 goto line_ending_error
;
216 giterr_set(GITERR_INVALID
, "Invalid input to line ending filter");
220 static int crlf_apply_to_workdir(
221 struct crlf_attrs
*ca
, git_buf
*to
, const git_buf
*from
)
223 const char *workdir_ending
= NULL
;
225 /* Empty file? Nothing to do. */
226 if (git_buf_len(from
) == 0)
229 /* Don't filter binary files */
230 if (git_buf_text_is_binary(from
))
231 return GIT_PASSTHROUGH
;
233 /* Determine proper line ending */
234 workdir_ending
= line_ending(ca
);
238 /* only LF->CRLF conversion is supported, do nothing on LF platforms */
239 if (strcmp(workdir_ending
, "\r\n") != 0)
240 return GIT_PASSTHROUGH
;
242 return git_buf_text_lf_to_crlf(to
, from
);
245 static int crlf_check(
247 void **payload
, /* points to NULL ptr on entry, may be set */
248 const git_filter_source
*src
,
249 const char **attr_values
)
252 struct crlf_attrs ca
;
257 ca
.crlf_action
= GIT_CRLF_GUESS
;
258 ca
.eol
= GIT_EOL_UNSET
;
260 ca
.crlf_action
= check_crlf(attr_values
[2]); /* text */
261 if (ca
.crlf_action
== GIT_CRLF_GUESS
)
262 ca
.crlf_action
= check_crlf(attr_values
[0]); /* clrf */
263 ca
.eol
= check_eol(attr_values
[1]); /* eol */
265 ca
.auto_crlf
= GIT_AUTO_CRLF_DEFAULT
;
268 * Use the core Git logic to see if we should perform CRLF for this file
269 * based on its attributes & the value of `core.autocrlf`
271 ca
.crlf_action
= crlf_input_action(&ca
);
273 if (ca
.crlf_action
== GIT_CRLF_BINARY
)
274 return GIT_PASSTHROUGH
;
276 if (ca
.crlf_action
== GIT_CRLF_GUESS
||
277 (ca
.crlf_action
== GIT_CRLF_AUTO
&&
278 git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)) {
280 error
= git_repository__cvar(
281 &ca
.auto_crlf
, git_filter_source_repo(src
), GIT_CVAR_AUTO_CRLF
);
285 if (ca
.auto_crlf
== GIT_AUTO_CRLF_FALSE
)
286 return GIT_PASSTHROUGH
;
288 if (ca
.auto_crlf
== GIT_AUTO_CRLF_INPUT
&&
289 git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)
290 return GIT_PASSTHROUGH
;
293 if (git_filter_source_mode(src
) == GIT_FILTER_CLEAN
) {
294 error
= git_repository__cvar(
295 &ca
.safe_crlf
, git_filter_source_repo(src
), GIT_CVAR_SAFE_CRLF
);
299 /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
300 if ((git_filter_source_options(src
) & GIT_FILTER_OPT_ALLOW_UNSAFE
) &&
301 ca
.safe_crlf
== GIT_SAFE_CRLF_FAIL
)
302 ca
.safe_crlf
= GIT_SAFE_CRLF_WARN
;
305 *payload
= git__malloc(sizeof(ca
));
306 GITERR_CHECK_ALLOC(*payload
);
307 memcpy(*payload
, &ca
, sizeof(ca
));
312 static int crlf_apply(
314 void **payload
, /* may be read and/or set */
317 const git_filter_source
*src
)
319 /* initialize payload in case `check` was bypassed */
321 int error
= crlf_check(self
, payload
, src
, NULL
);
322 if (error
< 0 && error
!= GIT_PASSTHROUGH
)
326 if (git_filter_source_mode(src
) == GIT_FILTER_SMUDGE
)
327 return crlf_apply_to_workdir(*payload
, to
, from
);
329 return crlf_apply_to_odb(*payload
, to
, from
, src
);
332 static void crlf_cleanup(
340 git_filter
*git_crlf_filter_new(void)
342 struct crlf_filter
*f
= git__calloc(1, sizeof(struct crlf_filter
));
344 f
->f
.version
= GIT_FILTER_VERSION
;
345 f
->f
.attributes
= "crlf eol text";
346 f
->f
.initialize
= NULL
;
347 f
->f
.shutdown
= git_filter_free
;
348 f
->f
.check
= crlf_check
;
349 f
->f
.apply
= crlf_apply
;
350 f
->f
.cleanup
= crlf_cleanup
;
352 return (git_filter
*)f
;