commit 684a9a6c2461ac3b30d1f0445ec45c94add21931 from: Omar Polo date: Sat Dec 31 17:19:37 2022 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 - cf536071bc57734308f29cda79d67c88abb3b9f0 commit + 684a9a6c2461ac3b30d1f0445ec45c94add21931 blob - 6c31c4b5e3788850c30c78ed552e90f1eddd3982 blob + 6227992d23f21048e0238cc7a3bfe018b85c9cf5 --- lib/patch.c +++ lib/patch.c @@ -586,6 +586,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 - becf27d3e00feb1fd2b32823931dc0d1b580cfb6 blob + d0f6b68a23b13b9ec9d5271bb00d093a979e05cb --- 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