Mercurial > public > mercurial-scm > hg
comparison mercurial/merge.py @ 6274:f3f383efbeae
copies: move findcopies code to its own module
- pass in contexts
- fold symmetricdifference check into copies.copies
author | Matt Mackall <mpm@selenic.com> |
---|---|
date | Sat, 15 Mar 2008 10:02:31 -0500 |
parents | 20aa460a52b6 |
children | fda369b5779c |
comparison
equal
deleted
inserted
replaced
6273:20aa460a52b6 | 6274:f3f383efbeae |
---|---|
5 # This software may be used and distributed according to the terms | 5 # This software may be used and distributed according to the terms |
6 # of the GNU General Public License, incorporated herein by reference. | 6 # of the GNU General Public License, incorporated herein by reference. |
7 | 7 |
8 from node import nullid, nullrev | 8 from node import nullid, nullrev |
9 from i18n import _ | 9 from i18n import _ |
10 import errno, util, os, heapq, filemerge, ancestor | 10 import errno, util, os, filemerge, copies |
11 | 11 |
12 def _checkunknown(wctx, mctx): | 12 def _checkunknown(wctx, mctx): |
13 "check for collisions between unknown files and files in mctx" | 13 "check for collisions between unknown files and files in mctx" |
14 for f in wctx.unknown(): | 14 for f in wctx.unknown(): |
15 if f in mctx and mctx[f].cmp(wctx[f].data()): | 15 if f in mctx and mctx[f].cmp(wctx[f].data()): |
52 if f not in mctx: | 52 if f not in mctx: |
53 action.append((f, "f")) | 53 action.append((f, "f")) |
54 | 54 |
55 return action | 55 return action |
56 | 56 |
57 def _nonoverlap(d1, d2, d3): | |
58 "Return list of elements in d1 not in d2 or d3" | |
59 l = [d for d in d1 if d not in d3 and d not in d2] | |
60 l.sort() | |
61 return l | |
62 | |
63 def _dirname(f): | |
64 s = f.rfind("/") | |
65 if s == -1: | |
66 return "" | |
67 return f[:s] | |
68 | |
69 def _dirs(files): | |
70 d = {} | |
71 for f in files: | |
72 f = _dirname(f) | |
73 while f not in d: | |
74 d[f] = True | |
75 f = _dirname(f) | |
76 return d | |
77 | |
78 def _findoldnames(fctx, limit): | |
79 "find files that path was copied from, back to linkrev limit" | |
80 old = {} | |
81 seen = {} | |
82 orig = fctx.path() | |
83 visit = [fctx] | |
84 while visit: | |
85 fc = visit.pop() | |
86 s = str(fc) | |
87 if s in seen: | |
88 continue | |
89 seen[s] = 1 | |
90 if fc.path() != orig and fc.path() not in old: | |
91 old[fc.path()] = 1 | |
92 if fc.rev() < limit: | |
93 continue | |
94 visit += fc.parents() | |
95 | |
96 old = old.keys() | |
97 old.sort() | |
98 return old | |
99 | |
100 def findcopies(repo, m1, m2, ma, limit): | |
101 """ | |
102 Find moves and copies between m1 and m2 back to limit linkrev | |
103 """ | |
104 | |
105 wctx = repo.workingctx() | |
106 | |
107 def makectx(f, n): | |
108 if len(n) == 20: | |
109 return repo.filectx(f, fileid=n) | |
110 return wctx.filectx(f) | |
111 ctx = util.cachefunc(makectx) | |
112 | |
113 copy = {} | |
114 fullcopy = {} | |
115 diverge = {} | |
116 | |
117 def checkcopies(f, m1, m2): | |
118 '''check possible copies of f from m1 to m2''' | |
119 c1 = ctx(f, m1[f]) | |
120 for of in _findoldnames(c1, limit): | |
121 fullcopy[f] = of # remember for dir rename detection | |
122 if of in m2: # original file not in other manifest? | |
123 # if the original file is unchanged on the other branch, | |
124 # no merge needed | |
125 if m2[of] != ma.get(of): | |
126 c2 = ctx(of, m2[of]) | |
127 ca = c1.ancestor(c2) | |
128 # related and named changed on only one side? | |
129 if ca and ca.path() == f or ca.path() == c2.path(): | |
130 if c1 != ca or c2 != ca: # merge needed? | |
131 copy[f] = of | |
132 elif of in ma: | |
133 diverge.setdefault(of, []).append(f) | |
134 | |
135 if not repo.ui.configbool("merge", "followcopies", True): | |
136 return {}, {} | |
137 | |
138 # avoid silly behavior for update from empty dir | |
139 if not m1 or not m2 or not ma: | |
140 return {}, {} | |
141 | |
142 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit) | |
143 | |
144 u1 = _nonoverlap(m1, m2, ma) | |
145 u2 = _nonoverlap(m2, m1, ma) | |
146 | |
147 if u1: | |
148 repo.ui.debug(_(" unmatched files in local:\n %s\n") | |
149 % "\n ".join(u1)) | |
150 if u2: | |
151 repo.ui.debug(_(" unmatched files in other:\n %s\n") | |
152 % "\n ".join(u2)) | |
153 | |
154 for f in u1: | |
155 checkcopies(f, m1, m2) | |
156 | |
157 for f in u2: | |
158 checkcopies(f, m2, m1) | |
159 | |
160 diverge2 = {} | |
161 for of, fl in diverge.items(): | |
162 if len(fl) == 1: | |
163 del diverge[of] # not actually divergent | |
164 else: | |
165 diverge2.update(dict.fromkeys(fl)) # reverse map for below | |
166 | |
167 if fullcopy: | |
168 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n")) | |
169 for f in fullcopy: | |
170 note = "" | |
171 if f in copy: note += "*" | |
172 if f in diverge2: note += "!" | |
173 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note)) | |
174 | |
175 del diverge2 | |
176 | |
177 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True): | |
178 return copy, diverge | |
179 | |
180 repo.ui.debug(_(" checking for directory renames\n")) | |
181 | |
182 # generate a directory move map | |
183 d1, d2 = _dirs(m1), _dirs(m2) | |
184 invalid = {} | |
185 dirmove = {} | |
186 | |
187 # examine each file copy for a potential directory move, which is | |
188 # when all the files in a directory are moved to a new directory | |
189 for dst, src in fullcopy.items(): | |
190 dsrc, ddst = _dirname(src), _dirname(dst) | |
191 if dsrc in invalid: | |
192 # already seen to be uninteresting | |
193 continue | |
194 elif dsrc in d1 and ddst in d1: | |
195 # directory wasn't entirely moved locally | |
196 invalid[dsrc] = True | |
197 elif dsrc in d2 and ddst in d2: | |
198 # directory wasn't entirely moved remotely | |
199 invalid[dsrc] = True | |
200 elif dsrc in dirmove and dirmove[dsrc] != ddst: | |
201 # files from the same directory moved to two different places | |
202 invalid[dsrc] = True | |
203 else: | |
204 # looks good so far | |
205 dirmove[dsrc + "/"] = ddst + "/" | |
206 | |
207 for i in invalid: | |
208 if i in dirmove: | |
209 del dirmove[i] | |
210 | |
211 del d1, d2, invalid | |
212 | |
213 if not dirmove: | |
214 return copy, diverge | |
215 | |
216 for d in dirmove: | |
217 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d])) | |
218 | |
219 # check unaccounted nonoverlapping files against directory moves | |
220 for f in u1 + u2: | |
221 if f not in fullcopy: | |
222 for d in dirmove: | |
223 if f.startswith(d): | |
224 # new file added in a directory that was moved, move it | |
225 copy[f] = dirmove[d] + f[len(d):] | |
226 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f])) | |
227 break | |
228 | |
229 return copy, diverge | |
230 | |
231 def manifestmerge(repo, p1, p2, pa, overwrite, partial): | 57 def manifestmerge(repo, p1, p2, pa, overwrite, partial): |
232 """ | 58 """ |
233 Merge p1 and p2 with ancestor ma and generate merge action list | 59 Merge p1 and p2 with ancestor ma and generate merge action list |
234 | 60 |
235 overwrite = whether we clobber working files | 61 overwrite = whether we clobber working files |
243 m1 = p1.manifest() | 69 m1 = p1.manifest() |
244 m2 = p2.manifest() | 70 m2 = p2.manifest() |
245 ma = pa.manifest() | 71 ma = pa.manifest() |
246 backwards = (pa == p2) | 72 backwards = (pa == p2) |
247 action = [] | 73 action = [] |
248 copy = {} | 74 copy, copied, diverge = {}, {}, {} |
249 diverge = {} | |
250 | 75 |
251 def fmerge(f, f2=None, fa=None): | 76 def fmerge(f, f2=None, fa=None): |
252 """merge flags""" | 77 """merge flags""" |
253 if not f2: | 78 if not f2: |
254 f2 = f | 79 f2 = f |
274 def act(msg, m, f, *args): | 99 def act(msg, m, f, *args): |
275 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) | 100 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) |
276 action.append((f, m) + args) | 101 action.append((f, m) + args) |
277 | 102 |
278 if not (backwards or overwrite): | 103 if not (backwards or overwrite): |
279 rev1 = p1.rev() | 104 copy, diverge = copies.copies(repo, p1, p2, pa) |
280 if rev1 is None: | 105 copied = dict.fromkeys(copy.values()) |
281 # p1 is a workingctx | 106 for of, fl in diverge.items(): |
282 rev1 = p1.parents()[0].rev() | 107 act("divergent renames", "dr", of, fl) |
283 pr = repo.changelog.parentrevs | |
284 def parents(rev): | |
285 return [p for p in pr(rev) if p != nullrev] | |
286 limit = min(ancestor.symmetricdifference(rev1, p2.rev(), parents)) | |
287 copy, diverge = findcopies(repo, m1, m2, ma, limit) | |
288 | |
289 for of, fl in diverge.items(): | |
290 act("divergent renames", "dr", of, fl) | |
291 | |
292 copied = dict.fromkeys(copy.values()) | |
293 | 108 |
294 # Compare manifests | 109 # Compare manifests |
295 for f, n in m1.iteritems(): | 110 for f, n in m1.iteritems(): |
296 if partial and not partial(f): | 111 if partial and not partial(f): |
297 continue | 112 continue |