[PATCH] Emulate git diff --merge-base

Marius Feraru altblue at n0i.net
Tue Sep 29 00:22:02 UTC 2026


* cgit.h: cgit_query: + diffmergebase
* cgit.c: querystring_cb: dmb -> diffmergebase
    If "dmb" query arg is present, and it isn't "0",
    then set "diffmergebase".
* ui-diff:
  - cgit_print_diff_ctrls: add "dmb" to control "diffmergebase"
  - cgit_print_diff: handle "diffmergebase"
      by using "diff_get_merge_base" to change "old_rev_oid"
      to detected "merge base"
* tests/t0112-diffmergebase.sh: check dmb bits on diff page

Signed-off-by: Marius Feraru <altblue at n0i.net>
---
 cgit.c                       |  2 ++
 cgit.h                       |  1 +
 tests/t0112-diffmergebase.sh | 37 ++++++++++++++++++++++++++++++++++++
 ui-diff.c                    | 22 +++++++++++++++++++++
 4 files changed, 62 insertions(+)
 create mode 100755 tests/t0112-diffmergebase.sh

diff --git a/cgit.c b/cgit.c
index 2efbb2c..52ab686 100644
--- a/cgit.c
+++ b/cgit.c
@@ -362,10 +362,12 @@ static void querystring_cb(const char *name, const char *value)
 		ctx.qry.context = atoi(value);
 	} else if (!strcmp(name, "ignorews")) {
 		ctx.qry.ignorews = atoi(value);
 	} else if (!strcmp(name, "follow")) {
 		ctx.qry.follow = atoi(value);
+	} else if (!strcmp(name, "dmb") && strcmp(value, "0")) {
+		ctx.qry.diffmergebase = true;
 	}
 }
 
 static void prepare_context(void)
 {
diff --git a/cgit.h b/cgit.h
index 7d7ece7..9537e24 100644
--- a/cgit.h
+++ b/cgit.h
@@ -189,10 +189,11 @@ struct cgit_query {
 	int show_all;
 	int context;
 	int ignorews;
 	int follow;
 	char *vpath;
+	bool diffmergebase;
 };
 
 struct cgit_config {
 	char *agefile;
 	char *cache_root;
diff --git a/tests/t0112-diffmergebase.sh b/tests/t0112-diffmergebase.sh
new file mode 100755
index 0000000..9524f1f
--- /dev/null
+++ b/tests/t0112-diffmergebase.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+test_description='Check merge-base bits on diff page'
+. ./setup.sh
+
+test_expect_success 'generate branch' '
+    cd repos/foo &&
+    git checkout -b b1 --track master &&
+    echo b1 >b1-file &&
+    git add --all &&
+    git commit -m "commit b1 changes" &&
+    git checkout - &&
+    echo m1 >m1-file &&
+    git add --all &&
+    git commit -m "commit m1 changes" &&
+    cd -
+'
+
+test_expect_success 'generate diff w/o dmb' 'cgit_url "foo/diff&h=b1&id=b1&id2=b1%40%7Bu%7D" >diff-wo-dmb'
+test_expect_success 'view has diffmergebase unchecked ctrl' '
+    grep -P "<input type=.checkbox. name=.dmb. onchange" diff-wo-dmb
+'
+test_expect_success 'view includes diff about newer file' '
+    grep -qF m1-file diff-wo-dmb
+'
+
+test_expect_success 'generate diff w/ dmb' '
+    cgit_url "foo/diff&h=b1&id=b1&id2=b1%40%7Bu%7D&dmb" >diff-w-dmb
+'
+test_expect_success 'view has diffmergebase checked ctrl' '
+    grep -P "<input type=.checkbox. name=.dmb. checked onchange" diff-w-dmb
+'
+test_expect_success 'view does not include diff about newer file' '
+    ! grep -qF m1-file diff-w-dmb
+'
+
+test_done
diff --git a/ui-diff.c b/ui-diff.c
index 6cfa697..80ec86f 100644
--- a/ui-diff.c
+++ b/ui-diff.c
@@ -11,10 +11,11 @@
 #include "cgit.h"
 #include "ui-diff.h"
 #include "html.h"
 #include "ui-shared.h"
 #include "ui-ssdiff.h"
+#include "commit-reach.h"
 
 struct object_id old_rev_oid[1];
 struct object_id new_rev_oid[1];
 
 static int files, slots;
@@ -374,10 +375,16 @@ void cgit_print_diff_ctrls(void)
 	curr = ctx.qry.has_difftype ? ctx.qry.difftype : ctx.cfg.difftype;
 	html_intoption(0, "unified", curr);
 	html_intoption(1, "ssdiff", curr);
 	html_intoption(2, "stat only", curr);
 	html("</select></td></tr>");
+	html("<tr><td class='ctrl' colspan='2'>");
+	html("<label><input type='checkbox' name='dmb'");
+	if (ctx.qry.diffmergebase) {
+	    html(" checked");
+	}
+	html(" onchange='this.form.submit()'> merge base</label></td></tr>");
 	html("<tr><td/><td class='ctrl'>");
 	html("<noscript><input type='submit' value='reload'/></noscript>");
 	html("</td></tr></table>");
 	html("</form>");
 	html("</div>");
@@ -427,10 +434,25 @@ void cgit_print_diff(const char *new_rev, const char *old_rev,
 		oidcpy(old_rev_oid, &commit->parents->item->object.oid);
 	} else {
 		oidclr(old_rev_oid, the_repository->hash_algo);
 	}
 
+	if (ctx.qry.diffmergebase &&
+		!is_null_oid(old_rev_oid) &&
+		!is_null_oid(new_rev_oid)) {
+		struct rev_info revs;
+		repo_init_revisions(the_repository, &revs, prefix);
+		add_pending_object(&revs,
+			parse_object(the_repository, old_rev_oid),
+			oid_to_hex(old_rev_oid));
+		add_pending_object(&revs,
+			parse_object(the_repository, new_rev_oid),
+			oid_to_hex(new_rev_oid));
+		diff_get_merge_base(&revs, old_rev_oid);
+		release_revisions(&revs);
+	}
+
 	if (!is_null_oid(old_rev_oid)) {
 		commit2 = lookup_commit_reference(the_repository, old_rev_oid);
 		if (!commit2 || repo_parse_commit(the_repository, commit2)) {
 			cgit_print_error_page(404, "Not found",
 				"Bad commit: %s", oid_to_hex(old_rev_oid));


More information about the CGit mailing list