PetSerAl's comment has the (or "a", at least) command you can use to achieve what you want. The only thing missing is an explanation as to why:
git reset --soft $(git log --format=%B -n 1 |
git commit-tree HEAD^{tree} -p HEAD^ -p other_branch)
works. I'll get to that in a bit, but first...
A literal answer
The literal answer to your question as asked, though, is "no". Since subject lines can be edited away, let me quote it:
Is there a way to merge with Strategy “ours” without producing a new commit?
The word merge, in Git, can refer to two things: the act of merging, or the result of a previous act-of-merging. The first of these is a verb, to merge; the second is an adjective, a merge commit, or even a noun, a merge. A merge commit is a commit with two parents (or more, but we only really care about two).
Running git merge --squash
tells Git to perform the merge—i.e., do the verb part—but at the end, when making a new commit, make an ordinary, non-merge commit, i.e., suppress the merge-as-adjective effect.1 The point of this is that when you have a series of commits, e.g., a "branch" in the sense that's not just "a name pointing to a specific commit" (see What exactly do we mean by "branch"?)
Running git merge -s ours
tells Git to perform a faked-up act of merging—that is, prentend to do merge-as-a-verb, but not really do anything—resulting in a merge-as-an-adjective/noun commit. Since the verb form is gone, the only thing remaining is the after-effect. This is why using --squash
is useless here: you propose to eliminate both the verb and noun, and there's nothing left.
1For no particularly good reason, git merge --squash
has the side effect of setting the --no-commit
argument as well, so that you have to manually run git commit
at the end. Note that a real merge (that makes a merge commit) can also be run with --no-commit
, making you run git commit
manually as well; or it can stop due to conflicts, which you must resolve, making you finish the commit manually. The latest versions of Git have added git merge --continue
to make this feel less awkward, but it just runs git commit
.
Git has --amend
but it doesn't quite get us there
The diagram shows what you want—which, if Git supported it, might be spelled git commit --amend --add-parent
, for instance—but doesn't produce the key insight, and in fact git commit --amend
is a small, or perhaps huge, lie, as it doesn't change a commit, it just makes a new commit that uses an unusual parent hash.
The normal git commit
process executes a bunch of steps as if they were done all at once, so that either they all finish correctly, or nothing happens (or seems to, at least). The steps are:
- Obtain the current commit's hash ID (
git rev-parse HEAD
). Call this P: the existing HEAD
will be the new commit's parent. (If you're concluding a merge, MERGE_HEAD
will also exist and git commit
reads it to get additional parents. That's just for finishing merges, though.)
- Write the current index to make a
tree
object (git write-tree
). Call this T, the tree hash ID. (This may or may not be the same tree as some previous commit.)
- Obtain your name and email address and the current time as committer. Normally, use these as author too (you can override them).
- Obtain a commit message.
- Write out a new commit with all of this information (
tree
T, parent
P, author
and committer
as obtained in step 3, and commit message obtained in step 4. The result is a new commit hash C.
- Write the new hash C into the current branch name, so that
git rev-parse HEAD
now produces C.
Using git commit --amend
changes the procedure right at step 1: instead of getting HEAD
as the parent commit, Git reads the current commit's parent hashes (there may be more than one, if you're --amend
-ing a merge), and uses those in step 5.
The effect is to shove the current commit aside:
...--o--o--* <-- master (HEAD)
becomes:
* [the commit that was HEAD before]
/
...--o--o--@ <-- master (HEAD)
What you want to get Git to do is a bit different.
Why (and how) the shell command works
Git's commit-tree
command produces new commit objects. It's like step 5 of the six-step commit sequence above. But it hasn't made a tree, and it does not have precomputed parent commit hashes ready to go, so it takes those as command line arguments:
git commit-tree tree-hash -p parent-hash-1 -p parent-hash-2
in this case. The tree-hash
we want is, like git merge -s ours
, the same tree that the current commit has. We can name that tree using HEAD^{tree}
, which is described in the gitrevisions documentation. The two parent hashes we want start with the parent of the current commit. (We can assume there's only one such parent.) Again, gitrevisions syntax gives us a way to write this: we can use parent^1
, or parent~1
, or leave out the 1
from either of those expressions. The other parent hash we want is the commit to which other_branch
points, so we can just name that. That gives us:
git commit-tree HEAD^{tree} -p HEAD^ -p other_branch
This command reads the commit message from its standard input. If we want to retain the commit message from the current commit, we can extract it with git log
: --format=%B
tells git log
to show each commit by printing its subject-and-body as text, and -n 1
tells git log
to show only one commit. The first commit that git log
shows is, by default, the HEAD
commit. So this gives us the:
git log --format=%B -n 1 |
part—we pipe this git log
's standard output to git commit-tree
's standard input.
What git commit-tree
does after making the commit is to print its hash ID to its own standard output. Hence if we just ran this pipeline by itself, we'd see the new commit hash printed, but we would not store it anywhere. What we need to do is change the current branch name—whatever that is—to point to the new commit; and git reset --soft commit-hash
will do that, hence:
git reset --soft $(...)
The $(...)
construct is the last bit: the shell takes this to mean run the given command, capturing its standard output, then treat that standard output text as command arguments to git reset --soft
. Since there's only one output-word—the new commit's hash—this runs git reset --soft
on the new commit ID.