git recursive merge strategy options within .gitattributes
I'm struggling with forcing a specific merge strategy (-s recursive -Xours
) for certain files within Git's .gitattributes
file.
I have been playing with both .git/config and .gitattributes files with no success, what I managed so far does not take into account the -X
option.
.gitattributes
*pom.xml merge=recursive
.git/config
[merge "recursive"]
driver = true
git version-control
add a comment |
I'm struggling with forcing a specific merge strategy (-s recursive -Xours
) for certain files within Git's .gitattributes
file.
I have been playing with both .git/config and .gitattributes files with no success, what I managed so far does not take into account the -X
option.
.gitattributes
*pom.xml merge=recursive
.git/config
[merge "recursive"]
driver = true
git version-control
add a comment |
I'm struggling with forcing a specific merge strategy (-s recursive -Xours
) for certain files within Git's .gitattributes
file.
I have been playing with both .git/config and .gitattributes files with no success, what I managed so far does not take into account the -X
option.
.gitattributes
*pom.xml merge=recursive
.git/config
[merge "recursive"]
driver = true
git version-control
I'm struggling with forcing a specific merge strategy (-s recursive -Xours
) for certain files within Git's .gitattributes
file.
I have been playing with both .git/config and .gitattributes files with no success, what I managed so far does not take into account the -X
option.
.gitattributes
*pom.xml merge=recursive
.git/config
[merge "recursive"]
driver = true
git version-control
git version-control
edited Nov 22 '18 at 10:49
codeJack
asked Nov 22 '18 at 10:46
codeJackcodeJack
85531326
85531326
add a comment |
add a comment |
1 Answer
1
active
oldest
votes
TL;DR
Use git merge-file
in your merge driver. Here, you can specify the options you want. You're asking for --ours
. Be aware that this does not always work right. You might even want --union
, but be aware that this does not really work right either. See Can git be made to mostly auto-merge XML order-insensitive files? and VS 2013 C# and Git: .csproj files don't merge upon pulling for issues with trying to use either method with XML files.
Long
First, let's define the terms the way Git uses them:
A strategy is the argument you pass to the
-s
flag togit merge
:git merge -s recursive
,git merge -s resolve
,git merge -s ours
, orgit merge -s octopus
. (In practice you would never use the last one explicitly—instead, it's implied if you ask Git to merge multiple heads.)
In other words, this is one of the names
ours
,recursive
, orresolve
. In theory, you can write your own Git merge strategy as well, but that's very non-trivial (so much so that I have not heard of anyone doing it, though this might not mean that much).
A strategy option the argument you pass to the
-X
flag, such as-X ours
or-X rename-threshold=<number>
. I find this to be a bad name, as it's too easy to confuse with the strategy. This is especially true when speaking: did you say "strategy" and then stop speaking but mean "strategy option" and you were just interrupted, or did you say "strategy" and stop speaking and actually mean "strategy"? I like to call these extended options, which goes well with the-X
flag, which then stands for "eXtended".)A driver is something you define in a
.gitattributes
file. There are several flavors (filter, diff, and merge); here we're concerned with merge drivers. Merge drivers are invoked by a strategy; the driver could, in theory, have access to at least some of the extended-options, but in practice they don't. (Git should define%
-expanders that provide them, and/or provide them in an environment variable; this would be relatively straightforward.)
With these definitions in mind, it's easy to see that this:
I'm struggling with forcing a specific merge strategy (
-s recursive -Xours
) for certain files within Git's.gitattributes
file.
is literally impossible. That's true whether you mean strategy or strategy option here: both have been chosen long before we get to the merge driver defined in the .gitattributes
file.
All is not necessarily lost, though!
You mention specifically pom.xml
, for which you want a merge driver—the thing you can define in .gitattributes
—that does a text style merge but takes "ours" if there is a conflict, a la the extended-option "ours". What you have, instead, is a merge driver that will do an "ours" strategy merge: ignore both the merge base and their changes entirely, just taking "our" version of the file.
Compare your attempt with the answer at Whats the Proper usage of .gitattributes with merge=ours: the only difference is the spelling of the driver name, where you use recursive
instead of ours
. The name of the driver is not important, as long as it's not one of the known magic names (text
, binary
, and union
): Git will run your driver instead of doing its own file-level merge, when it determines that there is a file-level merge to be performed.
The gitattributes documentation has this to say about defining a merge driver:
The definition of a merge driver is done in the
.git/config
file, not in thegitattributes
file, so strictly speaking this manual page is a wrong place to talk about it. However...
To define a custom merge driver
filfre
, add a section to your$GIT_DIR/config
file (or$HOME/.gitconfig
file) like this:
[merge "filfre"]
name = feel-free merge driver
driver = filfre %O %A %B %L %P
recursive = binary
(Side note: "recursive = binary" is not necessarily good advice, but let's press on here for now.)
The
merge.*.name
variable gives the driver a human-readable name.
The
merge.*.driver
variable’s value is used to construct a command to run to merge ancestor’s version (%O
), current version (%A
) and the other branches’ version (%B
). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built. Additionally,%L
will be replaced with the conflict marker size (see below).
That is, %O
names a temporary file containing the merge-base version of the file, while %A
and %B
name temporary files containing the --ours
and --theirs
versions of the file. Your job, as filter-driver-writer, is to come up with a command that will perform the merge:
The merge driver is expected to leave the result of the merge in the file named with
%A
by overwriting it, and exit with zero status if it managed to merge them cleanly, or non-zero if there were conflicts.
Having performed the merge, you should overwrite the %A
-named file with the result. Since the %A
-named file contains the --ours
contents, a driver that does nothing, but then exits, declares that the correct contents for merging %O
vs %A
and %B
is whatever is already in %A
.
In other words, this is like the ours strategy. It is not like the merge-favoring-ours-in-case-of-conflicts, resolve-or-recursive-with--Xours
strategy-plus-option. But you don't want a whole strategy at all, as that deals with entire commits; you want a single file operation. This is where git merge-file
comes in. Let's finish off the rest of the description now, though:
The
merge.*.recursive
variable specifies what other merge driver to use when the merge driver is called for an internal merge between common ancestors, when there are more than one. When left unspecified, the driver itself is used for both internal merge and the final merge.
This is a bit confusing, but it applies only to the recursive strategy, and even then, only if git merge-base --all HEAD <theirs>
prints out multiple hash IDs. Most of the time, you don't need anything special here at all. Using recursive = binary
is equivalent to setting, for this one file, the strategy to resolve
, because the binary
internal merge driver just takes the ours
version, which means that when -s recursive
repeatedly merges merge bases, it just gets the first merge base version back each time.
Multiple merge bases are rare, and it's not always clear what to do with them, but omitting any setting will cause the internal recursive merges to use your merge driver, which is probably fine for most cases. Your driver won't be called recursively; the recursion happens outside the calls to your driver. If you view the recursion as a tree, your driver is, in effect, called as a post-order traversal.
The merge driver can learn the pathname in which the merged result will be stored via placeholder
%P
.
More concretely, your driver will be called with %A
expanding to, e.g., .mergetmpA12345
, %B
expanding to .mergetmpB12345
, and %O
expanding to .mergetmpO12345
. This is true regardless of whether you're being called to merge three copies of README.txt
, three copies of main.py
, or three copies of pom.xml
: the temporary file names will be .merge<something>
.
If you want, for whatever reason, to know that the "real name" of the file is README.txt
or main.py
or whatever, that is available in %P
. Don't use that file now—that's neither an input nor an output. That's just the work-tree file's eventual name—it is, or will be, its (eventual) name, once the merge is all done. For the moment, though, the name you should use—the one where you should deliver your merged file data—is whatever %A
expands to (some .merge<something>
).
Using git merge-file
The git merge-file
documentation tells us that the usage for git merge-file
is:
git merge-file [
-L <current-name>
[-L <base-name>
[-L <other-name>
]]]
[--ours
|--theirs
|--union
] [-p
|--stdout
] [-q
|--quiet
] [--marker-size=<n>
]
[--
[no-
]diff3
]<current-file> <base-file> <other-file>
The three -L
options are labels that git merge-file
will place around conflicted sections. The --marker-size
option sets the size of the marker that git merge-file
will place around conflicted sections. The defaults are pretty similar to Git's own internal merge, so a lot of the time, these are not needed. In your case, you're going to specify --ours
, which means there will never be unresolved conflicts and therefore -L
and --marker-size
are not needed.
The --ours
or --theirs
or --union
flag tells git merge-file
how to handle conflicts. Two of these flags are the same as the extended-options -X ours
and -X theirs
; you gain a third one that's not available in regular Git merge.
The --diff3
or --no-diff3
options again affect how conflicts are shown: they are equivalent to setting merge.style
to diff3
or to merge
respectively. Since you won't have conflicts, these are irrelevant.
Finally, you need to name three files: one containing the current ("ours") version data, one with the merge base version, and one with the other ("theirs") version. When git merge-file
finishes, it will rewrite the current-version file with the merged data. Your git merge
has, conveniently, built three such files, and uses them exactly this way, so you need only provide the %A %O %B
options:
*pom.xml merge=favor-ours
and:
[merge "favor-ours"]
name = merge driver that merges but favors ours a la -X ours
driver = git merge-file --ours %A %O %B
Because of the --ours
option, this merge will always succeed (git merge-file
will exit 0), even if the result is not valid XML.
add a comment |
Your Answer
StackExchange.ifUsing("editor", function () {
StackExchange.using("externalEditor", function () {
StackExchange.using("snippets", function () {
StackExchange.snippets.init();
});
});
}, "code-snippets");
StackExchange.ready(function() {
var channelOptions = {
tags: "".split(" "),
id: "1"
};
initTagRenderer("".split(" "), "".split(" "), channelOptions);
StackExchange.using("externalEditor", function() {
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled) {
StackExchange.using("snippets", function() {
createEditor();
});
}
else {
createEditor();
}
});
function createEditor() {
StackExchange.prepareEditor({
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader: {
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
},
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
});
}
});
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53429202%2fgit-recursive-merge-strategy-options-within-gitattributes%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
1 Answer
1
active
oldest
votes
1 Answer
1
active
oldest
votes
active
oldest
votes
active
oldest
votes
TL;DR
Use git merge-file
in your merge driver. Here, you can specify the options you want. You're asking for --ours
. Be aware that this does not always work right. You might even want --union
, but be aware that this does not really work right either. See Can git be made to mostly auto-merge XML order-insensitive files? and VS 2013 C# and Git: .csproj files don't merge upon pulling for issues with trying to use either method with XML files.
Long
First, let's define the terms the way Git uses them:
A strategy is the argument you pass to the
-s
flag togit merge
:git merge -s recursive
,git merge -s resolve
,git merge -s ours
, orgit merge -s octopus
. (In practice you would never use the last one explicitly—instead, it's implied if you ask Git to merge multiple heads.)
In other words, this is one of the names
ours
,recursive
, orresolve
. In theory, you can write your own Git merge strategy as well, but that's very non-trivial (so much so that I have not heard of anyone doing it, though this might not mean that much).
A strategy option the argument you pass to the
-X
flag, such as-X ours
or-X rename-threshold=<number>
. I find this to be a bad name, as it's too easy to confuse with the strategy. This is especially true when speaking: did you say "strategy" and then stop speaking but mean "strategy option" and you were just interrupted, or did you say "strategy" and stop speaking and actually mean "strategy"? I like to call these extended options, which goes well with the-X
flag, which then stands for "eXtended".)A driver is something you define in a
.gitattributes
file. There are several flavors (filter, diff, and merge); here we're concerned with merge drivers. Merge drivers are invoked by a strategy; the driver could, in theory, have access to at least some of the extended-options, but in practice they don't. (Git should define%
-expanders that provide them, and/or provide them in an environment variable; this would be relatively straightforward.)
With these definitions in mind, it's easy to see that this:
I'm struggling with forcing a specific merge strategy (
-s recursive -Xours
) for certain files within Git's.gitattributes
file.
is literally impossible. That's true whether you mean strategy or strategy option here: both have been chosen long before we get to the merge driver defined in the .gitattributes
file.
All is not necessarily lost, though!
You mention specifically pom.xml
, for which you want a merge driver—the thing you can define in .gitattributes
—that does a text style merge but takes "ours" if there is a conflict, a la the extended-option "ours". What you have, instead, is a merge driver that will do an "ours" strategy merge: ignore both the merge base and their changes entirely, just taking "our" version of the file.
Compare your attempt with the answer at Whats the Proper usage of .gitattributes with merge=ours: the only difference is the spelling of the driver name, where you use recursive
instead of ours
. The name of the driver is not important, as long as it's not one of the known magic names (text
, binary
, and union
): Git will run your driver instead of doing its own file-level merge, when it determines that there is a file-level merge to be performed.
The gitattributes documentation has this to say about defining a merge driver:
The definition of a merge driver is done in the
.git/config
file, not in thegitattributes
file, so strictly speaking this manual page is a wrong place to talk about it. However...
To define a custom merge driver
filfre
, add a section to your$GIT_DIR/config
file (or$HOME/.gitconfig
file) like this:
[merge "filfre"]
name = feel-free merge driver
driver = filfre %O %A %B %L %P
recursive = binary
(Side note: "recursive = binary" is not necessarily good advice, but let's press on here for now.)
The
merge.*.name
variable gives the driver a human-readable name.
The
merge.*.driver
variable’s value is used to construct a command to run to merge ancestor’s version (%O
), current version (%A
) and the other branches’ version (%B
). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built. Additionally,%L
will be replaced with the conflict marker size (see below).
That is, %O
names a temporary file containing the merge-base version of the file, while %A
and %B
name temporary files containing the --ours
and --theirs
versions of the file. Your job, as filter-driver-writer, is to come up with a command that will perform the merge:
The merge driver is expected to leave the result of the merge in the file named with
%A
by overwriting it, and exit with zero status if it managed to merge them cleanly, or non-zero if there were conflicts.
Having performed the merge, you should overwrite the %A
-named file with the result. Since the %A
-named file contains the --ours
contents, a driver that does nothing, but then exits, declares that the correct contents for merging %O
vs %A
and %B
is whatever is already in %A
.
In other words, this is like the ours strategy. It is not like the merge-favoring-ours-in-case-of-conflicts, resolve-or-recursive-with--Xours
strategy-plus-option. But you don't want a whole strategy at all, as that deals with entire commits; you want a single file operation. This is where git merge-file
comes in. Let's finish off the rest of the description now, though:
The
merge.*.recursive
variable specifies what other merge driver to use when the merge driver is called for an internal merge between common ancestors, when there are more than one. When left unspecified, the driver itself is used for both internal merge and the final merge.
This is a bit confusing, but it applies only to the recursive strategy, and even then, only if git merge-base --all HEAD <theirs>
prints out multiple hash IDs. Most of the time, you don't need anything special here at all. Using recursive = binary
is equivalent to setting, for this one file, the strategy to resolve
, because the binary
internal merge driver just takes the ours
version, which means that when -s recursive
repeatedly merges merge bases, it just gets the first merge base version back each time.
Multiple merge bases are rare, and it's not always clear what to do with them, but omitting any setting will cause the internal recursive merges to use your merge driver, which is probably fine for most cases. Your driver won't be called recursively; the recursion happens outside the calls to your driver. If you view the recursion as a tree, your driver is, in effect, called as a post-order traversal.
The merge driver can learn the pathname in which the merged result will be stored via placeholder
%P
.
More concretely, your driver will be called with %A
expanding to, e.g., .mergetmpA12345
, %B
expanding to .mergetmpB12345
, and %O
expanding to .mergetmpO12345
. This is true regardless of whether you're being called to merge three copies of README.txt
, three copies of main.py
, or three copies of pom.xml
: the temporary file names will be .merge<something>
.
If you want, for whatever reason, to know that the "real name" of the file is README.txt
or main.py
or whatever, that is available in %P
. Don't use that file now—that's neither an input nor an output. That's just the work-tree file's eventual name—it is, or will be, its (eventual) name, once the merge is all done. For the moment, though, the name you should use—the one where you should deliver your merged file data—is whatever %A
expands to (some .merge<something>
).
Using git merge-file
The git merge-file
documentation tells us that the usage for git merge-file
is:
git merge-file [
-L <current-name>
[-L <base-name>
[-L <other-name>
]]]
[--ours
|--theirs
|--union
] [-p
|--stdout
] [-q
|--quiet
] [--marker-size=<n>
]
[--
[no-
]diff3
]<current-file> <base-file> <other-file>
The three -L
options are labels that git merge-file
will place around conflicted sections. The --marker-size
option sets the size of the marker that git merge-file
will place around conflicted sections. The defaults are pretty similar to Git's own internal merge, so a lot of the time, these are not needed. In your case, you're going to specify --ours
, which means there will never be unresolved conflicts and therefore -L
and --marker-size
are not needed.
The --ours
or --theirs
or --union
flag tells git merge-file
how to handle conflicts. Two of these flags are the same as the extended-options -X ours
and -X theirs
; you gain a third one that's not available in regular Git merge.
The --diff3
or --no-diff3
options again affect how conflicts are shown: they are equivalent to setting merge.style
to diff3
or to merge
respectively. Since you won't have conflicts, these are irrelevant.
Finally, you need to name three files: one containing the current ("ours") version data, one with the merge base version, and one with the other ("theirs") version. When git merge-file
finishes, it will rewrite the current-version file with the merged data. Your git merge
has, conveniently, built three such files, and uses them exactly this way, so you need only provide the %A %O %B
options:
*pom.xml merge=favor-ours
and:
[merge "favor-ours"]
name = merge driver that merges but favors ours a la -X ours
driver = git merge-file --ours %A %O %B
Because of the --ours
option, this merge will always succeed (git merge-file
will exit 0), even if the result is not valid XML.
add a comment |
TL;DR
Use git merge-file
in your merge driver. Here, you can specify the options you want. You're asking for --ours
. Be aware that this does not always work right. You might even want --union
, but be aware that this does not really work right either. See Can git be made to mostly auto-merge XML order-insensitive files? and VS 2013 C# and Git: .csproj files don't merge upon pulling for issues with trying to use either method with XML files.
Long
First, let's define the terms the way Git uses them:
A strategy is the argument you pass to the
-s
flag togit merge
:git merge -s recursive
,git merge -s resolve
,git merge -s ours
, orgit merge -s octopus
. (In practice you would never use the last one explicitly—instead, it's implied if you ask Git to merge multiple heads.)
In other words, this is one of the names
ours
,recursive
, orresolve
. In theory, you can write your own Git merge strategy as well, but that's very non-trivial (so much so that I have not heard of anyone doing it, though this might not mean that much).
A strategy option the argument you pass to the
-X
flag, such as-X ours
or-X rename-threshold=<number>
. I find this to be a bad name, as it's too easy to confuse with the strategy. This is especially true when speaking: did you say "strategy" and then stop speaking but mean "strategy option" and you were just interrupted, or did you say "strategy" and stop speaking and actually mean "strategy"? I like to call these extended options, which goes well with the-X
flag, which then stands for "eXtended".)A driver is something you define in a
.gitattributes
file. There are several flavors (filter, diff, and merge); here we're concerned with merge drivers. Merge drivers are invoked by a strategy; the driver could, in theory, have access to at least some of the extended-options, but in practice they don't. (Git should define%
-expanders that provide them, and/or provide them in an environment variable; this would be relatively straightforward.)
With these definitions in mind, it's easy to see that this:
I'm struggling with forcing a specific merge strategy (
-s recursive -Xours
) for certain files within Git's.gitattributes
file.
is literally impossible. That's true whether you mean strategy or strategy option here: both have been chosen long before we get to the merge driver defined in the .gitattributes
file.
All is not necessarily lost, though!
You mention specifically pom.xml
, for which you want a merge driver—the thing you can define in .gitattributes
—that does a text style merge but takes "ours" if there is a conflict, a la the extended-option "ours". What you have, instead, is a merge driver that will do an "ours" strategy merge: ignore both the merge base and their changes entirely, just taking "our" version of the file.
Compare your attempt with the answer at Whats the Proper usage of .gitattributes with merge=ours: the only difference is the spelling of the driver name, where you use recursive
instead of ours
. The name of the driver is not important, as long as it's not one of the known magic names (text
, binary
, and union
): Git will run your driver instead of doing its own file-level merge, when it determines that there is a file-level merge to be performed.
The gitattributes documentation has this to say about defining a merge driver:
The definition of a merge driver is done in the
.git/config
file, not in thegitattributes
file, so strictly speaking this manual page is a wrong place to talk about it. However...
To define a custom merge driver
filfre
, add a section to your$GIT_DIR/config
file (or$HOME/.gitconfig
file) like this:
[merge "filfre"]
name = feel-free merge driver
driver = filfre %O %A %B %L %P
recursive = binary
(Side note: "recursive = binary" is not necessarily good advice, but let's press on here for now.)
The
merge.*.name
variable gives the driver a human-readable name.
The
merge.*.driver
variable’s value is used to construct a command to run to merge ancestor’s version (%O
), current version (%A
) and the other branches’ version (%B
). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built. Additionally,%L
will be replaced with the conflict marker size (see below).
That is, %O
names a temporary file containing the merge-base version of the file, while %A
and %B
name temporary files containing the --ours
and --theirs
versions of the file. Your job, as filter-driver-writer, is to come up with a command that will perform the merge:
The merge driver is expected to leave the result of the merge in the file named with
%A
by overwriting it, and exit with zero status if it managed to merge them cleanly, or non-zero if there were conflicts.
Having performed the merge, you should overwrite the %A
-named file with the result. Since the %A
-named file contains the --ours
contents, a driver that does nothing, but then exits, declares that the correct contents for merging %O
vs %A
and %B
is whatever is already in %A
.
In other words, this is like the ours strategy. It is not like the merge-favoring-ours-in-case-of-conflicts, resolve-or-recursive-with--Xours
strategy-plus-option. But you don't want a whole strategy at all, as that deals with entire commits; you want a single file operation. This is where git merge-file
comes in. Let's finish off the rest of the description now, though:
The
merge.*.recursive
variable specifies what other merge driver to use when the merge driver is called for an internal merge between common ancestors, when there are more than one. When left unspecified, the driver itself is used for both internal merge and the final merge.
This is a bit confusing, but it applies only to the recursive strategy, and even then, only if git merge-base --all HEAD <theirs>
prints out multiple hash IDs. Most of the time, you don't need anything special here at all. Using recursive = binary
is equivalent to setting, for this one file, the strategy to resolve
, because the binary
internal merge driver just takes the ours
version, which means that when -s recursive
repeatedly merges merge bases, it just gets the first merge base version back each time.
Multiple merge bases are rare, and it's not always clear what to do with them, but omitting any setting will cause the internal recursive merges to use your merge driver, which is probably fine for most cases. Your driver won't be called recursively; the recursion happens outside the calls to your driver. If you view the recursion as a tree, your driver is, in effect, called as a post-order traversal.
The merge driver can learn the pathname in which the merged result will be stored via placeholder
%P
.
More concretely, your driver will be called with %A
expanding to, e.g., .mergetmpA12345
, %B
expanding to .mergetmpB12345
, and %O
expanding to .mergetmpO12345
. This is true regardless of whether you're being called to merge three copies of README.txt
, three copies of main.py
, or three copies of pom.xml
: the temporary file names will be .merge<something>
.
If you want, for whatever reason, to know that the "real name" of the file is README.txt
or main.py
or whatever, that is available in %P
. Don't use that file now—that's neither an input nor an output. That's just the work-tree file's eventual name—it is, or will be, its (eventual) name, once the merge is all done. For the moment, though, the name you should use—the one where you should deliver your merged file data—is whatever %A
expands to (some .merge<something>
).
Using git merge-file
The git merge-file
documentation tells us that the usage for git merge-file
is:
git merge-file [
-L <current-name>
[-L <base-name>
[-L <other-name>
]]]
[--ours
|--theirs
|--union
] [-p
|--stdout
] [-q
|--quiet
] [--marker-size=<n>
]
[--
[no-
]diff3
]<current-file> <base-file> <other-file>
The three -L
options are labels that git merge-file
will place around conflicted sections. The --marker-size
option sets the size of the marker that git merge-file
will place around conflicted sections. The defaults are pretty similar to Git's own internal merge, so a lot of the time, these are not needed. In your case, you're going to specify --ours
, which means there will never be unresolved conflicts and therefore -L
and --marker-size
are not needed.
The --ours
or --theirs
or --union
flag tells git merge-file
how to handle conflicts. Two of these flags are the same as the extended-options -X ours
and -X theirs
; you gain a third one that's not available in regular Git merge.
The --diff3
or --no-diff3
options again affect how conflicts are shown: they are equivalent to setting merge.style
to diff3
or to merge
respectively. Since you won't have conflicts, these are irrelevant.
Finally, you need to name three files: one containing the current ("ours") version data, one with the merge base version, and one with the other ("theirs") version. When git merge-file
finishes, it will rewrite the current-version file with the merged data. Your git merge
has, conveniently, built three such files, and uses them exactly this way, so you need only provide the %A %O %B
options:
*pom.xml merge=favor-ours
and:
[merge "favor-ours"]
name = merge driver that merges but favors ours a la -X ours
driver = git merge-file --ours %A %O %B
Because of the --ours
option, this merge will always succeed (git merge-file
will exit 0), even if the result is not valid XML.
add a comment |
TL;DR
Use git merge-file
in your merge driver. Here, you can specify the options you want. You're asking for --ours
. Be aware that this does not always work right. You might even want --union
, but be aware that this does not really work right either. See Can git be made to mostly auto-merge XML order-insensitive files? and VS 2013 C# and Git: .csproj files don't merge upon pulling for issues with trying to use either method with XML files.
Long
First, let's define the terms the way Git uses them:
A strategy is the argument you pass to the
-s
flag togit merge
:git merge -s recursive
,git merge -s resolve
,git merge -s ours
, orgit merge -s octopus
. (In practice you would never use the last one explicitly—instead, it's implied if you ask Git to merge multiple heads.)
In other words, this is one of the names
ours
,recursive
, orresolve
. In theory, you can write your own Git merge strategy as well, but that's very non-trivial (so much so that I have not heard of anyone doing it, though this might not mean that much).
A strategy option the argument you pass to the
-X
flag, such as-X ours
or-X rename-threshold=<number>
. I find this to be a bad name, as it's too easy to confuse with the strategy. This is especially true when speaking: did you say "strategy" and then stop speaking but mean "strategy option" and you were just interrupted, or did you say "strategy" and stop speaking and actually mean "strategy"? I like to call these extended options, which goes well with the-X
flag, which then stands for "eXtended".)A driver is something you define in a
.gitattributes
file. There are several flavors (filter, diff, and merge); here we're concerned with merge drivers. Merge drivers are invoked by a strategy; the driver could, in theory, have access to at least some of the extended-options, but in practice they don't. (Git should define%
-expanders that provide them, and/or provide them in an environment variable; this would be relatively straightforward.)
With these definitions in mind, it's easy to see that this:
I'm struggling with forcing a specific merge strategy (
-s recursive -Xours
) for certain files within Git's.gitattributes
file.
is literally impossible. That's true whether you mean strategy or strategy option here: both have been chosen long before we get to the merge driver defined in the .gitattributes
file.
All is not necessarily lost, though!
You mention specifically pom.xml
, for which you want a merge driver—the thing you can define in .gitattributes
—that does a text style merge but takes "ours" if there is a conflict, a la the extended-option "ours". What you have, instead, is a merge driver that will do an "ours" strategy merge: ignore both the merge base and their changes entirely, just taking "our" version of the file.
Compare your attempt with the answer at Whats the Proper usage of .gitattributes with merge=ours: the only difference is the spelling of the driver name, where you use recursive
instead of ours
. The name of the driver is not important, as long as it's not one of the known magic names (text
, binary
, and union
): Git will run your driver instead of doing its own file-level merge, when it determines that there is a file-level merge to be performed.
The gitattributes documentation has this to say about defining a merge driver:
The definition of a merge driver is done in the
.git/config
file, not in thegitattributes
file, so strictly speaking this manual page is a wrong place to talk about it. However...
To define a custom merge driver
filfre
, add a section to your$GIT_DIR/config
file (or$HOME/.gitconfig
file) like this:
[merge "filfre"]
name = feel-free merge driver
driver = filfre %O %A %B %L %P
recursive = binary
(Side note: "recursive = binary" is not necessarily good advice, but let's press on here for now.)
The
merge.*.name
variable gives the driver a human-readable name.
The
merge.*.driver
variable’s value is used to construct a command to run to merge ancestor’s version (%O
), current version (%A
) and the other branches’ version (%B
). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built. Additionally,%L
will be replaced with the conflict marker size (see below).
That is, %O
names a temporary file containing the merge-base version of the file, while %A
and %B
name temporary files containing the --ours
and --theirs
versions of the file. Your job, as filter-driver-writer, is to come up with a command that will perform the merge:
The merge driver is expected to leave the result of the merge in the file named with
%A
by overwriting it, and exit with zero status if it managed to merge them cleanly, or non-zero if there were conflicts.
Having performed the merge, you should overwrite the %A
-named file with the result. Since the %A
-named file contains the --ours
contents, a driver that does nothing, but then exits, declares that the correct contents for merging %O
vs %A
and %B
is whatever is already in %A
.
In other words, this is like the ours strategy. It is not like the merge-favoring-ours-in-case-of-conflicts, resolve-or-recursive-with--Xours
strategy-plus-option. But you don't want a whole strategy at all, as that deals with entire commits; you want a single file operation. This is where git merge-file
comes in. Let's finish off the rest of the description now, though:
The
merge.*.recursive
variable specifies what other merge driver to use when the merge driver is called for an internal merge between common ancestors, when there are more than one. When left unspecified, the driver itself is used for both internal merge and the final merge.
This is a bit confusing, but it applies only to the recursive strategy, and even then, only if git merge-base --all HEAD <theirs>
prints out multiple hash IDs. Most of the time, you don't need anything special here at all. Using recursive = binary
is equivalent to setting, for this one file, the strategy to resolve
, because the binary
internal merge driver just takes the ours
version, which means that when -s recursive
repeatedly merges merge bases, it just gets the first merge base version back each time.
Multiple merge bases are rare, and it's not always clear what to do with them, but omitting any setting will cause the internal recursive merges to use your merge driver, which is probably fine for most cases. Your driver won't be called recursively; the recursion happens outside the calls to your driver. If you view the recursion as a tree, your driver is, in effect, called as a post-order traversal.
The merge driver can learn the pathname in which the merged result will be stored via placeholder
%P
.
More concretely, your driver will be called with %A
expanding to, e.g., .mergetmpA12345
, %B
expanding to .mergetmpB12345
, and %O
expanding to .mergetmpO12345
. This is true regardless of whether you're being called to merge three copies of README.txt
, three copies of main.py
, or three copies of pom.xml
: the temporary file names will be .merge<something>
.
If you want, for whatever reason, to know that the "real name" of the file is README.txt
or main.py
or whatever, that is available in %P
. Don't use that file now—that's neither an input nor an output. That's just the work-tree file's eventual name—it is, or will be, its (eventual) name, once the merge is all done. For the moment, though, the name you should use—the one where you should deliver your merged file data—is whatever %A
expands to (some .merge<something>
).
Using git merge-file
The git merge-file
documentation tells us that the usage for git merge-file
is:
git merge-file [
-L <current-name>
[-L <base-name>
[-L <other-name>
]]]
[--ours
|--theirs
|--union
] [-p
|--stdout
] [-q
|--quiet
] [--marker-size=<n>
]
[--
[no-
]diff3
]<current-file> <base-file> <other-file>
The three -L
options are labels that git merge-file
will place around conflicted sections. The --marker-size
option sets the size of the marker that git merge-file
will place around conflicted sections. The defaults are pretty similar to Git's own internal merge, so a lot of the time, these are not needed. In your case, you're going to specify --ours
, which means there will never be unresolved conflicts and therefore -L
and --marker-size
are not needed.
The --ours
or --theirs
or --union
flag tells git merge-file
how to handle conflicts. Two of these flags are the same as the extended-options -X ours
and -X theirs
; you gain a third one that's not available in regular Git merge.
The --diff3
or --no-diff3
options again affect how conflicts are shown: they are equivalent to setting merge.style
to diff3
or to merge
respectively. Since you won't have conflicts, these are irrelevant.
Finally, you need to name three files: one containing the current ("ours") version data, one with the merge base version, and one with the other ("theirs") version. When git merge-file
finishes, it will rewrite the current-version file with the merged data. Your git merge
has, conveniently, built three such files, and uses them exactly this way, so you need only provide the %A %O %B
options:
*pom.xml merge=favor-ours
and:
[merge "favor-ours"]
name = merge driver that merges but favors ours a la -X ours
driver = git merge-file --ours %A %O %B
Because of the --ours
option, this merge will always succeed (git merge-file
will exit 0), even if the result is not valid XML.
TL;DR
Use git merge-file
in your merge driver. Here, you can specify the options you want. You're asking for --ours
. Be aware that this does not always work right. You might even want --union
, but be aware that this does not really work right either. See Can git be made to mostly auto-merge XML order-insensitive files? and VS 2013 C# and Git: .csproj files don't merge upon pulling for issues with trying to use either method with XML files.
Long
First, let's define the terms the way Git uses them:
A strategy is the argument you pass to the
-s
flag togit merge
:git merge -s recursive
,git merge -s resolve
,git merge -s ours
, orgit merge -s octopus
. (In practice you would never use the last one explicitly—instead, it's implied if you ask Git to merge multiple heads.)
In other words, this is one of the names
ours
,recursive
, orresolve
. In theory, you can write your own Git merge strategy as well, but that's very non-trivial (so much so that I have not heard of anyone doing it, though this might not mean that much).
A strategy option the argument you pass to the
-X
flag, such as-X ours
or-X rename-threshold=<number>
. I find this to be a bad name, as it's too easy to confuse with the strategy. This is especially true when speaking: did you say "strategy" and then stop speaking but mean "strategy option" and you were just interrupted, or did you say "strategy" and stop speaking and actually mean "strategy"? I like to call these extended options, which goes well with the-X
flag, which then stands for "eXtended".)A driver is something you define in a
.gitattributes
file. There are several flavors (filter, diff, and merge); here we're concerned with merge drivers. Merge drivers are invoked by a strategy; the driver could, in theory, have access to at least some of the extended-options, but in practice they don't. (Git should define%
-expanders that provide them, and/or provide them in an environment variable; this would be relatively straightforward.)
With these definitions in mind, it's easy to see that this:
I'm struggling with forcing a specific merge strategy (
-s recursive -Xours
) for certain files within Git's.gitattributes
file.
is literally impossible. That's true whether you mean strategy or strategy option here: both have been chosen long before we get to the merge driver defined in the .gitattributes
file.
All is not necessarily lost, though!
You mention specifically pom.xml
, for which you want a merge driver—the thing you can define in .gitattributes
—that does a text style merge but takes "ours" if there is a conflict, a la the extended-option "ours". What you have, instead, is a merge driver that will do an "ours" strategy merge: ignore both the merge base and their changes entirely, just taking "our" version of the file.
Compare your attempt with the answer at Whats the Proper usage of .gitattributes with merge=ours: the only difference is the spelling of the driver name, where you use recursive
instead of ours
. The name of the driver is not important, as long as it's not one of the known magic names (text
, binary
, and union
): Git will run your driver instead of doing its own file-level merge, when it determines that there is a file-level merge to be performed.
The gitattributes documentation has this to say about defining a merge driver:
The definition of a merge driver is done in the
.git/config
file, not in thegitattributes
file, so strictly speaking this manual page is a wrong place to talk about it. However...
To define a custom merge driver
filfre
, add a section to your$GIT_DIR/config
file (or$HOME/.gitconfig
file) like this:
[merge "filfre"]
name = feel-free merge driver
driver = filfre %O %A %B %L %P
recursive = binary
(Side note: "recursive = binary" is not necessarily good advice, but let's press on here for now.)
The
merge.*.name
variable gives the driver a human-readable name.
The
merge.*.driver
variable’s value is used to construct a command to run to merge ancestor’s version (%O
), current version (%A
) and the other branches’ version (%B
). These three tokens are replaced with the names of temporary files that hold the contents of these versions when the command line is built. Additionally,%L
will be replaced with the conflict marker size (see below).
That is, %O
names a temporary file containing the merge-base version of the file, while %A
and %B
name temporary files containing the --ours
and --theirs
versions of the file. Your job, as filter-driver-writer, is to come up with a command that will perform the merge:
The merge driver is expected to leave the result of the merge in the file named with
%A
by overwriting it, and exit with zero status if it managed to merge them cleanly, or non-zero if there were conflicts.
Having performed the merge, you should overwrite the %A
-named file with the result. Since the %A
-named file contains the --ours
contents, a driver that does nothing, but then exits, declares that the correct contents for merging %O
vs %A
and %B
is whatever is already in %A
.
In other words, this is like the ours strategy. It is not like the merge-favoring-ours-in-case-of-conflicts, resolve-or-recursive-with--Xours
strategy-plus-option. But you don't want a whole strategy at all, as that deals with entire commits; you want a single file operation. This is where git merge-file
comes in. Let's finish off the rest of the description now, though:
The
merge.*.recursive
variable specifies what other merge driver to use when the merge driver is called for an internal merge between common ancestors, when there are more than one. When left unspecified, the driver itself is used for both internal merge and the final merge.
This is a bit confusing, but it applies only to the recursive strategy, and even then, only if git merge-base --all HEAD <theirs>
prints out multiple hash IDs. Most of the time, you don't need anything special here at all. Using recursive = binary
is equivalent to setting, for this one file, the strategy to resolve
, because the binary
internal merge driver just takes the ours
version, which means that when -s recursive
repeatedly merges merge bases, it just gets the first merge base version back each time.
Multiple merge bases are rare, and it's not always clear what to do with them, but omitting any setting will cause the internal recursive merges to use your merge driver, which is probably fine for most cases. Your driver won't be called recursively; the recursion happens outside the calls to your driver. If you view the recursion as a tree, your driver is, in effect, called as a post-order traversal.
The merge driver can learn the pathname in which the merged result will be stored via placeholder
%P
.
More concretely, your driver will be called with %A
expanding to, e.g., .mergetmpA12345
, %B
expanding to .mergetmpB12345
, and %O
expanding to .mergetmpO12345
. This is true regardless of whether you're being called to merge three copies of README.txt
, three copies of main.py
, or three copies of pom.xml
: the temporary file names will be .merge<something>
.
If you want, for whatever reason, to know that the "real name" of the file is README.txt
or main.py
or whatever, that is available in %P
. Don't use that file now—that's neither an input nor an output. That's just the work-tree file's eventual name—it is, or will be, its (eventual) name, once the merge is all done. For the moment, though, the name you should use—the one where you should deliver your merged file data—is whatever %A
expands to (some .merge<something>
).
Using git merge-file
The git merge-file
documentation tells us that the usage for git merge-file
is:
git merge-file [
-L <current-name>
[-L <base-name>
[-L <other-name>
]]]
[--ours
|--theirs
|--union
] [-p
|--stdout
] [-q
|--quiet
] [--marker-size=<n>
]
[--
[no-
]diff3
]<current-file> <base-file> <other-file>
The three -L
options are labels that git merge-file
will place around conflicted sections. The --marker-size
option sets the size of the marker that git merge-file
will place around conflicted sections. The defaults are pretty similar to Git's own internal merge, so a lot of the time, these are not needed. In your case, you're going to specify --ours
, which means there will never be unresolved conflicts and therefore -L
and --marker-size
are not needed.
The --ours
or --theirs
or --union
flag tells git merge-file
how to handle conflicts. Two of these flags are the same as the extended-options -X ours
and -X theirs
; you gain a third one that's not available in regular Git merge.
The --diff3
or --no-diff3
options again affect how conflicts are shown: they are equivalent to setting merge.style
to diff3
or to merge
respectively. Since you won't have conflicts, these are irrelevant.
Finally, you need to name three files: one containing the current ("ours") version data, one with the merge base version, and one with the other ("theirs") version. When git merge-file
finishes, it will rewrite the current-version file with the merged data. Your git merge
has, conveniently, built three such files, and uses them exactly this way, so you need only provide the %A %O %B
options:
*pom.xml merge=favor-ours
and:
[merge "favor-ours"]
name = merge driver that merges but favors ours a la -X ours
driver = git merge-file --ours %A %O %B
Because of the --ours
option, this merge will always succeed (git merge-file
will exit 0), even if the result is not valid XML.
answered Nov 23 '18 at 0:45
torektorek
193k18239321
193k18239321
add a comment |
add a comment |
Thanks for contributing an answer to Stack Overflow!
- Please be sure to answer the question. Provide details and share your research!
But avoid …
- Asking for help, clarification, or responding to other answers.
- Making statements based on opinion; back them up with references or personal experience.
To learn more, see our tips on writing great answers.
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
StackExchange.ready(
function () {
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53429202%2fgit-recursive-merge-strategy-options-within-gitattributes%23new-answer', 'question_page');
}
);
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function () {
StackExchange.helpers.onClickDraftSave('#login-link');
});
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown
Required, but never shown