File Coverage

deps/libgit2/src/libgit2/crlf.c
Criterion Covered Total %
statement 120 180 66.6
branch 71 158 44.9
condition n/a
subroutine n/a
pod n/a
total 191 338 56.5


line stmt bran cond sub pod time code
1             /*
2             * Copyright (C) the libgit2 contributors. All rights reserved.
3             *
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.
6             */
7              
8             #include "common.h"
9              
10             #include "git2/attr.h"
11             #include "git2/blob.h"
12             #include "git2/index.h"
13             #include "git2/sys/filter.h"
14              
15             #include "buf.h"
16             #include "futils.h"
17             #include "hash.h"
18             #include "filter.h"
19             #include "repository.h"
20              
21             typedef enum {
22             GIT_CRLF_UNDEFINED,
23             GIT_CRLF_BINARY,
24             GIT_CRLF_TEXT,
25             GIT_CRLF_TEXT_INPUT,
26             GIT_CRLF_TEXT_CRLF,
27             GIT_CRLF_AUTO,
28             GIT_CRLF_AUTO_INPUT,
29             GIT_CRLF_AUTO_CRLF
30             } git_crlf_t;
31              
32             struct crlf_attrs {
33             int attr_action; /* the .gitattributes setting */
34             int crlf_action; /* the core.autocrlf setting */
35              
36             int auto_crlf;
37             int safe_crlf;
38             int core_eol;
39             };
40              
41             struct crlf_filter {
42             git_filter f;
43             };
44              
45 520           static git_crlf_t check_crlf(const char *value)
46             {
47 520 50         if (GIT_ATTR_IS_TRUE(value))
48 0           return GIT_CRLF_TEXT;
49 520 50         else if (GIT_ATTR_IS_FALSE(value))
50 0           return GIT_CRLF_BINARY;
51 520 50         else if (GIT_ATTR_IS_UNSPECIFIED(value))
52             ;
53 0 0         else if (strcmp(value, "input") == 0)
54 0           return GIT_CRLF_TEXT_INPUT;
55 0 0         else if (strcmp(value, "auto") == 0)
56 0           return GIT_CRLF_AUTO;
57              
58 520           return GIT_CRLF_UNDEFINED;
59             }
60              
61 260           static git_configmap_value check_eol(const char *value)
62             {
63 260 50         if (GIT_ATTR_IS_UNSPECIFIED(value))
64             ;
65 0 0         else if (strcmp(value, "lf") == 0)
66 0           return GIT_EOL_LF;
67 0 0         else if (strcmp(value, "crlf") == 0)
68 0           return GIT_EOL_CRLF;
69              
70 260           return GIT_EOL_UNSET;
71             }
72              
73 57           static int has_cr_in_index(const git_filter_source *src)
74             {
75 57           git_repository *repo = git_filter_source_repo(src);
76 57           const char *path = git_filter_source_path(src);
77             git_index *index;
78             const git_index_entry *entry;
79             git_blob *blob;
80             const void *blobcontent;
81             git_object_size_t blobsize;
82             bool found_cr;
83              
84 57 50         if (!path)
85 0           return false;
86              
87 57 50         if (git_repository_index__weakptr(&index, repo) < 0) {
88 0           git_error_clear();
89 0           return false;
90             }
91              
92 57 100         if (!(entry = git_index_get_bypath(index, path, 0)) &&
    100          
93 11           !(entry = git_index_get_bypath(index, path, 1)))
94 10           return false;
95              
96 47 50         if (!S_ISREG(entry->mode)) /* don't crlf filter non-blobs */
97 0           return true;
98              
99 47 50         if (git_blob_lookup(&blob, repo, &entry->id) < 0)
100 0           return false;
101              
102 47           blobcontent = git_blob_rawcontent(blob);
103 47           blobsize = git_blob_rawsize(blob);
104 47 50         if (!git__is_sizet(blobsize))
105 0           blobsize = (size_t)-1;
106              
107 47 50         found_cr = (blobcontent != NULL &&
108 94 50         blobsize > 0 &&
    50          
109 47           memchr(blobcontent, '\r', (size_t)blobsize) != NULL);
110              
111 47           git_blob_free(blob);
112 57           return found_cr;
113             }
114              
115 0           static int text_eol_is_crlf(struct crlf_attrs *ca)
116             {
117 0 0         if (ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
118 0           return 1;
119 0 0         else if (ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
120 0           return 0;
121              
122 0 0         if (ca->core_eol == GIT_EOL_CRLF)
123 0           return 1;
124             if (ca->core_eol == GIT_EOL_UNSET && GIT_EOL_NATIVE == GIT_EOL_CRLF)
125             return 1;
126              
127 0           return 0;
128             }
129              
130 16           static git_configmap_value output_eol(struct crlf_attrs *ca)
131             {
132 16           switch (ca->crlf_action) {
133             case GIT_CRLF_BINARY:
134 0           return GIT_EOL_UNSET;
135             case GIT_CRLF_TEXT_CRLF:
136 0           return GIT_EOL_CRLF;
137             case GIT_CRLF_TEXT_INPUT:
138 0           return GIT_EOL_LF;
139             case GIT_CRLF_UNDEFINED:
140             case GIT_CRLF_AUTO_CRLF:
141 6           return GIT_EOL_CRLF;
142             case GIT_CRLF_AUTO_INPUT:
143 10           return GIT_EOL_LF;
144             case GIT_CRLF_TEXT:
145             case GIT_CRLF_AUTO:
146 0 0         return text_eol_is_crlf(ca) ? GIT_EOL_CRLF : GIT_EOL_LF;
147             }
148              
149             /* TODO: warn when available */
150 0           return ca->core_eol;
151             }
152              
153 57           GIT_INLINE(int) check_safecrlf(
154             struct crlf_attrs *ca,
155             const git_filter_source *src,
156             git_str_text_stats *stats)
157             {
158 57           const char *filename = git_filter_source_path(src);
159              
160 57 50         if (!ca->safe_crlf)
161 57           return 0;
162              
163 0 0         if (output_eol(ca) == GIT_EOL_LF) {
164             /*
165             * CRLFs would not be restored by checkout:
166             * check if we'd remove CRLFs
167             */
168 0 0         if (stats->crlf) {
169 0 0         if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) {
170             /* TODO: issue a warning when available */
171             } else {
172 0 0         if (filename && *filename)
    0          
173 0           git_error_set(
174             GIT_ERROR_FILTER, "CRLF would be replaced by LF in '%s'",
175             filename);
176             else
177 0           git_error_set(
178             GIT_ERROR_FILTER, "CRLF would be replaced by LF");
179              
180 0           return -1;
181             }
182             }
183 0 0         } else if (output_eol(ca) == GIT_EOL_CRLF) {
184             /*
185             * CRLFs would be added by checkout:
186             * check if we have "naked" LFs
187             */
188 0 0         if (stats->crlf != stats->lf) {
189 0 0         if (ca->safe_crlf == GIT_SAFE_CRLF_WARN) {
190             /* TODO: issue a warning when available */
191             } else {
192 0 0         if (filename && *filename)
    0          
193 0           git_error_set(
194             GIT_ERROR_FILTER, "LF would be replaced by CRLF in '%s'",
195             filename);
196             else
197 0           git_error_set(
198             GIT_ERROR_FILTER, "LF would be replaced by CRLF");
199              
200 0           return -1;
201             }
202             }
203             }
204              
205 0           return 0;
206             }
207              
208 59           static int crlf_apply_to_odb(
209             struct crlf_attrs *ca,
210             git_str *to,
211             const git_str *from,
212             const git_filter_source *src)
213             {
214             git_str_text_stats stats;
215             bool is_binary;
216             int error;
217              
218             /* Binary attribute? Empty file? Nothing to do */
219 59 50         if (ca->crlf_action == GIT_CRLF_BINARY || from->size == 0)
    100          
220 2           return GIT_PASSTHROUGH;
221              
222 57           is_binary = git_str_gather_text_stats(&stats, from, false);
223              
224             /* Heuristics to see if we can skip the conversion.
225             * Straight from Core Git.
226             */
227 57 50         if (ca->crlf_action == GIT_CRLF_AUTO ||
    100          
228 12 50         ca->crlf_action == GIT_CRLF_AUTO_INPUT ||
229 12           ca->crlf_action == GIT_CRLF_AUTO_CRLF) {
230              
231 57 50         if (is_binary)
232 0           return GIT_PASSTHROUGH;
233              
234             /*
235             * If the file in the index has any CR in it, do not convert.
236             * This is the new safer autocrlf handling.
237             */
238 57 50         if (has_cr_in_index(src))
239 0           return GIT_PASSTHROUGH;
240             }
241              
242 57 50         if ((error = check_safecrlf(ca, src, &stats)) < 0)
243 0           return error;
244              
245             /* If there are no CR characters to filter out, then just pass */
246 57 100         if (!stats.crlf)
247 54           return GIT_PASSTHROUGH;
248              
249             /* Actually drop the carriage returns */
250 59           return git_str_crlf_to_lf(to, from);
251             }
252              
253 16           static int crlf_apply_to_workdir(
254             struct crlf_attrs *ca,
255             git_str *to,
256             const git_str *from)
257             {
258             git_str_text_stats stats;
259             bool is_binary;
260              
261             /* Empty file? Nothing to do. */
262 16 50         if (git_str_len(from) == 0 || output_eol(ca) != GIT_EOL_CRLF)
    100          
263 10           return GIT_PASSTHROUGH;
264              
265 6           is_binary = git_str_gather_text_stats(&stats, from, false);
266              
267             /* If there are no LFs, or all LFs are part of a CRLF, nothing to do */
268 6 100         if (stats.lf == 0 || stats.lf == stats.crlf)
    50          
269 3           return GIT_PASSTHROUGH;
270              
271 3 50         if (ca->crlf_action == GIT_CRLF_AUTO ||
    50          
272 3 50         ca->crlf_action == GIT_CRLF_AUTO_INPUT ||
273 3           ca->crlf_action == GIT_CRLF_AUTO_CRLF) {
274              
275             /* If we have any existing CR or CRLF line endings, do nothing */
276 3 50         if (stats.cr > 0)
277 0           return GIT_PASSTHROUGH;
278              
279             /* Don't filter binary files */
280 3 50         if (is_binary)
281 0           return GIT_PASSTHROUGH;
282             }
283              
284 16           return git_str_lf_to_crlf(to, from);
285             }
286              
287 260           static int convert_attrs(
288             struct crlf_attrs *ca,
289             const char **attr_values,
290             const git_filter_source *src)
291             {
292             int error;
293              
294 260           memset(ca, 0, sizeof(struct crlf_attrs));
295              
296 260 50         if ((error = git_repository__configmap_lookup(&ca->auto_crlf,
297 260 50         git_filter_source_repo(src), GIT_CONFIGMAP_AUTO_CRLF)) < 0 ||
298 260           (error = git_repository__configmap_lookup(&ca->safe_crlf,
299 260 50         git_filter_source_repo(src), GIT_CONFIGMAP_SAFE_CRLF)) < 0 ||
300 260           (error = git_repository__configmap_lookup(&ca->core_eol,
301             git_filter_source_repo(src), GIT_CONFIGMAP_EOL)) < 0)
302 0           return error;
303              
304             /* downgrade FAIL to WARN if ALLOW_UNSAFE option is used */
305 260 100         if ((git_filter_source_flags(src) & GIT_FILTER_ALLOW_UNSAFE) &&
    50          
306 160           ca->safe_crlf == GIT_SAFE_CRLF_FAIL)
307 0           ca->safe_crlf = GIT_SAFE_CRLF_WARN;
308              
309 260 50         if (attr_values) {
310             /* load the text attribute */
311 260           ca->crlf_action = check_crlf(attr_values[2]); /* text */
312              
313 260 50         if (ca->crlf_action == GIT_CRLF_UNDEFINED)
314 260           ca->crlf_action = check_crlf(attr_values[0]); /* crlf */
315              
316 260 50         if (ca->crlf_action != GIT_CRLF_BINARY) {
317             /* load the eol attribute */
318 260           int eol_attr = check_eol(attr_values[1]);
319              
320 260 50         if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_LF)
    0          
321 0           ca->crlf_action = GIT_CRLF_AUTO_INPUT;
322 260 50         else if (ca->crlf_action == GIT_CRLF_AUTO && eol_attr == GIT_EOL_CRLF)
    0          
323 0           ca->crlf_action = GIT_CRLF_AUTO_CRLF;
324 260 50         else if (eol_attr == GIT_EOL_LF)
325 0           ca->crlf_action = GIT_CRLF_TEXT_INPUT;
326 260 50         else if (eol_attr == GIT_EOL_CRLF)
327 0           ca->crlf_action = GIT_CRLF_TEXT_CRLF;
328             }
329              
330 260           ca->attr_action = ca->crlf_action;
331             } else {
332 0           ca->crlf_action = GIT_CRLF_UNDEFINED;
333             }
334              
335 260 50         if (ca->crlf_action == GIT_CRLF_TEXT)
336 0 0         ca->crlf_action = text_eol_is_crlf(ca) ? GIT_CRLF_TEXT_CRLF : GIT_CRLF_TEXT_INPUT;
337 260 50         if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_FALSE)
    100          
