The sound distributed version control system

#508 unhelpful conflict markers

Opened by zseri on August 5, 2021 Beta
zseri on August 5, 2021

I recently ran into a few conflicts, but currently the conflict markers are suboptimal, because it is almost impossible to know how to fix the conflict without looking at the original states without conflicts… e.g. (while trying to fix conflicts in discussion 488)

        let RemoteDelta {
            inodes,
            remote_ref,
            mut to_download,
            remote_unrecs,
            ..
        } = self
            .to_download(&mut *txn.write(), &mut channel, &mut repo, &mut remote)
            .await?;
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                
================================
>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
                inodes.extend(inodes_.iter());
                            && (inodes_.is_empty()
                                }))
                        {
                            to_download.push(h.into())
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
        let hash = super::pending(txn.clone(), &mut channel, &mut repo)?;
            
        if let Some(ref r) = remote_ref {
            remote.update_identities(&mut repo, r).await?;
        }
        
        notify_remote_unrecords(&repo, remote_unrecs.as_slice());

The lines inside the conflicts markers clearly come from ~somewhere~, but are as-is basically almost “garbage” to work with.

pmeunier on August 5, 2021

Since Pijul has very detailed information about conflicts, we could theoretically output almost anything, including conflict markers compatible with the language’s syntax (here, the choice of < and > can slow down my text editor quite a bit, for example).

Any idea for a way to make these customisable? My current plan is to have a section of the global config file with a syntax like:

[conflicts.rs]
begin_conflict_marker = "/**** BEGIN CONFLICT: %s %a"
conflict_separator = "**************** %s %a"
end_conflict_marker = "**** END CONFLICT */"

Where %s could mean something like “short identifier of the patch for the conflict’s first side” and %a is the author of that patch.

What do you think?

pmeunier on August 5, 2021

Zombie conflicts (like the one you had) are slightly trickier, since there could be context missing around them, and these markers are less useful.

pmeunier added tag Beta on August 5, 2021
zseri on August 5, 2021

I would like to differentiate between that. It might be a good idea to make them customisable, but that is not my problem… My problem is, that I

  • don’t know which changes conflict (git would usually print branch names here, but change ids would suffice in pijul)
  • what the original snippet looks like (git would display similiar conflict sections, but the problem here is that in pijul it looks like multiple conflicts sometimes overlay or significant, but unchanged lines in the middle of conflicts are suppressed, which makes these sections unreadable, because context is missing)
  • The result is that, e.g. for trivial or even some more complicated conflicts in git, where one change is made redundant, you could just delete half of the conflict section, and all the conflict markers, and it would compile again. This often doesn’t seem to apply with pijul, because necessary lines likes } else { or such are just missing or misplaced.
  • At other times, it just feels like you’re ripping out a part, and just don’t know if it is required, because you don’t know where it came from.
zseri on August 5, 2021

conflict resolution for the conflict above looks also a bit weird: #TFTQAJHCELIHDUOKHP6VN3I77GKFCXYA5IVSSCKZDOLKLILMR5UQC

spacefrogg on August 12, 2021

In my understanding, the conflict representation in a file has some important properties:

  1. It is a linear presentation of a non-linear problem.
  2. More than two changes can conflict at the same piece of text.
  3. It serves the purpose of presenting to the user what each contributing change would make the relevant section look like on its own.

Regarding 1 and 2: This linearisation limits reuse of partially common context. So, context may be repeated to make the effect of each contribution visible in the surrounding context. To emphasize, conflicts must never be represented as inside one another. Regarding 3: It is important to find a stable representation that captures (and outputs!) enough context in each piece, such that the piece and the surrounding common code can stand on their own. To clarify, it should be possible to arrive at the content a single change would have created at a conflict by just removing the pieces of the other conflicting changes (and the conflict markers).

Overall, the conflict representation must serve one particular purpose: The user must be able to judge, what each change contributes to the conflicting section. So, she can decide, which parts to delete, or merge, or rewrite, to make the code work again. It is not the purpose of conflict representation to be a faithful representation of the underlying conflict graph but of the influence the conflict has on the content of the file.

Example:

aaaaa
bbbbb
 >>>>>>> [1:...]
 ccccc
 ====== [2:...]
 ccccc
 ====== [3:...]
 <<<<<<
 ddddd

So this shows that [1] and [2] are both contributing ccccc, so dropping either or both (i.e., opting for [3]), or merging both may be the correct resolve.

Regarding zombie conflicts: I haven’t grasped their influence on the content of a file, yet. What does a zombie mean? What lines of text does it add or remove? Because of 1. and 3., representation of a zombie conflict should be changed so that the user can see what the zombie would make the file look like on its own.