Download raw body.
got patch: merge patches with diff3
Stefan Sperling <stsp@stsp.name> wrote: > On Fri, Jun 17, 2022 at 11:48:31AM +0200, Omar Polo wrote: > > Stefan Sperling <stsp@stsp.name> wrote: > > > On Thu, Jun 16, 2022 at 12:35:31AM +0200, Omar Polo wrote: > > > apply_patch() seems to treat the occurrence of a merge conflict as a > > > fatal error. Why? > > > > in fact, i don't want to treat them as fatal error. The idea was to use > > GOT_ERR_PATCH_CONFLICT to signal the caller (got_patch) that a conflict > > was found, but not to stop the patch operation. (like what happens for > > a failed patch, so that in the end 'got patch' exits with non-zero) > > > > I lost a change in got_patch when putting the diff into shape, thanks > > for spotting this! > > You could instead pass overlapcnt back to the caller. thanks for the suggestion; it's neater this way, thanks! here's the pile of patches updated. I'm attaching also the previous refactoring diff (that i've split into two). regarding the merge i've: - fixed the case where the blob id is not found, we will silently skip it. The parsing code is not bulletproof and may pick up noise as a blob id. adding a test case for this too. - added back a missing check in the last fseeko - changed the merge labels to "--- path/to/file", "+++ path/to/file" and the blob id instead of "ancestor", as suggested in irc (thanks) ----------------------------------------------- commit a2ac884fc64becea0771c8d0fe85c49a0c8c3580 from: Omar Polo <op@omarpolo.com> date: Fri Jun 17 10:41:10 2022 UTC patch_file: move file ownership to parent diff eea58cb9757387e01590325f3106af94fe69b570 dac899116d2c355fbd05e81b459fa78213c87bc5 blob - 7167f541b49d2fddbb5d61d45e13bed060e2d33e blob + cb80bed14230259d21207905afb95b11f1e4063c --- lib/patch.c +++ lib/patch.c @@ -455,14 +455,12 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, int *l } static const struct got_error * -patch_file(struct got_patch *p, const char *path, FILE *tmp, int nop, - mode_t *mode) +patch_file(struct got_patch *p, FILE *orig, FILE *tmp, int nop, mode_t *mode) { const struct got_error *err = NULL; struct got_patch_hunk *h; struct stat sb; int lineno = 0; - FILE *orig; off_t copypos, pos; char *line = NULL; size_t linesize = 0; @@ -477,15 +475,8 @@ patch_file(struct got_patch *p, const char *path, FILE return apply_hunk(tmp, h, &lineno); } - if ((orig = fopen(path, "r")) == NULL) { - err = got_error_from_errno2("fopen", path); - goto done; - } - - if (fstat(fileno(orig), &sb) == -1) { - err = got_error_from_errno("fstat"); - goto done; - } + if (fstat(fileno(orig), &sb) == -1) + return got_error_from_errno("fstat"); *mode = sb.st_mode; copypos = 0; @@ -495,11 +486,11 @@ patch_file(struct got_patch *p, const char *path, FILE if (err != NULL && err->code == GOT_ERR_HUNK_FAILED) h->err = err; if (err != NULL) - goto done; + return err; if (!nop) err = copy(tmp, orig, copypos, pos); if (err != NULL) - goto done; + return err; copypos = pos; err = test_hunk(orig, h); @@ -508,20 +499,16 @@ patch_file(struct got_patch *p, const char *path, FILE * try to apply the hunk again starting the search * after the previous partial match. */ - if (fseeko(orig, pos, SEEK_SET) == -1) { - err = got_error_from_errno("fseeko"); - goto done; - } + if (fseeko(orig, pos, SEEK_SET) == -1) + return got_error_from_errno("fseeko"); linelen = getline(&line, &linesize, orig); - if (linelen == -1) { - err = got_error_from_errno("getline"); - goto done; - } + if (linelen == -1) + return got_error_from_errno("getline"); lineno++; goto tryagain; } if (err != NULL) - goto done; + return err; if (lineno + 1 != h->old_from) h->offset = lineno + 1 - h->old_from; @@ -529,13 +516,11 @@ patch_file(struct got_patch *p, const char *path, FILE if (!nop) err = apply_hunk(tmp, h, &lineno); if (err != NULL) - goto done; + return err; copypos = ftello(orig); - if (copypos == -1) { - err = got_error_from_errno("ftello"); - goto done; - } + if (copypos == -1) + return got_error_from_errno("ftello"); } if (p->new == NULL && sb.st_size != copypos) { @@ -545,9 +530,6 @@ patch_file(struct got_patch *p, const char *path, FILE } else if (!nop && !feof(orig)) err = copy(tmp, orig, copypos, -1); -done: - if (orig != NULL && fclose(orig) == EOF && err == NULL) - err = got_error_from_errno("fclose"); return err; } @@ -600,7 +582,7 @@ apply_patch(struct got_worktree *worktree, struct got_ int file_renamed = 0; char *oldpath = NULL, *newpath = NULL; char *tmppath = NULL, *template = NULL, *parent = NULL; - FILE *tmp = NULL; + FILE *oldfile = NULL, *tmp = NULL; mode_t mode = GOT_DEFAULT_FILE_MODE; if (asprintf(&oldpath, "%s/%s", got_worktree_get_root_path(worktree), @@ -623,11 +605,16 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } + if (p->old != NULL && (oldfile = fopen(oldpath, "r")) == NULL) { + err = got_error_from_errno2("open", oldpath); + goto done; + } + if (!nop) err = got_opentemp_named(&tmppath, &tmp, template); if (err) goto done; - err = patch_file(p, oldpath, tmp, nop, &mode); + err = patch_file(p, oldfile, tmp, nop, &mode); if (err) goto done; @@ -691,6 +678,8 @@ done: err = got_error_from_errno("fclose"); free(tmppath); free(oldpath); + if (oldfile != NULL && fclose(oldfile) == EOF && err == NULL) + err = got_error_from_errno("fclose"); free(newpath); return err; } ----------------------------------------------- commit 752735af2da45fc2ecf3474decd6f71ade942181 from: Omar Polo <op@omarpolo.com> date: Fri Jun 17 10:55:03 2022 UTC patch_file: fill tmp with the patched file even in nop mode future work with diff3 merge will need the fully patched file even in the nop mode diff dac899116d2c355fbd05e81b459fa78213c87bc5 0708493b774308f86e86f52afba802cae311ecdc blob - cb80bed14230259d21207905afb95b11f1e4063c blob + 0a98afc1b334061cef12aac2a80ebe56c7b4a167 --- lib/patch.c +++ lib/patch.c @@ -455,7 +455,7 @@ apply_hunk(FILE *tmp, struct got_patch_hunk *h, int *l } static const struct got_error * -patch_file(struct got_patch *p, FILE *orig, FILE *tmp, int nop, mode_t *mode) +patch_file(struct got_patch *p, FILE *orig, FILE *tmp, mode_t *mode) { const struct got_error *err = NULL; struct got_patch_hunk *h; @@ -470,8 +470,6 @@ 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); - if (nop) - return NULL; return apply_hunk(tmp, h, &lineno); } @@ -487,8 +485,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp, h->err = err; if (err != NULL) return err; - if (!nop) - err = copy(tmp, orig, copypos, pos); + err = copy(tmp, orig, copypos, pos); if (err != NULL) return err; copypos = pos; @@ -513,8 +510,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp, if (lineno + 1 != h->old_from) h->offset = lineno + 1 - h->old_from; - if (!nop) - err = apply_hunk(tmp, h, &lineno); + err = apply_hunk(tmp, h, &lineno); if (err != NULL) return err; @@ -527,7 +523,7 @@ patch_file(struct got_patch *p, FILE *orig, FILE *tmp, h = STAILQ_FIRST(&p->head); h->err = got_error(GOT_ERR_HUNK_FAILED); err = h->err; - } else if (!nop && !feof(orig)) + } else if (!feof(orig)) err = copy(tmp, orig, copypos, -1); return err; @@ -610,11 +606,10 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - if (!nop) - err = got_opentemp_named(&tmppath, &tmp, template); + err = got_opentemp_named(&tmppath, &tmp, template); if (err) goto done; - err = patch_file(p, oldfile, tmp, nop, &mode); + err = patch_file(p, oldfile, tmp, &mode); if (err) goto done; ----------------------------------------------- commit c3c0a28be901f5ecdbaba66d6b24ee6c04342ec8 (pmerge) from: Omar Polo <op@omarpolo.com> date: Fri Jun 17 10:55:03 2022 UTC got patch: use diff3 to merge the changes Parse the "blob -" metadata in diffs produced by 'got diff' so that later it's possible to patch the original file and merging it to the current version. This automatically solves many failure to apply a patch or at least turns them into a conflict. diff 0708493b774308f86e86f52afba802cae311ecdc 2e60647c81f1c14b2fab8c027c04d12e1f1d35eb blob - af9557d85c3c38603367d54310b5f2a59d7b3b11 blob + 8b9f4c059435abf6b351d2c9f180dd9926969528 --- lib/got_lib_privsep.h +++ lib/got_lib_privsep.h @@ -613,6 +613,7 @@ struct got_imsg_patch { int git; char old[PATH_MAX]; char new[PATH_MAX]; + char blob[41]; }; /* blob - 0a98afc1b334061cef12aac2a80ebe56c7b4a167 blob + 2f88cf5f2ee99332023bf8a005e3023ba04816cd --- lib/patch.c +++ lib/patch.c @@ -42,10 +42,12 @@ #include "got_reference.h" #include "got_cancel.h" #include "got_worktree.h" +#include "got_repository.h" #include "got_opentemp.h" #include "got_patch.h" #include "got_lib_delta.h" +#include "got_lib_diff.h" #include "got_lib_object.h" #include "got_lib_privsep.h" @@ -70,6 +72,7 @@ STAILQ_HEAD(got_patch_hunk_head, got_patch_hunk); struct got_patch { char *old; char *new; + char blob[41]; struct got_patch_hunk_head head; }; @@ -180,11 +183,14 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got memcpy(&patch, imsg.data, sizeof(patch)); if (patch.old[sizeof(patch.old)-1] != '\0' || - patch.new[sizeof(patch.new)-1] != '\0') { + patch.new[sizeof(patch.new)-1] != '\0' || + patch.blob[sizeof(patch.blob)-1] != '\0') { err = got_error(GOT_ERR_PRIVSEP_LEN); goto done; } + strlcpy(p->blob, patch.blob, sizeof(p->blob)); + /* automatically set strip=1 for git-style diffs */ if (strip == -1 && patch.git && (*patch.old == '\0' || !strncmp(patch.old, "a/", 2)) && @@ -569,18 +575,80 @@ patch_add(void *arg, unsigned char status, const char } static const struct got_error * -apply_patch(struct got_worktree *worktree, struct got_repository *repo, - struct got_fileindex *fileindex, const char *old, const char *new, - struct got_patch *p, int nop, struct patch_args *pa, - got_cancel_cb cancel_cb, void *cancel_arg) +open_blob(char **path, FILE **fp, const char *blobid, + struct got_repository *repo) { const struct got_error *err = NULL; - int file_renamed = 0; + struct got_object_id *id = NULL; + struct got_blob_object *blob = NULL; + + *fp = NULL; + *path = NULL; + + err = got_repo_match_object_id(&id, NULL, blobid, + GOT_OBJ_TYPE_BLOB, NULL /* do not resolve tags */, + repo); + if (err) + return err; + + err = got_object_open_as_blob(&blob, repo, id, 8192); + if (err) + goto done; + + err = got_opentemp_named(path, fp, GOT_TMPDIR_STR "/got-patch-blob"); + if (err) + goto done; + + err = got_object_blob_dump_to_file(NULL, NULL, NULL, *fp, blob); + if (err) + goto done; + +done: + if (id) + free(id); + if (blob) + got_object_blob_close(blob); + if (err) { + if (*fp != NULL) + fclose(*fp); + if (*path != NULL) + unlink(*path); + free(*path); + *fp = NULL; + *path = NULL; + } + return err; +} + +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, + struct patch_args *pa, got_cancel_cb cancel_cb, void *cancel_arg) +{ + const struct got_error *err = NULL; + int do_merge = 0, file_renamed = 0; + char *oldlabel = NULL, *newlabel = NULL; char *oldpath = NULL, *newpath = NULL; char *tmppath = NULL, *template = NULL, *parent = NULL; - FILE *oldfile = NULL, *tmp = NULL; + char *apath = NULL, *mergepath = NULL; + FILE *oldfile = NULL, *tmpfile = NULL, *afile = NULL, *mergefile = NULL; + int outfd; + const char *outpath; mode_t mode = GOT_DEFAULT_FILE_MODE; + *overlapcnt = 0; + + /* don't run the diff3 merge on creations/deletions */ + if (*p->blob != '\0' && p->old != NULL && p->new != NULL) { + err = open_blob(&apath, &afile, p->blob, repo); + if (err && err->code != GOT_ERR_NOT_REF) + return err; + else if (err == NULL) + do_merge = 1; + err = NULL; + } + if (asprintf(&oldpath, "%s/%s", got_worktree_get_root_path(worktree), old) == -1) { err = got_error_from_errno("asprintf"); @@ -606,13 +674,48 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - err = got_opentemp_named(&tmppath, &tmp, template); + err = got_opentemp_named(&tmppath, &tmpfile, template); if (err) goto done; - err = patch_file(p, oldfile, tmp, &mode); + outpath = tmppath; + outfd = fileno(tmpfile); + err = patch_file(p, afile != NULL ? afile : oldfile, tmpfile, &mode); if (err) goto done; + if (do_merge) { + if (fseeko(afile, 0, SEEK_SET) == -1 || + fseeko(oldfile, 0, SEEK_SET) == -1 || + fseeko(tmpfile, 0, SEEK_SET) == -1) { + err = got_error_from_errno("fseeko"); + goto done; + } + + if (asprintf(&oldlabel, "--- %s", p->old) == -1) { + err = got_error_from_errno("asprintf"); + oldlabel = NULL; + goto done; + } + + if (asprintf(&newlabel, "+++ %s", p->new) == -1) { + err = got_error_from_errno("asprintf"); + newlabel = NULL; + goto done; + } + + err = got_opentemp_named(&mergepath, &mergefile, template); + if (err) + goto done; + outpath = mergepath; + outfd = fileno(mergefile); + + err = got_merge_diff3(overlapcnt, outfd, tmpfile, afile, + oldfile, oldlabel, p->blob, newlabel, + GOT_DIFF_ALGORITHM_PATIENCE); + if (err) + goto done; + } + if (nop) goto done; @@ -622,14 +725,14 @@ apply_patch(struct got_worktree *worktree, struct got_ goto done; } - if (fchmod(fileno(tmp), mode) == -1) { + if (fchmod(outfd, mode) == -1) { err = got_error_from_errno2("chmod", tmppath); goto done; } - if (rename(tmppath, newpath) == -1) { + if (rename(outpath, newpath) == -1) { if (errno != ENOENT) { - err = got_error_from_errno3("rename", tmppath, + err = got_error_from_errno3("rename", outpath, newpath); goto done; } @@ -640,8 +743,8 @@ apply_patch(struct got_worktree *worktree, struct got_ err = got_path_mkdir(parent); if (err != NULL) goto done; - if (rename(tmppath, newpath) == -1) { - err = got_error_from_errno3("rename", tmppath, + if (rename(outpath, newpath) == -1) { + err = got_error_from_errno3("rename", outpath, newpath); goto done; } @@ -661,21 +764,40 @@ apply_patch(struct got_worktree *worktree, struct got_ fileindex, patch_add, pa); if (err) unlink(newpath); - } else + } else if (*overlapcnt > 0) + err = report_progress(pa, old, new, GOT_STATUS_CONFLICT, NULL); + else err = report_progress(pa, old, new, GOT_STATUS_MODIFY, NULL); done: free(parent); free(template); + if (tmppath != NULL) unlink(tmppath); - if (tmp != NULL && fclose(tmp) == EOF && err == NULL) + if (tmpfile != NULL && fclose(tmpfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); free(tmppath); + free(oldpath); if (oldfile != NULL && fclose(oldfile) == EOF && err == NULL) err = got_error_from_errno("fclose"); + + if (apath != NULL) + unlink(apath); + if (afile != NULL && fclose(afile) == EOF && err == NULL) + err = got_error_from_errno("fclose"); + free(apath); + + if (mergepath != NULL) + unlink(mergepath); + if (mergefile != NULL && fclose(mergefile) == EOF && err == NULL) + err = got_error_from_errno("fclose"); + free(mergepath); + free(newpath); + free(oldlabel); + free(newlabel); return err; } @@ -719,7 +841,7 @@ got_patch(int fd, struct got_worktree *worktree, struc char *oldpath, *newpath; struct imsgbuf *ibuf; int imsg_fds[2] = {-1, -1}; - int done = 0, failed = 0; + int overlapcnt, done = 0, failed = 0; pid_t pid; ibuf = calloc(1, sizeof(*ibuf)); @@ -778,8 +900,9 @@ got_patch(int fd, struct got_worktree *worktree, struc err = got_worktree_patch_check_path(p.old, p.new, &oldpath, &newpath, worktree, repo, fileindex); if (err == NULL) - err = apply_patch(worktree, repo, fileindex, oldpath, - newpath, &p, nop, &pa, cancel_cb, cancel_arg); + err = apply_patch(&overlapcnt, worktree, repo, + fileindex, oldpath, newpath, &p, nop, &pa, + cancel_cb, cancel_arg); if (err != NULL) { failed = 1; /* recoverable errors */ @@ -791,6 +914,8 @@ got_patch(int fd, struct got_worktree *worktree, struc err = report_progress(&pa, p.old, p.new, GOT_STATUS_CANNOT_UPDATE, NULL); } + if (overlapcnt != 0) + failed = 1; free(oldpath); free(newpath); blob - 23212abe02328d5837822225fecbf174e677f2bb blob + 3ace8a308d014810ccbc1758463bc197cfee8657 --- libexec/got-read-patch/got-read-patch.c +++ libexec/got-read-patch/got-read-patch.c @@ -60,7 +60,7 @@ struct imsgbuf ibuf; static const struct got_error * -send_patch(const char *oldname, const char *newname, int git) +send_patch(const char *oldname, const char *newname, const char *blob, int git) { struct got_imsg_patch p; @@ -72,9 +72,11 @@ send_patch(const char *oldname, const char *newname, i if (newname != NULL) strlcpy(p.new, newname, sizeof(p.new)); + if (blob != NULL) + strlcpy(p.blob, blob, sizeof(p.blob)); + p.git = git; - if (imsg_compose(&ibuf, GOT_IMSG_PATCH, 0, 0, -1, - &p, sizeof(p)) == -1) + if (imsg_compose(&ibuf, GOT_IMSG_PATCH, 0, 0, -1, &p, sizeof(p)) == -1) return got_error_from_errno("imsg_compose GOT_IMSG_PATCH"); return NULL; } @@ -127,6 +129,7 @@ find_patch(int *done, FILE *fp) { const struct got_error *err = NULL; char *old = NULL, *new = NULL; + char *blob = NULL; char *line = NULL; size_t linesize = 0; ssize_t linelen; @@ -147,13 +150,19 @@ find_patch(int *done, FILE *fp) } else if (!strncmp(line, "+++ ", 4)) { free(new); err = filename(line+4, &new); + } else if (!git && !strncmp(line, "blob - ", 7)) { + free(blob); + err = filename(line + 7, &blob); } else if (rename && !strncmp(line, "rename to ", 10)) { free(new); err = filename(line + 10, &new); } else if (git && !strncmp(line, "similarity index 100%", 21)) rename = 1; - else if (!strncmp(line, "diff --git a/", 13)) + else if (!strncmp(line, "diff --git a/", 13)) { git = 1; + free(blob); + blob = NULL; + } if (err) break; @@ -165,7 +174,7 @@ find_patch(int *done, FILE *fp) */ if (rename && old != NULL && new != NULL) { *done = 1; - err = send_patch(old, new, git); + err = send_patch(old, new, blob, git); break; } @@ -175,7 +184,7 @@ find_patch(int *done, FILE *fp) (!create && old == NULL)) err = got_error(GOT_ERR_PATCH_MALFORMED); else - err = send_patch(old, new, git); + err = send_patch(old, new, blob, git); if (err) break; @@ -189,6 +198,7 @@ find_patch(int *done, FILE *fp) free(old); free(new); + free(blob); free(line); if (ferror(fp) && err == NULL) err = got_error_from_errno("getline"); blob - c1e703a5e36a9140b460df532dc84b36583e5884 blob + defce1a79fc5cd6a7677f7c6021e2062b7d135ea --- regress/cmdline/patch.sh +++ regress/cmdline/patch.sh @@ -1443,6 +1443,177 @@ EOF test_done $testroot $ret } +test_patch_merge_simple() { + local testroot=`test_init patch_merge_simple` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 > $testroot/wt/numbers + (cd $testroot/wt && got add numbers && got commit -m +numbers) \ + > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 | sed 's/4/four/g' > $testroot/wt/numbers + + (cd $testroot/wt && got diff > $testroot/old.diff \ + && got revert numbers) >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 | sed 's/6/six/g' > $testroot/wt/numbers + (cd $testroot/wt && got commit -m 'edit numbers') \ + > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + (cd $testroot/wt && got patch $testroot/old.diff) \ + 2>&1 > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 | sed -e s/4/four/ -e s/6/six/ > $testroot/wt/numbers.expected + cmp -s $testroot/wt/numbers $testroot/wt/numbers.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/numbers $testroot/wt/numbers.expected + fi + test_done $testroot $ret +} + +test_patch_merge_conflict() { + local testroot=`test_init patch_merge_conflict` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 > $testroot/wt/numbers + (cd $testroot/wt && got add numbers && got commit -m +numbers) \ + > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 | sed 's/6/six/g' > $testroot/wt/numbers + + (cd $testroot/wt && got diff > $testroot/old.diff \ + && got revert numbers) >/dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + jot 10 | sed 's/6/3+3/g' > $testroot/wt/numbers + (cd $testroot/wt && got commit -m 'edit numbers') \ + > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + (cd $testroot/wt && got patch $testroot/old.diff) \ + >/dev/null 2>&1 + ret=$? + if [ $ret -eq 0 ]; then + echo "got patch merged a diff that should conflict" >&2 + test_done $testroot 0 + return 1 + fi + + # XXX: prefixing every line with a tab otherwise got thinks + # the file has conflicts in it. + cat <<-EOF > $testroot/wt/numbers.expected + 1 + 2 + 3 + 4 + 5 + <<<<<<< --- numbers + six + ||||||| f00c965d8307308469e537302baa73048488f162 + 6 + ======= + 3+3 + >>>>>>> +++ numbers + 7 + 8 + 9 + 10 +EOF + + cmp -s $testroot/wt/numbers $testroot/wt/numbers.expected + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/wt/numbers $testroot/wt/numbers.expected + fi + test_done $testroot $ret +} + +test_patch_merge_unknown_blob() { + local testroot=`test_init patch_merge_unknown_blob` + + got checkout $testroot/repo $testroot/wt > /dev/null + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + cat <<EOF > $testroot/wt/patch +I've got a +blob - aaaabbbbccccddddeeeeffffgggghhhhiiiillll +and also a +blob + 0000111122223333444455556666888899990000 +for this dummy diff +--- alpha ++++ alpha +@@ -1 +1 @@ +-alpha ++ALPHA +will it work? +EOF + + (cd $testroot/wt/ && got patch patch) > $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + test_done $testroot $ret + return 1 + fi + + echo 'M alpha' > $testroot/stdout.expected + cmp -s $testroot/stdout.expected $testroot/stdout + ret=$? + if [ $ret -ne 0 ]; then + diff -u $testroot/stdout.expected $testroot/stdout + fi + test_done $testroot $ret +} + test_parseargs "$@" run_test test_patch_simple_add_file run_test test_patch_simple_rm_file @@ -1468,3 +1639,6 @@ run_test test_patch_relative_paths run_test test_patch_with_path_prefix run_test test_patch_relpath_with_path_prefix run_test test_patch_reverse +run_test test_patch_merge_simple +run_test test_patch_merge_conflict +run_test test_patch_merge_unknown_blob
got patch: merge patches with diff3