Skip to contentSkip to conversation

RFC: In-core git merge-tree ("Server side merges")

PATCH Superseded Elijah Newren via GitGitGadget 11 participants 240 messages Jan 22, 2022 – Jun 18, 2022 1 message not in the archive Documentation Page 1 of 3

lore
Share & tools
lore
#0 lore raw

cover RFC: In-core git merge-tree ("Server side merges")

Note1: Depends on en/remerge-diff (but no need to pick it up; it's still RFC).

(Note2: This is a continuation of my series at [0], but I can't change the base repository for a pull request in GitHub so I had to open a new PR and that makes it look like a new series. [0]
https://lore.kernel.org/git/[email protected]/)

== Basic Summary ==

This series introduces a new mode to git merge-tree allowing it to perform real merges (three-way text content merges, recursive ancestor consolidation, rename detection, proper directory/file conflict handling, etc.) and write the result as a toplevel tree. It doesn't touch the working tree or index, and doesn't create any commits or update any refs.

== Updates Log ==

Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments on v2):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.
* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments
* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)
* Rebased on top of en/remerge-diff to avoid a small conflict

Updates since v1 (thanks to Johannes Altmanninger and Fabian for suggestions on v1):

* Fixed a bad patch splitting, and a style issue pointed out by Johannes Altimanninger
* Fixed misleading commit messages in new test cases
* Fixed my comments about how commit-tree could be used to correctly use two -p flags

== Other notes ==

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]
* A way to omit printing the generated tree hash[2][3] In regards to these, also see also some of the new info in Documentation/git-merge-tree.txt, namely the expanded paragraph on "the second form is deprecated" in the description as regards diff output usability and performance, and also the final paragraph of the new "Mistakes to avoid" section in regards to tree hash.

[1]
https://lore.kernel.org/git/[email protected]/
(section starting with "Providing a tree") [2]
https://lore.kernel.org/git/CABPp-BHvXrP0sTTmuTYfACoJTCcm9+wk_f441nj4TstrmQdqMQ@mail.gmail.com/
(sections starting with "avoid printing" and "Where did I suggest") [3]
https://lore.kernel.org/git/CABPp-BGdbh=HM7jA+_RTwSWVcMr_qvEY7RoNXooeBT2NB4Ubzw@mail.gmail.com/
(section starting with "Providing a tree object")

Elijah Newren (12):
merge-tree: rename merge_trees() to trivial_merge_trees()
merge-tree: move logic for existing merge into new function
merge-tree: add option parsing and initial shell for real merge
function
merge-tree: implement real merges
merge-ort: split out a separate display_update_messages() function
merge-ort: allow update messages to be written to different file
stream
merge-tree: support including merge messages in output
merge-ort: provide a merge_get_conflicted_files() helper function
merge-tree: provide a list of which files have conflicts
merge-tree: provide easy access to ls-files -u style info
merge-tree: add a --allow-unrelated-histories flag git-merge-tree.txt: add a section on potentional usage mistakes

 Documentation/git-merge-tree.txt | 182 +++++++++++++++++++++++++++++--
 builtin/merge-tree.c             | 178 ++++++++++++++++++++++++++++--
 git.c                            |   2 +-
 merge-ort.c                      | 109 ++++++++++++------
 merge-ort.h                      |  30 +++++
 t/t4301-merge-tree-real.sh       | 163 +++++++++++++++++++++++++++
 6 files changed, 603 insertions(+), 61 deletions(-)
 create mode 100755 t/t4301-merge-tree-real.sh

base-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v1
Pull-Request: https://github.com/gitgitgadget/git/pull/1122

Signature · 1 line
gitgitgadget
#1 lore raw

PATCH 1/12 merge-tree: rename merge_trees() to trivial_merge_trees()

From: Elijah Newren <[email protected]>

merge-recursive.h defined its own merge_trees() function, different than the one found in builtin/merge-tree.c. That was okay in the past, but we want merge-tree to be able to use the merge-ort functions, which will end up including merge-recursive.h. Rename the function found in builtin/merge-tree.c to avoid the conflict.

---
 builtin/merge-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
builtin/merge-tree.c +4 -4
index 5dc94d6f880..06f9eee9f78 100644
@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)
 	merge_result_end = &entry->next;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base);
+static void trivial_merge_trees(struct tree_desc t[3], const char *base);
 
 static const char *explanation(struct merge_list *entry)
 {
@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,
 	buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));
 #undef ENTRY_OID
 
-	merge_trees(t, newbase);
+	trivial_merge_trees(t, newbase);
 
 	free(buf0);
 	free(buf1);
@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
 	return mask;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base)
+static void trivial_merge_trees(struct tree_desc t[3], const char *base)
 {
 	struct traverse_info info;
 
@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
-	merge_trees(t, "");
+	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
 	free(buf3);
Signature · 1 line
gitgitgadget
#2 lore raw

PATCH 2/12 merge-tree: move logic for existing merge into new function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

In preparation for adding a non-trivial merge capability to merge-tree, move the existing merge logic for trivial merges into a new function.

---
 builtin/merge-tree.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
builtin/merge-tree.c +8 -4
index 06f9eee9f78..914ec960b7e 100644
@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+static int trivial_merge(int argc, const char **argv)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	if (argc != 4)
-		usage(merge_tree_usage);
-
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	show_result();
 	return 0;
 }
+
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+	if (argc != 4)
+		usage(merge_tree_usage);
+	return trivial_merge(argc, argv);
+}
Signature · 1 line
gitgitgadget
#3 lore raw

PATCH 3/12 merge-tree: add option parsing and initial shell for real merge function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Note that real merges differ from trivial merges in that they handle:
- three way content merges
- recursive ancestor consolidation
- renames
- proper directory/file conflict handling
- etc.
Basically all the stuff you'd expect from git merge, just without updating the index and working tree. The initial shell added here does nothing more than die with "real merges are not yet implemented", but that will be fixed in subsequent commits.

---
 builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------
 git.c                |  2 +-
 2 files changed, 59 insertions(+), 10 deletions(-)
builtin/merge-tree.c +58 -9
index 914ec960b7e..33e47cc1534 100644
@@ -3,13 +3,12 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "object-store.h"
+#include "parse-options.h"
 #include "repository.h"
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
 
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-
 struct merge_list {
 	struct merge_list *next;
 	struct merge_list *link;	/* other stages for this object */
@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+			 const char *branch1,
+			 const char *branch2)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	buf1 = get_tree_descriptor(r, t+0, argv[1]);
-	buf2 = get_tree_descriptor(r, t+1, argv[2]);
-	buf3 = get_tree_descriptor(r, t+2, argv[3]);
+	buf1 = get_tree_descriptor(r, t+0, base);
+	buf2 = get_tree_descriptor(r, t+1, branch1);
+	buf3 = get_tree_descriptor(r, t+2, branch2);
 	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
@@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)
 	return 0;
 }
 
+struct merge_tree_options {
+	int real;
+	int trivial;
+};
+
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {
+		OPT_BOOL(0, "write-tree", &o.real,
+			 N_("do a real merge instead of a trivial merge")),
+		OPT_BOOL(0, "trivial-merge", &o.trivial,
+			 N_("do a trivial merge only")),
+		OPT_END()
+	};
+
+	/* Check for a request for basic help */
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(merge_tree_usage, mt_options);
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,
+			     merge_tree_usage, 0);
+	if (o.real && o.trivial)
+		die(_("--write-tree and --trivial-merge are incompatible"));
+	if (o.real || o.trivial) {
+		expected_remaining_argc = (o.real ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.real = (argc == 2);
+	}
+
+	/* Do the relevant type of merge */
+	if (o.real)
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +1 -1
index 5ff21be21f3..6090a1289db 100644
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
Signature · 1 line
gitgitgadget
#4 lore raw

Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function

Am 22.01.22 um 22:55 schrieb Elijah Newren via GitGitGadget:

Quoted 95 lines

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Note that real merges differ from trivial merges in that they handle:
- three way content merges
- recursive ancestor consolidation
- renames
- proper directory/file conflict handling
- etc.
Basically all the stuff you'd expect from git merge, just without updating the index and working tree. The initial shell added here does nothing more than die with "real merges are not yet implemented", but that will be fixed in subsequent commits.

---
 builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------
 git.c                |  2 +-
 2 files changed, 59 insertions(+), 10 deletions(-)
builtin/merge-tree.c +27 -9
index 914ec960b7e..33e47cc1534 100644
@@ -3,13 +3,12 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "object-store.h"
+#include "parse-options.h"
 #include "repository.h"
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
 
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-
 struct merge_list {
 	struct merge_list *next;
 	struct merge_list *link;	/* other stages for this object */
@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+			 const char *branch1,
+			 const char *branch2)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	buf1 = get_tree_descriptor(r, t+0, argv[1]);
-	buf2 = get_tree_descriptor(r, t+1, argv[2]);
-	buf3 = get_tree_descriptor(r, t+2, argv[3]);
+	buf1 = get_tree_descriptor(r, t+0, base);
+	buf2 = get_tree_descriptor(r, t+1, branch1);
+	buf3 = get_tree_descriptor(r, t+2, branch2);
 	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
@@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)
 	return 0;
 }
 
+struct merge_tree_options {
+	int real;
+	int trivial;
+};
+
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 419

Quoted 10 lines
builtin/merge-tree.c +10 -0
+		OPT_BOOL(0, "write-tree", &o.real,
+			 N_("do a real merge instead of a trivial merge")),
+		OPT_BOOL(0, "trivial-merge", &o.trivial,
+			 N_("do a trivial merge only")),
+		OPT_END()
+	};
+
+	/* Check for a request for basic help */
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(merge_tree_usage, mt_options);

This is unnecessary; parse_options() handles -h already.

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 425

Quoted 6 lines
builtin/merge-tree.c +6 -0
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,
+			     merge_tree_usage, 0);
+	if (o.real && o.trivial)
+		die(_("--write-tree and --trivial-merge are incompatible"));

12909b6b8a (i18n: turn "options are incompatible" into "cannot be used together", 2022-01-05) standardized messages of that kind; let's stick to that to simplify translation:

		die(_("options '%s' and '%s' cannot be used together"),
		    "--write-tree", "--trivial-merge");
Quoted 29 lines
builtin/merge-tree.c +15 -0
+	if (o.real || o.trivial) {
+		expected_remaining_argc = (o.real ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.real = (argc == 2);
+	}
+
+	/* Do the relevant type of merge */
+	if (o.real)
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +1 -1
index 5ff21be21f3..6090a1289db 100644
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
#5 lore raw

Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function

Quoted 2 lines · René Scharfe

Am 22.01.22 um 22:55 schrieb Elijah Newren via GitGitGadget:

...

Quoted 13 lines

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 419

Quoted 3 lines
builtin/merge-tree.c +3 -0
+     /* Check for a request for basic help */
+     if (argc == 2 && !strcmp(argv[1], "-h"))
+             usage_with_options(merge_tree_usage, mt_options);

This is unnecessary; parse_options() handles -h already.

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 425

Quoted 6 lines
builtin/merge-tree.c +6 -0
+
+     /* Parse arguments */
+     argc = parse_options(argc, argv, prefix, mt_options,
+                          merge_tree_usage, 0);
+     if (o.real && o.trivial)
+             die(_("--write-tree and --trivial-merge are incompatible"));
Quoted 6 lines

12909b6b8a (i18n: turn "options are incompatible" into "cannot be used together", 2022-01-05) standardized messages of that kind; let's stick to that to simplify translation:

die(_("options '%s' and '%s' cannot be used together"),
"--write-tree", "--trivial-merge");

Ah, thanks for both the pointers; will fix.

#6 lore raw

Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function

In reply to Elijah Newren via GitGitGadget
Quoted 95 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Note that real merges differ from trivial merges in that they handle:
- three way content merges
- recursive ancestor consolidation
- renames
- proper directory/file conflict handling
- etc.
Basically all the stuff you'd expect from git merge, just without updating the index and working tree. The initial shell added here does nothing more than die with "real merges are not yet implemented", but that will be fixed in subsequent commits.

---
 builtin/merge-tree.c | 67 ++++++++++++++++++++++++++++++++++++++------
 git.c                |  2 +-
 2 files changed, 59 insertions(+), 10 deletions(-)
builtin/merge-tree.c +27 -9
index 914ec960b7e..33e47cc1534 100644
@@ -3,13 +3,12 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "object-store.h"
+#include "parse-options.h"
 #include "repository.h"
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
 
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-
 struct merge_list {
 	struct merge_list *next;
 	struct merge_list *link;	/* other stages for this object */
@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+			 const char *branch1,
+			 const char *branch2)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	buf1 = get_tree_descriptor(r, t+0, argv[1]);
-	buf2 = get_tree_descriptor(r, t+1, argv[2]);
-	buf3 = get_tree_descriptor(r, t+2, argv[3]);
+	buf1 = get_tree_descriptor(r, t+0, base);
+	buf2 = get_tree_descriptor(r, t+1, branch1);
+	buf3 = get_tree_descriptor(r, t+2, branch2);
 	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
@@ -384,9 +385,57 @@ static int trivial_merge(int argc, const char **argv)
 	return 0;
 }
 
+struct merge_tree_options {
+	int real;
+	int trivial;
+};
+
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 419

Quoted 10 lines
builtin/merge-tree.c +10 -0
+		OPT_BOOL(0, "write-tree", &o.real,
+			 N_("do a real merge instead of a trivial merge")),
+		OPT_BOOL(0, "trivial-merge", &o.trivial,
+			 N_("do a trivial merge only")),
+		OPT_END()
+	};
+
+	/* Check for a request for basic help */
+	if (argc == 2 && !strcmp(argv[1], "-h"))
+		usage_with_options(merge_tree_usage, mt_options);

Is this bit cargo-culted from something else, perhaps non-parse-options.c usage? I don't think this is needed, the parse_options() below intercepts "-h" by default.

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 425

Quoted 5 lines
builtin/merge-tree.c +5 -0
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,
+			     merge_tree_usage, 0);
+	if (o.real && o.trivial)
+		die(_("--write-tree and --trivial-merge are incompatible"));

Shouldn't those two just be OPT_CMDMODE()? Then you get this incompatibility checking for free. See 485fd2c3dae (cat-file: make --batch-all-objects a CMDMODE, 2021-12-28).

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 434

Quoted 9 lines
builtin/merge-tree.c +9 -0
+	if (o.real || o.trivial) {
+		expected_remaining_argc = (o.real ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.real = (argc == 2);
+	}

And this can also be done like this, but I wonder if using PARSE_OPT_STOP_AT_NON_OPTION and then routing to a sub-function wouldn't be better, i.e. to treat these like sub-commands if they've got different arity etc.

Quoted 19 lines
builtin/merge-tree.c +5 -0
+	/* Do the relevant type of merge */
+	if (o.real)
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +1 -1
index 5ff21be21f3..6090a1289db 100644
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
#7 lore raw

Re: [PATCH 03/12] merge-tree: add option parsing and initial shell for real merge function

Quoted 3 lines · Ævar Arnfjörð Bjarmason

On Sat, Jan 22 2022, Elijah Newren via GitGitGadget wrote:

...

Quoted 7 lines

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 419

Quoted 3 lines
builtin/merge-tree.c +3 -0
+     /* Check for a request for basic help */
+     if (argc == 2 && !strcmp(argv[1], "-h"))
+             usage_with_options(merge_tree_usage, mt_options);

Is this bit cargo-culted from something else, perhaps non-parse-options.c usage? I don't think this is needed, the parse_options() below intercepts "-h" by default.

Yep, sure was cargo-culted from somewhere else (my parse-options usage always is), but I'm pretty sure it was from another place also using parse-options. Probably one of these 15 places:

 $ comm -12 <(git grep -l parse-options builtin/ | sort) <(git grep -l
strcmp.*-h\\b builtin/ | sort)
builtin/am.c
builtin/branch.c
builtin/checkout-index.c
builtin/checkout--worker.c
builtin/commit.c
builtin/commit-tree.c
builtin/gc.c
builtin/ls-files.c
builtin/merge.c
builtin/merge-tree.c
builtin/rebase.c
builtin/rev-parse.c
builtin/sparse-checkout.c
builtin/submodule--helper.c
builtin/update-index.c
Quoted 9 lines

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 425

Quoted 5 lines
builtin/merge-tree.c +5 -0
+     /* Parse arguments */
+     argc = parse_options(argc, argv, prefix, mt_options,
+                          merge_tree_usage, 0);
+     if (o.real && o.trivial)
+             die(_("--write-tree and --trivial-merge are incompatible"));

Shouldn't those two just be OPT_CMDMODE()? Then you get this incompatibility checking for free. See 485fd2c3dae (cat-file: make --batch-all-objects a CMDMODE, 2021-12-28).

TIL. Thanks.

Quoted 10 lines

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 434

Quoted 9 lines
builtin/merge-tree.c +9 -0
+     if (o.real || o.trivial) {
+             expected_remaining_argc = (o.real ? 2 : 3);
+             if (argc != expected_remaining_argc)
+                     usage_with_options(merge_tree_usage, mt_options);
+     } else {
+             if (argc < 2 || argc > 3)
+                     usage_with_options(merge_tree_usage, mt_options);
+             o.real = (argc == 2);
+     }
Quoted 4 lines

And this can also be done like this, but I wonder if using PARSE_OPT_STOP_AT_NON_OPTION and then routing to a sub-function wouldn't be better, i.e. to treat these like sub-commands if they've got different arity etc.

Not sure what you mean; I already route to sub-functions. But I should definitely add PARSE_OPT_STOP_AT_NON_OPTION; it's unfortunate that it's not the default.

#8 lore raw

PATCH 4/12 merge-tree: implement real merges

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)

This output is meant to be used by some higher level script, perhaps in a sequence of steps like this:

   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
   test $? -eq 0 || die "There were conflicts..."
   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
   git update-ref $BRANCH1 $NEWCOMMIT

Note that higher level scripts may also want to access the conflict/warning messages normally output during a merge, or have quick access to a list of files with conflicts. That is not available in this preliminary implementation, but subsequent commits will add that ability.

---
 Documentation/git-merge-tree.txt | 74 ++++++++++++++++++++++-----
 builtin/merge-tree.c             | 55 +++++++++++++++++++-
 t/t4301-merge-tree-real.sh       | 87 ++++++++++++++++++++++++++++++++
 3 files changed, 203 insertions(+), 13 deletions(-)
 create mode 100755 t/t4301-merge-tree-real.sh
Documentation/git-merge-tree.txt +62 -12
index 58731c19422..b900bc1362c 100644
@@ -3,26 +3,76 @@ git-merge-tree(1)
 
 NAME
 ----
-git-merge-tree - Show three-way merge without touching index
+git-merge-tree - Perform merge without touching index or working tree
 
 
 SYNOPSIS
 --------
 [verse]
-'git merge-tree' <base-tree> <branch1> <branch2>
+'git merge-tree' [--write-tree] <branch1> <branch2>
+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>
 
 DESCRIPTION
 -----------
-Reads three tree-ish, and output trivial merge results and
-conflicting stages to the standard output.  This is similar to
-what three-way 'git read-tree -m' does, but instead of storing the
-results in the index, the command outputs the entries to the
-standard output.
-
-This is meant to be used by higher level scripts to compute
-merge results outside of the index, and stuff the results back into the
-index.  For this reason, the output from the command omits
-entries that match the <branch1> tree.
+
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The first form will merge the two branches, doing a full recursive
+merge with rename detection.  The rest of this manual (other than the
+next paragraph) describes the first form in more detail -- including
+options, output format, exit status, and usage notes.
+
+The second form is deprecated; it is kept for backward compatibility
+reasons but may be deleted in the future.  It will only do a trivial
+merge.  It reads three tree-ish, and outputs trivial merge results and
+conflicting stages to the standard output in a semi-diff format.
+Since this was designed for higher level scripts to consume and merge
+the results back into the index, it omits entries that match
+<branch1>.  The result of this second form is is similar to what
+three-way 'git read-tree -m' does, but instead of storing the results
+in the index, the command outputs the entries to the standard output.
+This form not only has limited applicability, the output format is
+also difficult to work with, and it will generally be less performant
+than the first form even on successful merges (especially if working
+in large repositories).  The remainder of this manual will only
+discuss the first form.
+
+OUTPUT
+------
+
+For either a successful or conflicted merge, the output from
+git-merge-tree is simply one line:
+
+	<OID of toplevel tree>
+
+The printed tree object corresponds to what would be checked out in
+the working tree at the end of `git merge`, and thus may have files
+with conflict markers in them.
+
+EXIT STATUS
+-----------
+
+For a successful, non-conflicted merge, the exit status is 0.  When the
+merge has conflicts, the exit status is 1.  If the merge is not able to
+complete (or start) due to some kind of error, the exit status is
+something other than 0 or 1.
+
+USAGE NOTES
+-----------
+
+git-merge-tree was written to be low-level plumbing, similar to
+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could
+be used as a part of a series of steps such as
+
+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
+       test $? -eq 0 || die "There were conflicts..."
+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
+       git update-ref $BRANCH1 $NEWCOMMIT
+
+However, it does not quite fit into the same category of low-level
+plumbing commands since the possibility of merge conflicts give it a
+much higher chance of the command not succeeding.
 
 GIT
 ---
builtin/merge-tree.c +54 -1

67 lines not on this page · raw message

t/t4301-merge-tree-real.sh new file +87 -0

87 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#9 lore raw

Re: [PATCH 04/12] merge-tree: implement real merges

builtin/merge-tree.c struct merge_tree_options { · line 423

Quoted 7 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +7 -0
+	/*
+	 * TODO: Support subtree and other -X options?
+	if (use_strategies_nr == 1 &&
+	    !strcmp(use_strategies[0]->name, "subtree"))
+		opt.subtree_shift = "";
+	for (x = 0; x < xopts_nr; x++)
+		if (parse_merge_opt(&opt, xopts[x]))

Better omitted WIP code in a non-RFC series?

builtin/merge-tree.c struct merge_tree_options { · line 424

Quoted 1 line
builtin/merge-tree.c +1 -0
+			die(_("Unknown strategy option: -X%s"), xopts[x]);

As a general issue with this series, die(), BUG() etc. messages should start with a non-capital letter.

builtin/merge-tree.c struct merge_tree_options { · line 443

Quoted 1 line
builtin/merge-tree.c +1 -0
+	printf("%s\n", oid_to_hex(&result.tree->object.oid));

And for both this...

builtin/merge-tree.c struct merge_tree_options { · line 447

Quoted 1 line
builtin/merge-tree.c +1 -0
+		printf(_("Conflicts!\n"));

... and this we can just use puts(). For the former it's just less code, but for the latter translators also don't need to see the always-there \n in the translated message.

t/t4301-merge-tree-real.sh line 8

Quoted 2 lines
t/t4301-merge-tree-real.sh new file +2 -0
+# This test is ort-specific
+test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort || {

Is this ${} trickery really needed? We're not testing with "set -u". So just:

	if test "$GIT_..." != "ort"
	then
		...
	fi

t/t4301-merge-tree-real.sh line 85

Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5 -0
+test_expect_success 'Barf on too many arguments' '
+	test_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&
+
+	grep "^usage: git merge-tree" expect
+'

Nit: In most other tests these usage assertions are at the top of the test, and for those we also make do with just testing the 129 exit code, which is probably enough here too...

#10 lore raw

Re: [PATCH 04/12] merge-tree: implement real merges

Quoted 11 lines · Ævar Arnfjörð Bjarmason

builtin/merge-tree.c struct merge_tree_options { · line 423

Quoted 7 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +7 -0
+     /*
+      * TODO: Support subtree and other -X options?
+     if (use_strategies_nr == 1 &&
+         !strcmp(use_strategies[0]->name, "subtree"))
+             opt.subtree_shift = "";
+     for (x = 0; x < xopts_nr; x++)
+             if (parse_merge_opt(&opt, xopts[x]))
Quoted 1 line

Better omitted WIP code in a non-RFC series?

It's RFC: https://lore.kernel.org/git/[email protected]/

But yeah, I should drop it. Previous rounds of this RFC submission got me feedback that the other commented-out code bit I used to have was something folks wanted in the initial version of the series so I uncommented and cleaned it up. The fact that no one has commented on this part suggests these options don't need to be supported from the start.

Quoted 4 lines
Quoted 1 line

+ die(_("Unknown strategy option: -X%s"), xopts[x]);

As a general issue with this series, die(), BUG() etc. messages should start with a non-capital letter.

Right, thanks for the reminder. I'll go through and try to fix up.

Quoted 9 lines
Quoted 1 line

+ printf("%s\n", oid_to_hex(&result.tree->object.oid));

And for both this...

Quoted 1 line

+ printf(_("Conflicts!\n"));

... and this we can just use puts(). For the former it's just less code, but for the latter translators also don't need to see the always-there \n in the translated message.

Makes sense.

Quoted 9 lines

t/t4301-merge-tree-real.sh line 8

Quoted 2 lines
t/t4301-merge-tree-real.sh new file +2 -0
+# This test is ort-specific
+test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort || {

Is this ${} trickery really needed? We're not testing with "set -u". So just:

        if test "$GIT_..." != "ort"
        then
                ...
        fi

Ah, that would be simpler; thanks.

Quoted 9 lines

t/t4301-merge-tree-real.sh line 85

Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5 -0
+test_expect_success 'Barf on too many arguments' '
+     test_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&
+
+     grep "^usage: git merge-tree" expect
+'

Nit: In most other tests these usage assertions are at the top of the test, and for those we also make do with just testing the 129 exit code, which is probably enough here too...

I see a fair number of counterexamples searching for 129 in the test suite, and I've been bitten enough times seeing tests expect an error but get a different kind of error than the commit message stated they were expecting that I prefer the extra check beyond the error code. Anyway, I'll leave this piece as-is.

#11 lore raw

Re: [PATCH 04/12] merge-tree: implement real merges

In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Documentation/git-merge-tree.txt git-merge-tree(1) · line 32

Quoted 7 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +7 -0
+The second form is deprecated; it is kept for backward compatibility
+reasons but may be deleted in the future.  It will only do a trivial
+merge.  It reads three tree-ish, and outputs trivial merge results and
+conflicting stages to the standard output in a semi-diff format.
+Since this was designed for higher level scripts to consume and merge
+the results back into the index, it omits entries that match
+<branch1>.  The result of this second form is is similar to what

There is a double "is" in this line. Taking a step back, I would suggest to not only remove this paragraph, but to mark the [--trivial-merge] option clearly as (DEPRECATED).

Documentation/git-merge-tree.txt git-merge-tree(1) · line 39

Quoted 7 lines
Documentation/git-merge-tree.txt +7 -0
+three-way 'git read-tree -m' does, but instead of storing the results
+in the index, the command outputs the entries to the standard output.
+This form not only has limited applicability, the output format is
+also difficult to work with, and it will generally be less performant
+than the first form even on successful merges (especially if working
+in large repositories).  The remainder of this manual will only
+discuss the first form.

Thank you,
Dscho

#12 lore raw

Re: [PATCH 04/12] merge-tree: implement real merges

In reply to Elijah Newren via GitGitGadget
Quoted 9 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

Quoted 3 lines

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)

The exit status can now actually be something other than 0 and 1 according to the doc and code below.

Documentation/git-merge-tree.txt git-merge-tree(1)

Quoted 5 lines
Documentation/git-merge-tree.txt +5 -0
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The first form will merge the two branches, doing a full recursive
+merge with rename detection.

Maybe this could already tell that the first form will also write a tree with the result of the merge (even in case of conflict) as this could help understand the reason why the associated option is called '--write-tree'. It could also help to say that we call such a merge a 'real' merge.

Quoted 9 lines

The rest of this manual (other than the

builtin/merge-tree.c +3 -1
+next paragraph) describes the first form in more detail -- including
+options, output format, exit status, and usage notes.
 
 static int real_merge(struct merge_tree_options *o,
                      const char *branch1, const char *branch2)
 {
-       die(_("real merges are not yet implemented"));
+       struct commit *parent1, *parent2;

builtin/merge-tree.c struct merge_tree_options { · line 409

Quoted 10 lines
builtin/merge-tree.c +10 -0
+       struct commit_list *common;
+       struct commit_list *merge_bases = NULL;
+       struct commit_list *j;
+       struct merge_options opt;
+       struct merge_result result = { 0 };
+
+       parent1 = get_merge_parent(branch1);
+       if (!parent1)
+               help_unknown_ref(branch1, "merge",
+                                _("not something we can merge"));

The second argument is supposed to be the command (it's called "cmd"), so maybe "merge-tree" instead of "merge".

builtin/merge-tree.c struct merge_tree_options { · line 414

Quoted 4 lines
builtin/merge-tree.c +4 -0
+       parent2 = get_merge_parent(branch2);
+       if (!parent2)
+               help_unknown_ref(branch2, "merge",
+                                _("not something we can merge"));

idem

builtin/merge-tree.c struct merge_tree_options { · line 430

Quoted 4 lines
builtin/merge-tree.c +4 -0
+       opt.show_rename_progress = 0;
+
+       opt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */
+       opt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */

I think just:

opt.branch1 = branch1
opt.branch2 = branch2

might be better for users as it should show the name as it was passed to the command.

builtin/merge-tree.c struct merge_tree_options { · line 443

Quoted 2 lines
builtin/merge-tree.c +2 -0
+       merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
+       printf("%s\n", oid_to_hex(&result.tree->object.oid));

I wonder if we can actually always output a valid tree when result.clean < 0. In case we might not, the printing should go a few lines below.

builtin/merge-tree.c struct merge_tree_options { · line 446

Quoted 3 lines
builtin/merge-tree.c +3 -0
+       if (result.clean < 0)
+               die(_("failure to merge"));
+       else if (!result.clean)

The "else" is not necessary above.

t/t4301-merge-tree-real.sh

Quoted 10 lines
builtin/merge-tree.c +3 -0
+               printf(_("Conflicts!\n"));
+       merge_finalize(&opt, &result);
+       return !result.clean; /* result.clean < 0 handled above */
 }
t/t4301-merge-tree-real.sh new file +0 -0
new file mode 100755
index 00000000000..e03688515c5

I wonder if it would be better named 't4301-merge-tree-write-tree.sh'...

t/t4301-merge-tree-real.sh line 3

Quoted 4 lines
t/t4301-merge-tree-real.sh new file +3 -0
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+test_description='git merge-tree --write-tree'

... especially given this description.

#13 lore raw

Re: [PATCH 04/12] merge-tree: implement real merges

Quoted 12 lines · Christian Couder
Quoted 9 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

Quoted 6 lines
Quoted 3 lines

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)

The exit status can now actually be something other than 0 and 1 according to the doc and code below.

Thanks for catching; will fix.

Quoted 11 lines

Documentation/git-merge-tree.txt git-merge-tree(1)

Quoted 5 lines
Documentation/git-merge-tree.txt +5 -0
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The first form will merge the two branches, doing a full recursive
+merge with rename detection.

Maybe this could already tell that the first form will also write a tree with the result of the merge (even in case of conflict) as this could help understand the reason why the associated option is called '--write-tree'. It could also help to say that we call such a merge a 'real' merge.

Makes sense.

Quoted 20 lines
Quoted 9 lines

The rest of this manual (other than the

builtin/merge-tree.c +3 -1
+next paragraph) describes the first form in more detail -- including
+options, output format, exit status, and usage notes.
 
 static int real_merge(struct merge_tree_options *o,
                      const char *branch1, const char *branch2)
 {
-       die(_("real merges are not yet implemented"));
+       struct commit *parent1, *parent2;

builtin/merge-tree.c struct merge_tree_options { · line 409

Quoted 10 lines
builtin/merge-tree.c +10 -0
+       struct commit_list *common;
+       struct commit_list *merge_bases = NULL;
+       struct commit_list *j;
+       struct merge_options opt;
+       struct merge_result result = { 0 };
+
+       parent1 = get_merge_parent(branch1);
+       if (!parent1)
+               help_unknown_ref(branch1, "merge",
+                                _("not something we can merge"));
Quoted 2 lines

The second argument is supposed to be the command (it's called "cmd"), so maybe "merge-tree" instead of "merge".

Oh, good catch; thanks for pointing this out.

Quoted 13 lines

builtin/merge-tree.c struct merge_tree_options { · line 414

Quoted 5 lines
builtin/merge-tree.c +4 -0
+       parent2 = get_merge_parent(branch2);
+       if (!parent2)
+               help_unknown_ref(branch2, "merge",
+                                _("not something we can merge"));

idem

builtin/merge-tree.c struct merge_tree_options { · line 430

Quoted 4 lines
builtin/merge-tree.c +4 -0
+       opt.show_rename_progress = 0;
+
+       opt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */
+       opt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */
Quoted 7 lines

I think just:

opt.branch1 = branch1
opt.branch2 = branch2

might be better for users as it should show the name as it was passed to the command.

After digging for a bit, I think in this case there actually isn't a difference to users because both will give the same result. But, if that's the case, the simpler code is warranted.

Quoted 6 lines

builtin/merge-tree.c struct merge_tree_options { · line 443

Quoted 2 lines
builtin/merge-tree.c +2 -0
+       merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
+       printf("%s\n", oid_to_hex(&result.tree->object.oid));

I wonder if we can actually always output a valid tree when result.clean < 0. In case we might not, the printing should go a few lines below.

Yeah, I caught that and fixed it, but got it squashed into a later commit. I'll fix it up.

Quoted 7 lines

builtin/merge-tree.c struct merge_tree_options { · line 446

Quoted 3 lines
builtin/merge-tree.c +3 -0
+       if (result.clean < 0)
+               die(_("failure to merge"));
+       else if (!result.clean)

The "else" is not necessary above.

Quoted 1 line

+ printf(_("Conflicts!\n"));

Yes, and the else clause should just be ripped out.

Quoted 12 lines

t/t4301-merge-tree-real.sh

Quoted 9 lines
builtin/merge-tree.c +2 -0
+       merge_finalize(&opt, &result);
+       return !result.clean; /* result.clean < 0 handled above */
 }
t/t4301-merge-tree-real.sh new file +0 -0
new file mode 100755
index 00000000000..e03688515c5

I wonder if it would be better named 't4301-merge-tree-write-tree.sh'...

Quoted 6 lines

t/t4301-merge-tree-real.sh line 3

Quoted 4 lines
t/t4301-merge-tree-real.sh new file +3 -0
@@ -0,0 +1,87 @@
+#!/bin/sh
+
+test_description='git merge-tree --write-tree'

... especially given this description.

Makes sense; will rename.

#14 lore raw

PATCH 5/12 merge-ort: split out a separate display_update_messages() function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

No functional changes included in this patch; it's just a preparatory step to allow the printed messages to be handled differently by other callers, such as in git merge-tree --write-tree.

---
 merge-ort.c | 77 ++++++++++++++++++++++++++++-------------------------
 merge-ort.h |  8 ++++++
 2 files changed, 49 insertions(+), 36 deletions(-)
merge-ort.c +41 -36
index 9bf15a01db8..f9e35b0f96b 100644
@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 	return errs;
 }
 
+void merge_display_update_messages(struct merge_options *opt,
+				   struct merge_result *result)
+{
+	struct merge_options_internal *opti = result->priv;
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+	struct string_list olist = STRING_LIST_INIT_NODUP;
+	int i;
+
+	if (opt->record_conflict_msgs_as_headers)
+		BUG("Either display conflict messages or record them as headers, not both");
+
+	trace2_region_enter("merge", "display messages", opt->repo);
+
+	/* Hack to pre-allocate olist to the desired size */
+	ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
+		   olist.alloc);
+
+	/* Put every entry from output into olist, then sort */
+	strmap_for_each_entry(&opti->output, &iter, e) {
+		string_list_append(&olist, e->key)->util = e->value;
+	}
+	string_list_sort(&olist);
+
+	/* Iterate over the items, printing them */
+	for (i = 0; i < olist.nr; ++i) {
+		struct strbuf *sb = olist.items[i].util;
+
+		printf("%s", sb->buf);
+	}
+	string_list_clear(&olist, 0);
+
+	/* Also include needed rename limit adjustment now */
+	diff_warn_rename_limit("merge.renamelimit",
+			       opti->renames.needed_limit, 0);
+
+	trace2_region_leave("merge", "display messages", opt->repo);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
 			    struct tree *head,
 			    struct merge_result *result,
@@ -4273,42 +4312,8 @@ void merge_switch_to_result(struct merge_options *opt,
 		trace2_region_leave("merge", "write_auto_merge", opt->repo);
 	}
 
-	if (display_update_msgs) {
-		struct merge_options_internal *opti = result->priv;
-		struct hashmap_iter iter;
-		struct strmap_entry *e;
-		struct string_list olist = STRING_LIST_INIT_NODUP;
-		int i;
-
-		if (opt->record_conflict_msgs_as_headers)
-			BUG("Either display conflict messages or record them as headers, not both");
-
-		trace2_region_enter("merge", "display messages", opt->repo);
-
-		/* Hack to pre-allocate olist to the desired size */
-		ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
-			   olist.alloc);
-
-		/* Put every entry from output into olist, then sort */
-		strmap_for_each_entry(&opti->output, &iter, e) {
-			string_list_append(&olist, e->key)->util = e->value;
-		}
-		string_list_sort(&olist);
-
-		/* Iterate over the items, printing them */
-		for (i = 0; i < olist.nr; ++i) {
-			struct strbuf *sb = olist.items[i].util;
-
-			printf("%s", sb->buf);
-		}
-		string_list_clear(&olist, 0);
-
-		/* Also include needed rename limit adjustment now */
-		diff_warn_rename_limit("merge.renamelimit",
-				       opti->renames.needed_limit, 0);
-
-		trace2_region_leave("merge", "display messages", opt->repo);
-	}
+	if (display_update_msgs)
+		merge_display_update_messages(opt, result);
 
 	merge_finalize(opt, result);
 }
merge-ort.h +8 -0
index fe599b87868..e5aec45b18f 100644
@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,
 			    int update_worktree_and_index,
 			    int display_update_msgs);
 
+/*
+ * Display messages about conflicts and which files were 3-way merged.
+ * Automatically called by merge_switch_to_result() with stream == stdout,
+ * so only call this when bypassing merge_switch_to_result().
+ */
+void merge_display_update_messages(struct merge_options *opt,
+				   struct merge_result *result);
+
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
 		    struct merge_result *result);
Signature · 1 line
gitgitgadget
#15 lore raw

Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function

merge-ort.c static int record_conflicted_index_entries(struct merge_options *opt) · line 4254

Quoted 5 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>
[...]

merge-ort.c +3 -0
+	/* Hack to pre-allocate olist to the desired size */
+	ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
+		   olist.alloc);

Perhaps just add a string_list_grow()? But I wonder if this is really needed v.s. just using the default growing pattern here.

merge-ort.c static int record_conflicted_index_entries(struct merge_options *opt) · line 4267

Quoted 13 lines
merge-ort.c +13 -0
+
+	/* Put every entry from output into olist, then sort */
+	strmap_for_each_entry(&opti->output, &iter, e) {
+		string_list_append(&olist, e->key)->util = e->value;
+	}
+	string_list_sort(&olist);
+
+	/* Iterate over the items, printing them */
+	for (i = 0; i < olist.nr; ++i) {
+		struct strbuf *sb = olist.items[i].util;
+
+		printf("%s", sb->buf);
+	}

Shorter/nicer:

	for_each_string_list_item(item, &olist) {
		struct strbuf *sb = item->util;
	        puts(sb->buf);
	}
Quoted 19 lines
merge-ort.c +0 -19
-	if (display_update_msgs) {
-		struct merge_options_internal *opti = result->priv;
-		struct hashmap_iter iter;
-		struct strmap_entry *e;
-		struct string_list olist = STRING_LIST_INIT_NODUP;
-		int i;
-
-		if (opt->record_conflict_msgs_as_headers)
-			BUG("Either display conflict messages or record them as headers, not both");
-
-		trace2_region_enter("merge", "display messages", opt->repo);
-
-		/* Hack to pre-allocate olist to the desired size */
-		ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
-			   olist.alloc);
-
-		/* Put every entry from output into olist, then sort */
-		strmap_for_each_entry(&opti->output, &iter, e) {
-			string_list_append(&olist, e->key)->util = e->value;

merge-ort.c void merge_switch_to_result(struct merge_options *opt, · line 4304

Quoted 10 lines
merge-ort.c +0 -10
-		}
-		string_list_sort(&olist);
-
-		/* Iterate over the items, printing them */
-		for (i = 0; i < olist.nr; ++i) {
-			struct strbuf *sb = olist.items[i].util;
-
-			printf("%s", sb->buf);
-		}
-		string_list_clear(&olist, 0);

Ah, at this point I see you're just moving code around :) Sending this anyway in case it's useful :)

#16 lore raw

Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function

Quoted 11 lines · Ævar Arnfjörð Bjarmason

merge-ort.c static int record_conflicted_index_entries(struct merge_options *opt) · line 4254

Quoted 5 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>
[...]

merge-ort.c +3 -0
+     /* Hack to pre-allocate olist to the desired size */
+     ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
+                olist.alloc);

Perhaps just add a string_list_grow()? But I wonder if this is really needed v.s. just using the default growing pattern here.

A string_list_grow() would probably be helpful to add at some point; then it could also be used in process_entries().

Quoted 16 lines

merge-ort.c static int record_conflicted_index_entries(struct merge_options *opt) · line 4267

Quoted 13 lines
merge-ort.c +13 -0
+
+     /* Put every entry from output into olist, then sort */
+     strmap_for_each_entry(&opti->output, &iter, e) {
+             string_list_append(&olist, e->key)->util = e->value;
+     }
+     string_list_sort(&olist);
+
+     /* Iterate over the items, printing them */
+     for (i = 0; i < olist.nr; ++i) {
+             struct strbuf *sb = olist.items[i].util;
+
+             printf("%s", sb->buf);
+     }

Shorter/nicer:

Quoted 4 lines
        for_each_string_list_item(item, &olist) {
                struct strbuf *sb = item->util;
                puts(sb->buf);
        }

How did I not know about and not find for_each_string_list_item() when I was writing this code a couple years ago? (and still didn't learn of it until now?)

Thanks for the pointer. Won't change anything right now, though, since...

Quoted 24 lines
Quoted 24 lines
merge-ort.c +0 -24
-     if (display_update_msgs) {
-             struct merge_options_internal *opti = result->priv;
-             struct hashmap_iter iter;
-             struct strmap_entry *e;
-             struct string_list olist = STRING_LIST_INIT_NODUP;
-             int i;
-
-             if (opt->record_conflict_msgs_as_headers)
-                     BUG("Either display conflict messages or record them as headers, not both");
-
-             trace2_region_enter("merge", "display messages", opt->repo);
-
-             /* Hack to pre-allocate olist to the desired size */
-             ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
-                        olist.alloc);
-
-             /* Put every entry from output into olist, then sort */
-             strmap_for_each_entry(&opti->output, &iter, e) {
-                     string_list_append(&olist, e->key)->util = e->value;
-             }
-             string_list_sort(&olist);
-
-             /* Iterate over the items, printing them */
-             for (i = 0; i < olist.nr; ++i) {
Quoted 8 lines

merge-ort.c void merge_switch_to_result(struct merge_options *opt, · line 4304

Quoted 5 lines
merge-ort.c +0 -5
-                     struct strbuf *sb = olist.items[i].util;
-
-                     printf("%s", sb->buf);
-             }
-             string_list_clear(&olist, 0);

Ah, at this point I see you're just moving code around :) Sending this anyway in case it's useful :)

yep, so I won't change anything now, but yes th for_each_string_list_item() tip is still useful as a heads up. Thanks.

#17 lore raw

Re: [PATCH 05/12] merge-ort: split out a separate display_update_messages() function

In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Quoted 5 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

No functional changes included in this patch; it's just a preparatory step to allow the printed messages to be handled differently by other callers, such as in git merge-tree --write-tree.

Looks good. FWIW I cheated and looked at the output of

	git show pr-1122/newren/in-core-merge-tree-v1~7 \
		--color-moved \
		--color-moved-ws=allow-indentation-change

(after fetching the tag from https://github.com/gitgitgadget/git) instead of this patch (which is a lot easier to digest for me, because of the color-coding, and since GitGitGadget sent the patch, I know that it is identical to the commit).

Ciao,
Dscho

#18 lore raw

PATCH 6/12 merge-ort: allow update messages to be written to different file stream

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This modifies the new display_update_messages() function to allow printing to somewhere other than stdout.

---
 merge-ort.c | 7 ++++---
 merge-ort.h | 3 ++-
 2 files changed, 6 insertions(+), 4 deletions(-)
merge-ort.c +4 -3
index f9e35b0f96b..b78dde55ad9 100644
@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 }
 
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result)
+				   struct merge_result *result,
+				   FILE *stream)
 {
 	struct merge_options_internal *opti = result->priv;
 	struct hashmap_iter iter;
@@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,
 	for (i = 0; i < olist.nr; ++i) {
 		struct strbuf *sb = olist.items[i].util;
 
-		printf("%s", sb->buf);
+		fprintf(stream, "%s", sb->buf);
 	}
 	string_list_clear(&olist, 0);
 
@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,
 	}
 
 	if (display_update_msgs)
-		merge_display_update_messages(opt, result);
+		merge_display_update_messages(opt, result, stdout);
 
 	merge_finalize(opt, result);
 }
merge-ort.h +2 -1
index e5aec45b18f..d643b47cb7c 100644
@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,
  * so only call this when bypassing merge_switch_to_result().
  */
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result);
+				   struct merge_result *result,
+				   FILE *stream);
 
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
Signature · 1 line
gitgitgadget
#19 lore raw

Re: [PATCH 06/12] merge-ort: allow update messages to be written to different file stream

Hi Elijah,

Quoted 15 lines · Elijah Newren via GitGitGadget
merge-ort.c +2 -1
index f9e35b0f96b..b78dde55ad9 100644
@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 }
 
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result)
+				   struct merge_result *result,
+				   FILE *stream)
 {
 	struct merge_options_internal *opti = result->priv;
 	struct hashmap_iter iter;

merge-ort.c void merge_display_update_messages(struct merge_options *opt, · line 4267

Quoted 5 lines
merge-ort.c +1 -1
@@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,
 	for (i = 0; i < olist.nr; ++i) {
 		struct strbuf *sb = olist.items[i].util;
 
-		printf("%s", sb->buf);
+		fprintf(stream, "%s", sb->buf);

Maybe strbuf_write(sb, stream); instead? Whenever I see a "%s", I feel like it's unnecessary churn...

merge-ort.c void merge_display_update_messages(struct merge_options *opt, · line 4270

Quoted 3 lines
merge-ort.c +0 -0
 	}
 	string_list_clear(&olist, 0);
 

Missing from this hunk:

        /* Also include needed rename limit adjustment now */
        diff_warn_rename_limit("merge.renamelimit",
                               opti->renames.needed_limit, 0);

This function explicitly writes to stdout, and will need to be adjusted, I think, before we can include an adjustment to this call in this patch.

Unless we override warn_routine() (which is used inside that function), that is. Which is hacky, and we would not have addressed the fflush(stdout) in diff_warn_rename_limit(). So I would much prefer something like this:

-- snip --

diff.c +14 -6
index 861282db1c3..87966602cbd 100644
@@ -6312,17 +6312,25 @@ static const char rename_limit_advice[] =
 N_("you may want to set your %s variable to at least "
    "%d and retry the command.");
 
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+			    FILE *out)
 {
-	fflush(stdout);
+	const char *fmt = NULL;
+
 	if (degraded_cc)
-		warning(_(degrade_cc_to_c_warning));
+		fmt = _(degrade_cc_to_c_warning);
 	else if (needed)
-		warning(_(rename_limit_warning));
+		fmt = _(rename_limit_warning);
 	else
 		return;
 	if (0 < needed)
-		warning(_(rename_limit_advice), varname, needed);
+		fmt = _(rename_limit_advice);
+
+	fflush(out);
+	if (out == stdout)
+		warning(fmt, varname, needed);
+	else
+		fprintf(out, fmt, varname, needed);
 }
 
 static void diff_flush_patch_all_file_pairs(struct diff_options *o)
@@ -6754,7 +6762,7 @@ int diff_result_code(struct diff_options *opt, int status)
 
 	diff_warn_rename_limit("diff.renameLimit",
 			       opt->needed_rename_limit,
-			       opt->degraded_cc_to_c);
+			       opt->degraded_cc_to_c, stdout);
 	if (!opt->flags.exit_with_status &&
 	    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
 		return status;
diff.h +2 -1
index 8ba85c5e605..be4ee68c0a2 100644
@@ -596,7 +596,8 @@ void diffcore_fix_diff_index(void);
 int diff_queue_is_empty(void);
 void diff_flush(struct diff_options*);
 void diff_free(struct diff_options*);
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+			    FILE *out);
 
 /* diff-raw status letters */
 #define DIFF_STATUS_ADDED		'A'
merge-ort.c +1 -1
index 0342f104836..e6b5a0e7c64 100644
@@ -4264,7 +4264,7 @@ void merge_switch_to_result(struct merge_options *opt,
 
 		/* Also include needed rename limit adjustment now */
 		diff_warn_rename_limit("merge.renamelimit",
-				       opti->renames.needed_limit, 0);
+				       opti->renames.needed_limit, 0, stdout);
 
 		trace2_region_leave("merge", "display messages", opt->repo);
 	}
merge-recursive.c +2 -1
index d9457797dbb..10b2948678c 100644
@@ -3731,7 +3731,8 @@ static void merge_finalize(struct merge_options *opt)
 		strbuf_release(&opt->obuf);
 	if (show(opt, 2))
 		diff_warn_rename_limit("merge.renamelimit",
-				       opt->priv->needed_rename_limit, 0);
+				       opt->priv->needed_rename_limit, 0,
+				       stdout);
 	FREE_AND_NULL(opt->priv);
 }
 

-- snap --

The rest of the patch looks good to me.

Thanks,
Dscho

Quoted 27 lines
merge-ort.c +1 -1
@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,
 	}
 
 	if (display_update_msgs)
-		merge_display_update_messages(opt, result);
+		merge_display_update_messages(opt, result, stdout);
 
 	merge_finalize(opt, result);
 }
merge-ort.h +2 -2
index e5aec45b18f..d643b47cb7c 100644
@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,
  * so only call this when bypassing merge_switch_to_result().
  */
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result);
+				   struct merge_result *result,
+				   FILE *stream);
 
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
--

gitgitgadget

#20 lore raw

Re: [PATCH 06/12] merge-ort: allow update messages to be written to different file stream

Quoted 23 lines · Johannes Schindelin

Hi Elijah,

Quoted 18 lines · Elijah Newren via GitGitGadget
merge-ort.c +2 -1
index f9e35b0f96b..b78dde55ad9 100644
@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 }
 
 void merge_display_update_messages(struct merge_options *opt,
-                                struct merge_result *result)
+                                struct merge_result *result,
+                                FILE *stream)
 {
      struct merge_options_internal *opti = result->priv;
      struct hashmap_iter iter;
@@ -4263,7 +4264,7 @@ void merge_display_update_messages(struct merge_options *opt,
      for (i = 0; i < olist.nr; ++i) {
              struct strbuf *sb = olist.items[i].util;
 
Quoted 5 lines

merge-ort.c void merge_display_update_messages(struct merge_options *opt, · line 4267

Quoted 2 lines
merge-ort.c +1 -1
-             printf("%s", sb->buf);
+             fprintf(stream, "%s", sb->buf);

Maybe strbuf_write(sb, stream); instead? Whenever I see a "%s", I feel like it's unnecessary churn...

Makes sense.

Quoted 4 lines

merge-ort.c void merge_display_update_messages(struct merge_options *opt, · line 4270

Quoted 3 lines
merge-ort.c +0 -0
      }
      string_list_clear(&olist, 0);
 
Quoted 8 lines

Missing from this hunk:

        /* Also include needed rename limit adjustment now */
        diff_warn_rename_limit("merge.renamelimit",
                               opti->renames.needed_limit, 0);

This function explicitly writes to stdout, and will need to be adjusted, I think, before we can include an adjustment to this call in this patch.

Oh, good point.

Quoted 82 lines

Unless we override warn_routine() (which is used inside that function), that is. Which is hacky, and we would not have addressed the fflush(stdout) in diff_warn_rename_limit(). So I would much prefer something like this:

-- snip --

diff.c +14 -6
index 861282db1c3..87966602cbd 100644
@@ -6312,17 +6312,25 @@ static const char rename_limit_advice[] =
 N_("you may want to set your %s variable to at least "
    "%d and retry the command.");
 
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+                           FILE *out)
 {
-       fflush(stdout);
+       const char *fmt = NULL;
+
        if (degraded_cc)
-               warning(_(degrade_cc_to_c_warning));
+               fmt = _(degrade_cc_to_c_warning);
        else if (needed)
-               warning(_(rename_limit_warning));
+               fmt = _(rename_limit_warning);
        else
                return;
        if (0 < needed)
-               warning(_(rename_limit_advice), varname, needed);
+               fmt = _(rename_limit_advice);
+
+       fflush(out);
+       if (out == stdout)
+               warning(fmt, varname, needed);
+       else
+               fprintf(out, fmt, varname, needed);
 }
 
 static void diff_flush_patch_all_file_pairs(struct diff_options *o)
@@ -6754,7 +6762,7 @@ int diff_result_code(struct diff_options *opt, int status)
 
        diff_warn_rename_limit("diff.renameLimit",
                               opt->needed_rename_limit,
-                              opt->degraded_cc_to_c);
+                              opt->degraded_cc_to_c, stdout);
        if (!opt->flags.exit_with_status &&
            !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
                return status;
diff.h +2 -1
index 8ba85c5e605..be4ee68c0a2 100644
@@ -596,7 +596,8 @@ void diffcore_fix_diff_index(void);
 int diff_queue_is_empty(void);
 void diff_flush(struct diff_options*);
 void diff_free(struct diff_options*);
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+                           FILE *out);
 
 /* diff-raw status letters */
 #define DIFF_STATUS_ADDED              'A'
merge-ort.c +1 -1
index 0342f104836..e6b5a0e7c64 100644
@@ -4264,7 +4264,7 @@ void merge_switch_to_result(struct merge_options *opt,
 
                /* Also include needed rename limit adjustment now */
                diff_warn_rename_limit("merge.renamelimit",
-                                      opti->renames.needed_limit, 0);
+                                      opti->renames.needed_limit, 0, stdout);
 
                trace2_region_leave("merge", "display messages", opt->repo);
        }
merge-recursive.c +0 -0
index d9457797dbb..10b2948678c 100644

merge-recursive.c static void merge_finalize(struct merge_options *opt)

Quoted 10 lines
merge-recursive.c +2 -2
@@ -3731,7 +3731,8 @@ static void merge_finalize(struct merge_options *opt)
                strbuf_release(&opt->obuf);
        if (show(opt, 2))
                diff_warn_rename_limit("merge.renamelimit",
-                                      opt->priv->needed_rename_limit, 0);
+                                      opt->priv->needed_rename_limit, 0,
+                                      stdout);
        FREE_AND_NULL(opt->priv);
 }
 
-- snap --

I like this, but believe it makes more sense as a preparatory patch. So I created one and made you the author, and included your signed-off-by. That okay with you?

Quoted 2 lines

The rest of the patch looks good to me.

Thanks!

#21 lore raw

PATCH 7/12 merge-tree: support including merge messages in output

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

When running git merge-tree --write-tree, we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge. Merges also have informational messages, ("Auto-merging <PATH>", "CONFLICT (content):
...", "CONFLICT (file/directory)", etc.) In fact, when non-content conflicts occur (such as file/directory, modify/delete, add/add with differing modes, rename/rename (1to2), etc.), these informational messages are often the only notification since these conflicts are not representable in the contents of the file.

Add a --[no-]messages option so that callers can request these messages be included at the end of the output. Include such messages by default when there are conflicts, and omit them by default when the merge is clean.

---
 Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----
 builtin/merge-tree.c             | 24 +++++++++++++----
 t/t4301-merge-tree-real.sh       | 21 +++++++++++++++
 3 files changed, 78 insertions(+), 12 deletions(-)
Documentation/git-merge-tree.txt +38 -7
index b900bc1362c..fd7a867de60 100644
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree
 SYNOPSIS
 --------
 [verse]
-'git merge-tree' [--write-tree] <branch1> <branch2>
+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>
 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>
 
 DESCRIPTION
@@ -38,17 +38,47 @@ than the first form even on successful merges (especially if working
 in large repositories).  The remainder of this manual will only
 discuss the first form.
 
+OPTIONS
+-------
+
+--[no-]messages::
+	Write any informational messages such as "Auto-merging <path>"
+	or CONFLICT notices to the end of stdout.  If unspecified, the
+	default is to include these messages if there are merge
+	conflicts, and to omit them otherwise.
+
 OUTPUT
 ------
 
-For either a successful or conflicted merge, the output from
-git-merge-tree is simply one line:
+By default, for a successful merge, the output from git-merge-tree is
+simply one line:
+
+	<OID of toplevel tree>
+
+Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Informational messages>
+
+These are discussed individually below.
+
+OID of toplevel tree
+~~~~~~~~~~~~~~~~~~~~
+
+This is a tree object that represents what would be checked out in the
+working tree at the end of `git merge`.  If there were conflicts, then
+files within this tree may have embedded conflict markers.
+
+Informational messages
+~~~~~~~~~~~~~~~~~~~~~~
+
+This always starts with a blank line to separate it from the previous
+section, and then has free-form messages about the merge, such as:
 
-The printed tree object corresponds to what would be checked out in
-the working tree at the end of `git merge`, and thus may have files
-with conflict markers in them.
+  * "Auto-merging <file>"
+  * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
+  * "Failed to merge submodule <submodule> (<reason>)"
+  * "Warning: cannot merge binary files: <filename>"
 
 EXIT STATUS
 -----------
@@ -72,7 +102,8 @@ be used as a part of a series of steps such as
 
 However, it does not quite fit into the same category of low-level
 plumbing commands since the possibility of merge conflicts give it a
-much higher chance of the command not succeeding.
+much higher chance of the command not succeeding (and NEWTREE containing
+a bunch of stuff other than just a toplevel tree).
 
 GIT
 ---
builtin/merge-tree.c +19 -5
index 0c19639594d..560640ad911 100644
@@ -391,6 +391,7 @@ static int trivial_merge(const char *base,
 struct merge_tree_options {
 	int real;
 	int trivial;
+	int show_messages;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,
 		commit_list_insert(j->item, &merge_bases);
 
 	merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+
 	if (result.clean < 0)
 		die(_("failure to merge"));
-	else if (!result.clean)
-		printf(_("Conflicts!\n"));
+
+	if (o->show_messages == -1)
+		o->show_messages = !result.clean;
+
+	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+	if (o->show_messages) {
+		printf("\n");
+		merge_display_update_messages(&opt, &result, stdout);
+	}
 	merge_finalize(&opt, &result);
 	return !result.clean; /* result.clean < 0 handled above */
 }
 
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	struct merge_tree_options o = { 0 };
+	struct merge_tree_options o = { .show_messages = -1 };
 	int expected_remaining_argc;
+	int original_argc;
 
 	const char * const merge_tree_usage[] = {
-		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
 		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
 		NULL
 	};
@@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			 N_("do a real merge instead of a trivial merge")),
 		OPT_BOOL(0, "trivial-merge", &o.trivial,
 			 N_("do a trivial merge only")),
+		OPT_BOOL(0, "messages", &o.show_messages,
+			 N_("also show informational/conflict messages")),
 		OPT_END()
 	};
 
@@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 		usage_with_options(merge_tree_usage, mt_options);
 
 	/* Parse arguments */
+	original_argc = argc;
 	argc = parse_options(argc, argv, prefix, mt_options,
 			     merge_tree_usage, 0);
 	if (o.real && o.trivial)
 		die(_("--write-tree and --trivial-merge are incompatible"));
+	if (!o.real && original_argc < argc)
+		die(_("--write-tree must be specified if any other options are"));
 	if (o.real || o.trivial) {
 		expected_remaining_argc = (o.real ? 2 : 3);
 		if (argc != expected_remaining_argc)
t/t4301-merge-tree-real.sh +21 -0
index e03688515c5..c34f8e6c1ed 100755
@@ -84,4 +84,25 @@ test_expect_success 'Barf on too many arguments' '
 	grep "^usage: git merge-tree" expect
 '
 
+test_expect_success 'test conflict notices and such' '
+	test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	# Expected results:
+	#   "greeting" should merge with conflicts
+	#   "numbers" should merge cleanly
+	#   "whatever" has *both* a modify/delete and a file/directory conflict
+	cat <<-EOF >expect &&
+	HASH
+
+	Auto-merging greeting
+	CONFLICT (content): Merge conflict in greeting
+	Auto-merging numbers
+	CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
+	CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.
+	EOF
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#22 lore raw

Re: [PATCH 07/12] merge-tree: support including merge messages in output

Documentation/git-merge-tree.txt be used as a part of a series of steps such as · line 106

Quoted 9 lines · Elijah Newren via GitGitGadget

EXIT STATUS
-----------

Documentation/git-merge-tree.txt +2 -1
@@ -72,7 +102,8 @@ be used as a part of a series of steps such as
 
 However, it does not quite fit into the same category of low-level
 plumbing commands since the possibility of merge conflicts give it a
-much higher chance of the command not succeeding.
+much higher chance of the command not succeeding (and NEWTREE containing
+a bunch of stuff other than just a toplevel tree).

Is this hunk really related to this commit or should it go into a previous commit?

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 446

Quoted 8 lines
builtin/merge-tree.c +1 -1
@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,
                commit_list_insert(j->item, &merge_bases);
 
        merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-       printf("%s\n", oid_to_hex(&result.tree->object.oid));
+
        if (result.clean < 0)
                die(_("failure to merge"));

So this addresses the comment I made in a previous commit related to the fact that if result.clean < 0 we might not have a valid tree that we can print. I think though that it would be better if that was addressed in a previous commit.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 447

Quoted 2 lines
builtin/merge-tree.c +0 -2
-       else if (!result.clean)
-               printf(_("Conflicts!\n"));

Ok, so we don't print "Conflicts!\n" now, which makes me wonder if we should have printed it in the first place in previous commits.

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 492

Quoted 4 lines
builtin/merge-tree.c +2 -0
        if (o.real && o.trivial)
                die(_("--write-tree and --trivial-merge are incompatible"));
+       if (!o.real && original_argc < argc)
+               die(_("--write-tree must be specified if any other options are"));

Is this necessary? It looks to me like another thing that would be simplified if we were just adding a new command...

#23 lore raw

Re: [PATCH 07/12] merge-tree: support including merge messages in output

Quoted 8 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget

EXIT STATUS
-----------

(quoted diff) +0 -0
@@ -72,7 +102,8 @@ be used as a part of a series of steps such as
 
Quoted 8 lines

Documentation/git-merge-tree.txt be used as a part of a series of steps such as · line 106

Quoted 5 lines
Documentation/git-merge-tree.txt +2 -1
 However, it does not quite fit into the same category of low-level
 plumbing commands since the possibility of merge conflicts give it a
-much higher chance of the command not succeeding.
+much higher chance of the command not succeeding (and NEWTREE containing
+a bunch of stuff other than just a toplevel tree).

Is this hunk really related to this commit or should it go into a previous commit?

It's meant to first be related to this commit, though as you pointed out below, I had some accidental stuff not cleaned out of the earlier commit.

Quoted 14 lines

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 446

Quoted 8 lines
builtin/merge-tree.c +1 -1
@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,
                commit_list_insert(j->item, &merge_bases);
 
        merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-       printf("%s\n", oid_to_hex(&result.tree->object.oid));
+
        if (result.clean < 0)
                die(_("failure to merge"));

So this addresses the comment I made in a previous commit related to the fact that if result.clean < 0 we might not have a valid tree that we can print. I think though that it would be better if that was addressed in a previous commit.

Quoted 5 lines

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 447

Quoted 2 lines
builtin/merge-tree.c +0 -2
-       else if (!result.clean)
-               printf(_("Conflicts!\n"));

Ok, so we don't print "Conflicts!\n" now, which makes me wonder if we should have printed it in the first place in previous commits.

Yep, good flag on both of these last two comments. When I was fixing this up I didn't squash it back in early enough. Thanks for reading carefully.

Quoted 7 lines

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 492

Quoted 4 lines
builtin/merge-tree.c +2 -0
        if (o.real && o.trivial)
                die(_("--write-tree and --trivial-merge are incompatible"));
+       if (!o.real && original_argc < argc)
+               die(_("--write-tree must be specified if any other options are"));

Is this necessary? It looks to me like another thing that would be simplified if we were just adding a new command...

I think the code is harder to read than it should be. I changed it to:

    if (o.mode == 't' && original_argc < argc)
        die(_("--trivial-merge is incompatible with all other options"));

which I think is clearer; it points out that it's only about the deprecated --trivial-merge option and how it's incompatible with all other options.

#24 lore raw

Re: [PATCH 07/12] merge-tree: support including merge messages in output

In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Quoted 12 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

When running git merge-tree --write-tree, we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge. Merges also have informational messages, ("Auto-merging <PATH>", "CONFLICT (content):
...", "CONFLICT (file/directory)", etc.) In fact, when non-content conflicts occur (such as file/directory, modify/delete, add/add with differing modes, rename/rename (1to2), etc.), these informational messages are often the only notification since these conflicts are not representable in the contents of the file.

Quoted 4 lines

Add a --[no-]messages option so that callers can request these messages be included at the end of the output. Include such messages by default when there are conflicts, and omit them by default when the merge is clean.

Makes sense.

Quoted 13 lines
builtin/merge-tree.c +1 -2
index 0c19639594d..560640ad911 100644
@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,
 		commit_list_insert(j->item, &merge_bases);
 
 	merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+
 	if (result.clean < 0)
 		die(_("failure to merge"));
-	else if (!result.clean)

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 455

Quoted 10 lines
builtin/merge-tree.c +9 -1
-		printf(_("Conflicts!\n"));
+
+	if (o->show_messages == -1)
+		o->show_messages = !result.clean;
+
+	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+	if (o->show_messages) {
+		printf("\n");
+		merge_display_update_messages(&opt, &result, stdout);
+	}

Excellent.

Quoted 27 lines
builtin/merge-tree.c +5 -2
 	merge_finalize(&opt, &result);
 	return !result.clean; /* result.clean < 0 handled above */
 }
 
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	struct merge_tree_options o = { 0 };
+	struct merge_tree_options o = { .show_messages = -1 };
 	int expected_remaining_argc;
+	int original_argc;
 
 	const char * const merge_tree_usage[] = {
-		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
 		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
 		NULL
 	};
@@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			 N_("do a real merge instead of a trivial merge")),
 		OPT_BOOL(0, "trivial-merge", &o.trivial,
 			 N_("do a trivial merge only")),
+		OPT_BOOL(0, "messages", &o.show_messages,
+			 N_("also show informational/conflict messages")),
 		OPT_END()
 	};
 

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 492

Quoted 10 lines
builtin/merge-tree.c +3 -0
@@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 		usage_with_options(merge_tree_usage, mt_options);
 
 	/* Parse arguments */
+	original_argc = argc;
 	argc = parse_options(argc, argv, prefix, mt_options,
 			     merge_tree_usage, 0);
 	if (o.real && o.trivial)
 		die(_("--write-tree and --trivial-merge are incompatible"));
+	if (!o.real && original_argc < argc)
+		die(_("--write-tree must be specified if any other options are"));

Hmm. Well. Hmm.

I'd rather keep --write-tree neat and optional. What's wrong with allowing

	git merge-tree --no-messages HEAD MERGE_HEAD

?

To be clear, I think we need this instead:

	if (o.trivial && o.show_messages >= 0)
		die(_("--trivial-merge is incompatible with additional options"));

I like the rest of the patch very much!

Thank you,
Dscho

Quoted 37 lines
builtin/merge-tree.c +0 -0
 	if (o.real || o.trivial) {
 		expected_remaining_argc = (o.real ? 2 : 3);
 		if (argc != expected_remaining_argc)
t/t4301-merge-tree-real.sh +21 -1
index e03688515c5..c34f8e6c1ed 100755
@@ -84,4 +84,25 @@ test_expect_success 'Barf on too many arguments' '
 	grep "^usage: git merge-tree" expect
 '
 
+test_expect_success 'test conflict notices and such' '
+	test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	# Expected results:
+	#   "greeting" should merge with conflicts
+	#   "numbers" should merge cleanly
+	#   "whatever" has *both* a modify/delete and a file/directory conflict
+	cat <<-EOF >expect &&
+	HASH
+
+	Auto-merging greeting
+	CONFLICT (content): Merge conflict in greeting
+	Auto-merging numbers
+	CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
+	CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.
+	EOF
+
+	test_cmp expect actual
+'
+
 test_done
--

gitgitgadget

#25 lore raw

Re: [PATCH 07/12] merge-tree: support including merge messages in output

Quoted 91 lines · Johannes Schindelin

Hi Elijah,

Quoted 12 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

When running git merge-tree --write-tree, we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge. Merges also have informational messages, ("Auto-merging <PATH>", "CONFLICT (content):
...", "CONFLICT (file/directory)", etc.) In fact, when non-content conflicts occur (such as file/directory, modify/delete, add/add with differing modes, rename/rename (1to2), etc.), these informational messages are often the only notification since these conflicts are not representable in the contents of the file.

Quoted 4 lines

Add a --[no-]messages option so that callers can request these messages be included at the end of the output. Include such messages by default when there are conflicts, and omit them by default when the merge is clean.

Makes sense.

Quoted 13 lines
builtin/merge-tree.c +1 -2
index 0c19639594d..560640ad911 100644
@@ -440,22 +441,30 @@ static int real_merge(struct merge_tree_options *o,
              commit_list_insert(j->item, &merge_bases);
 
      merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-     printf("%s\n", oid_to_hex(&result.tree->object.oid));
+
      if (result.clean < 0)
              die(_("failure to merge"));
-     else if (!result.clean)

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 455

Quoted 10 lines
builtin/merge-tree.c +9 -1
-             printf(_("Conflicts!\n"));
+
+     if (o->show_messages == -1)
+             o->show_messages = !result.clean;
+
+     printf("%s\n", oid_to_hex(&result.tree->object.oid));
+     if (o->show_messages) {
+             printf("\n");
+             merge_display_update_messages(&opt, &result, stdout);
+     }

Excellent.

Quoted 27 lines
builtin/merge-tree.c +5 -2
      merge_finalize(&opt, &result);
      return !result.clean; /* result.clean < 0 handled above */
 }
 
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-     struct merge_tree_options o = { 0 };
+     struct merge_tree_options o = { .show_messages = -1 };
      int expected_remaining_argc;
+     int original_argc;
 
      const char * const merge_tree_usage[] = {
-             N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+             N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
              N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
              NULL
      };
@@ -464,6 +473,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                       N_("do a real merge instead of a trivial merge")),
              OPT_BOOL(0, "trivial-merge", &o.trivial,
                       N_("do a trivial merge only")),
+             OPT_BOOL(0, "messages", &o.show_messages,
+                      N_("also show informational/conflict messages")),
              OPT_END()
      };
 

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 492

Quoted 10 lines
builtin/merge-tree.c +3 -0
@@ -472,10 +483,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
              usage_with_options(merge_tree_usage, mt_options);
 
      /* Parse arguments */
+     original_argc = argc;
      argc = parse_options(argc, argv, prefix, mt_options,
                           merge_tree_usage, 0);
      if (o.real && o.trivial)
              die(_("--write-tree and --trivial-merge are incompatible"));
+     if (!o.real && original_argc < argc)
+             die(_("--write-tree must be specified if any other options are"));

Hmm. Well. Hmm.

Quoted 6 lines

I'd rather keep --write-tree neat and optional. What's wrong with allowing

        git merge-tree --no-messages HEAD MERGE_HEAD

?

Yeah, oops. Nothing is wrong with that...but do note that there is something wrong with this:

    git merge-tree --no-messages MERGE_BASE HEAD MERGE_HEAD

because the three argument form is signalling the deprecated trivial merge, and the trivial merge code doesn't handle any options. You were right to call out my code since I placed it too early and made it a bit obtuse. I've changed it to:

    if (o.mode == 't' && original_argc < argc)
        die(_("--trivial-merge is incompatible with all other options"));

where o.mode is guaranteed to be set before that check.

Quoted 4 lines

To be clear, I think we need this instead:

        if (o.trivial && o.show_messages >= 0)
                die(_("--trivial-merge is incompatible with additional options"));

Most of that is better, assuming the o.trivial check handles the implicit case too. Thanks. I'm not so sure about the check on o.show_messages, though, because then I'll have to keep adding additional checks for each new additional option (and future patches will add more). So, I think I'll keep the original_argc < argc part of the check instead of that. :-)

Quoted 1 line

I like the rest of the patch very much!

Thanks!

#26 lore raw

PATCH 8/12 merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u -- conflicted files with their mode, oid, and stage.

---
 merge-ort.c | 31 +++++++++++++++++++++++++++++++
 merge-ort.h | 21 +++++++++++++++++++++
 2 files changed, 52 insertions(+)
merge-ort.c +31 -0
index b78dde55ad9..5e7cea6cc8f 100644
@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,
 	trace2_region_leave("merge", "display messages", opt->repo);
 }
 
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files)
+{
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+	struct merge_options_internal *opti = result->priv;
+
+	strmap_for_each_entry(&opti->conflicted, &iter, e) {
+		const char *path = e->key;
+		struct conflict_info *ci = e->value;
+		int i;
+
+		VERIFY_CI(ci);
+
+		for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+			struct stage_info *si;
+
+			if (!(ci->filemask & (1ul << i)))
+				continue;
+
+			si = xmalloc(sizeof(*si));
+			si->stage = i+1;
+			si->mode = ci->stages[i].mode;
+			oidcpy(&si->oid, &ci->stages[i].oid);
+			string_list_append(conflicted_files, path)->util = si;
+		}
+	}
+	/* string_list_sort() uses a stable sort, so we're good */
+	string_list_sort(conflicted_files);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
 			    struct tree *head,
 			    struct merge_result *result,
merge-ort.h +21 -0
index d643b47cb7c..e635a294ea8 100644
@@ -2,6 +2,7 @@
 #define MERGE_ORT_H
 
 #include "merge-recursive.h"
+#include "hash.h"
 
 struct commit;
 struct tree;
@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,
 				   struct merge_result *result,
 				   FILE *stream);
 
+struct stage_info {
+	struct object_id oid;
+	int mode;
+	int stage;
+};
+
+/*
+ * Provide a list of path -> {struct stage_info*} mappings for
+ * all conflicted files.  Note that each path could appear up to three
+ * times in the list, corresponding to 3 different stage entries.  In short,
+ * this basically provides the info that would be printed by `ls-files -u`.
+ *
+ * result should have been populated by a call to
+ * one of the merge_incore_[non]recursive() functions.
+ *
+ * conflicted_files should be empty before calling this function.
+ */
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files);
+
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
 		    struct merge_result *result);
Signature · 1 line
gitgitgadget
#27 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 2 lines · Elijah Newren via GitGitGadget

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u -- conflicted

This made me wonder if "-- conflicted" should be part of the ls-files -u command. Maybe "by ls-files -u, which means" would make things a bit clearer.

Quoted 1 line

files with their mode, oid, and stage.

#28 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 4 lines · Christian Couder

On Sat, Jan 22, 2022 at 10:56 PM Elijah Newren via GitGitGadget <[email protected]> wrote:

Quoted 8 lines
Quoted 2 lines

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u -- conflicted

This made me wonder if "-- conflicted" should be part of the ls-files -u command. Maybe "by ls-files -u, which means" would make things a bit clearer.

Quoted 1 line

files with their mode, oid, and stage.

Yes, I like that wording better. Thanks!

#29 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Elijah Newren via GitGitGadget
Quoted 11 lines · Elijah Newren via GitGitGadget
merge-ort.c +11 -0
+void merge_get_conflicted_files(struct merge_result *result,
+                               struct string_list *conflicted_files)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *e;
+       struct merge_options_internal *opti = result->priv;
+
+       strmap_for_each_entry(&opti->conflicted, &iter, e) {
+               const char *path = e->key;
+               struct conflict_info *ci = e->value;
+               int i;

merge-ort.c void merge_display_update_messages(struct merge_options *opt, · line 4298

Quoted 10 lines
merge-ort.c +10 -0
+
+               VERIFY_CI(ci);
+
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                       struct stage_info *si;
+
+                       if (!(ci->filemask & (1ul << i)))
+                               continue;
+
+                       si = xmalloc(sizeof(*si));

It's probably a premature optimization, so feel free to ignore, but as MERGE_BASE and MERGE_SIDE2 are constants, and ci->filemask is constant inside the 'for' loop, we could compute before the 'for' loop how many 'struct stage_info' we will need and allocate them all at once before the 'for' loop.

Quoted 9 lines
merge-ort.c +9 -0
+                       si->stage = i+1;
+                       si->mode = ci->stages[i].mode;
+                       oidcpy(&si->oid, &ci->stages[i].oid);
+                       string_list_append(conflicted_files, path)->util = si;
+               }
+       }
+       /* string_list_sort() uses a stable sort, so we're good */
+       string_list_sort(conflicted_files);
+}
#30 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 23 lines · Christian Couder
Quoted 19 lines · Elijah Newren via GitGitGadget
merge-ort.c +19 -0
+void merge_get_conflicted_files(struct merge_result *result,
+                               struct string_list *conflicted_files)
+{
+       struct hashmap_iter iter;
+       struct strmap_entry *e;
+       struct merge_options_internal *opti = result->priv;
+
+       strmap_for_each_entry(&opti->conflicted, &iter, e) {
+               const char *path = e->key;
+               struct conflict_info *ci = e->value;
+               int i;
+
+               VERIFY_CI(ci);
+
+               for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                       struct stage_info *si;
+
+                       if (!(ci->filemask & (1ul << i)))
+                               continue;
Quoted 8 lines

Review

Quoted 2 lines
(quoted diff) +2 -0
+
+                       si = xmalloc(sizeof(*si));

It's probably a premature optimization, so feel free to ignore, but as MERGE_BASE and MERGE_SIDE2 are constants, and ci->filemask is constant inside the 'for' loop, we could compute before the 'for' loop how many 'struct stage_info' we will need and allocate them all at once before the 'for' loop.

That's an interesting idea, but if we allocate all at once, then we'd need to be able to deallocate all at once. I'm not sure how to do that, but basically the straightforward

    string_list_free(conflicted_files, 1);

that callers can use with the current function would no longer be valid, and it might be somewhat difficult for callers to figure out how to free the memory that was allocated.

#31 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Quoted 5 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u -- conflicted files with their mode, oid, and stage.

Hmm. Okay.

I am not really a fan of that output where we use a variable number of lines per file. As in: in the regular case, where a file was modified in divergent ways, we get three lines. But if it was deleted in one branch, or if it was created in both branches, we have only two lines.

Frankly, I'd much rather have 3 lines for each and every conflict.

Granted, we currently only have three stages, and we can pretty much guarantee that at least two of these stages are non-empty (otherwise where would be the conflict?).

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

But still. It makes me uneasy to have that much variability in machine-consumable output.

And who knows, maybe if we're ever implementing more sophisticated merge strategies for octopus merges, we might end up with more stages...

In other words...

Quoted 61 lines
---
 merge-ort.c | 31 +++++++++++++++++++++++++++++++
 merge-ort.h | 21 +++++++++++++++++++++
 2 files changed, 52 insertions(+)
merge-ort.c +31 -0
index b78dde55ad9..5e7cea6cc8f 100644
@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,
 	trace2_region_leave("merge", "display messages", opt->repo);
 }
 
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files)
+{
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+	struct merge_options_internal *opti = result->priv;
+
+	strmap_for_each_entry(&opti->conflicted, &iter, e) {
+		const char *path = e->key;
+		struct conflict_info *ci = e->value;
+		int i;
+
+		VERIFY_CI(ci);
+
+		for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+			struct stage_info *si;
+
+			if (!(ci->filemask & (1ul << i)))
+				continue;
+
+			si = xmalloc(sizeof(*si));
+			si->stage = i+1;
+			si->mode = ci->stages[i].mode;
+			oidcpy(&si->oid, &ci->stages[i].oid);
+			string_list_append(conflicted_files, path)->util = si;
+		}
+	}
+	/* string_list_sort() uses a stable sort, so we're good */
+	string_list_sort(conflicted_files);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
 			    struct tree *head,
 			    struct merge_result *result,
merge-ort.h +1 -0
index d643b47cb7c..e635a294ea8 100644
@@ -2,6 +2,7 @@
 #define MERGE_ORT_H
 
 #include "merge-recursive.h"
+#include "hash.h"
 
 struct commit;
 struct tree;

merge-ort.h void merge_display_update_messages(struct merge_options *opt, · line 97

Quoted 8 lines
merge-ort.h +5 -0
@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,
 				   struct merge_result *result,
 				   FILE *stream);
 
+struct stage_info {
+	struct object_id oid;
+	int mode;
+	int stage;
+};

... I'd rather not tack this onto a string_list but instead have something like:

	struct conflict_info {
		struct {
			struct object_id oid;
			int mode;
			const char *path;
		} stages[3]
	};

where path can be NULL to indicate that this stage is missing.

Apart from this concern about the overall design, the patch looks good, of course.

Ciao,
Dscho

Quoted 22 lines
merge-ort.h +15 -1
+
+/*
+ * Provide a list of path -> {struct stage_info*} mappings for
+ * all conflicted files.  Note that each path could appear up to three
+ * times in the list, corresponding to 3 different stage entries.  In short,
+ * this basically provides the info that would be printed by `ls-files -u`.
+ *
+ * result should have been populated by a call to
+ * one of the merge_incore_[non]recursive() functions.
+ *
+ * conflicted_files should be empty before calling this function.
+ */
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files);
+
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
 		    struct merge_result *result);
--

gitgitgadget

#32 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 11 lines · Johannes Schindelin

Hi Elijah,

Quoted 5 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u -- conflicted files with their mode, oid, and stage.

Quoted 8 lines

Hmm. Okay.

I am not really a fan of that output where we use a variable number of lines per file. As in: in the regular case, where a file was modified in divergent ways, we get three lines. But if it was deleted in one branch, or if it was created in both branches, we have only two lines.

Frankly, I'd much rather have 3 lines for each and every conflict.

Out of curiosity, does this also mean you feel like git ls-files -u is mis-designed (even if we're stuck with it for backward compatibility reasons)?

Anyway, if we make this change guaranteeing there are 3 lines for each and every conflict, that probably implies we can remove the stage field (just letting it be implicit), right? And it also implies that the "path" field of the lines is now duplicate information. Should the path be printed on each line regardless of the duplication? Should the path be printed separately on its own line, followed by the three lines of (mode, oid) pairs? Or should the format be something else entirely?

Quoted 1 line

Granted, we currently only have three stages...

I thought that was true too, but Junio informed me otherwise[1]. Granted, ort does not currently make use of that, but it could. Not sure I want to rule it out.

[1] https://lore.kernel.org/git/[email protected]/

Quoted 3 lines

...and we can pretty much
guarantee that at least two of these stages are non-empty (otherwise where would be the conflict?).

No, that's not true. See the paragraph about "conflict types" from the final patch in this series, where I mentioned three different types of conflicts that can result in a path having a single higher order stage.

Quoted 4 lines

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?
In such a design, you'd have to do the extra post-processing to
recognize the all-zero modes and hashes and toss them.

To me, that seems like more work than just handling the ls-files -u style of output, and doesn't have the advantage of showing things in a format users may already be familiar with (see above about dropping stages and maybe also pathname). Since the ls-files -u output is simply several lines of:
<mode> <hash> <stage> <filename>
You can get the number of conflicted files by counting the number of *unique* filenames. If you want to see which lines are about the same file, get all the lines with the same filename -- they are sorted by (<filename>, <stage>) for convenience, much like ls-files -u is.

On a different angle, I'm also slightly worried there's an embedded assumption here, one that I tried to address in the "Mistake to avoid" section I added in my last patch of this series. What if you have a conflicting merge and 0 lines of output in the conflicting info section? Is there something about the reason you're asking for three lines of output per conflicted file which is going to make you overlook that particular possibility and not handle it?

Quoted 5 lines

But still. It makes me uneasy to have that much variability in machine-consumable output.

And who knows, maybe if we're ever implementing more sophisticated merge strategies for octopus merges, we might end up with more stages...

Um, if we need to handle more stages, wouldn't that undercut your request for exactly 3 stages? Wouldn't it suggest that we would indeed want to have a flexible number of lines?

Quoted 75 lines

In other words...

Quoted 61 lines
---
 merge-ort.c | 31 +++++++++++++++++++++++++++++++
 merge-ort.h | 21 +++++++++++++++++++++
 2 files changed, 52 insertions(+)
merge-ort.c +31 -0
index b78dde55ad9..5e7cea6cc8f 100644
@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,
      trace2_region_leave("merge", "display messages", opt->repo);
 }
 
+void merge_get_conflicted_files(struct merge_result *result,
+                             struct string_list *conflicted_files)
+{
+     struct hashmap_iter iter;
+     struct strmap_entry *e;
+     struct merge_options_internal *opti = result->priv;
+
+     strmap_for_each_entry(&opti->conflicted, &iter, e) {
+             const char *path = e->key;
+             struct conflict_info *ci = e->value;
+             int i;
+
+             VERIFY_CI(ci);
+
+             for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+                     struct stage_info *si;
+
+                     if (!(ci->filemask & (1ul << i)))
+                             continue;
+
+                     si = xmalloc(sizeof(*si));
+                     si->stage = i+1;
+                     si->mode = ci->stages[i].mode;
+                     oidcpy(&si->oid, &ci->stages[i].oid);
+                     string_list_append(conflicted_files, path)->util = si;
+             }
+     }
+     /* string_list_sort() uses a stable sort, so we're good */
+     string_list_sort(conflicted_files);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
                          struct tree *head,
                          struct merge_result *result,
merge-ort.h +1 -0
index d643b47cb7c..e635a294ea8 100644
@@ -2,6 +2,7 @@
 #define MERGE_ORT_H
 
 #include "merge-recursive.h"
+#include "hash.h"
 
 struct commit;
 struct tree;

merge-ort.h void merge_display_update_messages(struct merge_options *opt, · line 97

Quoted 8 lines
merge-ort.h +5 -0
@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,
                                 struct merge_result *result,
                                 FILE *stream);
 
+struct stage_info {
+     struct object_id oid;
+     int mode;
+     int stage;
+};

... I'd rather not tack this onto a string_list but instead have something like:

Quoted 1 line
        struct conflict_info {

Nit: "conflict_info" is already taken for another structure; we'd need a different name. But it does illustrate the idea just fine.

Quoted 8 lines
                struct {
                        struct object_id oid;
                        int mode;
                        const char *path;
                } stages[3]
        };

where path can be NULL to indicate that this stage is missing.

I'll get back to the data structure in a second, but the note about path being NULL is interesting. I'm presuming that oid and mode are all-zeros as well, yes? Now, your original request was that you wanted a line printed for all three stages. If we're printing oid, mode, and path for each stage, what do we print for the path one these lines? Is it (1) "(null)", (2) "", or (3) the real pathname (via carefully comparing to surrounding stages to find out what it is)? If we're changing the format to only print the name of the path once, does the code need to loop over the list of conflicts in order to find out the name (since the first or second stage might have a NULL path field)?

In regards to the overall data structure and your comment about
string_list: I'm slightly confused. You say you want to avoid a string_list, but you've only accounted for there being one conflict in this data structure. I don't see how this suggestion avoids the need for an array or a string_list or some kind of container to hold multiple of these.

The inclusion of path within the stages array is also interesting -- should there be (up to) three copies of the pathname allocated per conflict? Seems a bit wasteful. Wouldn't it make more sense to have something like

    struct conflicts {
        struct {
            struct object_id oid;
            unsigned short mode;
        } stages[3]
    };

and then have a string_list storing the pathname -> conflicts mappings to avoid the pathname duplication? Or is there something you really find problematic about the string_list and you really want an array or other data structure?

#33 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

-- Hannes

#34 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 15 lines · Johannes Sixt

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Quoted 5 lines

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

Ah, so...I didn't understand his misunderstanding? Using stages as a cue to the parser where the new conflict begins is broken; you should instead check for when the filename listed on a line does not match the filename on the previous line. In particular, if one conflict has stages 1 and 2, and the next conflict has only stage 3, then looking at stages only might cause you to accidentally lump unrelated conflicts together.

#35 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 22 lines · Elijah Newren
Quoted 15 lines · Johannes Sixt

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Quoted 5 lines

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

Quoted 4 lines

Ah, so...I didn't understand his misunderstanding? Using stages as a cue to the parser where the new conflict begins is broken; you should instead check for when the filename listed on a line does not match the filename on the previous line.

But that would break down in case of rename/rename conflicts, right?

Quoted 3 lines

In particular, if one conflict has stages 1 and 2, and the next conflict has only stage 3, then looking at stages only might cause you to accidentally lump unrelated conflicts together.

Precisely. That's why I would love to have a way to deviate from the output of ls-files -u's format, and have a reliable way to indicate stages that belong to the same merge conflict.

Thanks,
Dscho

#36 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 34 lines · Johannes Schindelin

Hi Elijah,

Quoted 22 lines · Elijah Newren
Quoted 15 lines · Johannes Sixt

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Quoted 5 lines

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

Quoted 4 lines

Ah, so...I didn't understand his misunderstanding? Using stages as a cue to the parser where the new conflict begins is broken; you should instead check for when the filename listed on a line does not match the filename on the previous line.

But that would break down in case of rename/rename conflicts, right?

Quoted 7 lines
Quoted 3 lines

In particular, if one conflict has stages 1 and 2, and the next conflict has only stage 3, then looking at stages only might cause you to accidentally lump unrelated conflicts together.

Precisely. That's why I would love to have a way to deviate from the output of ls-files -u's format, and have a reliable way to indicate stages that belong to the same merge conflict.

Ah, attempting to somehow identify and present logical separate conflicts? That could be awesome, but I'm not sure it's technically possible. It certainly isn't with today's merge-ort.

Let me ask some questions first...

If I understand you correctly then in the event of a rename/rename, i.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages all listed together. Right? And in some way that you can identify them as related?

If we do so, how do we mark the beginning and the end of what you call "the same merge conflict"? If you say it's always 3 stages (with the possibility of all-zero modes/oids), then what about the rename/rename case above modified so that the side that did foo->baz also added a different 'bar'? That'd be 4 non-zero modes/oids, all of them relevant. Or what if the side that did foo->bar also renamed something else to 'baz', giving us even more non-zero stages for these three paths? Perhaps you consider these different conflicts and want them listed separately -- if so, where does one conflict begin and another start and which stages are parts of which conflict?

If you are attempting to somehow present the stuff that "belongs to the same merge conflict" are you also trying to identify what kind of merge conflict it is? If so, do you want each type of merge conflict listed? For example, let's switch from the example above of logically disjoint paths coming together to result in more than 3 stages, and instead pick an example with a single logical path with less than three stages. And now let's say that path has multiple conflicts associated with it; let's use an example with 3: rename/delete + modify/delete + directory/file (one side renames foo->bar while modifying the contents, the other side deletes foo and adds the directory 'bar/'). In this case, there is a target file 'bar' that has two non-zero modes/oids in the ls-files-u output. If all three types of conflicts need to be listed, does each need to be listed with the two non-zero modes/oids (and perhaps one zero mode/oid), resulting in six listings for 'bar'? Or would the duplication be confusing enough that we instead decide to list some merge conflicts with no stages associated with them?

Thinking about both sets of questions in the last two paragraphs from a higher level -- should we focus on and group the higher order stages by the individual conflicts that happen, or should we group them by the paths that they happen to (which is what ls-files -u happens to do), or should we not bother grouping them and instead duplicate the higher order stages for each logical conflict it is part of?

As an alternative to duplicating higher order stages, do we sometimes decide to "lump" separate conflicts together and treat them as one conflict? If so, what are the rules on how we decide to lump conflicts and when not to? Is there a bright line boundary? And can it be done without sucking in arbitrarily more stages for a single conflict?

Some testcases that might be useful while considering the above
questions: take a look at the "rad", "rrdd", and "mod6" tests of t6422. How many "same merge conflicts" are there for each of those, and what's the boundary between them? And can you give the answer in the form of rules that generically handle all cases, rather than just answering these three specific cases?

I've thought about this problem long and hard before (in part because of some conversations I had with Edward Thompson about libgit2 and merging at Git Merge 2020). It wasn't at all clear to me that libgit2 had considered anything beyond simple rename cases. The only rules I ever figured out that made sense to me was "group the stages by target filename rather than by logical conflict" (so we get ls -files -u populated) and print a meant-for-human message for each logical conflict (found in the <Informational Messages> section for merge-tree), and make NO attempt to connect stages by conflict type.

I'm sure that's not what you wanted to hear, and maybe doesn't even play nicely with your design. But short of ignoring the edge and corner cases, I don't see how to solve that problem. If you do just want to ignore edge and corner cases, then just ignore the rename/rename case you brought up in the first place and just use ls-files -u-type output as-is within your design. If you don't want to ignore edge cases and want something that works with a specific design that somehow groups conflicted file stages by conflict type, then we're going to have to dig into all these questions above and do some big replumbing within merge-ort.

#37 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 46 lines · Elijah Newren
Quoted 32 lines · Johannes Schindelin
Quoted 22 lines · Elijah Newren
Quoted 15 lines · Johannes Sixt

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Quoted 5 lines

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

Quoted 4 lines

Ah, so...I didn't understand his misunderstanding? Using stages as a cue to the parser where the new conflict begins is broken; you should instead check for when the filename listed on a line does not match the filename on the previous line.

But that would break down in case of rename/rename conflicts, right?

Quoted 7 lines
Quoted 3 lines

In particular, if one conflict has stages 1 and 2, and the next conflict has only stage 3, then looking at stages only might cause you to accidentally lump unrelated conflicts together.

Precisely. That's why I would love to have a way to deviate from the output of ls-files -u's format, and have a reliable way to indicate stages that belong to the same merge conflict.

Ah, attempting to somehow identify and present logical separate conflicts? That could be awesome, but I'm not sure it's technically possible. It certainly isn't with today's merge-ort.

Quoted 6 lines

Let me ask some questions first...

If I understand you correctly then in the event of a rename/rename, i.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages all listed together. Right? And in some way that you can identify them as related?

Yes, that was kind of my idea ;-)

Quoted 10 lines

If we do so, how do we mark the beginning and the end of what you call "the same merge conflict"? If you say it's always 3 stages (with the possibility of all-zero modes/oids), then what about the rename/rename case above modified so that the side that did foo->baz also added a different 'bar'? That'd be 4 non-zero modes/oids, all of them relevant. Or what if the side that did foo->bar also renamed something else to 'baz', giving us even more non-zero stages for these three paths? Perhaps you consider these different conflicts and want them listed separately -- if so, where does one conflict begin and another start and which stages are parts of which conflict?

Thank you for calling me out on an only half-finished thought.

To be quite honest, I previously did not have much time to think of the non-trivial nature of representing merge conflicts in a web UI when performing merges with rename detection, in particular when performing _recursive_ merges (where, as you point out so correctly, an arbitrary number of rename conflicts can happen _for the same file_).

Of course, this gets even more complicated when we're also thinking about type changes (file -> symlink, file -> directory, and vice versa).

Quoted 41 lines

If you are attempting to somehow present the stuff that "belongs to the same merge conflict" are you also trying to identify what kind of merge conflict it is? If so, do you want each type of merge conflict listed? For example, let's switch from the example above of logically disjoint paths coming together to result in more than 3 stages, and instead pick an example with a single logical path with less than three stages. And now let's say that path has multiple conflicts associated with it; let's use an example with 3: rename/delete + modify/delete + directory/file (one side renames foo->bar while modifying the contents, the other side deletes foo and adds the directory 'bar/'). In this case, there is a target file 'bar' that has two non-zero modes/oids in the ls-files-u output. If all three types of conflicts need to be listed, does each need to be listed with the two non-zero modes/oids (and perhaps one zero mode/oid), resulting in six listings for 'bar'? Or would the duplication be confusing enough that we instead decide to list some merge conflicts with no stages associated with them?

Thinking about both sets of questions in the last two paragraphs from a higher level -- should we focus on and group the higher order stages by the individual conflicts that happen, or should we group them by the paths that they happen to (which is what ls-files -u happens to do), or should we not bother grouping them and instead duplicate the higher order stages for each logical conflict it is part of?

As an alternative to duplicating higher order stages, do we sometimes decide to "lump" separate conflicts together and treat them as one conflict? If so, what are the rules on how we decide to lump conflicts and when not to? Is there a bright line boundary? And can it be done without sucking in arbitrarily more stages for a single conflict?

Some testcases that might be useful while considering the above
questions: take a look at the "rad", "rrdd", and "mod6" tests of t6422. How many "same merge conflicts" are there for each of those, and what's the boundary between them? And can you give the answer in the form of rules that generically handle all cases, rather than just answering these three specific cases?

Quoted 2 lines

I've thought about this problem long and hard before (in part because of some conversations I had with Edward Thompson about libgit2 and

Not a big deal for _me_, but I seem to remember that Ed cared a lot about having no p in their surname ;-)

Quoted 8 lines

merging at Git Merge 2020). It wasn't at all clear to me that libgit2 had considered anything beyond simple rename cases. The only rules I ever figured out that made sense to me was "group the stages by target filename rather than by logical conflict" (so we get ls -files -u populated) and print a meant-for-human message for each logical conflict (found in the <Informational Messages> section for merge-tree), and make NO attempt to connect stages by conflict type.

Quoted 10 lines

I'm sure that's not what you wanted to hear, and maybe doesn't even play nicely with your design. But short of ignoring the edge and corner cases, I don't see how to solve that problem. If you do just want to ignore edge and corner cases, then just ignore the rename/rename case you brought up in the first place and just use ls-files -u-type output as-is within your design. If you don't want to ignore edge cases and want something that works with a specific design that somehow groups conflicted file stages by conflict type, then we're going to have to dig into all these questions above and do some big replumbing within merge-ort.

There is sometimes a big difference between what I want to hear and what I need to hear. Thank you for providing so many details that I needed to hear.

So let's take a step back and look at my goal here, as in: the over-arching goal: to use merge-ort on the server side.

From what you said above, it becomes very clear to me that there is very little chance to resolve such conflicts on the server side.

For example, if a topic branch renames a file differently than the main branch, there is a really good chance that the user tasked with merging the topic branch will have to do a whole lot more than just click a few buttons to perform that task. There might very well be the need to edit files that do not contain merge conflict markers (I like to call those cases "non-semantic merge conflicts"), and almost certainly local testing will be necessary.

So I guess the best we can do in those complicated cases is to give a comprehensive overview of the problems in the web UI, with the note that this merge conflict has to be resolved on the local side.

Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

		path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

Ciao,
Dscho

#38 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 236 lines · Johannes Schindelin

Hi Elijah,

Quoted 46 lines · Elijah Newren
Quoted 32 lines · Johannes Schindelin
Quoted 22 lines · Elijah Newren
Quoted 15 lines · Johannes Sixt

Just a heckling from the peanut gallery...

Am 29.01.22 um 07:08 schrieb Elijah Newren:

Quoted 10 lines
Quoted 4 lines · Johannes Schindelin

Meaning: Even if stage 3 is missing from the first conflict and stage 1 is missing from the second conflict, in the output we would see stages 1, 2, 2, 3, i.e. a duplicate stage 2, signifying that we're talking about two different conflicts.

I don't understand why you're fixating on the stage here. Why would you want to group all the stage 2s together, count them up, and then determine there are N conflicting files because there are N stage 2's?

Quoted 5 lines

Looks like you are misunderstanding Dscho's point: When you have two conflicts, the first with stages 1 and 2, the second with stages 2 and 3, then the 2s occur lumped together when the 4 lines are printed in a row, and that is the cue to the parser where the new conflict begins. Dscho did not mean that all N 2s of should be listed together.

Quoted 4 lines

Ah, so...I didn't understand his misunderstanding? Using stages as a cue to the parser where the new conflict begins is broken; you should instead check for when the filename listed on a line does not match the filename on the previous line.

But that would break down in case of rename/rename conflicts, right?

Quoted 7 lines
Quoted 3 lines

In particular, if one conflict has stages 1 and 2, and the next conflict has only stage 3, then looking at stages only might cause you to accidentally lump unrelated conflicts together.

Precisely. That's why I would love to have a way to deviate from the output of ls-files -u's format, and have a reliable way to indicate stages that belong to the same merge conflict.

Ah, attempting to somehow identify and present logical separate conflicts? That could be awesome, but I'm not sure it's technically possible. It certainly isn't with today's merge-ort.

Quoted 6 lines

Let me ask some questions first...

If I understand you correctly then in the event of a rename/rename, i.e. foo->bar & foo->baz, then you want foo's, bar's, & baz's stages all listed together. Right? And in some way that you can identify them as related?

Yes, that was kind of my idea ;-)

Quoted 10 lines

If we do so, how do we mark the beginning and the end of what you call "the same merge conflict"? If you say it's always 3 stages (with the possibility of all-zero modes/oids), then what about the rename/rename case above modified so that the side that did foo->baz also added a different 'bar'? That'd be 4 non-zero modes/oids, all of them relevant. Or what if the side that did foo->bar also renamed something else to 'baz', giving us even more non-zero stages for these three paths? Perhaps you consider these different conflicts and want them listed separately -- if so, where does one conflict begin and another start and which stages are parts of which conflict?

Thank you for calling me out on an only half-finished thought.

To be quite honest, I previously did not have much time to think of the non-trivial nature of representing merge conflicts in a web UI when performing merges with rename detection, in particular when performing _recursive_ merges (where, as you point out so correctly, an arbitrary number of rename conflicts can happen _for the same file_).

Of course, this gets even more complicated when we're also thinking about type changes (file -> symlink, file -> directory, and vice versa).

Quoted 41 lines

If you are attempting to somehow present the stuff that "belongs to the same merge conflict" are you also trying to identify what kind of merge conflict it is? If so, do you want each type of merge conflict listed? For example, let's switch from the example above of logically disjoint paths coming together to result in more than 3 stages, and instead pick an example with a single logical path with less than three stages. And now let's say that path has multiple conflicts associated with it; let's use an example with 3: rename/delete + modify/delete + directory/file (one side renames foo->bar while modifying the contents, the other side deletes foo and adds the directory 'bar/'). In this case, there is a target file 'bar' that has two non-zero modes/oids in the ls-files-u output. If all three types of conflicts need to be listed, does each need to be listed with the two non-zero modes/oids (and perhaps one zero mode/oid), resulting in six listings for 'bar'? Or would the duplication be confusing enough that we instead decide to list some merge conflicts with no stages associated with them?

Thinking about both sets of questions in the last two paragraphs from a higher level -- should we focus on and group the higher order stages by the individual conflicts that happen, or should we group them by the paths that they happen to (which is what ls-files -u happens to do), or should we not bother grouping them and instead duplicate the higher order stages for each logical conflict it is part of?

As an alternative to duplicating higher order stages, do we sometimes decide to "lump" separate conflicts together and treat them as one conflict? If so, what are the rules on how we decide to lump conflicts and when not to? Is there a bright line boundary? And can it be done without sucking in arbitrarily more stages for a single conflict?

Some testcases that might be useful while considering the above
questions: take a look at the "rad", "rrdd", and "mod6" tests of t6422. How many "same merge conflicts" are there for each of those, and what's the boundary between them? And can you give the answer in the form of rules that generically handle all cases, rather than just answering these three specific cases?

Quoted 2 lines

I've thought about this problem long and hard before (in part because of some conversations I had with Edward Thompson about libgit2 and

Not a big deal for _me_, but I seem to remember that Ed cared a lot about having no p in their surname ;-)

Quoted 8 lines

merging at Git Merge 2020). It wasn't at all clear to me that libgit2 had considered anything beyond simple rename cases. The only rules I ever figured out that made sense to me was "group the stages by target filename rather than by logical conflict" (so we get ls -files -u populated) and print a meant-for-human message for each logical conflict (found in the <Informational Messages> section for merge-tree), and make NO attempt to connect stages by conflict type.

Quoted 10 lines

I'm sure that's not what you wanted to hear, and maybe doesn't even play nicely with your design. But short of ignoring the edge and corner cases, I don't see how to solve that problem. If you do just want to ignore edge and corner cases, then just ignore the rename/rename case you brought up in the first place and just use ls-files -u-type output as-is within your design. If you don't want to ignore edge cases and want something that works with a specific design that somehow groups conflicted file stages by conflict type, then we're going to have to dig into all these questions above and do some big replumbing within merge-ort.

There is sometimes a big difference between what I want to hear and what I need to hear. Thank you for providing so many details that I needed to hear.

So let's take a step back and look at my goal here, as in: the over-arching goal: to use merge-ort on the server side.

From what you said above, it becomes very clear to me that there is very little chance to resolve such conflicts on the server side.

For example, if a topic branch renames a file differently than the main branch, there is a really good chance that the user tasked with merging the topic branch will have to do a whole lot more than just click a few buttons to perform that task. There might very well be the need to edit files that do not contain merge conflict markers (I like to call those cases "non-semantic merge conflicts"), and almost certainly local testing will be necessary.

So I guess the best we can do in those complicated cases is to give a comprehensive overview of the problems in the web UI, with the note that this merge conflict has to be resolved on the local side.

Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

		path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

Ciao,
Dscho

#39 lore raw

machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)

In reply to Johannes Schindelin

On Mon, Feb 21 2022, Johannes Schindelin wrote:

[I sent out an empty reply to this earlier by mistake, sorry about that]

Quoted 60 lines

[...]
Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

		path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

Quoted 5 lines

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

That sounds like a rather nasty hack, this is too, but demonstrates that we can pretty easily extract this in a machine-readable format with just a few lines now):

merge-ort.c +12 -1
index 8a5f201d190..a906881f9b3 100644
@@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,
 		     int omittable_hint, /* skippable under --remerge-diff */
 		     const char *fmt, ...)
 {
-	va_list ap;
+	va_list ap, cp;
 	struct strbuf *sb, *dest;
 	struct strbuf tmp = STRBUF_INIT;
 
@@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,
 
 	dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
 
+	va_copy(cp, ap);
 	va_start(ap, fmt);
+
 	if (opt->priv->call_depth) {
 		strbuf_addchars(dest, ' ', 2);
 		strbuf_addstr(dest, "From inner merge:");
@@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,
 	strbuf_vaddf(dest, fmt, ap);
 	va_end(ap);
 
+	va_start(cp, fmt);
+	trace2_region_enter_printf("merge", "conflict/path", opt->repo, "%s", path);
+	trace2_region_leave("merge", "conflict/path", opt->repo);
+	trace2_region_enter_printf("merge", "conflict/fmt", opt->repo, "%s", fmt);
+	trace2_region_leave("merge", "conflict/fmt", opt->repo);
+	trace2_region_enter_printf_va("merge", "conflict/msg", opt->repo, fmt, cp);
+	trace2_region_leave("merge", "conflict/msg", opt->repo);
+	va_end(cp);
+
 	if (opt->record_conflict_msgs_as_headers) {
 		int i_sb = 0, i_tmp = 0;
 

You can run that with one of the tests added in this series to get the output as JSON, e.g.:

GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '"msg"'
"msg": "whatever~side1"
"msg": "CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead."
"msg": "CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead."
"msg": "whatever~side1"
"msg": "CONFLICT (modify/delete): %s deleted in %s and modified in %s. Version %s of %s left in tree."
"msg": "CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree." "msg": "numbers"
"msg": "Auto-merging %s"
"msg": "Auto-merging numbers"
"msg": "greeting"
"msg": "Auto-merging %s"
"msg": "Auto-merging greeting"
"msg": "greeting"
"msg": "CONFLICT (%s): Merge conflict in %s"
"msg": "CONFLICT (content): Merge conflict in greeting"

A "proper" fix for this doesn't sound too hard, we'd just instrument the path_msg() function to pass along some "message category", see e.g. unpack_plumbing_errors in unpack-trees.c for one example of such a thing, or the "enum fsck_msg_id".

Then we'd just allow you to emit any of the sprintf() format itself, or the expanded version, the path, or an id like "CONFLICT:file/directory" or "auto-merging" etc.

I think that would be particularly useful in conjuction with the --format changes I was proposing for this (and hacked up a WIP patch for). You could just have a similar --format-messages or whatever.

Then you could pick \0\0 as a delimiter for your "main" --format, and "\0" as the delimiter for your --format-messages, and thus be able to parse N-nested \0-delimited content.

#40 lore raw

Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)

Quoted 134 lines · Ævar Arnfjörð Bjarmason

On Mon, Feb 21 2022, Johannes Schindelin wrote:

[I sent out an empty reply to this earlier by mistake, sorry about that]

Quoted 60 lines

[...]
Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

              path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

Quoted 5 lines

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

That sounds like a rather nasty hack, this is too, but demonstrates that we can pretty easily extract this in a machine-readable format with just a few lines now):

merge-ort.c +12 -1
index 8a5f201d190..a906881f9b3 100644
@@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,
                     int omittable_hint, /* skippable under --remerge-diff */
                     const char *fmt, ...)
 {
-       va_list ap;
+       va_list ap, cp;
        struct strbuf *sb, *dest;
        struct strbuf tmp = STRBUF_INIT;
 
@@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,
 
        dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
 
+       va_copy(cp, ap);
        va_start(ap, fmt);
+
        if (opt->priv->call_depth) {
                strbuf_addchars(dest, ' ', 2);
                strbuf_addstr(dest, "From inner merge:");
@@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,
        strbuf_vaddf(dest, fmt, ap);
        va_end(ap);
 
+       va_start(cp, fmt);
+       trace2_region_enter_printf("merge", "conflict/path", opt->repo, "%s", path);
+       trace2_region_leave("merge", "conflict/path", opt->repo);
+       trace2_region_enter_printf("merge", "conflict/fmt", opt->repo, "%s", fmt);
+       trace2_region_leave("merge", "conflict/fmt", opt->repo);
+       trace2_region_enter_printf_va("merge", "conflict/msg", opt->repo, fmt, cp);
+       trace2_region_leave("merge", "conflict/msg", opt->repo);
+       va_end(cp);
+
        if (opt->record_conflict_msgs_as_headers) {
                int i_sb = 0, i_tmp = 0;
 

You can run that with one of the tests added in this series to get the output as JSON, e.g.:

GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '"msg"'
"msg": "whatever~side1"
"msg": "CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead."
"msg": "CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead."
"msg": "whatever~side1"
"msg": "CONFLICT (modify/delete): %s deleted in %s and modified in %s. Version %s of %s left in tree."
"msg": "CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree." "msg": "numbers"
"msg": "Auto-merging %s"
"msg": "Auto-merging numbers"
"msg": "greeting"
"msg": "Auto-merging %s"
"msg": "Auto-merging greeting"
"msg": "greeting"
"msg": "CONFLICT (%s): Merge conflict in %s"
"msg": "CONFLICT (content): Merge conflict in greeting"

Quoted 8 lines

A "proper" fix for this doesn't sound too hard, we'd just instrument the path_msg() function to pass along some "message category", see e.g. unpack_plumbing_errors in unpack-trees.c for one example of such a thing, or the "enum fsck_msg_id".

Then we'd just allow you to emit any of the sprintf() format itself, or the expanded version, the path, or an id like "CONFLICT:file/directory" or "auto-merging" etc.

I don't see how this helps solve the problem Dscho was bringing up at all. Your reference to "the path" means you've missed his whole complaint -- that with more complex conflicts (renames, directory/file conflicts resolved via moving the file out of the way, mode conflicts resolved by moving both files out of the way, etc) there are multiple paths involved and he's trying to determine what those paths are. He's particularly focusing on rename/rename cases where a single path was renamed differently by the two sides of history (which results in a conflict message only being associated with the path from the merge base in order to avoid repeating the same message 2-3 times, but that one message has three distinct paths embedded in the string).

Also, the additional paths is not part of the API to path_msg; it's merely embedded in a string. (And, in case it bears repeating: as mentioned elsewhere, we cannot assume there will only be one path_msg() call per path, and we at least currently can't assume that each path_msg() call is for a separate logical conflict; there might be two for a single "conflict".)

I agree that parsing these meant-for-human-consumption (and not promised to be stable) messages is not a good way to go, but pretending the current API has enough info to answer his questions isn't right either.

Quoted 7 lines

I think that would be particularly useful in conjuction with the --format changes I was proposing for this (and hacked up a WIP patch for). You could just have a similar --format-messages or whatever.

Then you could pick \0\0 as a delimiter for your "main" --format, and "\0" as the delimiter for your --format-messages, and thus be able to parse N-nested \0-delimited content.

To be honest, the --format stuff is sounding a little bit like a solution in search of a problem.

#41 lore raw

Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)

Quoted 164 lines · Elijah Newren
Quoted 134 lines · Ævar Arnfjörð Bjarmason

On Mon, Feb 21 2022, Johannes Schindelin wrote:

[I sent out an empty reply to this earlier by mistake, sorry about that]

Quoted 60 lines

[...]
Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

              path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

Quoted 5 lines

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

That sounds like a rather nasty hack, this is too, but demonstrates that we can pretty easily extract this in a machine-readable format with just a few lines now):

merge-ort.c +12 -1
index 8a5f201d190..a906881f9b3 100644
@@ -633,7 +633,7 @@ static void path_msg(struct merge_options *opt,
                     int omittable_hint, /* skippable under --remerge-diff */
                     const char *fmt, ...)
 {
-       va_list ap;
+       va_list ap, cp;
        struct strbuf *sb, *dest;
        struct strbuf tmp = STRBUF_INIT;
 
@@ -650,7 +650,9 @@ static void path_msg(struct merge_options *opt,
 
        dest = (opt->record_conflict_msgs_as_headers ? &tmp : sb);
 
+       va_copy(cp, ap);
        va_start(ap, fmt);
+
        if (opt->priv->call_depth) {
                strbuf_addchars(dest, ' ', 2);
                strbuf_addstr(dest, "From inner merge:");
@@ -659,6 +661,15 @@ static void path_msg(struct merge_options *opt,
        strbuf_vaddf(dest, fmt, ap);
        va_end(ap);
 
+       va_start(cp, fmt);
+       trace2_region_enter_printf("merge", "conflict/path", opt->repo, "%s", path);
+       trace2_region_leave("merge", "conflict/path", opt->repo);
+       trace2_region_enter_printf("merge", "conflict/fmt", opt->repo, "%s", fmt);
+       trace2_region_leave("merge", "conflict/fmt", opt->repo);
+       trace2_region_enter_printf_va("merge", "conflict/msg", opt->repo, fmt, cp);
+       trace2_region_leave("merge", "conflict/msg", opt->repo);
+       va_end(cp);
+
        if (opt->record_conflict_msgs_as_headers) {
                int i_sb = 0, i_tmp = 0;
 

You can run that with one of the tests added in this series to get the output as JSON, e.g.:

GIT_TRACE2_EVENT=/dev/stderr GIT_TRACE2_EVENT_NESTING=10 ~/g/git/git merge-tree --write-tree --no-messages --name-only --messages side1 side2 2>&1|jq -r .| grep '"msg"'
"msg": "whatever~side1"
"msg": "CONFLICT (file/directory): directory in the way of %s from %s; moving it to %s instead."
"msg": "CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead."
"msg": "whatever~side1"
"msg": "CONFLICT (modify/delete): %s deleted in %s and modified in %s. Version %s of %s left in tree."
"msg": "CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree." "msg": "numbers"
"msg": "Auto-merging %s"
"msg": "Auto-merging numbers"
"msg": "greeting"
"msg": "Auto-merging %s"
"msg": "Auto-merging greeting"
"msg": "greeting"
"msg": "CONFLICT (%s): Merge conflict in %s"
"msg": "CONFLICT (content): Merge conflict in greeting"

Quoted 8 lines

A "proper" fix for this doesn't sound too hard, we'd just instrument the path_msg() function to pass along some "message category", see e.g. unpack_plumbing_errors in unpack-trees.c for one example of such a thing, or the "enum fsck_msg_id".

Then we'd just allow you to emit any of the sprintf() format itself, or the expanded version, the path, or an id like "CONFLICT:file/directory" or "auto-merging" etc.

I don't see how this helps solve the problem Dscho was bringing up at all. Your reference to "the path" means you've missed his whole complaint -- that with more complex conflicts (renames, directory/file conflicts resolved via moving the file out of the way, mode conflicts resolved by moving both files out of the way, etc) there are multiple paths involved and he's trying to determine what those paths are. He's particularly focusing on rename/rename cases where a single path was renamed differently by the two sides of history (which results in a conflict message only being associated with the path from the merge base in order to avoid repeating the same message 2-3 times, but that one message has three distinct paths embedded in the string).

Also, the additional paths is not part of the API to path_msg; it's merely embedded in a string. (And, in case it bears repeating: as mentioned elsewhere, we cannot assume there will only be one path_msg() call per path, and we at least currently can't assume that each path_msg() call is for a separate logical conflict; there might be two for a single "conflict".)

Quoted 4 lines

I agree that parsing these meant-for-human-consumption (and not promised to be stable) messages is not a good way to go, but pretending the current API has enough info to answer his questions isn't right either.

The intent here wasn't to present a complete solution, but to reply to the part of Johannes's E-Mail that e.g. mention "and I would be loathe to switch _all_ callers to do the quoting themselves.".

I.e. it's a POC for passing this data further up the stack. The issue you mention with the renaming case could/should be handled by having whatever handles the vargs accept those N arguments, the POC doesn't handle it.

But in any case, needing to convert "28 calls to [path_msg()]" doesn't seem like it's required.

But obviously we wouldn't want to use trace2 as a plumbing layer for message passing, but could format the same data in a similar way, especially in the context of a discussion about filenames with odd characters in them (some of which JSON is inherently incapable of encoding).

Quoted 10 lines
Quoted 7 lines

I think that would be particularly useful in conjuction with the --format changes I was proposing for this (and hacked up a WIP patch for). You could just have a similar --format-messages or whatever.

Then you could pick \0\0 as a delimiter for your "main" --format, and "\0" as the delimiter for your --format-messages, and thus be able to parse N-nested \0-delimited content.

To be honest, the --format stuff is sounding a little bit like a solution in search of a problem.

Opinions on this obviously differ, and I'm not going to pick this as my particular hill to die on :)

But I do think it's the other way around, in that hardcoded output formats are a problem requiring solutions.

Which e.g. is seen (to be fair, in rather small inconveniences) in this series where you need to grep out the first OID line etc. in the tests, instead of the command just being able to be asked for the exact thing the user wants.

Now, perfect shouldn't be the enemy of the good, and I think this series is definitely in good enough shape as it is. But as
e.g. "builtin/ls-tree.c" in "seen" currently shows using the strbuf_expand() callback mechanism to emit output will result in less code/special cases.

Now, in this case the problem is mostly orthagonal, i.e. that the merge-ort.c API understandably isn't returning structured data in this case, as the goal so far has been replacing the in-tree merge-recursive.c use-case.

But if & when that happens I think e.g. returning those as a "struct string_list" with some custom struct in the "util" member might be a good idea, followed by a strbuf_expand() knowing how to format fields from that struct.

The whole thing could then become e.g. (whitespace added for convenience):

	--format="\
		%(objectname)
		\0\0
		%(conflict:%(
			%(conflictmode)
                        \0
                        %(conflictobject)
                        \0
                        %(conflictstage)
                        \0
                        %(conflictfilename)
		)
	"

And it seems to me like the whole of --show-messages etc. could be generalized as some (the "if" syntax would need to be extracted and generalized from ref-filter.c:

    %(if:notequal=0)%(mergeresult)%(then)...%(end)

So again, I'm not saying it's needed now, just that I think the pattern of preparing structured data and throwing it at a formatting engine is worth considering sooner than later.

It we didn't have that for "git-for-each-ref" I think it would have N number of "--only-this-but-not-that"-type options, which this new command already requires.

And with a formatting engine things like "I want my output quoted for consumption by programming language X" become much harder, but which "git-for-each-ref" already supports using that formatting mechanism.

#42 lore raw

Re: machine-parsable git-merge-tree messages (was: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function)

Quoted 3 lines · Ævar Arnfjörð Bjarmason

On Tue, Feb 22 2022, Elijah Newren wrote:

[...]

Quoted 28 lines
Quoted 19 lines

I don't see how this helps solve the problem Dscho was bringing up at all. Your reference to "the path" means you've missed his whole complaint -- that with more complex conflicts (renames, directory/file conflicts resolved via moving the file out of the way, mode conflicts resolved by moving both files out of the way, etc) there are multiple paths involved and he's trying to determine what those paths are. He's particularly focusing on rename/rename cases where a single path was renamed differently by the two sides of history (which results in a conflict message only being associated with the path from the merge base in order to avoid repeating the same message 2-3 times, but that one message has three distinct paths embedded in the string).

Also, the additional paths is not part of the API to path_msg; it's merely embedded in a string. (And, in case it bears repeating: as mentioned elsewhere, we cannot assume there will only be one path_msg() call per path, and we at least currently can't assume that each path_msg() call is for a separate logical conflict; there might be two for a single "conflict".)

Quoted 4 lines

I agree that parsing these meant-for-human-consumption (and not promised to be stable) messages is not a good way to go, but pretending the current API has enough info to answer his questions isn't right either.

The intent here wasn't to present a complete solution, but to reply to the part of Johannes's E-Mail that e.g. mention "and I would be loathe to switch _all_ callers to do the quoting themselves.".

Quoted 7 lines

I.e. it's a POC for passing this data further up the stack. The issue you mention with the renaming case could/should be handled by having whatever handles the vargs accept those N arguments, the POC doesn't handle it.

But in any case, needing to convert "28 calls to [path_msg()]" doesn't seem like it's required.

The problem isn't that it's an incomplete solution, it's that AFAICT, the user's stated problem is not aided at all by this POC. Passing existing data further up the stack cannot solve the problem if the data being passed is insufficient for the problem at hand.

Perhaps you have some clever solution to get the extra information, though? Could you elaborate on how path_msg() could handle its varargs differently to deduce which of those correspond to paths? The only way I can see how to do that, short of modifying all 28 callers of path_msg() to pass those paths as additional information, is hacks like parsing the (non-stable, not-meant-for-machine-parseability) format string.

(Getting the paths would get us most of the way to a solution, though it's still incomplete. But it's the relevant bit under discussion here.)

Quoted 14 lines

But obviously we wouldn't want to use trace2 as a plumbing layer for message passing, but could format the same data in a similar way, especially in the context of a discussion about filenames with odd characters in them (some of which JSON is inherently incapable of encoding).

Quoted 8 lines
Quoted 7 lines

I think that would be particularly useful in conjuction with the --format changes I was proposing for this (and hacked up a WIP patch for). You could just have a similar --format-messages or whatever.

Then you could pick \0\0 as a delimiter for your "main" --format, and "\0" as the delimiter for your --format-messages, and thus be able to parse N-nested \0-delimited content.

Quoted 8 lines
Quoted 2 lines

To be honest, the --format stuff is sounding a little bit like a solution in search of a problem.

Opinions on this obviously differ, and I'm not going to pick this as my particular hill to die on :)

But I do think it's the other way around, in that hardcoded output formats are a problem requiring solutions.

I might be more convinced if folks tried to address how to output things _after_ we had determined *what* things should be output. If we don't have sufficient information to solve what users want, discussing how we format the information we do have cannot help solve the actual problems. It might be useful as an add-on later, but discussing it first is putting the cart before the horse.

#43 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Johannes Schindelin

Hi Elijah,

Quoted 7 lines · Johannes Schindelin

[...] since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

[...]

Do you think we can switch to sq_quote_buf_pretty() for these messages?

Or maybe much better: use NUL to separate those messages if -z is passed to merge-tree? That would address the issue in one elegant diff.

What do you think?

Ciao,
Dscho

#44 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 9 lines · Johannes Schindelin

Hi Elijah,

Quoted 4 lines · Johannes Schindelin

[...] since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Quoted 8 lines
Quoted 3 lines

[...]

Do you think we can switch to sq_quote_buf_pretty() for these messages?

Or maybe much better: use NUL to separate those messages if -z is passed to merge-tree? That would address the issue in one elegant diff.

What do you think?

Separating the combination of messages for a single target path from the combination of messages for a different target path by a NUL character may make sense. Would we also want the messages for a path to be prepended by the pathname and a NUL character, in this case, to make it easier to determine which path the group of messages are for?

I'm not sure if that does exactly what you are asking, though.

The thing that is stored (in opt->priv->output) is a strbuf per path, not an array of strbufs per path. So, if we have a rename/delete and a rename/add and a mode conflict for the same "path" (A->B on one side, other side deletes A and adds a symlink B, resulting in three messages for path "B" that are all appended into a single strbuf), then we'll have a single "message" which has three newlines. We can add a NUL character at the end of that, but not between the messages without restructuring things a bit.

There's also at least one example, with submodules, where there are two path_msg() calls for the same individual conflict in order to split conflict info from resolution advice, and those really shouldn't be thought of as messages for different conflicts. (I'm starting to wonder if the resolution advice should just be tossed; I kept it because merge-recursive had it, but it might not make sense with merge-ort being used by server side merges. But even if we toss that one, I'm not sure I want to commit to one path_msg() call per "logical conflict".)

But...maybe this would be good enough for some kind of use you have? Because if you only want to care about "simple" cases, you could potentially define those as ones with only one newline in them.

#45 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 18 lines · Elijah Newren
Quoted 7 lines · Johannes Schindelin
Quoted 4 lines · Johannes Schindelin

[...] since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Quoted 8 lines
Quoted 3 lines

[...]

Do you think we can switch to sq_quote_buf_pretty() for these messages?

Or maybe much better: use NUL to separate those messages if -z is passed to merge-tree? That would address the issue in one elegant diff.

What do you think?

Quoted 7 lines

Separating the combination of messages for a single target path from the combination of messages for a different target path by a NUL character may make sense. Would we also want the messages for a path to be prepended by the pathname and a NUL character, in this case, to make it easier to determine which path the group of messages are for?

I'm not sure if that does exactly what you are asking, though.

The most important thing I am asking for is a way for a program calling merge-tree to figure out whether it knows how to handle all the types of conflicts encountered in this merge.

So that a web UI could present e.g. simple content conflicts, and even rename/rename conflicts, but would know that it cannot resolve the conflicts e.g. when a submodule is affected.

So... I am fairly certain that we're not as close to addressing this as I had hoped for.

Quoted 19 lines

The thing that is stored (in opt->priv->output) is a strbuf per path, not an array of strbufs per path. So, if we have a rename/delete and a rename/add and a mode conflict for the same "path" (A->B on one side, other side deletes A and adds a symlink B, resulting in three messages for path "B" that are all appended into a single strbuf), then we'll have a single "message" which has three newlines. We can add a NUL character at the end of that, but not between the messages without restructuring things a bit.

There's also at least one example, with submodules, where there are two path_msg() calls for the same individual conflict in order to split conflict info from resolution advice, and those really shouldn't be thought of as messages for different conflicts. (I'm starting to wonder if the resolution advice should just be tossed; I kept it because merge-recursive had it, but it might not make sense with merge-ort being used by server side merges. But even if we toss that one, I'm not sure I want to commit to one path_msg() call per "logical conflict".)

Quoted 3 lines

But...maybe this would be good enough for some kind of use you have? Because if you only want to care about "simple" cases, you could potentially define those as ones with only one newline in them.

We cannot rely on newline character parsing because that is a valid filename character on Unix:

	$ echo a >'with
	> a newline'
	$ ls -la with*
	-rw-r--r-- 1 me me 2 Feb 25 17:10 'with'$'\n''a newline'

I guess we have to work harder on this and add more than just an strbuf so that we can output <path>NUL<conflict-type>NUL pairs (where we promise to keep the <conflict-type> strings constant) or something similar.

Ciao,
Dscho

#46 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Johannes Schindelin
Quoted 9 lines · Johannes Schindelin

Hi Elijah,

Quoted 4 lines · Elijah Newren
Quoted 2 lines · Johannes Schindelin

On Sat, 29 Jan 2022, Elijah Newren wrote:

[...]

Quoted 5 lines
Quoted 2 lines

I've thought about this problem long and hard before (in part because of some conversations I had with Edward Thompson about libgit2 and

Not a big deal for _me_, but I seem to remember that Ed cared a lot about having no p in their surname ;-)

Eek! My apologies to Ed; I'll try to remember and do better.

Quoted 23 lines
Quoted 8 lines

merging at Git Merge 2020). It wasn't at all clear to me that libgit2 had considered anything beyond simple rename cases. The only rules I ever figured out that made sense to me was "group the stages by target filename rather than by logical conflict" (so we get ls -files -u populated) and print a meant-for-human message for each logical conflict (found in the <Informational Messages> section for merge-tree), and make NO attempt to connect stages by conflict type.

Quoted 10 lines

I'm sure that's not what you wanted to hear, and maybe doesn't even play nicely with your design. But short of ignoring the edge and corner cases, I don't see how to solve that problem. If you do just want to ignore edge and corner cases, then just ignore the rename/rename case you brought up in the first place and just use ls-files -u-type output as-is within your design. If you don't want to ignore edge cases and want something that works with a specific design that somehow groups conflicted file stages by conflict type, then we're going to have to dig into all these questions above and do some big replumbing within merge-ort.

There is sometimes a big difference between what I want to hear and what I need to hear. Thank you for providing so many details that I needed to hear.

Quoted 5 lines

So let's take a step back and look at my goal here, as in: the over-arching goal: to use merge-ort on the server side.

From what you said above, it becomes very clear to me that there is very little chance to resolve such conflicts on the server side.

No, it's still resolvable on the server side. It's just that attempts to break up the information into individual logical conflicts is problematic; if you provide _all_ the informational conflict messages to the user, and iterate through the paths with conflicts, then things are fine. It's only when you attempt to find "the relevant subset" that things become hard. Of course, providing it all at once might be a UI that you hate (perhaps because it's too much like how the command line behaves...)

Quoted 7 lines

For example, if a topic branch renames a file differently than the main branch, there is a really good chance that the user tasked with merging the topic branch will have to do a whole lot more than just click a few buttons to perform that task. There might very well be the need to edit files that do not contain merge conflict markers (I like to call those cases "non-semantic merge conflicts"), and almost certainly local testing will be necessary.

Sidenote: Do you lump in binary merge conflicts with "non-semantic merge conflicts"? You would by your definition, but I'm not sure it matches.

I tend to call things either content-based conflicts or path-based conflicts, where content-based usually means textual-based but also includes merges of binaries.

Quoted 9 lines

So I guess the best we can do in those complicated cases is to give a comprehensive overview of the problems in the web UI, with the note that this merge conflict has to be resolved on the local side.

Which brings me to the next concern: since merge-tree is a low-level tool meant to be called by programs rather than humans, we need to make sure that those messages remain machine-parseable, even if they contain file names.

Quoted 7 lines

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording:
"""
In all cases, the
<Informational messages> section has the necessary info, though it is not designed to be machine parseable.
"""
though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Quoted 46 lines

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

                path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

Quoted 5 lines

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

What do you think?

Switching to single quoting paths as a matter of style might make sense, but only if we go through and change every caller to do so so that we can make sure it applies to all paths. And only paths and not OIDs.

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

#47 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 7 lines · Elijah Newren

Sidenote: Do you lump in binary merge conflicts with "non-semantic merge conflicts"? You would by your definition, but I'm not sure it matches.

I tend to call things either content-based conflicts or path-based conflicts, where content-based usually means textual-based but also includes merges of binaries.

I like "content-based conflicts".

And no, I had not even thought about binary merge conflicts yet...

Quoted 11 lines
Quoted 8 lines · Johannes Schindelin

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Quoted 13 lines

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording:
"""
In all cases, the
<Informational messages> section has the necessary info, though it is not designed to be machine parseable.
"""
though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Okay.

I thought that we could keep the CONFLICT (<type>) constant enough to serve as such a machine-parseable thing. And then presenting <path>NUL<message>NUL could have served my use case well...

Quoted 50 lines
Quoted 50 lines

Here's an excerpt from t4301:

-- snip --
Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
Auto-merging numbers
CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead. CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1. Version side1 of whatever~side1 left in tree. -- snap --

This is the complete set of messages provided in the test conflict notices and such test case.

I immediately notice that every line contains at least one file name. Looking at https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1899, it does not seem as if the file names are quoted:

                path_msg(opt, path, 1, _("Auto-merging %s"), path);

(where path is used verbatim in a call to merge_3way() before that, i.e. it must not have been quoted)

I would like to register a wish to ensure that file names with special characters (such as most notably line-feed characters) are quoted in these messages, so that a simple server-side parser can handle messages starting with Auto-merging and with CONFLICT (content): Merge conflict in , and "throw the hands up in the air" if any other message prefix is seen.

Do you think we can switch to sq_quote_buf_pretty() for these messages? For the Auto-merging one, it would be trivial, but I fear that we will have to work a bit on the path_msg() function
(https://github.com/git/git/blob/v2.35.1/merge-ort.c#L630-L649) because it accepts a variable list of arguments without any clue whether the arguments refer to paths or not. (And I would be loathe to switch _all_ callers to do the quoting themselves.)

I see 28 calls to that function, and at least a couple that pass not only a path but also an OID (e.g.
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1611-L1613).

We could of course be sloppy and pass even OIDs through sq_quote_buf_pretty() in path_msg(), knowing that there won't be any special characters in them, but it gets more complicated e.g. in
https://github.com/git/git/blob/v2.35.1/merge-ort.c#L1648-L1651, where we pass an strbuf that contains a somewhat free-form commit message.

I guess we could still pass those through sq_quote_buf_pretty(), even if they are not paths, to ensure that there are no special characters in the machine-parseable lines.

Quoted 6 lines
Quoted 1 line

What do you think?

Switching to single quoting paths as a matter of style might make sense, but only if we go through and change every caller to do so so that we can make sure it applies to all paths. And only paths and not OIDs.

Yes, that sounds unappealing.

Quoted 8 lines

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

Makes sense.

So we need something in addition to those messages.

Ciao,
Dscho

#48 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 5 lines · Johannes Schindelin
Quoted 3 lines

I tend to call things either content-based conflicts or path-based conflicts, where content-based usually means textual-based but also includes merges of binaries.

I like "content-based conflicts".

Yup, even before ort existed, we had clear distinction between tree level merges (i.e. which path corresponds to which other path, which is done in unpack_trees() and "read-tree O A B") and content level merges (which is done with ll_merge()).

Quoted 16 lines
Quoted 4 lines

Switching to single quoting paths as a matter of style might make sense, but only if we go through and change every caller to do so so that we can make sure it applies to all paths. And only paths and not OIDs.

Yes, that sounds unappealing.

Quoted 8 lines

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

Quoted 1 line

Makes sense.

As long as it is made clear that the path_msg() is not for machine consumption (perhaps we can sprinkle <RED><RESET> at random places to make it impossible for machines to handle), I think the direction makes sense, too ;-)

#49 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Johannes Schindelin

Hi Dscho,

Quoted 42 lines · Johannes Schindelin

Hi Elijah,

Quoted 7 lines · Elijah Newren

Sidenote: Do you lump in binary merge conflicts with "non-semantic merge conflicts"? You would by your definition, but I'm not sure it matches.

I tend to call things either content-based conflicts or path-based conflicts, where content-based usually means textual-based but also includes merges of binaries.

I like "content-based conflicts".

And no, I had not even thought about binary merge conflicts yet...

Quoted 11 lines
Quoted 8 lines · Johannes Schindelin

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Quoted 13 lines

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording:
"""
In all cases, the
<Informational messages> section has the necessary info, though it is not designed to be machine parseable.
"""
though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Quoted 4 lines

Okay.

I thought that we could keep the CONFLICT (<type>) constant enough to serve as such a machine-parseable thing.

That _probably_ is, but I thought you wanted to parse all N paths embedded in the message after that part as well?

Quoted 2 lines

And then presenting
<path>NUL<message>NUL could have served my use case well...

Would it? Wouldn't you need something more like

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL
?

I mean, if rename/rename is what you want to handle, there are three paths in that message. And you need to know all three paths in order to combine the relevant parts of the <Conflicted File Info> section together.

(Also, while we're at it, I decided to throw a stable short-type description string (e.g. "CONFLICT (rename/rename)") in there, which will _probably_ be the first part of the message string but still allow us to change the message string later if we want.)

Also, we'd want those parsing this information to keep in mind that:
* Any given conflict can affect multiple paths
* Any path can be part of multiple conflicts
* (The above two items imply a potentially many-to-many relationship
between paths and conflicts)
* Paths listed in these logical conflicts may not correspond to a
file in the index (they could be a directory, or file that was in a previous version)
* Some of these "logical conflicts" are not actually conflicts but
just notices (e.g. "auto-merging" or "submodule updated" or "WARNING" or "<submodules are weird>" messages)

and we'd have to do some work to make sure the paths in the given messages lined up with the files actually recorded in the index (e.g. with distinct types we rename both files to avoid the collision, but print the conflict notice for the original path rather than the new paths)

[...]

Quoted 9 lines
Quoted 8 lines

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

Quoted 3 lines

Makes sense.

So we need something in addition to those messages.

Yes. Does the proposal above sound like it'd cover your needs? If so, we'd probably need to go through all the callers to path_msg() and either add an immediate call to another function immediately afterwards that stores this additional information or somehow change the path_msg() call itself to somehow take an additional arbitrary list of arguments representing the paths and short-desc we want to store somewhere.

#50 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 30 lines · Elijah Newren
Quoted 28 lines · Johannes Schindelin
Quoted 24 lines · Elijah Newren
Quoted 8 lines · Johannes Schindelin

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording:
"""
In all cases, the
<Informational messages> section has the necessary info, though it is not designed to be machine parseable.
"""
though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Quoted 7 lines
Quoted 4 lines

Okay.

I thought that we could keep the CONFLICT (<type>) constant enough to serve as such a machine-parseable thing.

That _probably_ is, but I thought you wanted to parse all N paths embedded in the message after that part as well?

Actually, no, sorry for being unclear. My main aim with the machine-parseable messages was to detect whether a given failed merge contains _only_ conflicts of the sort that a particular UI can handle.

In that respect, I would not even need to parse the file names, I'd just require them not to contain line feed characters ;-)

Quoted 7 lines
Quoted 2 lines

And then presenting
<path>NUL<message>NUL could have served my use case well...

Would it? Wouldn't you need something more like

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL
?

Probably, you're right.

Quoted 9 lines

I mean, if rename/rename is what you want to handle, there are three paths in that message. And you need to know all three paths in order to combine the relevant parts of the <Conflicted File Info> section together.

(Also, while we're at it, I decided to throw a stable short-type description string (e.g. "CONFLICT (rename/rename)") in there, which will _probably_ be the first part of the message string but still allow us to change the message string later if we want.)

Yes, I very much like that idea to keep the prefix in a format that we can guarantee to be stable enough for applications (or server backends) to rely on.

Quoted 32 lines

Also, we'd want those parsing this information to keep in mind that:
* Any given conflict can affect multiple paths
* Any path can be part of multiple conflicts
* (The above two items imply a potentially many-to-many relationship
between paths and conflicts)
* Paths listed in these logical conflicts may not correspond to a
file in the index (they could be a directory, or file that was in a previous version)
* Some of these "logical conflicts" are not actually conflicts but
just notices (e.g. "auto-merging" or "submodule updated" or "WARNING" or "<submodules are weird>" messages)

and we'd have to do some work to make sure the paths in the given messages lined up with the files actually recorded in the index (e.g. with distinct types we rename both files to avoid the collision, but print the conflict notice for the original path rather than the new paths)

[...]

Quoted 9 lines
Quoted 8 lines

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

Quoted 3 lines

Makes sense.

So we need something in addition to those messages.

Quoted 7 lines

Yes. Does the proposal above sound like it'd cover your needs? If so, we'd probably need to go through all the callers to path_msg() and either add an immediate call to another function immediately afterwards that stores this additional information or somehow change the path_msg() call itself to somehow take an additional arbitrary list of arguments representing the paths and short-desc we want to store somewhere.

Yes, you're right, the proper thing to do is to go through the callers to path_msg() so that we can provide that stable output you proposed. A couple of thoughts about this:

* These are not informal messages, i.e. I think we would need another flag that would then trigger another double-NUL-separated section to be shown, probably between the conflicted file info and the informational messages.

* Instead of _adding_ the calls, we could look into refactoring the existing path_msg() calls, introducing yet another function that would call path_msg() but also optionally generate that machine-parseable conflict info.

* None of this needs to hold up en/merge-tree. I am sorry that I am the blocker (unintentionally so, I guarantee you that!).

Ciao,
Dscho

#51 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 40 lines · Johannes Schindelin

Hi Elijah,

Quoted 35 lines · Elijah Newren
Quoted 32 lines · Johannes Schindelin
Quoted 11 lines · Elijah Newren
Quoted 8 lines · Johannes Schindelin

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Quoted 13 lines

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording:
"""
In all cases, the
<Informational messages> section has the necessary info, though it is not designed to be machine parseable.
"""
though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Okay.

I thought that we could keep the CONFLICT (<type>) constant enough to serve as such a machine-parseable thing.

Quoted 6 lines
Quoted 2 lines

That _probably_ is, but I thought you wanted to parse all N paths embedded in the message after that part as well?

Actually, no, sorry for being unclear. My main aim with the machine-parseable messages was to detect whether a given failed merge contains _only_ conflicts of the sort that a particular UI can handle.

...and in order to do that, you'd need to parse all the filenames to determine whether files were involved in multiple conflict types, since you earlier suggested you would just bail on such conflicts (at
https://lore.kernel.org/git/[email protected]/).
In particular, I think the "mod6" and "rrdd" and "rad" cases kind of triggered those comments, and rename/rename is involved in two of those.

Further, even if you could assume there'd only be simple conflicts such as the simple rename/rename case, you'd still need the files so that you know _which_ of the modes/oids in the <Conflicted file info> section relate to the message you are looking at. (As mentioned previously, sorting the <Conflicted file info> to attempt to put them together is not feasible and is a no-go.)

Quoted 15 lines
Quoted 7 lines
Quoted 2 lines

And then presenting
<path>NUL<message>NUL could have served my use case well...

Would it? Wouldn't you need something more like

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL
?

Probably, you're right.

Quoted 5 lines

I mean, if rename/rename is what you want to handle, there are three paths in that message. And you need to know all three paths in order to combine the relevant parts of the <Conflicted File Info> section together.

Quoted 8 lines
Quoted 4 lines

(Also, while we're at it, I decided to throw a stable short-type description string (e.g. "CONFLICT (rename/rename)") in there, which will _probably_ be the first part of the message string but still allow us to change the message string later if we want.)

Yes, I very much like that idea to keep the prefix in a format that we can guarantee to be stable enough for applications (or server backends) to rely on.

Um, I explicitly avoided saying the prefix would be stable by introducing a separate short string just so that we could change the prefix later if wanted. The short string is _probably_ the current prefix or something close to it, but that stable string wouldn't necessarily remain the prefix of the message string, since the entire message string would be allowed to change.

Quoted 40 lines
Quoted 32 lines

Also, we'd want those parsing this information to keep in mind that:
* Any given conflict can affect multiple paths
* Any path can be part of multiple conflicts
* (The above two items imply a potentially many-to-many relationship
between paths and conflicts)
* Paths listed in these logical conflicts may not correspond to a
file in the index (they could be a directory, or file that was in a previous version)
* Some of these "logical conflicts" are not actually conflicts but
just notices (e.g. "auto-merging" or "submodule updated" or "WARNING" or "<submodules are weird>" messages)

and we'd have to do some work to make sure the paths in the given messages lined up with the files actually recorded in the index (e.g. with distinct types we rename both files to avoid the collision, but print the conflict notice for the original path rather than the new paths)

[...]

Quoted 9 lines
Quoted 8 lines

But I'm going to reserve the right in merge-ort to modify, add, or delete any of those messages passed to path_msg(), which might wreak havoc on your attempts to parse those strings. I think they're a bad form for communicating information to a script or program, and trying to transform them into such risks making them suboptimal at communicating info to humans. These messages should optimize the latter, and if we want something for the former, it should probably be a new independent bit of info.

Quoted 3 lines

Makes sense.

So we need something in addition to those messages.

Quoted 7 lines

Yes. Does the proposal above sound like it'd cover your needs? If so, we'd probably need to go through all the callers to path_msg() and either add an immediate call to another function immediately afterwards that stores this additional information or somehow change the path_msg() call itself to somehow take an additional arbitrary list of arguments representing the paths and short-desc we want to store somewhere.

Quoted 8 lines

Yes, you're right, the proper thing to do is to go through the callers to path_msg() so that we can provide that stable output you proposed. A couple of thoughts about this:

* These are not informal messages, i.e. I think we would need another flag that would then trigger another double-NUL-separated section to be shown, probably between the conflicted file info and the informational messages.

Why would that be a separate section, though? While we don't want machines parsing the informal messages and trying to determine the components of that message, they definitely should be able to tell which informal messages are associated with which paths (otherwise how can you group the <Conflict message info> as per your needs and how do you know which message to show the user when dealing with that particular conflict?). That requirement suggests the informal messages should be part of the same section. Thus my suggestion to just make it be the <message> part at the end of my earlier suggestion of

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL

Quoted 4 lines

* Instead of _adding_ the calls, we could look into refactoring the existing path_msg() calls, introducing yet another function that would call path_msg() but also optionally generate that machine-parseable conflict info.

So of the two alternatives I suggested above, it sounds like you're in favor of the "or somehow change the path_msg() call itself to somehow take an additional arbitrary list of arguments representing the paths and short-desc we want to store somewhere" option I suggested. I think I prefer that one too.

Quoted 2 lines

* None of this needs to hold up en/merge-tree. I am sorry that I am the blocker (unintentionally so, I guarantee you that!).

Sometimes I find it frustrating that changes don't merge down for months, particularly when the topic was already finished weeks and weeks (if not months) ago and/or when there's a follow-on series depending on it. But in this case I have no follow-on series ready to send, and this topic has been under (very!) active discussion essentially the whole time. And, perhaps even more importantly, I'd like to avoid solidifying the "machine parseable output format" prematurely and making it harder to work around later. I think addressing this issue you bring up requires fundamentally redoing the informational messages section as per my proposal, and it makes me wonder what should even be shown without the -z option (thoughts welcome).

So, this is one series where even if everyone else says to merge it already, I'd like to wait a bit longer on it until I feel confident we have a solution that handles at least the current usecases.

#52 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 42 lines · Elijah Newren
Quoted 40 lines · Johannes Schindelin
Quoted 36 lines · Elijah Newren
Quoted 27 lines · Johannes Schindelin
Quoted 12 lines · Elijah Newren
Quoted 9 lines · Johannes Schindelin

Concretely: while I am not currently aware of any web UI that allows to resolve simple rename/rename conflicts, it is easily conceivable how to implement such a thing. When that happens, we will need to be able to teach the server-side code to discern between the cases that can be handled in the web UI (trivial merge conflicts, trivial rename/rename conflicts) as compared to scenarios where the conflicts are just too complex.

Quoted 11 lines

Um, I'm really worried about attempting to make the conflict notices machine parseable. I don't like that idea at all, and I even tried to rule that out already with my wording: """ In all cases, the <Informational messages> section has the necessary info, though it is not designed to be machine parseable. """ though maybe I should have been even more explicit. The restrictions that those messages be stable is too rigid, I think. I also think they're a poor way to communicate information to a higher level tool. I would much rather us add some kind of additional return data structures from merge ort and use them if we want extra info.

Quoted 4 lines

Okay.

I thought that we could keep the CONFLICT (<type>) constant enough to serve as such a machine-parseable thing.

That _probably_ is, but I thought you wanted to parse all N paths embedded in the message after that part as well?

Quoted 8 lines
Quoted 3 lines

Actually, no, sorry for being unclear. My main aim with the machine-parseable messages was to detect whether a given failed merge contains _only_ conflicts of the sort that a particular UI can handle.

...and in order to do that, you'd need to parse all the filenames to determine whether files were involved in multiple conflict types, since you earlier suggested you would just bail on such conflicts (at
https://lore.kernel.org/git/[email protected]/).

That's a good point. I had somehow still in my mind that the CONFLICT (<type>) prefix would already give me enough data to decide whether to bail out or not, but that prefix would only tell me about a single rename/rename conflict, not about multiple such conflicts involving the _same_ file.

Quoted 9 lines

In particular, I think the "mod6" and "rrdd" and "rad" cases kind of triggered those comments, and rename/rename is involved in two of those.

Further, even if you could assume there'd only be simple conflicts such as the simple rename/rename case, you'd still need the files so that you know _which_ of the modes/oids in the <Conflicted file info> section relate to the message you are looking at. (As mentioned previously, sorting the <Conflicted file info> to attempt to put them together is not feasible and is a no-go.)

True.

Quoted 24 lines
Quoted 15 lines
Quoted 7 lines
Quoted 2 lines

And then presenting <path>NUL<message>NUL could have served my use case well...

Would it? Wouldn't you need something more like

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL ?

Probably, you're right.

Quoted 5 lines

I mean, if rename/rename is what you want to handle, there are three paths in that message. And you need to know all three paths in order to combine the relevant parts of the <Conflicted File Info> section together.

Quoted 8 lines
Quoted 4 lines

(Also, while we're at it, I decided to throw a stable short-type description string (e.g. "CONFLICT (rename/rename)") in there, which will _probably_ be the first part of the message string but still allow us to change the message string later if we want.)

Yes, I very much like that idea to keep the prefix in a format that we can guarantee to be stable enough for applications (or server backends) to rely on.

Quoted 6 lines

Um, I explicitly avoided saying the prefix would be stable by introducing a separate short string just so that we could change the prefix later if wanted. The short string is _probably_ the current prefix or something close to it, but that stable string wouldn't necessarily remain the prefix of the message string, since the entire message string would be allowed to change.

Okay.

Quoted 10 lines
Quoted 8 lines

[T]he proper thing to do is to go through the callers to path_msg() so that we can provide that stable output you proposed. A couple of thoughts about this:

* These are not informal messages, i.e. I think we would need another flag that would then trigger another double-NUL-separated section to be shown, probably between the conflicted file info and the informational messages.

Why would that be a separate section, though?

I thought you wanted to keep the informal messages human-readable, so naturally I thought that the machine-readable part should be in a separate section.

Quoted 10 lines

While we don't want machines parsing the informal messages and trying to determine the components of that message, they definitely should be able to tell which informal messages are associated with which paths (otherwise how can you group the <Conflict message info> as per your needs and how do you know which message to show the user when dealing with that particular conflict?). That requirement suggests the informal messages should be part of the same section. Thus my suggestion to just make it be the <message> part at the end of my earlier suggestion of

<number-of-paths>NUL<path1>NUL<path2>NUL<pathN>NUL<stable-short-type-description>NUL<message>NUL

Oh, okay.

Should this format be the one with -z, and the current format remain the one being shown without -z?

Quoted 10 lines
Quoted 4 lines

* Instead of _adding_ the calls, we could look into refactoring the existing path_msg() calls, introducing yet another function that would call path_msg() but also optionally generate that machine-parseable conflict info.

So of the two alternatives I suggested above, it sounds like you're in favor of the "or somehow change the path_msg() call itself to somehow take an additional arbitrary list of arguments representing the paths and short-desc we want to store somewhere" option I suggested. I think I prefer that one too.

Yes!

Quoted 15 lines
Quoted 2 lines

* None of this needs to hold up en/merge-tree. I am sorry that I am the blocker (unintentionally so, I guarantee you that!).

Sometimes I find it frustrating that changes don't merge down for months, particularly when the topic was already finished weeks and weeks (if not months) ago and/or when there's a follow-on series depending on it. But in this case I have no follow-on series ready to send, and this topic has been under (very!) active discussion essentially the whole time. And, perhaps even more importantly, I'd like to avoid solidifying the "machine parseable output format" prematurely and making it harder to work around later. I think addressing this issue you bring up requires fundamentally redoing the informational messages section as per my proposal, and it makes me wonder what should even be shown without the -z option (thoughts welcome).

My suggestion is above: without -z, the messages should be identical to what is shown now.

Quoted 3 lines

So, this is one series where even if everyone else says to merge it already, I'd like to wait a bit longer on it until I feel confident we have a solution that handles at least the current usecases.

Fair enough, you're in charge of this series, and I really like what you came up with.

My thinking was driven more by the users' side, as I am relatively eager to integrate this into production, but am loathe to do that with an early iteration of en/merge-tree that might be substantially revamped, still.

It can bog me down quite well if I have to adjust local (or not-so-local) patch series to refactorings in core Git. For example, the other day, I was blocked from more interesting work until I could resolve all kinds of conflicts where core Git renaming hash_object_file_literally() to write_object_file_literally() completely intersected with actually-interesting unsigned long to size_t changes in Git for Windows. And let me tell you, once you have to deal with such conflicts a couple of times, being prevented from doing work that is actually fun, it is relatively easy to become frustrated and think about those refactorings as pointless and as useless churn, no matter how well-intended they may be.

So my prodding is kind of selfish, as an en/merge-tree cherry-picked from next would provide somewhat of a promise that I do _not_ have to spend _so_ much time adjusting my code in case a new iteration with substantial changes shows up ;-)

But you're right, let's tread wisely and only call this patch series done only when we are reasonably certain that the machine-parseable output is stable.

Speaking of which: Would you like me to give that path_msg() refactoring a try, or are you already busy with it? (I cannot make any promises, but I also do not want to lean on you, to leave you alone with that work, I would feel really bad about that.)

Ciao,
Dscho

#53 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 6 lines · Johannes Schindelin
Quoted 3 lines · Elijah Newren

So, this is one series where even if everyone else says to merge it already, I'd like to wait a bit longer on it until I feel confident we have a solution that handles at least the current usecases.

Quoted 6 lines

Fair enough, you're in charge of this series, and I really like what you came up with.

My thinking was driven more by the users' side, as I am relatively eager to integrate this into production, but am loathe to do that with an early iteration of en/merge-tree that might be substantially revamped, still.

I've been bogged down with things elsewhere, but should now have time to help on this end.

Elijah, _is_ there anything I can help with?

Thanks,
Dscho

#54 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Johannes,

Quoted 14 lines · Johannes Schindelin

Hi Elijah,

Quoted 9 lines · Johannes Schindelin
Quoted 3 lines · Elijah Newren

So, this is one series where even if everyone else says to merge it already, I'd like to wait a bit longer on it until I feel confident we have a solution that handles at least the current usecases.

Fair enough, you're in charge of this series, and I really like what you came up with.

Quoted 8 lines
Quoted 3 lines

My thinking was driven more by the users' side, as I am relatively eager to integrate this into production, but am loathe to do that with an early iteration of en/merge-tree that might be substantially revamped, still.

I've been bogged down with things elsewhere, but should now have time to help on this end.

Elijah, _is_ there anything I can help with?

Yeah, I've been bogged down with other things too; the little Git time I've had has been spent responding to review requests or other things folks manually were asking for my input on.

I think I got a fair amount of this implemented about a month or so ago. I just pushed up what I have to the wip-for-in-core-merge-tree branch of newren/git. Some notes:

* A big "WIP" commit that needs to be broken up
* The previous "output" member of merge_result, containing a strmap
of conflict and informational messages (basically a mapping of filename -> strbuf) now needs to be replaced by a strmap "conflicts", which is now a mapping of primary_filename -> logical_conflicts, and logical_conflicts is an array of logical_conflict, and logical_conflict has a type, array of paths, and message.
* Since "output" is no longer part of merge_result, the new
remerge-diff functionality is going to need to be modified since it used that field, and instead iterate on "conflicts" to get the same information
* I have some FIXME comments in a couple places where I need to
figure out how I want to pass the variable number of arguments (in a function already accepting a variable number of arguments for other reasons, making the function in a way have to variable length lists of arguments)
* The new enums and structs I added to merge-ort.c really have to be
added to merge-ort.h and become part of the API. Feels a little unfortunate since it'll make the API _much_ more involved, but I don't see any other way to solve your usecase.

If you want to take a stab at the above, or even see if my changes make sense (sorry for it all being squashed into one big commit and not having good commit messages, but, you know...you did ask), that'd be great.

#55 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 23 lines · Elijah Newren
Quoted 12 lines · Johannes Schindelin
Quoted 9 lines · Johannes Schindelin
Quoted 3 lines · Elijah Newren

So, this is one series where even if everyone else says to merge it already, I'd like to wait a bit longer on it until I feel confident we have a solution that handles at least the current usecases.

Fair enough, you're in charge of this series, and I really like what you came up with.

Quoted 8 lines
Quoted 3 lines

My thinking was driven more by the users' side, as I am relatively eager to integrate this into production, but am loathe to do that with an early iteration of en/merge-tree that might be substantially revamped, still.

I've been bogged down with things elsewhere, but should now have time to help on this end.

Elijah, _is_ there anything I can help with?

Quoted 7 lines

Yeah, I've been bogged down with other things too; the little Git time I've had has been spent responding to review requests or other things folks manually were asking for my input on.

I think I got a fair amount of this implemented about a month or so ago. I just pushed up what I have to the wip-for-in-core-merge-tree branch of newren/git.

Thank you so much!

I worked a few hours on this and pushed up my changes under the same branch name to dscho/git.

Quoted 3 lines

Some notes:

* A big "WIP" commit that needs to be broken up

I did not yet start on that.

Quoted 10 lines

* The previous "output" member of merge_result, containing a strmap
of conflict and informational messages (basically a mapping of filename -> strbuf) now needs to be replaced by a strmap "conflicts", which is now a mapping of primary_filename -> logical_conflicts, and logical_conflicts is an array of logical_conflict, and logical_conflict has a type, array of paths, and message.
* Since "output" is no longer part of merge_result, the new
remerge-diff functionality is going to need to be modified since it used that field, and instead iterate on "conflicts" to get the same information

I punted on that for now, recreating an output-style strmap and storing it as path_messages attribute.

Quoted 5 lines

* I have some FIXME comments in a couple places where I need to
figure out how I want to pass the variable number of arguments (in a function already accepting a variable number of arguments for other reasons, making the function in a way have to variable length lists of arguments)

In my WIP fixups, I refactored this into a version that takes varargs and another version that takes a string_list.

However, after getting all this to compile and t4301 to pass, I think we actually only need a version that takes up to two "other" paths, and a version that takes a string_list with those "other" paths, where the former constructs a temporary string_list and then calls the latter.

Quoted 4 lines

* The new enums and structs I added to merge-ort.c really have to be
added to merge-ort.h and become part of the API. Feels a little unfortunate since it'll make the API _much_ more involved, but I don't see any other way to solve your usecase.

I agree, but I did not do that yet ;-)

Another thing I noticed is that we can probably ensure consistency between the conflict_and_info_types enum and the type_short_descriptions array by using the same C99 construct we're already using in the advice_setting array in advice.c:

	static const char *type_short_descriptions[NB_CONFLICT_TYPES] = {
		/*** "Simple" conflicts and informational messages ***/
		[INFO_AUTO_MERGING] = "Auto-merging",
		[CONFLICT_CONTENTS] = "CONFLICT (contents)",
	[...]
Quoted 4 lines

If you want to take a stab at the above, or even see if my changes make sense (sorry for it all being squashed into one big commit and not having good commit messages, but, you know...you did ask), that'd be great.

Yes, I did ask, and I did receive ;-)

Thank you so much! It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Thanks!
Dscho

#56 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 10 lines · Johannes Schindelin
Quoted 3 lines · Elijah Newren

I think I got a fair amount of this implemented about a month or so ago. I just pushed up what I have to the wip-for-in-core-merge-tree branch of newren/git.

Thank you so much!

I worked a few hours on this and pushed up my changes under the same branch name to dscho/git.

And I worked on it some more, and pushed up the result (which passes the CI build except for some problem caused by changes outside of Git's source code) ;-)

Quoted 5 lines
Quoted 3 lines

Some notes:

* A big "WIP" commit that needs to be broken up

I did not yet start on that.

Still no start on that yet.

Quoted 11 lines
Quoted 10 lines

* The previous "output" member of merge_result, containing a strmap
of conflict and informational messages (basically a mapping of filename -> strbuf) now needs to be replaced by a strmap "conflicts", which is now a mapping of primary_filename -> logical_conflicts, and logical_conflicts is an array of logical_conflict, and logical_conflict has a type, array of paths, and message.
* Since "output" is no longer part of merge_result, the new
remerge-diff functionality is going to need to be modified since it used that field, and instead iterate on "conflicts" to get the same information

Quoted 2 lines

I punted on that for now, recreating an output-style strmap and storing it as path_messages attribute.

I still punted on that because I wanted to see whether I could address the test suite failures first (narrator's voice: he could).

Quoted 6 lines
Quoted 5 lines

* I have some FIXME comments in a couple places where I need to
figure out how I want to pass the variable number of arguments (in a function already accepting a variable number of arguments for other reasons, making the function in a way have to variable length lists of arguments)

Quoted 7 lines

In my WIP fixups, I refactored this into a version that takes varargs and another version that takes a string_list.

However, after getting all this to compile and t4301 to pass, I think we actually only need a version that takes up to two "other" paths, and a version that takes a string_list with those "other" paths, where the former constructs a temporary string_list and then calls the latter.

I ended up refactoring the refactor. The path_msg() function now takes the "other paths" in two different forms: it accepts two (optional) const char* parameters and an (also optional) struct string_list *. Either of them, if non-NULL, will be added to the struct strvec field.

This looks _slightly_ ugly to me, but from an implementation point is the cleanest solution.

Quoted 12 lines
Quoted 4 lines

* The new enums and structs I added to merge-ort.c really have to be
added to merge-ort.h and become part of the API. Feels a little unfortunate since it'll make the API _much_ more involved, but I don't see any other way to solve your usecase.

I agree, but I did not do that yet ;-)

Another thing I noticed is that we can probably ensure consistency between the conflict_and_info_types enum and the type_short_descriptions array by using the same C99 construct we're already using in the advice_setting array in advice.c:

Quoted 5 lines
	static const char *type_short_descriptions[NB_CONFLICT_TYPES] = {
		/*** "Simple" conflicts and informational messages ***/
		[INFO_AUTO_MERGING] = "Auto-merging",
		[CONFLICT_CONTENTS] = "CONFLICT (contents)",
	[...]

Still haven't done that, either, as it is merely syntactic sugar, really, and not really an interesting change. I think I'll leave that to a time after I managed to modify the remerge-diff machinery to accept the new-style conflicts map (instead of recreating the old-style output map, as I am doing for now).

Quoted 4 lines

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

Ciao,
Dscho

#57 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

[sorry for sending this flurry of mails, I just wasn't sure how consecutively I could work on the merge-tree patches, and therefore sent mails whenever I had to take a break in case I wouldn't be able to get back to this project for a couple of days.]

Quoted 11 lines · Johannes Schindelin
Quoted 9 lines · Johannes Schindelin
Quoted 7 lines · Elijah Newren

* The previous "output" member of merge_result, containing a strmap of conflict and informational messages (basically a mapping of filename -> strbuf) now needs to be replaced by a strmap "conflicts", which is now a mapping of primary_filename -> logical_conflicts, and logical_conflicts is an array of logical_conflict, and logical_conflict has a type, array of paths, and message.

I massaged this a bit further: since we no longer actually need a strbuf there (we no longer append to the strbuf but instead to the list of logical conflicts), I replaced struct logical_conflicts with a string_list where each item contains the conflict message and its util points to a struct logical_merge_info that contains the type and the involved paths.

This lets me...

Quoted 10 lines
Quoted 7 lines
Quoted 4 lines

* Since "output" is no longer part of merge_result, the new
remerge-diff functionality is going to need to be modified since it used that field, and instead iterate on "conflicts" to get the same information

I punted on that for now, recreating an output-style strmap and storing it as path_messages attribute.

I still punted on that because I wanted to see whether I could address the test suite failures first (narrator's voice: he could).

... address this issue without resorting to declaring the logical_merge struct in merge-ort.h (which would get a bit messy, not only because we would have to #include <strvec.h> in that header because that struct contains a struct strvec paths and therefore must know the storage size of strvec, but also because merge-ort.h is not included in diff.c, and it would be a bit iffy to do that).

Since the per-path conflicts are now stored as a string_list, and since we only need the messages in remerge, we can continue to simply pass the pointer to strmap conflicts to the remerge machinery (in the common case, if pathspecs are involved, we continue to lightly copy-filter that strmap).

Quoted 6 lines
Quoted 6 lines
Quoted 4 lines

* The new enums and structs I added to merge-ort.c really have to be
added to merge-ort.h and become part of the API. Feels a little unfortunate since it'll make the API _much_ more involved, but I don't see any other way to solve your usecase.

I agree, but I did not do that yet ;-)

As mentioned above, I think the better way is to _not_ declare that enum and structs in merge-ort.h and instead store the per-path conflicts as a string_list whose strings contain the conflict message and whose util contains the type and the list of involved paths.

This simplifies the API rather dramatically, since the current user (remerge) is only interested in the conflict message, but not in the type nor in the full list of involved paths.

Should we ever need to access the type or the paths outside of merge-ort.c, it is easy enough to add a simple function to access that information via the string_list's util.

Quoted 11 lines
Quoted 10 lines

Another thing I noticed is that we can probably ensure consistency between the conflict_and_info_types enum and the type_short_descriptions array by using the same C99 construct we're already using in the advice_setting array in advice.c:

	static const char *type_short_descriptions[NB_CONFLICT_TYPES] = {
		/*** "Simple" conflicts and informational messages ***/
		[INFO_AUTO_MERGING] = "Auto-merging",
		[CONFLICT_CONTENTS] = "CONFLICT (contents)",
	[...]
Quoted 5 lines

Still haven't done that, either, as it is merely syntactic sugar, really, and not really an interesting change. I think I'll leave that to a time after I managed to modify the remerge-diff machinery to accept the new-style conflicts map (instead of recreating the old-style output map, as I am doing for now).

Since I addressed that output issue, I now also C99-ified the type_short_descriptions array.

Quoted 8 lines
Quoted 4 lines

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

I hope to find some time to work on this more tomorrow; If not, I will get back to the project on Wednesday and push it further.

Ciao,
Dscho

#58 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

[after this update, I will shut up until you chime in, promise!]

Quoted 9 lines · Johannes Schindelin
Quoted 7 lines · Johannes Schindelin
Quoted 4 lines · Johannes Schindelin

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Quoted 6 lines
Quoted 3 lines

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

I hope to find some time to work on this more tomorrow; If not, I will get back to the project on Wednesday and push it further.

I did get a chance, and polished the patch series a bit. I also rebased it onto the current tip of Git's main branch, mainly to address some merge conflicts preemptively. The result is pushed up to
https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the range-diff relative to newren/wip-for-in-core-merge-tree:

-- snip --

 1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases
 2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb
 3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability
 4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`
 5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings
 6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion
 7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable
 8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers
 9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths
10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers
11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable
12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges
13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()
14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function
15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function
16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges
17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function
18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output
19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function
25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort

@@ merge-ort.c: static int merge_submodule(struct merge_options *opt,
strbuf_release(&sb);
break;
default:
+
+ ## t/t6437-submodule-merge.sh ##
+@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '
+ (cd merge-search &&
+ git checkout -b test-nonforward b &&
+ (cd sub &&
+- git rev-parse sub-d > ../expect) &&
++ git rev-parse --short sub-d > ../expect) &&
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge c >actual
20: 7b1ee417f3d ! 9: c92b81e7366 merge-tree: provide a list of which files have conflicts

    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
     +		string_list_clear(&conflicted_files, 1);
     +	}
      	if (o->show_messages) {
    - 		printf("\n");
    +-		printf("\n");
    ++		putchar(line_termination);
      		merge_display_update_messages(&opt, &result);
    + 	}
    + 	merge_finalize(&opt, &result);
     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)

      	/* Do the relevant type of merge */
21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info
 -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf
 -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list
 -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information
 -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output
22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated
    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
      	if (!result.clean) {
      		struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
      		const char *last = NULL;
    -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
    - 		string_list_clear(&conflicted_files, 1);
    - 	}
    - 	if (o->show_messages) {
    --		printf("\n");
    -+		putchar(line_termination);
    - 		merge_display_update_messages(&opt, &result);
    - 	}
    - 	merge_finalize(&opt, &result);
     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
      			    N_("do a trivial merge only"), MODE_TRIVIAL),
      		OPT_BOOL(0, "messages", &o.show_messages,
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +	test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&
     +	anonymize_hash out >actual &&
    ++	printf "\\n" >>actual &&
     +
     +	# Expected results:
     +	#   "greeting" should merge with conflicts
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +	EOF
     +
    -+	cat <<-EOF >>expect &&
    -+	Auto-merging greeting
    -+	CONFLICT (content): Merge conflict in greeting
    -+	CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    -+	CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    -+	Auto-merging Αυτά μου φαίνονται κινέζικα
    -+	CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++	q_to_nul <<-EOF >>expect &&
    ++	1QgreetingQAuto-mergingQAuto-merging greeting
    ++	Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
    ++	Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    ++	Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    ++	Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα
    ++	Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++	Q
     +	EOF
     +
     +	test_cmp expect actual
23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag
24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes

@@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:
+<<IM,Informational messages>> section has the necessary info, though it +is not designed to be machine parseable.
+
++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and ++the logical conflicts in the <<IM,Informational messages>> have a ++one-to-one mapping, nor that there is a one-to-many mapping, nor a ++many-to-one mapping. Many-to-many mappings exist, meaning that each ++path can have many logical conflict types in a single merge, and each ++logical conflict type can affect many paths.
++
+Do NOT assume all filenames listed in the <<IM,Informational messages>> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging <file>".
26: 78e1243eca1 < -: ----------- WIP
-- snap --

I am pretty happy with the current state of the patches, and hope that we can push this patch series over the finish line.

If you can think of anything I can do to help with this, please do let me know, I am _very_ interested in getting this done, and finally am in a position to help.

Ciao,
Dscho

#59 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Dscho,

Quoted 20 lines · Johannes Schindelin

Hi Elijah,

[after this update, I will shut up until you chime in, promise!]

Quoted 13 lines · Johannes Schindelin
Quoted 10 lines · Johannes Schindelin
Quoted 4 lines · Johannes Schindelin

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

Quoted 8 lines
Quoted 2 lines

I hope to find some time to work on this more tomorrow; If not, I will get back to the project on Wednesday and push it further.

I did get a chance, and polished the patch series a bit. I also rebased it onto the current tip of Git's main branch, mainly to address some merge conflicts preemptively. The result is pushed up to
https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the range-diff relative to newren/wip-for-in-core-merge-tree:

This is so awesome; thanks for working on this. Sorry I haven't had time to review yet, but I'm hoping to be able to near the end of this week. I'm excited to see how it looks. :-)

Quoted 121 lines

-- snip --

 1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases
 2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb
 3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability
 4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`
 5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings
 6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion
 7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable
 8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers
 9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths
10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers
11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable
12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges
13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()
14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function
15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function
16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges
17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function
18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output
19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function
25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort

@@ merge-ort.c: static int merge_submodule(struct merge_options *opt,
strbuf_release(&sb);
break;
default:
+
+ ## t/t6437-submodule-merge.sh ##
+@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '
+ (cd merge-search &&
+ git checkout -b test-nonforward b &&
+ (cd sub &&
+- git rev-parse sub-d > ../expect) &&
++ git rev-parse --short sub-d > ../expect) &&
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge c >actual
20: 7b1ee417f3d ! 9: c92b81e7366 merge-tree: provide a list of which files have conflicts
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
+ string_list_clear(&conflicted_files, 1);
+ }
if (o->show_messages) {
- printf("\n");
+- printf("\n");
++ putchar(line_termination);
merge_display_update_messages(&opt, &result);
+ }
+ merge_finalize(&opt, &result);
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)

/* Do the relevant type of merge */

21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info
 -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf
 -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list
 -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information
 -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output
22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated
    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
        if (!result.clean) {
                struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
                const char *last = NULL;
    -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
    -           string_list_clear(&conflicted_files, 1);
    -   }
    -   if (o->show_messages) {
    --          printf("\n");
    -+          putchar(line_termination);
    -           merge_display_update_messages(&opt, &result);
    -   }
    -   merge_finalize(&opt, &result);
     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                            N_("do a trivial merge only"), MODE_TRIVIAL),
                OPT_BOOL(0, "messages", &o.show_messages,
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&
     +  anonymize_hash out >actual &&
    ++  printf "\\n" >>actual &&
     +
     +  # Expected results:
     +  #   "greeting" should merge with conflicts
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  EOF
     +
    -+  cat <<-EOF >>expect &&
    -+  Auto-merging greeting
    -+  CONFLICT (content): Merge conflict in greeting
    -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    -+  Auto-merging Αυτά μου φαίνονται κινέζικα
    -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  q_to_nul <<-EOF >>expect &&
    ++  1QgreetingQAuto-mergingQAuto-merging greeting
    ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
    ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα
    ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  Q
     +  EOF
     +
     +  test_cmp expect actual
23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag
24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes

@@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:
+<<IM,Informational messages>> section has the necessary info, though it +is not designed to be machine parseable.
+
++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and ++the logical conflicts in the <<IM,Informational messages>> have a ++one-to-one mapping, nor that there is a one-to-many mapping, nor a ++many-to-one mapping. Many-to-many mappings exist, meaning that each ++path can have many logical conflict types in a single merge, and each ++logical conflict type can affect many paths.
++
+Do NOT assume all filenames listed in the <<IM,Informational messages>> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging <file>".
26: 78e1243eca1 < -: ----------- WIP
-- snap --

Quoted 6 lines

I am pretty happy with the current state of the patches, and hope that we can push this patch series over the finish line.

If you can think of anything I can do to help with this, please do let me know, I am _very_ interested in getting this done, and finally am in a position to help.

Very much appreciated. Looks like you're now blocking on my review, so I'll try to make some time by end of week to look over things.

#60 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Quoted 162 lines · Elijah Newren

Hi Dscho,

Quoted 20 lines · Johannes Schindelin

Hi Elijah,

[after this update, I will shut up until you chime in, promise!]

Quoted 13 lines · Johannes Schindelin
Quoted 10 lines · Johannes Schindelin
Quoted 4 lines · Johannes Schindelin

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

Quoted 8 lines
Quoted 2 lines

I hope to find some time to work on this more tomorrow; If not, I will get back to the project on Wednesday and push it further.

I did get a chance, and polished the patch series a bit. I also rebased it onto the current tip of Git's main branch, mainly to address some merge conflicts preemptively. The result is pushed up to
https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the range-diff relative to newren/wip-for-in-core-merge-tree:

This is so awesome; thanks for working on this. Sorry I haven't had time to review yet, but I'm hoping to be able to near the end of this week. I'm excited to see how it looks. :-)

Quoted 124 lines

-- snip --

 1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases
 2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb
 3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability
 4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`
 5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings
 6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion
 7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable
 8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers
 9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths
10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers
11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable
12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges
13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()
14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function
15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function
16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges
17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function
18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output
19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function
25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort

@@ merge-ort.c: static int merge_submodule(struct merge_options *opt,
strbuf_release(&sb);
break;
default:
+
+ ## t/t6437-submodule-merge.sh ##
+@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '
+ (cd merge-search &&
+ git checkout -b test-nonforward b &&
+ (cd sub &&
+- git rev-parse sub-d > ../expect) &&
++ git rev-parse --short sub-d > ../expect) &&
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge c >actual
20: 7b1ee417f3d ! 9: c92b81e7366 merge-tree: provide a list of which files have conflicts
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
+ string_list_clear(&conflicted_files, 1);
+ }
if (o->show_messages) {
- printf("\n");
+- printf("\n");
++ putchar(line_termination);
merge_display_update_messages(&opt, &result);
+ }
+ merge_finalize(&opt, &result);
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)

/* Do the relevant type of merge */

21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info
 -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf
 -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list
 -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information
 -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output
22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated
    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
        if (!result.clean) {
                struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
                const char *last = NULL;
    -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
    -           string_list_clear(&conflicted_files, 1);
    -   }
    -   if (o->show_messages) {
    --          printf("\n");
    -+          putchar(line_termination);
    -           merge_display_update_messages(&opt, &result);
    -   }
    -   merge_finalize(&opt, &result);
     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                            N_("do a trivial merge only"), MODE_TRIVIAL),
                OPT_BOOL(0, "messages", &o.show_messages,
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&
     +  anonymize_hash out >actual &&
    ++  printf "\\n" >>actual &&
     +
     +  # Expected results:
     +  #   "greeting" should merge with conflicts
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  EOF
     +
    -+  cat <<-EOF >>expect &&
    -+  Auto-merging greeting
    -+  CONFLICT (content): Merge conflict in greeting
    -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    -+  Auto-merging Αυτά μου φαίνονται κινέζικα
    -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  q_to_nul <<-EOF >>expect &&
    ++  1QgreetingQAuto-mergingQAuto-merging greeting
    ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
    ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα
    ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  Q
     +  EOF
     +
     +  test_cmp expect actual
23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag
24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes

@@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:
+<<IM,Informational messages>> section has the necessary info, though it +is not designed to be machine parseable.
+
++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and ++the logical conflicts in the <<IM,Informational messages>> have a ++one-to-one mapping, nor that there is a one-to-many mapping, nor a ++many-to-one mapping. Many-to-many mappings exist, meaning that each ++path can have many logical conflict types in a single merge, and each ++logical conflict type can affect many paths.
++
+Do NOT assume all filenames listed in the <<IM,Informational messages>> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging <file>".
26: 78e1243eca1 < -: ----------- WIP
-- snap --

I am pretty happy with the current state of the patches, and hope that we can push this patch series over the finish line.

Quoted 6 lines
Quoted 3 lines

If you can think of anything I can do to help with this, please do let me know, I am _very_ interested in getting this done, and finally am in a position to help.

Very much appreciated. Looks like you're now blocking on my review, so I'll try to make some time by end of week to look over things.

Sorry for the delay. However, I have looked over the changes in detail. Well done! I very much like this version. Thanks for taking the time to break up my WIP patches, fix my bugs, address all the FIXMEs, and then going and adding some nice cleanups and improvements on top! I'll add my Signed-off-by on a couple patches and have gitgitgadget submit this round.

#61 lore raw

Re: [PATCH 08/12] merge-ort: provide a merge_get_conflicted_files() helper function

Hi Elijah,

Quoted 166 lines · Elijah Newren
Quoted 158 lines · Elijah Newren
Quoted 18 lines · Johannes Schindelin

[after this update, I will shut up until you chime in, promise!]

Quoted 13 lines · Johannes Schindelin
Quoted 10 lines · Johannes Schindelin
Quoted 4 lines · Johannes Schindelin

It would be great if you could have a quick look over the commits I added on top of your branch, to see whether things make more or less sense to you. But if you're too busy elsewhere, I am one of the best persons to understand that, too.

Hopefully I will get this into a reviewable shape before you get to looking at it, so that your time is spent more wisely than what I asked for ;-)

Quoted 8 lines
Quoted 2 lines

I hope to find some time to work on this more tomorrow; If not, I will get back to the project on Wednesday and push it further.

I did get a chance, and polished the patch series a bit. I also rebased it onto the current tip of Git's main branch, mainly to address some merge conflicts preemptively. The result is pushed up to
https://github.com/dscho/git/tree/js/in-core-merge-tree. This is the range-diff relative to newren/wip-for-in-core-merge-tree:

This is so awesome; thanks for working on this. Sorry I haven't had time to review yet, but I'm hoping to be able to near the end of this week. I'm excited to see how it looks. :-)

Quoted 124 lines

-- snip --

 1:  cccb3888070 <  -:  ----------- tmp-objdir: new API for creating temporary writable databases
 2:  4e44121c2d7 <  -:  ----------- tmp-objdir: disable ref updates when replacing the primary odb
 3:  0b94724311d <  -:  ----------- show, log: provide a --remerge-diff capability
 4:  f06de6c1b2f <  -:  ----------- log: clean unneeded objects during `log --remerge-diff`
 5:  8d6c3d48f0e <  -:  ----------- ll-merge: make callers responsible for showing warnings
 6:  de8e8f88fa4 <  -:  ----------- merge-ort: capture and print ll-merge warnings in our preferred fashion
 7:  6b535a4d55a <  -:  ----------- merge-ort: mark a few more conflict messages as omittable
 8:  e2441608c63 <  -:  ----------- merge-ort: format messages slightly different for use in headers
 9:  62734beb693 <  -:  ----------- diff: add ability to insert additional headers for paths
10:  17eccf7e0d6 <  -:  ----------- show, log: include conflict/warning messages in --remerge-diff headers
11:  b3e7656cfc6 <  -:  ----------- merge-ort: mark conflict/warning messages from inner merges as omittable
12:  ea5df61cf35 <  -:  ----------- diff-merges: avoid history simplifications when diffing merges
13:  4a7cd5542bb =  1:  8fb51817ed4 merge-tree: rename merge_trees() to trivial_merge_trees()
14:  4780ff6784d =  2:  8e0a79fa1ad merge-tree: move logic for existing merge into new function
15:  60253745f5c =  3:  baf0950bcb6 merge-tree: add option parsing and initial shell for real merge function
16:  f8266d39c1b =  4:  697470e50ae merge-tree: implement real merges
17:  6629af14919 =  5:  069af1ecc30 merge-ort: split out a separate display_update_messages() function
18:  17b57efb714 =  6:  53c92a5d8d9 merge-tree: support including merge messages in output
19:  4c8f42372dd =  7:  67a728d35f0 merge-ort: provide a merge_get_conflicted_files() helper function
25:  8fe5be07cd0 !  8:  6419487e26b merge-ort: remove command-line-centric submodule message from merge-ort

@@ merge-ort.c: static int merge_submodule(struct merge_options *opt,
strbuf_release(&sb);
break;
default:
+
+ ## t/t6437-submodule-merge.sh ##
+@@ t/t6437-submodule-merge.sh: test_expect_success 'merging should conflict for non fast-forward' '
+ (cd merge-search &&
+ git checkout -b test-nonforward b &&
+ (cd sub &&
+- git rev-parse sub-d > ../expect) &&
++ git rev-parse --short sub-d > ../expect) &&
+ if test "$GIT_TEST_MERGE_ALGORITHM" = ort
+ then
+ test_must_fail git merge c >actual
20: 7b1ee417f3d ! 9: c92b81e7366 merge-tree: provide a list of which files have conflicts
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
+ string_list_clear(&conflicted_files, 1);
+ }
if (o->show_messages) {
- printf("\n");
+- printf("\n");
++ putchar(line_termination);
merge_display_update_messages(&opt, &result);
+ }
+ merge_finalize(&opt, &result);
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)

/* Do the relevant type of merge */

21:  f1231a8fbc8 = 10:  d7360f58f16 merge-tree: provide easy access to `ls-files -u` style info
 -:  ----------- > 11:  b53ef9c2116 merge-ort: store messages in a list, not in a single strbuf
 -:  ----------- > 12:  b16d570d248 merge-ort: make `path_messages` a strmap to a string_list
 -:  ----------- > 13:  b575a6b5f8a merge-ort: store more specific conflict information
 -:  ----------- > 14:  4f245cc28ae merge-ort: optionally produce machine-readable output
22:  22297e6ce75 ! 15:  6a369f837be merge-tree: allow `ls-files -u` style info to be NUL terminated
    @@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
        if (!result.clean) {
                struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
                const char *last = NULL;
    -@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
    -           string_list_clear(&conflicted_files, 1);
    -   }
    -   if (o->show_messages) {
    --          printf("\n");
    -+          putchar(line_termination);
    -           merge_display_update_messages(&opt, &result);
    -   }
    -   merge_finalize(&opt, &result);
     @@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                            N_("do a trivial merge only"), MODE_TRIVIAL),
                OPT_BOOL(0, "messages", &o.show_messages,
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  test_expect_code 1 git merge-tree --write-tree -z tweak1 side2 >out &&
     +  anonymize_hash out >actual &&
    ++  printf "\\n" >>actual &&
     +
     +  # Expected results:
     +  #   "greeting" should merge with conflicts
    @@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and
     +
     +  EOF
     +
    -+  cat <<-EOF >>expect &&
    -+  Auto-merging greeting
    -+  CONFLICT (content): Merge conflict in greeting
    -+  CONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    -+  CONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    -+  Auto-merging Αυτά μου φαίνονται κινέζικα
    -+  CONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  q_to_nul <<-EOF >>expect &&
    ++  1QgreetingQAuto-mergingQAuto-merging greeting
    ++  Q1QgreetingQCONFLICT (contents)QCONFLICT (content): Merge conflict in greeting
    ++  Q2Qwhatever~tweak1QwhateverQCONFLICT (file/directory)QCONFLICT (file/directory): directory in the way of whatever from tweak1; moving it to whatever~tweak1 instead.
    ++  Q1Qwhatever~tweak1QCONFLICT (modify/delete)QCONFLICT (modify/delete): whatever~tweak1 deleted in side2 and modified in tweak1.  Version tweak1 of whatever~tweak1 left in tree.
    ++  Q1QΑυτά μου φαίνονται κινέζικαQAuto-mergingQAuto-merging Αυτά μου φαίνονται κινέζικα
    ++  Q1QΑυτά μου φαίνονται κινέζικαQCONFLICT (contents)QCONFLICT (content): Merge conflict in Αυτά μου φαίνονται κινέζικα
    ++  Q
     +  EOF
     +
     +  test_cmp expect actual
23:  db73c6dd823 = 16:  47146dd59dd merge-tree: add a --allow-unrelated-histories flag
24:  d58a7c7a9f6 ! 17:  3ce28f6fd97 git-merge-tree.txt: add a section on potentional usage mistakes

@@ Documentation/git-merge-tree.txt: with linkgit:git-merge[1]:
+<<IM,Informational messages>> section has the necessary info, though it +is not designed to be machine parseable.
+
++Do NOT assume that each paths from <<CFI,Conflicted file info>>, and ++the logical conflicts in the <<IM,Informational messages>> have a ++one-to-one mapping, nor that there is a one-to-many mapping, nor a ++many-to-one mapping. Many-to-many mappings exist, meaning that each ++path can have many logical conflict types in a single merge, and each ++logical conflict type can affect many paths.
++
+Do NOT assume all filenames listed in the <<IM,Informational messages>> +section had conflicts. Messages can be included for files that have no +conflicts, such as "Auto-merging <file>".
26: 78e1243eca1 < -: ----------- WIP
-- snap --

I am pretty happy with the current state of the patches, and hope that we can push this patch series over the finish line.

Quoted 6 lines
Quoted 3 lines

If you can think of anything I can do to help with this, please do let me know, I am _very_ interested in getting this done, and finally am in a position to help.

Very much appreciated. Looks like you're now blocking on my review, so I'll try to make some time by end of week to look over things.

Quoted 6 lines

Sorry for the delay. However, I have looked over the changes in detail. Well done! I very much like this version. Thanks for taking the time to break up my WIP patches, fix my bugs, address all the FIXMEs, and then going and adding some nice cleanups and improvements on top! I'll add my Signed-off-by on a couple patches and have gitgitgadget submit this round.

That's great news, thank you so much!

Ciao,
Dscho

#62 lore raw

PATCH 9/12 merge-tree: provide a list of which files have conflicts

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Callers of git merge-tree --write-tree will often want to know which files had conflicts. While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable. Provide a simpler mechanism of just printing the files (in the same format as git ls-files with quoting, but restricted to unmerged files) in the output before the free-form messages.

---
 Documentation/git-merge-tree.txt |  8 ++++++++
 builtin/merge-tree.c             | 24 ++++++++++++++++++++++--
 t/t4301-merge-tree-real.sh       | 11 +++++++++++
 3 files changed, 41 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +8 -0
index fd7a867de60..041a4ac2785 100644
@@ -58,6 +58,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Conflicted file list>
 	<Informational messages>
 
 These are discussed individually below.
@@ -69,6 +70,13 @@ This is a tree object that represents what would be checked out in the
 working tree at the end of `git merge`.  If there were conflicts, then
 files within this tree may have embedded conflict markers.
 
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).
+
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
builtin/merge-tree.c +22 -2
index 560640ad911..d8eeeb3f306 100644
@@ -11,6 +11,9 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "quote.h"
+
+static int line_termination = '\n';
 
 struct merge_list {
 	struct merge_list *next;
@@ -395,7 +398,8 @@ struct merge_tree_options {
 };
 
 static int real_merge(struct merge_tree_options *o,
-		      const char *branch1, const char *branch2)
+		      const char *branch1, const char *branch2,
+		      const char *prefix)
 {
 	struct commit *parent1, *parent2;
 	struct commit_list *common;
@@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,
 		o->show_messages = !result.clean;
 
 	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+	if (!result.clean) {
+		struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
+		const char *last = NULL;
+		int i;
+
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;
+		}
+		string_list_clear(&conflicted_files, 1);
+	}
 	if (o->show_messages) {
 		printf("\n");
 		merge_display_update_messages(&opt, &result, stdout);
@@ -502,7 +522,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 
 	/* Do the relevant type of merge */
 	if (o.real)
-		return real_merge(&o, argv[0], argv[1]);
+		return real_merge(&o, argv[0], argv[1], prefix);
 	else
 		return trivial_merge(argv[0], argv[1], argv[2]);
 }
t/t4301-merge-tree-real.sh +11 -0
index c34f8e6c1ed..43c9950dedb 100755
@@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '
 	#   "whatever" has *both* a modify/delete and a file/directory conflict
 	cat <<-EOF >expect &&
 	HASH
+	greeting
+	whatever~side1
 
 	Auto-merging greeting
 	CONFLICT (content): Merge conflict in greeting
@@ -105,4 +107,13 @@ test_expect_success 'test conflict notices and such' '
 	test_cmp expect actual
 '
 
+test_expect_success 'Just the conflicted files without the messages' '
+	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	test_write_lines HASH greeting whatever~side1 >expect &&
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#63 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

Quoted 13 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Callers of git merge-tree --write-tree will often want to know which files had conflicts. While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable. Provide a simpler mechanism of just printing the files (in the same format as git ls-files with quoting, but restricted to [...]

builtin/merge-tree.c +0 -0
index 560640ad911..d8eeeb3f306 100644

builtin/merge-tree.c line 16

Quoted 6 lines
builtin/merge-tree.c +3 -0
@@ -11,6 +11,9 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "quote.h"
+
+static int line_termination = '\n';

But unlike ls-files we don't do anything with line_termination as a != '\n', maybe in a later commit?

Quoted 18 lines
builtin/merge-tree.c +4 -1
 struct merge_list {
 	struct merge_list *next;
@@ -395,7 +398,8 @@ struct merge_tree_options {
 };
 
 static int real_merge(struct merge_tree_options *o,
-		      const char *branch1, const char *branch2)
+		      const char *branch1, const char *branch2,
+		      const char *prefix)
 {
 	struct commit *parent1, *parent2;
 	struct commit_list *common;
@@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,
 		o->show_messages = !result.clean;
 
 	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+	if (!result.clean) {
+		struct string_list conflicted_files = STRING_LIST_INIT_NODUP;

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 467

Quoted 10 lines
builtin/merge-tree.c +10 -0
+		const char *last = NULL;
+		int i;
+
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);

But here it's never \0 or whatever.

#64 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

Quoted 41 lines · Ævar Arnfjörð Bjarmason
Quoted 13 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Callers of git merge-tree --write-tree will often want to know which files had conflicts. While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable. Provide a simpler mechanism of just printing the files (in the same format as git ls-files with quoting, but restricted to [...]

builtin/merge-tree.c +0 -0
index 560640ad911..d8eeeb3f306 100644

builtin/merge-tree.c line 16

Quoted 6 lines
builtin/merge-tree.c +3 -0
@@ -11,6 +11,9 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "quote.h"
+
+static int line_termination = '\n';

But unlike ls-files we don't do anything with line_termination as a != '\n', maybe in a later commit?

Quoted 15 lines
builtin/merge-tree.c +2 -1
 struct merge_list {
      struct merge_list *next;
@@ -395,7 +398,8 @@ struct merge_tree_options {
 };
 
 static int real_merge(struct merge_tree_options *o,
-                   const char *branch1, const char *branch2)
+                   const char *branch1, const char *branch2,
+                   const char *prefix)
 {
      struct commit *parent1, *parent2;
      struct commit_list *common;
@@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,
              o->show_messages = !result.clean;
 
Quoted 15 lines

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 467

Quoted 13 lines
builtin/merge-tree.c +12 -0
      printf("%s\n", oid_to_hex(&result.tree->object.oid));
+     if (!result.clean) {
+             struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
+             const char *last = NULL;
+             int i;
+
+             merge_get_conflicted_files(&result, &conflicted_files);
+             for (i = 0; i < conflicted_files.nr; i++) {
+                     const char *name = conflicted_files.items[i].string;
+                     if (last && !strcmp(last, name))
+                             continue;
+                     write_name_quoted_relative(
+                             name, prefix, stdout, line_termination);

But here it's never \0 or whatever.

Correct, I didn't add any option for changing it. But why hardcode it to "\n"? Leaving it this way makes it easier to change later if folks say they want NUL-terminated output. Since the series is RFC and the output already has changed drastically and appears to be the primary discussion and disagreement point, I wanted to provide what seemed like a reasonable suggestion and maintain flexiibility to address feedback (though who knows -- I might need to just completely redo the output again in ways much bigger than adding a -z option).

#65 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Quoted 21 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Callers of git merge-tree --write-tree will often want to know which files had conflicts. While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable. Provide a simpler mechanism of just printing the files (in the same format as git ls-files with quoting, but restricted to unmerged files) in the output before the free-form messages.

---
 Documentation/git-merge-tree.txt |  8 ++++++++
 builtin/merge-tree.c             | 24 ++++++++++++++++++++++--
 t/t4301-merge-tree-real.sh       | 11 +++++++++++
 3 files changed, 41 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +0 -0
index fd7a867de60..041a4ac2785 100644

Documentation/git-merge-tree.txt simply one line: · line 62

Quoted 5 lines
Documentation/git-merge-tree.txt +1 -0
@@ -58,6 +58,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Conflicted file list>
 	<Informational messages>

To distinguish between the list of conflicted files and the informational messages, I think it would be good to insert an empty line, as a separator, like. And...

Quoted 52 lines
Documentation/git-merge-tree.txt +7 -0
 These are discussed individually below.
@@ -69,6 +70,13 @@ This is a tree object that represents what would be checked out in the
 working tree at the end of `git merge`.  If there were conflicts, then
 files within this tree may have embedded conflict markers.
 
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).
+
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
builtin/merge-tree.c +13 -1
index 560640ad911..d8eeeb3f306 100644
@@ -11,6 +11,9 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "quote.h"
+
+static int line_termination = '\n';
 
 struct merge_list {
 	struct merge_list *next;
@@ -395,7 +398,8 @@ struct merge_tree_options {
 };
 
 static int real_merge(struct merge_tree_options *o,
-		      const char *branch1, const char *branch2)
+		      const char *branch1, const char *branch2,
+		      const char *prefix)
 {
 	struct commit *parent1, *parent2;
 	struct commit_list *common;
@@ -449,6 +453,22 @@ static int real_merge(struct merge_tree_options *o,
 		o->show_messages = !result.clean;
 
 	printf("%s\n", oid_to_hex(&result.tree->object.oid));
+	if (!result.clean) {
+		struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
+		const char *last = NULL;
+		int i;
+
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 473

Quoted 10 lines
builtin/merge-tree.c +8 -0
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;
+		}
+		string_list_clear(&conflicted_files, 1);
+	}
 	if (o->show_messages) {
 		printf("\n");

... it seems that we do this already...

Quoted 15 lines
builtin/merge-tree.c +1 -1
 		merge_display_update_messages(&opt, &result, stdout);
@@ -502,7 +522,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 
 	/* Do the relevant type of merge */
 	if (o.real)
-		return real_merge(&o, argv[0], argv[1]);
+		return real_merge(&o, argv[0], argv[1], prefix);
 	else
 		return trivial_merge(argv[0], argv[1], argv[2]);
 }
t/t4301-merge-tree-real.sh +0 -0
index c34f8e6c1ed..43c9950dedb 100755

t/t4301-merge-tree-real.sh test_expect_success 'test conflict notices and such' ' · line 101

Quoted 8 lines
t/t4301-merge-tree-real.sh +2 -0
@@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '
 	#   "whatever" has *both* a modify/delete and a file/directory conflict
 	cat <<-EOF >expect &&
 	HASH
+	greeting
+	whatever~side1
 
 	Auto-merging greeting
 	CONFLICT (content): Merge conflict in greeting

... as illustrated by the test, too. I guess the documentation should show the empty line, too?

Ciao,
Dscho

Quoted 18 lines
t/t4301-merge-tree-real.sh +9 -1
@@ -105,4 +107,13 @@ test_expect_success 'test conflict notices and such' '
 	test_cmp expect actual
 '
 
+test_expect_success 'Just the conflicted files without the messages' '
+	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	test_write_lines HASH greeting whatever~side1 >expect &&
+
+	test_cmp expect actual
+'
+
 test_done
--

gitgitgadget

#66 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

Quoted 7 lines · Johannes Schindelin

Hi Elijah,

Quoted 2 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

[...]

Quoted 7 lines
Quoted 7 lines
Documentation/git-merge-tree.txt +0 -0
index fd7a867de60..041a4ac2785 100644
@@ -58,6 +58,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
Quoted 7 lines

Documentation/git-merge-tree.txt simply one line: · line 62

Quoted 3 lines
Documentation/git-merge-tree.txt +1 -0
      <OID of toplevel tree>
+     <Conflicted file list>
      <Informational messages>

To distinguish between the list of conflicted files and the informational messages, I think it would be good to insert an empty line, as a separator, like.

Yes, I agree; that's why I did so. :-)

[...]

Quoted 4 lines

Documentation/git-merge-tree.txt simply one line: · line 64

Quoted 2 lines
Documentation/git-merge-tree.txt +0 -0
      if (o->show_messages) {
              printf("\n");

... it seems that we do this already...

Yep.

[...]

Quoted 11 lines
Quoted 11 lines
t/t4301-merge-tree-real.sh +2 -0
index c34f8e6c1ed..43c9950dedb 100755
@@ -94,6 +94,8 @@ test_expect_success 'test conflict notices and such' '
      #   "whatever" has *both* a modify/delete and a file/directory conflict
      cat <<-EOF >expect &&
      HASH
+     greeting
+     whatever~side1
 
Quoted 5 lines

t/t4301-merge-tree-real.sh test_expect_success 'test conflict notices and such' ' · line 101

Quoted 2 lines
t/t4301-merge-tree-real.sh +0 -0
      Auto-merging greeting
      CONFLICT (content): Merge conflict in greeting

... as illustrated by the test, too. I guess the documentation should show the empty line, too?

It does:

Informational messages
~~~~~~~~~~~~~~~~~~~~~~

This always starts with a blank line to separate it from the previous sections, and then has free-form messages about the merge, such as:

The newline should not be split out separately, because --no-messages suppresses the newline. (Without the messages section, the newline isn't needed.)

#67 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

Hi Elijah,

Quoted 25 lines · Elijah Newren
Quoted 7 lines · Johannes Schindelin

Hi Elijah,

Quoted 2 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

[...]

Quoted 7 lines
Quoted 7 lines
Documentation/git-merge-tree.txt +0 -0
index fd7a867de60..041a4ac2785 100644
@@ -58,6 +58,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
Quoted 7 lines

Documentation/git-merge-tree.txt simply one line: · line 62

Quoted 3 lines
Documentation/git-merge-tree.txt +1 -0
      <OID of toplevel tree>
+     <Conflicted file list>
      <Informational messages>

To distinguish between the list of conflicted files and the informational messages, I think it would be good to insert an empty line, as a separator, like.

Quoted 1 line

Yes, I agree; that's why I did so. :-)

My concern was that I did not see this empty line reflected in the quoted diff. I would have expected an empty line between the <Conflicted [...]> and the <Informational [...]> line.

Thanks,
Dscho

A message that is not in the archive (sent off-list or not yet received): CABPp-BFyaakDSjHULpBRPQqq_jz2keyufHo1MjNS6dHQNR+JLQ@mail.gmail.com

#69 lore raw

Re: [PATCH 09/12] merge-tree: provide a list of which files have conflicts

Hi Elijah,

[reinstating the Cc: list]

Quoted 32 lines · Elijah Newren
Quoted 30 lines · Johannes Schindelin
Quoted 26 lines · Elijah Newren
Quoted 7 lines · Johannes Schindelin

Hi Elijah,

Quoted 2 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

[...]

Quoted 7 lines
Quoted 7 lines
Documentation/git-merge-tree.txt +0 -0
index fd7a867de60..041a4ac2785 100644
@@ -58,6 +58,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
Quoted 7 lines

Documentation/git-merge-tree.txt simply one line: · line 62

Quoted 3 lines
Documentation/git-merge-tree.txt +1 -0
      <OID of toplevel tree>
+     <Conflicted file list>
      <Informational messages>

To distinguish between the list of conflicted files and the informational messages, I think it would be good to insert an empty line, as a separator, like.

Yes, I agree; that's why I did so. :-)

Quoted 8 lines
Quoted 3 lines

My concern was that I did not see this empty line reflected in the quoted diff. I would have expected an empty line between the <Conflicted [...]> and the <Informational [...]> line.

As stated later in the same email, the newline is only printed if the <Informational messages> section is printed. As such, it's part of the <Informational messages> section and listing it between the sections could be misleading.

Thank you for the clarification. I still think that it might cause confusion in the documentation not to see the explicit newline, quite possible when _I_ re-read this in six months from now. But then, if -z is in effect, it won't be an explicit newline, it will be a NUL instead.

In short: I am fine with leaving this as-is, it might actually reduce even _my_ confusion to the minimum possible.

Thanks,
Dscho

#70 lore raw

PATCH 10/12 merge-tree: provide easy access to `ls-files -u` style info

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Much like git merge updates the index with information of the form
(mode, oid, stage, name)
provide this output for conflicted files for merge-tree as well. Provide an --exclude-oids-and-modes option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames.

---
 Documentation/git-merge-tree.txt | 30 ++++++++++++++++++++++++------
 builtin/merge-tree.c             | 11 ++++++++++-
 t/t4301-merge-tree-real.sh       | 26 ++++++++++++++++++++++++--
 3 files changed, 58 insertions(+), 9 deletions(-)
Documentation/git-merge-tree.txt +24 -6
index 041a4ac2785..beb08269a70 100644
@@ -41,6 +41,11 @@ discuss the first form.
 OPTIONS
 -------
 
+--exclude-oids-and-modes::
+	Instead of writing a list of (mode, oid, stage, path) tuples
+	to output for conflicted files, just provide a list of
+	filenames with conflicts.
+
 --[no-]messages::
 	Write any informational messages such as "Auto-merging <path>"
 	or CONFLICT notices to the end of stdout.  If unspecified, the
@@ -58,7 +63,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
-	<Conflicted file list>
+	<Conflicted file info>
 	<Informational messages>
 
 These are discussed individually below.
@@ -70,18 +75,23 @@ This is a tree object that represents what would be checked out in the
 working tree at the end of `git merge`.  If there were conflicts, then
 files within this tree may have embedded conflict markers.
 
-Conflicted file list
+Conflicted file info
 ~~~~~~~~~~~~~~~~~~~~
 
-This is a sequence of lines containing a filename on each line, quoted
-as explained for the configuration variable `core.quotePath` (see
-linkgit:git-config[1]).
+This is a sequence of lines with the format
+
+	<mode> <object> <stage> <filename>
+
+The filename will be quoted as explained for the configuration
+variable `core.quotePath` (see linkgit:git-config[1]).  However, if
+the `--exclude-oids-and-modes` option is passed, the mode, object, and
+stage will be omitted.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
 This always starts with a blank line to separate it from the previous
-section, and then has free-form messages about the merge, such as:
+sections, and then has free-form messages about the merge, such as:
 
   * "Auto-merging <file>"
   * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
@@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a
 much higher chance of the command not succeeding (and NEWTREE containing
 a bunch of stuff other than just a toplevel tree).
 
+git-merge-tree was written to provide users with the same information
+that they'd have access to if using `git merge`:
+  * what would be written to the working tree (the <OID of toplevel tree>)
+  * the higher order stages that would be written to the index (the
+    <Conflicted file info>)
+  * any messages that would have been printed to stdout (the <Informational
+    messages>)
+
 GIT
 ---
 Part of the linkgit:git[1] suite
builtin/merge-tree.c +10 -1
index d8eeeb3f306..7aa7f9fd54a 100644
@@ -395,6 +395,7 @@ struct merge_tree_options {
 	int real;
 	int trivial;
 	int show_messages;
+	int exclude_oids_and_modes;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,
 		merge_get_conflicted_files(&result, &conflicted_files);
 		for (i = 0; i < conflicted_files.nr; i++) {
 			const char *name = conflicted_files.items[i].string;
-			if (last && !strcmp(last, name))
+			struct stage_info *c = conflicted_files.items[i].util;
+			if (!o->exclude_oids_and_modes)
+				printf("%06o %s %d\t",
+				       c->mode, oid_to_hex(&c->oid), c->stage);
+			else if (last && !strcmp(last, name))
 				continue;
 			write_name_quoted_relative(
 				name, prefix, stdout, line_termination);
@@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			 N_("do a trivial merge only")),
 		OPT_BOOL(0, "messages", &o.show_messages,
 			 N_("also show informational/conflict messages")),
+		OPT_BOOL_F(0, "exclude-oids-and-modes",
+			   &o.exclude_oids_and_modes,
+			   N_("list conflicted files without oids and modes"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};
 
t/t4301-merge-tree-real.sh +24 -2
index 43c9950dedb..e921115cd2a 100755
@@ -46,6 +46,7 @@ test_expect_success 'Content merge and a few conflicts' '
 	expected_tree=$(cat .git/AUTO_MERGE) &&
 
 	# We will redo the merge, while we are still in a conflicted state!
+	git ls-files -u >conflicted-file-info &&
 	test_when_finished "git reset --hard" &&
 
 	test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
@@ -85,7 +86,7 @@ test_expect_success 'Barf on too many arguments' '
 '
 
 test_expect_success 'test conflict notices and such' '
-	test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --exclude-oids-and-modes side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	# Expected results:
@@ -108,7 +109,7 @@ test_expect_success 'test conflict notices and such' '
 '
 
 test_expect_success 'Just the conflicted files without the messages' '
-	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-oids-and-modes side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	test_write_lines HASH greeting whatever~side1 >expect &&
@@ -116,4 +117,25 @@ test_expect_success 'Just the conflicted files without the messages' '
 	test_cmp expect actual
 '
 
+test_expect_success 'Check conflicted oids and modes without messages' '
+	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	# Compare the basic output format
+	q_to_tab >expect <<-\EOF &&
+	HASH
+	100644 HASH 1Qgreeting
+	100644 HASH 2Qgreeting
+	100644 HASH 3Qgreeting
+	100644 HASH 1Qwhatever~side1
+	100644 HASH 2Qwhatever~side1
+	EOF
+
+	test_cmp expect actual &&
+
+	# Check the actual hashes against the `ls-files -u` output too
+	tail -n +2 out | sed -e s/side1/HEAD/ >actual &&
+	test_cmp conflicted-file-info actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#71 lore raw

Re: [PATCH 10/12] merge-tree: provide easy access to `ls-files -u` style info

Quoted 76 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Much like git merge updates the index with information of the form
(mode, oid, stage, name)
provide this output for conflicted files for merge-tree as well. Provide an --exclude-oids-and-modes option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames. [...]
+--exclude-oids-and-modes::

Documentation/git-merge-tree.txt +21 -4
+	Instead of writing a list of (mode, oid, stage, path) tuples
+	to output for conflicted files, just provide a list of
+	filenames with conflicts.
+
[...]
-This is a sequence of lines containing a filename on each line, quoted
-as explained for the configuration variable `core.quotePath` (see
-linkgit:git-config[1]).
+This is a sequence of lines with the format
+
+	<mode> <object> <stage> <filename>
+
+The filename will be quoted as explained for the configuration
+variable `core.quotePath` (see linkgit:git-config[1]).  However, if
+the `--exclude-oids-and-modes` option is passed, the mode, object, and
+stage will be omitted.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
 This always starts with a blank line to separate it from the previous
-section, and then has free-form messages about the merge, such as:
+sections, and then has free-form messages about the merge, such as:
 
   * "Auto-merging <file>"
   * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
@@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a
 much higher chance of the command not succeeding (and NEWTREE containing
 a bunch of stuff other than just a toplevel tree).
 
+git-merge-tree was written to provide users with the same information
+that they'd have access to if using `git merge`:
+  * what would be written to the working tree (the <OID of toplevel tree>)
+  * the higher order stages that would be written to the index (the
+    <Conflicted file info>)
+  * any messages that would have been printed to stdout (the <Informational
+    messages>)
+
 GIT
 ---
 Part of the linkgit:git[1] suite
builtin/merge-tree.c +6 -1
index d8eeeb3f306..7aa7f9fd54a 100644
@@ -395,6 +395,7 @@ struct merge_tree_options {
 	int real;
 	int trivial;
 	int show_messages;
+	int exclude_oids_and_modes;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,
 		merge_get_conflicted_files(&result, &conflicted_files);
 		for (i = 0; i < conflicted_files.nr; i++) {
 			const char *name = conflicted_files.items[i].string;
-			if (last && !strcmp(last, name))
+			struct stage_info *c = conflicted_files.items[i].util;
+			if (!o->exclude_oids_and_modes)
+				printf("%06o %s %d\t",
+				       c->mode, oid_to_hex(&c->oid), c->stage);
+			else if (last && !strcmp(last, name))
 				continue;
 			write_name_quoted_relative(
 				name, prefix, stdout, line_termination);

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 508

Quoted 9 lines
builtin/merge-tree.c +4 -0
@@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			 N_("do a trivial merge only")),
 		OPT_BOOL(0, "messages", &o.show_messages,
 			 N_("also show informational/conflict messages")),
+		OPT_BOOL_F(0, "exclude-oids-and-modes",
+			   &o.exclude_oids_and_modes,
+			   N_("list conflicted files without oids and modes"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};

Perhaps this really is the last formatting information anyone will want, but with a default of "<mode> <object> <stage> <filename>" being made "<stage> <filename>" with --exclude-oids-and-modes perhaps we'll want --exclude-all-except-filename etc. later.

It seems a lot simpler for new code to just support a --conflict-format option, lifting some code from the in-flight
https://lore.kernel.org/git/db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com/

I.e. that inner loop becomes a slightly more verbose strbuf_expand(), but it's all easy boilerplate code.

Then we just feed "%(objectmode) %(objectname) %(objectstage) %(filename)" into it by default, and allow the user to customize it.

#72 lore raw

Re: [PATCH 10/12] merge-tree: provide easy access to `ls-files -u` style info

Quoted 85 lines · Ævar Arnfjörð Bjarmason
Quoted 82 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Much like git merge updates the index with information of the form
(mode, oid, stage, name)
provide this output for conflicted files for merge-tree as well. Provide an --exclude-oids-and-modes option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames. [...]
+--exclude-oids-and-modes::

Documentation/git-merge-tree.txt +21 -4
+     Instead of writing a list of (mode, oid, stage, path) tuples
+     to output for conflicted files, just provide a list of
+     filenames with conflicts.
+
[...]
-This is a sequence of lines containing a filename on each line, quoted
-as explained for the configuration variable `core.quotePath` (see
-linkgit:git-config[1]).
+This is a sequence of lines with the format
+
+     <mode> <object> <stage> <filename>
+
+The filename will be quoted as explained for the configuration
+variable `core.quotePath` (see linkgit:git-config[1]).  However, if
+the `--exclude-oids-and-modes` option is passed, the mode, object, and
+stage will be omitted.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
 This always starts with a blank line to separate it from the previous
-section, and then has free-form messages about the merge, such as:
+sections, and then has free-form messages about the merge, such as:
 
   * "Auto-merging <file>"
   * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
@@ -113,6 +123,14 @@ plumbing commands since the possibility of merge conflicts give it a
 much higher chance of the command not succeeding (and NEWTREE containing
 a bunch of stuff other than just a toplevel tree).
 
+git-merge-tree was written to provide users with the same information
+that they'd have access to if using `git merge`:
+  * what would be written to the working tree (the <OID of toplevel tree>)
+  * the higher order stages that would be written to the index (the
+    <Conflicted file info>)
+  * any messages that would have been printed to stdout (the <Informational
+    messages>)
+
 GIT
 ---
 Part of the linkgit:git[1] suite
builtin/merge-tree.c +9 -1
index d8eeeb3f306..7aa7f9fd54a 100644
@@ -395,6 +395,7 @@ struct merge_tree_options {
      int real;
      int trivial;
      int show_messages;
+     int exclude_oids_and_modes;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -461,7 +462,11 @@ static int real_merge(struct merge_tree_options *o,
              merge_get_conflicted_files(&result, &conflicted_files);
              for (i = 0; i < conflicted_files.nr; i++) {
                      const char *name = conflicted_files.items[i].string;
-                     if (last && !strcmp(last, name))
+                     struct stage_info *c = conflicted_files.items[i].util;
+                     if (!o->exclude_oids_and_modes)
+                             printf("%06o %s %d\t",
+                                    c->mode, oid_to_hex(&c->oid), c->stage);
+                     else if (last && !strcmp(last, name))
                              continue;
                      write_name_quoted_relative(
                              name, prefix, stdout, line_termination);
@@ -495,6 +500,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                       N_("do a trivial merge only")),
              OPT_BOOL(0, "messages", &o.show_messages,
                       N_("also show informational/conflict messages")),
+             OPT_BOOL_F(0, "exclude-oids-and-modes",
+                        &o.exclude_oids_and_modes,
+                        N_("list conflicted files without oids and modes"),
Quoted 8 lines

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 508

Quoted 3 lines
builtin/merge-tree.c +1 -0
+                        PARSE_OPT_NONEG),
              OPT_END()
      };

Perhaps this really is the last formatting information anyone will want, but with a default of "<mode> <object> <stage> <filename>" being made "<stage> <filename>" with --exclude-oids-and-modes perhaps we'll want --exclude-all-except-filename etc. later.

Um, that's actually what this option does. Maybe my chosen name was bad.

--name-only like ls-files uses would have been nice, but it's misleading since it only affects the <conflict info> section of the output, not the printed tree or the informational messages.

Quoted 9 lines

It seems a lot simpler for new code to just support a --conflict-format option, lifting some code from the in-flight
https://lore.kernel.org/git/db058bf670c5668fc5b95baf83667cc282cb739b.1641978175.git.dyroneteng@gmail.com/

I.e. that inner loop becomes a slightly more verbose strbuf_expand(), but it's all easy boilerplate code.

Then we just feed "%(objectmode) %(objectname) %(objectstage) %(filename)" into it by default, and allow the user to customize it.

"simpler"? More flexible certainly.

I'm not sure that the flexibility is warranted, in this case, though. In ls-trees, where users don't need to process the output and can feed it directly to something else, that flexibility makes sense. But here the output *needs* special post-processing anyway since it's mixing three different types of output by default: top-level tree, conflicted file info, and informational conflict messages. (In my previous round I tried to split these kinds of output to separate locations so they could be parsed separately, but both Dscho and Christian didn't like that).

So, the default I figured should be to just provide all the information and just let others process it as wanted. But Taylor had said earlier that if there were conflicts the only information he wanted was a list of conflicted files (no stages, oids, or modes), so I figured an option making that a bit easier to extract was worthwhile. And that's what this patch is about.

#73 lore raw

PATCH 11/12 merge-tree: add a --allow-unrelated-histories flag

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Folks may want to merge histories that have no common ancestry; provide a flag with the same name as used by git merge to allow this.

---
 Documentation/git-merge-tree.txt |  5 +++++
 builtin/merge-tree.c             |  7 ++++++-
 t/t4301-merge-tree-real.sh       | 24 +++++++++++++++++++++++-
 3 files changed, 34 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +5 -0
index beb08269a70..df10a5963c7 100644
@@ -52,6 +52,11 @@ OPTIONS
 	default is to include these messages if there are merge
 	conflicts, and to omit them otherwise.
 
+--allow-unrelated-histories::
+	merge-tree will by default error out if the two branches specified
+	share no common history.  This flag can be given to override that
+	check and make the merge proceed anyway.
+
 OUTPUT
 ------
 
builtin/merge-tree.c +6 -1
index 7aa7f9fd54a..98441d5e05b 100644
@@ -394,6 +394,7 @@ static int trivial_merge(const char *base,
 struct merge_tree_options {
 	int real;
 	int trivial;
+	int allow_unrelated_histories;
 	int show_messages;
 	int exclude_oids_and_modes;
 };
@@ -440,7 +441,7 @@ static int real_merge(struct merge_tree_options *o,
 	 * merge_incore_recursive in merge-ort.h
 	 */
 	common = get_merge_bases(parent1, parent2);
-	if (!common)
+	if (!common && !o->allow_unrelated_histories)
 		die(_("refusing to merge unrelated histories"));
 	for (j = common; j; j = j->next)
 		commit_list_insert(j->item, &merge_bases);
@@ -504,6 +505,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			   &o.exclude_oids_and_modes,
 			   N_("list conflicted files without oids and modes"),
 			   PARSE_OPT_NONEG),
+		OPT_BOOL_F(0, "allow-unrelated-histories",
+			   &o.allow_unrelated_histories,
+			   N_("allow merging unrelated histories"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};
 
t/t4301-merge-tree-real.sh +23 -1
index e921115cd2a..a0447410655 100755
@@ -37,7 +37,13 @@ test_expect_success setup '
 	>whatever/empty &&
 	git add numbers greeting whatever/empty &&
 	test_tick &&
-	git commit -m other-modifications
+	git commit -m other-modifications &&
+
+	git switch --orphan unrelated &&
+	>something-else &&
+	git add something-else &&
+	test_tick &&
+	git commit -m first-commit
 '
 
 test_expect_success 'Content merge and a few conflicts' '
@@ -138,4 +144,20 @@ test_expect_success 'Check conflicted oids and modes without messages' '
 	test_cmp conflicted-file-info actual
 '
 
+test_expect_success 'error out by default for unrelated histories' '
+	test_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&
+
+	grep "refusing to merge unrelated histories" error
+'
+
+test_expect_success 'can override merge of unrelated histories' '
+	git merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&
+	TREE=$(cat tree) &&
+
+	git rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&
+	git rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#74 lore raw

PATCH 12/12 git-merge-tree.txt: add a section on potentional usage mistakes

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

---
 Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
Documentation/git-merge-tree.txt +46 -0
index df10a5963c7..3c1aa0ffbae 100644
@@ -136,6 +136,52 @@ that they'd have access to if using `git merge`:
   * any messages that would have been printed to stdout (the <Informational
     messages>)
 
+MISTAKES TO AVOID
+-----------------
+
+Do NOT look through the resulting toplevel tree to try to find which
+files conflict; parse the <Conflicted file info> section instead.  Not
+only would parsing an entire tree be horrendously slow in large
+repositories, there are numerous types of conflicts not representable by
+conflict markers (modify/delete, mode conflict, binary file changed on
+both sides, file/directory conflicts, various rename conflict
+permutations, etc.)
+
+Do NOT interpret an empty <Conflicted file info> list as a clean merge;
+check the exit status.  A merge can have conflicts without having
+individual files conflict (there are a few types of directory rename
+conflicts that fall into this category, and others might also be added
+in the future).
+
+Do NOT attempt to guess or make the user guess the conflict types from
+the <Conflicted file info> list.  The information there is insufficient
+to do so.  For example: Rename/rename(1to2) conflicts (both sides
+renamed the same file differently) will result in three different file
+having higher order stages (but each only has one higher order stage),
+with no way (short of the <Informational messages> section) to determine
+which three files are related.  File/directory conflicts also result in
+a file with exactly one higher order stage.
+Possibly-involved-in-directory-rename conflicts (when
+"merge.directoryRenames" is unset or set to "conflicts") also result in
+a file with exactly one higher order stage.  In all cases, the
+<Informational messages> section has the necessary info, though it is
+not designed to be machine parseable.
+
+Do NOT assume all filenames listed in the <Informational messages>
+section had conflicts.  Messages can be included for files that have no
+conflicts, such as "Auto-merging <file>".
+
+AVOID taking the OIDS from the <Conflicted file info> and re-merging
+them to present the conflicts to the user.  This will lose information.
+Instead, look up the version of the file found within the <OID of
+toplevel tree> and show that instead.  In particular, the latter will
+have conflict markers annotated with the original branch/commit being
+merged and, if renames were involved, the original filename.  While you
+could include the original branch/commit in the conflict marker
+annotations when re-merging, the original filename is not available from
+the <Conflicted file info> and thus you would be losing information that
+might help the user resolve the conflict.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
#75 lore raw
In reply to Elijah Newren via GitGitGadget
Quoted 12 lines · Elijah Newren via GitGitGadget

Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments on v2):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.

The above changes seem good to me.

Quoted 6 lines

* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments

I still think that it might be simpler and cleaner to leave 'git merge-tree' alone for now, and just add a new command named for example 'git write-merge-tree'. Later we can always add flags to 'git merge-tree' or add 'git trivial-merge-tree' as an alias for 'git merge-tree', and eventually slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if that's where we want to go.

Quoted 7 lines

* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)

The above changes seem good to me too.

Quoted 4 lines

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them. The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Thanks for working on this anyway!

#76 lore raw

Hi Christian,

Quoted 11 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget

* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments

I still think that it might be simpler and cleaner to leave 'git merge-tree' alone for now, and just add a new command named for example 'git write-merge-tree'.

That would assume that the original git merge-tree implementation was useful. That notion has been thoroughly refuted in the meantime, though.

I am really opposed to introducing a new command here. Elijah took the best approach we can take here: save the merge-tree command by teaching it to do something useful.

Quoted 4 lines

Later we can always add flags to 'git merge-tree' or add 'git trivial-merge-tree' as an alias for 'git merge-tree', and eventually slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if that's where we want to go.

I suggested before, and seem to need to repeat again, that we need to let ourselves be guided less by hypothetical scenarios, and more by actual, concrete use cases where the revamped merge-tree command is useful.

And since I already provided some feedback based on my work from working on a server-side backend, I am fairly certain that we already have a pretty good idea where we want to go.

Quoted 7 lines
Quoted 4 lines

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them.

But why? That _so_ smells like a hypothetical scenario.

We do not need the diffs. It is highly unlikely that the server-side wants to have diffs, and if a user does want the diffs, it is very, very easy to generate them by chaining low-level commands.

So there is absolutely no need for git merge-tree to produce diffs.

Quoted 5 lines

The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

That assumes that server-side merge UIs will present merge conflicts in the form of diffs containing merge conflict markers. Which I don't think will happen, like, ever.

In short: I completely disagree that we should introduce a new command, and I also completely disagree that the merge-tree command should output any diffs.

I do agree that we need to be mindful of what we actually need, and in that regard, I reiterate that we need to let concrete use cases guide us. As part of GitLab, you might be in an excellent position to look at GitLab's concrete server-side needs when it comes to use git merge-tree to perform merges.

Ciao,
Dscho

#77 lore raw

Hi Dscho,

Quoted 17 lines · Johannes Schindelin

Hi Christian,

Quoted 11 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget

* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments

I still think that it might be simpler and cleaner to leave 'git merge-tree' alone for now, and just add a new command named for example 'git write-merge-tree'.

Quoted 6 lines

That would assume that the original git merge-tree implementation was useful. That notion has been thoroughly refuted in the meantime, though.

I am really opposed to introducing a new command here. Elijah took the best approach we can take here: save the merge-tree command by teaching it to do something useful.

I think it's a question of point of view. If a command is completely useless, then most of the time it needs to die, not be "saved". We would need good statistics, but I doubt we have "saved" many useless commands before, compared to commands we have just killed.

Quoted 8 lines
Quoted 4 lines

Later we can always add flags to 'git merge-tree' or add 'git trivial-merge-tree' as an alias for 'git merge-tree', and eventually slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if that's where we want to go.

I suggested before, and seem to need to repeat again, that we need to let ourselves be guided less by hypothetical scenarios, and more by actual, concrete use cases where the revamped merge-tree command is useful.

Ok, see below.

Quoted 26 lines

And since I already provided some feedback based on my work from working on a server-side backend, I am fairly certain that we already have a pretty good idea where we want to go.

Quoted 7 lines
Quoted 4 lines

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them.

But why? That _so_ smells like a hypothetical scenario.

We do not need the diffs. It is highly unlikely that the server-side wants to have diffs, and if a user does want the diffs, it is very, very easy to generate them by chaining low-level commands.

So there is absolutely no need for git merge-tree to produce diffs.

Quoted 5 lines

The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Quoted 3 lines

That assumes that server-side merge UIs will present merge conflicts in the form of diffs containing merge conflict markers. Which I don't think will happen, like, ever.

Please take a look at:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor

As you can see in the image there are conflict markers in the file displayed by the server UI.

Quoted 9 lines

In short: I completely disagree that we should introduce a new command, and I also completely disagree that the merge-tree command should output any diffs.

I do agree that we need to be mindful of what we actually need, and in that regard, I reiterate that we need to let concrete use cases guide us. As part of GitLab, you might be in an excellent position to look at GitLab's concrete server-side needs when it comes to use git merge-tree to perform merges.

I hope I provided a concrete use case with the link above.

#78 lore raw

Hi Christian,

Quoted 15 lines · Christian Couder
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines · Christian Couder

The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Quoted 3 lines

That assumes that server-side merge UIs will present merge conflicts in the form of diffs containing merge conflict markers. Which I don't think will happen, like, ever.

Quoted 6 lines

Please take a look at:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor

As you can see in the image there are conflict markers in the file displayed by the server UI.

Please note the difference between what I wrote above (present merge conflicts in the form of diffs containing merge conflict markers) and what is shown in the document you linked to (present a file annotated with merge conflict markers).

There is no diff in that page.

What's more: there are not only conflict markers in the editor, there is clearly a visual marker next to the line number that indicates that the editor has a fundamental understanding where the conflict markers are. Which means that the conflict markers must have been generated independently of Git rather than parsed in some random diff that was produced by Git.

In other words: you are making my case for me that git merge-tree should not generate diff output because it would not even be used.

Quoted 11 lines
Quoted 9 lines

In short: I completely disagree that we should introduce a new command, and I also completely disagree that the merge-tree command should output any diffs.

I do agree that we need to be mindful of what we actually need, and in that regard, I reiterate that we need to let concrete use cases guide us. As part of GitLab, you might be in an excellent position to look at GitLab's concrete server-side needs when it comes to use git merge-tree to perform merges.

I hope I provided a concrete use case with the link above.

Sorry, I apparently was a bit unclear.

In the context of discussing git merge-tree, a low-level Git command, when I talk about a user, I do not mean a human being, but a program that calls that command and parses its output.

Corollary: by "use case" I refer to a concrete implementation of a server-side merge operation, I refer to backend code that currently calls into libgit2 to perform the merge, and would benefit from calling git merge-tree instead. Such a use case informs us about the type and amount of information that is required of the code that is currently being discussed in this mail thread. And I highly doubt that you will find such a use case that wants libgit2 (and later, git merge-tree) to generate diffs. Because _diffs_ are certainly _not_ what is consumed by the inline editor you referenced.

Of course, I am still left guessing what the server-side needs concretely, because that is not at all obvious from the user-facing web site to which you sent me. What is needed is a good, hard look at the actual _code_, the code that calls into libgit2 to perform a merge, and that could instead spawn a git merge-tree process to accomplish the same thing.

We need to get away from hypothetical scenarios. They're not helping.

Ciao,
Johannes

#79 lore raw

Hi Dscho,

Quoted 27 lines · Johannes Schindelin

Hi Christian,

Quoted 15 lines · Christian Couder
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines · Christian Couder

The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Quoted 3 lines

That assumes that server-side merge UIs will present merge conflicts in the form of diffs containing merge conflict markers. Which I don't think will happen, like, ever.

Quoted 6 lines

Please take a look at:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor

As you can see in the image there are conflict markers in the file displayed by the server UI.

Quoted 6 lines

Please note the difference between what I wrote above (present merge conflicts in the form of diffs containing merge conflict markers) and what is shown in the document you linked to (present a file annotated with merge conflict markers).

There is no diff in that page.

The server UI could just get a diff with the conflicts inside instead of the full files with conflict inside, as the diff would be smaller to parse than the full files. So even if it's not shown, the diff could still be useful.

Also just above the section of the link I sent, there is this section

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-interactive-mode

where one can see diff markers in the image. There are no conflict markers in those images, but it's possible that a future UI could combine both a diff and conflict markers.

Also please note that I don't absolutely require diffs. At the beginning of the paragraph from my original email that you quoted above, there was:

"It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them."

So I was just saying that the format and code should be flexible enough to be able to easily accommodate sending further data like diffs with additional options. I think it's a very reasonable request. So please don't make it a huge issue. You can always NACK a patch adding such an option later.

Quoted 1 line

What's more: there are not only conflict markers in the editor,

You don't see the ">>>>>>>"?

Quoted 3 lines

there is
clearly a visual marker next to the line number that indicates that the editor has a fundamental understanding where the conflict markers are.

Yeah, so this shows that those markers can be important for the editor.

Quoted 3 lines

Which means that the conflict markers must have been generated independently of Git rather than parsed in some random diff that was produced by Git.

Why couldn't they be generated by Git and then just parsed from a diff in the future, even if that was not the case here?

Quoted 2 lines

In other words: you are making my case for me that git merge-tree should not generate diff output because it would not even be used.

The other link above in this email actually shows that diffs are used right now to resolve conflicts.

Quoted 10 lines
Quoted 10 lines
Quoted 9 lines

In short: I completely disagree that we should introduce a new command, and I also completely disagree that the merge-tree command should output any diffs.

I do agree that we need to be mindful of what we actually need, and in that regard, I reiterate that we need to let concrete use cases guide us. As part of GitLab, you might be in an excellent position to look at GitLab's concrete server-side needs when it comes to use git merge-tree to perform merges.

Quoted 7 lines
Quoted 1 line

I hope I provided a concrete use case with the link above.

Sorry, I apparently was a bit unclear.

In the context of discussing git merge-tree, a low-level Git command, when I talk about a user, I do not mean a human being, but a program that calls that command and parses its output.

So it could very well parse diffs containing conflict markers and show those conflict markers.

[...]

Quoted 7 lines

Of course, I am still left guessing what the server-side needs concretely, because that is not at all obvious from the user-facing web site to which you sent me. What is needed is a good, hard look at the actual _code_, the code that calls into libgit2 to perform a merge, and that could instead spawn a git merge-tree process to accomplish the same thing.

We need to get away from hypothetical scenarios. They're not helping.

I am not even asking for a feature, just to make it possible to extend the output of a brand new command in an RFC with some possibly useful things, and you are making such requests...

Please relax a bit on this.

#80 lore raw

Hi Christian,

Quoted 32 lines · Christian Couder
Quoted 30 lines · Johannes Schindelin
Quoted 15 lines · Christian Couder
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines · Christian Couder

The reason is that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them. So it would be simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Quoted 3 lines

That assumes that server-side merge UIs will present merge conflicts in the form of diffs containing merge conflict markers. Which I don't think will happen, like, ever.

Quoted 6 lines

Please take a look at:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-the-inline-editor

As you can see in the image there are conflict markers in the file displayed by the server UI.

Please note the difference between what I wrote above (present merge conflicts in the form of diffs containing merge conflict markers) and what is shown in the document you linked to (present a file annotated with merge conflict markers).

Quoted 6 lines
Quoted 1 line

There is no diff in that page.

The server UI could just get a diff with the conflicts inside instead of the full files with conflict inside, as the diff would be smaller to parse than the full files. So even if it's not shown, the diff could still be useful.

You really need to get away from talking about this in hypothetical terms.

Quoted 5 lines

Also just above the section of the link I sent, there is this section

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#resolve-conflicts-in-interactive-mode

where one can see diff markers in the image.

That's a side-by-side diff. Git cannot even produce those.

Quoted 3 lines
Quoted 1 line

What's more: there are not only conflict markers in the editor,

You don't see the ">>>>>>>"?

Yes, I do. And not only that. I also see that the editor knows very specifically where the conflict happens.

And since any file can contain >>>>>>> _without_ it being a conflict marker, the editor most likely does not parse the output of Git that contains a conflict marker. At least I hope it does not because it would then very easily be confused by strings that look like conflict markers, but aren't.

Think about our very own test suite, and why we specifically set conflict-marker-size=32 for those files. Same reason why the server backend cannot simply ingest files with conflict markers and then hope to figure out which >>>>>>> are real conflict markers and which are not.

Quoted 5 lines
Quoted 3 lines

there is clearly a visual marker next to the line number that indicates that the editor has a fundamental understanding where the conflict markers are.

Yeah, so this shows that those markers can be important for the editor.

Of course they are important! That's my point!

Quoted 5 lines
Quoted 2 lines

In other words: you are making my case for me that git merge-tree should not generate diff output because it would not even be used.

The other link above in this email actually shows that diffs are used right now to resolve conflicts.

It shows that Git was not used to generate the diff, is what it shows.

I see that you are still trying to guess what the server-side needs actually are. It really is time to stop guessing. So I will keep challenging you to actually look at the GitLab code, to take a stab at teaching it to use git merge-tree to perform merges. And then to come back with what you learned. I guarantee you that that will be multiple times more useful than talking about it in hypotheticals.

And you are in such an almost unique position to contribute to this patch series, to provide that very valuable feedback how git merge-tree could be improved to support actual, real-life server-side code that is currently in use! So why not make the most out of it?

Ciao,
Johannes

#81 lore raw
In reply to Christian Couder
Quoted 17 lines · Christian Couder
Quoted 12 lines · Elijah Newren via GitGitGadget

Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments on v2):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.

Quoted 1 line

The above changes seem good to me.

:-)

Quoted 7 lines
Quoted 6 lines

* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments

Quoted 6 lines

I still think that it might be simpler and cleaner to leave 'git merge-tree' alone for now, and just add a new command named for example 'git write-merge-tree'. Later we can always add flags to 'git merge-tree' or add 'git trivial-merge-tree' as an alias for 'git merge-tree', and eventually slowly switch 'git merge-tree' to mean only 'git write-merge-tree' if that's where we want to go.

Understood. Since we can't immediately kill the old merge-tree, I don't think there's a perfectly clean solution here, and it's totally understandable that different folks would have different opinions on which interim choice would be cleanest. What you suggest is reasonable, I just personally prefer the path in this series.

Quoted 9 lines
Quoted 7 lines

* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)

The above changes seem good to me too.

Thanks for reading over things carefully and for providing many detailed, helpful comments!

Quoted 7 lines
Quoted 4 lines

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them.

My main concern is just that merge-tree remain a low-level tool and have machine-parseable output. I was a little worried that both you and Dscho wanted everything on stdout rather than in separate files, as the <Informational messages> part of the output is rather free-form. But since it's at the end, and has a machine-parseable beginning, it can just be slurped in and we're all good. The diff output raises my eyebrow because I'm worried we're losing this property. If there are clear usecases for adding more output, and we can do so without losing this machine-parseable property, I don't have a problem with adding an option for it.

One analogy we might use here is that git merge provides a diffstat at the end. What you're asking is more than a diffstat, but might be considered similar-ish in nature.

Quoted 3 lines

The reason is
that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them.

I'm a little worried about the assumption here that conflict size is measurable and visible via diffs. That might be true in some cases, but a UI written with that assumption is going to be very confusing when hitting cases where that assumption does not hold. For example:

* What if there is a binary file conflict, or a modify/delete or
rename/delete conflict, or failed-to-merge submodule conflict, or a file location conflict? (For these, there is no diff relative to the first parent and hence this conflict would have no diff output for it)?
* What if there was a simple file/directory conflict? A diff would
show a rename (even when neither side did any renames), but not any conflict markers.
* What if there was a rename/rename conflict (both sides renamed
same file differently) or a distinct types conflict? The former results in three different conflicting files, none of them with conflict markers, while the latter results in two different conflicting files both without conflict markers? Showing individual per-file diffs totally loses all context here -- it'll show no-diff for one of the files, and totally new additions for the ones.

Such a problem statement just seems fraught with edge cases to me, and suggests that the problem statement might be in need of revisiting.

Don't read this as me closing the door on the possibility of diffs; I'm not trying to do that. I'm listing my misgivings about how I think they might be used (i.e. be careful if you're headed down this path as you might be digging yourself a never-ending support hole). You can also think of my comments as feedback to consider and address when you propose a future feature addition for adding diffs. If/when you propose such a feature, we'd probably be able to dive more into specifics and usecases at that time, which may or may not circumvent my concerns.

Quoted 4 lines

So it would be
simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Um, this part I'm not sure I get. I thought the reason for the diffs was performance -- you knew you wanted the diffs, and you wanted it done as part of the same process. But why would this be simpler? Your patch series included three different diffs, and the emails you pointed me at suggested all kinds of configurability. That suggests the merge-tree command would have to take the exact same options the user would supply to diff, and thus would have to be told all the same options, right? I don't see how this removes any complexity at all for the user.

Unless...is the request in some way similar to merge's diffstat where there is always a very specific type of diff that is wanted and you aren't envisioning much flexibility in what kind of diff or what to diff against -- is that where the simplification comes from?

#82 lore raw
Quoted 15 lines · Elijah Newren
Quoted 11 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them.

Quoted 10 lines

My main concern is just that merge-tree remain a low-level tool and have machine-parseable output. I was a little worried that both you and Dscho wanted everything on stdout rather than in separate files, as the <Informational messages> part of the output is rather free-form. But since it's at the end, and has a machine-parseable beginning, it can just be slurped in and we're all good. The diff output raises my eyebrow because I'm worried we're losing this property. If there are clear usecases for adding more output, and we can do so without losing this machine-parseable property, I don't have a problem with adding an option for it.

That's ok for me for now. I will certainly not work on adding options for diff output without any usecase.

Quoted 13 lines

One analogy we might use here is that git merge provides a diffstat at the end. What you're asking is more than a diffstat, but might be considered similar-ish in nature.

Quoted 3 lines

The reason is
that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them.

I'm a little worried about the assumption here that conflict size is measurable and visible via diffs. That might be true in some cases, but a UI written with that assumption is going to be very confusing when hitting cases where that assumption does not hold. For example:

Quoted 15 lines

* What if there is a binary file conflict, or a modify/delete or
rename/delete conflict, or failed-to-merge submodule conflict, or a file location conflict? (For these, there is no diff relative to the first parent and hence this conflict would have no diff output for it)?
* What if there was a simple file/directory conflict? A diff would
show a rename (even when neither side did any renames), but not any conflict markers.
* What if there was a rename/rename conflict (both sides renamed
same file differently) or a distinct types conflict? The former results in three different conflicting files, none of them with conflict markers, while the latter results in two different conflicting files both without conflict markers? Showing individual per-file diffs totally loses all context here -- it'll show no-diff for one of the files, and totally new additions for the ones.

In those cases we just tell users that they cannot resolve those conflicts in the user interface, see the following doc:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface

Quoted 2 lines

Such a problem statement just seems fraught with edge cases to me, and suggests that the problem statement might be in need of revisiting.

Users understand that some kinds of conflicts cannot yet be resolved using a user interface. Maybe we will be able to make improvements so that more kinds of conflicts can be resolved in a UI in the future though. That's why a flexible and extensible output could help.

Quoted 9 lines

Don't read this as me closing the door on the possibility of diffs; I'm not trying to do that. I'm listing my misgivings about how I think they might be used (i.e. be careful if you're headed down this path as you might be digging yourself a never-ending support hole). You can also think of my comments as feedback to consider and address when you propose a future feature addition for adding diffs. If/when you propose such a feature, we'd probably be able to dive more into specifics and usecases at that time, which may or may not circumvent my concerns.

I know that diffs, or any new single feature, will likely not be a silver bullet.

Quoted 8 lines
Quoted 4 lines

So it would be
simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Um, this part I'm not sure I get. I thought the reason for the diffs was performance -- you knew you wanted the diffs, and you wanted it done as part of the same process. But why would this be simpler?

In the commit message of 4/12 you show an example of using it in simple scripts:

NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..."
...

So I think it would be simpler for someone interested in seeing the conflicts, like a script writer, or maybe someone using it manually for example as a dry run before performing a merge, to be able to get them right away from the command rather than to have to use another command (which means finding the right command, arguments and options for that) to get them.

Quoted 11 lines

Your patch series included three different diffs, and the emails you pointed me at suggested all kinds of configurability. That suggests the merge-tree command would have to take the exact same options the user would supply to diff, and thus would have to be told all the same options, right? I don't see how this removes any complexity at all for the user.

Unless...is the request in some way similar to merge's diffstat where there is always a very specific type of diff that is wanted and you aren't envisioning much flexibility in what kind of diff or what to diff against -- is that where the simplification comes from?

Well I just think the default diff output could be tailored for the most likely usecases and options made available later for more advanced usecases or users.

#83 lore raw

Hi Christian,

Quoted 60 lines · Christian Couder
Quoted 15 lines · Elijah Newren
Quoted 11 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget

Stuff intentionally NOT included, but which others seemed to feel strongly about; they'd need to convince me more on these:

* Any form of diff output[1]

It's not a big issue for me to not include them right now as long as it's possible to add cli options later that add them.

Quoted 10 lines

My main concern is just that merge-tree remain a low-level tool and have machine-parseable output. I was a little worried that both you and Dscho wanted everything on stdout rather than in separate files, as the <Informational messages> part of the output is rather free-form. But since it's at the end, and has a machine-parseable beginning, it can just be slurped in and we're all good. The diff output raises my eyebrow because I'm worried we're losing this property. If there are clear usecases for adding more output, and we can do so without losing this machine-parseable property, I don't have a problem with adding an option for it.

That's ok for me for now. I will certainly not work on adding options for diff output without any usecase.

Quoted 13 lines

One analogy we might use here is that git merge provides a diffstat at the end. What you're asking is more than a diffstat, but might be considered similar-ish in nature.

Quoted 3 lines

The reason is
that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them.

I'm a little worried about the assumption here that conflict size is measurable and visible via diffs. That might be true in some cases, but a UI written with that assumption is going to be very confusing when hitting cases where that assumption does not hold. For example:

Quoted 15 lines

* What if there is a binary file conflict, or a modify/delete or
rename/delete conflict, or failed-to-merge submodule conflict, or a file location conflict? (For these, there is no diff relative to the first parent and hence this conflict would have no diff output for it)?
* What if there was a simple file/directory conflict? A diff would
show a rename (even when neither side did any renames), but not any conflict markers.
* What if there was a rename/rename conflict (both sides renamed
same file differently) or a distinct types conflict? The former results in three different conflicting files, none of them with conflict markers, while the latter results in two different conflicting files both without conflict markers? Showing individual per-file diffs totally loses all context here -- it'll show no-diff for one of the files, and totally new additions for the ones.

Quoted 4 lines

In those cases we just tell users that they cannot resolve those conflicts in the user interface, see the following doc:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface

So...I think you may have just convinced me that my fears were justified and that I should probably NAK any attempt to add diffs to the merge-tree command. I won't jump to conclusions but you've provided some pretty strong signal to me against going down that route. The list of limitations in the link you provide do mostly avoid the broken cases I listed above, but it enshrines those limitations on that webpage as fundamental rather than just as current implementation shortcomings. You may not be able to remove those limitations on that webpage without either expunging the diffs from the UI or exposing the brokenness of the various cases above.

If you do propose a diff option in the future, come prepared to discuss how you'll avoid accidentally leading others down into paths with the same fundamental issues, and/or how the above types of conflicts might still be meaningfully handled.

Also, the list of limitations you have may not be quite comprehensive enough to avoid all problems (though it certainly avoids most of them). Can I ask a couple clarifying questions about your list of limitations in that link? :

* When that page says the file cannot already contain conflict
markers, is the check performed on the version of the file in the two trees being merged, or is the check performed on the 2nd and 3rd index stage of the merge result (these are not equivalent checks, even if they often give the same answer)?
* When that page says the file must already exist in the same path
on both branches, is the check performed on by checking the path in the two trees being merged, or is the check performed on the 2nd and 3rd index stage of the merge result (again, these are not equivalent checks)?

Quoted 7 lines
Quoted 2 lines

Such a problem statement just seems fraught with edge cases to me, and suggests that the problem statement might be in need of revisiting.

Users understand that some kinds of conflicts cannot yet be resolved using a user interface. Maybe we will be able to make improvements so that more kinds of conflicts can be resolved in a UI in the future though. That's why a flexible and extensible output could help.

I think future improvements to handle more conflict types may well hinge on removing the diff-output-using portion of your interface; I think it may well be fundamentally incompatible.

I agree we want to leave the output format open for extension, I'm just saying we have to be careful about what extensions are included and this one worries me.

Quoted 10 lines
Quoted 9 lines

Don't read this as me closing the door on the possibility of diffs; I'm not trying to do that. I'm listing my misgivings about how I think they might be used (i.e. be careful if you're headed down this path as you might be digging yourself a never-ending support hole). You can also think of my comments as feedback to consider and address when you propose a future feature addition for adding diffs. If/when you propose such a feature, we'd probably be able to dive more into specifics and usecases at that time, which may or may not circumvent my concerns.

Quoted 2 lines

I know that diffs, or any new single feature, will likely not be a silver bullet.

Sure that's fair; not being a silver bullet is fine. We do need to avoid providing a kryptonite bullet, though.

Quoted 11 lines
Quoted 8 lines
Quoted 4 lines

So it would be
simpler to just have an option to show any conflict right away, rather than have the user launch another command (a diff-tree against which tree and with which options?).

Um, this part I'm not sure I get. I thought the reason for the diffs was performance -- you knew you wanted the diffs, and you wanted it done as part of the same process. But why would this be simpler?

In the commit message of 4/12 you show an example of using it in simple scripts:

Quoted 8 lines

NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..."
...

So I think it would be simpler for someone interested in seeing the conflicts, like a script writer, or maybe someone using it manually for example as a dry run before performing a merge, to be able to get them right away from the command...

Okay, but the command already does that. When there are conflicts, NEWTREE won't actually be a tree; it'll be lots of lines of output. That's (part of) the reason for the exit status check. So users already get that information from the command.

Quoted 3 lines

...rather than to have to use another
command (which means finding the right command, arguments and options for that) to get them.

As for finding the right arguments and options...

Quoted 7 lines
Quoted 7 lines

Your patch series included three different diffs, and the emails you pointed me at suggested all kinds of configurability. That suggests the merge-tree command would have to take the exact same options the user would supply to diff, and thus would have to be told all the same options, right? I don't see how this removes any complexity at all for the user.

Quoted 8 lines
Quoted 4 lines

Unless...is the request in some way similar to merge's diffstat where there is always a very specific type of diff that is wanted and you aren't envisioning much flexibility in what kind of diff or what to diff against -- is that where the simplification comes from?

Well I just think the default diff output could be tailored for the most likely usecases and options made available later for more advanced usecases or users.

Ah, so this may go against your earlier comments at [1] about a merge-tree on steroids and a huge array of diff options, or against your comments about diffs not being provided by default (also [1]). Because if you have that huge range of diff options, and the diffs are not provided by default, then it's not clear how you've simplified things because users would still need to figure out the right arguments and options to pass, it's just that the user would have to pass all (or maybe just most?) of those arguments and options to merge-tree instead of to diff. Or is the simpler UI you're discussing really just about not needing to include 1 argument, the name of the new toplevel tree, since that single argument could be implicit?

I'm having a hard time buying the "simpler UI for script writers" angle of argument here, especially for script writers who should fully be able to look up the appropriate commands and use them. Your earlier arguments about performance being important (having both the merge & the diff in the same process) seemed much more convincing to me. But maybe I'm still just missing something about your "simpler" angle?

[1] https://lore.kernel.org/git/CAP8UFD0wKnAg5oyMWchXysPTg3K9Vb4M1tRcPzPE81QM903pYg@mail.gmail.com/

#84 lore raw
Quoted 10 lines · Elijah Newren

Hi Christian,

Quoted 5 lines · Christian Couder
Quoted 3 lines · Elijah Newren

On Wed, Jan 26, 2022 at 12:48 AM Christian Couder
<[email protected]> wrote:

[...]

Quoted 41 lines
Quoted 25 lines
Quoted 9 lines
Quoted 3 lines

The reason is
that I think in many cases when there are conflicts, the conflicts will be small and the user will want to see them.

I'm a little worried about the assumption here that conflict size is measurable and visible via diffs. That might be true in some cases, but a UI written with that assumption is going to be very confusing when hitting cases where that assumption does not hold. For example:

Quoted 15 lines

* What if there is a binary file conflict, or a modify/delete or
rename/delete conflict, or failed-to-merge submodule conflict, or a file location conflict? (For these, there is no diff relative to the first parent and hence this conflict would have no diff output for it)?
* What if there was a simple file/directory conflict? A diff would
show a rename (even when neither side did any renames), but not any conflict markers.
* What if there was a rename/rename conflict (both sides renamed
same file differently) or a distinct types conflict? The former results in three different conflicting files, none of them with conflict markers, while the latter results in two different conflicting files both without conflict markers? Showing individual per-file diffs totally loses all context here -- it'll show no-diff for one of the files, and totally new additions for the ones.

Quoted 4 lines

In those cases we just tell users that they cannot resolve those conflicts in the user interface, see the following doc:

https://docs.gitlab.com/ee/user/project/merge_requests/conflicts.html#conflicts-you-can-resolve-in-the-user-interface

So...I think you may have just convinced me that my fears were justified and that I should probably NAK any attempt to add diffs to the merge-tree command. I won't jump to conclusions but you've provided some pretty strong signal to me against going down that route. The list of limitations in the link you provide do mostly avoid the broken cases I listed above, but it enshrines those limitations on that webpage as fundamental rather than just as current implementation shortcomings. You may not be able to remove those limitations on that webpage without either expunging the diffs from the UI or exposing the brokenness of the various cases above.

Quoted 4 lines

If you do propose a diff option in the future, come prepared to discuss how you'll avoid accidentally leading others down into paths with the same fundamental issues, and/or how the above types of conflicts might still be meaningfully handled.

Actually, after having a few extra days to think about it, I thought of something that should have been obvious to me, given my other in-flight series that this depends upon...

If you used the same trick that remerge-diff does to include the CONFLICT (and related messages) headers in the diff, then the kinds of conflicts that are normally either invisible or misleading/confusing to show via a diff would suddenly have the extra notices needed to explain them, and make this problem tractable.

Further, it'd only make sense to do the special diff as part of the merge-tree process, since it has the conflict messages strmap needed to do this.

And there's not all that much work that would be needed to take advantage of this, especially since this series already depends upon the remerge-diff series.

So, maybe this is fine after all.

Quoted 5 lines

Also, the list of limitations you have may not be quite comprehensive enough to avoid all problems (though it certainly avoids most of them). Can I ask a couple clarifying questions about your list of limitations in that link? :

Quoted 10 lines

* When that page says the file cannot already contain conflict
markers, is the check performed on the version of the file in the two trees being merged, or is the check performed on the 2nd and 3rd index stage of the merge result (these are not equivalent checks, even if they often give the same answer)?
* When that page says the file must already exist in the same path
on both branches, is the check performed on by checking the path in the two trees being merged, or is the check performed on the 2nd and 3rd index stage of the merge result (again, these are not equivalent checks)?

I am still curious about this either way.

#85 lore raw
In reply to Elijah Newren via GitGitGadget

Hi Elijah,

Quoted 2 lines · Elijah Newren via GitGitGadget

Note1: Depends on en/remerge-diff (but no need to pick it up; it's still RFC).

For reasons, I will have to backport this on top of v2.33.1 ;-)

Quoted 20 lines

== Updates Log ==

Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments on v2):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.
* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments

Quoted 8 lines

* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)
* Rebased on top of en/remerge-diff to avoid a small conflict

I am really happy with the way this patch series is going, and hope to be a lot more active on it in the near future.

I've read through the current iteration and left a few suggestions, nothing major.

Thank you so much for your outstanding work!
Dscho

#86 lore raw

v2 cover In-core git merge-tree ("Server side merges")

In reply to Elijah Newren via GitGitGadget

Note: Depends on en/remerge-diff

(Note2: This is a continuation of my series at [0], but I can't change the base repository for a pull request in GitHub so I had to open a new PR and that makes it look like a new series. [0]
https://lore.kernel.org/git/[email protected]/)

== Basic Summary ==

This series introduces a new mode to git merge-tree allowing it to perform real merges (three-way text content merges, recursive ancestor consolidation, rename detection, proper directory/file conflict handling, etc.) and write the result as a toplevel tree. It doesn't touch the working tree or index, and doesn't create any commits or update any refs.

== Updates Log ==

Stuff NOT included that reviewers brought up in the last round:

* Very generic (mode, oid, stage, filename) printing formatting[1]
* Always printing 3 stages for each filename with conflicts[2] [1]
https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/
[2]
https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/

Updates since v3 (which looks like v1 due to the re-submit; thanks to René, Ævar, Christian, Dscho for very helpful feedback):

* New patch from Dscho allowing diff_warn_rename_limit() to print somewhere other than stdout (I hope he's okay with me including his Signed-off-by)
* Now prints filenames relative to prefix, much like ls-files
* Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave it a -l shorthand; I'm wondering if I should just drop this option, though.
* And numerous cleanups, in lots of areas:
* Multiple parse-options cleanups
* Lots of commit message cleanups
* Wording tweaks to the "Description" section of the manual
* Several small code cleanups
* I dropped the RFC label

Updates since v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments on v2):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.
* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments
* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)
* Rebased on top of en/remerge-diff to avoid a small conflict

Updates since v1 (thanks to Johannes Altmanninger and Fabian for suggestions on v1):

* Fixed a bad patch splitting, and a style issue pointed out by Johannes Altimanninger
* Fixed misleading commit messages in new test cases
* Fixed my comments about how commit-tree could be used to correctly use two -p flags

Elijah Newren (12):
merge-tree: rename merge_trees() to trivial_merge_trees()
merge-tree: move logic for existing merge into new function
merge-tree: add option parsing and initial shell for real merge
function
merge-tree: implement real merges
merge-ort: split out a separate display_update_messages() function
merge-ort: allow update messages to be written to different file
stream
merge-tree: support including merge messages in output
merge-ort: provide a merge_get_conflicted_files() helper function
merge-tree: provide a list of which files have conflicts
merge-tree: provide easy access to ls-files -u style info
merge-tree: add a --allow-unrelated-histories flag git-merge-tree.txt: add a section on potentional usage mistakes

Johannes Schindelin (1):
diff: allow diff_warn_rename_limit to write somewhere besides stdout

 Documentation/git-merge-tree.txt | 179 ++++++++++++++++++++++++++++---
 builtin/merge-tree.c             | 162 +++++++++++++++++++++++++---
 diff.c                           |  20 ++--
 diff.h                           |   3 +-
 git.c                            |   2 +-
 merge-ort.c                      | 109 ++++++++++++-------
 merge-ort.h                      |  30 ++++++
 merge-recursive.c                |   3 +-
 t/t4301-merge-tree-write-tree.sh | 164 ++++++++++++++++++++++++++++
 9 files changed, 603 insertions(+), 69 deletions(-)
 create mode 100755 t/t4301-merge-tree-write-tree.sh

base-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v2
Pull-Request: https://github.com/gitgitgadget/git/pull/1122

Range-diff vs v1:

  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()
  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function
  3:  65fdae9ddba !  3:  63f42df21ae merge-tree: add option parsing and initial shell for real merge function
     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)
       }
       
      +struct merge_tree_options {
     -+	int real;
     -+	int trivial;
     ++	int mode;
      +};
      +
      +static int real_merge(struct merge_tree_options *o,
     @@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)
      +		NULL
      +	};
      +	struct option mt_options[] = {
     -+		OPT_BOOL(0, "write-tree", &o.real,
     -+			 N_("do a real merge instead of a trivial merge")),
     -+		OPT_BOOL(0, "trivial-merge", &o.trivial,
     -+			 N_("do a trivial merge only")),
     ++		OPT_CMDMODE(0, "write-tree", &o.mode,
     ++			    N_("do a real merge instead of a trivial merge"),
     ++			    'w'),
     ++		OPT_CMDMODE(0, "trivial-merge", &o.mode,
     ++			    N_("do a trivial merge only"), 't'),
      +		OPT_END()
      +	};
      +
     -+	/* Check for a request for basic help */
     -+	if (argc == 2 && !strcmp(argv[1], "-h"))
     -+		usage_with_options(merge_tree_usage, mt_options);
     -+
      +	/* Parse arguments */
      +	argc = parse_options(argc, argv, prefix, mt_options,
     -+			     merge_tree_usage, 0);
     -+	if (o.real && o.trivial)
     -+		die(_("--write-tree and --trivial-merge are incompatible"));
     -+	if (o.real || o.trivial) {
     -+		expected_remaining_argc = (o.real ? 2 : 3);
     ++			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
     ++	if (o.mode) {
     ++		expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
      +		if (argc != expected_remaining_argc)
      +			usage_with_options(merge_tree_usage, mt_options);
      +	} else {
      +		if (argc < 2 || argc > 3)
      +			usage_with_options(merge_tree_usage, mt_options);
     -+		o.real = (argc == 2);
     ++		o.mode = (argc == 2 ? 'w' : 't');
      +	}
      +
      +	/* Do the relevant type of merge */
     -+	if (o.real)
     ++	if (o.mode == 'w')
      +		return real_merge(&o, argv[0], argv[1]);
      +	else
      +		return trivial_merge(argv[0], argv[1], argv[2]);
  4:  05bd17686e1 !  4:  02c29f920d0 merge-tree: implement real merges
     @@ Commit message
      
          The only output is:
            - the toplevel resulting tree printed on stdout
     -      - exit status of 0 (clean) or 1 (conflicts present)
     +      - exit status of 0 (clean), 1 (conflicts present), anything else
     +        (merge could not be performed; unknown if clean or conflicted)
      
          This output is meant to be used by some higher level script, perhaps in
          a sequence of steps like this:
     @@ Commit message
          preliminary implementation, but subsequent commits will add that
          ability.
      
     +    This also marks the traditional trivial merge of merge-tree as
     +    deprecated.  The trivial merge not only had limited applicability, the
     +    output format was also difficult to work with (and its format
     +    undocumented), and will generally be less performant than real merges.
     +
          Signed-off-by: Elijah Newren <[email protected]>
      
       ## Documentation/git-merge-tree.txt ##
     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)
       [verse]
      -'git merge-tree' <base-tree> <branch1> <branch2>
      +'git merge-tree' [--write-tree] <branch1> <branch2>
     -+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>
     ++'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)
       
       DESCRIPTION
       -----------
     @@ Documentation/git-merge-tree.txt: git-merge-tree(1)
      +Performs a merge, but does not make any new commits and does not read
      +from or write to either the working tree or index.
      +
     -+The first form will merge the two branches, doing a full recursive
     -+merge with rename detection.  The rest of this manual (other than the
     -+next paragraph) describes the first form in more detail -- including
     -+options, output format, exit status, and usage notes.
     -+
     -+The second form is deprecated; it is kept for backward compatibility
     -+reasons but may be deleted in the future.  It will only do a trivial
     -+merge.  It reads three tree-ish, and outputs trivial merge results and
     -+conflicting stages to the standard output in a semi-diff format.
     -+Since this was designed for higher level scripts to consume and merge
     -+the results back into the index, it omits entries that match
     -+<branch1>.  The result of this second form is is similar to what
     -+three-way 'git read-tree -m' does, but instead of storing the results
     -+in the index, the command outputs the entries to the standard output.
     -+This form not only has limited applicability, the output format is
     -+also difficult to work with, and it will generally be less performant
     -+than the first form even on successful merges (especially if working
     -+in large repositories).  The remainder of this manual will only
     -+discuss the first form.
     ++The second form is deprecated and supported only for backward
     ++compatibility.  It will likely be removed in the future, and will not
     ++be discussed further in this manual.
     ++
     ++The first form will merge the two branches, doing a real merge.  A real
     ++merge is distinguished from a trivial merge in that it includes:
     ++
     ++  * three way content merges of individual files
     ++  * rename detection
     ++  * proper directory/file conflict handling
     ++  * recursive ancestor consolidation (i.e. when there is more than one
     ++    merge base, creating a virtual merge base by merging the merge bases)
     ++  * etc.
     ++
     ++After the merge completes, it will create a new toplevel tree object.
     ++See `OUTPUT` below for details.
      +
      +OUTPUT
      +------
     @@ builtin/merge-tree.c: struct merge_tree_options {
      +
      +	parent1 = get_merge_parent(branch1);
      +	if (!parent1)
     -+		help_unknown_ref(branch1, "merge",
     ++		help_unknown_ref(branch1, "merge-tree",
      +				 _("not something we can merge"));
      +
      +	parent2 = get_merge_parent(branch2);
      +	if (!parent2)
     -+		help_unknown_ref(branch2, "merge",
     ++		help_unknown_ref(branch2, "merge-tree",
      +				 _("not something we can merge"));
      +
      +	init_merge_options(&opt, the_repository);
     -+	/*
     -+	 * TODO: Support subtree and other -X options?
     -+	if (use_strategies_nr == 1 &&
     -+	    !strcmp(use_strategies[0]->name, "subtree"))
     -+		opt.subtree_shift = "";
     -+	for (x = 0; x < xopts_nr; x++)
     -+		if (parse_merge_opt(&opt, xopts[x]))
     -+			die(_("Unknown strategy option: -X%s"), xopts[x]);
     -+	*/
      +
      +	opt.show_rename_progress = 0;
      +
     -+	opt.branch1 = merge_remote_util(parent1)->name; /* or just branch1? */
     -+	opt.branch2 = merge_remote_util(parent2)->name; /* or just branch2? */
     ++	opt.branch1 = branch1;
     ++	opt.branch2 = branch2;
      +
      +	/*
      +	 * Get the merge bases, in reverse order; see comment above
     @@ builtin/merge-tree.c: struct merge_tree_options {
      +		commit_list_insert(j->item, &merge_bases);
      +
      +	merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
     -+	printf("%s\n", oid_to_hex(&result.tree->object.oid));
      +	if (result.clean < 0)
      +		die(_("failure to merge"));
     -+	else if (!result.clean)
     -+		printf(_("Conflicts!\n"));
     ++	puts(oid_to_hex(&result.tree->object.oid));
      +	merge_finalize(&opt, &result);
      +	return !result.clean; /* result.clean < 0 handled above */
       }
       
       int cmd_merge_tree(int argc, const char **argv, const char *prefix)
      
     - ## t/t4301-merge-tree-real.sh (new) ##
     + ## t/t4301-merge-tree-write-tree.sh (new) ##
      @@
      +#!/bin/sh
      +
     @@ t/t4301-merge-tree-real.sh (new)
      +. ./test-lib.sh
      +
      +# This test is ort-specific
     -+test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort || {
     ++if test "${GIT_TEST_MERGE_ALGORITHM}" != "ort"
     ++then
      +	skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
      +	test_done
     -+}
     ++fi
      +
      +test_expect_success setup '
      +	test_write_lines 1 2 3 4 5 >numbers &&
  -:  ----------- >  5:  6fb4f4580a5 diff: allow diff_warn_rename_limit to write somewhere besides stdout
  5:  095aa266c2b !  6:  28368c03898 merge-ort: split out a separate display_update_messages() function

@@ Metadata
## Commit message ##
merge-ort: split out a separate display_update_messages() function

- No functional changes included in this patch; it's just a preparatory
- step to allow the printed messages to be handled differently by other
- callers, such as in git merge-tree --write-tree.
+ This patch includes no new code; it simply moves a bunch of lines into a
+ new function. As such, there are no functional changes. This is just a
+ preparatory step to allow the printed messages to be handled differently
+ by other callers, such as in git merge-tree --write-tree.
+
+ (Patch best viewed with
+ --color-moved --color-moved-ws=allow-indentation-change
+ to see that it is a simple code movement.)

@@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op
+
+ /* Also include needed rename limit adjustment now */
+ diff_warn_rename_limit("merge.renamelimit",
-+ opti->renames.needed_limit, 0);
++ opti->renames.needed_limit, 0, stdout);
+
+ trace2_region_leave("merge", "display messages", opt->repo);
+}
@@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
-
- /* Also include needed rename limit adjustment now */
- diff_warn_rename_limit("merge.renamelimit",
-- opti->renames.needed_limit, 0);
+- opti->renames.needed_limit, 0, stdout);
-
- trace2_region_leave("merge", "display messages", opt->repo);
- }
6: e3ef17eb46f ! 7: 593d0c00b57 merge-ort: allow update messages to be written to different file stream

     @@ merge-ort.c: void merge_display_update_messages(struct merge_options *opt,
       		struct strbuf *sb = olist.items[i].util;
       
      -		printf("%s", sb->buf);
     -+		fprintf(stream, "%s", sb->buf);
     ++		strbuf_write(sb, stream);
       	}
       	string_list_clear(&olist, 0);
       
     + 	/* Also include needed rename limit adjustment now */
     + 	diff_warn_rename_limit("merge.renamelimit",
     +-			       opti->renames.needed_limit, 0, stdout);
     ++			       opti->renames.needed_limit, 0, stream);
     + 
     + 	trace2_region_leave("merge", "display messages", opt->repo);
     + }
      @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
       	}

7: 2f296aeeefb ! 8: d0d30e92ecd merge-tree: support including merge messages in output
@@ Commit message
When running git merge-tree --write-tree, we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge. Merges also have
- informational messages, ("Auto-merging <PATH>", "CONFLICT (content):
- ...", "CONFLICT (file/directory)", etc.) In fact, when non-content
- conflicts occur (such as file/directory, modify/delete, add/add with
- differing modes, rename/rename (1to2), etc.), these informational
- messages are often the only notification since these conflicts are not
- representable in the contents of the file.
+ informational messages, such as:
+ * "Auto-merging <PATH>"
+ * "CONFLICT (content): ..."
+ * "CONFLICT (file/directory)"
+ * etc.
+ In fact, when non-content conflicts occur (such as file/directory,
+ modify/delete, add/add with differing modes, rename/rename (1to2),
+ etc.), these informational messages may be the only notification the
+ user gets since these conflicts are not representable in the contents
+ of the file.

Add a --[no-]messages option so that callers can request these messages be included at the end of the output. Include such messages by default
@@ Documentation/git-merge-tree.txt: git-merge-tree - Perform merge without touchin
[verse]
-'git merge-tree' [--write-tree] <branch1> <branch2>
+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>
- 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2>
+ 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)

DESCRIPTION
-@@ Documentation/git-merge-tree.txt: than the first form even on successful merges (especially if working
- in large repositories). The remainder of this manual will only
- discuss the first form.
+@@ Documentation/git-merge-tree.txt: merge is distinguished from a trivial merge in that it includes:
+ After the merge completes, it will create a new toplevel tree object.
+ See OUTPUT below for details.

+OPTIONS
+-------
@@ Documentation/git-merge-tree.txt: be used as a part of a series of steps such as

## builtin/merge-tree.c ##
@@ builtin/merge-tree.c: static int trivial_merge(const char *base,
+
struct merge_tree_options {
- int real;
- int trivial;
+ int mode;
+ int show_messages;
};

static int real_merge(struct merge_tree_options *o,
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
- commit_list_insert(j->item, &merge_bases);
-
merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
-- printf("%s\n", oid_to_hex(&result.tree->object.oid));
-+
if (result.clean < 0)
die(_("failure to merge"));
-- else if (!result.clean)
-- printf(_("Conflicts!\n"));
+
+ if (o->show_messages == -1)
+ o->show_messages = !result.clean;
+
-+ printf("%s\n", oid_to_hex(&result.tree->object.oid));
+ puts(oid_to_hex(&result.tree->object.oid));
+ if (o->show_messages) {
+ printf("\n");
+ merge_display_update_messages(&opt, &result, stdout);
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
NULL
};
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
- N_("do a real merge instead of a trivial merge")),
- OPT_BOOL(0, "trivial-merge", &o.trivial,
- N_("do a trivial merge only")),
+ 'w'),
+ OPT_CMDMODE(0, "trivial-merge", &o.mode,
+ N_("do a trivial merge only"), 't'),
+ OPT_BOOL(0, "messages", &o.show_messages,
+ N_("also show informational/conflict messages")),
OPT_END()
};

-@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
- usage_with_options(merge_tree_usage, mt_options);
-
/* Parse arguments */
+ original_argc = argc;
argc = parse_options(argc, argv, prefix, mt_options,
- merge_tree_usage, 0);
- if (o.real && o.trivial)
- die(_("--write-tree and --trivial-merge are incompatible"));
-+ if (!o.real && original_argc < argc)
-+ die(_("--write-tree must be specified if any other options are"));
- if (o.real || o.trivial) {
- expected_remaining_argc = (o.real ? 2 : 3);
- if (argc != expected_remaining_argc)
+ merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+ if (o.mode) {
+@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+ usage_with_options(merge_tree_usage, mt_options);
+ o.mode = (argc == 2 ? 'w' : 't');
+ }
++ if (o.mode == 't' && original_argc < argc)
++ die(_("--trivial-merge is incompatible with all other options"));
+
+ /* Do the relevant type of merge */
+ if (o.mode == 'w')

- ## t/t4301-merge-tree-real.sh ##
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'Barf on too many arguments' '
+ ## t/t4301-merge-tree-write-tree.sh ##
+@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '
grep "^usage: git merge-tree" expect
'

8: 35e0ed9271a ! 9: 9c2334ae9f2 merge-ort: provide a merge_get_conflicted_files() helper function
@@ Commit message
merge-ort: provide a merge_get_conflicted_files() helper function

After a merge, this function allows the user to extract the same
- information that would be printed by ls-files -u -- conflicted
+ information that would be printed by ls-files -u, which means
files with their mode, oid, and stage.

9: fcbb087fa88 ! 10: 243134dc247 merge-tree: provide a list of which files have conflicts
@@ builtin/merge-tree.c: struct merge_tree_options {
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
o->show_messages = !result.clean;

- printf("%s\n", oid_to_hex(&result.tree->object.oid));
+ puts(oid_to_hex(&result.tree->object.oid));
+ if (!result.clean) {
+ struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
+ const char *last = NULL;
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)

/* Do the relevant type of merge */
- if (o.real)
+ if (o.mode == 'w')
- return real_merge(&o, argv[0], argv[1]);
+ return real_merge(&o, argv[0], argv[1], prefix);
else
return trivial_merge(argv[0], argv[1], argv[2]);
}

- ## t/t4301-merge-tree-real.sh ##
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' '
+ ## t/t4301-merge-tree-write-tree.sh ##
+@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '
# "whatever" has *both* a modify/delete and a file/directory conflict
cat <<-EOF >expect &&
HASH
@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such'

Auto-merging greeting
CONFLICT (content): Merge conflict in greeting
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' ' +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '
test_cmp expect actual
'

10: 050add3e498 ! 11: c322e4c6938 merge-tree: provide easy access to ls-files -u style info
@@ Commit message
Much like git merge updates the index with information of the form
(mode, oid, stage, name)
provide this output for conflicted files for merge-tree as well.
- Provide an --exclude-oids-and-modes option for users to exclude the
- mode, oid, and stage and only get the list of conflicted filenames.
+ Provide an --exclude-modes-oids-stages/-l option for users to exclude
+ the mode, oid, and stage and only get the list of conflicted filenames.

## Documentation/git-merge-tree.txt ##
-@@ Documentation/git-merge-tree.txt: discuss the first form. +@@ Documentation/git-merge-tree.txt: See OUTPUT below for details.
OPTIONS
-------

@@ Documentation/git-merge-tree.txt: plumbing commands since the possibility of mer
Part of the linkgit:git[1] suite

## builtin/merge-tree.c ##
-@@ builtin/merge-tree.c: struct merge_tree_options {
- int real;
- int trivial;
+@@ builtin/merge-tree.c: static int trivial_merge(const char *base,
+ struct merge_tree_options {
+ int mode;
int show_messages;
-+ int exclude_oids_and_modes;
++ int exclude_modes_oids_stages;
};

static int real_merge(struct merge_tree_options *o,
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
const char *name = conflicted_files.items[i].string;
- if (last && !strcmp(last, name))
+ struct stage_info *c = conflicted_files.items[i].util;
-+ if (!o->exclude_oids_and_modes)
++ if (!o->exclude_modes_oids_stages)
+ printf("%06o %s %d\t",
+ c->mode, oid_to_hex(&c->oid), c->stage);
+ else if (last && !strcmp(last, name))
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
write_name_quoted_relative(
name, prefix, stdout, line_termination);
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
- N_("do a trivial merge only")),
+ N_("do a trivial merge only"), 't'),
OPT_BOOL(0, "messages", &o.show_messages,
N_("also show informational/conflict messages")),
-+ OPT_BOOL_F(0, "exclude-oids-and-modes",
-+ &o.exclude_oids_and_modes,
-+ N_("list conflicted files without oids and modes"),
++ OPT_BOOL_F('l', "exclude-modes-oids-stages",
++ &o.exclude_modes_oids_stages,
++ N_("list conflicted files without modes/oids/stages"),
+ PARSE_OPT_NONEG),
OPT_END()
};

- ## t/t4301-merge-tree-real.sh ##
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'Content merge and a few conflicts' '
+ ## t/t4301-merge-tree-write-tree.sh ##
+@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Content merge and a few conflicts' '
expected_tree=$(cat .git/AUTO_MERGE) &&

# We will redo the merge, while we are still in a conflicted state!
@@ t/t4301-merge-tree-real.sh: test_expect_success 'Content merge and a few conflic
test_when_finished "git reset --hard" &&

test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'Barf on too many arguments' ' +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Barf on too many arguments' '
'

test_expect_success 'test conflict notices and such' '
- test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
-+ test_expect_code 1 git merge-tree --write-tree --exclude-oids-and-modes side1 side2 >out &&
++ test_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&
sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&

# Expected results:
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'test conflict notices and such' ' +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'test conflict notices and such' '
'

test_expect_success 'Just the conflicted files without the messages' '
- test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
-+ test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-oids-and-modes side1 side2 >out &&
++ test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&
sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&

test_write_lines HASH greeting whatever~side1 >expect &&
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'Just the conflicted files without the messages' ' +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Just the conflicted files without the messages' '
test_cmp expect actual
'

11: ba8a50f03cb ! 12: 25677d5038c merge-tree: add a --allow-unrelated-histories flag
@@ Documentation/git-merge-tree.txt: OPTIONS

## builtin/merge-tree.c ##
@@ builtin/merge-tree.c: static int trivial_merge(const char *base,
+
struct merge_tree_options {
- int real;
- int trivial;
+ int mode;
+ int allow_unrelated_histories;
int show_messages;
- int exclude_oids_and_modes;
+ int exclude_modes_oids_stages;
};
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
* merge_incore_recursive in merge-ort.h
@@ builtin/merge-tree.c: static int real_merge(struct merge_tree_options *o,
for (j = common; j; j = j->next)
commit_list_insert(j->item, &merge_bases);
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char *prefix)
- &o.exclude_oids_and_modes,
- N_("list conflicted files without oids and modes"),
+ &o.exclude_modes_oids_stages,
+ N_("list conflicted files without modes/oids/stages"),
PARSE_OPT_NONEG),
+ OPT_BOOL_F(0, "allow-unrelated-histories",
+ &o.allow_unrelated_histories,
@@ builtin/merge-tree.c: int cmd_merge_tree(int argc, const char **argv, const char
};

- ## t/t4301-merge-tree-real.sh ##
-@@ t/t4301-merge-tree-real.sh: test_expect_success setup '
+ ## t/t4301-merge-tree-write-tree.sh ##
+@@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '
>whatever/empty &&
git add numbers greeting whatever/empty && test_tick &&
@@ t/t4301-merge-tree-real.sh: test_expect_success setup '
'

test_expect_success 'Content merge and a few conflicts' '
-@@ t/t4301-merge-tree-real.sh: test_expect_success 'Check conflicted oids and modes without messages' ' +@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and modes without messages' '
test_cmp conflicted-file-info actual
'

12: 4123209cafc = 13: e7c63425a0e git-merge-tree.txt: add a section on potentional usage mistakes

Signature · 1 line
gitgitgadget
#87 lore raw

PATCH v2 1/13 merge-tree: rename merge_trees() to trivial_merge_trees()

From: Elijah Newren <[email protected]>

merge-recursive.h defined its own merge_trees() function, different than the one found in builtin/merge-tree.c. That was okay in the past, but we want merge-tree to be able to use the merge-ort functions, which will end up including merge-recursive.h. Rename the function found in builtin/merge-tree.c to avoid the conflict.

---
 builtin/merge-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
builtin/merge-tree.c +4 -4
index 5dc94d6f880..06f9eee9f78 100644
@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)
 	merge_result_end = &entry->next;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base);
+static void trivial_merge_trees(struct tree_desc t[3], const char *base);
 
 static const char *explanation(struct merge_list *entry)
 {
@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,
 	buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));
 #undef ENTRY_OID
 
-	merge_trees(t, newbase);
+	trivial_merge_trees(t, newbase);
 
 	free(buf0);
 	free(buf1);
@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
 	return mask;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base)
+static void trivial_merge_trees(struct tree_desc t[3], const char *base)
 {
 	struct traverse_info info;
 
@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
-	merge_trees(t, "");
+	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
 	free(buf3);
Signature · 1 line
gitgitgadget
#88 lore raw

PATCH v2 2/13 merge-tree: move logic for existing merge into new function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

In preparation for adding a non-trivial merge capability to merge-tree, move the existing merge logic for trivial merges into a new function.

---
 builtin/merge-tree.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
builtin/merge-tree.c +8 -4
index 06f9eee9f78..914ec960b7e 100644
@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+static int trivial_merge(int argc, const char **argv)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	if (argc != 4)
-		usage(merge_tree_usage);
-
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	show_result();
 	return 0;
 }
+
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+	if (argc != 4)
+		usage(merge_tree_usage);
+	return trivial_merge(argc, argv);
+}
Signature · 1 line
gitgitgadget
#89 lore raw

PATCH v2 3/13 merge-tree: add option parsing and initial shell for real merge function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Note that real merges differ from trivial merges in that they handle:
- three way content merges
- recursive ancestor consolidation
- renames
- proper directory/file conflict handling
- etc.
Basically all the stuff you'd expect from git merge, just without updating the index and working tree. The initial shell added here does nothing more than die with "real merges are not yet implemented", but that will be fixed in subsequent commits.

---
 builtin/merge-tree.c | 61 +++++++++++++++++++++++++++++++++++++-------
 git.c                |  2 +-
 2 files changed, 53 insertions(+), 10 deletions(-)
builtin/merge-tree.c +52 -9
index 914ec960b7e..e98ec8a9f1d 100644
@@ -3,13 +3,12 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "object-store.h"
+#include "parse-options.h"
 #include "repository.h"
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
 
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-
 struct merge_list {
 	struct merge_list *next;
 	struct merge_list *link;	/* other stages for this object */
@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+			 const char *branch1,
+			 const char *branch2)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	buf1 = get_tree_descriptor(r, t+0, argv[1]);
-	buf2 = get_tree_descriptor(r, t+1, argv[2]);
-	buf3 = get_tree_descriptor(r, t+2, argv[3]);
+	buf1 = get_tree_descriptor(r, t+0, base);
+	buf2 = get_tree_descriptor(r, t+1, branch1);
+	buf3 = get_tree_descriptor(r, t+2, branch2);
 	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
@@ -384,9 +385,51 @@ static int trivial_merge(int argc, const char **argv)
 	return 0;
 }
 
+struct merge_tree_options {
+	int mode;
+};
+
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {
+		OPT_CMDMODE(0, "write-tree", &o.mode,
+			    N_("do a real merge instead of a trivial merge"),
+			    'w'),
+		OPT_CMDMODE(0, "trivial-merge", &o.mode,
+			    N_("do a trivial merge only"), 't'),
+		OPT_END()
+	};
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,
+			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (o.mode) {
+		expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.mode = (argc == 2 ? 'w' : 't');
+	}
+
+	/* Do the relevant type of merge */
+	if (o.mode == 'w')
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +1 -1
index 5ff21be21f3..6090a1289db 100644
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
Signature · 1 line
gitgitgadget
#90 lore raw

Re: [PATCH v2 03/13] merge-tree: add option parsing and initial shell for real merge function

Quoted 17 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +11 -3
+struct merge_tree_options {
+	int mode;
+};
 
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 411

Quoted 10 lines
builtin/merge-tree.c +10 -0
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {
+		OPT_CMDMODE(0, "write-tree", &o.mode,
+			    N_("do a real merge instead of a trivial merge"),
+			    'w'),

Given the length of the second line in the usage[] array, it would make more sense to have "'w')," on the same line to make it match better with the following one.

Quoted 7 lines
builtin/merge-tree.c +7 -0
+		OPT_CMDMODE(0, "trivial-merge", &o.mode,
+			    N_("do a trivial merge only"), 't'),
+		OPT_END()
+	};
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 428

Quoted 10 lines
builtin/merge-tree.c +10 -0
+			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (o.mode) {
+		expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.mode = (argc == 2 ? 'w' : 't');
+	}

We are not planning to have tons of different command modes, but the above looks quite brittle in assuming that 'w' and 't' are the only ones, and not having an easy way to extend the part without major rewrite when the assumption will have to be broken. I wonder:

	switch (o.cmd_mode) {
        default:
		BUG("unexpected cmdmode %c", o.cmd_mode);
	case 0:
		switch (argc) {
		default:
			usage_with_options(merge_tree_usage, mt_options);
		case 2: 
			o.cmd_mode = 'w';
			break;
		case 3:
			o.cmd_mode = 't';
			break;
		}
                expected_remaining_argc = argc;
		break;
	case 'w':
		expected_remaining_argc = 2;
                break;
	case 't':
		expected_remaining_argc = 3;
 		break;
	}

        if (argc != expected_remaining_argc)
		usage_with_options(merge_tree_usage, mt_options);

even though it is a tad longer with more boilerplate, is easlier to manage.

Quoted 11 lines
builtin/merge-tree.c +5 -0
+	/* Do the relevant type of merge */
+	if (o.mode == 'w')
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +0 -0
index 5ff21be21f3..6090a1289db 100644

git.c static struct cmd_struct commands[] = { · line 561

Quoted 5 lines
git.c +1 -1
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },

This affects git_support_parseopt_helper function in the completion script, but it by itself is very unlikely to break existing tests on the completion, as the bit only affects how the command line options are completed, and the command didn't have any command line options to be tested before this series ;-)

Quoted 3 lines
git.c +0 -0
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
#91 lore raw

PATCH v2 4/13 merge-tree: implement real merges

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean), 1 (conflicts present), anything else (merge could not be performed; unknown if clean or conflicted)

This output is meant to be used by some higher level script, perhaps in a sequence of steps like this:

   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
   test $? -eq 0 || die "There were conflicts..."
   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
   git update-ref $BRANCH1 $NEWCOMMIT

Note that higher level scripts may also want to access the conflict/warning messages normally output during a merge, or have quick access to a list of files with conflicts. That is not available in this preliminary implementation, but subsequent commits will add that ability.

This also marks the traditional trivial merge of merge-tree as deprecated. The trivial merge not only had limited applicability, the output format was also difficult to work with (and its format undocumented), and will generally be less performant than real merges.

---
 Documentation/git-merge-tree.txt | 71 +++++++++++++++++++++-----
 builtin/merge-tree.c             | 44 +++++++++++++++-
 t/t4301-merge-tree-write-tree.sh | 88 ++++++++++++++++++++++++++++++++
 3 files changed, 190 insertions(+), 13 deletions(-)
 create mode 100755 t/t4301-merge-tree-write-tree.sh
Documentation/git-merge-tree.txt +59 -12
index 58731c19422..569485815a0 100644
@@ -3,26 +3,73 @@ git-merge-tree(1)
 
 NAME
 ----
-git-merge-tree - Show three-way merge without touching index
+git-merge-tree - Perform merge without touching index or working tree
 
 
 SYNOPSIS
 --------
 [verse]
-'git merge-tree' <base-tree> <branch1> <branch2>
+'git merge-tree' [--write-tree] <branch1> <branch2>
+'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)
 
 DESCRIPTION
 -----------
-Reads three tree-ish, and output trivial merge results and
-conflicting stages to the standard output.  This is similar to
-what three-way 'git read-tree -m' does, but instead of storing the
-results in the index, the command outputs the entries to the
-standard output.
-
-This is meant to be used by higher level scripts to compute
-merge results outside of the index, and stuff the results back into the
-index.  For this reason, the output from the command omits
-entries that match the <branch1> tree.
+
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The second form is deprecated and supported only for backward
+compatibility.  It will likely be removed in the future, and will not
+be discussed further in this manual.
+
+The first form will merge the two branches, doing a real merge.  A real
+merge is distinguished from a trivial merge in that it includes:
+
+  * three way content merges of individual files
+  * rename detection
+  * proper directory/file conflict handling
+  * recursive ancestor consolidation (i.e. when there is more than one
+    merge base, creating a virtual merge base by merging the merge bases)
+  * etc.
+
+After the merge completes, it will create a new toplevel tree object.
+See `OUTPUT` below for details.
+
+OUTPUT
+------
+
+For either a successful or conflicted merge, the output from
+git-merge-tree is simply one line:
+
+	<OID of toplevel tree>
+
+The printed tree object corresponds to what would be checked out in
+the working tree at the end of `git merge`, and thus may have files
+with conflict markers in them.
+
+EXIT STATUS
+-----------
+
+For a successful, non-conflicted merge, the exit status is 0.  When the
+merge has conflicts, the exit status is 1.  If the merge is not able to
+complete (or start) due to some kind of error, the exit status is
+something other than 0 or 1.
+
+USAGE NOTES
+-----------
+
+git-merge-tree was written to be low-level plumbing, similar to
+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could
+be used as a part of a series of steps such as
+
+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
+       test $? -eq 0 || die "There were conflicts..."
+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
+       git update-ref $BRANCH1 $NEWCOMMIT
+
+However, it does not quite fit into the same category of low-level
+plumbing commands since the possibility of merge conflicts give it a
+much higher chance of the command not succeeding.
 
 GIT
 ---
builtin/merge-tree.c +43 -1

56 lines not on this page · raw message

t/t4301-merge-tree-write-tree.sh new file +88 -0

88 lines not on this page · raw message

Signature · 1 line
gitgitgadget
#92 lore raw

Re: [PATCH v2 04/13] merge-tree: implement real merges

Quoted 13 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean), 1 (conflicts present), anything else (merge could not be performed; unknown if clean or conflicted)

Quoted 7 lines

This output is meant to be used by some higher level script, perhaps in a sequence of steps like this:

   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
   test $? -eq 0 || die "There were conflicts..."
   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
   git update-ref $BRANCH1 $NEWCOMMIT

It is unclear what NEWTREE has, if anything meaningful, when the command exited with non-zero status. Let's read on.

Documentation/git-merge-tree.txt git-merge-tree(1) · line 6

Quoted 10 lines
Documentation/git-merge-tree.txt +1 -1
index 58731c19422..569485815a0 100644
@@ -3,26 +3,73 @@ git-merge-tree(1)
 
 NAME
 ----
-git-merge-tree - Show three-way merge without touching index
+git-merge-tree - Perform merge without touching index or working tree

OK. It is interesting that both apply equally to either command mode ;-)

Documentation/git-merge-tree.txt git-merge-tree(1) · line 23

Quoted 6 lines
Documentation/git-merge-tree.txt +6 -0
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The second form is deprecated and supported only for backward
+compatibility.  It will likely be removed in the future, and will not
+be discussed further in this manual.

This, especially the deletion of the original description on what trivial merge does, may be premature, especially if it is still "supported for backward compatibility".

Documentation/git-merge-tree.txt git-merge-tree(1) · line 26

Quoted 2 lines
Documentation/git-merge-tree.txt +2 -0
+The first form will merge the two branches, doing a real merge.  A real
+merge is distinguished from a trivial merge in that it includes:

I think this is worth keeping, simply because I do not think you should entirely omit description on the trivial one.

But if we were to remove the description on the trivial one, then it is totally meaningless to label the following as "... distinguished from a trivial merge in that". The list of things the real merge command mode does (below) is of course worth having.

Quoted 11 lines
Documentation/git-merge-tree.txt +11 -0
+  * three way content merges of individual files
+  * rename detection
+  * proper directory/file conflict handling
+  * recursive ancestor consolidation (i.e. when there is more than one
+    merge base, creating a virtual merge base by merging the merge bases)
+  * etc.
+
+After the merge completes, it will create a new toplevel tree object.
+See `OUTPUT` below for details.
+
+OUTPUT

Documentation/git-merge-tree.txt git-merge-tree(1) · line 48

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+------
+
+For either a successful or conflicted merge, the output from
+git-merge-tree is simply one line:
+
+	<OID of toplevel tree>
+
+The printed tree object corresponds to what would be checked out in
+the working tree at the end of `git merge`, and thus may have files
+with conflict markers in them.

So we would leave cruft in the object store, but that is very much on purpose, and the expectation is that the user would commit-tree the tree object and reference it with a ref soon enough before garbage collection prunes them, just like how write-tree is meant to be used. OK.

Documentation/git-merge-tree.txt git-merge-tree(1) · line 56

Quoted 7 lines
Documentation/git-merge-tree.txt +7 -0
+EXIT STATUS
+-----------
+
+For a successful, non-conflicted merge, the exit status is 0.  When the
+merge has conflicts, the exit status is 1.  If the merge is not able to
+complete (or start) due to some kind of error, the exit status is
+something other than 0 or 1.

And the output given to the standard output stream, when the command exits with status higher than 1, is...? "unspecified" is of course an acceptable answer, of course, but I am wondering if it is worth spelling out in the doc.

Documentation/git-merge-tree.txt git-merge-tree(1) · line 62

Quoted 5 lines
Documentation/git-merge-tree.txt +5 -0
+USAGE NOTES
+-----------
+
+git-merge-tree was written to be low-level plumbing, similar to
+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could

A notable omission in the above list is 'write-tree'.

Documentation/git-merge-tree.txt git-merge-tree(1) · line 72

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+be used as a part of a series of steps such as
+
+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
+       test $? -eq 0 || die "There were conflicts..."
+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
+       git update-ref $BRANCH1 $NEWCOMMIT
+
+However, it does not quite fit into the same category of low-level
+plumbing commands since the possibility of merge conflicts give it a
+much higher chance of the command not succeeding.

I am not sure if that is a fair categorization. It is a fine building block at the lowest level of the tool hierarchy. The primary thing that differentiates plumbing from Porcelain is that the former is a better fit for scripting: doing one thing and one thing well with minimum and stable UI. The complexity of that one thing it does has much less to do with the categorization, and the rate at which users may use the command in a failure-inducing situation has nothing to do with it. The write-tree plumbing command will reliably fail with 100% chance when the index used as its input is unmerged.

Quoted 36 lines
Documentation/git-merge-tree.txt +31 -1
@@ -392,7 +395,46 @@ struct merge_tree_options {
 static int real_merge(struct merge_tree_options *o,
 		      const char *branch1, const char *branch2)
 {
-	die(_("real merges are not yet implemented"));
+	struct commit *parent1, *parent2;
+	struct commit_list *common;
+	struct commit_list *merge_bases = NULL;
+	struct commit_list *j;
+	struct merge_options opt;
+	struct merge_result result = { 0 };
+
+	parent1 = get_merge_parent(branch1);
+	if (!parent1)
+		help_unknown_ref(branch1, "merge-tree",
+				 _("not something we can merge"));
+
+	parent2 = get_merge_parent(branch2);
+	if (!parent2)
+		help_unknown_ref(branch2, "merge-tree",
+				 _("not something we can merge"));
+
+	init_merge_options(&opt, the_repository);
+
+	opt.show_rename_progress = 0;
+
+	opt.branch1 = branch1;
+	opt.branch2 = branch2;
+
+	/*
+	 * Get the merge bases, in reverse order; see comment above
+	 * merge_incore_recursive in merge-ort.h
+	 */
+	common = get_merge_bases(parent1, parent2);
+	if (!common)
+		die(_("refusing to merge unrelated histories"));

Documentation/git-merge-tree.txt struct merge_tree_options { · line 438

Quoted 10 lines
Documentation/git-merge-tree.txt +9 -0
+	for (j = common; j; j = j->next)
+		commit_list_insert(j->item, &merge_bases);
+
+	merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
+	if (result.clean < 0)
+		die(_("failure to merge"));
+	puts(oid_to_hex(&result.tree->object.oid));
+	merge_finalize(&opt, &result);
+	return !result.clean; /* result.clean < 0 handled above */
 }

The implementation is rather straight-forward, if you know how to drive the merge_incore_recursive() helper ;-)

Quoted 50 lines
Documentation/git-merge-tree.txt +50 -0
+test_expect_success setup '
+	test_write_lines 1 2 3 4 5 >numbers &&
+	echo hello >greeting &&
+	echo foo >whatever &&
+	git add numbers greeting whatever &&
+	test_tick &&
+	git commit -m initial &&
+
+	git branch side1 &&
+	git branch side2 &&
+
+	git checkout side1 &&
+	test_write_lines 1 2 3 4 5 6 >numbers &&
+	echo hi >greeting &&
+	echo bar >whatever &&
+	git add numbers greeting whatever &&
+	test_tick &&
+	git commit -m modify-stuff &&
+
+	git checkout side2 &&
+	test_write_lines 0 1 2 3 4 5 >numbers &&
+	echo yo >greeting &&
+	git rm whatever &&
+	mkdir whatever &&
+	>whatever/empty &&
+	git add numbers greeting whatever/empty &&
+	test_tick &&
+	git commit -m other-modifications
+'
+
+test_expect_success 'Content merge and a few conflicts' '
+	git checkout side1^0 &&
+	test_must_fail git merge side2 &&
+	expected_tree=$(cat .git/AUTO_MERGE) &&
+
+	# We will redo the merge, while we are still in a conflicted state!
+	test_when_finished "git reset --hard" &&
+
+	test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
+	actual_tree=$(head -n 1 RESULT) &&
+
+	# Due to differences of e.g. "HEAD" vs "side1", the results will not
+	# exactly match.  Dig into individual files.
+
+	# Numbers should have three-way merged cleanly
+	test_write_lines 0 1 2 3 4 5 6 >expect &&
+	git show ${actual_tree}:numbers >actual &&
+	test_cmp expect actual &&
+
+	# whatever and whatever~<branch> should have same HASHES

Documentation/git-merge-tree.txt struct merge_tree_options {

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+	git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&
+	git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&
+	test_cmp expect actual &&
+
+	# greeting should have a merge conflict
+	git show ${expected_tree}:greeting >tmp &&
+	cat tmp | sed -e s/HEAD/side1/ >expect &&
+	git show ${actual_tree}:greeting >actual &&
+	test_cmp expect actual
+'

It is somewhat sad that we need to reivent merge test cases over and over, instead of easily reuse an existing one by replacing

	git checkout one &&
	git merge two

with

	git checkout one &&
	T=$(git merge-tree HEAD two) &&
	C=$(git commit-tree $T -p HEAD -p two) &&
	git reset --hard $C

;-)

Quoted 12 lines

+> +test_expect_success 'Barf on misspelled option, with exit code other than 0 or 1' '

Documentation/git-merge-tree.txt +11 -0
+	# Mis-spell with single "s" instead of double "s"
+	test_expect_code 129 git merge-tree --write-tree --mesages FOOBAR side1 side2 2>expect &&
+
+	grep "error: unknown option.*mesages" expect
+'
+
+test_expect_success 'Barf on too many arguments' '
+	test_expect_code 129 git merge-tree --write-tree side1 side2 side3 2>expect &&
+
+	grep "^usage: git merge-tree" expect
+'
#93 lore raw

Re: [PATCH v2 04/13] merge-tree: implement real merges

Quoted 41 lines · Junio C Hamano
Quoted 13 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This adds the ability to perform real merges rather than just trivial merges (meaning handling three way content merges, recursive ancestor consolidation, renames, proper directory/file conflict handling, and so forth). However, unlike git merge, the working tree and index are left alone and no branch is updated.

The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean), 1 (conflicts present), anything else (merge could not be performed; unknown if clean or conflicted)

Quoted 7 lines

This output is meant to be used by some higher level script, perhaps in a sequence of steps like this:

   NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
   test $? -eq 0 || die "There were conflicts..."
   NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
   git update-ref $BRANCH1 $NEWCOMMIT

It is unclear what NEWTREE has, if anything meaningful, when the command exited with non-zero status. Let's read on.

Documentation/git-merge-tree.txt git-merge-tree(1) · line 6

Quoted 10 lines
Documentation/git-merge-tree.txt +1 -1
index 58731c19422..569485815a0 100644
@@ -3,26 +3,73 @@ git-merge-tree(1)
 
 NAME
 ----
-git-merge-tree - Show three-way merge without touching index
+git-merge-tree - Perform merge without touching index or working tree

OK. It is interesting that both apply equally to either command mode ;-)

Quoted 10 lines

Documentation/git-merge-tree.txt git-merge-tree(1) · line 23

Quoted 6 lines
Documentation/git-merge-tree.txt +6 -0
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The second form is deprecated and supported only for backward
+compatibility.  It will likely be removed in the future, and will not
+be discussed further in this manual.

This, especially the deletion of the original description on what trivial merge does, may be premature, especially if it is still "supported for backward compatibility".

I actually extended it, but Dscho suggested removing it entirely --
https://lore.kernel.org/git/[email protected]/.
I can restore it; does that paragraph look good to you (you can see the full thing even if it's split by Dscho's commentary).

Quoted 39 lines

Documentation/git-merge-tree.txt git-merge-tree(1) · line 26

Quoted 2 lines
Documentation/git-merge-tree.txt +2 -0
+The first form will merge the two branches, doing a real merge.  A real
+merge is distinguished from a trivial merge in that it includes:

I think this is worth keeping, simply because I do not think you should entirely omit description on the trivial one.

But if we were to remove the description on the trivial one, then it is totally meaningless to label the following as "... distinguished from a trivial merge in that". The list of things the real merge command mode does (below) is of course worth having.

Quoted 11 lines
Documentation/git-merge-tree.txt +11 -0
+  * three way content merges of individual files
+  * rename detection
+  * proper directory/file conflict handling
+  * recursive ancestor consolidation (i.e. when there is more than one
+    merge base, creating a virtual merge base by merging the merge bases)
+  * etc.
+
+After the merge completes, it will create a new toplevel tree object.
+See `OUTPUT` below for details.
+
+OUTPUT

Documentation/git-merge-tree.txt git-merge-tree(1) · line 48

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+------
+
+For either a successful or conflicted merge, the output from
+git-merge-tree is simply one line:
+
+     <OID of toplevel tree>
+
+The printed tree object corresponds to what would be checked out in
+the working tree at the end of `git merge`, and thus may have files
+with conflict markers in them.

So we would leave cruft in the object store, but that is very much on purpose, and the expectation is that the user would commit-tree the tree object and reference it with a ref soon enough before garbage collection prunes them, just like how write-tree is meant to be used. OK.

Quoted 12 lines

Documentation/git-merge-tree.txt git-merge-tree(1) · line 56

Quoted 7 lines
Documentation/git-merge-tree.txt +7 -0
+EXIT STATUS
+-----------
+
+For a successful, non-conflicted merge, the exit status is 0.  When the
+merge has conflicts, the exit status is 1.  If the merge is not able to
+complete (or start) due to some kind of error, the exit status is
+something other than 0 or 1.

And the output given to the standard output stream, when the command exits with status higher than 1, is...? "unspecified" is of course an acceptable answer, of course, but I am wondering if it is worth spelling out in the doc.

Yeah, "unspecified" -- whatever random error the user triggered (couldn't parse arguments, the refs you gave don't exist, the repo you are running from isn't a repo, disk is full, etc.)

Quoted 7 lines

Documentation/git-merge-tree.txt git-merge-tree(1) · line 62

Quoted 5 lines
Documentation/git-merge-tree.txt +5 -0
+USAGE NOTES
+-----------
+
+git-merge-tree was written to be low-level plumbing, similar to
+hash-object, mktree, commit-tree, update-ref, and mktag.  Thus, it could

A notable omission in the above list is 'write-tree'.

Ooh, yeah, I should add that one.

Quoted 63 lines

Documentation/git-merge-tree.txt git-merge-tree(1) · line 72

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+be used as a part of a series of steps such as
+
+       NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)
+       test $? -eq 0 || die "There were conflicts..."
+       NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2)
+       git update-ref $BRANCH1 $NEWCOMMIT
+
+However, it does not quite fit into the same category of low-level
+plumbing commands since the possibility of merge conflicts give it a
+much higher chance of the command not succeeding.

I am not sure if that is a fair categorization. It is a fine building block at the lowest level of the tool hierarchy. The primary thing that differentiates plumbing from Porcelain is that the former is a better fit for scripting: doing one thing and one thing well with minimum and stable UI. The complexity of that one thing it does has much less to do with the categorization, and the rate at which users may use the command in a failure-inducing situation has nothing to do with it. The write-tree plumbing command will reliably fail with 100% chance when the index used as its input is unmerged.

Quoted 41 lines
Documentation/git-merge-tree.txt +36 -1
@@ -392,7 +395,46 @@ struct merge_tree_options {
 static int real_merge(struct merge_tree_options *o,
                    const char *branch1, const char *branch2)
 {
-     die(_("real merges are not yet implemented"));
+     struct commit *parent1, *parent2;
+     struct commit_list *common;
+     struct commit_list *merge_bases = NULL;
+     struct commit_list *j;
+     struct merge_options opt;
+     struct merge_result result = { 0 };
+
+     parent1 = get_merge_parent(branch1);
+     if (!parent1)
+             help_unknown_ref(branch1, "merge-tree",
+                              _("not something we can merge"));
+
+     parent2 = get_merge_parent(branch2);
+     if (!parent2)
+             help_unknown_ref(branch2, "merge-tree",
+                              _("not something we can merge"));
+
+     init_merge_options(&opt, the_repository);
+
+     opt.show_rename_progress = 0;
+
+     opt.branch1 = branch1;
+     opt.branch2 = branch2;
+
+     /*
+      * Get the merge bases, in reverse order; see comment above
+      * merge_incore_recursive in merge-ort.h
+      */
+     common = get_merge_bases(parent1, parent2);
+     if (!common)
+             die(_("refusing to merge unrelated histories"));
+     for (j = common; j; j = j->next)
+             commit_list_insert(j->item, &merge_bases);
+
+     merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
+     if (result.clean < 0)
Quoted 8 lines

Documentation/git-merge-tree.txt struct merge_tree_options { · line 438

Quoted 5 lines
Documentation/git-merge-tree.txt +4 -0
+             die(_("failure to merge"));
+     puts(oid_to_hex(&result.tree->object.oid));
+     merge_finalize(&opt, &result);
+     return !result.clean; /* result.clean < 0 handled above */
 }

The implementation is rather straight-forward, if you know how to drive the merge_incore_recursive() helper ;-)

:-)

Quoted 68 lines
Quoted 51 lines
Documentation/git-merge-tree.txt +50 -0
+test_expect_success setup '
+     test_write_lines 1 2 3 4 5 >numbers &&
+     echo hello >greeting &&
+     echo foo >whatever &&
+     git add numbers greeting whatever &&
+     test_tick &&
+     git commit -m initial &&
+
+     git branch side1 &&
+     git branch side2 &&
+
+     git checkout side1 &&
+     test_write_lines 1 2 3 4 5 6 >numbers &&
+     echo hi >greeting &&
+     echo bar >whatever &&
+     git add numbers greeting whatever &&
+     test_tick &&
+     git commit -m modify-stuff &&
+
+     git checkout side2 &&
+     test_write_lines 0 1 2 3 4 5 >numbers &&
+     echo yo >greeting &&
+     git rm whatever &&
+     mkdir whatever &&
+     >whatever/empty &&
+     git add numbers greeting whatever/empty &&
+     test_tick &&
+     git commit -m other-modifications
+'
+
+test_expect_success 'Content merge and a few conflicts' '
+     git checkout side1^0 &&
+     test_must_fail git merge side2 &&
+     expected_tree=$(cat .git/AUTO_MERGE) &&
+
+     # We will redo the merge, while we are still in a conflicted state!
+     test_when_finished "git reset --hard" &&
+
+     test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
+     actual_tree=$(head -n 1 RESULT) &&
+
+     # Due to differences of e.g. "HEAD" vs "side1", the results will not
+     # exactly match.  Dig into individual files.
+
+     # Numbers should have three-way merged cleanly
+     test_write_lines 0 1 2 3 4 5 6 >expect &&
+     git show ${actual_tree}:numbers >actual &&
+     test_cmp expect actual &&
+
+     # whatever and whatever~<branch> should have same HASHES

Documentation/git-merge-tree.txt struct merge_tree_options {

Quoted 10 lines
Documentation/git-merge-tree.txt +10 -0
+     git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&
+     git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&
+     test_cmp expect actual &&
+
+     # greeting should have a merge conflict
+     git show ${expected_tree}:greeting >tmp &&
+     cat tmp | sed -e s/HEAD/side1/ >expect &&
+     git show ${actual_tree}:greeting >actual &&
+     test_cmp expect actual
+'

It is somewhat sad that we need to reivent merge test cases over and over, instead of easily reuse an existing one by replacing

git checkout one &&
git merge two

Quoted 8 lines

with

        git checkout one &&
        T=$(git merge-tree HEAD two) &&
        C=$(git commit-tree $T -p HEAD -p two) &&
        git reset --hard $C

;-)

Sorry...I'm afraid I'm not following.

#94 lore raw

Re: [PATCH v2 04/13] merge-tree: implement real merges

Hi,

Quoted 15 lines · Elijah Newren
Quoted 13 lines · Junio C Hamano

Documentation/git-merge-tree.txt git-merge-tree(1) · line 23

Quoted 6 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +6 -0
+Performs a merge, but does not make any new commits and does not read
+from or write to either the working tree or index.
+
+The second form is deprecated and supported only for backward
+compatibility.  It will likely be removed in the future, and will not
+be discussed further in this manual.

This, especially the deletion of the original description on what trivial merge does, may be premature, especially if it is still "supported for backward compatibility".

Quoted 4 lines

I actually extended it, but Dscho suggested removing it entirely --
https://lore.kernel.org/git/[email protected]/.
I can restore it; does that paragraph look good to you (you can see the full thing even if it's split by Dscho's commentary).

I do not necessarily feel strong about keeping the paragraph, but I would appreciate a better rationale for that than "because it was there previously".

At this point there cannot be any doubt that the "trivial mode" is worth deprecating and getting rid off because it has been in little or no use.

I mean, elsewhere we are overzealously removing stuff "because _core Git_ does not use it", and here we keep something for... reasons?

Quoted 39 lines
Quoted 39 lines
Quoted 20 lines
t/t4301-merge-tree-write-tree.sh new file +20 -0
+test_expect_success 'Content merge and a few conflicts' '
+     git checkout side1^0 &&
+     test_must_fail git merge side2 &&
+     expected_tree=$(cat .git/AUTO_MERGE) &&
+
+     # We will redo the merge, while we are still in a conflicted state!
+     test_when_finished "git reset --hard" &&
+
+     test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
+     actual_tree=$(head -n 1 RESULT) &&
+
+     # Due to differences of e.g. "HEAD" vs "side1", the results will not
+     # exactly match.  Dig into individual files.
+
+     # Numbers should have three-way merged cleanly
+     test_write_lines 0 1 2 3 4 5 6 >expect &&
+     git show ${actual_tree}:numbers >actual &&
+     test_cmp expect actual &&
+
+     # whatever and whatever~<branch> should have same HASHES

t/t4301-merge-tree-write-tree.sh line 73

Quoted 10 lines
t/t4301-merge-tree-write-tree.sh new file +10 -0
+     git rev-parse ${expected_tree}:whatever ${expected_tree}:whatever~HEAD >expect &&
+     git rev-parse ${actual_tree}:whatever ${actual_tree}:whatever~side1 >actual &&
+     test_cmp expect actual &&
+
+     # greeting should have a merge conflict
+     git show ${expected_tree}:greeting >tmp &&
+     cat tmp | sed -e s/HEAD/side1/ >expect &&
+     git show ${actual_tree}:greeting >actual &&
+     test_cmp expect actual
+'

It is somewhat sad that we need to reivent merge test cases over and over, instead of easily reuse an existing one by replacing

git checkout one &&
git merge two

with

Quoted 8 lines
Quoted 6 lines
        git checkout one &&
        T=$(git merge-tree HEAD two) &&
        C=$(git commit-tree $T -p HEAD -p two) &&
        git reset --hard $C

;-)

Sorry...I'm afraid I'm not following.

I cannot speak for Junio (nor do I want to). Having said that, I interpret the quoted section as a lament on the somewhat repetitive complexity that is required by our tests merely to set up a scenario where we can run the actual command we want to test.

If my interpretation is correct, then it is probably fair to suggest using test_merge in a setup-type of test case (because that not only runs git merge but also tags the result), and then use git reset --hard <tag> in subsequent test cases instead of re-running the checkout && merge dance.

Ciao,
Dscho

#95 lore raw

PATCH v2 5/13 diff: allow diff_warn_rename_limit to write somewhere besides stdout

In reply to Elijah Newren via GitGitGadget

From: Johannes Schindelin <[email protected]>

diff_warn_rename_limit() is hardcoded to write to stdout. Make it accept an output location parameter to make it more flexible.

---
 diff.c            | 20 ++++++++++++++------
 diff.h            |  3 ++-
 merge-ort.c       |  2 +-
 merge-recursive.c |  3 ++-
 4 files changed, 19 insertions(+), 9 deletions(-)
diff.c +14 -6
index 1bfb01c18ec..6952035046f 100644
@@ -6377,17 +6377,25 @@ static const char rename_limit_advice[] =
 N_("you may want to set your %s variable to at least "
    "%d and retry the command.");
 
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc)
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+			    FILE *out)
 {
-	fflush(stdout);
+	const char *fmt = NULL;
+
 	if (degraded_cc)
-		warning(_(degrade_cc_to_c_warning));
+		fmt = _(degrade_cc_to_c_warning);
 	else if (needed)
-		warning(_(rename_limit_warning));
+		fmt = _(rename_limit_warning);
 	else
 		return;
 	if (0 < needed)
-		warning(_(rename_limit_advice), varname, needed);
+		fmt = _(rename_limit_advice);
+
+	fflush(out);
+	if (out == stdout)
+		warning(fmt, varname, needed);
+	else
+		fprintf(out, fmt, varname, needed);
 }
 
 static void create_filepairs_for_header_only_notifications(struct diff_options *o)
@@ -6870,7 +6878,7 @@ int diff_result_code(struct diff_options *opt, int status)
 
 	diff_warn_rename_limit("diff.renameLimit",
 			       opt->needed_rename_limit,
-			       opt->degraded_cc_to_c);
+			       opt->degraded_cc_to_c, stdout);
 	if (!opt->flags.exit_with_status &&
 	    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
 		return status;
diff.h +2 -1
index ce9e2cf2e4f..40c5b78fb0a 100644
@@ -597,7 +597,8 @@ void diffcore_fix_diff_index(void);
 int diff_queue_is_empty(struct diff_options *o);
 void diff_flush(struct diff_options*);
 void diff_free(struct diff_options*);
-void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc);
+void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
+			    FILE *out);
 
 /* diff-raw status letters */
 #define DIFF_STATUS_ADDED		'A'
merge-ort.c +1 -1
index 9bf15a01db8..65618048b59 100644
@@ -4305,7 +4305,7 @@ void merge_switch_to_result(struct merge_options *opt,
 
 		/* Also include needed rename limit adjustment now */
 		diff_warn_rename_limit("merge.renamelimit",
-				       opti->renames.needed_limit, 0);
+				       opti->renames.needed_limit, 0, stdout);
 
 		trace2_region_leave("merge", "display messages", opt->repo);
 	}
merge-recursive.c +2 -1
index 9ec1e6d043a..d2eeca9fa20 100644
@@ -3738,7 +3738,8 @@ static void merge_finalize(struct merge_options *opt)
 		strbuf_release(&opt->obuf);
 	if (show(opt, 2))
 		diff_warn_rename_limit("merge.renamelimit",
-				       opt->priv->needed_rename_limit, 0);
+				       opt->priv->needed_rename_limit, 0,
+				       stdout);
 	FREE_AND_NULL(opt->priv);
 }
 
Signature · 1 line
gitgitgadget
#96 lore raw

PATCH v2 6/13 merge-ort: split out a separate display_update_messages() function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This patch includes no new code; it simply moves a bunch of lines into a new function. As such, there are no functional changes. This is just a preparatory step to allow the printed messages to be handled differently by other callers, such as in git merge-tree --write-tree.

(Patch best viewed with

     --color-moved --color-moved-ws=allow-indentation-change

to see that it is a simple code movement.)

---
 merge-ort.c | 77 ++++++++++++++++++++++++++++-------------------------
 merge-ort.h |  8 ++++++
 2 files changed, 49 insertions(+), 36 deletions(-)
merge-ort.c +41 -36
index 65618048b59..1ada3198390 100644
@@ -4235,6 +4235,45 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 	return errs;
 }
 
+void merge_display_update_messages(struct merge_options *opt,
+				   struct merge_result *result)
+{
+	struct merge_options_internal *opti = result->priv;
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+	struct string_list olist = STRING_LIST_INIT_NODUP;
+	int i;
+
+	if (opt->record_conflict_msgs_as_headers)
+		BUG("Either display conflict messages or record them as headers, not both");
+
+	trace2_region_enter("merge", "display messages", opt->repo);
+
+	/* Hack to pre-allocate olist to the desired size */
+	ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
+		   olist.alloc);
+
+	/* Put every entry from output into olist, then sort */
+	strmap_for_each_entry(&opti->output, &iter, e) {
+		string_list_append(&olist, e->key)->util = e->value;
+	}
+	string_list_sort(&olist);
+
+	/* Iterate over the items, printing them */
+	for (i = 0; i < olist.nr; ++i) {
+		struct strbuf *sb = olist.items[i].util;
+
+		printf("%s", sb->buf);
+	}
+	string_list_clear(&olist, 0);
+
+	/* Also include needed rename limit adjustment now */
+	diff_warn_rename_limit("merge.renamelimit",
+			       opti->renames.needed_limit, 0, stdout);
+
+	trace2_region_leave("merge", "display messages", opt->repo);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
 			    struct tree *head,
 			    struct merge_result *result,
@@ -4273,42 +4312,8 @@ void merge_switch_to_result(struct merge_options *opt,
 		trace2_region_leave("merge", "write_auto_merge", opt->repo);
 	}
 
-	if (display_update_msgs) {
-		struct merge_options_internal *opti = result->priv;
-		struct hashmap_iter iter;
-		struct strmap_entry *e;
-		struct string_list olist = STRING_LIST_INIT_NODUP;
-		int i;
-
-		if (opt->record_conflict_msgs_as_headers)
-			BUG("Either display conflict messages or record them as headers, not both");
-
-		trace2_region_enter("merge", "display messages", opt->repo);
-
-		/* Hack to pre-allocate olist to the desired size */
-		ALLOC_GROW(olist.items, strmap_get_size(&opti->output),
-			   olist.alloc);
-
-		/* Put every entry from output into olist, then sort */
-		strmap_for_each_entry(&opti->output, &iter, e) {
-			string_list_append(&olist, e->key)->util = e->value;
-		}
-		string_list_sort(&olist);
-
-		/* Iterate over the items, printing them */
-		for (i = 0; i < olist.nr; ++i) {
-			struct strbuf *sb = olist.items[i].util;
-
-			printf("%s", sb->buf);
-		}
-		string_list_clear(&olist, 0);
-
-		/* Also include needed rename limit adjustment now */
-		diff_warn_rename_limit("merge.renamelimit",
-				       opti->renames.needed_limit, 0, stdout);
-
-		trace2_region_leave("merge", "display messages", opt->repo);
-	}
+	if (display_update_msgs)
+		merge_display_update_messages(opt, result);
 
 	merge_finalize(opt, result);
 }
merge-ort.h +8 -0
index fe599b87868..e5aec45b18f 100644
@@ -80,6 +80,14 @@ void merge_switch_to_result(struct merge_options *opt,
 			    int update_worktree_and_index,
 			    int display_update_msgs);
 
+/*
+ * Display messages about conflicts and which files were 3-way merged.
+ * Automatically called by merge_switch_to_result() with stream == stdout,
+ * so only call this when bypassing merge_switch_to_result().
+ */
+void merge_display_update_messages(struct merge_options *opt,
+				   struct merge_result *result);
+
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
 		    struct merge_result *result);
Signature · 1 line
gitgitgadget
#97 lore raw

PATCH v2 7/13 merge-ort: allow update messages to be written to different file stream

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

This modifies the new display_update_messages() function to allow printing to somewhere other than stdout.

---
 merge-ort.c | 9 +++++----
 merge-ort.h | 3 ++-
 2 files changed, 7 insertions(+), 5 deletions(-)
merge-ort.c +5 -4
index 1ada3198390..d28d1721d14 100644
@@ -4236,7 +4236,8 @@ static int record_conflicted_index_entries(struct merge_options *opt)
 }
 
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result)
+				   struct merge_result *result,
+				   FILE *stream)
 {
 	struct merge_options_internal *opti = result->priv;
 	struct hashmap_iter iter;
@@ -4263,13 +4264,13 @@ void merge_display_update_messages(struct merge_options *opt,
 	for (i = 0; i < olist.nr; ++i) {
 		struct strbuf *sb = olist.items[i].util;
 
-		printf("%s", sb->buf);
+		strbuf_write(sb, stream);
 	}
 	string_list_clear(&olist, 0);
 
 	/* Also include needed rename limit adjustment now */
 	diff_warn_rename_limit("merge.renamelimit",
-			       opti->renames.needed_limit, 0, stdout);
+			       opti->renames.needed_limit, 0, stream);
 
 	trace2_region_leave("merge", "display messages", opt->repo);
 }
@@ -4313,7 +4314,7 @@ void merge_switch_to_result(struct merge_options *opt,
 	}
 
 	if (display_update_msgs)
-		merge_display_update_messages(opt, result);
+		merge_display_update_messages(opt, result, stdout);
 
 	merge_finalize(opt, result);
 }
merge-ort.h +2 -1
index e5aec45b18f..d643b47cb7c 100644
@@ -86,7 +86,8 @@ void merge_switch_to_result(struct merge_options *opt,
  * so only call this when bypassing merge_switch_to_result().
  */
 void merge_display_update_messages(struct merge_options *opt,
-				   struct merge_result *result);
+				   struct merge_result *result,
+				   FILE *stream);
 
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
Signature · 1 line
gitgitgadget
#98 lore raw

PATCH v2 8/13 merge-tree: support including merge messages in output

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

When running git merge-tree --write-tree, we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge. Merges also have informational messages, such as:
* "Auto-merging <PATH>"
* "CONFLICT (content): ..."
* "CONFLICT (file/directory)"
* etc.
In fact, when non-content conflicts occur (such as file/directory, modify/delete, add/add with differing modes, rename/rename (1to2), etc.), these informational messages may be the only notification the user gets since these conflicts are not representable in the contents of the file.

Add a --[no-]messages option so that callers can request these messages be included at the end of the output. Include such messages by default when there are conflicts, and omit them by default when the merge is clean.

---
 Documentation/git-merge-tree.txt | 45 +++++++++++++++++++++++++++-----
 builtin/merge-tree.c             | 19 ++++++++++++--
 t/t4301-merge-tree-write-tree.sh | 21 +++++++++++++++
 3 files changed, 76 insertions(+), 9 deletions(-)
Documentation/git-merge-tree.txt +38 -7
index 569485815a0..42e0f8f6183 100644
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree
 SYNOPSIS
 --------
 [verse]
-'git merge-tree' [--write-tree] <branch1> <branch2>
+'git merge-tree' [--write-tree] [<options>] <branch1> <branch2>
 'git merge-tree' [--trivial-merge] <base-tree> <branch1> <branch2> (deprecated)
 
 DESCRIPTION
@@ -35,17 +35,47 @@ merge is distinguished from a trivial merge in that it includes:
 After the merge completes, it will create a new toplevel tree object.
 See `OUTPUT` below for details.
 
+OPTIONS
+-------
+
+--[no-]messages::
+	Write any informational messages such as "Auto-merging <path>"
+	or CONFLICT notices to the end of stdout.  If unspecified, the
+	default is to include these messages if there are merge
+	conflicts, and to omit them otherwise.
+
 OUTPUT
 ------
 
-For either a successful or conflicted merge, the output from
-git-merge-tree is simply one line:
+By default, for a successful merge, the output from git-merge-tree is
+simply one line:
+
+	<OID of toplevel tree>
+
+Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Informational messages>
+
+These are discussed individually below.
+
+OID of toplevel tree
+~~~~~~~~~~~~~~~~~~~~
+
+This is a tree object that represents what would be checked out in the
+working tree at the end of `git merge`.  If there were conflicts, then
+files within this tree may have embedded conflict markers.
+
+Informational messages
+~~~~~~~~~~~~~~~~~~~~~~
+
+This always starts with a blank line to separate it from the previous
+section, and then has free-form messages about the merge, such as:
 
-The printed tree object corresponds to what would be checked out in
-the working tree at the end of `git merge`, and thus may have files
-with conflict markers in them.
+  * "Auto-merging <file>"
+  * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
+  * "Failed to merge submodule <submodule> (<reason>)"
+  * "Warning: cannot merge binary files: <filename>"
 
 EXIT STATUS
 -----------
@@ -69,7 +99,8 @@ be used as a part of a series of steps such as
 
 However, it does not quite fit into the same category of low-level
 plumbing commands since the possibility of merge conflicts give it a
-much higher chance of the command not succeeding.
+much higher chance of the command not succeeding (and NEWTREE containing
+a bunch of stuff other than just a toplevel tree).
 
 GIT
 ---
builtin/merge-tree.c +17 -2
index d14c9f6e44e..6a556ab1c9c 100644
@@ -390,6 +390,7 @@ static int trivial_merge(const char *base,
 
 struct merge_tree_options {
 	int mode;
+	int show_messages;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -432,18 +433,27 @@ static int real_merge(struct merge_tree_options *o,
 	merge_incore_recursive(&opt, merge_bases, parent1, parent2, &result);
 	if (result.clean < 0)
 		die(_("failure to merge"));
+
+	if (o->show_messages == -1)
+		o->show_messages = !result.clean;
+
 	puts(oid_to_hex(&result.tree->object.oid));
+	if (o->show_messages) {
+		printf("\n");
+		merge_display_update_messages(&opt, &result, stdout);
+	}
 	merge_finalize(&opt, &result);
 	return !result.clean; /* result.clean < 0 handled above */
 }
 
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	struct merge_tree_options o = { 0 };
+	struct merge_tree_options o = { .show_messages = -1 };
 	int expected_remaining_argc;
+	int original_argc;
 
 	const char * const merge_tree_usage[] = {
-		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--write-tree] [<options>] <branch1> <branch2>"),
 		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
 		NULL
 	};
@@ -453,10 +463,13 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			    'w'),
 		OPT_CMDMODE(0, "trivial-merge", &o.mode,
 			    N_("do a trivial merge only"), 't'),
+		OPT_BOOL(0, "messages", &o.show_messages,
+			 N_("also show informational/conflict messages")),
 		OPT_END()
 	};
 
 	/* Parse arguments */
+	original_argc = argc;
 	argc = parse_options(argc, argv, prefix, mt_options,
 			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
 	if (o.mode) {
@@ -468,6 +481,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			usage_with_options(merge_tree_usage, mt_options);
 		o.mode = (argc == 2 ? 'w' : 't');
 	}
+	if (o.mode == 't' && original_argc < argc)
+		die(_("--trivial-merge is incompatible with all other options"));
 
 	/* Do the relevant type of merge */
 	if (o.mode == 'w')
t/t4301-merge-tree-write-tree.sh +21 -0
index 66c3eaf2021..e2255711f9c 100755
@@ -85,4 +85,25 @@ test_expect_success 'Barf on too many arguments' '
 	grep "^usage: git merge-tree" expect
 '
 
+test_expect_success 'test conflict notices and such' '
+	test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	# Expected results:
+	#   "greeting" should merge with conflicts
+	#   "numbers" should merge cleanly
+	#   "whatever" has *both* a modify/delete and a file/directory conflict
+	cat <<-EOF >expect &&
+	HASH
+
+	Auto-merging greeting
+	CONFLICT (content): Merge conflict in greeting
+	Auto-merging numbers
+	CONFLICT (file/directory): directory in the way of whatever from side1; moving it to whatever~side1 instead.
+	CONFLICT (modify/delete): whatever~side1 deleted in side2 and modified in side1.  Version side1 of whatever~side1 left in tree.
+	EOF
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#99 lore raw

Re: [PATCH v2 08/13] merge-tree: support including merge messages in output

Documentation/git-merge-tree.txt merge is distinguished from a trivial merge in that it includes: · line 58

Quoted 9 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +7 -0
+By default, for a successful merge, the output from git-merge-tree is
+simply one line:
+
+	<OID of toplevel tree>
+
+Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Informational messages>

Sounds useful. This made me wonder, as the only shuffling of the output destination in the past few steps were to send the output to some "FILE *", how you send the findings you make while coming up with the result _after_ the result. It turns out that the ORT machinery already buffers these findings in a strbuf per path, so there is no trouble doing so ;-)

It still makes me wonder how the "send rename warnings to the standard output stream, instead of the standard error stream" change interacts with this change, though. That needs to be done way before you finish computing the result, and it does not seem to be buffered in-core, like per-path conflict information messages.

#100 lore raw

Re: [PATCH v2 08/13] merge-tree: support including merge messages in output

Quoted 10 lines · Junio C Hamano
Quoted 7 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +6 -0
+By default, for a successful merge, the output from git-merge-tree is
+simply one line:
+
+     <OID of toplevel tree>
+
+Whereas for a conflicted merge, the output is by default of the form:
Quoted 9 lines
Quoted 2 lines

<OID of toplevel tree>
+ <Informational messages>

Sounds useful. This made me wonder, as the only shuffling of the output destination in the past few steps were to send the output to some "FILE *", how you send the findings you make while coming up with the result _after_ the result. It turns out that the ORT machinery already buffers these findings in a strbuf per path, so there is no trouble doing so ;-)

:-)

Quoted 5 lines

It still makes me wonder how the "send rename warnings to the standard output stream, instead of the standard error stream" change interacts with this change, though. That needs to be done way before you finish computing the result, and it does not seem to be buffered in-core, like per-path conflict information messages.

Actually, that's also stashed away in detect_regular_renames() from merge-ort.c:

    if (diff_opts.needed_rename_limit > renames->needed_limit)
        renames->needed_limit = diff_opts.needed_rename_limit;

and then the call to diff_warn_rename_limit() is deferred until after all the informational messages are printed. Also, this series modifies diff_warn_rename_limit() later to allow it to send that message to a different stream.

#101 lore raw

PATCH v2 9/13 merge-ort: provide a merge_get_conflicted_files() helper function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

After a merge, this function allows the user to extract the same information that would be printed by ls-files -u, which means files with their mode, oid, and stage.

---
 merge-ort.c | 31 +++++++++++++++++++++++++++++++
 merge-ort.h | 21 +++++++++++++++++++++
 2 files changed, 52 insertions(+)
merge-ort.c +31 -0
index d28d1721d14..c4ce6027dc4 100644
@@ -4275,6 +4275,37 @@ void merge_display_update_messages(struct merge_options *opt,
 	trace2_region_leave("merge", "display messages", opt->repo);
 }
 
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files)
+{
+	struct hashmap_iter iter;
+	struct strmap_entry *e;
+	struct merge_options_internal *opti = result->priv;
+
+	strmap_for_each_entry(&opti->conflicted, &iter, e) {
+		const char *path = e->key;
+		struct conflict_info *ci = e->value;
+		int i;
+
+		VERIFY_CI(ci);
+
+		for (i = MERGE_BASE; i <= MERGE_SIDE2; i++) {
+			struct stage_info *si;
+
+			if (!(ci->filemask & (1ul << i)))
+				continue;
+
+			si = xmalloc(sizeof(*si));
+			si->stage = i+1;
+			si->mode = ci->stages[i].mode;
+			oidcpy(&si->oid, &ci->stages[i].oid);
+			string_list_append(conflicted_files, path)->util = si;
+		}
+	}
+	/* string_list_sort() uses a stable sort, so we're good */
+	string_list_sort(conflicted_files);
+}
+
 void merge_switch_to_result(struct merge_options *opt,
 			    struct tree *head,
 			    struct merge_result *result,
merge-ort.h +21 -0
index d643b47cb7c..e635a294ea8 100644
@@ -2,6 +2,7 @@
 #define MERGE_ORT_H
 
 #include "merge-recursive.h"
+#include "hash.h"
 
 struct commit;
 struct tree;
@@ -89,6 +90,26 @@ void merge_display_update_messages(struct merge_options *opt,
 				   struct merge_result *result,
 				   FILE *stream);
 
+struct stage_info {
+	struct object_id oid;
+	int mode;
+	int stage;
+};
+
+/*
+ * Provide a list of path -> {struct stage_info*} mappings for
+ * all conflicted files.  Note that each path could appear up to three
+ * times in the list, corresponding to 3 different stage entries.  In short,
+ * this basically provides the info that would be printed by `ls-files -u`.
+ *
+ * result should have been populated by a call to
+ * one of the merge_incore_[non]recursive() functions.
+ *
+ * conflicted_files should be empty before calling this function.
+ */
+void merge_get_conflicted_files(struct merge_result *result,
+				struct string_list *conflicted_files);
+
 /* Do needed cleanup when not calling merge_switch_to_result() */
 void merge_finalize(struct merge_options *opt,
 		    struct merge_result *result);
Signature · 1 line
gitgitgadget
#102 lore raw

PATCH v2 10/13 merge-tree: provide a list of which files have conflicts

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Callers of git merge-tree --write-tree will often want to know which files had conflicts. While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable. Provide a simpler mechanism of just printing the files (in the same format as git ls-files with quoting, but restricted to unmerged files) in the output before the free-form messages.

---
 Documentation/git-merge-tree.txt |  8 ++++++++
 builtin/merge-tree.c             | 24 ++++++++++++++++++++++--
 t/t4301-merge-tree-write-tree.sh | 11 +++++++++++
 3 files changed, 41 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +8 -0
index 42e0f8f6183..160e8f44b62 100644
@@ -55,6 +55,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
+	<Conflicted file list>
 	<Informational messages>
 
 These are discussed individually below.
@@ -66,6 +67,13 @@ This is a tree object that represents what would be checked out in the
 working tree at the end of `git merge`.  If there were conflicts, then
 files within this tree may have embedded conflict markers.
 
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).
+
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
builtin/merge-tree.c +22 -2
index 6a556ab1c9c..54dae018203 100644
@@ -11,6 +11,9 @@
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
+#include "quote.h"
+
+static int line_termination = '\n';
 
 struct merge_list {
 	struct merge_list *next;
@@ -394,7 +397,8 @@ struct merge_tree_options {
 };
 
 static int real_merge(struct merge_tree_options *o,
-		      const char *branch1, const char *branch2)
+		      const char *branch1, const char *branch2,
+		      const char *prefix)
 {
 	struct commit *parent1, *parent2;
 	struct commit_list *common;
@@ -438,6 +442,22 @@ static int real_merge(struct merge_tree_options *o,
 		o->show_messages = !result.clean;
 
 	puts(oid_to_hex(&result.tree->object.oid));
+	if (!result.clean) {
+		struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
+		const char *last = NULL;
+		int i;
+
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;
+		}
+		string_list_clear(&conflicted_files, 1);
+	}
 	if (o->show_messages) {
 		printf("\n");
 		merge_display_update_messages(&opt, &result, stdout);
@@ -486,7 +506,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 
 	/* Do the relevant type of merge */
 	if (o.mode == 'w')
-		return real_merge(&o, argv[0], argv[1]);
+		return real_merge(&o, argv[0], argv[1], prefix);
 	else
 		return trivial_merge(argv[0], argv[1], argv[2]);
 }
t/t4301-merge-tree-write-tree.sh +11 -0
index e2255711f9c..7113d060bc5 100755
@@ -95,6 +95,8 @@ test_expect_success 'test conflict notices and such' '
 	#   "whatever" has *both* a modify/delete and a file/directory conflict
 	cat <<-EOF >expect &&
 	HASH
+	greeting
+	whatever~side1
 
 	Auto-merging greeting
 	CONFLICT (content): Merge conflict in greeting
@@ -106,4 +108,13 @@ test_expect_success 'test conflict notices and such' '
 	test_cmp expect actual
 '
 
+test_expect_success 'Just the conflicted files without the messages' '
+	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	test_write_lines HASH greeting whatever~side1 >expect &&
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#103 lore raw

Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts

Documentation/git-merge-tree.txt This is a tree object that represents what would be checked out in the · line 75

Quoted 6 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +6 -0
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).

Makes sense. Ideally things like this should be discoverable by inspecting the tree object shown as the result of the (conflicted) merge, but since the design of the output is to show only a single tree, there is nowhere to store such an extra piece of information per path (grepping for markers in blobs of course does not count).

I guess an alternative to show four trees when conflicted instead of one (i.e. the primary tree may either contain only the cleanly merged paths _or_ also blobs with conflict markers for conflicted paths; the three other trees record three stages that would be in the index, if we were performing the same merge using the index), but a machine-parseable list of paths is fine.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 457

Quoted 8 lines
builtin/merge-tree.c +8 -0
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;

OK. The iteration used here makes casual readers wonder why the helper doesn't make paths unique, but the string list item holds in its util pointer a pointer to a structure with <stage, mode, oid> tuple, so it is natural to make the consumer, who wants uniquified list, responsible for deduping, like this loop.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 459

Quoted 2 lines
builtin/merge-tree.c +2 -0
+		}
+		string_list_clear(&conflicted_files, 1);

And the stage-info structure associated with these paths are deallocated with this call. Good.

Quoted 1 line
(quoted diff) +1 -0
+	}
#104 lore raw

Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts

In reply to Elijah Newren via GitGitGadget

Documentation/git-merge-tree.txt This is a tree object that represents what would be checked out in the · line 75

Quoted 6 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +6 -0
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).

Makes sense. Ideally things like this should be discoverable by inspecting the tree object shown as the result of the (conflicted) merge, but since the design of the output is to show only a single tree, there is nowhere to store such an extra piece of information per path (grepping for markers in blobs of course does not count).

I guess an alternative to show four trees when conflicted instead of one (i.e. the primary tree may either contain only the cleanly merged paths _or_ also blobs with conflict markers for conflicted paths; the three other trees record three stages that would be in the index, if we were performing the same merge using the index), but a machine-parseable list of paths is fine.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 457

Quoted 8 lines
builtin/merge-tree.c +8 -0
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;

OK. The iteration used here makes casual readers wonder why the helper doesn't make paths unique, but the string list item holds in its util pointer a pointer to a structure with <stage, mode, oid> tuple, so it is natural to make the consumer, who wants uniquified list, responsible for deduping, like this loop.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 459

Quoted 2 lines
builtin/merge-tree.c +2 -0
+		}
+		string_list_clear(&conflicted_files, 1);

And the stage-info structure associated with these paths are deallocated with this call. Good.

Quoted 1 line
(quoted diff) +1 -0
+	}
#105 lore raw

Re: [PATCH v2 10/13] merge-tree: provide a list of which files have conflicts

In reply to Elijah Newren via GitGitGadget

Documentation/git-merge-tree.txt This is a tree object that represents what would be checked out in the · line 75

Quoted 6 lines · Elijah Newren via GitGitGadget
Documentation/git-merge-tree.txt +6 -0
+Conflicted file list
+~~~~~~~~~~~~~~~~~~~~
+
+This is a sequence of lines containing a filename on each line, quoted
+as explained for the configuration variable `core.quotePath` (see
+linkgit:git-config[1]).

Makes sense. Ideally things like this should be discoverable by inspecting the tree object shown as the result of the (conflicted) merge, but since the design of the output is to show only a single tree, there is nowhere to store such an extra piece of information per path (grepping for markers in blobs of course does not count).

I guess an alternative to show four trees when conflicted instead of one (i.e. the primary tree may either contain only the cleanly merged paths _or_ also blobs with conflict markers for conflicted paths; the three other trees record three stages that would be in the index, if we were performing the same merge using the index), but a machine-parseable list of paths is fine.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 457

Quoted 8 lines
builtin/merge-tree.c +8 -0
+		merge_get_conflicted_files(&result, &conflicted_files);
+		for (i = 0; i < conflicted_files.nr; i++) {
+			const char *name = conflicted_files.items[i].string;
+			if (last && !strcmp(last, name))
+				continue;
+			write_name_quoted_relative(
+				name, prefix, stdout, line_termination);
+			last = name;

OK. The iteration used here makes casual readers wonder why the helper doesn't make paths unique, but the string list item holds in its util pointer a pointer to a structure with <stage, mode, oid> tuple, so it is natural to make the consumer, who wants uniquified list, responsible for deduping, like this loop.

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 459

Quoted 2 lines
builtin/merge-tree.c +2 -0
+		}
+		string_list_clear(&conflicted_files, 1);

And the stage-info structure associated with these paths are deallocated with this call. Good.

Quoted 1 line
(quoted diff) +1 -0
+	}
#106 lore raw

PATCH v2 11/13 merge-tree: provide easy access to `ls-files -u` style info

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Much like git merge updates the index with information of the form
(mode, oid, stage, name)
provide this output for conflicted files for merge-tree as well. Provide an --exclude-modes-oids-stages/-l option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames.

---
 Documentation/git-merge-tree.txt | 30 ++++++++++++++++++++++++------
 builtin/merge-tree.c             | 11 ++++++++++-
 t/t4301-merge-tree-write-tree.sh | 26 ++++++++++++++++++++++++--
 3 files changed, 58 insertions(+), 9 deletions(-)
Documentation/git-merge-tree.txt +24 -6
index 160e8f44b62..55bb7bc61c1 100644
@@ -38,6 +38,11 @@ See `OUTPUT` below for details.
 OPTIONS
 -------
 
+--exclude-oids-and-modes::
+	Instead of writing a list of (mode, oid, stage, path) tuples
+	to output for conflicted files, just provide a list of
+	filenames with conflicts.
+
 --[no-]messages::
 	Write any informational messages such as "Auto-merging <path>"
 	or CONFLICT notices to the end of stdout.  If unspecified, the
@@ -55,7 +60,7 @@ simply one line:
 Whereas for a conflicted merge, the output is by default of the form:
 
 	<OID of toplevel tree>
-	<Conflicted file list>
+	<Conflicted file info>
 	<Informational messages>
 
 These are discussed individually below.
@@ -67,18 +72,23 @@ This is a tree object that represents what would be checked out in the
 working tree at the end of `git merge`.  If there were conflicts, then
 files within this tree may have embedded conflict markers.
 
-Conflicted file list
+Conflicted file info
 ~~~~~~~~~~~~~~~~~~~~
 
-This is a sequence of lines containing a filename on each line, quoted
-as explained for the configuration variable `core.quotePath` (see
-linkgit:git-config[1]).
+This is a sequence of lines with the format
+
+	<mode> <object> <stage> <filename>
+
+The filename will be quoted as explained for the configuration
+variable `core.quotePath` (see linkgit:git-config[1]).  However, if
+the `--exclude-oids-and-modes` option is passed, the mode, object, and
+stage will be omitted.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
 
 This always starts with a blank line to separate it from the previous
-section, and then has free-form messages about the merge, such as:
+sections, and then has free-form messages about the merge, such as:
 
   * "Auto-merging <file>"
   * "CONFLICT (rename/delete): <oldfile> renamed...but deleted in..."
@@ -110,6 +120,14 @@ plumbing commands since the possibility of merge conflicts give it a
 much higher chance of the command not succeeding (and NEWTREE containing
 a bunch of stuff other than just a toplevel tree).
 
+git-merge-tree was written to provide users with the same information
+that they'd have access to if using `git merge`:
+  * what would be written to the working tree (the <OID of toplevel tree>)
+  * the higher order stages that would be written to the index (the
+    <Conflicted file info>)
+  * any messages that would have been printed to stdout (the <Informational
+    messages>)
+
 GIT
 ---
 Part of the linkgit:git[1] suite
builtin/merge-tree.c +10 -1
index 54dae018203..dc52cd02dce 100644
@@ -394,6 +394,7 @@ static int trivial_merge(const char *base,
 struct merge_tree_options {
 	int mode;
 	int show_messages;
+	int exclude_modes_oids_stages;
 };
 
 static int real_merge(struct merge_tree_options *o,
@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,
 		merge_get_conflicted_files(&result, &conflicted_files);
 		for (i = 0; i < conflicted_files.nr; i++) {
 			const char *name = conflicted_files.items[i].string;
-			if (last && !strcmp(last, name))
+			struct stage_info *c = conflicted_files.items[i].util;
+			if (!o->exclude_modes_oids_stages)
+				printf("%06o %s %d\t",
+				       c->mode, oid_to_hex(&c->oid), c->stage);
+			else if (last && !strcmp(last, name))
 				continue;
 			write_name_quoted_relative(
 				name, prefix, stdout, line_termination);
@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			    N_("do a trivial merge only"), 't'),
 		OPT_BOOL(0, "messages", &o.show_messages,
 			 N_("also show informational/conflict messages")),
+		OPT_BOOL_F('l', "exclude-modes-oids-stages",
+			   &o.exclude_modes_oids_stages,
+			   N_("list conflicted files without modes/oids/stages"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};
 
t/t4301-merge-tree-write-tree.sh +24 -2
index 7113d060bc5..1572f460da0 100755
@@ -47,6 +47,7 @@ test_expect_success 'Content merge and a few conflicts' '
 	expected_tree=$(cat .git/AUTO_MERGE) &&
 
 	# We will redo the merge, while we are still in a conflicted state!
+	git ls-files -u >conflicted-file-info &&
 	test_when_finished "git reset --hard" &&
 
 	test_expect_code 1 git merge-tree --write-tree side1 side2 >RESULT &&
@@ -86,7 +87,7 @@ test_expect_success 'Barf on too many arguments' '
 '
 
 test_expect_success 'test conflict notices and such' '
-	test_expect_code 1 git merge-tree --write-tree side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	# Expected results:
@@ -109,7 +110,7 @@ test_expect_success 'test conflict notices and such' '
 '
 
 test_expect_success 'Just the conflicted files without the messages' '
-	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	test_write_lines HASH greeting whatever~side1 >expect &&
@@ -117,4 +118,25 @@ test_expect_success 'Just the conflicted files without the messages' '
 	test_cmp expect actual
 '
 
+test_expect_success 'Check conflicted oids and modes without messages' '
+	test_expect_code 1 git merge-tree --write-tree --no-messages side1 side2 >out &&
+	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
+
+	# Compare the basic output format
+	q_to_tab >expect <<-\EOF &&
+	HASH
+	100644 HASH 1Qgreeting
+	100644 HASH 2Qgreeting
+	100644 HASH 3Qgreeting
+	100644 HASH 1Qwhatever~side1
+	100644 HASH 2Qwhatever~side1
+	EOF
+
+	test_cmp expect actual &&
+
+	# Check the actual hashes against the `ls-files -u` output too
+	tail -n +2 out | sed -e s/side1/HEAD/ >actual &&
+	test_cmp conflicted-file-info actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#107 lore raw

Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 461

Quoted 13 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +5 -1
@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,
 		merge_get_conflicted_files(&result, &conflicted_files);
 		for (i = 0; i < conflicted_files.nr; i++) {
 			const char *name = conflicted_files.items[i].string;
-			if (last && !strcmp(last, name))
+			struct stage_info *c = conflicted_files.items[i].util;
+			if (!o->exclude_modes_oids_stages)
+				printf("%06o %s %d\t",
+				       c->mode, oid_to_hex(&c->oid), c->stage);
+			else if (last && !strcmp(last, name))
 				continue;
 			write_name_quoted_relative(
 				name, prefix, stdout, line_termination);

OK. The addition (and disabling of the deduping) is quite trivial. We do not even have to worry about line termination since the extra pieces of info are prepended to the pathname. Nice.

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 496

Quoted 8 lines
builtin/merge-tree.c +4 -0
@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			    N_("do a trivial merge only"), 't'),
 		OPT_BOOL(0, "messages", &o.show_messages,
 			 N_("also show informational/conflict messages")),
+		OPT_BOOL_F('l', "exclude-modes-oids-stages",
+			   &o.exclude_modes_oids_stages,
+			   N_("list conflicted files without modes/oids/stages"),
+			   PARSE_OPT_NONEG),

Why does "-l" give shorter output than without it? "-l" strongly hints a longer output than without, at least to me. Just wondering if this will not become a source of confusion to future scripting users.

Quoted 3 lines
builtin/merge-tree.c +0 -0
 		OPT_END()
 	};
 
#108 lore raw

Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info

Quoted 21 lines · Junio C Hamano

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 461

Quoted 13 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +5 -1
@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,
              merge_get_conflicted_files(&result, &conflicted_files);
              for (i = 0; i < conflicted_files.nr; i++) {
                      const char *name = conflicted_files.items[i].string;
-                     if (last && !strcmp(last, name))
+                     struct stage_info *c = conflicted_files.items[i].util;
+                     if (!o->exclude_modes_oids_stages)
+                             printf("%06o %s %d\t",
+                                    c->mode, oid_to_hex(&c->oid), c->stage);
+                     else if (last && !strcmp(last, name))
                              continue;
                      write_name_quoted_relative(
                              name, prefix, stdout, line_termination);

OK. The addition (and disabling of the deduping) is quite trivial. We do not even have to worry about line termination since the extra pieces of info are prepended to the pathname. Nice.

Quoted 13 lines

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 496

Quoted 8 lines
builtin/merge-tree.c +4 -0
@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                          N_("do a trivial merge only"), 't'),
              OPT_BOOL(0, "messages", &o.show_messages,
                       N_("also show informational/conflict messages")),
+             OPT_BOOL_F('l', "exclude-modes-oids-stages",
+                        &o.exclude_modes_oids_stages,
+                        N_("list conflicted files without modes/oids/stages"),
+                        PARSE_OPT_NONEG),

Why does "-l" give shorter output than without it? "-l" strongly hints a longer output than without, at least to me. Just wondering if this will not become a source of confusion to future scripting users.

Here's another example where I was struggling with naming. Something like ls-tree's --name-only would have been nice, but I was worried it'd be confusing since it only affected the conflicted info section and does not suppress the printing of the toplevel tree or the informational messages sections. And the name --exclude-modes-oids-stages was long enough that I wanted a short flag for it, and just used the first letter of the description ("list conflicted files..."). I'm happy to change either the long or the short name for this flag if anyone has suggestions.

#109 lore raw

Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info

Quoted 36 lines · Elijah Newren
Quoted 21 lines · Junio C Hamano

Review static int real_merge(struct merge_tree_options *o, · line 461

Quoted 13 lines · Elijah Newren via GitGitGadget
(quoted diff) +5 -1
@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,
              merge_get_conflicted_files(&result, &conflicted_files);
              for (i = 0; i < conflicted_files.nr; i++) {
                      const char *name = conflicted_files.items[i].string;
-                     if (last && !strcmp(last, name))
+                     struct stage_info *c = conflicted_files.items[i].util;
+                     if (!o->exclude_modes_oids_stages)
+                             printf("%06o %s %d\t",
+                                    c->mode, oid_to_hex(&c->oid), c->stage);
+                     else if (last && !strcmp(last, name))
                              continue;
                      write_name_quoted_relative(
                              name, prefix, stdout, line_termination);

OK. The addition (and disabling of the deduping) is quite trivial. We do not even have to worry about line termination since the extra pieces of info are prepended to the pathname. Nice.

Quoted 13 lines

Review int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 496

Quoted 8 lines
(quoted diff) +4 -0
@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                          N_("do a trivial merge only"), 't'),
              OPT_BOOL(0, "messages", &o.show_messages,
                       N_("also show informational/conflict messages")),
+             OPT_BOOL_F('l', "exclude-modes-oids-stages",
+                        &o.exclude_modes_oids_stages,
+                        N_("list conflicted files without modes/oids/stages"),
+                        PARSE_OPT_NONEG),

Why does "-l" give shorter output than without it? "-l" strongly hints a longer output than without, at least to me. Just wondering if this will not become a source of confusion to future scripting users.

Quoted 9 lines

Here's another example where I was struggling with naming. Something like ls-tree's --name-only would have been nice, but I was worried it'd be confusing since it only affected the conflicted info section and does not suppress the printing of the toplevel tree or the informational messages sections. And the name --exclude-modes-oids-stages was long enough that I wanted a short flag for it, and just used the first letter of the description ("list conflicted files..."). I'm happy to change either the long or the short name for this flag if anyone has suggestions.

There's always sidestepping it by replacing it with a --format :)

Anyway, I'd mentioned that in an earlier review in <[email protected]>. FWIW here's an experiment to do that that I polished up (mostly copied from the ls-tree WIP code I'd written already).

I don't know if it will ever be useful, or if you think it's worthwhile/simpler, but in either case I think in doing this I spotted the following issues or otherwise noted inconsistencies in the pre-image:

The docs say that "<stage> <path>" is SP-separated, but it's actually TAB-separated, the rest is SP-separated.

* That you de-dupe --exclude-modes-oids-stages is a bit of a hidden feature, but argubly initiative. Should it by optional? In any case my formatting experiment makes it optional, since it then needs to be generalized to de-dupe after we've formatted.

* Perhaps we should support --abbrev as ls-tree does? The below diff shows it's easy enough.

* The dance you have with sed-ing out the hash in the tests could be made much easier with "sed 1d <out >actual" and --no-messages for some existing tests.

Documentation/git-merge-tree.txt +8 -9
index 6a2ed475106..e906d1dc9bf 100644
@@ -44,10 +44,9 @@ OPTIONS
 	newline.  Also begin the messages section with a NUL character
 	instead of a newline.  See OUTPUT below for more information.
 
---exclude-oids-and-modes::
-	Instead of writing a list of (mode, oid, stage, path) tuples
-	to output for conflicted files, just provide a list of
-	filenames with conflicts.
+--conflict-format::
+	Override the default "%(objectmode) %(objectname)
+	%(stage)%x09%(path)" format.
 
 --[no-]messages::
 	Write any informational messages such as "Auto-merging <path>"
@@ -89,13 +88,13 @@ Conflicted file info
 
 This is a sequence of lines with the format
 
-	<mode> <object> <stage> <filename>
+	%(objectmode) %(objectname) %(stage)%x09%(path)
 
 The filename will be quoted as explained for the configuration
-variable `core.quotePath` (see linkgit:git-config[1]).  However, if
-the `--exclude-oids-and-modes` option is passed, the mode, object, and
-stage will be omitted.  If `-z` is passed, the "lines" are terminated
-by a NUL character instead of a newline character.
+variable `core.quotePath` (see linkgit:git-config[1]).
+
+If `-z` is passed, the "lines" are terminated by a NUL character
+instead of a newline character.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
builtin/merge-tree.c +98 -18

151 lines not on this page · raw message

t/t4301-merge-tree-write-tree.sh +29 -2
index 4de089d976d..e6354b2d284 100755
@@ -93,7 +93,7 @@ test_expect_success 'Barf on too many arguments' '
 '
 
 test_expect_success 'test conflict notices and such' '
-	test_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	# Expected results:
@@ -115,8 +115,35 @@ test_expect_success 'test conflict notices and such' '
 	test_cmp expect actual
 '
 
+test_expect_success 'merge-tree --unique-conflicts is the default' '
+	test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-messages side1 side2 >out &&
+	sed 1d <out >actual &&
+	cat >expect <<-\EOF &&
+	greeting
+	whatever~side1
+	EOF
+	test_cmp expect actual &&
+
+	test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-messages side1 side2 >out2 &&
+	sed 1d <out2 >actual2 &&
+	test_cmp actual actual2
+'
+
+test_expect_success 'merge-tree --no-unique-conflicts' '
+	test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-unique-conflicts --no-messages side1 side2 >out &&
+	sed 1d <out >actual &&
+	cat >expect <<-\EOF &&
+	greeting
+	greeting
+	greeting
+	whatever~side1
+	whatever~side1
+	EOF
+	test_cmp expect actual
+'
+
 test_expect_success 'Just the conflicted files without the messages' '
-	test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&
+	test_expect_code 1 git merge-tree --write-tree --no-messages --conflict-format="%(path)" side1 side2 >out &&
 	sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
 	test_write_lines HASH greeting whatever~side1 >expect &&
#110 lore raw

Re: [PATCH v2 11/13] merge-tree: provide easy access to `ls-files -u` style info

Quoted 49 lines · Ævar Arnfjörð Bjarmason
Quoted 36 lines · Elijah Newren
Quoted 21 lines · Junio C Hamano

Review static int real_merge(struct merge_tree_options *o, · line 461

Quoted 13 lines · Elijah Newren via GitGitGadget
(quoted diff) +5 -1
@@ -450,7 +451,11 @@ static int real_merge(struct merge_tree_options *o,
              merge_get_conflicted_files(&result, &conflicted_files);
              for (i = 0; i < conflicted_files.nr; i++) {
                      const char *name = conflicted_files.items[i].string;
-                     if (last && !strcmp(last, name))
+                     struct stage_info *c = conflicted_files.items[i].util;
+                     if (!o->exclude_modes_oids_stages)
+                             printf("%06o %s %d\t",
+                                    c->mode, oid_to_hex(&c->oid), c->stage);
+                     else if (last && !strcmp(last, name))
                              continue;
                      write_name_quoted_relative(
                              name, prefix, stdout, line_termination);

OK. The addition (and disabling of the deduping) is quite trivial. We do not even have to worry about line termination since the extra pieces of info are prepended to the pathname. Nice.

Quoted 13 lines

Review int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 496

Quoted 8 lines
(quoted diff) +4 -0
@@ -485,6 +490,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                          N_("do a trivial merge only"), 't'),
              OPT_BOOL(0, "messages", &o.show_messages,
                       N_("also show informational/conflict messages")),
+             OPT_BOOL_F('l', "exclude-modes-oids-stages",
+                        &o.exclude_modes_oids_stages,
+                        N_("list conflicted files without modes/oids/stages"),
+                        PARSE_OPT_NONEG),

Why does "-l" give shorter output than without it? "-l" strongly hints a longer output than without, at least to me. Just wondering if this will not become a source of confusion to future scripting users.

Quoted 9 lines

Here's another example where I was struggling with naming. Something like ls-tree's --name-only would have been nice, but I was worried it'd be confusing since it only affected the conflicted info section and does not suppress the printing of the toplevel tree or the informational messages sections. And the name --exclude-modes-oids-stages was long enough that I wanted a short flag for it, and just used the first letter of the description ("list conflicted files..."). I'm happy to change either the long or the short name for this flag if anyone has suggestions.

Quoted 1 line

There's always sidestepping it by replacing it with a --format :)

Another solution that occurred to me, and I was _really_ close to doing it for v3, was to just flat drop this patch entirely and not include any such option. But...

* "Which files had conflicts?" seems like such an obvious question
* I've used git ls-files -u | awk {print\$4} | uniq a lot in the
past after git merge (Or git rebase) to get this info (yeah, it turns out git diff --name-only --diff-filter=U is 4 fewer characters)
* "display the list of files where conflicts were present in the web
UI" was listed as an early usecase[1]

[1] https://lore.kernel.org/git/[email protected]/

So it seemed like making that question easy to answer was worthwhile.

Quoted 11 lines

Anyway, I'd mentioned that in an earlier review in <[email protected]>. FWIW here's an experiment to do that that I polished up (mostly copied from the ls-tree WIP code I'd written already).

I don't know if it will ever be useful, or if you think it's worthwhile/simpler, but in either case I think in doing this I spotted the following issues or otherwise noted inconsistencies in the pre-image:

The docs say that "<stage> <path>" is SP-separated, but it's actually TAB-separated, the rest is SP-separated.

Yeah, good catch. However, it doesn't actually say they are SP-separated; it's ambiguous about the spacing. Which probably isn't a good thing, but it was kind of copied from the ls-files manual:

"""
git ls-files just outputs the filenames unless --stage is specified in which case it outputs:

[<tag> ]<mode> <object> <stage> <file>
"""

(which also uses a tab between <stage> and <file> and a space otherwise, but the output above may lead you to believe otherwise.)

Quoted 4 lines

* That you de-dupe --exclude-modes-oids-stages is a bit of a hidden feature, but argubly initiative. Should it by optional? In any case my formatting experiment makes it optional, since it then needs to be generalized to de-dupe after we've formatted.

I think without de-duping the flag isn't helpful enough to bother implementing. Requiring two flags also seems painful, given the common case scenario.

I hope I'm not coming across as dismissive. I think eventually adding a --format and --dedupe (the combination of which might be implied by whatever flag is used now) might be useful additions. Maybe --abbrev too...eventually. But I'm worried that it's distracting from focusing on usecases. In particular, I'm worried it leads to "well, script writers technically can get what they want because we provided everything" rather than focusing on making the most common things easy to get, and then extending the command for flexibility as needed later.

I'd really rather that early versions _just_ focus on actual usecases as far as UI is concerned (and thus I was really happy to see Dscho and Taylor concentrate on that side; I think Christian might have been talking about that angle some but it was hard to differentiate from the "merge-tree on steroids" spitballing). While I want to be careful to avoid preventing UI flexibility, I think building it in from the beginning tends to lead to a design that is less usable. (e.g. the possible loss of de-duping that would naturally have arisen from looking at things from the other angle.) It's just a bias I have.

Quoted 2 lines

* Perhaps we should support --abbrev as ls-tree does? The below diff shows it's easy enough.

This one is less problematic to me, but I'd still rather that the UI side of things focused on the usecases for early versions.

Quoted 2 lines

* The dance you have with sed-ing out the hash in the tests could be made much easier with "sed 1d <out >actual" and --no-messages for some existing tests.

Ignoring the first line is semantically different than verifying it looks like a hash. It also only works on the first line, and hashes appear in multiple places, so you'd need a variety of different sed commands for different parts of the output, which doesn't seem any easier at all to me; I think using the same replacement everywhere is simpler. But perhaps I should turn it into a shell function that I use in each case.

Quoted 178 lines
Documentation/git-merge-tree.txt +8 -9
index 6a2ed475106..e906d1dc9bf 100644
@@ -44,10 +44,9 @@ OPTIONS
        newline.  Also begin the messages section with a NUL character
        instead of a newline.  See OUTPUT below for more information.
 
---exclude-oids-and-modes::
-       Instead of writing a list of (mode, oid, stage, path) tuples
-       to output for conflicted files, just provide a list of
-       filenames with conflicts.
+--conflict-format::
+       Override the default "%(objectmode) %(objectname)
+       %(stage)%x09%(path)" format.
 
 --[no-]messages::
        Write any informational messages such as "Auto-merging <path>"
@@ -89,13 +88,13 @@ Conflicted file info
 
 This is a sequence of lines with the format
 
-       <mode> <object> <stage> <filename>
+       %(objectmode) %(objectname) %(stage)%x09%(path)
 
 The filename will be quoted as explained for the configuration
-variable `core.quotePath` (see linkgit:git-config[1]).  However, if
-the `--exclude-oids-and-modes` option is passed, the mode, object, and
-stage will be omitted.  If `-z` is passed, the "lines" are terminated
-by a NUL character instead of a newline character.
+variable `core.quotePath` (see linkgit:git-config[1]).
+
+If `-z` is passed, the "lines" are terminated by a NUL character
+instead of a newline character.
 
 Informational messages
 ~~~~~~~~~~~~~~~~~~~~~~
builtin/merge-tree.c +93 -14
index 58c0ddc5a32..14fed95a8ce 100644
@@ -395,9 +395,64 @@ struct merge_tree_options {
        int mode;
        int allow_unrelated_histories;
        int show_messages;
-       int exclude_modes_oids_stages;
+       const char *conflict_format;
+       int unique_conflicts;
+       int abbrev;
 };
 
+struct expand_conflict_data {
+       const char *prefix;
+       struct string_list_item *item;
+       struct strbuf *scratch;
+       int abbrev;
+       struct strbuf *sb_tmp;
+};
+static size_t expand_conflict_format(struct strbuf *sb,
+                                    const char *start,
+                                    void *context)
+{
+       struct expand_conflict_data *data = context;
+       struct string_list_item *item = data->item;
+       struct stage_info *info = item->util;
+       const char *end;
+       const char *p;
+       size_t len;
+
+       len = strbuf_expand_literal_cb(sb, start, NULL);
+       if (len)
+               return len;
+
+       if (*start != '(')
+               die(_("bad format as of '%s'"), start);
+       end = strchr(start + 1, ')');
+       if (!end)
+               die(_("format element '%s' does not end in ')'"), start);
+       len = end - start + 1;
+
+       if (skip_prefix(start, "(objectmode)", &p)) {
+               strbuf_addf(sb, "%06o", info->mode);
+       } else if (skip_prefix(start, "(objectname)", &p)) {
+               strbuf_addstr(sb, find_unique_abbrev(&info->oid, data->abbrev));
+       } else if (skip_prefix(start, "(stage)", &p)) {
+               strbuf_addf(sb, "%d", info->stage);
+       } else if (skip_prefix(start, "(path)", &p)) {
+               const char *name = item->string;
+
+               if (data->prefix)
+                       name = relative_path(name, data->prefix, data->scratch);
+               strbuf_addstr(sb, name);
+
+               strbuf_reset(data->sb_tmp);
+               /* The relative_path() function resets "scratch" */
+
+       } else {
+               unsigned int errlen = (unsigned long)len;
+               die(_("bad format specifier %%%.*s"), errlen, start);
+       }
+
+       return len;
+}
+
 static int real_merge(struct merge_tree_options *o,
                      const char *branch1, const char *branch2,
                      const char *prefix)
@@ -446,23 +501,43 @@ static int real_merge(struct merge_tree_options *o,
        puts(oid_to_hex(&result.tree->object.oid));
        if (!result.clean) {
                struct string_list conflicted_files = STRING_LIST_INIT_NODUP;
-               const char *last = NULL;
-               int i;
+               struct string_list_item *item;
+               char *last = NULL;
+               struct strbuf sb = STRBUF_INIT;
+               struct strbuf tmp = STRBUF_INIT;
 
                merge_get_conflicted_files(&result, &conflicted_files);
-               for (i = 0; i < conflicted_files.nr; i++) {
-                       const char *name = conflicted_files.items[i].string;
-                       struct stage_info *c = conflicted_files.items[i].util;
-                       if (!o->exclude_modes_oids_stages)
-                               printf("%06o %s %d\t",
-                                      c->mode, oid_to_hex(&c->oid), c->stage);
-                       else if (last && !strcmp(last, name))
+               for_each_string_list_item(item, &conflicted_files) {
+                       struct expand_conflict_data ctx = {
+                               .prefix = prefix,
+                               .item = item,
+                               .abbrev = o->abbrev,
+                               .scratch = &sb,
+                               .sb_tmp = &tmp,
+                       };
+
+                       strbuf_expand(&sb, o->conflict_format, expand_conflict_format, &ctx);
+                       strbuf_addch(&sb, line_termination);
+
+                       if (o->unique_conflicts && last && !strcmp(last, sb.buf)) {
+                               free(last);
+                               last = strbuf_detach(&sb, NULL);
                                continue;
-                       write_name_quoted_relative(
-                               name, prefix, stdout, line_termination);
-                       last = name;
+                       }
+
+                       fwrite(sb.buf, sb.len, 1, stdout);
+
+                       if (o->unique_conflicts) {
+                               free(last);
+                               last = strbuf_detach(&sb, NULL);
+                       } else {
+                               strbuf_reset(&sb);
+                       }
                }
                string_list_clear(&conflicted_files, 1);
+               strbuf_release(&sb);
+               strbuf_release(&tmp);
+               free(last);
        }
        if (o->show_messages) {
                putchar(line_termination);
@@ -474,7 +549,11 @@ static int real_merge(struct merge_tree_options *o,
 
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-       struct merge_tree_options o = { .show_messages = -1 };
+       struct merge_tree_options o = {
+               .show_messages = -1,
+               .conflict_format = "%(objectmode) %(objectname) %(stage)%x09%(path)",
+               .unique_conflicts = 1,
+       };
        int expected_remaining_argc;
        int original_argc;
 
@@ -493,14 +572,15 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
                         N_("also show informational/conflict messages")),

builtin/merge-tree.c int cmd_merge_tree(int argc, const char **argv, const char *prefix) · line 578

Quoted 10 lines
builtin/merge-tree.c +4 -4
                OPT_SET_INT('z', NULL, &line_termination,
                            N_("separate paths with the NUL character"), '\0'),
-               OPT_BOOL_F('l', "exclude-modes-oids-stages",
-                          &o.exclude_modes_oids_stages,
-                          N_("list conflicted files without modes/oids/stages"),
-                          PARSE_OPT_NONEG),
+               OPT_STRING(0, "conflict-format", &o.conflict_format, N_("format"),
+                          N_("specify a custom format to use for conflicted files")),
+               OPT_BOOL(0, "unique-conflicts", &o.unique_conflicts,
+                        N_("omit duplicate --conflict-format lines")),

The latter of which you didn't include in the manual? Also, unique_conflicts seems like something that is trivial to understand from the coding perspective, but probably require quite a bit more explanation from the manual. For example, if objectname is included in the format, unique-conflicts is essentially a no-op. And that's the default...so, you'd probably have to spend time in the manual explaining under what circumstances it's useful. I'm also not sure if a user who wanted (mode, path) would want unique_conflicts to default to 1; it may be something only meaningful in the particular case of "just give me conflicted filenames".

Quoted 58 lines
builtin/merge-tree.c +1 -0
                OPT_BOOL_F(0, "allow-unrelated-histories",
                           &o.allow_unrelated_histories,
                           N_("allow merging unrelated histories"),
                           PARSE_OPT_NONEG),
+               OPT__ABBREV(&o.abbrev),
                OPT_END()
        };
 
t/t4301-merge-tree-write-tree.sh +29 -2
index 4de089d976d..e6354b2d284 100755
@@ -93,7 +93,7 @@ test_expect_success 'Barf on too many arguments' '
 '
 
 test_expect_success 'test conflict notices and such' '
-       test_expect_code 1 git merge-tree --write-tree --exclude-modes-oids-stages side1 side2 >out &&
+       test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" side1 side2 >out &&
        sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
        # Expected results:
@@ -115,8 +115,35 @@ test_expect_success 'test conflict notices and such' '
        test_cmp expect actual
 '
 
+test_expect_success 'merge-tree --unique-conflicts is the default' '
+       test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-messages side1 side2 >out &&
+       sed 1d <out >actual &&
+       cat >expect <<-\EOF &&
+       greeting
+       whatever~side1
+       EOF
+       test_cmp expect actual &&
+
+       test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-messages side1 side2 >out2 &&
+       sed 1d <out2 >actual2 &&
+       test_cmp actual actual2
+'
+
+test_expect_success 'merge-tree --no-unique-conflicts' '
+       test_expect_code 1 git merge-tree --write-tree --conflict-format="%(path)" --no-unique-conflicts --no-messages side1 side2 >out &&
+       sed 1d <out >actual &&
+       cat >expect <<-\EOF &&
+       greeting
+       greeting
+       greeting
+       whatever~side1
+       whatever~side1
+       EOF
+       test_cmp expect actual
+'
+
 test_expect_success 'Just the conflicted files without the messages' '
-       test_expect_code 1 git merge-tree --write-tree --no-messages --exclude-modes-oids-stages side1 side2 >out &&
+       test_expect_code 1 git merge-tree --write-tree --no-messages --conflict-format="%(path)" side1 side2 >out &&
        sed -e "s/[0-9a-f]\{40,\}/HASH/g" out >actual &&
 
        test_write_lines HASH greeting whatever~side1 >expect &&
#111 lore raw

PATCH v2 12/13 merge-tree: add a --allow-unrelated-histories flag

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Folks may want to merge histories that have no common ancestry; provide a flag with the same name as used by git merge to allow this.

---
 Documentation/git-merge-tree.txt |  5 +++++
 builtin/merge-tree.c             |  7 ++++++-
 t/t4301-merge-tree-write-tree.sh | 24 +++++++++++++++++++++++-
 3 files changed, 34 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +5 -0
index 55bb7bc61c1..d35710c81d5 100644
@@ -49,6 +49,11 @@ OPTIONS
 	default is to include these messages if there are merge
 	conflicts, and to omit them otherwise.
 
+--allow-unrelated-histories::
+	merge-tree will by default error out if the two branches specified
+	share no common history.  This flag can be given to override that
+	check and make the merge proceed anyway.
+
 OUTPUT
 ------
 
builtin/merge-tree.c +6 -1
index dc52cd02dce..cca5075d521 100644
@@ -393,6 +393,7 @@ static int trivial_merge(const char *base,
 
 struct merge_tree_options {
 	int mode;
+	int allow_unrelated_histories;
 	int show_messages;
 	int exclude_modes_oids_stages;
 };
@@ -430,7 +431,7 @@ static int real_merge(struct merge_tree_options *o,
 	 * merge_incore_recursive in merge-ort.h
 	 */
 	common = get_merge_bases(parent1, parent2);
-	if (!common)
+	if (!common && !o->allow_unrelated_histories)
 		die(_("refusing to merge unrelated histories"));
 	for (j = common; j; j = j->next)
 		commit_list_insert(j->item, &merge_bases);
@@ -494,6 +495,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			   &o.exclude_modes_oids_stages,
 			   N_("list conflicted files without modes/oids/stages"),
 			   PARSE_OPT_NONEG),
+		OPT_BOOL_F(0, "allow-unrelated-histories",
+			   &o.allow_unrelated_histories,
+			   N_("allow merging unrelated histories"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};
 
t/t4301-merge-tree-write-tree.sh +23 -1
index 1572f460da0..996bdfaab7d 100755
@@ -38,7 +38,13 @@ test_expect_success setup '
 	>whatever/empty &&
 	git add numbers greeting whatever/empty &&
 	test_tick &&
-	git commit -m other-modifications
+	git commit -m other-modifications &&
+
+	git switch --orphan unrelated &&
+	>something-else &&
+	git add something-else &&
+	test_tick &&
+	git commit -m first-commit
 '
 
 test_expect_success 'Content merge and a few conflicts' '
@@ -139,4 +145,20 @@ test_expect_success 'Check conflicted oids and modes without messages' '
 	test_cmp conflicted-file-info actual
 '
 
+test_expect_success 'error out by default for unrelated histories' '
+	test_expect_code 128 git merge-tree --write-tree side1 unrelated 2>error &&
+
+	grep "refusing to merge unrelated histories" error
+'
+
+test_expect_success 'can override merge of unrelated histories' '
+	git merge-tree --write-tree --allow-unrelated-histories side1 unrelated >tree &&
+	TREE=$(cat tree) &&
+
+	git rev-parse side1:numbers side1:greeting side1:whatever unrelated:something-else >expect &&
+	git rev-parse $TREE:numbers $TREE:greeting $TREE:whatever $TREE:something-else >actual &&
+
+	test_cmp expect actual
+'
+
 test_done
Signature · 1 line
gitgitgadget
#112 lore raw

Re: [PATCH v2 12/13] merge-tree: add a --allow-unrelated-histories flag

builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 437

Quoted 9 lines · Elijah Newren via GitGitGadget
builtin/merge-tree.c +1 -1
@@ -430,7 +431,7 @@ static int real_merge(struct merge_tree_options *o,
 	 * merge_incore_recursive in merge-ort.h
 	 */
 	common = get_merge_bases(parent1, parent2);
-	if (!common)
+	if (!common && !o->allow_unrelated_histories)
 		die(_("refusing to merge unrelated histories"));
 	for (j = common; j; j = j->next)
 		commit_list_insert(j->item, &merge_bases);

Curious. This step _adds_ an "--allow" option from the command line, but we actually did not have to die() when seeing that there is no common ancestor before this step. The end result is OK either way.

Quoted 10 lines
builtin/merge-tree.c +4 -0
@@ -494,6 +495,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 			   &o.exclude_modes_oids_stages,
 			   N_("list conflicted files without modes/oids/stages"),
 			   PARSE_OPT_NONEG),
+		OPT_BOOL_F(0, "allow-unrelated-histories",
+			   &o.allow_unrelated_histories,
+			   N_("allow merging unrelated histories"),
+			   PARSE_OPT_NONEG),
 		OPT_END()
 	};
#113 lore raw

PATCH v2 13/13 git-merge-tree.txt: add a section on potentional usage mistakes

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

---
 Documentation/git-merge-tree.txt | 46 ++++++++++++++++++++++++++++++++
 1 file changed, 46 insertions(+)
Documentation/git-merge-tree.txt +46 -0
index d35710c81d5..b97ddc58a7a 100644
@@ -133,6 +133,52 @@ that they'd have access to if using `git merge`:
   * any messages that would have been printed to stdout (the <Informational
     messages>)
 
+MISTAKES TO AVOID
+-----------------
+
+Do NOT look through the resulting toplevel tree to try to find which
+files conflict; parse the <Conflicted file info> section instead.  Not
+only would parsing an entire tree be horrendously slow in large
+repositories, there are numerous types of conflicts not representable by
+conflict markers (modify/delete, mode conflict, binary file changed on
+both sides, file/directory conflicts, various rename conflict
+permutations, etc.)
+
+Do NOT interpret an empty <Conflicted file info> list as a clean merge;
+check the exit status.  A merge can have conflicts without having
+individual files conflict (there are a few types of directory rename
+conflicts that fall into this category, and others might also be added
+in the future).
+
+Do NOT attempt to guess or make the user guess the conflict types from
+the <Conflicted file info> list.  The information there is insufficient
+to do so.  For example: Rename/rename(1to2) conflicts (both sides
+renamed the same file differently) will result in three different file
+having higher order stages (but each only has one higher order stage),
+with no way (short of the <Informational messages> section) to determine
+which three files are related.  File/directory conflicts also result in
+a file with exactly one higher order stage.
+Possibly-involved-in-directory-rename conflicts (when
+"merge.directoryRenames" is unset or set to "conflicts") also result in
+a file with exactly one higher order stage.  In all cases, the
+<Informational messages> section has the necessary info, though it is
+not designed to be machine parseable.
+
+Do NOT assume all filenames listed in the <Informational messages>
+section had conflicts.  Messages can be included for files that have no
+conflicts, such as "Auto-merging <file>".
+
+AVOID taking the OIDS from the <Conflicted file info> and re-merging
+them to present the conflicts to the user.  This will lose information.
+Instead, look up the version of the file found within the <OID of
+toplevel tree> and show that instead.  In particular, the latter will
+have conflict markers annotated with the original branch/commit being
+merged and, if renames were involved, the original filename.  While you
+could include the original branch/commit in the conflict marker
+annotations when re-merging, the original filename is not available from
+the <Conflicted file info> and thus you would be losing information that
+might help the user resolve the conflict.
+
 GIT
 ---
 Part of the linkgit:git[1] suite
Signature · 1 line
gitgitgadget
#114 lore raw

v3 cover In-core git merge-tree ("Server side merges")

In reply to Elijah Newren via GitGitGadget

Note: Depends on en/remerge-diff (to avoid a small textual conflict). Still under heavy discussion, though, so no need to pick up yet.

== Basic Summary ==

This series introduces a new mode to git merge-tree allowing it to perform real merges (three-way text content merges, recursive ancestor consolidation, rename detection, proper directory/file conflict handling, etc.) and write the result as a toplevel tree. It doesn't touch the working tree or index, and doesn't create any commits or update any refs.

== Quick Overview ==

* Patches 1-2: preparatory cleanups
* Patches 3-4: implement basic real merges
* Patches 5-9: include informational messages ("CONFLICT" messages and such) in output
* to be honest, patches 5, 6, & 8 may be less relevant since we're now including these messages on stdout anyway...
* Patches 10-13: add ability to include ls-files -u style of info in the output
* Patch 14: support --allow-unrelated-histories
* Patch 15: augment the manual with potential usage mistakes

== Updates Log ==

Updates since v2 (or v4, if you include the rounds at
https://lore.kernel.org/git/[email protected]/):

* Improved patches from Dscho for the diff_warn_rename_limit() handling
* Add a -z option for NUL-terminated conflict info lines (so that filenames do not have to be quoted)

Stuff NOT included that reviewers brought up in earlier rounds:

* Very generic (mode, oid, stage, filename) printing formatting[1]
* Always printing 3 stages for each filename with conflicts[2] [1]
https://lore.kernel.org/git/CABPp-BGnOes7J_piDyBUeuLVm274w4-9G3k0vR-0it3z7TPn_w@mail.gmail.com/
[2]
https://lore.kernel.org/git/CABPp-BG2rMEYBLuBW=0wtpJe4aUFGCFa8D0NTSKz9Sm+CkXPxw@mail.gmail.com/

Updates since v1 (or v3 depending on how you count; thanks to René, Ævar, Christian, Dscho for very helpful feedback):

* New patch from Dscho allowing diff_warn_rename_limit() to print somewhere other than stdout (I hope he's okay with me including his Signed-off-by)
* Now prints filenames relative to prefix, much like ls-files
* Renamed --exclude-oids-and-modes to --exclude-modes-oids-stages and gave it a -l shorthand; I'm wondering if I should just drop this option, though.
* And numerous cleanups, in lots of areas:
* Multiple parse-options cleanups
* Lots of commit message cleanups
* Wording tweaks to the "Description" section of the manual
* Several small code cleanups
* I dropped the RFC label

Updates since original submission v2 (thanks to Christian, Dscho, Ramsay, and René for suggestions and comments):

* Significant changes to output format:
* Flags no longer take a filename for additional output; they write to stdout instead.
* More information included by default when there are conflicts (no need to request it with additional flags, instead flags can be used to suppress it).
* Provide (mode, oid, stage, file) tuples -- i.e. ls-files -u style of information -- when there are conflicts. Add a flag to only list conflicted files if that's preferred.
* Much more thorough manual for git-merge-tree.txt
* Renamed option from --real to --write-tree
* Accept an optional --trivial-merge option to get old style merge-tree behavior
* Allow both --write-tree and --trivial-merge to be omitted since we can deduce which from number of arguments
* Document exit code when the merge cannot be run (so we can distinguish other error cases from conflicts)
* testcase cleanups: test_tick, early skip of test when using recursive backend, variable renames, etc.
* various minor code cleanups
* Add a new --allow-unrelated-histories option (with same meaning as the one used in git merge)
* Rebased on top of en/remerge-diff to avoid a small conflict

Updates since original submission v1 (thanks to Johannes Altmanninger and Fabian for suggestions):

* Fixed a bad patch splitting, and a style issue pointed out by Johannes Altimanninger
* Fixed misleading commit messages in new test cases
* Fixed my comments about how commit-tree could be used to correctly use two -p flags

Elijah Newren (13):
merge-tree: rename merge_trees() to trivial_merge_trees()
merge-tree: move logic for existing merge into new function
merge-tree: add option parsing and initial shell for real merge
function
merge-tree: implement real merges
merge-ort: split out a separate display_update_messages() function
merge-ort: allow update messages to be written to different file
stream
merge-tree: support including merge messages in output
merge-ort: provide a merge_get_conflicted_files() helper function
merge-tree: provide a list of which files have conflicts
merge-tree: provide easy access to ls-files -u style info
merge-tree: allow ls-files -u style info to be NUL terminated
merge-tree: add a --allow-unrelated-histories flag git-merge-tree.txt: add a section on potentional usage mistakes

Johannes Schindelin (2):
Introduce a variant of the warning() function that takes a FILE *
diff: allow diff_warn_rename_limit to write somewhere besides stderr

 Documentation/git-merge-tree.txt | 192 +++++++++++++++++++++++++++--
 builtin/merge-tree.c             | 164 +++++++++++++++++++++++--
 diff.c                           |  13 +-
 diff.h                           |   3 +-
 git-compat-util.h                |   1 +
 git.c                            |   2 +-
 merge-ort.c                      | 109 +++++++++++------
 merge-ort.h                      |  30 +++++
 merge-recursive.c                |   3 +-
 t/t4301-merge-tree-write-tree.sh | 204 +++++++++++++++++++++++++++++++
 usage.c                          |  14 +++
 11 files changed, 667 insertions(+), 68 deletions(-)
 create mode 100755 t/t4301-merge-tree-write-tree.sh

base-commit: ea5df61cf358d3c831189e2f04863abc2157e3e1
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1122%2Fnewren%2Fin-core-merge-tree-v3
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1122/newren/in-core-merge-tree-v3
Pull-Request: https://github.com/gitgitgadget/git/pull/1122

Range-diff vs v2:

  1:  4a7cd5542bb =  1:  4a7cd5542bb merge-tree: rename merge_trees() to trivial_merge_trees()
  2:  4780ff6784d =  2:  4780ff6784d merge-tree: move logic for existing merge into new function
  3:  63f42df21ae =  3:  63f42df21ae merge-tree: add option parsing and initial shell for real merge function
  4:  02c29f920d0 =  4:  02c29f920d0 merge-tree: implement real merges
  -:  ----------- >  5:  290b42846b5 Introduce a variant of the `warning()` function that takes a `FILE *`
  5:  6fb4f4580a5 !  6:  2083fbe9b2e diff: allow diff_warn_rename_limit to write somewhere besides stdout
     @@ Metadata
      Author: Johannes Schindelin <[email protected]>
      
       ## Commit message ##
     -    diff: allow diff_warn_rename_limit to write somewhere besides stdout
     +    diff: allow diff_warn_rename_limit to write somewhere besides stderr
      
     -    diff_warn_rename_limit() is hardcoded to write to stdout.  Make it
     -    accept an output location parameter to make it more flexible.
     +    diff_warn_rename_limit() is hardcoded to write to stderr.  Make it
     +    accept a file stream parameter to make it more flexible.
      
          Signed-off-by: Johannes Schindelin <[email protected]>
          Signed-off-by: Elijah Newren <[email protected]>
     @@ diff.c: static const char rename_limit_advice[] =
      +void diff_warn_rename_limit(const char *varname, int needed, int degraded_cc,
      +			    FILE *out)
       {
     --	fflush(stdout);
     -+	const char *fmt = NULL;
     -+
     + 	fflush(stdout);
       	if (degraded_cc)
      -		warning(_(degrade_cc_to_c_warning));
     -+		fmt = _(degrade_cc_to_c_warning);
     ++		warning_fp(out, _(degrade_cc_to_c_warning));
       	else if (needed)
      -		warning(_(rename_limit_warning));
     -+		fmt = _(rename_limit_warning);
     ++		warning_fp(out, _(rename_limit_warning));
       	else
       		return;
     ++
     ++
       	if (0 < needed)
      -		warning(_(rename_limit_advice), varname, needed);
     -+		fmt = _(rename_limit_advice);
     -+
     -+	fflush(out);
     -+	if (out == stdout)
     -+		warning(fmt, varname, needed);
     -+	else
     -+		fprintf(out, fmt, varname, needed);
     ++		warning_fp(out, _(rename_limit_advice), varname, needed);
       }
       
       static void create_filepairs_for_header_only_notifications(struct diff_options *o)
     @@ diff.c: int diff_result_code(struct diff_options *opt, int status)
       	diff_warn_rename_limit("diff.renameLimit",
       			       opt->needed_rename_limit,
      -			       opt->degraded_cc_to_c);
     -+			       opt->degraded_cc_to_c, stdout);
     ++			       opt->degraded_cc_to_c, stderr);
       	if (!opt->flags.exit_with_status &&
       	    !(opt->output_format & DIFF_FORMAT_CHECKDIFF))
       		return status;
     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
       		/* Also include needed rename limit adjustment now */
       		diff_warn_rename_limit("merge.renamelimit",
      -				       opti->renames.needed_limit, 0);
     -+				       opti->renames.needed_limit, 0, stdout);
     ++				       opti->renames.needed_limit, 0, stderr);
       
       		trace2_region_leave("merge", "display messages", opt->repo);
       	}
     @@ merge-recursive.c: static void merge_finalize(struct merge_options *opt)
       		diff_warn_rename_limit("merge.renamelimit",
      -				       opt->priv->needed_rename_limit, 0);
      +				       opt->priv->needed_rename_limit, 0,
     -+				       stdout);
     ++				       stderr);
       	FREE_AND_NULL(opt->priv);
       }

6: 28368c03898 ! 7: 1be858e6aa6 merge-ort: split out a separate display_update_messages() function

     @@ merge-ort.c: static int record_conflicted_index_entries(struct merge_options *op
      +
      +	/* Also include needed rename limit adjustment now */
      +	diff_warn_rename_limit("merge.renamelimit",
     -+			       opti->renames.needed_limit, 0, stdout);
     ++			       opti->renames.needed_limit, 0, stderr);
      +
      +	trace2_region_leave("merge", "display messages", opt->repo);
      +}
     @@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
      -
      -		/* Also include needed rename limit adjustment now */
      -		diff_warn_rename_limit("merge.renamelimit",
     --				       opti->renames.needed_limit, 0, stdout);
     +-				       opti->renames.needed_limit, 0, stderr);
      -
      -		trace2_region_leave("merge", "display messages", opt->repo);
      -	}

7: 593d0c00b57 ! 8: 04c3bdc44d2 merge-ort: allow update messages to be written to different file stream
@@ Commit message
merge-ort: allow update messages to be written to different file stream

This modifies the new display_update_messages() function to allow
- printing to somewhere other than stdout.
+ printing to somewhere other than stdout. It also consolidates the
+ location of the diff_warn_rename_limit() message with the rest of the
+ CONFLICT and other update messages to all go to the same stream.

@@ merge-ort.c: void merge_display_update_messages(struct merge_options *opt,

/* Also include needed rename limit adjustment now */ diff_warn_rename_limit("merge.renamelimit",
-- opti->renames.needed_limit, 0, stdout);
+- opti->renames.needed_limit, 0, stderr);
+ opti->renames.needed_limit, 0, stream);

trace2_region_leave("merge", "display messages", opt->repo);

  8:  d0d30e92ecd =  9:  c8ed002408d merge-tree: support including merge messages in output
  9:  9c2334ae9f2 = 10:  1c2a3f5ef63 merge-ort: provide a merge_get_conflicted_files() helper function
 10:  243134dc247 = 11:  9c2389eef0e merge-tree: provide a list of which files have conflicts
 11:  c322e4c6938 = 12:  2188a8ca1e7 merge-tree: provide easy access to `ls-files -u` style info
  -:  ----------- > 13:  52339b396fa merge-tree: allow `ls-files -u` style info to be NUL terminated
 12:  25677d5038c ! 14:  c854ecb5f4a merge-tree: add a --allow-unrelated-histories flag

@@ t/t4301-merge-tree-write-tree.sh: test_expect_success setup '
'

test_expect_success 'Content merge and a few conflicts' '
-@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'Check conflicted oids and modes without messages' '
- test_cmp conflicted-file-info actual
+@@ t/t4301-merge-tree-write-tree.sh: test_expect_success 'NUL terminated conflicted file "lines"' '
+ test_cmp expect actual
'

+test_expect_success 'error out by default for unrelated histories' '
13: e7c63425a0e = 15: bc8591bbb63 git-merge-tree.txt: add a section on potentional usage mistakes

Signature · 1 line
gitgitgadget
#115 lore raw

PATCH v3 1/15 merge-tree: rename merge_trees() to trivial_merge_trees()

From: Elijah Newren <[email protected]>

merge-recursive.h defined its own merge_trees() function, different than the one found in builtin/merge-tree.c. That was okay in the past, but we want merge-tree to be able to use the merge-ort functions, which will end up including merge-recursive.h. Rename the function found in builtin/merge-tree.c to avoid the conflict.

---
 builtin/merge-tree.c | 8 ++++----
 1 file changed, 4 insertions(+), 4 deletions(-)
builtin/merge-tree.c +4 -4
index 5dc94d6f880..06f9eee9f78 100644
@@ -28,7 +28,7 @@ static void add_merge_entry(struct merge_list *entry)
 	merge_result_end = &entry->next;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base);
+static void trivial_merge_trees(struct tree_desc t[3], const char *base);
 
 static const char *explanation(struct merge_list *entry)
 {
@@ -225,7 +225,7 @@ static void unresolved_directory(const struct traverse_info *info,
 	buf2 = fill_tree_descriptor(r, t + 2, ENTRY_OID(n + 2));
 #undef ENTRY_OID
 
-	merge_trees(t, newbase);
+	trivial_merge_trees(t, newbase);
 
 	free(buf0);
 	free(buf1);
@@ -342,7 +342,7 @@ static int threeway_callback(int n, unsigned long mask, unsigned long dirmask, s
 	return mask;
 }
 
-static void merge_trees(struct tree_desc t[3], const char *base)
+static void trivial_merge_trees(struct tree_desc t[3], const char *base)
 {
 	struct traverse_info info;
 
@@ -378,7 +378,7 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
-	merge_trees(t, "");
+	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
 	free(buf3);
Signature · 1 line
gitgitgadget
#116 lore raw

PATCH v3 2/15 merge-tree: move logic for existing merge into new function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

In preparation for adding a non-trivial merge capability to merge-tree, move the existing merge logic for trivial merges into a new function.

---
 builtin/merge-tree.c | 12 ++++++++----
 1 file changed, 8 insertions(+), 4 deletions(-)
builtin/merge-tree.c +8 -4
index 06f9eee9f78..914ec960b7e 100644
@@ -366,15 +366,12 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+static int trivial_merge(int argc, const char **argv)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	if (argc != 4)
-		usage(merge_tree_usage);
-
 	buf1 = get_tree_descriptor(r, t+0, argv[1]);
 	buf2 = get_tree_descriptor(r, t+1, argv[2]);
 	buf3 = get_tree_descriptor(r, t+2, argv[3]);
@@ -386,3 +383,10 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 	show_result();
 	return 0;
 }
+
+int cmd_merge_tree(int argc, const char **argv, const char *prefix)
+{
+	if (argc != 4)
+		usage(merge_tree_usage);
+	return trivial_merge(argc, argv);
+}
Signature · 1 line
gitgitgadget
#117 lore raw

PATCH v3 3/15 merge-tree: add option parsing and initial shell for real merge function

In reply to Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Note that real merges differ from trivial merges in that they handle:
- three way content merges
- recursive ancestor consolidation
- renames
- proper directory/file conflict handling
- etc.
Basically all the stuff you'd expect from git merge, just without updating the index and working tree. The initial shell added here does nothing more than die with "real merges are not yet implemented", but that will be fixed in subsequent commits.

---
 builtin/merge-tree.c | 61 +++++++++++++++++++++++++++++++++++++-------
 git.c                |  2 +-
 2 files changed, 53 insertions(+), 10 deletions(-)
builtin/merge-tree.c +52 -9
index 914ec960b7e..e98ec8a9f1d 100644
@@ -3,13 +3,12 @@
 #include "tree-walk.h"
 #include "xdiff-interface.h"
 #include "object-store.h"
+#include "parse-options.h"
 #include "repository.h"
 #include "blob.h"
 #include "exec-cmd.h"
 #include "merge-blobs.h"
 
-static const char merge_tree_usage[] = "git merge-tree <base-tree> <branch1> <branch2>";
-
 struct merge_list {
 	struct merge_list *next;
 	struct merge_list *link;	/* other stages for this object */
@@ -366,15 +365,17 @@ static void *get_tree_descriptor(struct repository *r,
 	return buf;
 }
 
-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+			 const char *branch1,
+			 const char *branch2)
 {
 	struct repository *r = the_repository;
 	struct tree_desc t[3];
 	void *buf1, *buf2, *buf3;
 
-	buf1 = get_tree_descriptor(r, t+0, argv[1]);
-	buf2 = get_tree_descriptor(r, t+1, argv[2]);
-	buf3 = get_tree_descriptor(r, t+2, argv[3]);
+	buf1 = get_tree_descriptor(r, t+0, base);
+	buf2 = get_tree_descriptor(r, t+1, branch1);
+	buf3 = get_tree_descriptor(r, t+2, branch2);
 	trivial_merge_trees(t, "");
 	free(buf1);
 	free(buf2);
@@ -384,9 +385,51 @@ static int trivial_merge(int argc, const char **argv)
 	return 0;
 }
 
+struct merge_tree_options {
+	int mode;
+};
+
+static int real_merge(struct merge_tree_options *o,
+		      const char *branch1, const char *branch2)
+{
+	die(_("real merges are not yet implemented"));
+}
+
 int cmd_merge_tree(int argc, const char **argv, const char *prefix)
 {
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {
+		OPT_CMDMODE(0, "write-tree", &o.mode,
+			    N_("do a real merge instead of a trivial merge"),
+			    'w'),
+		OPT_CMDMODE(0, "trivial-merge", &o.mode,
+			    N_("do a trivial merge only"), 't'),
+		OPT_END()
+	};
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,
+			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (o.mode) {
+		expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.mode = (argc == 2 ? 'w' : 't');
+	}
+
+	/* Do the relevant type of merge */
+	if (o.mode == 'w')
+		return real_merge(&o, argv[0], argv[1]);
+	else
+		return trivial_merge(argv[0], argv[1], argv[2]);
 }
git.c +1 -1
index 5ff21be21f3..6090a1289db 100644
@@ -558,7 +558,7 @@ static struct cmd_struct commands[] = {
 	{ "merge-recursive-ours", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-recursive-theirs", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
 	{ "merge-subtree", cmd_merge_recursive, RUN_SETUP | NEED_WORK_TREE | NO_PARSEOPT },
-	{ "merge-tree", cmd_merge_tree, RUN_SETUP | NO_PARSEOPT },
+	{ "merge-tree", cmd_merge_tree, RUN_SETUP },
 	{ "mktag", cmd_mktag, RUN_SETUP | NO_PARSEOPT },
 	{ "mktree", cmd_mktree, RUN_SETUP },
 	{ "multi-pack-index", cmd_multi_pack_index, RUN_SETUP },
Signature · 1 line
gitgitgadget
#118 lore raw

Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function

Quoted 7 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Maybe that ship has sailed, but just my 0.02: I thought this whole thing was much less confusing with your initial merge-tree-ort proposal at
https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;
I.e. the end-state of merge-tree.c is that you end up reading largely unrelated code (various static functions only used by one side or another).

But maybe that's all water under the bridge etc, however...

Quoted 24 lines

int cmd_merge_tree(int argc, const char **argv, const char *prefix) {

builtin/merge-tree.c +19 -3
-	if (argc != 4)
-		usage(merge_tree_usage);
-	return trivial_merge(argc, argv);
+	struct merge_tree_options o = { 0 };
+	int expected_remaining_argc;
+
+	const char * const merge_tree_usage[] = {
+		N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+		N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+		NULL
+	};
+	struct option mt_options[] = {
+		OPT_CMDMODE(0, "write-tree", &o.mode,
+			    N_("do a real merge instead of a trivial merge"),
+			    'w'),
+		OPT_CMDMODE(0, "trivial-merge", &o.mode,
+			    N_("do a trivial merge only"), 't'),
+		OPT_END()
+	};
+
+	/* Parse arguments */
+	argc = parse_options(argc, argv, prefix, mt_options,

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 428

Quoted 10 lines
builtin/merge-tree.c +10 -0
+			     merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+	if (o.mode) {
+		expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+		if (argc != expected_remaining_argc)
+			usage_with_options(merge_tree_usage, mt_options);
+	} else {
+		if (argc < 2 || argc > 3)
+			usage_with_options(merge_tree_usage, mt_options);
+		o.mode = (argc == 2 ? 'w' : 't');
+	}

Do we really need to make this interface more special-casey by auto-guessing based on argc what argument you want? I.e. instead of usage like:

	N_("git merge-tree [--write-tree] <branch1> <branch2>"),
	N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),

Wouldn't it be simpler to just have the equivalent of:

	# old
        git merge-tree ...
        # new
        git merge-tree --new-thing ...

And not have to look at ... to figure out if we're dispatching to the new or old thing.

#119 lore raw

Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function

Quoted 11 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Quoted 6 lines

Maybe that ship has sailed, but just my 0.02: I thought this whole thing was much less confusing with your initial merge-tree-ort proposal at
https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;
I.e. the end-state of merge-tree.c is that you end up reading largely unrelated code (various static functions only used by one side or another).

Christian's merge-tree-ort proposal?

Quoted 46 lines

But maybe that's all water under the bridge etc, however...

Quoted 24 lines

int cmd_merge_tree(int argc, const char **argv, const char *prefix) {

builtin/merge-tree.c +19 -3
-     if (argc != 4)
-             usage(merge_tree_usage);
-     return trivial_merge(argc, argv);
+     struct merge_tree_options o = { 0 };
+     int expected_remaining_argc;
+
+     const char * const merge_tree_usage[] = {
+             N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+             N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+             NULL
+     };
+     struct option mt_options[] = {
+             OPT_CMDMODE(0, "write-tree", &o.mode,
+                         N_("do a real merge instead of a trivial merge"),
+                         'w'),
+             OPT_CMDMODE(0, "trivial-merge", &o.mode,
+                         N_("do a trivial merge only"), 't'),
+             OPT_END()
+     };
+
+     /* Parse arguments */
+     argc = parse_options(argc, argv, prefix, mt_options,

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 428

Quoted 10 lines
builtin/merge-tree.c +10 -0
+                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+     if (o.mode) {
+             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+             if (argc != expected_remaining_argc)
+                     usage_with_options(merge_tree_usage, mt_options);
+     } else {
+             if (argc < 2 || argc > 3)
+                     usage_with_options(merge_tree_usage, mt_options);
+             o.mode = (argc == 2 ? 'w' : 't');
+     }

Do we really need to make this interface more special-casey by auto-guessing based on argc what argument you want? I.e. instead of usage like:

        N_("git merge-tree [--write-tree] <branch1> <branch2>"),
        N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),

Wouldn't it be simpler to just have the equivalent of:

Quoted 7 lines
        # old
        git merge-tree ...
        # new
        git merge-tree --new-thing ...

And not have to look at ... to figure out if we're dispatching to the new or old thing.

You seem to be focusing on code simplicity? Sure, that'd be simpler code, it'd just be a less useful feature.

I think passing --write-tree all the time would be an annoyance. I don't see why anyone would ever use the other mode. However, for as long as both exist in the manual, it makes the manual easier to explain to users, and example testcases more self-documenting by having the flag there. That's the sole purpose of the flag.

I'm never going to actually use it when I invoke it from the command line. And I suspect most would leave it off.

#120 lore raw

Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function

Quoted 79 lines · Elijah Newren
Quoted 11 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Quoted 6 lines

Maybe that ship has sailed, but just my 0.02: I thought this whole thing was much less confusing with your initial merge-tree-ort proposal at
https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;
I.e. the end-state of merge-tree.c is that you end up reading largely unrelated code (various static functions only used by one side or another).

Christian's merge-tree-ort proposal?

Quoted 46 lines

But maybe that's all water under the bridge etc, however...

Quoted 24 lines

int cmd_merge_tree(int argc, const char **argv, const char *prefix) {

builtin/merge-tree.c +19 -3
-     if (argc != 4)
-             usage(merge_tree_usage);
-     return trivial_merge(argc, argv);
+     struct merge_tree_options o = { 0 };
+     int expected_remaining_argc;
+
+     const char * const merge_tree_usage[] = {
+             N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+             N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+             NULL
+     };
+     struct option mt_options[] = {
+             OPT_CMDMODE(0, "write-tree", &o.mode,
+                         N_("do a real merge instead of a trivial merge"),
+                         'w'),
+             OPT_CMDMODE(0, "trivial-merge", &o.mode,
+                         N_("do a trivial merge only"), 't'),
+             OPT_END()
+     };
+
+     /* Parse arguments */
+     argc = parse_options(argc, argv, prefix, mt_options,

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 428

Quoted 10 lines
builtin/merge-tree.c +10 -0
+                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+     if (o.mode) {
+             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+             if (argc != expected_remaining_argc)
+                     usage_with_options(merge_tree_usage, mt_options);
+     } else {
+             if (argc < 2 || argc > 3)
+                     usage_with_options(merge_tree_usage, mt_options);
+             o.mode = (argc == 2 ? 'w' : 't');
+     }

Do we really need to make this interface more special-casey by auto-guessing based on argc what argument you want? I.e. instead of usage like:

        N_("git merge-tree [--write-tree] <branch1> <branch2>"),
        N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),

Wouldn't it be simpler to just have the equivalent of:

Quoted 7 lines
        # old
        git merge-tree ...
        # new
        git merge-tree --new-thing ...

And not have to look at ... to figure out if we're dispatching to the new or old thing.

You seem to be focusing on code simplicity? Sure, that'd be simpler code, it'd just be a less useful feature.

Quoted 8 lines

I think passing --write-tree all the time would be an annoyance. I don't see why anyone would ever use the other mode. However, for as long as both exist in the manual, it makes the manual easier to explain to users, and example testcases more self-documenting by having the flag there. That's the sole purpose of the flag.

I'm never going to actually use it when I invoke it from the command line. And I suspect most would leave it off.

...also, even if we did require the --write-tree flag, we'd still have to look at argc. Since the option parsing handles both modes, someone could leave off --write-tree, but include a bunch of other options that only make sense with --write-tree. Individually checking the setting of every extra flag along with write_tree is a royal pain and I don't want to repeat that for each new option added. Simply checking argc allows you to provide an error message if the user does that.

(And I think it's sad that in Git we often forgot to warn and notify users of options that are only functional with certain other arguments; it makes it harder for users to figure out, and has in the past even made it harder for other developers to figure out what was meant and how things are to be used. I think I've seen multiple Git devs be confused over ls-files --directory and --no-empty-directory options, assuming they'd do something sensible for tracked files, when in fact those arguments are simply ignored because they are only modifiers for how untracked files are treated.)

#121 lore raw

Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function

Quoted 98 lines · Elijah Newren
Quoted 79 lines · Elijah Newren
Quoted 11 lines · Ævar Arnfjörð Bjarmason
Quoted 7 lines · Elijah Newren via GitGitGadget

From: Elijah Newren <[email protected]>

Let merge-tree accept a --write-tree parameter for choosing real merges instead of trivial merges, and accept an optional --trivial-merge option to get the traditional behavior. Note that these accept different numbers of arguments, though, so these names need not actually be used.

Quoted 6 lines

Maybe that ship has sailed, but just my 0.02: I thought this whole thing was much less confusing with your initial merge-tree-ort proposal at
https://lore.kernel.org/git/CABPp-BEeBpJoU4yXdfA6vRAYVAUbd2gRhEV6j4VEqoqcu=FGSw@mail.gmail.com/;
I.e. the end-state of merge-tree.c is that you end up reading largely unrelated code (various static functions only used by one side or another).

Christian's merge-tree-ort proposal?

Quoted 46 lines

But maybe that's all water under the bridge etc, however...

Quoted 24 lines

int cmd_merge_tree(int argc, const char **argv, const char *prefix) {

builtin/merge-tree.c +19 -3
-     if (argc != 4)
-             usage(merge_tree_usage);
-     return trivial_merge(argc, argv);
+     struct merge_tree_options o = { 0 };
+     int expected_remaining_argc;
+
+     const char * const merge_tree_usage[] = {
+             N_("git merge-tree [--write-tree] <branch1> <branch2>"),
+             N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),
+             NULL
+     };
+     struct option mt_options[] = {
+             OPT_CMDMODE(0, "write-tree", &o.mode,
+                         N_("do a real merge instead of a trivial merge"),
+                         'w'),
+             OPT_CMDMODE(0, "trivial-merge", &o.mode,
+                         N_("do a trivial merge only"), 't'),
+             OPT_END()
+     };
+
+     /* Parse arguments */
+     argc = parse_options(argc, argv, prefix, mt_options,

builtin/merge-tree.c static int trivial_merge(int argc, const char **argv) · line 428

Quoted 10 lines
builtin/merge-tree.c +10 -0
+                          merge_tree_usage, PARSE_OPT_STOP_AT_NON_OPTION);
+     if (o.mode) {
+             expected_remaining_argc = (o.mode == 'w' ? 2 : 3);
+             if (argc != expected_remaining_argc)
+                     usage_with_options(merge_tree_usage, mt_options);
+     } else {
+             if (argc < 2 || argc > 3)
+                     usage_with_options(merge_tree_usage, mt_options);
+             o.mode = (argc == 2 ? 'w' : 't');
+     }

Do we really need to make this interface more special-casey by auto-guessing based on argc what argument you want? I.e. instead of usage like:

        N_("git merge-tree [--write-tree] <branch1> <branch2>"),
        N_("git merge-tree [--trivial-merge] <base-tree> <branch1> <branch2>"),

Wouldn't it be simpler to just have the equivalent of:

Quoted 7 lines
        # old
        git merge-tree ...
        # new
        git merge-tree --new-thing ...

And not have to look at ... to figure out if we're dispatching to the new or old thing.

You seem to be focusing on code simplicity? Sure, that'd be simpler code, it'd just be a less useful feature.

Quoted 8 lines

I think passing --write-tree all the time would be an annoyance. I don't see why anyone would ever use the other mode. However, for as long as both exist in the manual, it makes the manual easier to explain to users, and example testcases more self-documenting by having the flag there. That's the sole purpose of the flag.

I'm never going to actually use it when I invoke it from the command line. And I suspect most would leave it off.

...also, even if we did require the --write-tree flag, we'd still have to look at argc. Since the option parsing handles both modes, someone could leave off --write-tree, but include a bunch of other options that only make sense with --write-tree. Individually checking the setting of every extra flag along with write_tree is a royal pain and I don't want to repeat that for each new option added. Simply checking argc allows you to provide an error message if the user does that.

Quoted 9 lines

(And I think it's sad that in Git we often forgot to warn and notify users of options that are only functional with certain other arguments; it makes it harder for users to figure out, and has in the past even made it harder for other developers to figure out what was meant and how things are to be used. I think I've seen multiple Git devs be confused over ls-files --directory and --no-empty-directory options, assuming they'd do something sensible for tracked files, when in fact those arguments are simply ignored because they are only modifiers for how untracked files are treated.)

There's a much simpler way to do what you're trying to do here which is to only parse --write-tree, and as soon as you have that pass off two one function or the other, and have those functions call parse_options().

This is how builtin/{bundle,stash,commit-graph}.c etc. solve the same problem. It avoids having to the sort of check you did in 09/15:

	+	if (o.mode == 't' && original_argc < argc)
	+		die(_("--trivial-merge is incompatible with all other options"));

The builtin/submodule--helper.c then does it with a first argument that --looks-like-an-option, but is really the same sort of sub-command selector.

Which, at least for me brings this back to liking your merge-tree-ort (or merge-tree-ng, merge-tree-new or whatever) better. I.e. both for the code & manual we effectively have two completely different commands here anyway. Splitting them up would make both the code simpler, and also what we have to explain to users.

#122 lore raw

Re: [PATCH v3 03/15] merge-tree: add option parsing and initial shell for real merge function

Quoted 3 lines · Ævar Arnfjörð Bjarmason

On Thu, Feb 03 2022, Elijah Newren wrote:

[...]

Quoted 19 lines
Quoted 9 lines

...also, even if we did require the --write-tree flag, we'd still have to look at argc. Since the option parsing handles both modes, someone could leave off --write-tree, but include a bunch of other options that only make sense with --write-tree. Individually checking the setting of every extra flag along with write_tree is a royal pain and I don't want to repeat that for each new option added. Simply checking argc allows you to provide an error message if the user does that.

Quoted 9 lines

(And I think it's sad that in Git we often forgot to warn and notify users of options that are only functional with certain other arguments; it makes it harder for users to figure out, and has in the past even made it harder for other developers to figure out what was meant and how things are to be used. I think I've seen multiple Git devs be confused over ls-files --directory and --no-empty-directory options, assuming they'd do something sensible for tracked files, when in fact those arguments are simply ignored because they are only modifiers for how untracked files are treated.)

Quoted 4 lines

There's a much simpler way to do what you're trying to do here which is to only parse --write-tree, and as soon as you have that pass off two one function or the other, and have those functions call parse_options().

But that makes --write-tree a mandatory argument when trying to use that mode, right? If so, that is not a simpler way to do what I'm trying to do at all; it breaks my intended usage.

--write-tree is a documentation-only construct that users should never have to pass.

Also, what happens if we remove the --trivial-merge flag and its whole mode after a sufficient deprecation period? Would the --write-tree parameter remain required in your model to select the only existing mode, simply due to us having gone through a transition period?