"GOT", but the "O" is a cute, smiling pufferfish. Index | Thread | Search

From:
Omar Polo <op@omarpolo.com>
Subject:
Re: got patch: merge patches with diff3
To:
Stefan Sperling <stsp@stsp.name>
Cc:
gameoftrees@openbsd.org
Date:
Sun, 19 Jun 2022 12:14:37 +0200

Download raw body.

Thread
Stefan Sperling <stsp@stsp.name> wrote:
> On Fri, Jun 17, 2022 at 01:04:50PM +0200, Omar Polo wrote:
> > +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);
> 
> I think it would be better to use got_parse_sha1_digest() instead
> of got_repo_match_object_id(). You can copy the result to id->sha1.
> Inside lib/ code it is fine to declare 'struct got_object_id id'
> on the stack and use it like this:
> 
> 	if (!got_parse_sha1_digest(id.sha1, blobid))
> 		return got_error(GOT_ERR_BAD_OBJ_ID_STR);
> 
> 	err = got_object_open_as_blob(&blob, repo, &id, 8192);
> 	if (err)
> 		goto done;

done, thanks for the improvement

> > @@ -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);
> 
> You could validate the blob ID here by checking if
> got_parse_sha1_digest() can successfully parse it.

You're right.  I was kind of abusing filename.  Revised diff includes a
`blobid' helper that extracts and validates the blob id.

> > +	# 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
> 
> The label should indicate what kind of object this is:
> 
> 	||||||| blob f00c965d8307308469e537302baa73048488f162
> 
> Perhaps people would have an easier time looking up commit IDs
> and then find the file by path? Pretty much all you can do with
> just one blob ID is to use 'got cat' on it. So instead you could
> parse the commit ID from the patch as well and show it here:
> 
> 	||||||| commit abcde...

agreed!  it's so much better!

reattaching the pile of patches.  The change to parse the commit id is
in the latest commit, i've kept it separate in the hope of making the
review easier.

-----------------------------------------------
commit 6d35c44df99d3cb68eb86d136c4ccd2d1f346af0
from: Omar Polo <op@omarpolo.com>
date: Sun Jun 19 09:50:33 2022 UTC
 
 patch_file: move file ownership to parent
 
diff c0cd4d97bab79998909bad510b116ccb434db7fe 111038dd67196e333e11f8aefbbca3c787db39e7
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 ea353d92350359bc600a488f39d6539c485b84b0
from: Omar Polo <op@omarpolo.com>
date: Sun Jun 19 09:50:33 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 111038dd67196e333e11f8aefbbca3c787db39e7 1435eb1c1db57938c53f2df4157f2ca8e197caeb
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 d49804087c43d65213b42b77038a3abf8f20809a
from: Omar Polo <op@omarpolo.com>
date: Sun Jun 19 09:50:49 2022 UTC
 
 got patch: use diff3 to merge the changes
 
 Parse the "blob -" metadata in diffs produced by 'got diff' and use the
 original file for patching.  Then, use the diff3 with the current
 version of the file to merge the differences.
 
 This solves many failures automagically or at least turns them into a
 conflict.
 
diff 1435eb1c1db57938c53f2df4157f2ca8e197caeb 463d871bda8e2a668463810ccb34cf7963c8992f
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 + 9fbe2170f84b59a8c01710ada69f683bbfc91f38
--- lib/patch.c
+++ lib/patch.c
@@ -42,12 +42,15 @@
 #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"
+#include "got_lib_sha1.h"
 
 #define MIN(a, b) ((a) < (b) ? (a) : (b))
 
@@ -70,6 +73,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 +184,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 +576,75 @@ 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_blob_object *blob = NULL;
+	struct got_object_id id;
+
+	*fp = NULL;
+	*path = NULL;
+
+	if (!got_parse_sha1_digest(id.sha1, blobid))
+		return got_error(GOT_ERR_BAD_OBJ_ID_STR);
+
+	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 (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 +670,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 +721,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 +739,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 +760,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 +837,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 +896,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 +910,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 + ba9f841f31f227976e27fe6dbd4cd56b5ae30699
--- libexec/got-read-patch/got-read-patch.c
+++ libexec/got-read-patch/got-read-patch.c
@@ -56,11 +56,12 @@
 #include "got_lib_delta.h"
 #include "got_lib_object.h"
 #include "got_lib_privsep.h"
+#include "got_lib_sha1.h"
 
 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 +73,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;
 }
@@ -123,10 +126,31 @@ filename(const char *at, char **name)
 }
 
 static const struct got_error *
+blobid(const char *line, char **blob)
+{
+	uint8_t digest[SHA1_DIGEST_LENGTH];
+	size_t len;
+
+	*blob = NULL;
+
+	len = strspn(line, "0123456789abcdefABCDEF");
+	if ((*blob = strndup(line, len)) == NULL)
+		return got_error_from_errno("strndup");
+
+	if (!got_parse_sha1_digest(digest, *blob)) {
+		/* silently ignore invalid blob ids */
+		free(*blob);
+		*blob = NULL;
+	}
+	return NULL;
+}
+
+static const struct got_error *
 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 +171,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 = blobid(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 +195,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 +205,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 +219,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 + 6c9ee03971f22c84e0e5a023a0b18209a73bbdd8
--- 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 - aaaabbbbccccddddeeeeffff0000111122223333
+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

-----------------------------------------------
commit f547bb37e8704c5b468b7fd6532d159d83c904eb (pmerge)
from: Omar Polo <op@omarpolo.com>
date: Sun Jun 19 09:50:50 2022 UTC
 
 use the commitid in the patch diff3 conflict header
 
 suggested by stsp@
 
diff 463d871bda8e2a668463810ccb34cf7963c8992f 284ae8f207332a1628a04d881443f5dfda442d68
blob - 8b9f4c059435abf6b351d2c9f180dd9926969528
blob + 8e99bbe74f6769ad1550cbcbe10d5ff1368d116d
--- 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	cid[41];
 	char	blob[41];
 };
 
blob - 9fbe2170f84b59a8c01710ada69f683bbfc91f38
blob + 9209e54ce7111b54b69980a44a48a92a333e5888
--- lib/patch.c
+++ lib/patch.c
@@ -73,6 +73,7 @@ STAILQ_HEAD(got_patch_hunk_head, got_patch_hunk);
 struct got_patch {
 	char	*old;
 	char	*new;
+	char	 cid[41];
 	char	 blob[41];
 	struct got_patch_hunk_head head;
 };
@@ -185,12 +186,16 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got
 
 	if (patch.old[sizeof(patch.old)-1] != '\0' ||
 	    patch.new[sizeof(patch.new)-1] != '\0' ||
+	    patch.cid[sizeof(patch.cid)-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));
+	if (*patch.cid != '\0' && *patch.blob != '\0') {
+		strlcpy(p->cid, patch.cid, sizeof(p->cid));
+		strlcpy(p->blob, patch.blob, sizeof(p->blob));
+	}
 
 	/* automatically set strip=1 for git-style diffs */
 	if (strip == -1 && patch.git &&
@@ -624,7 +629,7 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 {
 	const struct got_error *err = NULL;
 	int do_merge = 0, file_renamed = 0;
-	char *oldlabel = NULL, *newlabel = NULL;
+	char *oldlabel = NULL, *newlabel = NULL, *anclabel = NULL;
 	char *oldpath = NULL, *newpath = NULL;
 	char *tmppath = NULL, *template = NULL, *parent = NULL;
 	char *apath = NULL, *mergepath = NULL;
@@ -699,6 +704,12 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 			goto done;
 		}
 
+		if (asprintf(&anclabel, "commit %s", p->cid) == -1) {
+			err = got_error_from_errno("asprintf");
+			anclabel = NULL;
+			goto done;
+		}
+
 		err = got_opentemp_named(&mergepath, &mergefile, template);
 		if (err)
 			goto done;
@@ -706,7 +717,7 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 		outfd = fileno(mergefile);
 
 		err = got_merge_diff3(overlapcnt, outfd, tmpfile, afile,
-		    oldfile, oldlabel, p->blob, newlabel,
+		    oldfile, oldlabel, anclabel, newlabel,
 		    GOT_DIFF_ALGORITHM_PATIENCE);
 		if (err)
 			goto done;
@@ -794,6 +805,7 @@ done:
 	free(newpath);
 	free(oldlabel);
 	free(newlabel);
+	free(anclabel);
 	return err;
 }
 
blob - ba9f841f31f227976e27fe6dbd4cd56b5ae30699
blob + 5f1a2842034c7b2f4296aa5b45ca262a927c4a7a
--- libexec/got-read-patch/got-read-patch.c
+++ libexec/got-read-patch/got-read-patch.c
@@ -61,7 +61,8 @@
 struct imsgbuf ibuf;
 
 static const struct got_error *
-send_patch(const char *oldname, const char *newname, const char *blob, int git)
+send_patch(const char *oldname, const char *newname, const char *commitid,
+    const char *blob, int git)
 {
 	struct got_imsg_patch p;
 
@@ -73,8 +74,10 @@ send_patch(const char *oldname, const char *newname, c
 	if (newname != NULL)
 		strlcpy(p.new, newname, sizeof(p.new));
 
-	if (blob != NULL)
+	if (commitid != NULL && blob != NULL) {
+		strlcpy(p.cid, commitid, sizeof(p.cid));
 		strlcpy(p.blob, blob, sizeof(p.blob));
+	}
 
 	p.git = git;
 	if (imsg_compose(&ibuf, GOT_IMSG_PATCH, 0, 0, -1, &p, sizeof(p)) == -1)
@@ -150,7 +153,7 @@ find_patch(int *done, FILE *fp)
 {
 	const struct got_error *err = NULL;
 	char	*old = NULL, *new = NULL;
-	char	*blob = NULL;
+	char	*commitid = NULL, *blob = NULL;
 	char	*line = NULL;
 	size_t	 linesize = 0;
 	ssize_t	 linelen;
@@ -181,8 +184,13 @@ find_patch(int *done, FILE *fp)
 			rename = 1;
 		else if (!strncmp(line, "diff --git a/", 13)) {
 			git = 1;
+			free(commitid);
+			commitid = NULL;
 			free(blob);
 			blob = NULL;
+		} else if (!git && !strncmp(line, "diff ", 5)) {
+			free(commitid);
+			err = blobid(line + 5, &commitid);
 		}
 
 		if (err)
@@ -195,7 +203,8 @@ find_patch(int *done, FILE *fp)
 		 */
 		if (rename && old != NULL && new != NULL) {
 			*done = 1;
-			err = send_patch(old, new, blob, git);
+			err = send_patch(old, new, commitid,
+			    blob, git);
 			break;
 		}
 
@@ -205,7 +214,8 @@ find_patch(int *done, FILE *fp)
 			    (!create && old == NULL))
 				err = got_error(GOT_ERR_PATCH_MALFORMED);
 			else
-				err = send_patch(old, new, blob, git);
+				err = send_patch(old, new, commitid,
+				    blob, git);
 
 			if (err)
 				break;
@@ -219,6 +229,7 @@ find_patch(int *done, FILE *fp)
 
 	free(old);
 	free(new);
+	free(commitid);
 	free(blob);
 	free(line);
 	if (ferror(fp) && err == NULL)
blob - 6c9ee03971f22c84e0e5a023a0b18209a73bbdd8
blob + 20e899eab3a352dc9e8636b15e894c186595d20a
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -1517,6 +1517,8 @@ test_patch_merge_conflict() {
 		return 1
 	fi
 
+	local commit_id=`git_show_head $testroot/repo`
+
 	jot 10 | sed 's/6/six/g' > $testroot/wt/numbers
 
 	(cd $testroot/wt && got diff > $testroot/old.diff \
@@ -1555,7 +1557,7 @@ test_patch_merge_conflict() {
 	5
 	<<<<<<< --- numbers
 	six
-	||||||| f00c965d8307308469e537302baa73048488f162
+	||||||| commit $commit_id
 	6
 	=======
 	3+3