0

Abstract Scenario: There is master and a branch. branch is a descendent is master. Along with other files not shown here, both contain a subdirectory called subdir that contains a selection of json files:

branch:
/subdir/1.json
/subdir/2.json

master:
/subdir/2.json
/subdir/3.json

What I would like to do is merge just the subdir from master to branch, where all the files in branch's subdir are removed and replaced with the contents of master without losing the history of the commits in branch and without affecting any other files in branch.

So after the merge, branch's subdir and master's subdir look exactly the same.

paiego
  • 3,619
  • 34
  • 43

1 Answers1

2

There is no merge argument that makes it do that.

There is a way to do that within a single merge, but this might be a bad idea. This kind of merge is called an evil merge, at least by some (see Evil merges in git? for various takes on what exactly an "evil merge" is). A way to do it that's "not evil" is to do the merge, commit the result, and then make a followup commit that puts things right. Another way to do it that's "not evil" is to make a commit that causes the merge to be correct, then do the merge. Either way you have two commits, one of which is the non-evil ordinary everyday merge.

But if you do want to do it as a single merge, devil-may-care as to whether it's evil or not, here is how you do that:

$ git checkout branch
$ git merge --no-commit -s ours master
... Git does the merge, but stops before committing ...

$ git rm -r -- subdir           # needed only if there are files to remove

$ git checkout master -- subdir
$ git status                    # use git status often!
... you'll see some status ...
$ git diff --cached HEAD        # optional: see what's changing vs tip of branch "branch"
... you'll see some status ...
$ git diff --cached --name-status HEAD  # optional: see what files differ
... you'll see some status ...
$ git status                    # it's never wrong to use git status too often
... you'll see some status ...
... ok, we're really ready ...
$ git commit
<and write a good merge message>

Note that having made this merge, Git now believes that the correct result of combining the two commits you just combined, is whatever you just committed. This affects future merge operations!

Note the git rm -r step (I forgot this at first): you need this if there are files in the current (tip-of-branch) commit that aren't in the master tip commit, that should be removed in the merge. If there are no such files, git rm -r is not harmful, but does nothing useful: we're just going to replace all the files with the subsequent git checkout master -- subdir step.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks, I've given the single merge a try and so far it's working. – paiego Apr 18 '19 at 05:00
  • One thing I don't fully understand is the call to "git merge --no-commit -s ours master". This will merge master to branch but I'm concerned that it might write files to branch from master that aren't wanted. And if it actually doesn't do that, then what is it actually doing, i.e. why is it needed? – paiego Apr 18 '19 at 19:55
  • 1
    The point of `git merge --no-commit -s ours master` is to set things up so that the *next* commit made will be a merge commit. The `-s ours` makes Git take no actual merging action. We're about to *completely replace* some parts of the index and work-tree, through the `git checkout master -- subdir` step, so it's OK if Git does nothing but mark the *next* commit to be made as "will be a merge". The real key to understanding all of this is that Git makes new commits from what's in the *index*. Your work-tree exists only so that you can do work. What Git cares about is the index. – torek Apr 18 '19 at 20:34