| |
| When merging property lists together during 'svn up' and 'svn merge', |
| things can get tricky. In the case of file texts, we have a nice |
| diff3 algorithm to do a 3-way merge for us. But for properties, |
| libsvn_wc needs to do the 3-way merge itself. This document explains |
| what's going on. |
| |
| |
| 'svn update' |
| ------------- |
| |
| The old-baseprops live in .svn/. |
| |
| During update, the server sends a bunch of 'propset' commands of the |
| form {name, val}. (val == NULL implies a deletion. If name doesn't |
| already exist, it implies an addition.) |
| |
| The propsets are applied to old-baseprops, producing new-baseprops. |
| |
| Meanwhile, the user may have made local edits to the workingprops. |
| |
| So our ancestry diagram looks like: |
| |
| old-baseprops |
| / \ |
| (server-propsets) / \ (local-edit-propsets) |
| / \ |
| v v |
| new-baseprops workingprops |
| |
| |
| Because the old-baseprops are a common ancestor, the 3-way merge |
| algorithm is relatively simple. All we need to do is compare two |
| lists of 'propsets': |
| |
| 1. the propset list sent from the server (which transforms |
| old-baseprops into new-baseprops) |
| |
| 2. the propset list representing local edits (which transforms |
| old-baseprops into workingprops) |
| |
| If there are no local edits at all (i.e. the 2nd list is empty), then |
| we simply apply the first list to workingprops, and we're done. No |
| conflicts can possibly happen; the common ancestor here guarantees |
| that workingprops and new-baseprops will be identical when we're done. |
| |
| If there are local edits (i.e. the 2nd list is non-empty), then |
| |
| foreach propset in server-propsets: |
| if propname exists in localedit-propsets: |
| compare intentions of the 2 propsets, possibly mark conflict. |
| else: |
| apply propset to workingprops. |
| |
| Note that because of the common ancestor here, the *only way* property |
| conflicts can happen is if local-mods are present. |
| |
| |
| |
| 'svn merge' |
| ----------- |
| |
| This is a harder problem, because we're not guaranteed a common |
| ancestor. |
| |
| During the merge command, the server sends the complete list of |
| old-baseprops from the 'left' side of the merge, as well as a list of |
| propsets which can be applied to get the new-baseprops (which |
| represent the 'right' side of the merge): |
| |
| old-baseprops |
| | |
| | (server-propsets) |
| | |
| v |
| new-baseprops |
| |
| But the target of the merge could be *anything*. It has a completely |
| unrelated set of baseprops, no common ancestor. |
| |
| |
| old-baseprops target-baseprops |
| | | |
| | (server-propsets) | (localedit-propsets) |
| | | |
| v v |
| new-baseprops workingprops |
| |
| |
| So for a correct 3-way merge, our algorithm needs to be different. We |
| can't blindly apply the server propsets to the workingprops, we need |
| to carefully look at the server-propsets as *deltas*, noticing the |
| "from" and "to" values. |
| |
| The upshot here is that while 'svn update' can *only* produce |
| conflicts when local-mods are present, 'svn merge' can produce |
| conflicts anytime, even in the absence of local-mods. |
| |
| After some discussion on the dev@ list, we've decided to implement the |
| following algorithm. It works correctly for 'svn merge', because it |
| makes no assumption about a common ancestor. The FROM variable |
| represents the old-baseprops coming from the server. The algorithm |
| still works for 'svn up', of course; in that case, the FROM variable |
| simply represents the value of target-baseprops. |
| |
| |
| foreach propset in server-propsets: |
| |
| /* we have old-baseprops, so we can do this */ |
| convert into propdelta of the form (PROPNAME, FROM, TO). |
| |
| if FROM == NULL: /* adding a new property */ |
| |
| if PROPNAME exists in workingprops: |
| if (value of PROPNAME in workingprops) == TO: |
| do nothing, it's a 'clean merge' |
| else: |
| conflict: "property already exists with different value" |
| |
| else: /* PROPNAME doesn't exist in workingprops */ |
| set property as a new local mod |
| |
| |
| else: /* FROM != NULL -- changing or deleting a property */ |
| |
| if PROPNAME doesn't exist in workingprops: |
| print "skipped: cannot change/delete non-existent property" |
| |
| else: /* PROPNAME exists in workingprops */ |
| |
| if (value of PROPNAME in workingprops) == FROM: |
| /* note: this may destroy existing local mods, |
| because target-baseprops are being ignored. */ |
| apply the propchange. |
| |
| else if (value of PROPNAME in workingprops) == TO: |
| do nothing, it's a 'clean merge' |
| |
| else: /* has some other value */ |
| conflict: "property has conflicting value" |
| |
| |
| |