3 #include "run-command.h"
6 * convert.c - convert a file when checking it out and checking it in.
8 * This should use the pathname to decide on whether it wants to do some
9 * more interesting conversions (automatic gzip/unzip, general format
10 * conversions etc etc), but by default it just does automatic CRLF<->LF
11 * translation when the "auto_crlf" option is set.
14 #define CRLF_GUESS (-1)
20 /* CR, LF and CRLF counts */
21 unsigned cr
, lf
, crlf
;
23 /* These are just approximations! */
24 unsigned printable
, nonprintable
;
27 static void gather_stats(const char *buf
, unsigned long size
, struct text_stat
*stats
)
31 memset(stats
, 0, sizeof(*stats
));
33 for (i
= 0; i
< size
; i
++) {
34 unsigned char c
= buf
[i
];
37 if (i
+1 < size
&& buf
[i
+1] == '\n')
47 stats
->nonprintable
++;
50 /* BS, HT, ESC and FF */
51 case '\b': case '\t': case '\033': case '\014':
55 stats
->nonprintable
++;
64 * The same heuristics as diff.c::mmfile_is_binary()
66 static int is_binary(unsigned long size
, struct text_stat
*stats
)
69 if ((stats
->printable
>> 7) < stats
->nonprintable
)
72 * Other heuristics? Average line length might be relevant,
73 * as might LF vs CR vs CRLF counts..
75 * NOTE! It might be normal to have a low ratio of CRLF to LF
76 * (somebody starts with a LF-only file and edits it with an editor
77 * that adds CRLF only to lines that are added..). But do we
78 * want to support CR-only? Probably not.
83 static char *crlf_to_git(const char *path
, const char *src
, unsigned long *sizep
, int action
)
86 unsigned long size
, nsize
;
87 struct text_stat stats
;
89 if ((action
== CRLF_BINARY
) || (action
== CRLF_GUESS
&& !auto_crlf
))
96 gather_stats(src
, size
, &stats
);
98 /* No CR? Nothing to convert, regardless. */
102 if (action
== CRLF_GUESS
) {
104 * We're currently not going to even try to convert stuff
105 * that has bare CR characters. Does anybody do that crazy
108 if (stats
.cr
!= stats
.crlf
)
112 * And add some heuristics for binary vs text, of course...
114 if (is_binary(size
, &stats
))
119 * Ok, allocate a new buffer, fill it in, and return it
120 * to let the caller know that we switched buffers.
122 nsize
= size
- stats
.crlf
;
123 buffer
= xmalloc(nsize
);
127 if (action
== CRLF_GUESS
) {
129 * If we guessed, we already know we rejected a file with
130 * lone CR, and we can strip a CR without looking at what
134 unsigned char c
= *src
++;
140 unsigned char c
= *src
++;
141 if (! (c
== '\r' && (1 < size
&& *src
== '\n')))
149 static char *crlf_to_worktree(const char *path
, const char *src
, unsigned long *sizep
, int action
)
152 unsigned long size
, nsize
;
153 struct text_stat stats
;
156 if ((action
== CRLF_BINARY
) || (action
== CRLF_INPUT
) ||
157 (action
== CRLF_GUESS
&& auto_crlf
<= 0))
164 gather_stats(src
, size
, &stats
);
166 /* No LF? Nothing to convert, regardless. */
170 /* Was it already in CRLF format? */
171 if (stats
.lf
== stats
.crlf
)
174 if (action
== CRLF_GUESS
) {
175 /* If we have any bare CR characters, we're not going to touch it */
176 if (stats
.cr
!= stats
.crlf
)
179 if (is_binary(size
, &stats
))
184 * Ok, allocate a new buffer, fill it in, and return it
185 * to let the caller know that we switched buffers.
187 nsize
= size
+ stats
.lf
- stats
.crlf
;
188 buffer
= xmalloc(nsize
);
194 unsigned char c
= *src
++;
195 if (c
== '\n' && last
!= '\r')
204 static int filter_buffer(const char *path
, const char *src
,
205 unsigned long size
, const char *cmd
)
208 * Spawn cmd and feed the buffer contents through its stdin.
210 struct child_process child_process
;
212 int write_err
, status
;
214 memset(&child_process
, 0, sizeof(child_process
));
216 if (pipe(pipe_feed
) < 0) {
217 error("cannot create pipe to run external filter %s", cmd
);
221 child_process
.pid
= fork();
222 if (child_process
.pid
< 0) {
223 error("cannot fork to run external filter %s", cmd
);
228 if (!child_process
.pid
) {
229 dup2(pipe_feed
[0], 0);
232 execlp("sh", "sh", "-c", cmd
, NULL
);
237 write_err
= (write_in_full(pipe_feed
[1], src
, size
) < 0);
238 if (close(pipe_feed
[1]))
241 error("cannot feed the input to external filter %s", cmd
);
243 status
= finish_command(&child_process
);
245 error("external filter %s failed %d", cmd
, -status
);
246 return (write_err
|| status
);
249 static char *apply_filter(const char *path
, const char *src
,
250 unsigned long *sizep
, const char *cmd
)
253 * Create a pipeline to have the command filter the buffer's
256 * (child --> cmd) --> us
258 const int SLOP
= 4096;
262 unsigned long dstsize
, dstalloc
;
263 struct child_process child_process
;
268 memset(&child_process
, 0, sizeof(child_process
));
270 if (pipe(pipe_feed
) < 0) {
271 error("cannot create pipe to run external filter %s", cmd
);
276 child_process
.pid
= fork();
277 if (child_process
.pid
< 0) {
278 error("cannot fork to run external filter %s", cmd
);
283 if (!child_process
.pid
) {
284 dup2(pipe_feed
[1], 1);
287 exit(filter_buffer(path
, src
, *sizep
, cmd
));
292 dst
= xmalloc(dstalloc
);
296 ssize_t numread
= xread(pipe_feed
[0], dst
+ dstsize
,
302 error("read from external filter %s failed", cmd
);
308 if (dstalloc
<= dstsize
+ SLOP
) {
309 dstalloc
= dstsize
+ SLOP
;
310 dst
= xrealloc(dst
, dstalloc
);
313 if (close(pipe_feed
[0])) {
314 error("read from external filter %s failed", cmd
);
319 status
= finish_command(&child_process
);
321 error("external filter %s failed %d", cmd
, -status
);
331 static struct convert_driver
{
333 struct convert_driver
*next
;
336 } *user_convert
, **user_convert_tail
;
338 static int read_convert_config(const char *var
, const char *value
)
340 const char *ep
, *name
;
342 struct convert_driver
*drv
;
345 * External conversion drivers are configured using
346 * "filter.<name>.variable".
348 if (prefixcmp(var
, "filter.") || (ep
= strrchr(var
, '.')) == var
+ 6)
352 for (drv
= user_convert
; drv
; drv
= drv
->next
)
353 if (!strncmp(drv
->name
, name
, namelen
) && !drv
->name
[namelen
])
357 drv
= xcalloc(1, sizeof(struct convert_driver
));
358 namebuf
= xmalloc(namelen
+ 1);
359 memcpy(namebuf
, name
, namelen
);
360 namebuf
[namelen
] = 0;
363 *user_convert_tail
= drv
;
364 user_convert_tail
= &(drv
->next
);
370 * filter.<name>.smudge and filter.<name>.clean specifies
375 * The command-line will not be interpolated in any way.
378 if (!strcmp("smudge", ep
)) {
380 return error("%s: lacks value", var
);
381 drv
->smudge
= strdup(value
);
385 if (!strcmp("clean", ep
)) {
387 return error("%s: lacks value", var
);
388 drv
->clean
= strdup(value
);
394 static void setup_convert_check(struct git_attr_check
*check
)
396 static struct git_attr
*attr_crlf
;
397 static struct git_attr
*attr_ident
;
398 static struct git_attr
*attr_filter
;
401 attr_crlf
= git_attr("crlf", 4);
402 attr_ident
= git_attr("ident", 5);
403 attr_filter
= git_attr("filter", 6);
404 user_convert_tail
= &user_convert
;
405 git_config(read_convert_config
);
407 check
[0].attr
= attr_crlf
;
408 check
[1].attr
= attr_ident
;
409 check
[2].attr
= attr_filter
;
412 static int count_ident(const char *cp
, unsigned long size
)
415 * "$Id: 0000000000000000000000000000000000000000 $" <=> "$Id$"
427 if (memcmp("Id", cp
, 2))
438 * "$Id: ... "; scan up to the closing dollar sign and discard.
452 static char *ident_to_git(const char *path
, const char *src
, unsigned long *sizep
, int ident
)
461 cnt
= count_ident(src
, size
);
466 for (dst
= buf
; size
; size
--) {
469 if ((ch
== '$') && (3 <= size
) &&
470 !memcmp("Id:", src
, 3)) {
471 unsigned long rem
= size
- 3;
472 const char *cp
= src
+ 3;
481 memcpy(dst
, "Id$", 3);
492 static char *ident_to_worktree(const char *path
, const char *src
, unsigned long *sizep
, int ident
)
497 unsigned char sha1
[20];
503 cnt
= count_ident(src
, size
);
507 hash_sha1_file(src
, size
, "blob", sha1
);
508 buf
= xmalloc(size
+ cnt
* 43);
510 for (dst
= buf
; size
; size
--) {
514 if ((ch
!= '$') || (size
< 3) || memcmp("Id", src
, 2))
518 /* discard up to but not including the closing $ */
519 unsigned long rem
= size
- 3;
530 } else if (src
[2] == '$')
535 memcpy(dst
, "Id: ", 4);
537 memcpy(dst
, sha1_to_hex(sha1
), 40);
550 static int git_path_check_crlf(const char *path
, struct git_attr_check
*check
)
552 const char *value
= check
->value
;
554 if (ATTR_TRUE(value
))
556 else if (ATTR_FALSE(value
))
558 else if (ATTR_UNSET(value
))
560 else if (!strcmp(value
, "input"))
565 static struct convert_driver
*git_path_check_convert(const char *path
,
566 struct git_attr_check
*check
)
568 const char *value
= check
->value
;
569 struct convert_driver
*drv
;
571 if (ATTR_TRUE(value
) || ATTR_FALSE(value
) || ATTR_UNSET(value
))
573 for (drv
= user_convert
; drv
; drv
= drv
->next
)
574 if (!strcmp(value
, drv
->name
))
579 static int git_path_check_ident(const char *path
, struct git_attr_check
*check
)
581 const char *value
= check
->value
;
583 return !!ATTR_TRUE(value
);
586 char *convert_to_git(const char *path
, const char *src
, unsigned long *sizep
)
588 struct git_attr_check check
[3];
589 int crlf
= CRLF_GUESS
;
594 setup_convert_check(check
);
595 if (!git_checkattr(path
, ARRAY_SIZE(check
), check
)) {
596 struct convert_driver
*drv
;
597 crlf
= git_path_check_crlf(path
, check
+ 0);
598 ident
= git_path_check_ident(path
, check
+ 1);
599 drv
= git_path_check_convert(path
, check
+ 2);
600 if (drv
&& drv
->clean
)
604 buf
= apply_filter(path
, src
, sizep
, filter
);
606 buf2
= crlf_to_git(path
, buf
? buf
: src
, sizep
, crlf
);
612 buf2
= ident_to_git(path
, buf
? buf
: src
, sizep
, ident
);
621 char *convert_to_working_tree(const char *path
, const char *src
, unsigned long *sizep
)
623 struct git_attr_check check
[3];
624 int crlf
= CRLF_GUESS
;
629 setup_convert_check(check
);
630 if (!git_checkattr(path
, ARRAY_SIZE(check
), check
)) {
631 struct convert_driver
*drv
;
632 crlf
= git_path_check_crlf(path
, check
+ 0);
633 ident
= git_path_check_ident(path
, check
+ 1);
634 drv
= git_path_check_convert(path
, check
+ 2);
635 if (drv
&& drv
->smudge
)
636 filter
= drv
->smudge
;
639 buf
= ident_to_worktree(path
, src
, sizep
, ident
);
641 buf2
= crlf_to_worktree(path
, buf
? buf
: src
, sizep
, crlf
);
647 buf2
= apply_filter(path
, buf
? buf
: src
, sizep
, filter
);