commit 650a3405fbd209dde33ac82b91ac6bceb15104e4 from: Omar Polo via: Thomas Adam date: Mon Jan 02 11:44:07 2023 UTC got patch: handle the removal of binary files Diffs that remove binary files don't have hunks so got patch would skip over them, treating that part of the diff as "noise". Different programs outputs a slightly different diff for this kind of patches, but the "Binary files ... and /dev/null differ" is usually shown, so try to match it. The adedd regress test covers got diff, git diff, and OpenBSD' /usr/bin/diff. CVS diffs will currently fail because the guessed file name will be wrong. CVS prints the file name in the Index and RCS lines which got patch currently ignores, and shows an useless path in the "Binary files /tmp/cvs... and /dev/null differ" line. Discussed with and ok stsp@ commit - bbf94fd6ae7e0a158d5ad25a7e25c6f422136fcb commit + 650a3405fbd209dde33ac82b91ac6bceb15104e4 blob - 2ff96da7823c45ecb7b30768597f413f7aef1b9e blob + 398a62a30ab5ccc3cbc1d92618fb31d5c80dc9bd --- lib/patch.c +++ lib/patch.c @@ -584,6 +584,10 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp) return got_error(GOT_ERR_PATCH_MALFORMED); return apply_hunk(orig, tmp, h, &lineno, 0); } + + /* When deleting binary files there are no hunks to apply. */ + if (p->new == NULL && STAILQ_EMPTY(&p->head)) + return NULL; if (fstat(fileno(orig), &sb) == -1) return got_error_from_errno("fstat"); blob - 2629b8eb5ac5bf6ef386a08e8ee437b27ef5fd41 blob + a5ab7432c53352c80a292a19938782c1091de4d0 --- libexec/got-read-patch/got-read-patch.c +++ libexec/got-read-patch/got-read-patch.c @@ -129,6 +129,44 @@ filename(const char *at, char **name) } static int +binary_deleted(const char *line) +{ + const char *prefix = "Binary files "; + const char *suffix = " and /dev/null differ\n"; + size_t len, d; + + if (strncmp(line, prefix, strlen(prefix)) != 0) + return 0; + line += strlen(prefix); + + len = strlen(line); + if (len <= strlen(suffix)) + return 0; + d = len - strlen(suffix); + return (strcmp(line + d, suffix) == 0); +} + +static const struct got_error * +binaryfilename(const char *at, char **name) +{ + const char *suffix = " and /dev/null differ\n"; + size_t len, d; + + len = strlen(at); + if (len <= strlen(suffix)) + return NULL; + + d = len - strlen(suffix); + if (strcmp(at + d, suffix) != 0) + return NULL; + + *name = strndup(at, d); + if (*name == NULL) + return got_error_from_errno("strndup"); + return NULL; +} + +static int filexbit(const char *line) { char *m; @@ -187,7 +225,8 @@ patch_start(int *git, char **cid, FILE *fp) break; } else if (!strncmp(line, "--- ", 4) || !strncmp(line, "+++ ", 4) || - !strncmp(line, "blob - ", 7)) { + !strncmp(line, "blob - ", 7) || + binary_deleted(line)) { /* rewind to previous line */ if (fseeko(fp, -linelen, SEEK_CUR) == -1) err = got_error_from_errno("fseeko"); @@ -212,7 +251,7 @@ find_diff(int *done, int *next, FILE *fp, int git, con char *line = NULL; size_t linesize = 0; ssize_t linelen; - int create, rename = 0, xbit = 0; + int create, delete_binary = 0, rename = 0, xbit = 0; *done = 0; *next = 0; @@ -237,6 +276,10 @@ find_diff(int *done, int *next, FILE *fp, int git, con } else if (!git && !strncmp(line, "blob - ", 7)) { free(blob); err = blobid(line + 7, &blob, git); + } else if (!strncmp(line, "Binary files ", 13)) { + delete_binary = 1; + free(old); + err = binaryfilename(line + 13, &old); } else if (rename && !strncmp(line, "rename to ", 10)) { free(new); err = filename(line + 10, &new); @@ -264,6 +307,16 @@ find_diff(int *done, int *next, FILE *fp, int git, con * line. */ if (rename && old != NULL && new != NULL) { + *done = 1; + err = send_patch(old, new, commitid, + blob, xbit, git); + break; + } + + /* + * Diffs that remove binary files have no hunks. + */ + if (delete_binary && old != NULL) { *done = 1; err = send_patch(old, new, commitid, blob, xbit, git); blob - 9e3d4639d01241422b50bdddf316137ceb10d945 blob + 17d3810a7849186809369d7245dafdba62ab2db7 --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -1880,7 +1880,79 @@ EOF test_done "$testroot" 0 } + +test_patch_remove_binary_file() { + local testroot=`test_init patch_remove_binary_file` + + if ! got checkout $testroot/repo $testroot/wt >/dev/null; then + test_done $testroot $ret + return 1 + fi + + dd if=/dev/zero of=$testroot/wt/x bs=1 count=16 2>/dev/null >&2 + (cd $testroot/wt && got add x && got commit -m +x) >/dev/null + + (cd $testroot/wt && \ + got branch demo && \ + got rm x && \ + got ci -m -x && + got up -b master) >/dev/null + + echo 'D x' > $testroot/stdout.expected + (cd $testroot/wt && got log -c demo -l 1 -p >patch) + + (cd $testroot/wt && got patch $testroot/stdout + if [ $? -ne 0 ]; then + echo 'patch failed' >&2 + test_done $testroot 1 + return 1 + fi + + if ! cmp -s $testroot/stdout.expected $testroot/stdout; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot 1 + return 1 + fi + + # try again using a git produced diff + (cd $testroot/wt && got revert x) >/dev/null + + (cd $testroot/repo && git show demo) >$testroot/wt/patch + + (cd $testroot/wt && got patch $testroot/stdout + if [ $? -ne 0 ]; then + echo 'patch failed' >&2 + test_done $testroot 1 + return 1 + fi + + if ! cmp -s $testroot/stdout.expected $testroot/stdout; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot 1 + return 1 + fi + + # try again using a diff(1) style patch + (cd $testroot/wt && got revert x) >/dev/null + + echo "Binary files x and /dev/null differ" >$testroot/wt/patch + (cd $testroot/wt && got patch $testroot/stdout + if [ $? -ne 0 ]; then + echo 'patch failed' >&2 + test_done $testroot 1 + return 1 + fi + + if ! cmp -s $testroot/stdout.expected $testroot/stdout; then + diff -u $testroot/stdout.expected $testroot/stdout + test_done $testroot 1 + return 1 + fi + + test_done $testroot 0 +} + test_parseargs "$@" run_test test_patch_basic run_test test_patch_dont_apply @@ -1910,3 +1982,4 @@ run_test test_patch_merge_reverse run_test test_patch_newfile_xbit_got_diff run_test test_patch_newfile_xbit_git_diff run_test test_patch_umask +run_test test_patch_remove_binary_file