7
CONFLICT (file location): path/to/tests/NS/Domain/Projects/Foo/Bar/stories/workspace-public-channel-SOME_UUID.json added in HEAD inside a directory that was renamed in <some hash> (Reorganize tests), suggesting it should perhaps be moved to path/to/tests/unit/NS/Projects/Foo/Bar/stories/workspace-public-channel-SOME_UUID.json.

I examined this message and decided that the suggestion is correct.

What is the exact incantation to tell git to go ahead and do it? I have anxiety that the generic suggestion to "git add/rm <conflicted_files>", then run "git rebase --continue" may be insufficient in this particular case, or could mess things up.

EDIT to clarify: here's what git status shows for an example file with this conflict type:

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)

...
...
        deleted:    old/path/to/some/file.spec.js
...
...

And later in the output of the same git status:

Unmerged paths:
  (use "git restore --staged <file>..." to unstage)
  (use "git add <file>..." to mark resolution)
...
...
        added by us:     new/path/to/some/file.spec.js
...
...

And here is the error message when I attempt to git mv old/path/to/some/file.spec.js new/path/to/some/file.spec.js:

fatal: bad source, source=old/path/to/some/file.spec.js, destination=new/path/to/some/file.spec.js

Szczepan Hołyszewski
  • 2,707
  • 2
  • 25
  • 39
  • have you tried `git mv` and then `git rebase --continue`? – ignoring_gravity Nov 06 '20 at 10:53
  • @ignoring_gravity Please see my edit. – Szczepan Hołyszewski Nov 06 '20 at 11:45
  • OK, I think I would do `git rm old/path/to/some/file.spec.js` then `git add new/path/to/some/file.spec.js` then `git rebase --continue` – ignoring_gravity Nov 06 '20 at 11:49
  • I would abort the rebase and fix the directory renaming conflict. You renamed OldDir to NewDir in one branch but kept OldDir and edited its contents in the other branch. Bad idea. – matt Nov 06 '20 at 11:51
  • It's not just "renaming", it's reorganizing the source tree. The topic branch did the reorganization. The master branch added files to the old location. This conflict cannot be wished away, and rebasing **is** the means to resolve conflicts; rather than resolving conflicts being a prerequisite to rebasing. (Btw I hate that git calls it "added by us", because it ABSOLUTELY WASN'T added by "us" i.e. me and my Trill symbiont sitting here at this machine working on the topic branch; it was totally added by "them" i.e. the guys at HQ working on the master branch). – Szczepan Hołyszewski Nov 06 '20 at 12:00
  • Us and them are "reversed" for a rebase. So you are them and they are us. – matt Nov 07 '20 at 16:15

1 Answers1

15

What is the exact incantation to tell git to go ahead and do it?

The form of the question suggests that there is only one command that is correct. That's not the case. Any set of Git commands that wind up with that file in Git's index under that pathname will suffice.

Based on what you have shown:

git add new/path/to/some/file.spec.js

should suffice. If this doesn't work, though, or goes wrong, read on.

Long

The thing to understand here, the deep (or, well, less-shallow? ) magic in Git, is that when a merge1 stops with merge conflicts, what Git has done is to leave the conflicts in Git's index. Your job is now to repair the mess in the index.

The index, which is also called the staging area (or sometimes the cache), contains a series of name-and-hash-ID pairs, which you can see with:

git ls-files --stage

The names are the names of the files that will, or would, go into your next commit. The hash IDs are Git's internal identifiers that record the corresponding file content.

Normally, each entry is at stage number zero, and the output from git ls-files --stage looks like this (I've snipped just a few pieces of output from the command run in a Git repository for Git):

100644 b08a1416d86012134f823fe51443f498f4911909 0       .gitattributes
100644 536e55524db72bd2acf175208aef4f3dfc148d42 0       COPYING
100644 80d1908a44ca38058c58dcc4e3444ee96060757b 0       Documentation/Makefile
100644 7074bbdd53cc1141c75546ad6e009b5ac74a63c2 0       wt-status.c
100644 35b44c388edf0cd75cca00e1458b9995c8001538 0       wt-status.h

The 100644s denote the file mode (not-executable), the hashes record the content, the zeros by themselves are the stage number, and the rest of the line is the pathname, possibly including embedded (forward) slashes, as in this case for the Documentation/Makefile file.

If one were to run git commit right now, the new commit would contain exactly the set of files listed here. However, git commit itself will balk—fail—if any of those stage numbers are nonzero.

When git merge runs, it expands the index, filling in a lot of nonzero stage numbers. It then performs the merge, and each successful merge resolves a file and moves it back to stage zero, collapsing away the nonzero stage entries. Each failed merge leaves the nonzero stage entries in place.

The git status command separates out the stage-zero paths (which are staged for commit) from the nonzero-stage ones. Anything in staging slot #1 is a merge base file, that came out of the shared common commit. Anything in staging slot #2 is from the HEAD commit, and anything in staging slot #3 is from the other commit.

The path names may be modified by the merge operation, by picking up a rename request, but the file content might be from your HEAD commit. There might in fact be no file from their commit, so that staging slot #3 is empty, and if the file wasn't in the merge base either, staging slot #1 will be empty as well. In this case, you'll see this file listed as added by us. That's true even if the path name got changed somehow.

The git add command tells Git: make the index entry for this pathname match the work-tree file of the same pathname. This has the side effect of removing any stage 1, 2, and 3 entries for that path name, resolving conflicts. It adds the file at stage zero, or if the file is missing from your work-tree, just removes all the index entries for the file. Either way, there are no longer any nonzero stage number entries.

Most Git merge cases are pretty simple, but any time there are massive renames, or a directory gets renamed, things get messy. The path name of some file was old/path/to/file in the merge base. It may be old/path/to/file in your own commit—the one you're copying, via git rebase, to a new-and-improved commit—or it might be third/name/for/file, and the path you want is new/nameoffile or whatever. Whether and when Git puts the right path in some index slot is a bit iffy. There has recently been a bunch of work to improve this, and based on the messages you quoted, I think you have a newer Git.

All ("all"?) you really have to do here, though, is arrange for Git's index to hold the complete right set of file names and content hashes. Whatever is in Git's index, at stage zero, is what will go into the new commit when you run git rebase --continue. All of this stuff about conflicts, added by us, added by them, and so on is just a way to describe what's in nonzero staging slot numbers. Everything that shows up under changes to be committed is in staging slot zero, and is showing up there because it's different from what is in the commit that is currently HEAD.

See also footnote 1, for which I'll call this answer "done" now and proceed to write it ("it" being the footnote).


1You're running git rebase, not git merge. But rebase uses git cherry-pick, and git cherry-pick uses Git's merge engine. The trick here is that when rebase does each git cherry-pick, you're on a new branch that Git is building in place, that will be renamed to be your branch. This new branch starts out with the same last commit as their branch, the one you're rebasing onto. So when you see "added by us", it means "added by someone(s), somewhere along the way, starting from what they started with" and that might be them rather than us. It's extra-messy because by the time you're ten or twenty commits deep in the rebase, the "ours" or HEAD commit that Git is working with is a ten or twenty commit deep mix of your copied commits and their originals.

The gist of all this is that you should pay less attention to the annoying apparent reversal of "us" and "them" here sometimes, and just concentrate on resolving conflicts by putting together the final snapshots you want for each commit. I find setting merge.conflictStyle to diff3 helps, too.

torek
  • 448,244
  • 59
  • 642
  • 775
  • Thank you for the elaborate answer. Before I read on, I must stop at the point where you start mentioning "merge". The question is about a rebase, not about a merge. Are we at the abstraction level where these terms can be used interchangeably? – Szczepan Hołyszewski Nov 07 '20 at 09:04
  • See footnote 1. (Maybe I should move the reference to footnote 1 earlier, or start with it?) – torek Nov 07 '20 at 16:07
  • 1
    Thanks for the detailed explanation of how "us" and "they" work in this particular case out of dozens of particular cases, each requiring a completely different explanation of similar length and complexity. Let's admit it: "us/ours" and "they/theirs" terminology in git is a total mess! – Szczepan Hołyszewski Nov 08 '20 at 13:29
  • Us/them (ours/theirs) is indeed not good, but other VCSes like Mercurial and some Git tools have tried other names and they're equally (or almost?) as bad: "local" and "remote" for instance. Just calling them A and B, or numbering them—as Git does internally, 1=base 2=HEAD 3=other-commit—might be best. – torek Nov 08 '20 at 16:29