Download raw body.
got patch: add flag to ignore whitespace?
Omar Polo <op@omarpolo.com> wrote:
> The last patch from Mark reminded me that I thought some time ago to add
> a "-w" flag for got patch to ignore white spaces. It's conceptually
> similar to patch(1) -l/--ignore-whitespace or git-apply(1)
> --ignore-whitespace.
>
> Here's a possible usage (using Mark' last diff)
>
> % mshow | got patch
> # tog/tog.1
> @@ -295,7 +305,7 @@ hunk failed to apply
> G tog/tog.c
> got: patch failed to apply
> % # woops, try again with -w
> % mshow | got patch -w
> G tog/tog.1
> G tog/tog.c
> % # yay!
>
> The diff does exactly what one would expect. The line comparisons are
> now done in linecmp, which first runs strcmp(3) and optionally falls
> back to a whitespace-loose matching.
>
> There's a bit of churn in apply_hunk now. Before, I was re-using the
> lines saved in the hunk to apply it, now I can't. If the context lines
> of the hunk may have the whitespaces mangled and so I have to re-read
> the lines from the original file. Even if we're using a loose match,
> the context line needs not to be changed.
Wops, it introduced a stupid error in apply_hunk: when adding a file we
wouldn't add \n at the end of the lines. The regress suite didn't catch
this, I'll add a testcase for it later as it's independent from this
diff
The change with the previous is to strip newlines read in apply_hunk and
always add them when printing to the temp file.
diff refs/heads/main refs/heads/pw
commit - acf749fc600a43d8e276321e8a63cd97484f30bb
commit + 9fc8d3de91bd9dbf0aaa7cac05fcffec04d4c558
blob - d2db3f11b55b0dcd7008e9f4662210887aa2742b
blob + ac432bba43e6aa8dc04e5981fa8f99a8c9ae9e39
--- got/got.1
+++ got/got.1
@@ -1307,7 +1307,7 @@ option)
.El
.El
.Tg pa
-.It Cm patch Oo Fl n Oc Oo Fl p Ar strip-count Oc Oo Fl R Oc Op Ar patchfile
+.It Cm patch Oo Fl n Oc Oo Fl p Ar strip-count Oc Oo Fl R Oc Oo Fl w Oc Op Ar patchfile
.Dl Pq alias: Cm pa
Apply changes from
.Ar patchfile
@@ -1393,6 +1393,11 @@ path prefixes generated by
will be recognized and stripped automatically.
.It Fl R
Reverse the patch before applying it.
+.It Fl w
+Ignore whitespaces in context lines.
+Useful in case the tabs and spaces in the
+.Ar patchfile
+have been mangled.
.El
.Tg rv
.It Cm revert Oo Fl p Oc Oo Fl F Ar response-script Oc Oo Fl R Oc Ar path ...
blob - 5f7f00007937a048564e685aac0a8c6b9e98adab
blob + 62afd6dac8942d13f30de9135d7d8dcba4e79a9e
--- got/got.c
+++ got/got.c
@@ -7702,7 +7702,7 @@ __dead static void
usage_patch(void)
{
fprintf(stderr, "usage: %s patch [-n] [-p strip-count] "
- "[-R] [patchfile]\n", getprogname());
+ "[-R] [-w] [patchfile]\n", getprogname());
exit(1);
}
@@ -7791,11 +7791,11 @@ cmd_patch(int argc, char *argv[])
struct got_repository *repo = NULL;
const char *errstr;
char *cwd = NULL;
- int ch, nop = 0, strip = -1, reverse = 0;
+ int ch, nop = 0, strip = -1, reverse = 0, ws = 0;
int patchfd;
int *pack_fds = NULL;
- while ((ch = getopt(argc, argv, "np:R")) != -1) {
+ while ((ch = getopt(argc, argv, "np:Rw")) != -1) {
switch (ch) {
case 'n':
nop = 1;
@@ -7809,6 +7809,9 @@ cmd_patch(int argc, char *argv[])
case 'R':
reverse = 1;
break;
+ case 'w':
+ ws = 1;
+ break;
default:
usage_patch();
/* NOTREACHED */
@@ -7860,7 +7863,7 @@ cmd_patch(int argc, char *argv[])
err(1, "pledge");
#endif
- error = got_patch(patchfd, worktree, repo, nop, strip, reverse,
+ error = got_patch(patchfd, worktree, repo, nop, strip, reverse, ws,
&patch_progress, NULL, check_cancelled, NULL);
done:
blob - bc2e95bb4fa535617bc28f76935a78e36dea8a3e
blob + b91edd6f33b6aa71491a276c624ccb7a933573c6
--- include/got_patch.h
+++ include/got_patch.h
@@ -32,4 +32,4 @@ typedef const struct got_error *(*got_patch_progress_c
*/
const struct got_error *
got_patch(int, struct got_worktree *, struct got_repository *, int, int,
- int, got_patch_progress_cb, void *, got_cancel_cb, void *);
+ int, int, got_patch_progress_cb, void *, got_cancel_cb, void *);
blob - 6b6915f9998d30a1ae5b1846365b299ba2c974fe
blob + eaa49470db5ec9942152cefaba3e0ff5ed22aeb0
--- lib/patch.c
+++ lib/patch.c
@@ -26,6 +26,7 @@
#include <sys/stat.h>
#include <sys/uio.h>
+#include <ctype.h>
#include <errno.h>
#include <limits.h>
#include <sha1.h>
@@ -401,8 +402,30 @@ locate_hunk(FILE *orig, struct got_patch_hunk *h, off_
return err;
}
+static int
+linecmp(const char *a, const char *b, int ws)
+{
+ int c;
+
+ c = strcmp(a, b);
+ if (c == 0 || !ws)
+ return c;
+
+ 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 *
-test_hunk(FILE *orig, struct got_patch_hunk *h)
+test_hunk(FILE *orig, struct got_patch_hunk *h, int ws)
{
const struct got_error *err = NULL;
char *line = NULL;
@@ -426,7 +449,7 @@ 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, ws)) {
err = got_error(GOT_ERR_HUNK_FAILED);
goto done;
}
@@ -440,36 +463,65 @@ 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 *
-patch_file(struct got_patch *p, FILE *orig, FILE *tmp, mode_t *mode)
+patch_file(struct got_patch *p, FILE *orig, FILE *tmp, mode_t *mode, int ws)
{
const struct got_error *err = NULL;
struct got_patch_hunk *h;
@@ -484,7 +536,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)
@@ -504,7 +556,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp,
return err;
copypos = pos;
- err = test_hunk(orig, h);
+ err = test_hunk(orig, h, ws);
if (err != NULL && err->code == GOT_ERR_HUNK_FAILED) {
/*
* try to apply the hunk again starting the search
@@ -524,7 +576,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;
@@ -647,7 +699,7 @@ done:
static const struct got_error *
apply_patch(int *overlapcnt, struct got_worktree *worktree,
struct got_repository *repo, struct got_fileindex *fileindex,
- const char *old, const char *new, struct got_patch *p, int nop,
+ const char *old, const char *new, struct got_patch *p, int nop, int ws,
struct patch_args *pa, got_cancel_cb cancel_cb, void *cancel_arg)
{
const struct got_error *err = NULL;
@@ -708,7 +760,8 @@ apply_patch(int *overlapcnt, struct got_worktree *work
goto done;
outpath = tmppath;
outfd = fileno(tmpfile);
- err = patch_file(p, afile != NULL ? afile : oldfile, tmpfile, &mode);
+ err = patch_file(p, afile != NULL ? afile : oldfile, tmpfile,
+ &mode, ws);
if (err)
goto done;
@@ -880,7 +933,7 @@ reverse_patch(struct got_patch *p)
const struct got_error *
got_patch(int fd, struct got_worktree *worktree, struct got_repository *repo,
- int nop, int strip, int reverse, got_patch_progress_cb progress_cb,
+ int nop, int strip, int reverse, int ws, got_patch_progress_cb progress_cb,
void *progress_arg, got_cancel_cb cancel_cb, void *cancel_arg)
{
const struct got_error *err = NULL, *complete_err = NULL;
@@ -949,7 +1002,7 @@ got_patch(int fd, struct got_worktree *worktree, struc
&newpath, worktree, repo, fileindex);
if (err == NULL)
err = apply_patch(&overlapcnt, worktree, repo,
- fileindex, oldpath, newpath, &p, nop, &pa,
+ fileindex, oldpath, newpath, &p, nop, ws, &pa,
cancel_cb, cancel_arg);
if (err != NULL) {
failed = 1;
blob - 40309e5b82bddf39321e6da00a91b79dd7ec65b0
blob + ef4dd59512cedc603ff733648311fafe3cc5f207
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -1273,6 +1273,96 @@ EOF
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 <<EOF > $testroot/wt/hello.c
+#include <stdio.h>
+
+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 <<EOF > $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>/dev/null 1>&2
+ ret=$?
+ if [ $ret -ne 1 ]; then
+ echo "applied a mangled patch without -w?" >&2
+ test_done $testroot 1
+ return 1
+ fi
+
+ (cd $testroot/wt && got patch -w patch) 2>/dev/null > $testroot/stdout
+ ret=$?
+ if [ $ret -ne 0 ]; then
+ echo "failed to apply with -w" >&2
+ test_done $testroot $ret
+ return 1
+ fi
+
+ echo 'M hello.c' > $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 <<EOF > $testroot/wt/hello.c.expected
+#include <stdio.h>
+
+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() {
local testroot=`test_init patch_relative_paths`
@@ -1798,6 +1888,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
got patch: add flag to ignore whitespace?