RE: Rename conflicts in the index
From: Edward Thomson <hidden>
Date: 2016-06-15 22:56:31
Junio C Hamano [mailto:gitster@pobox.com] wrote:
Edward Thomson [off-list ref] writes:quoted
I would propose that we store the data about the file in conflict as it occurred through the renames. For example, in a rename 1->2 conflict where A was renamed to both B and C, you would have a single conflict entry containing the data for A, B and C. This would allow us to provide more detailed information to the user - and allow them to (say) choose a single name to proceed with. Is this something that has value to core git as well? Alternately, is there something particularly stupid about this proposal?I do not offhand see anything particularly stupid; a new optional index extension section CACHE_EXT_RENAME_CONFLICT might be a good addition. Is "one side moves A to B while the other side moves it to C" the only case, or is it just an example? Off the top of my head, "one side moves A to x while the other side moves B to x/y" would also be something we would want to know. I am sure there are other cases that need to be considered. I do not think we can discuss the design at the concrete level until the proposal spells out to cover all interesting cases in order for implementations to agree on the common semantics.
Sorry about the delay here: besides getting busy with some other things,
I wanted both a complete writeup and to have taken a pass at a test
implementation this in libgit2 to make sure seemed like a reasonably sensible
approach.
I would propose a new extension, 'CONF', to handle conflict data, differing
from the stage >0 entries in the index in that this extension tracks the
conflicting file across names if the underlying merge engine has support
for renames.
I made an attempt to keep the entry data similar to other entries in the
index. I would propose that entries in the conflict are as follows:
Flags
Four octets that describe the conflict. Data includes:
0x01 HAS_ANCESTOR
There is a file in the common ancestor branch that contributes
to this conflict. Its data will follow.
0x02 HAS_OURS
There is a file in "our" branch that contributes to this conflict.
Its data will follow.
0x04 HAS_THEIRS
There is a file in "their" branch that contributes to this conflict.
Its data will follow.
0x08 NAME_CONFLICT_OURS
This item has a path in "our" branch that overlaps a different
item in "their" branch. (Eg, this conflict represents the "our"
side of a rename/add conflict.)
0x10 NAME_CONFLICT_THEIRS
This item has a path in "their" branch that overlaps a different
item in "our" branch. (Eg, this conflict represents the "theirs"
side of a rename/add conflict.)
0x20 DF_CONFLICT_FILE
This is the file involved in a directory/file conflict.
0x40 DF_CONFLICT_CHILD
This is a child of a directory involved in a directory/file conflict.
Other bits are reserved.
Conflict Sides
The data about one side of a conflict will contain:
mode (ASCII string representation of octal, null-terminated)
path (null terminated)
sha1 (raw bytes)
The conflict sides will be written in this order:
Ancestor (if HAS_ANCESTOR is set)
Ours (if HAS_OURS is set)
Theirs (if HAS_THEIRS is set)
I would propose that this not simply track rename conflicts, but all
conflicts. Having a single canonical location is preferable - if the index
contains a CONF section (and the client supports it), it would use that.
Otherwise, the client would look at stage >0 entries.
I would propose that another extension, 'RSVD', track these conflicts once
they are resolved. The format would be the same - when a conflict is
resolved from the CONF the entry will be placed as-is in the RSVD.
Examples are not an exhaustive list, but should help elucidate the name
and d/f conflicts:
Normal edit / edit conflict, where A is edited in ours and theirs:
Conflict one:
Flags = HAS_ANCESTOR|HAS_OURS|HAS_THEIRS
Entry 1 = A [Ancestor]
Entry 2 = B [Ancestor]
Entry 3 = C [Ancestor]
Rename / add conflict, where A is renamed to B in ours and B is added in
theirs:
Conflict one:
Flags = HAS_ANCESTOR|HAS_OURS|NAME_CONFLICT_OURS
Entry 1 = A [Ancestor]
Entry 2 = B [Ours]
Entry 3 = A [Theirs]
Conflict two:
Flags = HAS_THEIRS|NAME_CONFLICT_THEIRS
Entry 1 = File B [Theirs]
D/F conflict, where some file A is deleted in theirs, and a directory
A is created with file child:
Conflict one:
Flags = HAS_ANCESTOR|HAS_OURS|HAS_THEIRS|DF_CONFLICT_FILE
Entry 1 = A [Ancestor]
Entry 2 = A [Ours]
Conflict two:
Flags = HAS_THEIRS|DF_CONFLICT_CHILD
Entry 1 = A/child [Theirs]
Thanks for your input on this.
-ed