##// END OF EJS Templates
merge: move symmetricdifferences to ancestor.py
Matt Mackall -
r6273:20aa460a default
parent child Browse files
Show More
@@ -1,83 +1,131 b''
1 1 # ancestor.py - generic DAG ancestor algorithm for mercurial
2 2 #
3 3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import heapq
9 9
10 10 def ancestor(a, b, pfunc):
11 11 """
12 12 return the least common ancestor of nodes a and b or None if there
13 13 is no such ancestor.
14 14
15 15 pfunc must return a list of parent vertices
16 16 """
17 17
18 18 if a == b:
19 19 return a
20 20
21 21 # find depth from root of all ancestors
22 22 visit = [a, b]
23 23 depth = {}
24 24 while visit:
25 25 vertex = visit[-1]
26 26 pl = pfunc(vertex)
27 27 if not pl:
28 28 depth[vertex] = 0
29 29 visit.pop()
30 30 else:
31 31 for p in pl:
32 32 if p == a or p == b: # did we find a or b as a parent?
33 33 return p # we're done
34 34 if p not in depth:
35 35 visit.append(p)
36 36 if visit[-1] == vertex:
37 37 depth[vertex] = min([depth[p] for p in pl]) - 1
38 38 visit.pop()
39 39
40 40 # traverse ancestors in order of decreasing distance from root
41 41 def ancestors(vertex):
42 42 h = [(depth[vertex], vertex)]
43 43 seen = {}
44 44 while h:
45 45 d, n = heapq.heappop(h)
46 46 if n not in seen:
47 47 seen[n] = 1
48 48 yield (d, n)
49 49 for p in pfunc(n):
50 50 heapq.heappush(h, (depth[p], p))
51 51
52 52 def generations(vertex):
53 53 sg, s = None, {}
54 54 for g, v in ancestors(vertex):
55 55 if g != sg:
56 56 if sg:
57 57 yield sg, s
58 58 sg, s = g, {v:1}
59 59 else:
60 60 s[v] = 1
61 61 yield sg, s
62 62
63 63 x = generations(a)
64 64 y = generations(b)
65 65 gx = x.next()
66 66 gy = y.next()
67 67
68 68 # increment each ancestor list until it is closer to root than
69 69 # the other, or they match
70 70 try:
71 71 while 1:
72 72 if gx[0] == gy[0]:
73 73 for v in gx[1]:
74 74 if v in gy[1]:
75 75 return v
76 76 gy = y.next()
77 77 gx = x.next()
78 78 elif gx[0] > gy[0]:
79 79 gy = y.next()
80 80 else:
81 81 gx = x.next()
82 82 except StopIteration:
83 83 return None
84
85 def symmetricdifference(a, b, pfunc):
86 """symmetric difference of the sets of ancestors of a and b
87
88 I.e. revisions that are ancestors of a or b, but not both.
89 """
90 # basic idea:
91 # - mark a and b with different colors
92 # - walk the graph in topological order with the help of a heap;
93 # for each revision r:
94 # - if r has only one color, we want to return it
95 # - add colors[r] to its parents
96 #
97 # We keep track of the number of revisions in the heap that
98 # we may be interested in. We stop walking the graph as soon
99 # as this number reaches 0.
100 WHITE = 1
101 BLACK = 2
102 ALLCOLORS = WHITE | BLACK
103 colors = {a: WHITE, b: BLACK}
104
105 visit = [-a, -b]
106 heapq.heapify(visit)
107 n_wanted = len(visit)
108 ret = []
109
110 while n_wanted:
111 r = -heapq.heappop(visit)
112 wanted = colors[r] != ALLCOLORS
113 n_wanted -= wanted
114 if wanted:
115 ret.append(r)
116
117 for p in pfunc(r):
118 if p not in colors:
119 # first time we see p; add it to visit
120 n_wanted += wanted
121 colors[p] = colors[r]
122 heapq.heappush(visit, -p)
123 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
124 # at first we thought we wanted p, but now
125 # we know we don't really want it
126 n_wanted -= 1
127 colors[p] |= colors[r]
128
129 del colors[r]
130
131 return ret
@@ -1,633 +1,584 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import nullid, nullrev
9 9 from i18n import _
10 import errno, util, os, heapq, filemerge
10 import errno, util, os, heapq, filemerge, ancestor
11 11
12 12 def _checkunknown(wctx, mctx):
13 13 "check for collisions between unknown files and files in mctx"
14 14 for f in wctx.unknown():
15 15 if f in mctx and mctx[f].cmp(wctx[f].data()):
16 16 raise util.Abort(_("untracked file in working directory differs"
17 17 " from file in requested revision: '%s'") % f)
18 18
19 19 def _checkcollision(mctx):
20 20 "check for case folding collisions in the destination context"
21 21 folded = {}
22 22 for fn in mctx:
23 23 fold = fn.lower()
24 24 if fold in folded:
25 25 raise util.Abort(_("case-folding collision between %s and %s")
26 26 % (fn, folded[fold]))
27 27 folded[fold] = fn
28 28
29 29 def _forgetremoved(wctx, mctx, branchmerge):
30 30 """
31 31 Forget removed files
32 32
33 33 If we're jumping between revisions (as opposed to merging), and if
34 34 neither the working directory nor the target rev has the file,
35 35 then we need to remove it from the dirstate, to prevent the
36 36 dirstate from listing the file when it is no longer in the
37 37 manifest.
38 38
39 39 If we're merging, and the other revision has removed a file
40 40 that is not present in the working directory, we need to mark it
41 41 as removed.
42 42 """
43 43
44 44 action = []
45 45 state = branchmerge and 'r' or 'f'
46 46 for f in wctx.deleted():
47 47 if f not in mctx:
48 48 action.append((f, state))
49 49
50 50 if not branchmerge:
51 51 for f in wctx.removed():
52 52 if f not in mctx:
53 53 action.append((f, "f"))
54 54
55 55 return action
56 56
57 57 def _nonoverlap(d1, d2, d3):
58 58 "Return list of elements in d1 not in d2 or d3"
59 59 l = [d for d in d1 if d not in d3 and d not in d2]
60 60 l.sort()
61 61 return l
62 62
63 63 def _dirname(f):
64 64 s = f.rfind("/")
65 65 if s == -1:
66 66 return ""
67 67 return f[:s]
68 68
69 69 def _dirs(files):
70 70 d = {}
71 71 for f in files:
72 72 f = _dirname(f)
73 73 while f not in d:
74 74 d[f] = True
75 75 f = _dirname(f)
76 76 return d
77 77
78 78 def _findoldnames(fctx, limit):
79 79 "find files that path was copied from, back to linkrev limit"
80 80 old = {}
81 81 seen = {}
82 82 orig = fctx.path()
83 83 visit = [fctx]
84 84 while visit:
85 85 fc = visit.pop()
86 86 s = str(fc)
87 87 if s in seen:
88 88 continue
89 89 seen[s] = 1
90 90 if fc.path() != orig and fc.path() not in old:
91 91 old[fc.path()] = 1
92 92 if fc.rev() < limit:
93 93 continue
94 94 visit += fc.parents()
95 95
96 96 old = old.keys()
97 97 old.sort()
98 98 return old
99 99
100 100 def findcopies(repo, m1, m2, ma, limit):
101 101 """
102 102 Find moves and copies between m1 and m2 back to limit linkrev
103 103 """
104 104
105 105 wctx = repo.workingctx()
106 106
107 107 def makectx(f, n):
108 108 if len(n) == 20:
109 109 return repo.filectx(f, fileid=n)
110 110 return wctx.filectx(f)
111 111 ctx = util.cachefunc(makectx)
112 112
113 113 copy = {}
114 114 fullcopy = {}
115 115 diverge = {}
116 116
117 117 def checkcopies(f, m1, m2):
118 118 '''check possible copies of f from m1 to m2'''
119 119 c1 = ctx(f, m1[f])
120 120 for of in _findoldnames(c1, limit):
121 121 fullcopy[f] = of # remember for dir rename detection
122 122 if of in m2: # original file not in other manifest?
123 123 # if the original file is unchanged on the other branch,
124 124 # no merge needed
125 125 if m2[of] != ma.get(of):
126 126 c2 = ctx(of, m2[of])
127 127 ca = c1.ancestor(c2)
128 128 # related and named changed on only one side?
129 129 if ca and ca.path() == f or ca.path() == c2.path():
130 130 if c1 != ca or c2 != ca: # merge needed?
131 131 copy[f] = of
132 132 elif of in ma:
133 133 diverge.setdefault(of, []).append(f)
134 134
135 135 if not repo.ui.configbool("merge", "followcopies", True):
136 136 return {}, {}
137 137
138 138 # avoid silly behavior for update from empty dir
139 139 if not m1 or not m2 or not ma:
140 140 return {}, {}
141 141
142 142 repo.ui.debug(_(" searching for copies back to rev %d\n") % limit)
143 143
144 144 u1 = _nonoverlap(m1, m2, ma)
145 145 u2 = _nonoverlap(m2, m1, ma)
146 146
147 147 if u1:
148 148 repo.ui.debug(_(" unmatched files in local:\n %s\n")
149 149 % "\n ".join(u1))
150 150 if u2:
151 151 repo.ui.debug(_(" unmatched files in other:\n %s\n")
152 152 % "\n ".join(u2))
153 153
154 154 for f in u1:
155 155 checkcopies(f, m1, m2)
156 156
157 157 for f in u2:
158 158 checkcopies(f, m2, m1)
159 159
160 160 diverge2 = {}
161 161 for of, fl in diverge.items():
162 162 if len(fl) == 1:
163 163 del diverge[of] # not actually divergent
164 164 else:
165 165 diverge2.update(dict.fromkeys(fl)) # reverse map for below
166 166
167 167 if fullcopy:
168 168 repo.ui.debug(_(" all copies found (* = to merge, ! = divergent):\n"))
169 169 for f in fullcopy:
170 170 note = ""
171 171 if f in copy: note += "*"
172 172 if f in diverge2: note += "!"
173 173 repo.ui.debug(_(" %s -> %s %s\n") % (f, fullcopy[f], note))
174 174
175 175 del diverge2
176 176
177 177 if not fullcopy or not repo.ui.configbool("merge", "followdirs", True):
178 178 return copy, diverge
179 179
180 180 repo.ui.debug(_(" checking for directory renames\n"))
181 181
182 182 # generate a directory move map
183 183 d1, d2 = _dirs(m1), _dirs(m2)
184 184 invalid = {}
185 185 dirmove = {}
186 186
187 187 # examine each file copy for a potential directory move, which is
188 188 # when all the files in a directory are moved to a new directory
189 189 for dst, src in fullcopy.items():
190 190 dsrc, ddst = _dirname(src), _dirname(dst)
191 191 if dsrc in invalid:
192 192 # already seen to be uninteresting
193 193 continue
194 194 elif dsrc in d1 and ddst in d1:
195 195 # directory wasn't entirely moved locally
196 196 invalid[dsrc] = True
197 197 elif dsrc in d2 and ddst in d2:
198 198 # directory wasn't entirely moved remotely
199 199 invalid[dsrc] = True
200 200 elif dsrc in dirmove and dirmove[dsrc] != ddst:
201 201 # files from the same directory moved to two different places
202 202 invalid[dsrc] = True
203 203 else:
204 204 # looks good so far
205 205 dirmove[dsrc + "/"] = ddst + "/"
206 206
207 207 for i in invalid:
208 208 if i in dirmove:
209 209 del dirmove[i]
210 210
211 211 del d1, d2, invalid
212 212
213 213 if not dirmove:
214 214 return copy, diverge
215 215
216 216 for d in dirmove:
217 217 repo.ui.debug(_(" dir %s -> %s\n") % (d, dirmove[d]))
218 218
219 219 # check unaccounted nonoverlapping files against directory moves
220 220 for f in u1 + u2:
221 221 if f not in fullcopy:
222 222 for d in dirmove:
223 223 if f.startswith(d):
224 224 # new file added in a directory that was moved, move it
225 225 copy[f] = dirmove[d] + f[len(d):]
226 226 repo.ui.debug(_(" file %s -> %s\n") % (f, copy[f]))
227 227 break
228 228
229 229 return copy, diverge
230 230
231 def _symmetricdifference(repo, rev1, rev2):
232 """symmetric difference of the sets of ancestors of rev1 and rev2
233
234 I.e. revisions that are ancestors of rev1 or rev2, but not both.
235 """
236 # basic idea:
237 # - mark rev1 and rev2 with different colors
238 # - walk the graph in topological order with the help of a heap;
239 # for each revision r:
240 # - if r has only one color, we want to return it
241 # - add colors[r] to its parents
242 #
243 # We keep track of the number of revisions in the heap that
244 # we may be interested in. We stop walking the graph as soon
245 # as this number reaches 0.
246 WHITE = 1
247 BLACK = 2
248 ALLCOLORS = WHITE | BLACK
249 colors = {rev1: WHITE, rev2: BLACK}
250
251 cl = repo.changelog
252
253 visit = [-rev1, -rev2]
254 heapq.heapify(visit)
255 n_wanted = len(visit)
256 ret = []
257
258 while n_wanted:
259 r = -heapq.heappop(visit)
260 wanted = colors[r] != ALLCOLORS
261 n_wanted -= wanted
262 if wanted:
263 ret.append(r)
264
265 for p in cl.parentrevs(r):
266 if p == nullrev:
267 continue
268 if p not in colors:
269 # first time we see p; add it to visit
270 n_wanted += wanted
271 colors[p] = colors[r]
272 heapq.heappush(visit, -p)
273 elif colors[p] != ALLCOLORS and colors[p] != colors[r]:
274 # at first we thought we wanted p, but now
275 # we know we don't really want it
276 n_wanted -= 1
277 colors[p] |= colors[r]
278
279 del colors[r]
280
281 return ret
282
283 231 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
284 232 """
285 233 Merge p1 and p2 with ancestor ma and generate merge action list
286 234
287 235 overwrite = whether we clobber working files
288 236 partial = function to filter file lists
289 237 """
290 238
291 239 repo.ui.note(_("resolving manifests\n"))
292 240 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
293 241 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
294 242
295 243 m1 = p1.manifest()
296 244 m2 = p2.manifest()
297 245 ma = pa.manifest()
298 246 backwards = (pa == p2)
299 247 action = []
300 248 copy = {}
301 249 diverge = {}
302 250
303 251 def fmerge(f, f2=None, fa=None):
304 252 """merge flags"""
305 253 if not f2:
306 254 f2 = f
307 255 fa = f
308 256 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
309 257 if m == n: # flags agree
310 258 return m # unchanged
311 259 if m and n: # flags are set but don't agree
312 260 if not a: # both differ from parent
313 261 r = repo.ui.prompt(
314 262 _(" conflicting flags for %s\n"
315 263 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
316 264 return r != "n" and r or ''
317 265 if m == a:
318 266 return n # changed from m to n
319 267 return m # changed from n to m
320 268 if m and m != a: # changed from a to m
321 269 return m
322 270 if n and n != a: # changed from a to n
323 271 return n
324 272 return '' # flag was cleared
325 273
326 274 def act(msg, m, f, *args):
327 275 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
328 276 action.append((f, m) + args)
329 277
330 278 if not (backwards or overwrite):
331 279 rev1 = p1.rev()
332 280 if rev1 is None:
333 281 # p1 is a workingctx
334 282 rev1 = p1.parents()[0].rev()
335 limit = min(_symmetricdifference(repo, rev1, p2.rev()))
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))
336 287 copy, diverge = findcopies(repo, m1, m2, ma, limit)
337 288
338 289 for of, fl in diverge.items():
339 290 act("divergent renames", "dr", of, fl)
340 291
341 292 copied = dict.fromkeys(copy.values())
342 293
343 294 # Compare manifests
344 295 for f, n in m1.iteritems():
345 296 if partial and not partial(f):
346 297 continue
347 298 if f in m2:
348 299 if overwrite or backwards:
349 300 rflags = m2.flags(f)
350 301 else:
351 302 rflags = fmerge(f)
352 303 # are files different?
353 304 if n != m2[f]:
354 305 a = ma.get(f, nullid)
355 306 # are we clobbering?
356 307 if overwrite:
357 308 act("clobbering", "g", f, rflags)
358 309 # or are we going back in time and clean?
359 310 elif backwards and not n[20:]:
360 311 act("reverting", "g", f, rflags)
361 312 # are both different from the ancestor?
362 313 elif n != a and m2[f] != a:
363 314 act("versions differ", "m", f, f, f, rflags, False)
364 315 # is remote's version newer?
365 316 elif m2[f] != a:
366 317 act("remote is newer", "g", f, rflags)
367 318 # local is newer, not overwrite, check mode bits
368 319 elif m1.flags(f) != rflags:
369 320 act("update permissions", "e", f, rflags)
370 321 # contents same, check mode bits
371 322 elif m1.flags(f) != rflags:
372 323 act("update permissions", "e", f, rflags)
373 324 elif f in copied:
374 325 continue
375 326 elif f in copy:
376 327 f2 = copy[f]
377 328 if f2 not in m2: # directory rename
378 329 act("remote renamed directory to " + f2, "d",
379 330 f, None, f2, m1.flags(f))
380 331 elif f2 in m1: # case 2 A,B/B/B
381 332 act("local copied to " + f2, "m",
382 333 f, f2, f, fmerge(f, f2, f2), False)
383 334 else: # case 4,21 A/B/B
384 335 act("local moved to " + f2, "m",
385 336 f, f2, f, fmerge(f, f2, f2), False)
386 337 elif f in ma:
387 338 if n != ma[f] and not overwrite:
388 339 if repo.ui.prompt(
389 340 _(" local changed %s which remote deleted\n"
390 341 "use (c)hanged version or (d)elete?") % f,
391 342 _("[cd]"), _("c")) == _("d"):
392 343 act("prompt delete", "r", f)
393 344 else:
394 345 act("other deleted", "r", f)
395 346 else:
396 347 # file is created on branch or in working directory
397 348 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
398 349 act("remote deleted", "r", f)
399 350
400 351 for f, n in m2.iteritems():
401 352 if partial and not partial(f):
402 353 continue
403 354 if f in m1:
404 355 continue
405 356 if f in copied:
406 357 continue
407 358 if f in copy:
408 359 f2 = copy[f]
409 360 if f2 not in m1: # directory rename
410 361 act("local renamed directory to " + f2, "d",
411 362 None, f, f2, m2.flags(f))
412 363 elif f2 in m2: # rename case 1, A/A,B/A
413 364 act("remote copied to " + f, "m",
414 365 f2, f, f, fmerge(f2, f, f2), False)
415 366 else: # case 3,20 A/B/A
416 367 act("remote moved to " + f, "m",
417 368 f2, f, f, fmerge(f2, f, f2), True)
418 369 elif f in ma:
419 370 if overwrite or backwards:
420 371 act("recreating", "g", f, m2.flags(f))
421 372 elif n != ma[f]:
422 373 if repo.ui.prompt(
423 374 _("remote changed %s which local deleted\n"
424 375 "use (c)hanged version or leave (d)eleted?") % f,
425 376 _("[cd]"), _("c")) == _("c"):
426 377 act("prompt recreating", "g", f, m2.flags(f))
427 378 else:
428 379 act("remote created", "g", f, m2.flags(f))
429 380
430 381 return action
431 382
432 383 def applyupdates(repo, action, wctx, mctx):
433 384 "apply the merge action list to the working directory"
434 385
435 386 updated, merged, removed, unresolved = 0, 0, 0, 0
436 387 action.sort()
437 388 # prescan for copy/renames
438 389 for a in action:
439 390 f, m = a[:2]
440 391 if m == 'm': # merge
441 392 f2, fd, flags, move = a[2:]
442 393 if f != fd:
443 394 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
444 395 repo.wwrite(fd, repo.wread(f), flags)
445 396
446 397 audit_path = util.path_auditor(repo.root)
447 398
448 399 for a in action:
449 400 f, m = a[:2]
450 401 if f and f[0] == "/":
451 402 continue
452 403 if m == "r": # remove
453 404 repo.ui.note(_("removing %s\n") % f)
454 405 audit_path(f)
455 406 try:
456 407 util.unlink(repo.wjoin(f))
457 408 except OSError, inst:
458 409 if inst.errno != errno.ENOENT:
459 410 repo.ui.warn(_("update failed to remove %s: %s!\n") %
460 411 (f, inst.strerror))
461 412 removed += 1
462 413 elif m == "m": # merge
463 414 f2, fd, flags, move = a[2:]
464 415 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
465 416 if r > 0:
466 417 unresolved += 1
467 418 else:
468 419 if r is None:
469 420 updated += 1
470 421 else:
471 422 merged += 1
472 423 util.set_flags(repo.wjoin(fd), flags)
473 424 if f != fd and move and util.lexists(repo.wjoin(f)):
474 425 repo.ui.debug(_("removing %s\n") % f)
475 426 os.unlink(repo.wjoin(f))
476 427 elif m == "g": # get
477 428 flags = a[2]
478 429 repo.ui.note(_("getting %s\n") % f)
479 430 t = mctx.filectx(f).data()
480 431 repo.wwrite(f, t, flags)
481 432 updated += 1
482 433 elif m == "d": # directory rename
483 434 f2, fd, flags = a[2:]
484 435 if f:
485 436 repo.ui.note(_("moving %s to %s\n") % (f, fd))
486 437 t = wctx.filectx(f).data()
487 438 repo.wwrite(fd, t, flags)
488 439 util.unlink(repo.wjoin(f))
489 440 if f2:
490 441 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
491 442 t = mctx.filectx(f2).data()
492 443 repo.wwrite(fd, t, flags)
493 444 updated += 1
494 445 elif m == "dr": # divergent renames
495 446 fl = a[2]
496 447 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
497 448 for nf in fl:
498 449 repo.ui.warn(" %s\n" % nf)
499 450 elif m == "e": # exec
500 451 flags = a[2]
501 452 util.set_flags(repo.wjoin(f), flags)
502 453
503 454 return updated, merged, removed, unresolved
504 455
505 456 def recordupdates(repo, action, branchmerge):
506 457 "record merge actions to the dirstate"
507 458
508 459 for a in action:
509 460 f, m = a[:2]
510 461 if m == "r": # remove
511 462 if branchmerge:
512 463 repo.dirstate.remove(f)
513 464 else:
514 465 repo.dirstate.forget(f)
515 466 elif m == "f": # forget
516 467 repo.dirstate.forget(f)
517 468 elif m in "ge": # get or exec change
518 469 if branchmerge:
519 470 repo.dirstate.normaldirty(f)
520 471 else:
521 472 repo.dirstate.normal(f)
522 473 elif m == "m": # merge
523 474 f2, fd, flag, move = a[2:]
524 475 if branchmerge:
525 476 # We've done a branch merge, mark this file as merged
526 477 # so that we properly record the merger later
527 478 repo.dirstate.merge(fd)
528 479 if f != f2: # copy/rename
529 480 if move:
530 481 repo.dirstate.remove(f)
531 482 if f != fd:
532 483 repo.dirstate.copy(f, fd)
533 484 else:
534 485 repo.dirstate.copy(f2, fd)
535 486 else:
536 487 # We've update-merged a locally modified file, so
537 488 # we set the dirstate to emulate a normal checkout
538 489 # of that file some time in the past. Thus our
539 490 # merge will appear as a normal local file
540 491 # modification.
541 492 repo.dirstate.normallookup(fd)
542 493 if move:
543 494 repo.dirstate.forget(f)
544 495 elif m == "d": # directory rename
545 496 f2, fd, flag = a[2:]
546 497 if not f2 and f not in repo.dirstate:
547 498 # untracked file moved
548 499 continue
549 500 if branchmerge:
550 501 repo.dirstate.add(fd)
551 502 if f:
552 503 repo.dirstate.remove(f)
553 504 repo.dirstate.copy(f, fd)
554 505 if f2:
555 506 repo.dirstate.copy(f2, fd)
556 507 else:
557 508 repo.dirstate.normal(fd)
558 509 if f:
559 510 repo.dirstate.forget(f)
560 511
561 512 def update(repo, node, branchmerge, force, partial):
562 513 """
563 514 Perform a merge between the working directory and the given node
564 515
565 516 branchmerge = whether to merge between branches
566 517 force = whether to force branch merging or file overwriting
567 518 partial = a function to filter file lists (dirstate not updated)
568 519 """
569 520
570 521 wlock = repo.wlock()
571 522 try:
572 523 wc = repo.workingctx()
573 524 if node is None:
574 525 # tip of current branch
575 526 try:
576 527 node = repo.branchtags()[wc.branch()]
577 528 except KeyError:
578 529 if wc.branch() == "default": # no default branch!
579 530 node = repo.lookup("tip") # update to tip
580 531 else:
581 532 raise util.Abort(_("branch %s not found") % wc.branch())
582 533 overwrite = force and not branchmerge
583 534 forcemerge = force and branchmerge
584 535 pl = wc.parents()
585 536 p1, p2 = pl[0], repo.changectx(node)
586 537 pa = p1.ancestor(p2)
587 538 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
588 539 fastforward = False
589 540
590 541 ### check phase
591 542 if not overwrite and len(pl) > 1:
592 543 raise util.Abort(_("outstanding uncommitted merges"))
593 544 if pa == p1 or pa == p2: # is there a linear path from p1 to p2?
594 545 if branchmerge:
595 546 if p1.branch() != p2.branch() and pa != p2:
596 547 fastforward = True
597 548 else:
598 549 raise util.Abort(_("there is nothing to merge, just use "
599 550 "'hg update' or look at 'hg heads'"))
600 551 elif not (overwrite or branchmerge):
601 552 raise util.Abort(_("update spans branches, use 'hg merge' "
602 553 "or 'hg update -C' to lose changes"))
603 554 if branchmerge and not forcemerge:
604 555 if wc.files() or wc.deleted():
605 556 raise util.Abort(_("outstanding uncommitted changes"))
606 557
607 558 ### calculate phase
608 559 action = []
609 560 if not force:
610 561 _checkunknown(wc, p2)
611 562 if not util.checkfolding(repo.path):
612 563 _checkcollision(p2)
613 564 action += _forgetremoved(wc, p2, branchmerge)
614 565 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
615 566
616 567 ### apply phase
617 568 if not branchmerge: # just jump to the new rev
618 569 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
619 570 if not partial:
620 571 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
621 572
622 573 stats = applyupdates(repo, action, wc, p2)
623 574
624 575 if not partial:
625 576 recordupdates(repo, action, branchmerge)
626 577 repo.dirstate.setparents(fp1, fp2)
627 578 if not branchmerge and not fastforward:
628 579 repo.dirstate.setbranch(p2.branch())
629 580 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
630 581
631 582 return stats
632 583 finally:
633 584 del wlock
General Comments 0
You need to be logged in to leave comments. Login now