RFC: Server side merges (no ref updating, no commit creating, no touching worktree or index)
cover RFC: Server side merges (no ref updating, no commit creating, no touching worktree or index)
(NOTE for Junio: This series has a minor conflict with en/remerge-diff -- this series moves a code block into a new function, but en/remerge-diff adds a BUG() message to that code block. But this series is just RFC, so you may want to wait to pick it up.)
NOTE2: A preliminary version of this series was discussed here:
https://lore.kernel.org/git/[email protected]/
This series introduces a new option to git-merge-tree: --real (best name I could come up with). This new option is designed to allow a server-side "real" merge (or allow folks client-side to do merges with branches they don't even have checked out). Real merges differ from trivial merges in that they handle:
* three way content merges
* recursive ancestor consolidation
* renames
* proper directory/file conflict handling
* etc.
The reason this is different from merge is that merge-tree does NOT:
* Read/write/update any working tree (and assumes there probably isn't one)
* Read/write/update any index (and assumes there probably isn't one)
* Create a commit object
* Update any refs
This series attempts to guess what kind of output would be wanted, basically choosing:
* clean merge or conflict signalled via exit status
* stdout consists solely of printing the hash of the resulting tree (though that tree may include files that have conflict markers)
* new optional --messages flag for specifying a file where informational messages (e.g. conflict notices and files involved in three-way-content merges) can be written; by default, this output is simply discarded
* new optional --conflicted-list flag for specifying a file where the names of conflicted-files can be written in a NUL-character-separated list
This design means it's basically just a low-level tool that other scripts would use and do additional work with. Perhaps something like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $BRANCH2) git update-ref $BRANCH1 $NEWCOMMIT
Elijah Newren (8):
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 saving merge messages to a separate file
merge-tree: provide an easy way to access which files have conflicts
Documentation/git-merge-tree.txt | 32 +++++-- builtin/merge-tree.c | 152 ++++++++++++++++++++++++++++--- git.c | 2 +- merge-ort.c | 85 ++++++++++------- merge-ort.h | 12 +++ t/t4301-merge-tree-real.sh | 108 ++++++++++++++++++++++ 6 files changed, 333 insertions(+), 58 deletions(-) create mode 100755 t/t4301-merge-tree-real.sh
base-commit: 2ae0a9cb8298185a94e5998086f380a355dd8907
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1114%2Fnewren%2Fmerge-into-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1114/newren/merge-into-v1
Pull-Request: https://github.com/git/git/pull/1114
Signature · 1 line
gitgitgadget
PATCH 1/8 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
@@ -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, sreturn 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
PATCH 2/8 merge-tree: move logic for existing merge into new function
In reply to Elijah Newren via GitGitGadgetFrom: 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 | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)
builtin/merge-tree.c +8 -5
@@ -366,15 +366,11 @@ 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 +382,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
Re: [PATCH 2/8] merge-tree: move logic for existing merge into new function
Quoted 15 lines · 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.
Signed-off-by Elijah Newren <[email protected]>--- builtin/merge-tree.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)builtin/merge-tree.c +0
-0
builtin/merge-tree.c static void *get_tree_descriptor(struct repository *r, · line 369
Quoted 6 lines
builtin/merge-tree.c +1
-2@@ -366,15 +366,11 @@ 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) {
I guess the brace should probably stay on its own line
Re: [PATCH 2/8] merge-tree: move logic for existing merge into new function
Quoted 20 lines · Johannes Altmanninger
Quoted 18 lines · 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.
Signed-off-by Elijah Newren <[email protected]>--- builtin/merge-tree.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-)builtin/merge-tree.c +0
-0@@ -366,15 +366,11 @@ static void *get_tree_descriptor(struct repository *r,return buf; }
Quoted 5 lines
builtin/merge-tree.c static void *get_tree_descriptor(struct repository *r, · line 369
Quoted 3 lines
builtin/merge-tree.c +1
-2-int cmd_merge_tree (int argc,const char **argv,const char *prefix) -{ +static int trivial_merge (int argc,const char **argv) {I guess the brace should probably stay on its own line
Whoops, indeed. Thanks for spotting.
PATCH 3/8 merge-tree: add option parsing and initial shell for real merge function
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Let merge-tree accept a --real parameter for choosing real merges instead of trivial merges. 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 | 56 +++++++++++++++++++++++++++++++++++++------- git.c | 2 +- 2 files changed, 48 insertions(+), 10 deletions(-)
builtin/merge-tree.c +47 -9
@@ -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,14 +365,16 @@ 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);
@@ -383,9 +384,46 @@ static int trivial_merge(int argc, const char **argv) {return 0 ; } +struct merge_tree_options { +int real; +}; + +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 --real <branch1> <branch2>" ), +N_ ("git merge-tree <base-tree> <branch1> <branch2>" ), +NULL + }; +struct option mt_options[] = { +OPT_BOOL (0 ,"real" , &o.real, +N_ ("do a real merge instead of a trivial merge" )), +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 ); + expected_remaining_argc = (o.real ?2 :3 ); +if (argc != expected_remaining_argc) +usage_with_options (merge_tree_usage, mt_options); + +/* 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
@@ -561,7 +561,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
PATCH 4/8 merge-tree: implement real merges
In reply to Elijah Newren via GitGitGadgetFrom: 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 mean to be used by some higher level script, perhaps in a sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $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 | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.sh
Documentation/git-merge-tree.txt +18 -10
@@ -3,26 +3,34 @@ 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' --real <branch1> <branch2> 'git merge-tree' <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. +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. -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. +The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`. The output will consist solely of the resulting toplevel tree +(which may have files including conflict markers). + +The second form is meant for backward compatibility and will only do a +trival 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. GIT ---
builtin/merge-tree.c +54 -1
67 lines not on this page · raw message
t/t4301-merge-tree-real.sh new file +81 -0
81 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH 4/8] merge-tree: implement real merges
Quoted 41 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) or 1 (conflicts present)This output is mean to be used by some higher level script, perhaps in a sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $BRANCH2) git update-ref $BRANCH1 $NEWCOMMITNote 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.
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-merge-tree.txt | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.shDocumentation/git-merge-tree.txt +0
-0@@ -3,26 +3,34 @@ git-merge-tree(1) NAME
Documentation/git-merge-tree.txt git-merge-tree(1) · line 13
Quoted 10 lines
Documentation/git-merge-tree.txt +2
-1---- -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' --real <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>
This is really exciting. It could replace the merge-machinery of git-revise (which is a "fast rebase" tool).
I think for cherry-pick/rebase we need to specify a custom merge base, would that suit the new form?
Re: [PATCH 4/8] merge-tree: implement real merges
Quoted 48 lines · Johannes Altmanninger
Quoted 46 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) or 1 (conflicts present)This output is mean to be used by some higher level script, perhaps in a sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $BRANCH2) git update-ref $BRANCH1 $NEWCOMMITNote 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.
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-merge-tree.txt | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.shDocumentation/git-merge-tree.txt +1
-1@@ -3,26 +3,34 @@ 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
Quoted 10 lines
Documentation/git-merge-tree.txt git-merge-tree(1) · line 13
Quoted 5 lines
Documentation/git-merge-tree.txt +1
-0SYNOPSIS -------- [verse] +'git merge-tree' --real <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>This is really exciting. It could replace the merge-machinery of git-revise (which is a "fast rebase" tool).
I think for cherry-pick/rebase we need to specify a custom merge base, would that suit the new form?
I'm glad you're excited about it. :-)
I think having a server side tool for replaying commits (which can double as a fast rebase/cherry-pick tool on the client side) is also important, but I think it should be part of a proper builtin, not some script that calls out to merge-tree --real.
merge-tree --real is simpler, though, so I implemented and submitted it first.
Re: [PATCH 4/8] merge-tree: implement real merges
In reply to Elijah Newren via GitGitGadgetQuoted 178 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) or 1 (conflicts present)This output is mean to be used by some higher level script, perhaps in a sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $BRANCH2) git update-ref $BRANCH1 $NEWCOMMITNote 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.
Signed-off-by Elijah Newren <[email protected]>---
Documentation/git-merge-tree.txt | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-)create mode 100755
t/t4301-merge-tree-real.shDocumentation/git-merge-tree.txt +0
-0@@ -3,26 +3,34 @@ git-merge-tree(1)NAME
----Documentation/git-merge-tree.txt +1
-1-git-merge-tree - Show three-way merge without touching index +git-merge-tree - Perform merge without touching index or working treeSYNOPSIS
--------
[verse]
+'git merge-tree' --real <branch1> <branch2>
'git merge-tree' <base-tree> <branch1> <branch2>DESCRIPTION
-----------Documentation/git-merge-tree.txt +16
-9-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. +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. -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. +The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`. The output will consist solely of the resulting toplevel tree +(which may have files including conflict markers). + +The second form is meant for backward compatibility and will only do a +trival 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.GIT
---builtin/merge-tree.c +54
-1@@ -2,6 +2,9 @@#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" #include "parse-options.h" #include "repository.h" @@ -391,7 +394,57 @@ 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" , +_ ("not something we can merge" )); + + parent2 =get_merge_parent (branch2); +if (!parent2) +help_unknown_ref (branch2,"merge" , +_ ("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? */ + +/* +* Get the merge bases, in reverse order; see comment above +* merge_incore_recursive in merge-ort.h +*/ + common =get_merge_bases (parent1, parent2); +for (j = common; j; j = j->next) +commit_list_insert (j->item, &merge_bases); + +/* +* TODO: notify if merging unrelated histories? +if (!common) +fprintf(stderr, _("merging unrelated histories")); +*/ + +merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); +printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); +merge_switch_to_result (&opt,NULL , &result,0 ,0 ); +return result.clean ?0 :1 ; }int cmd_merge_tree (int argc,const char **argv,const char *prefix)t/t4301-merge-tree-real.sh new file +16
-0@@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git merge-tree --real' + +. ./test-lib.sh + +# This test is ort-specific +GIT_TEST_MERGE_ALGORITHM=ort +export GIT_TEST_MERGE_ALGORITHM + +test_expect_success setup ' + test_write_lines 1 2 3 4 5 >numbers && + echo hello >greeting && + echo foo >whatever && + git add numbers greeting whatever && + git commit -m initial &&
t/t4301-merge-tree-real.sh line 26
Quoted 10 lines
t/t4301-merge-tree-real.sh new file +10
-0+ + 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 && + git commit -m rename-and-modify &&
The commit implies a rename as well which I think is missing.
t/t4301-merge-tree-real.sh line 35
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0+ + 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 && + git commit -m remove-and-rename
And this looks more like a remove-and-modify. Is it still a rename when we empty the files content?
Quoted 49 lines
t/t4301-merge-tree-real.sh new file +46
-0+' + +test_expect_success 'Content merge and a few conflicts' ' + git checkout side1^0 && + test_must_fail git merge side2 && + cp .git/AUTO_MERGE EXPECT && + E_TREE=$(cat EXPECT) && + + git reset --hard && + test_must_fail git merge-tree --real side1 side2 >RESULT && + R_TREE=$(cat 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 ${R_TREE}:numbers >actual && + test_cmp expect actual && + + # whatever and whatever~<branch> should have same HASHES + git rev-parse ${E_TREE}:whatever ${E_TREE}:whatever~HEAD >expect && + git rev-parse ${R_TREE}:whatever ${R_TREE}:whatever~side1 >actual && + test_cmp expect actual && + + # greeting should have a merge conflict + git show ${E_TREE}:greeting >tmp && + cat tmp | sed -e s/HEAD/side1/ >expect && + git show ${R_TREE}:greeting >actual && + test_cmp expect actual +' + +test_expect_success 'Barf on misspelled option' ' + # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --real --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 --real side1 side2 side3 2>expect && + + grep "^usage: git merge-tree" expect +' + +test_doneSignature · 1 line
gitgitgadget
Re: [PATCH 4/8] merge-tree: implement real merges
Quoted 10 lines · Fabian Stelzer
Quoted 8 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 10 lines
Quoted 10 lines
t/t4301-merge-tree-real.sh new file +10
-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 && + git commit -m initial && + + git branch side1 && + git branch side2 && +
Quoted 8 lines
t/t4301-merge-tree-real.sh line 26
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +6
-0+ git checkout side1 && + test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + git commit -m rename-and-modify &&The commit implies a rename as well which I think is missing.
Sorry, I revised the testcase (multiple times) and forgot to update this commit message string.
Quoted 11 lines
t/t4301-merge-tree-real.sh line 35
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0+ + 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 && + git commit -m remove-and-renameAnd this looks more like a remove-and-modify.
Likewise.
I'll fix these up; thanks for pointing them out.
PATCH 5/8 merge-ort: split out a separate display_update_messages() function
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
No functional changes included in this patch; it's just a preparatory step in anticipation of wanting to handle the printed messages differently in git merge-tree --real.
--- merge-ort.c | 69 ++++++++++++++++++++++++++++------------------------- merge-ort.h | 8 +++++++ 2 files changed, 44 insertions(+), 33 deletions(-)
merge-ort.c +36 -33
@@ -4197,6 +4197,42 @@ 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; + +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,
@@ -4235,39 +4271,6 @@ 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; - -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); - }merge_finalize (opt, result); }
merge-ort.h +8 -0
@@ -70,6 +70,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
Re: [PATCH 5/8] merge-ort: split out a separate display_update_messages() function
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 in anticipation of wanting to handle the printed messages differently in
git merge-tree --real.
Not quite. You are missing:
+ if (display_update_msgs)
+ merge_display_update_messages(opt, result, stdout);
in merge_switch_to_result(), which you added in the next commit. Not really relevant for the series as a whole but this commit alone breaks some functionality.
Quoted 116 lines
Signed-off-by Elijah Newren <[email protected]>---
merge-ort.c| 69 ++++++++++++++++++++++++++++-------------------------merge-ort.h| 8 +++++++2 files changed, 44 insertions(+), 33 deletions(-)merge-ort.c +36
-33@@ -4197,6 +4197,42 @@ 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; + +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,@@ -4235,39 +4271,6 @@ 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; - -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); - }merge_finalize (opt, result); }merge-ort.h +8
-0@@ -70,6 +70,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
Re: [PATCH 5/8] merge-ort: split out a separate display_update_messages() function
Quoted 11 lines · Fabian Stelzer
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 in anticipation of wanting to handle the printed messages differently in
git merge-tree --real.Not quite. You are missing:
(quoted diff) +2
-0+ if (display_update_msgs) + merge_display_update_messages(opt, result, stdout);
Quoted 1 line
in
merge_switch_to_result(), which you added in the next commit. Not really relevant for the series as a whole but this commit alone breaks some functionality.
Sorry, just saw this was already noted on the next patch.
Quoted 117 lines
Quoted 117 lines
Signed-off-by Elijah Newren <[email protected]>---
merge-ort.c| 69 ++++++++++++++++++++++++++++-------------------------merge-ort.h| 8 +++++++2 files changed, 44 insertions(+), 33 deletions(-)merge-ort.c +36
-33@@ -4197,6 +4197,42 @@ 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; + +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,@@ -4235,39 +4271,6 @@ 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; - -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); - }merge_finalize (opt, result); }merge-ort.h +8
-0@@ -70,6 +70,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
PATCH 6/8 merge-ort: allow update messages to be written to different file stream
In reply to Elijah Newren via GitGitGadgetFrom: 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, 7 insertions(+), 3 deletions(-)
merge-ort.c +5 -2
@@ -4198,7 +4198,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;
@@ -4222,7 +4223,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 );
@@ -4271,6 +4272,8 @@ void merge_switch_to_result(struct merge_options *opt,trace2_region_leave ("merge" ,"write_auto_merge" , opt->repo); } +if (display_update_msgs) +merge_display_update_messages (opt, result, stdout);merge_finalize (opt, result); }
merge-ort.h +2 -1
@@ -76,7 +76,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
Re: [PATCH 6/8] merge-ort: allow update messages to be written to different file stream
Quoted 17 lines · 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.Signed-off-by Elijah Newren <[email protected]>--- merge-ort.c | 7 +++++-- merge-ort.h | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-)merge-ort.c +0
-0
merge-ort.c void merge_switch_to_result(struct merge_options *opt, · line 4276
Quoted 5 lines
merge-ort.c +2
-0@@ -4271,6 +4272,8 @@ void merge_switch_to_result(struct merge_options *opt,trace2_region_leave ("merge" ,"write_auto_merge" , opt->repo); } +if (display_update_msgs) +merge_display_update_messages (opt, result, stdout);
is it intentional that the previous patch doesn't have this call?
Re: [PATCH 6/8] merge-ort: allow update messages to be written to different file stream
Quoted 22 lines · Johannes Altmanninger
Quoted 20 lines · 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.Signed-off-by Elijah Newren <[email protected]>--- merge-ort.c | 7 +++++-- merge-ort.h | 3 ++- 2 files changed, 7 insertions(+), 3 deletions(-)merge-ort.c +0
-0@@ -4271,6 +4272,8 @@ void merge_switch_to_result(struct merge_options *opt,trace2_region_leave ("merge" ,"write_auto_merge" , opt->repo); }
Quoted 4 lines
merge-ort.c void merge_switch_to_result(struct merge_options *opt, · line 4276
Quoted 2 lines
merge-ort.c +2
-0+if (display_update_msgs) +merge_display_update_messages (opt, result, stdout);is it intentional that the previous patch doesn't have this call?
Ugh, oops. Yeah, bad split of the patches; this should have been part of the previous one.
PATCH 7/8 merge-tree: support saving merge messages to a separate file
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
When running git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
--- Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)
Documentation/git-merge-tree.txt +4 -2
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' --real <branch1> <branch2> +'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2> DESCRIPTION
@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursive merge with rename detection. If the merge is clean, the exit status will be `0`, and if the merge has conflicts, the exit status will be `1`. The output will consist solely of the resulting toplevel tree -(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file. The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +16 -2
@@ -389,6 +389,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,
@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o,*/ merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp); + }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }
@@ -451,15 +459,18 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) {struct merge_tree_options o = {0 };int expected_remaining_argc; +int original_argc;const char *const merge_tree_usage[] = { -N_ ("git merge-tree --real <branch1> <branch2>" ), +N_ ("git merge-tree --real [<options>] <branch1> <branch2>" ),N_ ("git merge-tree <base-tree> <branch1> <branch2>" ),NULL };struct option mt_options[] = {OPT_BOOL (0 ,"real" , &o.real,N_ ("do a real merge instead of a trivial merge" )), +OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ), +N_ ("filename to write informational/conflict messages to" )),OPT_END () };
@@ -468,8 +479,11 @@ 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 && original_argc < argc) +die (_ ("--real must be specified if any other options are" )); expected_remaining_argc = (o.real ?2 :3 );if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options);
t/t4301-merge-tree-real.sh +18 -0
@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect ' +test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 && + + # 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 && + 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 MSG_FILE +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 59 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
When running
git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
Signed-off-by Elijah Newren <[email protected]>---
Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)Documentation/git-merge-tree.txt +0
-0@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working treeSYNOPSIS
--------
[verse]
-'git merge-tree' --real <branch1> <branch2>
+'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>DESCRIPTION
Documentation/git-merge-tree.txt +0
-0@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursivemerge with rename detection. If the merge is clean, the exit status will be
0, and if the merge has conflicts, the exit status will be1. The output will consist solely of the resulting toplevel treeDocumentation/git-merge-tree.txt +3
-1-(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file.The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +1
-0@@ -389,6 +389,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 450
Quoted 8 lines
builtin/merge-tree.c +5
-0@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o,*/ merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp);
I don't know enough about how merge-ort works internally, but it looks to me
like at this point the merge already happened and we just didn't clean up (finalize) yet. It feels wrong to die() at this point just because we can't open messages_file.
Quoted 70 lines
builtin/merge-tree.c +10
-2+ }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }@@ -451,15 +459,18 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) {struct merge_tree_options o = {0 };int expected_remaining_argc; +int original_argc;const char *const merge_tree_usage[] = { -N_ ("git merge-tree --real <branch1> <branch2>" ), +N_ ("git merge-tree --real [<options>] <branch1> <branch2>" ),N_ ("git merge-tree <base-tree> <branch1> <branch2>" ),NULL };struct option mt_options[] = {OPT_BOOL (0 ,"real" , &o.real,N_ ("do a real merge instead of a trivial merge" )), +OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ), +N_ ("filename to write informational/conflict messages to" )),OPT_END () };@@ -468,8 +479,11 @@ 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 && original_argc < argc) +die (_ ("--real must be specified if any other options are" )); expected_remaining_argc = (o.real ?2 :3 );if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options);t/t4301-merge-tree-real.sh +0
-0@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect'
t/t4301-merge-tree-real.sh +18
-0+test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 && + + # 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 && + 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 MSG_FILE +' +
test_doneSignature · 1 line
gitgitgadget
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 63 lines · Fabian Stelzer
Quoted 61 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
When running
git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
Signed-off-by Elijah Newren <[email protected]>---
Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)Documentation/git-merge-tree.txt +0
-0@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working treeSYNOPSIS
--------
[verse]
-'git merge-tree' --real <branch1> <branch2>
+'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>DESCRIPTION
Documentation/git-merge-tree.txt +0
-0@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursivemerge with rename detection. If the merge is clean, the exit status will be
0, and if the merge has conflicts, the exit status will be1. The output will consist solely of the resulting toplevel treeDocumentation/git-merge-tree.txt +3
-1-(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file.The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +1
-0@@ -389,6 +389,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o,*/
Quoted 11 lines
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 450
Quoted 6 lines
builtin/merge-tree.c +5
-0merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp);I don't know enough about how merge-ort works internally, but it looks to me like at this point the merge already happened and we just didn't clean up (finalize) yet. It feels wrong to
die()at this point just because we can't openmessages_file.
Yes, the merge already happened; there now exists a new toplevel tree (that nothing references). I'm not sure I understand what's wrong with die'ing here, though. I can't tell if you want to defer the die-ing until later, or just avoid the die-ing and return some kind of success despite failing to complete what the user requested.
Quoted 9 lines
Quoted 9 lines
builtin/merge-tree.c +3
-1+ }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 4 lines · Elijah Newren
Quoted 3 lines · Fabian Stelzer
Quoted 1 line · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
[...]
Quoted 17 lines
Quoted 5 lines
Quoted 5 lines
static int real_merge(struct
merge_tree_options*o,(quoted diff) +0
-0@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o, */Quoted 11 lines
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 450
Quoted 6 lines
builtin/merge-tree.c +5
-0merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp);I don't know enough about how merge-ort works internally, but it looks to me like at this point the merge already happened and we just didn't clean up (finalize) yet. It feels wrong to
die()at this point just because we can't openmessages_file.
Quoted 6 lines
Yes, the merge already happened; there now exists a new toplevel tree (that nothing references). I'm not sure I understand what's wrong with die'ing here, though. I can't tell if you want to defer the die-ing until later, or just avoid the die-ing and return some kind of success despite failing to complete what the user requested.
I think i would prefer the merge operation to abort before actually merging when not being able to write its logfile. Otherwise we possibly do a whole lot of work thats inaccessible afterwards isn't it? (since we dont print the hash)
Thanks for your work on this feature. I think this could open a lot of new possibilities.
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 31 lines · Fabian Stelzer
Quoted 4 lines · Elijah Newren
Quoted 3 lines · Fabian Stelzer
Quoted 1 line · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
[...]
Quoted 17 lines
Quoted 5 lines
Quoted 5 lines
static int real_merge(struct
merge_tree_options*o,(quoted diff) +0
-0@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o, */Quoted 11 lines
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 450
Quoted 6 lines
builtin/merge-tree.c +5
-0merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp);I don't know enough about how merge-ort works internally, but it looks to me like at this point the merge already happened and we just didn't clean up (finalize) yet. It feels wrong to
die()at this point just because we can't openmessages_file.Quoted 6 lines
Yes, the merge already happened; there now exists a new toplevel tree (that nothing references). I'm not sure I understand what's wrong with die'ing here, though. I can't tell if you want to defer the die-ing until later, or just avoid the die-ing and return some kind of success despite failing to complete what the user requested.
Quoted 4 lines
I think i would prefer the merge operation to abort before actually merging when not being able to write its logfile. Otherwise we possibly do a whole lot of work that
s inaccessible afterwards isn't it? (since we dont print the hash)
I see where you're coming from, but I don't see this as worth worrying about. For two reasons:
(1) I'm not sure I buy the "whole lot of work" concern.
merge-ort is pretty snappy. For a simple example of rebasing a single
patch in linux.git across a branch with 28000 renames, I get 176
milliseconds for merge_incore_nonrecursive(). Granted, linux.git is
pretty small in terms of number of files, but Stolee did some
measurements a while back on the Microsoft repos with millions of
files at HEAD. For those, for a trivial merge he sawmerge_incore_recursive() complete in 2 milliseconds, and for a trivial
rebase he saw merge_incore_nonrecursive() complete in 4 milliseconds
(See https://lore.kernel.org/git/CABPp-BHO7bZ3H7A=E9TudhvBoNfwPvRiDMm8S9kq3mYeSXrpXw@mail.gmail.com/).
So huge numbers of files pose much less of a problem than lots of
interesting work like renames, and merge-ort is pretty fast in either
case. Sure, if we were talking about traditional merge-recursive
which would have taken 150000 milliseconds on the same single patch in
linux.git testcase (due to the 28000 renames), then we might worry
more about not letting work get tossed, but at only 176 milliseconds
even with a crazy number of renames, it's just not worth worrying
about.
(2) Even if there is a lot of computation, I don't see why this error path merits extra coding work to salvage the computation somehow
By way of comparison, a regular git merge will abort after completing the same amount of merge work (i.e. after creating a new tree) when the user has a dirty working tree involving a path that would need to be updated by the merge operation. And that is not a bug; it's a requirement -- we cannot first check if the user has dirtied such a path before performing the merge because it's impossible to do so accurately in the face of renames. merge-recursive tried to do that and had early aborts that fell in the false-positive category and some that fell in the false-negative category. It was impossible to fix the false-positives and false-negatives without either (a) disallowing ever doing a merge with a dirty working tree under any conditions (a backwards compatibility break), or (b) waiting to do the notification of dirty-files-in-the-way until after the merge tree has been computed. I wasn't about to break that feature, so merge-ort had to delay error notifications instead.
Now, the dirty-file-in-the-way condition is for a very common case (either for users who intentionally like keeping dirty changes around and doing merges but the branch they are merging happens to touch a file they didn't know about, or users who just forgot that they had local modifications). In contrast, this case here is for when we cannot open a file for writing -- with the filename explicitly just specified by the user.
So, I'd rather keep the code nice and simple as it currently stands.
Quoted 2 lines
Thanks for your work on this feature. I think this could open a lot of new possibilities.
I hope people do interesting things with it, and with the server-side commit replaying I'm working on as well.
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 51 lines · Elijah Newren
Quoted 31 lines · Fabian Stelzer
Quoted 4 lines · Elijah Newren
Quoted 3 lines · Fabian Stelzer
Quoted 1 line · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
[...]
Quoted 17 lines
Quoted 5 lines
Quoted 5 lines
static int real_merge(struct
merge_tree_options*o,(quoted diff) +0
-0@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o, */Quoted 11 lines
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 450
Quoted 6 lines
builtin/merge-tree.c +5
-0merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp);I don't know enough about how merge-ort works internally, but it looks to me like at this point the merge already happened and we just didn't clean up (finalize) yet. It feels wrong to
die()at this point just because we can't openmessages_file.Quoted 6 lines
Yes, the merge already happened; there now exists a new toplevel tree (that nothing references). I'm not sure I understand what's wrong with die'ing here, though. I can't tell if you want to defer the die-ing until later, or just avoid the die-ing and return some kind of success despite failing to complete what the user requested.
Quoted 4 lines
I think i would prefer the merge operation to abort before actually merging when not being able to write its logfile. Otherwise we possibly do a whole lot of work that
s inaccessible afterwards isn't it? (since we dont print the hash)I see where you're coming from, but I don't see this as worth worrying about. For two reasons:
(1) I'm not sure I buy the "whole lot of work" concern.
merge-ort is pretty snappy. For a simple example of rebasing a single
patch in linux.git across a branch with 28000 renames, I get 176
milliseconds formerge_incore_nonrecursive(). Granted, linux.git is
pretty small in terms of number of files, but Stolee did some
measurements a while back on the Microsoft repos with millions of
files at HEAD. For those, for a trivial merge he sawmerge_incore_recursive()complete in 2 milliseconds, and for a trivial
rebase he sawmerge_incore_nonrecursive()complete in 4 milliseconds
(See https://lore.kernel.org/git/CABPp-BHO7bZ3H7A=E9TudhvBoNfwPvRiDMm8S9kq3mYeSXrpXw@mail.gmail.com/).
Quoted 8 lines
So huge numbers of files pose much less of a problem than lots of interesting work like renames, and merge-ort is pretty fast in either case. Sure, if we were talking about traditional merge-recursive which would have taken 150000 milliseconds on the same single patch in linux.git testcase (due to the 28000 renames), then we might worry more about not letting work get tossed, but at only 176 milliseconds even with a crazy number of renames, it's just not worth worrying about.
I guess I'm just not used to how good ort is yet. I was still expecting cases like the merge-recursive ones :)
Quoted 4 lines
(2) Even if there is a lot of computation, I don't see why this error path merits extra coding work to salvage the computation somehow
Quoted 16 lines
By way of comparison, a regular
git mergewill abort after completing the same amount of merge work (i.e. after creating a new tree) when the user has a dirty working tree involving a path that would need to be updated by the merge operation. And that is not a bug; it's a requirement -- we cannot first check if the user has dirtied such a path before performing the merge because it's impossible to do so accurately in the face of renames. merge-recursive tried to do that and had early aborts that fell in the false-positive category and some that fell in the false-negative category. It was impossible to fix the false-positives and false-negatives without either (a) disallowing ever doing a merge with a dirty working tree under any conditions (a backwards compatibility break), or (b) waiting to do the notification of dirty-files-in-the-way until after the merge tree has been computed. I wasn't about to break that feature, so merge-ort had to delay error notifications instead.
Completely understandable for the regular merge. In this case we are not touching the index/working tree at all though so I don't quite see how this is comparable.
Quoted 11 lines
Now, the dirty-file-in-the-way condition is for a very common case (either for users who intentionally like keeping dirty changes around and doing merges but the branch they are merging happens to touch a file they didn't know about, or users who just forgot that they had local modifications). In contrast, this case here is for when we cannot open a file for writing -- with the filename explicitly just specified by the user.
So, I'd rather keep the code nice and simple as it currently stands.
Especially since the extra work penalty is so miniscule i agree.
Thanks
Quoted 6 lines
Quoted 3 lines
Thanks for your work on this feature. I think this could open a lot of new possibilities.
I hope people do interesting things with it, and with the server-side commit replaying I'm working on as well.
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
In reply to Elijah Newren via GitGitGadgetQuoted 59 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
When running
git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
Signed-off-by Elijah Newren <[email protected]>---
Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)Documentation/git-merge-tree.txt +0
-0@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working treeSYNOPSIS
--------
[verse]
-'git merge-tree' --real <branch1> <branch2>
+'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>DESCRIPTION
Documentation/git-merge-tree.txt +0
-0@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursivemerge with rename detection. If the merge is clean, the exit status will be
0, and if the merge has conflicts, the exit status will be1. The output will consist solely of the resulting toplevel treeDocumentation/git-merge-tree.txt +3
-1-(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file.The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +1
-0@@ -389,6 +389,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 451
Quoted 9 lines
builtin/merge-tree.c +6
-0@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o,*/ merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp); + }
Something else I just wondered. Can the user differentiate between the die() in xfopen() and a failed/unclean merge?
Both just exit(1) don't they?
Quoted 69 lines
builtin/merge-tree.c +9
-2printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }@@ -451,15 +459,18 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) {struct merge_tree_options o = {0 };int expected_remaining_argc; +int original_argc;const char *const merge_tree_usage[] = { -N_ ("git merge-tree --real <branch1> <branch2>" ), +N_ ("git merge-tree --real [<options>] <branch1> <branch2>" ),N_ ("git merge-tree <base-tree> <branch1> <branch2>" ),NULL };struct option mt_options[] = {OPT_BOOL (0 ,"real" , &o.real,N_ ("do a real merge instead of a trivial merge" )), +OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ), +N_ ("filename to write informational/conflict messages to" )),OPT_END () };@@ -468,8 +479,11 @@ 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 && original_argc < argc) +die (_ ("--real must be specified if any other options are" )); expected_remaining_argc = (o.real ?2 :3 );if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options);t/t4301-merge-tree-real.sh +0
-0@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect'
t/t4301-merge-tree-real.sh +18
-0+test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 && + + # 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 && + 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 MSG_FILE +' +
test_doneSignature · 1 line
gitgitgadget
Re: [PATCH 7/8] merge-tree: support saving merge messages to a separate file
Quoted 63 lines · Fabian Stelzer
Quoted 61 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
When running
git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
Signed-off-by Elijah Newren <[email protected]>---
Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)Documentation/git-merge-tree.txt +0
-0@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working treeSYNOPSIS
--------
[verse]
-'git merge-tree' --real <branch1> <branch2>
+'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>DESCRIPTION
Documentation/git-merge-tree.txt +0
-0@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursivemerge with rename detection. If the merge is clean, the exit status will be
0, and if the merge has conflicts, the exit status will be1. The output will consist solely of the resulting toplevel treeDocumentation/git-merge-tree.txt +3
-1-(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file.The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +1
-0@@ -389,6 +389,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,@@ -442,8 +443,15 @@ static int real_merge(struct merge_tree_options *o,*/
Quoted 11 lines
builtin/merge-tree.c static int real_merge(struct merge_tree_options *o, · line 451
Quoted 7 lines
builtin/merge-tree.c +6
-0merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp); + }Something else I just wondered. Can the user differentiate between the
die()inxfopen()and a failed/unclean merge?
Both just exit(1) don't they?
xfopen() calls die_errno(), which calls die_routine(), which will be pointing at die_builtin() since we don't change it in builtin/merge-tree.c, and die_builtin() calls exit(128).
So, a different error code.
But good question...perhaps I should mention exit codes other than 0 and 1 in the documentation of merge-tree for other failures.
Quoted 70 lines
Quoted 70 lines
builtin/merge-tree.c +9
-2printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }@@ -451,15 +459,18 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) {struct merge_tree_options o = {0 };int expected_remaining_argc; +int original_argc;const char *const merge_tree_usage[] = { -N_ ("git merge-tree --real <branch1> <branch2>" ), +N_ ("git merge-tree --real [<options>] <branch1> <branch2>" ),N_ ("git merge-tree <base-tree> <branch1> <branch2>" ),NULL };struct option mt_options[] = {OPT_BOOL (0 ,"real" , &o.real,N_ ("do a real merge instead of a trivial merge" )), +OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ), +N_ ("filename to write informational/conflict messages to" )),OPT_END () };@@ -468,8 +479,11 @@ 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 && original_argc < argc) +die (_ ("--real must be specified if any other options are" )); expected_remaining_argc = (o.real ?2 :3 );if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options);t/t4301-merge-tree-real.sh +0
-0@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect'
t/t4301-merge-tree-real.sh +18
-0+test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 && + + # 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 && + 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 MSG_FILE +' +
test_done
--
gitgitgadget
PATCH 8/8 merge-tree: provide an easy way to access which files have conflicts
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Callers of git merge-tree --real might want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and then write a NUL-separated list of unmerged filenames to the specified file.
--- Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 16 ++++++++++++++++ merge-ort.c | 13 +++++++++++++ merge-ort.h | 3 +++ t/t4301-merge-tree-real.sh | 9 +++++++++ 5 files changed, 45 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +4 -2
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' --real [--messages=<file>] <branch1> <branch2> +'git merge-tree' --real [--messages=<file>] [--conflicted-list=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2> DESCRIPTION
@@ -23,7 +23,9 @@ will be `0`, and if the merge has conflicts, the exit status will be `1`. The output will consist solely of the resulting toplevel tree (which may have files including conflict markers). With `--messages`, it will write any informational messages (such as "Auto-merging -<path>" and conflict notices) to the given file. +<path>" and conflict notices) to the given file. With +`--conflicted-list`, it will write a list of unmerged files, one per +line, to the given file. The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +16 -0
@@ -390,6 +390,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real;char *messages_file; +char *conflicted_file; }; static int real_merge(struct merge_tree_options *o,
@@ -449,6 +450,19 @@ static int real_merge(struct merge_tree_options *o,merge_display_update_messages (&opt, &result, fp);fclose (fp); } +if (o->conflicted_file) { +struct string_list conflicted_files =STRING_LIST_INIT_NODUP ; +FILE *fp =xfopen (o->conflicted_file,"w" ); +int i; + +merge_get_conflicted_files (&result, &conflicted_files); +for (i =0 ; i < conflicted_files.nr; i++) { +fprintf (fp,"%s" , conflicted_files.items[i].string); +fputc ('\0' , fp); + } +fclose (fp); +string_list_clear (&conflicted_files,0 ); + }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid));merge_finalize (&opt, &result);
@@ -471,6 +485,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) N_("do a real merge instead of a trivial merge")),OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ),N_ ("filename to write informational/conflict messages to" )), +OPT_STRING (0 ,"conflicted-list" , &o.conflicted_file,N_ ("file" ), +N_ ("filename to write list of unmerged files" )), OPT_END() };
merge-ort.c +13 -0
@@ -4234,6 +4234,19 @@ 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) { +string_list_append (conflicted_files, e->key); + } +string_list_sort (conflicted_files); +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result,
merge-ort.h +3 -0
@@ -79,6 +79,9 @@ void merge_display_update_messages(struct merge_options *opt,struct merge_result *result, FILE *stream); +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);
t/t4301-merge-tree-real.sh +9 -0
@@ -96,4 +96,13 @@ test_expect_success '--messages gives us the conflict notices and such' ' test_cmp expect MSG_FILE ' +test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --conflicted-list=UNMERGED side1 side2 && + + cat UNMERGED | tr "\0" "\n" >actual && + test_write_lines greeting whatever~side1 >expect && + + test_cmp expect actual +' + test_done
Signature · 1 line
gitgitgadget
v2 cover RFC: Server side merges (no ref updating, no commit creating, no touching worktree or index)
In reply to Elijah Newren via GitGitGadget(NOTE for Junio: This series has a minor conflict with en/remerge-diff -- this series moves a code block into a new function, but en/remerge-diff adds a BUG() message to that code block. But this series is just RFC, so you may want to wait to pick it up.)
Updates since 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
NOTE2: A preliminary version of this series was discussed here:
https://lore.kernel.org/git/[email protected]/
NOTE3: An alternative has been implemented by Christian, over here:
https://lore.kernel.org/git/[email protected]/
This series introduces a new option to git-merge-tree: --real (best name I could come up with). This new option is designed to allow a server-side "real" merge (or allow folks client-side to do merges with branches they don't even have checked out). Real merges differ from trivial merges in that they handle:
* three way content merges
* recursive ancestor consolidation
* renames
* proper directory/file conflict handling
* etc.
The reason this is different from merge is that merge-tree does NOT:
* Read/write/update any working tree (and assumes there probably isn't one)
* Read/write/update any index (and assumes there probably isn't one)
* Create a commit object
* Update any refs
This series attempts to guess what kind of output would be wanted, basically choosing:
* clean merge or conflict signalled via exit status
* stdout consists solely of printing the hash of the resulting tree (though that tree may include files that have conflict markers)
* new optional --messages flag for specifying a file where informational messages (e.g. conflict notices and files involved in three-way-content merges) can be written; by default, this output is simply discarded
* new optional --conflicted-list flag for specifying a file where the names of conflicted-files can be written in a NUL-character-separated list
This design means it's basically just a low-level tool that other scripts would use and do additional work with. Perhaps something like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) git update-ref $BRANCH1 $NEWCOMMIT
Elijah Newren (8):
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 saving merge messages to a separate file
merge-tree: provide an easy way to access which files have conflicts
Documentation/git-merge-tree.txt | 32 +++++-- builtin/merge-tree.c | 151 ++++++++++++++++++++++++++++--- git.c | 2 +- merge-ort.c | 85 ++++++++++------- merge-ort.h | 12 +++ t/t4301-merge-tree-real.sh | 108 ++++++++++++++++++++++ 6 files changed, 333 insertions(+), 57 deletions(-) create mode 100755 t/t4301-merge-tree-real.sh
base-commit: 2ae0a9cb8298185a94e5998086f380a355dd8907
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-1114%2Fnewren%2Fmerge-into-v2
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-1114/newren/merge-into-v2
Pull-Request: https://github.com/git/git/pull/1114
Range-diff vs v1:
1: a7c7910d834 = 1: a7c7910d834 merge-tree: rename merge_trees() to trivial_merge_trees()
2: 9da8e77c1d7 ! 2: aafe67d7c69 merge-tree: move logic for existing merge into new function
@@ builtin/merge-tree.c: static void *get_tree_descriptor(struct repository *r,
}
-int cmd_merge_tree(int argc, const char **argv, const char *prefix)
--{
-+static int trivial_merge(int argc, const char **argv) {
++static int trivial_merge(int argc, const char **argv)
+ {
struct repository *r = the_repository;
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
3: 9d03d3f56ab ! 3: ee21aed0115 merge-tree: add option parsing and initial shell for real merge function
@@ builtin/merge-tree.c: static void *get_tree_descriptor(struct repository *r,
return buf;
}
--static int trivial_merge(int argc, const char **argv) {
+-static int trivial_merge(int argc, const char **argv)
+static int trivial_merge(const char *base,
+ const char *branch1,
-+ const char *branch2) {
++ const char *branch2)
+ {
struct repository *r = the_repository;
struct tree_desc t[3];
void *buf1, *buf2, *buf3;
@@ builtin/merge-tree.c: static void *get_tree_descriptor(struct repository *r,
trivial_merge_trees(t, "");
free(buf1);
free(buf2);
-@@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv) {
+@@ builtin/merge-tree.c: static int trivial_merge(int argc, const char **argv)
return 0;
} 4: 9fc71f4511b ! 4: 1710ba4a9e4 merge-tree: implement real merges
@@ Commit message
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2)
test $? -eq 0 || die "There were conflicts..."
- NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 $BRANCH2)
+ 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
@@ t/t4301-merge-tree-real.sh (new)
+ echo hi >greeting &&
+ echo bar >whatever &&
+ git add numbers greeting whatever &&
-+ git commit -m rename-and-modify &&
++ git commit -m modify-stuff &&
+
+ git checkout side2 &&
+ test_write_lines 0 1 2 3 4 5 >numbers &&
@@ t/t4301-merge-tree-real.sh (new)
+ mkdir whatever &&
+ >whatever/empty &&
+ git add numbers greeting whatever/empty &&
-+ git commit -m remove-and-rename
++ git commit -m other-modifications
+'
+
+test_expect_success 'Content merge and a few conflicts' '
5: aa816e766e9 ! 5: bc6d01f1a0e merge-ort: split out a separate display_update_messages() function
@@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
-
- trace2_region_leave("merge", "display messages", opt->repo);
- }
++ if (display_update_msgs)
++ merge_display_update_messages(opt, result);
merge_finalize(opt, result);
}
6: 32ad5b5c10d ! 6: c9e95a70d19 merge-ort: allow update messages to be written to different file stream
@@ merge-ort.c: void merge_display_update_messages(struct merge_options *opt,
string_list_clear(&olist, 0);
@@ merge-ort.c: void merge_switch_to_result(struct merge_options *opt,
- trace2_region_leave("merge", "write_auto_merge", opt->repo);
}
-+ if (display_update_msgs)
+ if (display_update_msgs)
+- merge_display_update_messages(opt, result);
+ merge_display_update_messages(opt, result, stdout);
merge_finalize(opt, result);
7: 777de92d9f1 = 7: 4b513a6d696 merge-tree: support saving merge messages to a separate file
8: 1d24a4f4070 = 8: 01364bb020e merge-tree: provide an easy way to access which files have conflictsSignature · 1 line
gitgitgadget
PATCH v2 1/8 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
@@ -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, sreturn 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
PATCH v2 2/8 merge-tree: move logic for existing merge into new function
In reply to Elijah Newren via GitGitGadgetFrom: 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
@@ -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
PATCH v2 3/8 merge-tree: add option parsing and initial shell for real merge function
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Let merge-tree accept a --real parameter for choosing real merges instead of trivial merges. 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 | 56 +++++++++++++++++++++++++++++++++++++------- git.c | 2 +- 2 files changed, 48 insertions(+), 10 deletions(-)
builtin/merge-tree.c +47 -9
@@ -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,46 @@ static int trivial_merge(int argc, const char **argv)return 0 ; } +struct merge_tree_options { +int real; +}; + +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 --real <branch1> <branch2>" ), +N_ ("git merge-tree <base-tree> <branch1> <branch2>" ), +NULL + }; +struct option mt_options[] = { +OPT_BOOL (0 ,"real" , &o.real, +N_ ("do a real merge instead of a trivial merge" )), +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 ); + expected_remaining_argc = (o.real ?2 :3 ); +if (argc != expected_remaining_argc) +usage_with_options (merge_tree_usage, mt_options); + +/* 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
@@ -561,7 +561,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
PATCH v2 4/8 merge-tree: implement real merges
In reply to Elijah Newren via GitGitGadgetFrom: 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 mean to be used by some higher level script, perhaps in a sequence of steps like this:
NEWTREE=$(git merge-tree --real $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 | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.sh
Documentation/git-merge-tree.txt +18 -10
@@ -3,26 +3,34 @@ 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' --real <branch1> <branch2> 'git merge-tree' <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. +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. -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. +The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`. The output will consist solely of the resulting toplevel tree +(which may have files including conflict markers). + +The second form is meant for backward compatibility and will only do a +trival 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. GIT ---
builtin/merge-tree.c +54 -1
67 lines not on this page · raw message
t/t4301-merge-tree-real.sh new file +81 -0
81 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v2 4/8] merge-tree: implement real merges
Hi Elijah,
Quoted 8 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 5 lines
The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)This output is mean to be used by some higher level script, perhaps in a
^^^^
My apologies for pointing out a grammar issue: This probably intended to say "meant", as the word "mean" changes the sense of the sentence.
In my defense, I have more substantial suggestions below.
Quoted 28 lines
sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) git update-ref $BRANCH1 $NEWCOMMITNote 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.
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-merge-tree.txt | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.shDocumentation/git-merge-tree.txt +0
-0@@ -3,26 +3,34 @@ git-merge-tree(1) NAME
Documentation/git-merge-tree.txt git-merge-tree(1) · line 13
Quoted 10 lines
Documentation/git-merge-tree.txt +2
-1---- -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' --real <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>
Here is an idea: How about aiming for this synopsis instead, exploiting the fact that the "real" mode takes a different amount of arguments?
'git merge-tree' [--write-tree] <branch1> <branch2>
'git merge-tree' [--demo-trivial-merge] <base-tree> <branch1> <branch2>
That way, the old mode can still function, and can even at some stage be deprecated and eventually removed.
Quoted 84 lines
Documentation/git-merge-tree.txt +16
-9DESCRIPTION ----------- -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. +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. -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. +The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`. The output will consist solely of the resulting toplevel tree +(which may have files including conflict markers). + +The second form is meant for backward compatibility and will only do a +trival 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. GIT ---builtin/merge-tree.c +36
-1@@ -2,6 +2,9 @@#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" #include "parse-options.h" #include "repository.h" @@ -392,7 +395,57 @@ 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" , +_ ("not something we can merge" )); + + parent2 =get_merge_parent (branch2); +if (!parent2) +help_unknown_ref (branch2,"merge" , +_ ("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? */ +
builtin/merge-tree.c struct merge_tree_options { · line 440
Quoted 10 lines
builtin/merge-tree.c +10
-0+/* +* Get the merge bases, in reverse order; see comment above +* merge_incore_recursive in merge-ort.h +*/ + common =get_merge_bases (parent1, parent2); +for (j = common; j; j = j->next) +commit_list_insert (j->item, &merge_bases); + +/* +* TODO: notify if merging unrelated histories?
I guess that it would make most sense to add a flag whether this is allowed or not, and I would suggest the default to be off.
builtin/merge-tree.c struct merge_tree_options { · line 447
Quoted 7 lines
builtin/merge-tree.c +7
-0+if (!common) +fprintf(stderr, _("merging unrelated histories")); +*/ + +merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); +printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); +merge_switch_to_result (&opt,NULL , &result,0 ,0 );
This looks to be idempotent to merge_finalize(&opt, &result), so maybe use that instead?
Quoted 10 lines
builtin/merge-tree.c +1
-0+return result.clean ?0 :1 ; }int cmd_merge_tree (int argc,const char **argv,const char *prefix)t/t4301-merge-tree-real.sh new file +0
-0
t/t4301-merge-tree-real.sh line 9
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0@@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git merge-tree --real' + +. ./test-lib.sh + +# This test is ort-specific +GIT_TEST_MERGE_ALGORITHM=ort +export GIT_TEST_MERGE_ALGORITHM
It might make sense to skip the entire test if the user asked for recursive to be tested:
test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort ||
skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
test_done
}t/t4301-merge-tree-real.sh line 16
Quoted 7 lines
t/t4301-merge-tree-real.sh new file +7
-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 && + git commit -m initial &&
I would really like to encourage the use of test_tick. It makes the commit consistent, just in case you run into an issue that depends on some hash order.
t/t4301-merge-tree-real.sh line 21
Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5
-0+ + git branch side1 && + git branch side2 && + + git checkout side1 &&
Please use git switch -c side1 or git checkout -b side1: it is more compact than git branch ... && git checkout ....
t/t4301-merge-tree-real.sh line 28
Quoted 7 lines
t/t4301-merge-tree-real.sh new file +7
-0+ test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + git commit -m modify-stuff && + + git checkout side2 &&
This could be written as git checkout -b side2 HEAD^, to make the setup more succinct.
Quoted 4 lines
t/t4301-merge-tree-real.sh new file +4
-0+ test_write_lines 0 1 2 3 4 5 >numbers && + echo yo >greeting && + git rm whatever && + mkdir whatever &&
t/t4301-merge-tree-real.sh line 42
Quoted 10 lines
t/t4301-merge-tree-real.sh new file +10
-0+ >whatever/empty && + git add numbers greeting whatever/empty && + git commit -m other-modifications +' + +test_expect_success 'Content merge and a few conflicts' ' + git checkout side1^0 && + test_must_fail git merge side2 && + cp .git/AUTO_MERGE EXPECT && + E_TREE=$(cat EXPECT) &&
The file EXPECT is not used below. And can we use a more obvious name? SOmething like:
expected_tree=$(cat .git/AUTO_MERGE)
t/t4301-merge-tree-real.sh line 44
Quoted 1 line
t/t4301-merge-tree-real.sh new file +1
-0+ git reset --hard &&
For an extra bonus, we could delay this via test_when_finished, to prove that git merge-tree --real works even in a dirty worktree _with conflicts_.
t/t4301-merge-tree-real.sh line 46
Quoted 2 lines
t/t4301-merge-tree-real.sh new file +2
-0+ test_must_fail git merge-tree --real side1 side2 >RESULT && + R_TREE=$(cat RESULT) &&
How about actual_tree instead?
Quoted 17 lines
t/t4301-merge-tree-real.sh new file +17
-0+ + # 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 ${R_TREE}:numbers >actual && + test_cmp expect actual && + + # whatever and whatever~<branch> should have same HASHES + git rev-parse ${E_TREE}:whatever ${E_TREE}:whatever~HEAD >expect && + git rev-parse ${R_TREE}:whatever ${R_TREE}:whatever~side1 >actual && + test_cmp expect actual && + + # greeting should have a merge conflict + git show ${E_TREE}:greeting >tmp && + cat tmp | sed -e s/HEAD/side1/ >expect &&
t/t4301-merge-tree-real.sh line 73
Quoted 10 lines
t/t4301-merge-tree-real.sh new file +10
-0+ git show ${R_TREE}:greeting >actual && + test_cmp expect actual +' + +test_expect_success 'Barf on misspelled option' ' + # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --real --mesages FOOBAR side1 side2 2>expect && + + grep "error: unknown option.*mesages" expect +'
I do not think that this test case adds much, and we already test the parse_options() machinery elsewhere.
t/t4301-merge-tree-real.sh line 81
Quoted 8 lines
t/t4301-merge-tree-real.sh new file +8
-0+ +test_expect_success 'Barf on too many arguments' ' + test_expect_code 129 git merge-tree --real side1 side2 side3 2>expect && + + grep "^usage: git merge-tree" expect +' + +test_done
The rest looks awesome. Thank you for working on it! I will definitely come back to review the rest (have to take a break now), and then probably add quite a bit of food for thought based on my experience _actually_ using merge-ort on the server-side. Stay tuned.
Thank you,
Dscho
Re: [PATCH v2 4/8] merge-tree: implement real merges
Quoted 17 lines · Johannes Schindelin
Hi Elijah,
Quoted 12 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) or 1 (conflicts present)
Quoted 5 lines
Quoted 1 line
This output is mean to be used by some higher level script, perhaps in a
^^^^My apologies for pointing out a grammar issue: This probably intended to say "meant", as the word "mean" changes the sense of the sentence.
Oops. Yeah, I'll correct that; thanks for pointing it out.
Quoted 35 lines
In my defense, I have more substantial suggestions below.
Quoted 33 lines
sequence of steps like this:
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) git update-ref $BRANCH1 $NEWCOMMITNote 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.
Signed-off-by Elijah Newren <[email protected]>--- Documentation/git-merge-tree.txt | 28 +++++++---- builtin/merge-tree.c | 55 +++++++++++++++++++++- t/t4301-merge-tree-real.sh | 81 ++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 11 deletions(-) create mode 100755 t/t4301-merge-tree-real.shDocumentation/git-merge-tree.txt +1
-1@@ -3,26 +3,34 @@ 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
Quoted 8 lines
Documentation/git-merge-tree.txt git-merge-tree(1) · line 13
Quoted 5 lines
Documentation/git-merge-tree.txt +1
-0SYNOPSIS -------- [verse] +'git merge-tree' --real <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2>Here is an idea: How about aiming for this synopsis instead, exploiting the fact that the "real" mode takes a different amount of arguments?
My turn on the grammar thing: s/amount/number/. :-)
Quoted 6 lines
'git merge-tree' [--write-tree] <branch1> <branch2> 'git merge-tree' [--demo-trivial-merge] <base-tree> <branch1> <branch2>That way, the old mode can still function, and can even at some stage be deprecated and eventually removed.
Ooh, interesting.
Quoted 89 lines
Quoted 89 lines
Documentation/git-merge-tree.txt +16
-9DESCRIPTION ----------- -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. +Performs a merge, but does not make any new commits and does not read +from or write to either the working tree or index. -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. +The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`. The output will consist solely of the resulting toplevel tree +(which may have files including conflict markers). + +The second form is meant for backward compatibility and will only do a +trival 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. GIT ---builtin/merge-tree.c +41
-1@@ -2,6 +2,9 @@#include "builtin.h" #include "tree-walk.h" #include "xdiff-interface.h" +#include "help.h" +#include "commit-reach.h" +#include "merge-ort.h" #include "object-store.h" #include "parse-options.h" #include "repository.h" @@ -392,7 +395,57 @@ 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" , +_ ("not something we can merge" )); + + parent2 =get_merge_parent (branch2); +if (!parent2) +help_unknown_ref (branch2,"merge" , +_ ("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? */ + +/* +* Get the merge bases, in reverse order; see comment above +* merge_incore_recursive in merge-ort.h +*/ + common =get_merge_bases (parent1, parent2);
Quoted 8 lines
builtin/merge-tree.c struct merge_tree_options { · line 440
Quoted 5 lines
builtin/merge-tree.c +5
-0+for (j = common; j; j = j->next) +commit_list_insert (j->item, &merge_bases); + +/* +* TODO: notify if merging unrelated histories? I guess that it would make most sense to add a flag whether this is allowed or not, and I would suggest the default to be
off.
Sounds fair. Thanks for commenting on one of the TODOs that I was unsure about.
Quoted 10 lines
builtin/merge-tree.c struct merge_tree_options { · line 447
Quoted 7 lines
builtin/merge-tree.c +7
-0+if (!common) +fprintf(stderr, _("merging unrelated histories")); +*/ + +merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); +printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); +merge_switch_to_result (&opt,NULL , &result,0 ,0 );This looks to be idempotent to
merge_finalize(&opt, &result), so maybe use that instead?
Yeah, and add a TODO about the display messages (that'll be addressed in a later patch, unlike the above TODOs).
Quoted 24 lines
Quoted 11 lines
builtin/merge-tree.c +1
-0+return result.clean ?0 :1 ; }int cmd_merge_tree (int argc,const char **argv,const char *prefix)t/t4301-merge-tree-real.sh new file +0
-0t/t4301-merge-tree-real.sh line 9
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0@@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git merge-tree --real' + +. ./test-lib.sh + +# This test is ort-specific +GIT_TEST_MERGE_ALGORITHM=ort +export GIT_TEST_MERGE_ALGORITHMIt might make sense to skip the entire test if the user asked for
recursiveto be tested:
Quoted 4 lines
test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort ||
skip_all="GIT_TEST_MERGE_ALGORITHM != ort"
test_done
}The idea makes sense, but it took me a bit to understand this code block. I think you're just missing an opening left curly brace right after the '||'?
Quoted 11 lines
t/t4301-merge-tree-real.sh line 16
Quoted 7 lines
t/t4301-merge-tree-real.sh new file +7
-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 && + git commit -m initial &&I would really like to encourage the use of
test_tick. It makes the commit consistent, just in case you run into an issue that depends on some hash order.
I've used test_tick before, but I already know this test can't depend on hash order. Further, the hashes in the output are also replaced before comparing in order to make the tests also work as-is under sha256. So the tests are explicitly ignoring precise hashes. As such, I'm not sure I see the value of test_tick here.
Quoted 8 lines
t/t4301-merge-tree-real.sh line 21
Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5
-0+ + git branch side1 && + git branch side2 && + + git checkout side1 &&Please use
git switch -c side1orgit checkout -b side1: it is more compact thangit branch ... && git checkout ....
Yes, but less forgiving to later modification where I go and add additional commits on one of the sides, because...
Quoted 11 lines
t/t4301-merge-tree-real.sh line 28
Quoted 8 lines
t/t4301-merge-tree-real.sh new file +7
-0+ test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + git commit -m modify-stuff && + + git checkout side2 &&This could be written as
git checkout -b side2 HEAD^, to make the setup more succinct.
...the presumption of HEAD^ is hardcoded and has to be parsed by readers to understand the test. It felt like more cognitive overhead to me, in addition to being less malleable.
Quoted 18 lines
Quoted 4 lines
t/t4301-merge-tree-real.sh new file +4
-0+ test_write_lines 0 1 2 3 4 5 >numbers && + echo yo >greeting && + git rm whatever && + mkdir whatever &&t/t4301-merge-tree-real.sh line 42
Quoted 10 lines
t/t4301-merge-tree-real.sh new file +10
-0+ >whatever/empty && + git add numbers greeting whatever/empty && + git commit -m other-modifications +' + +test_expect_success 'Content merge and a few conflicts' ' + git checkout side1^0 && + test_must_fail git merge side2 && + cp .git/AUTO_MERGE EXPECT && + E_TREE=$(cat EXPECT) &&The file
EXPECTis not used below. And can we use a more obvious name? SOmething like:
Quoted 1 line
expected_tree=$(cat .git/AUTO_MERGE)
There go my beautiful <80 character lines below. :-(
But on a more serious note, yeah this is probably better. I'll change it. :-)
Quoted 6 lines
t/t4301-merge-tree-real.sh line 44
Quoted 2 lines
t/t4301-merge-tree-real.sh new file +1
-0+ git reset --hard &&For an extra bonus, we could delay this via
test_when_finished, to prove thatgit merge-tree --realworks even in a dirty worktree_withconflicts_.
Ooh, good thought. I like that.
Quoted 5 lines
t/t4301-merge-tree-real.sh line 46
Quoted 3 lines
t/t4301-merge-tree-real.sh new file +2
-0+ test_must_fail git merge-tree --real side1 side2 >RESULT && + R_TREE=$(cat RESULT) &&How about
actual_treeinstead?
But my 80-characters rev-parse lines....waaah. Just kidding, yeah this would be better.
Quoted 22 lines
Quoted 22 lines
t/t4301-merge-tree-real.sh new file +22
-0+ + # 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 ${R_TREE}:numbers >actual && + test_cmp expect actual && + + # whatever and whatever~<branch> should have same HASHES + git rev-parse ${E_TREE}:whatever ${E_TREE}:whatever~HEAD >expect && + git rev-parse ${R_TREE}:whatever ${R_TREE}:whatever~side1 >actual && + test_cmp expect actual && + + # greeting should have a merge conflict + git show ${E_TREE}:greeting >tmp && + cat tmp | sed -e s/HEAD/side1/ >expect && + git show ${R_TREE}:greeting >actual && + test_cmp expect actual +' + +test_expect_success 'Barf on misspelled option' '
Quoted 8 lines
t/t4301-merge-tree-real.sh line 73
Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5
-0+ # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --real --mesages FOOBAR side1 side2 2>expect && + + grep "error: unknown option.*mesages" expect +'I do not think that this test case adds much, and we already test the
parse_options()machinery elsewhere.
It's more about verifying that exit codes of 0 & 1 are reserved for "completed with no conflicts" and "completed with conflicts". The 129 bit in this test is the important bit (and perhaps is well-known to lots of other folks, but I thought it was worth highlighting). That said, I did a bad job mentioning that in the test description; I'll fix it up.
Quoted 5 lines
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 --real side1 side2 side3 2>expect && + + grep "^usage: git merge-tree" expect
Quoted 8 lines
t/t4301-merge-tree-real.sh line 81
Quoted 3 lines
t/t4301-merge-tree-real.sh new file +3
-0+' + +test_doneThe rest looks awesome. Thank you for working on it! I will definitely come back to review the rest (have to take a break now), and then probably add quite a bit of food for thought based on my experience _actually_ using
merge-orton the server-side. Stay tuned.
Ooh, I'm intrigued. And thanks for reviewing!
Re: [PATCH v2 4/8] merge-tree: implement real merges
Hi Elijah,
Quoted 11 lines · Elijah Newren
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines · Elijah Newren via GitGitGadget
SYNOPSIS
--------
[verse]
+'git merge-tree' --real <branch1> <branch2>
'git merge-tree' <base-tree> <branch1> <branch2>
Quoted 4 lines
Quoted 2 lines
Here is an idea: How about aiming for this synopsis instead, exploiting the fact that the "real" mode takes a different amount of arguments?
My turn on the grammar thing: s/amount/number/. :-)
See? I know why I'm refraining from nitpicking. It's just not good for anyone involved.
Quoted 19 lines
Quoted 19 lines
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +0
-0t/t4301-merge-tree-real.sh line 9
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0@@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git merge-tree --real' + +. ./test-lib.sh + +# This test is ort-specific +GIT_TEST_MERGE_ALGORITHM=ort +export GIT_TEST_MERGE_ALGORITHMIt might make sense to skip the entire test if the user asked for
recursiveto be tested:
Quoted 8 lines
Quoted 4 lines
test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort || skip_all="GIT_TEST_MERGE_ALGORITHM != ort" test_done }The idea makes sense, but it took me a bit to understand this code block. I think you're just missing an opening left curly brace right after the '||'?
Yes. Sorry.
Quoted 11 lines
Quoted 10 lines
t/t4301-merge-tree-real.sh line 16
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +6
-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 && + git commit -m initial &&I would really like to encourage the use of
test_tick. It makes the commit consistent, just in case you run into an issue that depends on some hash order.
Quoted 5 lines
I've used
test_tickbefore, but I already know this test can't depend on hash order. Further, the hashes in the output are also replaced before comparing in order to make the tests also work as-is under sha256. So the tests are explicitly ignoring precise hashes. As such, I'm not sure I see the value oftest_tickhere.
Nevertheless. To make comparing logs of two different test runs easier, it makes more sense to insist on consistency.
Quoted 21 lines
Quoted 8 lines
t/t4301-merge-tree-real.sh line 21
Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5
-0+ + git branch side1 && + git branch side2 && + + git checkout side1 &&Please use
git switch -c side1orgit checkout -b side1: it is more compact thangit branch ... && git checkout ....Yes, but less forgiving to later modification where I go and add additional commits on one of the sides, because...
Quoted 9 lines
Quoted 8 lines
t/t4301-merge-tree-real.sh new file +7
-0+ test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + git commit -m modify-stuff && + + git checkout side2 &&
Quoted 6 lines
Quoted 2 lines
This could be written as
git checkout -b side2 HEAD^, to make the setup more succinct....the presumption of HEAD^ is hardcoded and has to be parsed by readers to understand the test. It felt like more cognitive overhead to me, in addition to being less malleable.
Right. Different developers, different preferences. I wish we had a standard way in the test suite to initialize a test setup that _everybody_ could agree to be succinct and helpful. So far, we use shell scripted Git commands to recreate an initial commit topology, but especially when comparing to existing test suites with fixtures that are not only well-documented but also easy to wrap your head around, I find Git's test suite awfully lacking. Mind you, the code _I_ introduced isn't stellar in this respect, either, not by a very far stretch.
Quoted 10 lines
Quoted 9 lines
t/t4301-merge-tree-real.sh line 73
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +6
-0+test_expect_success 'Barf on misspelled option' ' + # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --real --mesages FOOBAR side1 side2 2>expect && + + grep "error: unknown option.*mesages" expect +'I do not think that this test case adds much, and we already test the
parse_options()machinery elsewhere.
Quoted 4 lines
It's more about verifying that exit codes of 0 & 1 are reserved for "completed with no conflicts" and "completed with conflicts". The 129 bit in this test is the important bit (and perhaps is well-known to lots of other folks, but I thought it was worth highlighting).
Fair enough.
Ciao,
Dscho
Re: [PATCH v2 4/8] merge-tree: implement real merges
Hi Dscho,
Quoted 16 lines · Johannes Schindelin
Hi Elijah,
Quoted 11 lines · Elijah Newren
Quoted 9 lines · Johannes Schindelin
Quoted 5 lines · Elijah Newren via GitGitGadget
SYNOPSIS
--------
[verse]
+'git merge-tree' --real <branch1> <branch2>
'git merge-tree' <base-tree> <branch1> <branch2>
Quoted 7 lines
Quoted 4 lines
Quoted 2 lines
Here is an idea: How about aiming for this synopsis instead, exploiting the fact that the "real" mode takes a different amount of arguments?
My turn on the grammar thing: s/amount/number/. :-)
See? I know why I'm refraining from nitpicking. It's just not good for anyone involved.
Well, in your case, the point you brought up will improve the commit message for future readers, and so it was totally justified (and I'm glad you brought it up). My comment is useful for nothing more than a bit of good-natured ribbing. But I'm not sure it was taken that way, so I'm sorry if my comment had any effect other than making you smile.
Quoted 41 lines
Quoted 19 lines
Quoted 19 lines
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +0
-0t/t4301-merge-tree-real.sh line 9
Quoted 9 lines
t/t4301-merge-tree-real.sh new file +9
-0@@ -0,0 +1,81 @@ +#!/bin/sh + +test_description='git merge-tree --real' + +. ./test-lib.sh + +# This test is ort-specific +GIT_TEST_MERGE_ALGORITHM=ort +export GIT_TEST_MERGE_ALGORITHMIt might make sense to skip the entire test if the user asked for
recursiveto be tested:Quoted 8 lines
Quoted 4 lines
test "${GIT_TEST_MERGE_ALGORITHM:-ort}" = ort || skip_all="GIT_TEST_MERGE_ALGORITHM != ort" test_done }The idea makes sense, but it took me a bit to understand this code block. I think you're just missing an opening left curly brace right after the '||'?
Yes. Sorry.
Quoted 11 lines
Quoted 10 lines
t/t4301-merge-tree-real.sh line 16
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +6
-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 && + git commit -m initial &&I would really like to encourage the use of
test_tick. It makes the commit consistent, just in case you run into an issue that depends on some hash order.
Quoted 8 lines
Quoted 5 lines
I've used
test_tickbefore, but I already know this test can't depend on hash order. Further, the hashes in the output are also replaced before comparing in order to make the tests also work as-is under sha256. So the tests are explicitly ignoring precise hashes. As such, I'm not sure I see the value oftest_tickhere.Nevertheless. To make comparing logs of two different test runs easier, it makes more sense to insist on consistency.
Ah...comparing logs between two different test runs; that sounds like a reasonable justification. I'll add the test_tick's.
Quoted 56 lines
Quoted 21 lines
Quoted 8 lines
t/t4301-merge-tree-real.sh line 21
Quoted 5 lines
t/t4301-merge-tree-real.sh new file +5
-0+ + git branch side1 && + git branch side2 && + + git checkout side1 &&Please use
git switch -c side1orgit checkout -b side1: it is more compact thangit branch ... && git checkout ....Yes, but less forgiving to later modification where I go and add additional commits on one of the sides, because...
Quoted 9 lines
Quoted 8 lines
t/t4301-merge-tree-real.sh new file +7
-0+ test_write_lines 1 2 3 4 5 6 >numbers && + echo hi >greeting && + echo bar >whatever && + git add numbers greeting whatever && + git commit -m modify-stuff && + + git checkout side2 &&Quoted 6 lines
Quoted 2 lines
This could be written as
git checkout -b side2 HEAD^, to make the setup more succinct....the presumption of HEAD^ is hardcoded and has to be parsed by readers to understand the test. It felt like more cognitive overhead to me, in addition to being less malleable.
Right. Different developers, different preferences. I wish we had a standard way in the test suite to initialize a test setup that _everybody_ could agree to be succinct and helpful. So far, we use shell scripted Git commands to recreate an initial commit topology, but especially when comparing to existing test suites with fixtures that are not only well-documented but also easy to wrap your head around, I find Git's test suite awfully lacking. Mind you, the code _I_ introduced isn't stellar in this respect, either, not by a very far stretch.
Quoted 10 lines
Quoted 9 lines
t/t4301-merge-tree-real.sh line 73
Quoted 6 lines
t/t4301-merge-tree-real.sh new file +6
-0+test_expect_success 'Barf on misspelled option' ' + # Mis-spell with single "s" instead of double "s" + test_expect_code 129 git merge-tree --real --mesages FOOBAR side1 side2 2>expect && + + grep "error: unknown option.*mesages" expect +'I do not think that this test case adds much, and we already test the
parse_options()machinery elsewhere.Quoted 4 lines
It's more about verifying that exit codes of 0 & 1 are reserved for "completed with no conflicts" and "completed with conflicts". The 129 bit in this test is the important bit (and perhaps is well-known to lots of other folks, but I thought it was worth highlighting).
Fair enough.
Ciao,
Dscho
Re: [PATCH v2 4/8] merge-tree: implement real merges
In reply to Elijah NewrenQuoted 7 lines · Elijah Newren
Quoted 5 lines
'git merge-tree' [--write-tree] <branch1> <branch2> 'git merge-tree' [--demo-trivial-merge] <base-tree> <branch1> <branch2>That way, the old mode can still function, and can even at some stage be deprecated and eventually removed.
Ooh, interesting.
I wondered if we can _also_ extend the trivial-merge mode so that we do not have to call it "demo".
The internal result is expressed in this way:
struct merge_list {
struct merge_list *next;
struct merge_list *link; /* other stages for this object */
unsigned int stage : 2;
unsigned int mode;
const char *path;
struct blob *blob;
};because the command was not designed to resolve content level merges, but show the half-resolved state with the "stage" number. The "explanation" the command gives on the result is truly trivial, but there is no reason for it to stay that way.
Re: [PATCH v2 4/8] merge-tree: implement real merges
Hi Junio,
Quoted 25 lines · Junio C Hamano
Quoted 7 lines · Elijah Newren
Quoted 5 lines
'git merge-tree' [--write-tree] <branch1> <branch2> 'git merge-tree' [--demo-trivial-merge] <base-tree> <branch1> <branch2>That way, the old mode can still function, and can even at some stage be deprecated and eventually removed.
Ooh, interesting.
I wondered if we can _also_ extend the trivial-merge mode so that we do not have to call it "demo".
The internal result is expressed in this way:
struct merge_list { struct merge_list *next; struct merge_list *link; /* other stages for this object */ unsigned int stage : 2; unsigned int mode; const char *path; struct blob *blob; };
Quoted 4 lines
because the command was not designed to resolve content level merges, but show the half-resolved state with the "stage" number. The "explanation" the command gives on the result is truly trivial, but there is no reason for it to stay that way.
The original merge-tree code outputs a diff, which I think has now been firmly established as something a low-level merge tool should not do at all.
So I am not sure how necessary it is to maintain the original UI. I don't think it is a good UI. In fact, I am rather certain that we will want to get rid of it.
We can keep it for backwards-compatibility for now, keeping it working for existing users (if any!) by that 3-arg vs 2-arg trick, eventually deprecate and then remove it.
Ciao,
Dscho
Re: [PATCH v2 4/8] merge-tree: implement real merges
In reply to Elijah Newren via GitGitGadgetQuoted 3 lines · Elijah Newren via GitGitGadget
The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)
I thought that the merge-ort API could (at least theoretically according to merge-ort.h) return something < 0 in case of internal error. In this case I would be interested in knowing what's the output of the command.
Documentation/git-merge-tree.txt git-merge-tree(1)
Quoted 4 lines
Documentation/git-merge-tree.txt +4
-0+The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`.
No mention of what happens in case of an internal error in the merge-ort API.
builtin/merge-tree.c struct merge_tree_options { · line 448
Quoted 4 lines
builtin/merge-tree.c +4
-0+merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); +printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); +merge_switch_to_result (&opt,NULL , &result,0 ,0 ); +return result.clean ?0 :1 ;
If result.clean can be < 0, this might pretend that the merge was clean.
Re: [PATCH v2 4/8] merge-tree: implement real merges
Quoted 20 lines · Christian Couder
Quoted 3 lines · Elijah Newren via GitGitGadget
The only output is:
- the toplevel resulting tree printed on stdout
- exit status of 0 (clean) or 1 (conflicts present)I thought that the merge-ort API could (at least theoretically according to
merge-ort.h) return something < 0 in case of internal error. In this case I would be interested in knowing what's the output of the command.Documentation/git-merge-tree.txt git-merge-tree(1)
Quoted 4 lines
Documentation/git-merge-tree.txt +4
-0+The first form will merge the two branches, doing a full recursive +merge with rename detection. If the merge is clean, the exit status +will be `0`, and if the merge has conflicts, the exit status will be +`1`.No mention of what happens in case of an internal error in the merge-ort API.
Quoted 6 lines
builtin/merge-tree.c struct merge_tree_options { · line 448
Quoted 4 lines
builtin/merge-tree.c +4
-0+merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); +printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); +merge_switch_to_result (&opt,NULL , &result,0 ,0 ); +return result.clean ?0 :1 ;If result.clean can be < 0, this might pretend that the merge was clean.
Ooh, these are very good points. Thanks for bringing them up; I'll try to address them in a re-roll.
PATCH v2 5/8 merge-ort: split out a separate display_update_messages() function
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
No functional changes included in this patch; it's just a preparatory step in anticipation of wanting to handle the printed messages differently in git merge-tree --real.
--- merge-ort.c | 71 ++++++++++++++++++++++++++++------------------------- merge-ort.h | 8 ++++++ 2 files changed, 46 insertions(+), 33 deletions(-)
merge-ort.c +38 -33
@@ -4197,6 +4197,42 @@ 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; + +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,
@@ -4235,39 +4271,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; - -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
@@ -70,6 +70,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
PATCH v2 6/8 merge-ort: allow update messages to be written to different file stream
In reply to Elijah Newren via GitGitGadgetFrom: 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
@@ -4198,7 +4198,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;
@@ -4222,7 +4223,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 );
@@ -4272,7 +4273,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
@@ -76,7 +76,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
PATCH v2 7/8 merge-tree: support saving merge messages to a separate file
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
When running git merge-tree --real, 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 --messages option which names a file so that callers can request these messages be recorded somewhere.
--- Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 18 ++++++++++++++++-- t/t4301-merge-tree-real.sh | 18 ++++++++++++++++++ 3 files changed, 38 insertions(+), 4 deletions(-)
Documentation/git-merge-tree.txt +4 -2
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' --real <branch1> <branch2> +'git merge-tree' --real [--messages=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2> DESCRIPTION
@@ -21,7 +21,9 @@ The first form will merge the two branches, doing a full recursive merge with rename detection. If the merge is clean, the exit status will be `0`, and if the merge has conflicts, the exit status will be `1`. The output will consist solely of the resulting toplevel tree -(which may have files including conflict markers). +(which may have files including conflict markers). With `--messages`, +it will write any informational messages (such as "Auto-merging +<path>" and conflict notices) to the given file. The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +16 -2
@@ -390,6 +390,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real; +char *messages_file; }; static int real_merge(struct merge_tree_options *o,
@@ -443,8 +444,15 @@ static int real_merge(struct merge_tree_options *o,*/ merge_incore_recursive (&opt, merge_bases, parent1, parent2, &result); + +if (o->messages_file) { +FILE *fp =xfopen (o->messages_file,"w" ); +merge_display_update_messages (&opt, &result, fp); +fclose (fp); + }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid)); -merge_switch_to_result (&opt,NULL , &result,0 ,0 ); + +merge_finalize (&opt, &result);return result.clean ?0 :1 ; }
@@ -452,15 +460,18 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) {struct merge_tree_options o = {0 };int expected_remaining_argc; +int original_argc;const char *const merge_tree_usage[] = { -N_ ("git merge-tree --real <branch1> <branch2>" ), +N_ ("git merge-tree --real [<options>] <branch1> <branch2>" ),N_ ("git merge-tree <base-tree> <branch1> <branch2>" ),NULL };struct option mt_options[] = {OPT_BOOL (0 ,"real" , &o.real,N_ ("do a real merge instead of a trivial merge" )), +OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ), +N_ ("filename to write informational/conflict messages to" )),OPT_END () };
@@ -469,8 +480,11 @@ 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 && original_argc < argc) +die (_ ("--real must be specified if any other options are" )); expected_remaining_argc = (o.real ?2 :3 );if (argc != expected_remaining_argc)usage_with_options (merge_tree_usage, mt_options);
t/t4301-merge-tree-real.sh +18 -0
@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect ' +test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 && + + # 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 && + 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 MSG_FILE +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 7/8] merge-tree: support saving merge messages to a separate file
Hi Elijah,
t/t4301-merge-tree-real.sh test_expect_success 'Barf on too many arguments' ' · line 82
Quoted 10 lines · Elijah Newren via GitGitGadget
t/t4301-merge-tree-real.sh +2
-0@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect ' +test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 &&
Since we discern between exit status 1 (= merge conflict) and >1 (fatal error), we should probably use test_expect_code here.
Other than that, this patch looks good.
Thank you,
Dscho
Quoted 21 lines
t/t4301-merge-tree-real.sh +16
-1+ + # 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 && + 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 MSG_FILE +' + test_done --gitgitgadget
Re: [PATCH v2 7/8] merge-tree: support saving merge messages to a separate file
Quoted 13 lines · Johannes Schindelin
Hi Elijah,
Quoted 8 lines · Elijah Newren via GitGitGadget
t/t4301-merge-tree-real.sh +0
-0@@ -78,4 +78,22 @@ test_expect_success 'Barf on too many arguments' ' grep "^usage: git merge-tree" expect '
Quoted 5 lines
t/t4301-merge-tree-real.sh test_expect_success 'Barf on too many arguments' ' · line 82
Quoted 2 lines
t/t4301-merge-tree-real.sh +2
-0+test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --messages=MSG_FILE side1 side2 &&Since we discern between exit status 1 (= merge conflict) and >1 (fatal error), we should probably use
test_expect_codehere.
Good point; will do.
PATCH v2 8/8 merge-tree: provide an easy way to access which files have conflicts
In reply to Elijah Newren via GitGitGadgetFrom: Elijah Newren <[email protected]>
Callers of git merge-tree --real might want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and then write a NUL-separated list of unmerged filenames to the specified file.
--- Documentation/git-merge-tree.txt | 6 ++++-- builtin/merge-tree.c | 16 ++++++++++++++++ merge-ort.c | 13 +++++++++++++ merge-ort.h | 3 +++ t/t4301-merge-tree-real.sh | 9 +++++++++ 5 files changed, 45 insertions(+), 2 deletions(-)
Documentation/git-merge-tree.txt +4 -2
@@ -9,7 +9,7 @@ git-merge-tree - Perform merge without touching index or working tree SYNOPSIS -------- [verse] -'git merge-tree' --real [--messages=<file>] <branch1> <branch2> +'git merge-tree' --real [--messages=<file>] [--conflicted-list=<file>] <branch1> <branch2> 'git merge-tree' <base-tree> <branch1> <branch2> DESCRIPTION
@@ -23,7 +23,9 @@ will be `0`, and if the merge has conflicts, the exit status will be `1`. The output will consist solely of the resulting toplevel tree (which may have files including conflict markers). With `--messages`, it will write any informational messages (such as "Auto-merging -<path>" and conflict notices) to the given file. +<path>" and conflict notices) to the given file. With +`--conflicted-list`, it will write a list of unmerged files, one per +line, to the given file. The second form is meant for backward compatibility and will only do a trival merge. It reads three tree-ish, and outputs trivial merge
builtin/merge-tree.c +16 -0
@@ -391,6 +391,7 @@ static int trivial_merge(const char *base,struct merge_tree_options {int real;char *messages_file; +char *conflicted_file; }; static int real_merge(struct merge_tree_options *o,
@@ -450,6 +451,19 @@ static int real_merge(struct merge_tree_options *o,merge_display_update_messages (&opt, &result, fp);fclose (fp); } +if (o->conflicted_file) { +struct string_list conflicted_files =STRING_LIST_INIT_NODUP ; +FILE *fp =xfopen (o->conflicted_file,"w" ); +int i; + +merge_get_conflicted_files (&result, &conflicted_files); +for (i =0 ; i < conflicted_files.nr; i++) { +fprintf (fp,"%s" , conflicted_files.items[i].string); +fputc ('\0' , fp); + } +fclose (fp); +string_list_clear (&conflicted_files,0 ); + }printf ("%s\n" ,oid_to_hex (&result.tree->object.oid));merge_finalize (&opt, &result);
@@ -472,6 +486,8 @@ int cmd_merge_tree(int argc, const char **argv, const char *prefix) N_("do a real merge instead of a trivial merge")),OPT_STRING (0 ,"messages" , &o.messages_file,N_ ("file" ),N_ ("filename to write informational/conflict messages to" )), +OPT_STRING (0 ,"conflicted-list" , &o.conflicted_file,N_ ("file" ), +N_ ("filename to write list of unmerged files" )), OPT_END() };
merge-ort.c +13 -0
@@ -4234,6 +4234,19 @@ 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) { +string_list_append (conflicted_files, e->key); + } +string_list_sort (conflicted_files); +} + void merge_switch_to_result(struct merge_options *opt, struct tree *head, struct merge_result *result,
merge-ort.h +3 -0
@@ -79,6 +79,9 @@ void merge_display_update_messages(struct merge_options *opt,struct merge_result *result, FILE *stream); +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);
t/t4301-merge-tree-real.sh +9 -0
@@ -96,4 +96,13 @@ test_expect_success '--messages gives us the conflict notices and such' ' test_cmp expect MSG_FILE ' +test_expect_success '--messages gives us the conflict notices and such' ' + test_must_fail git merge-tree --real --conflicted-list=UNMERGED side1 side2 && + + cat UNMERGED | tr "\0" "\n" >actual && + test_write_lines greeting whatever~side1 >expect && + + test_cmp expect actual +' + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
Quoted 7 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Callers of
git merge-tree --realmight want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and then
s/unmerged-list/conflicted-list/
ATB,
Ramsay Jones
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
Quoted 11 lines · Ramsay Jones
Quoted 7 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Callers of
git merge-tree --realmight want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and thens/unmerged-list/conflicted-list/
Indeed. I had noticed after v1 that I had a mixture of using both "unmerged" and "conflicted" to refer to the same thing, and tried to fix it by always using the latter term. Unfortunately, I missed updating this commit message. Thanks for pointing it out; will fix.
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
In reply to Elijah Newren via GitGitGadgetHi Elijah,
Quoted 8 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Callers of
git merge-tree --realmight want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and then write a NUL-separated list of unmerged filenames to the specified file.
This patch does what the commit message says, and it looks quite plausible. However, in practice it seems that you need either a tree (if the merge succeeded) or the list of conflicted files (if the merge succeeded).
So while it looks relatively clean from the implementation's point of view, the design itself could probably withstand a bit of consideration.
As I hinted earlier (to be precise, in
https://lore.kernel.org/git/[email protected]/),
I had the chance in December to work on the server-side, using merge-ort for a bit. In the following, I will talk about this a bit more than about this particular patch, but I think it is highly relevant (not a tangent).
One of the things that became clear to me is that we really have an either/or situation here. Either the merge succeeds, and we _need_ that tree, or it fails, and we could not care less about the tree at all.
In fact, if the merge fails, we completely ignore the tree, and it would be better if we would not even write out any Git objects in that case at
all: even just writing the objects would be quite costly at the server-side scale.
So my (somewhat hacky) patches for a proof-of-concept produced _either_ the hash of the tree on stdout, _or_ a header saying that there were conflicts followed by a NUL-separated list of file names.
Mind you, I did not even get to the point of analyzing things even more deeply. My partner in crime and I only got to comparing the merge-ort way to the libgit2-based way, trying to get them to compare as much apples-to-apples as possible [*1*], and we found that even the time to spawn the Git process (~1-3ms, with all overhead counted in) is _quite_ noticeable, at server-side scale.
Of course, the merge-ort performance was _really_ nice when doing anything remotely complex, then merge-ort really blew the libgit2-based merge out of the water. But that's not the common case. The common case are merges that involve very few modified files, a single merge base, and they don't conflict. And those can be processed by the libgit2-based method within a fraction of the time it takes to even only so much as spawn git (libgit2-based merges can complete in less than a fifth millisecond, that's at most a fifth of the time it takes to merely run git merge-tree).
The difference between 0.2-0.5ms for libgit2-based merges on the one hand, and 1-3ms for merge-ort-based merges on the other hand, might not seem like much, but you have to multiply it by the times such a merge is performed on the server. Which is a _lot_. Way more often than I thought.
In this particular instance, there is a silver-lining: the libgit2-based merge is not actually recursive. It is a three-way merge. Which means that we first have to determine a merge base. In our case, this is done by spawning a Git process anyway, so one of my ideas to move forward is to fold that merge-base logic into git merge-tree, too.
Anyway, the short short is: whenever we can avoid unnecessary work, we should do so. In the context of this patch, I would say that we should avoid writing out a tree (and avoid printing its hash to stdout) if there are merge conflicts. And we should avoid writing (and later reading) a file, if we can get away with avoiding it.
At least in the default case, that is. We still might need a flag to produce some more information about those merge conflicts. But even in that case, it would be better to have a list of file names with the three associated stages than to output the hash of a tree that contains conflicts (and tons of files _without_ conflicts). The UI needs to re-generate those conflicts anyway. And remember: a tree can contain millions of files even if there is but a single conflict. It makes more sense for merge-tree --real to output a concrete list of files that conflicted, rather than expecting the caller to discern between conflicts and non-conflicts by processing a tree object.
Maybe you agree with this rationale and re-design the --real mode to try to avoid writing out files in the common case?
About the form of the patch itself: I was tempted to go with the nitpicking spirit I see on the Git mailing list these days, especially about the shell script code in the test scripts. But then I realized that I find such nitpicking pretty unhelpful, myself. The code is good as-is, even if I would write it differently. It is clear, and it does exactly what it is supposed to do.
Thank you,
Dscho
Footnote *1*: I did not _quite_ get to the point of comparing the merge-ort merges to the libgit2 ones, unfortunately. I was on my way to add code to respect merge.renames = false so that we could _truly_ compare the merge-ort merges to the libgit2 merges (we really will want to verify that the output is identical, before even considering to enable recursive merges on the server side, and then only after studying the time difference), and then had to take off due to the holidays. If you already have that need to be able to turn off rename-detection on your radar, even if only for a transitional period, I would be _so_ delighted.
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
Quoted 32 lines · Johannes Schindelin
Hi Elijah,
Quoted 8 lines · Elijah Newren via GitGitGadget
From: Elijah Newren <[email protected]>
Callers of
git merge-tree --realmight want an easy way to determine which files conflicted. While they could potentially use the --messages option and parse the resulting messages written to that file, those messages are not meant to be machine readable. Provide a simpler mechanism of having the user specify --unmerged-list=$FILENAME, and then write a NUL-separated list of unmerged filenames to the specified file.This patch does what the commit message says, and it looks quite plausible. However, in practice it seems that you need either a tree (if the merge succeeded) or the list of conflicted files (if the merge succeeded).
So while it looks relatively clean from the implementation's point of view, the design itself could probably withstand a bit of consideration.
As I hinted earlier (to be precise, in
https://lore.kernel.org/git/[email protected]/),
I had the chance in December to work on the server-side, usingmerge-ortfor a bit. In the following, I will talk about this a bit more than about this particular patch, but I think it is highly relevant (not a tangent).One of the things that became clear to me is that we really have an either/or situation here. Either the merge succeeds, and we _need_ that tree, or it fails, and we could not care less about the tree at all.
Quoted 8 lines
In fact, if the merge fails, we completely ignore the tree, and it would be better if we would not even write out any Git objects in that case at
all: even just writing the objects would be quite costly at the server-side scale.So my (somewhat hacky) patches for a proof-of-concept produced _either_ the hash of the tree on
stdout, _or_ a header saying that there were conflicts followed by a NUL-separated list of file names.
Did you really check that it only produced one of these? If you were using ort, you wrote blob and tree objects to disk, even if you didn't print their hash on stdout.
Quoted 6 lines
Mind you, I did not even get to the point of analyzing things even more deeply. My partner in crime and I only got to comparing the
merge-ortway to the libgit2-based way, trying to get them to compare as much apples-to-apples as possible [*1*], and we found that even the time to spawn the Git process (~1-3ms, with all overhead counted in) is _quite_ noticeable, at server-side scale.
1-3ms? I thought it was a good bit more than that.
Quoted 10 lines
Of course, the
merge-ortperformance was _really_ nice when doing anything remotely complex, thenmerge-ortreally blew the libgit2-based merge out of the water. But that's not the common case. The common case are merges that involve very few modified files, a single merge base, and they don't conflict. And those can be processed by the libgit2-based method within a fraction of the time it takes to even only so much as spawngit(libgit2-based merges can complete in less than a fifth millisecond, that's at most a fifth of the time it takes to merely rungit merge-tree).
Quoted 4 lines
The difference between 0.2-0.5ms for libgit2-based merges on the one hand, and 1-3ms for
merge-ort-based merges on the other hand, might not seem like much, but you have to multiply it by the times such a merge is performed on the server. Which is a _lot_. Way more often than I thought.
Ah, I had been wondering a bit about process overhead. Having something that avoids that is definitely helpful. I tried to design ort such that its API is relatively clean and easy-to-use, and I carefully tested and fixed it until it ran memory-leak free. If process execution overhead is such a big problem, perhaps you could use these new functions via libgit.a instead of invoking a git process and get the best of both worlds?
Unfortunately, in-process would run into problems with finding merge bases, because the revision walking machinery is definitely not leak-free -- an annoyance I had to deal with while attempting to clean up merge-ort since the code I was using always did the merge-base finding as well as the calls into merge-ort. (I hear Ævar has some not-yet-submitted patches that might help with memory leaks in the revision walking machinery.)
Quoted 5 lines
In this particular instance, there is a silver-lining: the libgit2-based merge is not actually recursive. It is a three-way merge. Which means that we first have to determine a merge base. In our case, this is done by spawning a Git process anyway, so one of my ideas to move forward is to fold that merge-base logic into
git merge-tree, too.
The merge-base logic is already part of merge-tree in my patches. What exactly did you do with merge-tree? Were you using the existing one and feeding it with a merge-base as an input, or did you write your own that was more like Christian's that expected a merge base?
Quoted 4 lines
Anyway, the short short is: whenever we can avoid unnecessary work, we should do so. In the context of this patch, I would say that we should avoid writing out a tree (and avoid printing its hash to
stdout) if there are merge conflicts.
We can avoid printing its hash to stdout, but it'd take significant work to avoid writing the tree to an object store, and it cannot be done at the merge-tree level, it'd require replumbing some bits of merge-ort (and making some already complex codepaths a bit more complex, but that's a price I'm willing to pay for significant performance wins).
Can I first suggest a simpler alternative that may give some performance wins despite keeping the object writes:
We could make use of the tmp_objdir API that recently merged (see b3cecf49ea ("tmp-objdir: new API for creating temporary writable databases", 2021-12-06)). We could put that tmp-objdir on /dev/shm or other ramdisk, and if the merge is clean, migrate the contents into the real object store. Perhaps we could even pack those objects first if there are a large number of them, but If it's not clean, we can just discard the tmp-objdir. Also, as a further variant on this alternative... packing these objects before migrating if there are a sufficient number of them. Now, this is rather unlikely to be needed in general by merge-tree, because you only need to write new objects (thus representing files modified on both sides, or whatever leading trees are needed to reference the updated paths). However, it might matter for big enough repos with large enough numbers of changes on both sides. And it'd align nicely with my idea for server-side rebases (where implementing this is on my TODO list), because server-side rebases are much more likely to generate a large number of objects.
But if you really want to learn about avoiding object writes...
If you really want to only write tree and blob objects when the merge is clean, then as far as I can tell you have two options in regards to the blobs: (1) you'll need to keep all files from three-way content merges simultaneously in memory until you've determined if the result is clean, so that you can then write the merged contents out as blobs at the end. Or (2) doing all the three-way content merges and keeping track of whether the result for each is clean, and if they all turn out to be clean, then redo every single one of those three-way content merges afterwards so that you can actually write out the merged-result to disk that time.
I think (2) would cost you a lot more work than you'd save, and I worry that (1) might risk using large amounts of memory in the big repositories if there are lots of changes on both sides. While that may be uncommon, I've seen folks try to merge things with lots of changes on both sides, and you do have the server side to worry about after all.
There are similar issues with the fact that trees are written as they are processed as well. Those would also require re-running afterwards to re-generate the trees from the list of relevant-files-and-trees we operate on.
However, if you are really curious about trying this out despite the fact that I think you might be causing more work than you're avoiding (or potentially requiring a lot more RAM), look for calls to write_tree() (there are precisely two in merge-ort.c, one for intermediate trees and one for the toplevel tree) and write_object_file() (there are precisely two in merge-ort.c, one within write_tree() for writing tree objects, and one in handle_content_merge() for writing blob objects).
Quoted 2 lines
And we should avoid writing (and later reading)
a file, if we can get away with avoiding it.
Sure, we can do that. Christian had a suggestion that if the --conflicted-list didn't have an associated filename, then we just print those to stdout. Would that be to your liking? And would you prefer NUL-separated, or ls-tree style escaping of filenames?
Quoted 10 lines
At least in the default case, that is. We still might need a flag to produce some more information about those merge conflicts. But even in that case, it would be better to have a list of file names with the three associated stages than to output the hash of a tree that contains conflicts (and tons of files _without_ conflicts). The UI needs to re-generate those conflicts anyway. And remember: a tree can contain millions of files even if there is but a single conflict. It makes more sense for
merge-tree --realto output a concrete list of files that conflicted, rather than expecting the caller to discern between conflicts and non-conflicts by processing a tree object.
???
Where did I suggest that we discern between conflicts and non-conflicts by processing a tree object? That's not merely something with atrocious performance, it's also utterly crazy from a UI perspective, and is downright *impossible* to achieve in general anyway. (Failure to merge binary files. modify/delete conflicts. mode conflicts. file/directory conflicts. Various rename permutatations. There's all kinds of non-content conflicts that are not representable in a tree, and which folks will miss if they attempt to parse a tree and surmise what conflicts there were.) Whatever I wrote that might have suggested such a course of action needs some serious rewording or clarification; I consider attempting that to be a horrible idea.
The tree exists so that if people want to get extra information (and presumably in a format similar to what they would find in their working directory if they had asked git merge to merge those same two branches on their laptop), then they can do so. For example, perhaps in the list of --conflicted-files they notice a file of interest and want to ask, "What's found in this particular file?". They can use the tree together with the filename to get that kind of info.
Quoted 2 lines
Maybe you agree with this rationale and re-design the
--realmode to try to avoid writing out files in the common case?
I'm totally open to something like having --conflicted-list be given without a filename (or maybe a '-') and write to stdout instead.
I'm amenable to various tradeoffs to improve performance, but I'm worried that "not-writing-tree-and-blob object files" sounds like a goal that might hurt performance rather than help it (either worse performance in general due to the need to do things twice when the merge is clean, or else a peak memory usage that is unmanageable and causes problems). Perhaps there's a smarter solution I'm not seeing, or my worries about maximum memory usage are overblown. However, if you still want to pursue such core changes, I would also like to see more performance measuring work to justify them -- especially since in the common case merge-ort won't recurse into most directories and will end up only writing a few object files (since only new blob or tree objects need to be written). In particular, I think we'd need to do the following first:
* Do a real comparison; libgit2 + the separate find-merge-base git
process, vs. the single git merge-tree --real call that handles both. Excluding the merge-base computation makes it apples to oranges in my opinion.
* While measuring the performance of the above, specifically use
trace2 to measure time spent in write_loose_object() in object-file.c. See how much time object writing actually takes vs. everything else.
* Try the tmp-objdir changes I suggested (examples of using the API
can be found by searching for "tmp_objdir" at
https://lore.kernel.org/git/d57ae218cf9eaee0b66db299ee1bba9b488b69b1.1640907369.git.gitgitgadget@gmail.com/
and https://lore.kernel.org/git/e1747ce00af7ab3170a69955b07d995d5321d6f3.1637020263.git.gitgitgadget@gmail.com/),
and then re-measure the performance particular in respect towrite_loose_object() as a percentage of overall time.
* Get some kind of measure of the maximum number of three-way
content merges needed in merges and the overall size of holding all the results in memory simultaneously. If repositories have a million files and there are merges involving a high enough percentage of files changes on both sides, then that could certainly theoretically be quite large.
I also think that doing something in-process (via linking against libgit.a?) rather than forking git merge-tree --real would probably net you _much_ bigger wins than avoiding writing these object files, though it'd be unsafe without memory leak fixes for all the merge-base computations. That, of course, might not be the only reason you're leery of such an approach.
Quoted 10 lines
About the form of the patch itself: I was tempted to go with the nitpicking spirit I see on the Git mailing list these days, especially about the shell script code in the test scripts. But then I realized that I find such nitpicking pretty unhelpful, myself. The code is good as-is, even if I would write it differently. It is clear, and it does exactly what it is supposed to do.
Thank you,
Dscho
Quoted 9 lines
Footnote *1*: I did not _quite_ get to the point of comparing the
merge-ortmerges to the libgit2 ones, unfortunately. I was on my way to add code to respectmerge.renames = falseso that we could _truly_ compare themerge-ortmerges to the libgit2 merges (we really will want to verify that the output is identical, before even considering to enable recursive merges on the server side, and then only after studying the time difference), and then had to take off due to the holidays. If you already have that need to be able to turn off rename-detection on your radar, even if only for a transitional period, I would be _so_ delighted.
Well, I had no intention of submitting it (and still don't), but I did implement it a while back for folks who have needs for it in a transitional period. It's a pretty simple change.
https://github.com/newren/git/commit/5330a9de77f56f20e546acc65c924fc783f092e6
https://github.com/newren/git/commit/2e7e8d79b0995d352558608b6308060fbc055fd1
:-)
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
Hi Elijah,
I meant to answer this mail much earlier, oh well. Sorry. I will only answer the still open questions below, clipping the quoted text to save every reader some time.
Quoted 10 lines · Elijah Newren
Quoted 3 lines · Johannes Schindelin
So my (somewhat hacky) patches for a proof-of-concept produced _either_ the hash of the tree on
stdout, _or_ a header saying that there were conflicts followed by a NUL-separated list of file names.Did you really check that it only produced one of these? If you were using ort, you wrote blob and tree objects to disk, even if you didn't print their hash on stdout.
D'oh. No, I had not checked that.
Quoted 8 lines
Quoted 6 lines
Mind you, I did not even get to the point of analyzing things even more deeply. My partner in crime and I only got to comparing the
merge-ortway to the libgit2-based way, trying to get them to compare as much apples-to-apples as possible [*1*], and we found that even the time to spawn the Git process (~1-3ms, with all overhead counted in) is _quite_ noticeable, at server-side scale.1-3ms? I thought it was a good bit more than that.
The absolute number really depends on a lot of factors, i.e. what is relevant is the relative difference between in-process vs spawning a new process.
Quoted 3 lines
If process execution overhead is such a big problem, perhaps you could use these new functions via libgit.a instead of invoking a git process and get the best of both worlds?
For now, I solved this by combining multiple Git process invocations into a single one, as described here:
Quoted 11 lines
Quoted 6 lines
In this particular instance, there is a silver-lining: the libgit2-based merge is not actually recursive. It is a three-way merge. Which means that we first have to determine a merge base. In our case, this is done by spawning a Git process anyway, so one of my ideas to move forward is to fold that merge-base logic into
git merge-tree, too.The merge-base logic is already part of merge-tree in my patches. What exactly did you do with merge-tree? Were you using the existing one and feeding it with a merge-base as an input, or did you write your own that was more like Christian's that expected a merge base?
For an apples-to-apples comparison, I had to stay with the very same merge base logic as before, i.e. originally I added a new option to merge-tree that would take a merge base and then take a non-recursive path. In my current version, it is merely an option that even determines said merge base in the same process (and yes, it leaks memory, but that does not matter because the process is short-lived anyway).
Quoted 11 lines
Quoted 4 lines
Anyway, the short short is: whenever we can avoid unnecessary work, we should do so. In the context of this patch, I would say that we should avoid writing out a tree (and avoid printing its hash to
stdout) if there are merge conflicts.We can avoid printing its hash to
stdout, but it'd take significant work to avoid writing the tree to an object store, and it cannot be done at the merge-tree level, it'd require replumbing some bits of merge-ort (and making some already complex codepaths a bit more complex, but that's a price I'm willing to pay for significant performance wins).
Yes, let's leave things as-are. It is not worth the trouble.
Quoted 43 lines
We could make use of the
tmp_objdirAPI that recently merged (seeb3cecf49ea("tmp-objdir: new API for creating temporary writable databases", 2021-12-06)). We could put that tmp-objdir on/dev/shmor other ramdisk, and if the merge is clean, migrate the contents into the real object store. Perhaps we could even pack those objects first if there are a large number of them, but If it's not clean, we can just discard the tmp-objdir. Also, as a further variant on this alternative... packing these objects before migrating if there are a sufficient number of them. Now, this is rather unlikely to be needed in general by merge-tree, because you only need to write new objects (thus representing files modified on both sides, or whatever leading trees are needed to reference the updated paths). However, it might matter for big enough repos with large enough numbers of changes on both sides. And it'd align nicely with my idea for server-side rebases (where implementing this is on my TODO list), because server-side rebases are much more likely to generate a large number of objects.But if you really want to learn about avoiding object writes...
If you really want to only write tree and blob objects when the merge is clean, then as far as I can tell you have two options in regards to the blobs: (1) you'll need to keep all files from three-way content merges simultaneously in memory until you've determined if the result is clean, so that you can then write the merged contents out as blobs at the end. Or (2) doing all the three-way content merges and keeping track of whether the result for each is clean, and if they all turn out to be clean, then redo every single one of those three-way content merges afterwards so that you can actually write out the merged-result to disk that time.
I think (2) would cost you a lot more work than you'd save, and I worry that (1) might risk using large amounts of memory in the big repositories if there are lots of changes on both sides. While that may be uncommon, I've seen folks try to merge things with lots of changes on both sides, and you do have the server side to worry about after all.
There are similar issues with the fact that trees are written as they are processed as well. Those would also require re-running afterwards to re-generate the trees from the list of relevant-files-and-trees we operate on.
Quoted 8 lines
However, if you are really curious about trying this out despite the fact that I think you might be causing more work than you're avoiding (or potentially requiring a lot more RAM), look for calls to
write_tree()(there are precisely two inmerge-ort.c, one for intermediate trees and one for the toplevel tree) andwrite_object_file()(there are precisely two inmerge-ort.c, one withinwrite_tree()for writing tree objects, and one inhandle_content_merge()for writing blob objects).
Thank you for this thorough analysis.
I am a big fan of crossing bridges when they are reached, and not miles before that. So _iff_ it turns out that the speed, or the potential cluttering with objects, should present a problem in the future, I am inclined to follow the tmp-objdir route you described above (thank you for pointing it out, I had not made the connection to this here scenario).
Quoted 10 lines
Quoted 9 lines
Footnote *1*: I did not _quite_ get to the point of comparing the
merge-ortmerges to the libgit2 ones, unfortunately. I was on my way to add code to respectmerge.renames = falseso that we could _truly_ compare themerge-ortmerges to the libgit2 merges (we really will want to verify that the output is identical, before even considering to enable recursive merges on the server side, and then only after studying the time difference), and then had to take off due to the holidays. If you already have that need to be able to turn off rename-detection on your radar, even if only for a transitional period, I would be _so_ delighted.
Quoted 6 lines
Well, I had no intention of submitting it (and still don't), but I did implement it a while back for folks who have needs for it in a transitional period. It's a pretty simple change.
https://github.com/newren/git/commit/5330a9de77f56f20e546acc65c924fc783f092e6
https://github.com/newren/git/commit/2e7e8d79b0995d352558608b6308060fbc055fd1
Thank you _so_ much for that. It was super helpful, and it allowed me to gather enough evidence to justify continuing to work on this code.
Ciao,
Dscho
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
In reply to Johannes SchindelinHi Dscho,
One more thing I forgot to ask...
Quoted 1 line · Johannes Schindelin
...
Quoted 7 lines
Mind you, I did not even get to the point of analyzing things even more deeply. My partner in crime and I only got to comparing the
merge-ortway to the libgit2-based way, trying to get them to compare as much apples-to-apples as possible [*1*], and we found that even the time to spawn the Git process (~1-3ms, with all overhead counted in) is _quite_ noticeable, at server-side scale.
Quoted 9 lines
Of course, the
merge-ortperformance was _really_ nice when doing anything remotely complex, thenmerge-ortreally blew the libgit2-based merge out of the water. But that's not the common case. The common case are merges that involve very few modified files, a single merge base, and they don't conflict. And those can be processed by the libgit2-based method within a fraction of the time it takes to even only so much as spawngit(libgit2-based merges can complete in less than a fifth millisecond, that's at most a fifth of the time it takes to merely rungit merge-tree).
Out of curiosity, are you only doing merges, or are you also attempting server-side rebases in some fashion?
Re: [PATCH v2 8/8] merge-tree: provide an easy way to access which files have conflicts
Hi Elijah,
Quoted 21 lines · Elijah Newren
Quoted 1 line · Johannes Schindelin
...
Quoted 7 lines
Mind you, I did not even get to the point of analyzing things even more deeply. My partner in crime and I only got to comparing the
merge-ortway to the libgit2-based way, trying to get them to compare as much apples-to-apples as possible [*1*], and we found that even the time to spawn the Git process (~1-3ms, with all overhead counted in) is _quite_ noticeable, at server-side scale.Quoted 9 lines
Of course, the
merge-ortperformance was _really_ nice when doing anything remotely complex, thenmerge-ortreally blew the libgit2-based merge out of the water. But that's not the common case. The common case are merges that involve very few modified files, a single merge base, and they don't conflict. And those can be processed by the libgit2-based method within a fraction of the time it takes to even only so much as spawngit(libgit2-based merges can complete in less than a fifth millisecond, that's at most a fifth of the time it takes to merely rungit merge-tree).
Quoted 2 lines
Out of curiosity, are you only doing merges, or are you also attempting server-side rebases in some fashion?
One step after another. For now, I am focusing on merges.
But yes, rebases are on my radar, too, and I am very grateful for the head-start you provided in t/helper/test-fast-rebase.c (and for the pun therein).
Ciao,
Dscho
Quoted 4 lines · Elijah Newren via GitGitGadget
This series introduces a new option to git-merge-tree: --real (best name I could come up with). This new option is designed to allow a server-side "real" merge (or allow folks client-side to do merges with branches they don't even have checked out).
Finally. merge-tree was added by Linus mostly as a demonstration of idea to trick other developers into enhancing it to implement a full merge that does not need to touch the index or the working tree, but everybody failed to be enticed by it so far. It is true that it can be used server-side, but I do not think that is what we want to sell it as (after all, receiving a push, merging it to the history in the central repository, and checking the result out to the working tree, would be a good "server-side" operation to have, but it can be done today without this series). The selling point would rather be it is done mostly in-core, without touching working tree or the index file, no?
Exciting ;-).
Quoted 8 lines · Junio C Hamano
Quoted 4 lines · Elijah Newren via GitGitGadget
This series introduces a new option to git-merge-tree: --real (best name I could come up with). This new option is designed to allow a server-side "real" merge (or allow folks client-side to do merges with branches they don't even have checked out).
Quoted 11 lines
Finally. merge-tree was added by Linus mostly as a demonstration of idea to trick other developers into enhancing it to implement a full merge that does not need to touch the index or the working tree, but everybody failed to be enticed by it so far. It is true that it can be used server-side, but I do not think that is what we want to sell it as (after all, receiving a push, merging it to the history in the central repository, and checking the result out to the working tree, would be a good "server-side" operation to have, but it can be done today without this series). The selling point would rather be it is done mostly in-core, without touching working tree or the index file, no?
You're probably right about how we try to sell it as a project to external folks, but I was focused instead on selling it to reviewers within the project.
"Server side merge" was the name of the topic at the Git Summit and lots of folks had interesting comments back then, so I was hoping to grab people's attention with a phrase they would have seen previously and commented on.
Further, the folks I know of who have experience trying to do an in-core merge are folks who operate on the server side (using libgit2 instead of git, which they have some gripes with). I wanted their experience and views in the review and wanted to make sure it met their needs, and tried to highlight that to lure them into responding and reviewing.
Quoted 1 line
Exciting ;-).
Thanks. :-)
Quoted 4 lines · Elijah Newren via GitGitGadget
This series attempts to guess what kind of output would be wanted, basically choosing:
* clean merge or conflict signalled via exit status
(Maybe s/signalled/signaled/)
Not sure that's the best way by default. I think it's very likely that many users will be interested in parsing the command ouput, and they might prefer that merge related errors be signaled in a different way than other errors.
Quoted 2 lines
* stdout consists solely of printing the hash of the resulting tree (though that tree may include files that have conflict markers)
Maybe users will want diffs, the conflicted list and other things on stdout, as they might want to parse it anyway, and it would be a burden to have to perform diffs, or get other interesting info in a different way or using a different process or call.
Quoted 5 lines
* new optional --messages flag for specifying a file where informational messages (e.g. conflict notices and files involved in three-way-content merges) can be written; by default, this output is simply discarded
* new optional --conflicted-list flag for specifying a file where the names of conflicted-files can be written in a NUL-character-separated list
It would be nice if output was printed on stdout when the above flags are used without argument.
Thanks for working on this!
Quoted 10 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget
This series attempts to guess what kind of output would be wanted, basically choosing:
* clean merge or conflict signalled via exit status
(Maybe s/signalled/signaled/)
I can't determine the difference after a few Google searches, and both seem to be in dictionaries with the same meaning so I'm having difficulty figuring out which is preferred. Usually my searches will either suggest that one is a misspelling or at least bring up whether one is a regional variance, but I'm not seeing anything of the sort.
It can't hurt to switch, though, so I'm happy to switch.
Quoted 4 lines
Not sure that's the best way by default. I think it's very likely that many users will be interested in parsing the command ouput, and they might prefer that merge related errors be signaled in a different way than other errors.
That's fair.
I was thinking in terms of various plumbing commands: hash-object, mktree, commit-tree, read-tree, write-tree and update-ref. Output from commands in that last can be fed as input to other commands and be chained together to do various interesting and useful things. I have done that at various times in the past. I thought merge-tree might augment that category of commands (particularly since Peff suggested to make the command be low-level at the summit), and thus outputting just a tree (at least by default) would make the command be a useful building block within that context. That was part of my reason for including the code snippet
NEWTREE=$(git merge-tree --real $BRANCH1 $BRANCH2) test $? -eq 0 || die "There were conflicts..." NEWCOMMIT=$(git commit-tree $NEWTREE -p $BRANCH1 -p $BRANCH2) git update-ref $BRANCH1 $NEWCOMMIT
in the cover letter.
But merge-tree is much more likely to run into problems (i.e. into merge conflicts), so maybe it doesn't belong in the same set, and the NEWTREE definition perhaps deserves to have additional special case command-line parsing that the user needs to do.
I'm curious about others' thoughts on this matter too.
Quoted 7 lines
Quoted 2 lines
* stdout consists solely of printing the hash of the resulting tree (though that tree may include files that have conflict markers)
Maybe users will want diffs, the conflicted list and other things on stdout, as they might want to parse it anyway, and it would be a burden to have to perform diffs, or get other interesting info in a different way or using a different process or call.
You mention the stdout thing both above and below, so I'll concentrate here on the diffs part.
Do you have a specific usecase you have in mind where diffs are wanted, separate from the two examples you gave in the other thread (namely Ævar's misguided hack for looking for whether there were conflicts, and a desire to just follow merge-tree's convoluted precedent)? I'd rather not add diffs pre-emptively on the basis that users "might" want them, especially if they come with the huge gamut of options Ævar was spitballing in [1] (some of which appeared to have misguided assumptions relative to the possibility of renames and might introduce edge and corner case bugs that'd be with us forever). If we don't have concrete usecases yet, I'd rather avoid adding such options until we do have concrete usecases so we don't paint ourselves into a corner.
[1] https://lore.kernel.org/git/[email protected]/
Quoted 8 lines
Quoted 5 lines
* new optional --messages flag for specifying a file where informational messages (e.g. conflict notices and files involved in three-way-content merges) can be written; by default, this output is simply discarded
* new optional --conflicted-list flag for specifying a file where the names of conflicted-files can be written in a NUL-character-separated listIt would be nice if output was printed on stdout when the above flags are used without argument.
Oh, that's an interesting idea. The --conflicted-list flag, though, separates filenames by NUL characters, for simplicity of parsing. If I'm printing them to stdout, would that be problematic? (If so, should it instead print them in e.g. ls-tree format, where it escapes filenames only when necessary)?
Quoted 1 line
Thanks for working on this!
Am 07.01.22 um 20:59 schrieb Elijah Newren:
Quoted 13 lines
Quoted 10 lines · Christian Couder
Quoted 4 lines · Elijah Newren via GitGitGadget
This series attempts to guess what kind of output would be wanted, basically choosing:
* clean merge or conflict signalled via exit status
(Maybe s/signalled/signaled/)
Quoted 7 lines
I can't determine the difference after a few Google searches, and both seem to be in dictionaries with the same meaning so I'm having difficulty figuring out which is preferred. Usually my searches will either suggest that one is a misspelling or at least bring up whether one is a regional variance, but I'm not seeing anything of the sort.
It can't hurt to switch, though, so I'm happy to switch.
https://en.wiktionary.org/wiki/signal#Verb says: "present participle (UK) signalling or (US) signaling". And Documentation/SubmittingPatches
says: "We prefer to gradually reconcile the inconsistencies in favor of US English". So this seems to go in the right direction.
René