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

From:
Omar Polo <op@omarpolo.com>
Subject:
got patch: diff3 merge for git diffs
To:
gameoftrees@openbsd.org
Date:
Mon, 27 Jun 2022 12:23:32 +0200

Download raw body.

Thread
the idea is to parse also the

	index oldblobid...newblobid

field in git diffs for the diff3 merge machinery.  Yep, those are blob
ids and not commit nor tree ids, I got it wrong initially too.

The diff seems to work (only tested thru the added regress) but could
use some help to tidy it a bit.  One annoying thing is that git uses
abbreviated blob ids, unlike got diff which prints the full blob id, at
least with a default config.  The change in `blobid' (got-read-patch.c)
is to accomodate for it, got_parse_sha1_digest fails if the given blob
id is abbreviated.

Other than that, the diff should be trivial.

Comments and improvements are welcome :)

diff refs/heads/main refs/heads/gitdiff
commit - 3d589bee0bbbe812bb91f3b0284fbf2596304132
commit + c0e8d0701b6a80242d834b624044f3fc81155902
blob - 965f8606f5dc0febfa47fceeba356519258f1021
blob + efe7f1a611a60b4528c47d210297d944f7e1fd38
--- lib/patch.c
+++ lib/patch.c
@@ -192,10 +192,11 @@ recv_patch(struct imsgbuf *ibuf, int *done, struct got
 		goto done;
 	}
 
-	if (*patch.cid != '\0' && *patch.blob != '\0') {
+	if (*patch.cid != '\0')
 		strlcpy(p->cid, patch.cid, sizeof(p->cid));
+
+	if (*patch.blob != '\0')
 		strlcpy(p->blob, patch.blob, sizeof(p->blob));
-	}
 
 	/* automatically set strip=1 for git-style diffs */
 	if (strip == -1 && patch.git &&
@@ -586,15 +587,25 @@ open_blob(char **path, FILE **fp, const char *blobid,
 {
 	const struct got_error *err = NULL;
 	struct got_blob_object *blob = NULL;
-	struct got_object_id id;
+	struct got_object_id id, *idptr, *matched_id = NULL;
 
 	*fp = NULL;
 	*path = NULL;
 
-	if (!got_parse_sha1_digest(id.sha1, blobid))
-		return got_error(GOT_ERR_BAD_OBJ_ID_STR);
+	if (strlen(blobid) != 40) {
+		err = got_repo_match_object_id(&matched_id, NULL, blobid,
+		    GOT_OBJ_TYPE_BLOB, NULL /* do not resolve tags */,
+		    repo);
+		if (err)
+			return err;
+		idptr = matched_id;
+	} else {
+		if (!got_parse_sha1_digest(id.sha1, blobid))
+			return got_error(GOT_ERR_BAD_OBJ_ID_STR);
+		idptr = &id;
+	}
 
-	err = got_object_open_as_blob(&blob, repo, &id, 8192);
+	err = got_object_open_as_blob(&blob, repo, idptr, 8192);
 	if (err)
 		goto done;
 
@@ -609,6 +620,8 @@ open_blob(char **path, FILE **fp, const char *blobid,
 done:
 	if (blob)
 		got_object_blob_close(blob);
+	if (matched_id != NULL)
+		free(matched_id);
 	if (err) {
 		if (*fp != NULL)
 			fclose(*fp);
@@ -647,7 +660,8 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 		 * ignore failures to open this blob, we might have
 		 * parsed gibberish.
 		 */
-		if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT))
+		if (err && !(err->code == GOT_ERR_ERRNO && errno == ENOENT) &&
+		    err->code != GOT_ERR_NO_OBJ)
 			return err;
 		else if (err == NULL)
 			do_merge = 1;
@@ -689,6 +703,8 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 		goto done;
 
 	if (do_merge) {
+		const char *type, *id;
+
 		if (fseeko(afile, 0, SEEK_SET) == -1 ||
 		    fseeko(oldfile, 0, SEEK_SET) == -1 ||
 		    fseeko(tmpfile, 0, SEEK_SET) == -1) {
@@ -708,7 +724,15 @@ apply_patch(int *overlapcnt, struct got_worktree *work
 			goto done;
 		}
 
-		if (asprintf(&anclabel, "commit %s", p->cid) == -1) {
+		if (*p->cid != '\0') {
+			type = "commit";
+			id = p->cid;
+		} else {
+			type = "blob";
+			id = p->blob;
+		}
+
+		if (asprintf(&anclabel, "%s %s", type, id) == -1) {
 			err = got_error_from_errno("asprintf");
 			anclabel = NULL;
 			goto done;
blob - 05415ddde579ebfde446284ba961406bc37a4255
blob + d6eea51f8d3203ae05b110adaac04ee330bd4d28
--- libexec/got-read-patch/got-read-patch.c
+++ libexec/got-read-patch/got-read-patch.c
@@ -74,10 +74,11 @@ send_patch(const char *oldname, const char *newname, c
 	if (newname != NULL)
 		strlcpy(p.new, newname, sizeof(p.new));
 
-	if (commitid != NULL && blob != NULL) {
+	if (commitid != NULL)
 		strlcpy(p.cid, commitid, sizeof(p.cid));
+
+	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)
@@ -129,7 +130,7 @@ filename(const char *at, char **name)
 }
 
 static const struct got_error *
-blobid(const char *line, char **blob)
+blobid(const char *line, char **blob, int git)
 {
 	uint8_t digest[SHA1_DIGEST_LENGTH];
 	size_t len;
@@ -140,7 +141,7 @@ blobid(const char *line, char **blob)
 	if ((*blob = strndup(line, len)) == NULL)
 		return got_error_from_errno("strndup");
 
-	if (!got_parse_sha1_digest(digest, *blob)) {
+	if (!git && !got_parse_sha1_digest(digest, *blob)) {
 		/* silently ignore invalid blob ids */
 		free(*blob);
 		*blob = NULL;
@@ -176,13 +177,16 @@ find_patch(int *done, FILE *fp)
 			err = filename(line+4, &new);
 		} else if (!git && !strncmp(line, "blob - ", 7)) {
 			free(blob);
-			err = blobid(line + 7, &blob);
+			err = blobid(line + 7, &blob, git);
 		} 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 (git && !strncmp(line, "index ", 6)) {
+			free(blob);
+			err = blobid(line + 6, &blob, git);
+		} else if (!strncmp(line, "diff --git a/", 13)) {
 			git = 1;
 			free(commitid);
 			commitid = NULL;
@@ -190,10 +194,10 @@ find_patch(int *done, FILE *fp)
 			blob = NULL;
 		} else if (!git && !strncmp(line, "diff ", 5)) {
 			free(commitid);
-			err = blobid(line + 5, &commitid);
+			err = blobid(line + 5, &commitid, git);
 		} else if (!git && !strncmp(line, "commit - ", 9)) {
 			free(commitid);
-			err = blobid(line + 9, &commitid);
+			err = blobid(line + 9, &commitid, git);
 		}
 
 		if (err)
blob - 9fb1dd39304f1cd716283dd5684a1162ee8f745c
blob + 51393375e18f15c1ea358bdef6876fc878f0d571
--- regress/cmdline/patch.sh
+++ regress/cmdline/patch.sh
@@ -1507,6 +1507,62 @@ test_patch_merge_simple() {
 	test_done $testroot $ret
 }
 
+test_patch_merge_gitdiff() {
+	local testroot=`test_init patch_merge_gitdiff`
+
+	jot 10 > $testroot/repo/numbers
+	(cd $testroot/repo && git add numbers && \
+		git_commit $testroot/repo -m "nums")
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	jot 10 | sed 's/4/four/g' > $testroot/repo/numbers
+	(cd $testroot/repo && git diff > $testroot/old.diff)
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# restore numbers
+	jot 10 > $testroot/repo/numbers
+
+	jot 10 | sed 's/6/six/g' > $testroot/repo/numbers
+	(cd $testroot/repo && git add numbers && \
+		git_commit $testroot/repo -m "edit")
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	# now work with got:
+	got checkout $testroot/repo $testroot/wt > /dev/null
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	(cd $testroot/wt && got patch $testroot/old.diff) > $testroot/stdout
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		test_done $testroot $ret
+		return 1
+	fi
+
+	echo 'G  numbers' > $testroot/stdout.expected
+	cmp -s $testroot/stdout $testroot/stdout.expected
+	ret=$?
+	if [ $ret -ne 0 ]; then
+		diff -u $testroot/stdout $testroot/stdout.expected
+	fi
+	test_done $testroot $ret
+}
+
 test_patch_merge_conflict() {
 	local testroot=`test_init patch_merge_conflict`
 
@@ -1665,7 +1721,35 @@ EOF
 	ret=$?
 	if [ $ret -ne 0 ]; then
 		diff -u $testroot/stdout.expected $testroot/stdout
+		test_done $testroot $ret
+		return 1
 	fi
+
+	# try again with a git-style diff
+	cat <<EOF > $testroot/wt/patch
+diff --git a/alpha b/alpha
+index 01234567..abcdef0
+--- a/alpha
++++ b/alpha
+@@ -1 +1 @@
+-alpha
++ALPHA
+EOF
+
+	(cd $testroot/wt && got revert alpha > /dev/null && 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
 }
 
@@ -1695,5 +1779,6 @@ 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_gitdiff
 run_test test_patch_merge_conflict
 run_test test_patch_merge_unknown_blob