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