commit a92a20426c3078772e9cc8671b4c58ed9a07b9bf from: Omar Polo date: Sat Jul 02 21:16:13 2022 UTC got patch: handle mangled whitespaces This makes 'got patch' try to ignore whitespaces when trying to match a hunk. Discused with and ok stsp@ commit - 5dffb1a14a4e706b322f648797d0fea44e684aa8 commit + a92a20426c3078772e9cc8671b4c58ed9a07b9bf blob - 5f7f00007937a048564e685aac0a8c6b9e98adab blob + 4e69daf4498d68765b5db8f7264410bc4a6d7b41 --- got/got.c +++ got/got.c @@ -7758,7 +7758,7 @@ static const struct got_error * patch_progress(void *arg, const char *old, const char *new, unsigned char status, const struct got_error *error, long old_from, long old_lines, long new_from, long new_lines, long offset, - const struct got_error *hunk_err) + int ws_mangled, const struct got_error *hunk_err) { const char *path = new == NULL ? old : new; @@ -7771,13 +7771,15 @@ patch_progress(void *arg, const char *old, const char if (error != NULL) fprintf(stderr, "%s: %s\n", getprogname(), error->msg); - if (offset != 0 || hunk_err != NULL) { + if (offset != 0 || hunk_err != NULL || ws_mangled) { printf("@@ -%ld,%ld +%ld,%ld @@ ", old_from, old_lines, new_from, new_lines); if (hunk_err != NULL) printf("%s\n", hunk_err->msg); - else + else if (offset != 0) printf("applied with offset %ld\n", offset); + else + printf("hunk contains mangled whitespace\n"); } return NULL; blob - bc2e95bb4fa535617bc28f76935a78e36dea8a3e blob + e72ded72ccf8311606f1c945e5241311de5cc179 --- include/got_patch.h +++ include/got_patch.h @@ -22,7 +22,7 @@ */ typedef const struct got_error *(*got_patch_progress_cb)(void *, const char *, const char *, unsigned char, const struct got_error *, - long, long, long, long, long, const struct got_error *); + long, long, long, long, long, int, const struct got_error *); /* * Apply the (already opened) patch to the repository and register the blob - 2cd5efa8a0d460c9850691dc1feb2a9f38ad1927 blob + f12c747f214e8e4ae165a7c3b1b15212bfdfa531 --- lib/patch.c +++ lib/patch.c @@ -26,6 +26,7 @@ #include #include +#include #include #include #include @@ -58,6 +59,7 @@ struct got_patch_hunk { STAILQ_ENTRY(got_patch_hunk) entries; const struct got_error *err; + int ws_mangled; int offset; int old_nonl; int new_nonl; @@ -399,6 +401,30 @@ locate_hunk(FILE *orig, struct got_patch_hunk *h, off_ free(line); return err; +} + +static int +linecmp(const char *a, const char *b, int *mangled) +{ + int c; + + *mangled = 0; + c = strcmp(a, b); + if (c == 0) + return c; + + *mangled = 1; + for (;;) { + while (isspace(*a)) + a++; + while (isspace(*b)) + b++; + if (*a == '\0' || *b == '\0' || *a != *b) + break; + a++, b++; + } + + return *a - *b; } static const struct got_error * @@ -408,6 +434,7 @@ test_hunk(FILE *orig, struct got_patch_hunk *h) char *line = NULL; size_t linesize = 0, i = 0; ssize_t linelen; + int mangled; for (i = 0; i < h->len; ++i) { switch (*h->lines[i]) { @@ -426,10 +453,12 @@ test_hunk(FILE *orig, struct got_patch_hunk *h) } if (line[linelen - 1] == '\n') line[linelen - 1] = '\0'; - if (strcmp(h->lines[i] + 1, line)) { + if (linecmp(h->lines[i] + 1, line, &mangled)) { err = got_error(GOT_ERR_HUNK_FAILED); goto done; } + if (mangled) + h->ws_mangled = 1; break; } } @@ -440,32 +469,61 @@ done: } static const struct got_error * -apply_hunk(FILE *tmp, struct got_patch_hunk *h, int *lineno) +apply_hunk(FILE *orig, FILE *tmp, struct got_patch_hunk *h, int *lineno, + off_t from) { - size_t i, new = 0; + const struct got_error *err = NULL; + const char *t; + size_t linesize = 0, i, new = 0; + char *line = NULL; + char mode; + ssize_t linelen; + if (orig != NULL && fseeko(orig, from, SEEK_SET) == -1) + return got_error_from_errno("fseeko"); + for (i = 0; i < h->len; ++i) { - switch (*h->lines[i]) { - case ' ': - if (fprintf(tmp, "%s\n", h->lines[i] + 1) < 0) - return got_error_from_errno("fprintf"); - /* fallthrough */ + switch (mode = *h->lines[i]) { case '-': + case ' ': (*lineno)++; + if (orig != NULL) { + linelen = getline(&line, &linesize, orig); + if (linelen == -1) { + err = got_error_from_errno("getline"); + goto done; + } + if (line[linelen - 1] == '\n') + line[linelen - 1] = '\0'; + t = line; + } else + t = h->lines[i] + 1; + if (mode == '-') + continue; + if (fprintf(tmp, "%s\n", t) < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } break; case '+': new++; - if (fprintf(tmp, "%s", h->lines[i] + 1) < 0) - return got_error_from_errno("fprintf"); + if (fprintf(tmp, "%s", h->lines[i] + 1) < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } if (new != h->new_lines || !h->new_nonl) { - if (fprintf(tmp, "\n") < 0) - return got_error_from_errno( - "fprintf"); + if (fprintf(tmp, "\n") < 0) { + err = got_error_from_errno("fprintf"); + goto done; + } } break; } } - return NULL; + +done: + free(line); + return err; } static const struct got_error * @@ -484,7 +542,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp) h = STAILQ_FIRST(&p->head); if (h == NULL || STAILQ_NEXT(h, entries) != NULL) return got_error(GOT_ERR_PATCH_MALFORMED); - return apply_hunk(tmp, h, &lineno); + return apply_hunk(orig, tmp, h, &lineno, 0); } if (fstat(fileno(orig), &sb) == -1) @@ -523,7 +581,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp) if (lineno + 1 != h->old_from) h->offset = lineno + 1 - h->old_from; - err = apply_hunk(tmp, h, &lineno); + err = apply_hunk(orig, tmp, h, &lineno, pos); if (err != NULL) return err; @@ -550,17 +608,17 @@ report_progress(struct patch_args *pa, const char *old struct got_patch_hunk *h; err = pa->progress_cb(pa->progress_arg, old, new, status, - orig_error, 0, 0, 0, 0, 0, NULL); + orig_error, 0, 0, 0, 0, 0, 0, NULL); if (err) return err; STAILQ_FOREACH(h, pa->head, entries) { - if (h->offset == 0 && h->err == NULL) + if (h->offset == 0 && !h->ws_mangled && h->err == NULL) continue; err = pa->progress_cb(pa->progress_arg, old, new, 0, NULL, h->old_from, h->old_lines, h->new_from, h->new_lines, - h->offset, h->err); + h->offset, h->ws_mangled, h->err); if (err) return err; } blob - c16e62c4b053ddda85d0a0c0b1761790032b168c blob + 7901777201377549e2dce265cf173b09d716ee4d --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -1275,6 +1275,91 @@ EOF diff -u $testroot/stderr.expected $testroot/stderr fi test_done $testroot 0 +} + +test_patch_whitespace() { + local testroot=`test_init patch_whitespace` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + trailing=" " + + cat < $testroot/wt/hello.c +#include + +int +main(void) +{ + /* the trailing whitespace is on purpose */ + printf("hello, world\n");$trailing + return 0; +} +EOF + + (cd $testroot/wt && got add hello.c && got ci -m '+hello.c') \ + > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + # test with a diff with various whitespace corruptions + cat < $testroot/wt/patch +--- hello.c ++++ hello.c +@@ -5,5 +5,5 @@ + { + /* the trailing whitespace is on purpose */ + printf("hello, world\n"); +- return 0; ++ return 5; /* always fails */ + } +EOF + + (cd $testroot/wt && got patch patch) \ + 2>$testroot/stderr >$testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + echo "failed to apply diff" >&2 + test_done $testroot $ret + return 1 + fi + + echo 'M hello.c' > $testroot/stdout.expected + echo '@@ -5,5 +5,5 @@ hunk contains mangled whitespace' \ + >> $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot $ret + return 1 + fi + + cat < $testroot/wt/hello.c.expected +#include + +int +main(void) +{ + /* the trailing whitespace is on purpose */ + printf("hello, world\n");$trailing + return 5; /* always fails */ +} +EOF + + cmp -s $testroot/wt/hello.c.expected $testroot/wt/hello.c + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/hello.c.expected $testroot/wt/hello.c + fi + test_done $testroot $ret } test_patch_relative_paths() { @@ -1811,6 +1896,7 @@ run_test test_patch_with_offset run_test test_patch_prefer_new_path run_test test_patch_no_newline run_test test_patch_strip +run_test test_patch_whitespace run_test test_patch_relative_paths run_test test_patch_with_path_prefix run_test test_patch_relpath_with_path_prefix