338 189           ca->crlf_action = GIT_CRLF_BINARY;
339 260 100         if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_TRUE)
    100          
340 16           ca->crlf_action = GIT_CRLF_AUTO_CRLF;
341 260 100         if (ca->crlf_action == GIT_CRLF_UNDEFINED && ca->auto_crlf == GIT_AUTO_CRLF_INPUT)
    50          
342 55           ca->crlf_action = GIT_CRLF_AUTO_INPUT;
343              
344 260           return 0;
345             }
346              
347 260           static int crlf_check(
348             git_filter *self,
349             void **payload, /* points to NULL ptr on entry, may be set */
350             const git_filter_source *src,
351             const char **attr_values)
352             {
353             struct crlf_attrs ca;
354              
355 260           GIT_UNUSED(self);
356              
357 260           convert_attrs(&ca, attr_values, src);
358              
359 260 100         if (ca.crlf_action == GIT_CRLF_BINARY)
360 189           return GIT_PASSTHROUGH;
361              
362 71           *payload = git__malloc(sizeof(ca));
363 71 50         GIT_ERROR_CHECK_ALLOC(*payload);
364 71           memcpy(*payload, &ca, sizeof(ca));
365              
366 260           return 0;
367             }
368              
369 75           static int crlf_apply(
370             git_filter *self,
371             void **payload, /* may be read and/or set */
372             git_str *to,
373             const git_str *from,
374             const git_filter_source *src)
375             {
376 75           int error = 0;
377              
378             /* initialize payload in case `check` was bypassed */
379 75 50         if (!*payload) {
380 0 0         if ((error = crlf_check(self, payload, src, NULL)) < 0)
381 0           return error;
382             }
383              
384 75 100         if (git_filter_source_mode(src) == GIT_FILTER_SMUDGE)
385 16           error = crlf_apply_to_workdir(*payload, to, from);
386             else
387 59           error = crlf_apply_to_odb(*payload, to, from, src);
388              
389 75           return error;
390             }
391              
392 75           static int crlf_stream(
393             git_writestream **out,
394             git_filter *self,
395             void **payload,
396             const git_filter_source *src,
397             git_writestream *next)
398             {
399 75           return git_filter_buffered_stream_new(out,
400             self, crlf_apply, NULL, payload, src, next);
401             }
402              
403 71           static void crlf_cleanup(
404             git_filter *self,
405             void *payload)
406             {
407 71           GIT_UNUSED(self);
408 71           git__free(payload);
409 71           }
410              
411 87           git_filter *git_crlf_filter_new(void)
412             {
413 87           struct crlf_filter *f = git__calloc(1, sizeof(struct crlf_filter));
414 87 50         if (f == NULL)
415 0           return NULL;
416              
417 87           f->f.version = GIT_FILTER_VERSION;
418 87           f->f.attributes = "crlf eol text";
419 87           f->f.initialize = NULL;
420 87           f->f.shutdown = git_filter_free;
421 87           f->f.check = crlf_check;
422 87           f->f.stream = crlf_stream;
423 87           f->f.cleanup = crlf_cleanup;
424              
425 87           return (git_filter *)f;
426             }