git merge-tree
mentioned in lbergnehr's answer is all the more valid with Git 2.38 (Q3 2022), as "git merge-tree
"(man) learned a new mode where it takes two commits and computes a tree that would result in the merge commit, if the histories leading to these two commits were to be merged.
TLDR; git merge-tree
just became awesome with Git 2.38 (Q3 2022)
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, commit 70176b7 (18 Jun 2022) 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 merge
, including:
- three way content merges of individual files
- rename detection
- proper directory/file conflict handling
- recursive ancestor consolidation (i.e. when there is more than one
merge base, creating a virtual merge base by merging the merge bases)
- etc.
After the merge completes, 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 merge
, and thus may have files
with conflict markers in them.
EXIT STATUS
For a successful, non-conflicted merge, the exit status is 0. When the
merge has conflicts, the exit status is 1. If the merge is not able to
complete (or start) due to some kind of error, the exit status is
something other than 0 or 1 (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-ref
, and 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-merge
, this 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
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.
With Git 2.39 (Q4 2022), "git merge-tree --stdin
"(man) is a new way to request a series of merges and report the merge results.
See commit ec1edbc, commit a9f5bb8 (23 Oct 2022) by Elijah Newren (newren
).
(Merged by Taylor Blau -- ttaylorr
-- in commit b1e3dd6, 30 Oct 2022)
merge-tree
: support multiple batched merges with --stdin
Signed-off-by: Elijah Newren
Add an option, --stdin
, to merge-tree which will accept lines of input with two branches to merge per line, and which will perform all the merges and give output for each in turn.
This option implies -z
, and modifies the output to also include a merge status since the exit code of the program can no longer convey that information now that multiple merges are involved.
This could be useful, for example, by Git hosting providers.
When one branch is updated, one may want to check whether all code reviews targeting that branch can still cleanly merge.
Avoiding the overhead of starting up a separate process for each of those code reviews might provide significant savings in a repository with many code reviews.
git merge-tree
now includes in its man page:
If --stdin
is passed, then there is
an extra section at the beginning, a NUL character at the end, and then
all the sections repeat for each line of input. Thus, if the first merge
is conflicted and the second is clean, the output would be of the form:
<Merge status>
<OID of toplevel tree>
<Conflicted file info>
<Informational messages>
NUL
<Merge status>
<OID of toplevel tree>
NUL
Merge status
This is an integer status followed by a NUL character.
The integer status is:
0
: merge had conflicts
1
: merge was clean
<0
: something prevented the merge from running
(e.g. access to repository objects denied by filesystem)
git merge-tree
now includes in its man page:
something other than 0 or 1 (and the output is unspecified).
When --stdin
is passed, the return status is
- 0 for both successful and conflicted merges, and
- something other than 0 or 1 if it cannot complete all the requested merges.