git merge – What is the proper way of programmatically checking if Git branches can be merged?

git merge-tree mentioned in lbergnehr’s answer is all the more valid with With Git 2.38 (Q3 2022), as “git merge-tree(man) A new mode where it takes two learned commits and computes a tree that would result in the merge commit, if the histories leading to these two commits were to be merged.


  • You can quickly test if two branches can merge (without doing the actual merge, meaning without modifying the branch, the index or worktree!):
NEWTREE=$(git merge-tree --write-tree $BRANCH1 $BRANCH2)  
test $? -eq 0 || die "There were conflicts..."
  • You can do so even if those branches are unrelated:

    git merge-tree --write-tree --allow-unrelated-histories $BRANCH1 $BRANCH2
    
  • You can list the files with conflicts and why they are in conflicts:

    git merge-tree --write-tree --no-messages branch1 branch2
    
  • You can list the files with conflicts (just their names):

    git merge-tree --write-tree --no-messages --name-only branch1 branch2
    

As I was saying, pretty impressive…


See commit 7260e87, commit 7976721, commit 7c48b27, commit de90581, commit cb26077, commit b520bc6, commit 7fa3338, commit a4040cf, commit fae26ce, commit a1a7811, commit a34edae, commit 1f0c3a2, commit 6ec755a, commit 55e48f6 (18 Jun) by Elijah Newren (newren).
See commit 2715e8a, commit 6debb75 (18 Jun 2022) by Johannes Schindelin (dscho).
(Merged by Junio ​​C Hamano — gitster — in commit be733e1, 14 Jul 2022)

merge-tree: implement real merges

Signed-off-by: Elijah Newren

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(man), the working tree and index are left alone and no branch is updated.

The only output is:

  • the toplevel resulting tree printed on stdout
  • exit status of 0 (clean), 1 (conflicts present), anything else (merge could not be performed; unknown if clean or conflicted)

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

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

Note that higher level scripts may also want to access the conflict/warning messages normally output during a merge, or have quick access to a list of files with conflicts.

This also marks the traditional trivial merge of merge-tree as deprecated.
The trivial merge not only had limited applicability, the output format was also difficult to work with (and its format undocumented), and will generally be less performant than real merges.

git merge-tree now includes in its man page:

git-merge-tree – Perform merge without touching index or working tree

git merge-tree now includes in its man page:

‘git merge-tree’ [–write-tree]
‘git merge-tree’ [–trivial-merge] (deprecated)

git merge-tree now includes in its man page:

This command has a modern --write-tree mode and a deprecated
--trivial-merge mode. With the exception of the
<<DEPMERGE,DEPRECATED DESCRIPTION>> section at the end, the rest of this documentation describes modern --write-tree mode.

Performs a merge, but does not make any new commits and does not read from or write to either the working tree or index.

The performed merge will use the same feature as the “real”
git mergeincluding:

  • three way content merges of individual files
  • rename detection
  • proper directory/file conflict handling
  • recursive ancestor consolidation (ie when there is more than one merge base, creating a virtual merge base by merging the merge bases)
  • etc.

After the merge completes, a new toplevel tree object is created. See
OUTPUT below for details.

OUTPUT

For either a successful or conflicted merge, the output from git-merge-tree is simply one line:

<OID of toplevel tree>

The printed tree object corresponds to what would be checked out in the working tree at the end of git mergeand thus may have files with conflict markers in them.

EXIT STATUS

For a successful, non-conflicted merge, the exit status is 0. When the merge has conflicts, the exit status is 1. If the merge is not able to complete (or start) due to some kind of error, the exit status is something other than 0 or 1 (and the output is unspecified).

USAGE NOTES

This command is intended as low-level plumbing, similar to
git hash-object, git mktree,
git commit-tree, git write-tree,
git update-refand git mktag. Thus, it can be used as a part of a series of steps such as:

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

DEPRECATED DESCRIPTION

Per the <<NEWMERGE,DESCRIPTION>> and unlike the rest of this documentation, this section describes the deprecated --trivial-merge
mode.

Other than the optional --trivial-mergethis mode accepts no options.

This mode 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 similar to what three-way ‘git read-tree -m‘ does, but instead of storing the results in the index, the command outputs the entries to the standard output.

