5

I want to test to see if one branch can be merged into another. For example, say I want to test if a release branch can be merged into master, without actually merging into master. I figured I'd do something like this:

#!/usr/env/bin bash
git checkout master
git checkout -b master_temp
git merge my_release_branch

So I figure the merge has succeeded if the exit code is 0, and otherwise the merge failed.

Here is my big concern though: say the merge succeeds, but it opens up the interactive portion. How can I avoid that interactive bit? Sometimes during merges, nano or vim opens and we have to do something (not sure what). Is there a way to turn that off?

This question is basically the same, however, the question does not stipulate that this needs to be done by anything other than a human. In my case, it needs to be automatic/programmatic.

alex
  • 6,818
  • 9
  • 52
  • 103
Alexander Mills
  • 90,741
  • 139
  • 482
  • 817
  • I've found reference to `git merge-tree` [here](https://stackoverflow.com/a/6283843/27302), but I'm not sure how to use it in this case. Are you OK with modifying the working tree or the index, or do you want it to be completely side-effect free, or what? – Daniel H Jul 14 '18 at 19:22
  • @DanielH you know on Github where they tell if two branches can be merged (w/o actually merging them)? That's what I want to do. I'd rather have no side effects, I figure that's possible if I just checkout throwaway branches? – Alexander Mills Jul 14 '18 at 19:28
  • On GitHub they do actually perform the merge in a side branch; you can fetch `refs/pull/$prnumber/merge` (although I think this behavior is no longer part of the API and they might change it). Checking out another branch would work, but would mess up `HEAD` and, if you don't have a bare repository, the working tree. You can use [`git worktree`](https://git-scm.com/docs/git-worktree) to get around that. – Daniel H Jul 14 '18 at 19:32

3 Answers3

7

Late to the party, but you can do this with

git merge-tree $(git merge-base feature_branch master) feature_branch master
lbergnehr
  • 1,578
  • 11
  • 15
  • Upvoted: that is a valid solution, especially with [Git 2.38 (Q3 2022)](https://stackoverflow.com/a/73091924/6309). – VonC Jul 23 '22 at 15:24
  • Better to use now `git merge-tree --write-tree feature_branch master` as mentioned by @VonC here https://stackoverflow.com/a/73091924/4441211 – Eyal Gerber Aug 08 '23 at 07:46
3

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.
VonC
  • 1,262,500
  • 529
  • 4,410
  • 5,250
2

Running git merge will start your preferred editor1 if and only if all of the following conditions hold:

  • The merge starts successfully (i.e., the index is not in a bad state, and the arguments to git merge were all parsed successfully).
  • Git found or made a merge base. Older versions of Git will use the empty tree as a merge base for unrelated histories; newer ones require --allow-unrelated-histories.
  • The merge base thus found is not the current (HEAD) commit, or you used --no-ff.
  • The other commit you passed to git merge is not already an ancestor of the current commit. (For octopus merges, the rules are a little more complicated, but the difference should be clear enough: there is more than one "other commit".)
  • The merge succeeded without conflicts, or all conflicts were low-level conflicts resolved via an eXtended-strategy-option (-X ours or -X theirs). (Note that the -s ours strategy never has conflicts, making this constraint vacuous there.)
  • You did not pass a -m and merge message argument (or you did but added --edit as well; see commentary below this answer).
  • You did not specify --no-commit (the editor is invoked due to the merge commit being made!).

You can therefore guarantee that Git won't run your preferred editor by passing a merge message via -m. Alternatively, you can deliberately not pass a merge message but provide an environment GIT_EDITOR setting that invokes something other than an actual editor. For instance:

GIT_EDITOR=true git merge --quiet $hash

will run the merge to completion without opening an editor, or will fail because the merge itself stopped with conflicts, or never started in the first place. And, as Daniel H noted in comments below, --no-commit suppresses the final commit, hence suppresses the editor.

Besides these techniques, it's possible to invoke git merge-recursive directly. The git stash script does so. Be careful with this trick since it bypasses a lot of the usual safety checks.


1Your preferred editor is defined by the first of these various settings:

  • $GIT_EDITOR in the environment
  • core.editor as configured through git config
  • the compiled-in default editor

Using git var GIT_EDITOR will tell you which editor you have chosen, or defaulted-to.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Doesn't that *actually do the merge* if possible? – Daniel H Jul 14 '18 at 18:45
  • @DanielH: Yes. You can opt to do it on a detached HEAD, or on a new branch; or you can reset it away. You must also be careful about fast-forwards since if `git merge` opted to do the merge as a fast-forward operation, `HEAD` needs to go back to `HEAD@{1}` or `ORIG_HEAD`, not just `HEAD^1`. – torek Jul 14 '18 at 18:46
  • I don't know how `--no-commit` interacts with the return code but that might match the desired behavior better, and it looks like `--no-edit` exists instead of using `GIT_EDITOR=true` but is discouraged. – Daniel H Jul 14 '18 at 19:15
  • @DanielH: `--no-commit` would work here too, yes. The `--no-edit` option is the default if you supply a message with `-m`; it's there sort of as a side effect of the boolean option parser, because the Git people wanted `--edit` to force invoking the editor when using `-m`, and when you say "this is a boolean flag" the built in parser adds the `--no-...` variety as well as the `--...` variety. – torek Jul 14 '18 at 19:18
  • So with the OP in mind using `--no-commit` and/or `-m` should prevent the default editor from opening? – Alexander Mills Jul 14 '18 at 19:29
  • @AlexanderMills: yes. You might want to provide a temporary index and even a temporary work-tree as well, depending on how much you want to leave the original Git repository alone. – torek Jul 14 '18 at 19:53
  • i am not sure i understand what the temp worktree or index is for. I am going to test the merge on temp branches, is that not safe enough? – Alexander Mills Jul 14 '18 at 20:28
  • Well: each repository comes with one index, which indexes / stages the contents of one work-tree. If someone is actively working in a repository, they are using both its index and its work-tree. The `git merge` command *also* uses the index and work-tree to do its job, so you must overwrite their work, or be unable to run if they have such work in progress, if you use the default index and work-tree that come with the repository. If they have no work in progress, this is not a problem. So: do they have work in progress? – torek Jul 14 '18 at 21:35