Follow-up to r29036: Now that the "mergeinfo" transaction file is no
[svn.git] / notes / propmerging_sanity.txt
bloba73770b163fa67826457b7cefbbc64e983e19a04
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
6 what's going on.
9 'svn update'
10 -------------
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:
24                      old-baseprops
25                     /             \
26  (server-propsets) /               \ (local-edit-propsets)
27                   /                 \
28                  v                   v
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
34 lists of 'propsets':
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.
52           else:
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.
60 'svn merge'
61 -----------
63 This is a harder problem, because we're not guaranteed a common
64 ancestor.
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):
71                 old-baseprops
72                     |
73                     |  (server-propsets)
74                     |
75                     v
76                 new-baseprops
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
83           |                                 |         
84           |  (server-propsets)              |  (localedit-propsets)
85           |                                 |                      
86           v                                 v
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'
117                else:
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"