This form not only has limited applicability (a trivial merge cannot handle content merges of individual files, rename detection, proper directory/file conflict handling, etc.), the output format is also difficult to work with, and it will generally be less performant than the first form even on successful merges (especially if working in large repositories).


You can avoid the information messages at the end:

merge-tree: support including merge messages in output

Signed-off-by: Elijah Newren

When running git merge-tree --write-tree(man)we previously would only return an exit status reflecting the cleanness of a merge, and print out the toplevel tree of the resulting merge.
Merges also have informational messages, such as:

  • Auto-merging <PATH>
  • CONFLICT (content): ...
  • CONFLICT (file/directory)
  • etc.

In fact, when non-content conflicts occur (such as file/directory, modify/delete, add/add with differing modes, rename/rename (1to2)etc.), these informational messages may be the only notification the user gets since these conflicts are not representable in the contents of the file.

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

git merge-tree now includes in its man page:

OPTIONS

--[no-]messages

Write any informational messages such as “Auto-merging <path>or CONFLICT notices to the end of stdout.

If unspecified, the default is to include these messages if there are merge conflicts, and to omit them otherwise.

For a successful merge, the output from git-merge-tree is simply one line:

<OID of toplevel tree>

Whereas for a conflicted merge, the output is by default of the form:

git merge-tree now includes in its man page:

OID of toplevel tree


This is a tree object that represents what would be checked out in the
working tree at the end of `git merge`.   
If there were conflicts, then
files within this tree may have embedded conflict markers.

Informational messages

This always starts with a blank line to separate it from the previous section, and then has free-form messages about the merge, such as:

  • Auto-merging <file>
  • CONFLICT (rename/delete): <oldfile> renamed...but deleted in...
  • Failed to merge submodule <submodule> (<reason>)
  • Warning: cannot merge binary files: <filename>

If you want to know which files have conflicts and why, use the previous --no-messages option!

git merge-tree --write-tree --no-messages branch1 branch2
                            ^^^^^^^^^^^^^

merge-tree: provide a list of which files have conflicts

Signed-off-by: Elijah Newren

Callers of git merge-tree(man) --write-tree will often want to know which files had conflicts.
While they could potentially attempt to parse the CONFLICT notices printed, those messages are not meant to be machine readable.
Provide a simpler mechanism of just printing the files (in the same format as git ls-files(man) with quoting, but restricted to unmerged files) in the output before the free-form messages.

git merge-tree now includes in its man page:

Conflicted file list


This is a sequence of lines containing a filename on each line, quoted
as explained for the configuration variable `core.quotePath` (see
[`git config`](https://git-scm.com/docs/git-config)).

If you want to know the list of files which have conficts (without the cause: just their filenames), use the new --name-only option!

git merge-tree --write-tree --name-only --no-messages branch1 branch2
                            ^^^^^^^^^^^

merge-tree: provide easy access to ls-files -u style info

Signed-off-by: Elijah Newren

Much like git merge(man) updates the index with information of the form

(mode, oid, stage, name)

provide this output for conflicted files for merge-tree as well.

Provide a --name-only option for users to exclude the mode, oid, and stage and only get the list of conflicted filenames.

git merge-tree now includes in its man page:

--name-only

In the Conflicted file info section, instead of writing a list of (mode, oid, stage, path) tuples to output for conflicted files, just provide a list of filenames with conflicts (and do not list filenames multiple times if they have multiple conflicting stages).

git merge-tree now includes in its man page:

This is a sequence of lines with the format

The filename will be quoted as explained for the configuration variable core.quotePath (see git config).
However, if the --name-only The option is passed, the mode, object, and stage will be omitted.


You can even do that if your two branches have no common ancestor (unrelated history/unrelated histories)!!

git merge-tree --write-tree --allow-unrelated-histories --name-only --no-messages branch1 branch2
                            ^^^^^^^^^^^^^^^^^^^^^^^^^^^

merge-tree: add a –allow-unrelated-histories flag

Signed-off-by: Elijah Newren

Folks may want to merge histories that have no common ancestry; provide a flag with the same name as used by git merge(man) to allow this.

git merge-tree now includes in its man page:

--allow-unrelated-histories

merge-tree will by default error out if the two branches specified share no common history.
This flag can be given to override that check and make the merge proceed anyway.

Leave a Comment