2 When merging property lists together during 'svn up' and 'svn merge',
3 things can get tricky. In the case of file texts, we have a nice
4 diff3 algorithm to do a 3-way merge for us. But for properties,
5 libsvn_wc needs to do the 3-way merge itself. This document explains
12 The old-baseprops live in .svn/.
14 During update, the server sends a bunch of 'propset' commands of the
15 form {name, val}. (val == NULL implies a deletion. If name doesn't
16 already exist, it implies an addition.)
18 The propsets are applied to old-baseprops, producing new-baseprops.
20 Meanwhile, the user may have made local edits to the workingprops.
22 So our ancestry diagram looks like:
26 (server-propsets) / \ (local-edit-propsets)
29 new-baseprops workingprops
32 Because the old-baseprops are a common ancestor, the 3-way merge
33 algorithm is relatively simple. All we need to do is compare two
36 1. the propset list sent from the server (which transforms
37 old-baseprops into new-baseprops)
39 2. the propset list representing local edits (which transforms
40 old-baseprops into workingprops)
42 If there are no local edits at all (i.e. the 2nd list is empty), then
43 we simply apply the first list to workingprops, and we're done. No
44 conflicts can possibly happen; the common ancestor here guarantees
45 that workingprops and new-baseprops will be identical when we're done.
47 If there are local edits (i.e. the 2nd list is non-empty), then
49 foreach propset in server-propsets:
50 if propname exists in localedit-propsets:
51 compare intentions of the 2 propsets, possibly mark conflict.
53 apply propset to workingprops.
55 Note that because of the common ancestor here, the *only way* property
56 conflicts can happen is if local-mods are present.
63 This is a harder problem, because we're not guaranteed a common
66 During the merge command, the server sends the complete list of
67 old-baseprops from the 'left' side of the merge, as well as a list of
68 propsets which can be applied to get the new-baseprops (which
69 represent the 'right' side of the merge):
78 But the target of the merge could be *anything*. It has a completely
79 unrelated set of baseprops, no common ancestor.
82 old-baseprops target-baseprops
84 | (server-propsets) | (localedit-propsets)
87 new-baseprops workingprops
90 So for a correct 3-way merge, our algorithm needs to be different. We
91 can't blindly apply the server propsets to the workingprops, we need
92 to carefully look at the server-propsets as *deltas*, noticing the
93 "from" and "to" values.
95 The upshot here is that while 'svn update' can *only* produce
96 conflicts when local-mods are present, 'svn merge' can produce
97 conflicts anytime, even in the absence of local-mods.
99 After some discussion on the dev@ list, we've decided to implement the
100 following algorithm. It works correctly for 'svn merge', because it
101 makes no assumption about a common ancestor. The FROM variable
102 represents the old-baseprops coming from the server. The algorithm
103 still works for 'svn up', of course; in that case, the FROM variable
104 simply represents the value of target-baseprops.
107 foreach propset in server-propsets:
109 /* we have old-baseprops, so we can do this */
110 convert into propdelta of the form (PROPNAME, FROM, TO).
112 if FROM == NULL: /* adding a new property */
114 if PROPNAME exists in workingprops:
115 if (value of PROPNAME in workingprops) == TO:
116 do nothing, it's a 'clean merge'
118 conflict: "property already exists with different value"
120 else: /* PROPNAME doesn't exist in workingprops */
121 set property as a new local mod
124 else: /* FROM != NULL -- changing or deleting a property */
126 if PROPNAME doesn't exist in workingprops:
127 print "skipped: cannot change/delete non-existent property"
129 else: /* PROPNAME exists in workingprops */
131 if (value of PROPNAME in workingprops) == FROM:
132 /* note: this may destroy existing local mods,
133 because target-baseprops are being ignored. */
134 apply the propchange.
136 else if (value of PROPNAME in workingprops) == TO:
137 do nothing, it's a 'clean merge'
139 else: /* has some other value */
140 conflict: "property has conflicting value"