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