Skip to content

Commit 9502751

Browse files
committed
Merge branch 'jn/apply-filename-with-sp'
* jn/apply-filename-with-sp: apply: handle traditional patches with space in filename tests: exercise "git apply" with weird filenames apply: split quoted filename handling into new function
2 parents 460645a + 5a12c88 commit 9502751

21 files changed

+456
-41
lines changed

builtin/apply.c

Lines changed: 211 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -416,48 +416,190 @@ static char *squash_slash(char *name)
416416
return name;
417417
}
418418

419-
static char *find_name(const char *line, char *def, int p_value, int terminate)
419+
static char *find_name_gnu(const char *line, char *def, int p_value)
420420
{
421-
int len;
422-
const char *start = NULL;
421+
struct strbuf name = STRBUF_INIT;
422+
char *cp;
423423

424-
if (p_value == 0)
425-
start = line;
424+
/*
425+
* Proposed "new-style" GNU patch/diff format; see
426+
* http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
427+
*/
428+
if (unquote_c_style(&name, line, NULL)) {
429+
strbuf_release(&name);
430+
return NULL;
431+
}
426432

427-
if (*line == '"') {
428-
struct strbuf name = STRBUF_INIT;
433+
for (cp = name.buf; p_value; p_value--) {
434+
cp = strchr(cp, '/');
435+
if (!cp) {
436+
strbuf_release(&name);
437+
return NULL;
438+
}
439+
cp++;
440+
}
429441

430-
/*
431-
* Proposed "new-style" GNU patch/diff format; see
432-
* http://marc.theaimsgroup.com/?l=git&m=112927316408690&w=2
433-
*/
434-
if (!unquote_c_style(&name, line, NULL)) {
435-
char *cp;
442+
/* name can later be freed, so we need
443+
* to memmove, not just return cp
444+
*/
445+
strbuf_remove(&name, 0, cp - name.buf);
446+
free(def);
447+
if (root)
448+
strbuf_insert(&name, 0, root, root_len);
449+
return squash_slash(strbuf_detach(&name, NULL));
450+
}
436451

437-
for (cp = name.buf; p_value; p_value--) {
438-
cp = strchr(cp, '/');
439-
if (!cp)
440-
break;
441-
cp++;
442-
}
443-
if (cp) {
444-
/* name can later be freed, so we need
445-
* to memmove, not just return cp
446-
*/
447-
strbuf_remove(&name, 0, cp - name.buf);
448-
free(def);
449-
if (root)
450-
strbuf_insert(&name, 0, root, root_len);
451-
return squash_slash(strbuf_detach(&name, NULL));
452-
}
453-
}
454-
strbuf_release(&name);
452+
static size_t tz_len(const char *line, size_t len)
453+
{
454+
const char *tz, *p;
455+
456+
if (len < strlen(" +0500") || line[len-strlen(" +0500")] != ' ')
457+
return 0;
458+
tz = line + len - strlen(" +0500");
459+
460+
if (tz[1] != '+' && tz[1] != '-')
461+
return 0;
462+
463+
for (p = tz + 2; p != line + len; p++)
464+
if (!isdigit(*p))
465+
return 0;
466+
467+
return line + len - tz;
468+
}
469+
470+
static size_t date_len(const char *line, size_t len)
471+
{
472+
const char *date, *p;
473+
474+
if (len < strlen("72-02-05") || line[len-strlen("-05")] != '-')
475+
return 0;
476+
p = date = line + len - strlen("72-02-05");
477+
478+
if (!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
479+
!isdigit(*p++) || !isdigit(*p++) || *p++ != '-' ||
480+
!isdigit(*p++) || !isdigit(*p++)) /* Not a date. */
481+
return 0;
482+
483+
if (date - line >= strlen("19") &&
484+
isdigit(date[-1]) && isdigit(date[-2])) /* 4-digit year */
485+
date -= strlen("19");
486+
487+
return line + len - date;
488+
}
489+
490+
static size_t short_time_len(const char *line, size_t len)
491+
{
492+
const char *time, *p;
493+
494+
if (len < strlen(" 07:01:32") || line[len-strlen(":32")] != ':')
495+
return 0;
496+
p = time = line + len - strlen(" 07:01:32");
497+
498+
/* Permit 1-digit hours? */
499+
if (*p++ != ' ' ||
500+
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
501+
!isdigit(*p++) || !isdigit(*p++) || *p++ != ':' ||
502+
!isdigit(*p++) || !isdigit(*p++)) /* Not a time. */
503+
return 0;
504+
505+
return line + len - time;
506+
}
507+
508+
static size_t fractional_time_len(const char *line, size_t len)
509+
{
510+
const char *p;
511+
size_t n;
512+
513+
/* Expected format: 19:41:17.620000023 */
514+
if (!len || !isdigit(line[len - 1]))
515+
return 0;
516+
p = line + len - 1;
517+
518+
/* Fractional seconds. */
519+
while (p > line && isdigit(*p))
520+
p--;
521+
if (*p != '.')
522+
return 0;
523+
524+
/* Hours, minutes, and whole seconds. */
525+
n = short_time_len(line, p - line);
526+
if (!n)
527+
return 0;
528+
529+
return line + len - p + n;
530+
}
531+
532+
static size_t trailing_spaces_len(const char *line, size_t len)
533+
{
534+
const char *p;
535+
536+
/* Expected format: ' ' x (1 or more) */
537+
if (!len || line[len - 1] != ' ')
538+
return 0;
539+
540+
p = line + len;
541+
while (p != line) {
542+
p--;
543+
if (*p != ' ')
544+
return line + len - (p + 1);
455545
}
456546

457-
for (;;) {
547+
/* All spaces! */
548+
return len;
549+
}
550+
551+
static size_t diff_timestamp_len(const char *line, size_t len)
552+
{
553+
const char *end = line + len;
554+
size_t n;
555+
556+
/*
557+
* Posix: 2010-07-05 19:41:17
558+
* GNU: 2010-07-05 19:41:17.620000023 -0500
559+
*/
560+
561+
if (!isdigit(end[-1]))
562+
return 0;
563+
564+
n = tz_len(line, end - line);
565+
end -= n;
566+
567+
n = short_time_len(line, end - line);
568+
if (!n)
569+
n = fractional_time_len(line, end - line);
570+
end -= n;
571+
572+
n = date_len(line, end - line);
573+
if (!n) /* No date. Too bad. */
574+
return 0;
575+
end -= n;
576+
577+
if (end == line) /* No space before date. */
578+
return 0;
579+
if (end[-1] == '\t') { /* Success! */
580+
end--;
581+
return line + len - end;
582+
}
583+
if (end[-1] != ' ') /* No space before date. */
584+
return 0;
585+
586+
/* Whitespace damage. */
587+
end -= trailing_spaces_len(line, end - line);
588+
return line + len - end;
589+
}
590+
591+
static char *find_name_common(const char *line, char *def, int p_value,
592+
const char *end, int terminate)
593+
{
594+
int len;
595+
const char *start = NULL;
596+
597+
if (p_value == 0)
598+
start = line;
599+
while (line != end) {
458600
char c = *line;
459601

460-
if (isspace(c)) {
602+
if (!end && isspace(c)) {
461603
if (c == '\n')
462604
break;
463605
if (name_terminate(start, line-start, c, terminate))
@@ -497,6 +639,37 @@ static char *find_name(const char *line, char *def, int p_value, int terminate)
497639
return squash_slash(xmemdupz(start, len));
498640
}
499641

642+
static char *find_name(const char *line, char *def, int p_value, int terminate)
643+
{
644+
if (*line == '"') {
645+
char *name = find_name_gnu(line, def, p_value);
646+
if (name)
647+
return name;
648+
}
649+
650+
return find_name_common(line, def, p_value, NULL, terminate);
651+
}
652+
653+
static char *find_name_traditional(const char *line, char *def, int p_value)
654+
{
655+
size_t len = strlen(line);
656+
size_t date_len;
657+
658+
if (*line == '"') {
659+
char *name = find_name_gnu(line, def, p_value);
660+
if (name)
661+
return name;
662+
}
663+
664+
len = strchrnul(line, '\n') - line;
665+
date_len = diff_timestamp_len(line, len);
666+
if (!date_len)
667+
return find_name_common(line, def, p_value, NULL, TERM_TAB);
668+
len -= date_len;
669+
670+
return find_name_common(line, def, p_value, line + len, 0);
671+
}
672+
500673
static int count_slashes(const char *cp)
501674
{
502675
int cnt = 0;
@@ -519,7 +692,7 @@ static int guess_p_value(const char *nameline)
519692

520693
if (is_dev_null(nameline))
521694
return -1;
522-
name = find_name(nameline, NULL, 0, TERM_SPACE | TERM_TAB);
695+
name = find_name_traditional(nameline, NULL, 0);
523696
if (!name)
524697
return -1;
525698
cp = strchr(name, '/');
@@ -638,16 +811,16 @@ static void parse_traditional_patch(const char *first, const char *second, struc
638811
if (is_dev_null(first)) {
639812
patch->is_new = 1;
640813
patch->is_delete = 0;
641-
name = find_name(second, NULL, p_value, TERM_SPACE | TERM_TAB);
814+
name = find_name_traditional(second, NULL, p_value);
642815
patch->new_name = name;
643816
} else if (is_dev_null(second)) {
644817
patch->is_new = 0;
645818
patch->is_delete = 1;
646-
name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
819+
name = find_name_traditional(first, NULL, p_value);
647820
patch->old_name = name;
648821
} else {
649-
name = find_name(first, NULL, p_value, TERM_SPACE | TERM_TAB);
650-
name = find_name(second, name, p_value, TERM_SPACE | TERM_TAB);
822+
name = find_name_traditional(first, NULL, p_value);
823+
name = find_name_traditional(second, name, p_value);
651824
if (has_epoch_timestamp(first)) {
652825
patch->is_new = 1;
653826
patch->is_delete = 0;

t/t4120-apply-popt.sh

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,50 @@ test_description='git apply -p handling.'
1010
test_expect_success setup '
1111
mkdir sub &&
1212
echo A >sub/file1 &&
13-
cp sub/file1 file1 &&
13+
cp sub/file1 file1.saved &&
1414
git add sub/file1 &&
1515
echo B >sub/file1 &&
1616
git diff >patch.file &&
17-
rm sub/file1 &&
18-
rmdir sub
17+
git checkout -- sub/file1 &&
18+
git mv sub süb &&
19+
echo B >süb/file1 &&
20+
git diff >patch.escaped &&
21+
grep "[\]" patch.escaped &&
22+
rm süb/file1 &&
23+
rmdir süb
1924
'
2025

2126
test_expect_success 'apply git diff with -p2' '
27+
cp file1.saved file1 &&
2228
git apply -p2 patch.file
2329
'
2430

2531
test_expect_success 'apply with too large -p' '
32+
cp file1.saved file1 &&
2633
test_must_fail git apply --stat -p3 patch.file 2>err &&
2734
grep "removing 3 leading" err
2835
'
2936

37+
test_expect_success 'apply (-p2) traditional diff with funny filenames' '
38+
cat >patch.quotes <<-\EOF &&
39+
diff -u "a/"sub/file1 "b/"sub/file1
40+
--- "a/"sub/file1
41+
+++ "b/"sub/file1
42+
@@ -1 +1 @@
43+
-A
44+
+B
45+
EOF
46+
echo B >expected &&
47+
48+
cp file1.saved file1 &&
49+
git apply -p2 patch.quotes &&
50+
test_cmp expected file1
51+
'
52+
53+
test_expect_success 'apply with too large -p and fancy filename' '
54+
cp file1.saved file1 &&
55+
test_must_fail git apply --stat -p3 patch.escaped 2>err &&
56+
grep "removing 3 leading" err
57+
'
58+
3059
test_done

0 commit comments

Comments
 (0)