##// END OF EJS Templates
merge: only sort manifests in debug mode (issue3769)
Matt Mackall -
r18456:8a811fa9 stable
parent child Browse files
Show More
@@ -1,636 +1,642 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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 10 import error, util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 fcd = wctx[dfile]
71 71 fco = octx[ofile]
72 72 fca = self._repo.filectx(afile, fileid=anode)
73 73 # "premerge" x flags
74 74 flo = fco.flags()
75 75 fla = fca.flags()
76 76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 77 if fca.node() == nullid:
78 78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 79 afile)
80 80 elif flags == fla:
81 81 flags = flo
82 82 # restore local
83 83 f = self._repo.opener("merge/" + hash)
84 84 self._repo.wwrite(dfile, f.read(), flags)
85 85 f.close()
86 86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
87 87 if r is None:
88 88 # no real conflict
89 89 del self._state[dfile]
90 90 elif not r:
91 91 self.mark(dfile, 'r')
92 92 return r
93 93
94 94 def _checkunknownfile(repo, wctx, mctx, f):
95 95 return (not repo.dirstate._ignore(f)
96 96 and os.path.isfile(repo.wjoin(f))
97 97 and repo.dirstate.normalize(f) not in repo.dirstate
98 98 and mctx[f].cmp(wctx[f]))
99 99
100 100 def _checkunknown(repo, wctx, mctx):
101 101 "check for collisions between unknown files and files in mctx"
102 102
103 103 error = False
104 104 for f in mctx:
105 105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
106 106 error = True
107 107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
108 108 if error:
109 109 raise util.Abort(_("untracked files in working directory differ "
110 110 "from files in requested revision"))
111 111
112 112 def _remains(f, m, ma, workingctx=False):
113 113 """check whether specified file remains after merge.
114 114
115 115 It is assumed that specified file is not contained in the manifest
116 116 of the other context.
117 117 """
118 118 if f in ma:
119 119 n = m[f]
120 120 if n != ma[f]:
121 121 return True # because it is changed locally
122 122 # even though it doesn't remain, if "remote deleted" is
123 123 # chosen in manifestmerge()
124 124 elif workingctx and n[20:] == "a":
125 125 return True # because it is added locally (linear merge specific)
126 126 else:
127 127 return False # because it is removed remotely
128 128 else:
129 129 return True # because it is added locally
130 130
131 131 def _checkcollision(mctx, extractxs):
132 132 "check for case folding collisions in the destination context"
133 133 folded = {}
134 134 for fn in mctx:
135 135 fold = util.normcase(fn)
136 136 if fold in folded:
137 137 raise util.Abort(_("case-folding collision between %s and %s")
138 138 % (fn, folded[fold]))
139 139 folded[fold] = fn
140 140
141 141 if extractxs:
142 142 wctx, actx = extractxs
143 143 # class to delay looking up copy mapping
144 144 class pathcopies(object):
145 145 @util.propertycache
146 146 def map(self):
147 147 # {dst@mctx: src@wctx} copy mapping
148 148 return copies.pathcopies(wctx, mctx)
149 149 pc = pathcopies()
150 150
151 151 for fn in wctx:
152 152 fold = util.normcase(fn)
153 153 mfn = folded.get(fold, None)
154 154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
155 155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
156 156 _remains(mfn, mctx.manifest(), actx.manifest())):
157 157 raise util.Abort(_("case-folding collision between %s and %s")
158 158 % (mfn, fn))
159 159
160 160 def _forgetremoved(wctx, mctx, branchmerge):
161 161 """
162 162 Forget removed files
163 163
164 164 If we're jumping between revisions (as opposed to merging), and if
165 165 neither the working directory nor the target rev has the file,
166 166 then we need to remove it from the dirstate, to prevent the
167 167 dirstate from listing the file when it is no longer in the
168 168 manifest.
169 169
170 170 If we're merging, and the other revision has removed a file
171 171 that is not present in the working directory, we need to mark it
172 172 as removed.
173 173 """
174 174
175 175 actions = []
176 176 state = branchmerge and 'r' or 'f'
177 177 for f in wctx.deleted():
178 178 if f not in mctx:
179 179 actions.append((f, state))
180 180
181 181 if not branchmerge:
182 182 for f in wctx.removed():
183 183 if f not in mctx:
184 184 actions.append((f, "f"))
185 185
186 186 return actions
187 187
188 188 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
189 189 """
190 190 Merge p1 and p2 with ancestor pa and generate merge action list
191 191
192 192 overwrite = whether we clobber working files
193 193 partial = function to filter file lists
194 194 """
195 195
196 196 def act(msg, m, f, *args):
197 197 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
198 198 actions.append((f, m) + args)
199 199
200 200 actions, copy, movewithdir = [], {}, {}
201 201
202 202 if overwrite:
203 203 pa = p1
204 204 elif pa == p2: # backwards
205 205 pa = p1.p1()
206 206 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 207 ret = copies.mergecopies(repo, p1, p2, pa)
208 208 copy, movewithdir, diverge, renamedelete = ret
209 209 for of, fl in diverge.iteritems():
210 210 act("divergent renames", "dr", of, fl)
211 211 for of, fl in renamedelete.iteritems():
212 212 act("rename and delete", "rd", of, fl)
213 213
214 214 repo.ui.note(_("resolving manifests\n"))
215 215 repo.ui.debug(" overwrite: %s, partial: %s\n"
216 216 % (bool(overwrite), bool(partial)))
217 217 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
218 218
219 219 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
220 220 copied = set(copy.values())
221 221 copied.update(movewithdir.values())
222 222
223 223 if '.hgsubstate' in m1:
224 224 # check whether sub state is modified
225 225 for s in sorted(p1.substate):
226 226 if p1.sub(s).dirty():
227 227 m1['.hgsubstate'] += "+"
228 228 break
229 229
230 230 # Compare manifests
231 for f, n in sorted(m1.iteritems()):
231 visit = m1.iteritems()
232 if repo.ui.debugflag:
233 visit = sorted(visit)
234 for f, n in visit:
232 235 if partial and not partial(f):
233 236 continue
234 237 if f in m2:
235 238 n2 = m2[f]
236 239 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 240 nol = 'l' not in fl1 + fl2 + fla
238 241 a = ma.get(f, nullid)
239 242 if n == n2 and fl1 == fl2:
240 243 pass # same - keep local
241 244 elif n2 == a and fl2 == fla:
242 245 pass # remote unchanged - keep local
243 246 elif n == a and fl1 == fla: # local unchanged - use remote
244 247 if n == n2: # optimization: keep local content
245 248 act("update permissions", "e", f, fl2)
246 249 else:
247 250 act("remote is newer", "g", f, fl2)
248 251 elif nol and n2 == a: # remote only changed 'x'
249 252 act("update permissions", "e", f, fl2)
250 253 elif nol and n == a: # local only changed 'x'
251 254 act("remote is newer", "g", f, fl)
252 255 else: # both changed something
253 256 act("versions differ", "m", f, f, f, False)
254 257 elif f in copied: # files we'll deal with on m2 side
255 258 pass
256 259 elif f in movewithdir: # directory rename
257 260 f2 = movewithdir[f]
258 261 act("remote renamed directory to " + f2, "d", f, None, f2,
259 262 m1.flags(f))
260 263 elif f in copy:
261 264 f2 = copy[f]
262 265 act("local copied/moved to " + f2, "m", f, f2, f, False)
263 266 elif f in ma: # clean, a different, no remote
264 267 if n != ma[f]:
265 268 if repo.ui.promptchoice(
266 269 _(" local changed %s which remote deleted\n"
267 270 "use (c)hanged version or (d)elete?") % f,
268 271 (_("&Changed"), _("&Delete")), 0):
269 272 act("prompt delete", "r", f)
270 273 else:
271 274 act("prompt keep", "a", f)
272 275 elif n[20:] == "a": # added, no remote
273 276 act("remote deleted", "f", f)
274 277 else:
275 278 act("other deleted", "r", f)
276 279
277 for f, n in sorted(m2.iteritems()):
280 visit = m2.iteritems()
281 if repo.ui.debugflag:
282 visit = sorted(visit)
283 for f, n in visit:
278 284 if partial and not partial(f):
279 285 continue
280 286 if f in m1 or f in copied: # files already visited
281 287 continue
282 288 if f in movewithdir:
283 289 f2 = movewithdir[f]
284 290 act("local renamed directory to " + f2, "d", None, f, f2,
285 291 m2.flags(f))
286 292 elif f in copy:
287 293 f2 = copy[f]
288 294 if f2 in m2:
289 295 act("remote copied to " + f, "m",
290 296 f2, f, f, False)
291 297 else:
292 298 act("remote moved to " + f, "m",
293 299 f2, f, f, True)
294 300 elif f not in ma:
295 301 if (not overwrite
296 302 and _checkunknownfile(repo, p1, p2, f)):
297 303 act("remote differs from untracked local",
298 304 "m", f, f, f, False)
299 305 else:
300 306 act("remote created", "g", f, m2.flags(f))
301 307 elif n != ma[f]:
302 308 if repo.ui.promptchoice(
303 309 _("remote changed %s which local deleted\n"
304 310 "use (c)hanged version or leave (d)eleted?") % f,
305 311 (_("&Changed"), _("&Deleted")), 0) == 0:
306 312 act("prompt recreating", "g", f, m2.flags(f))
307 313
308 314 return actions
309 315
310 316 def actionkey(a):
311 317 return a[1] == "r" and -1 or 0, a
312 318
313 319 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
314 320 """apply the merge action list to the working directory
315 321
316 322 wctx is the working copy context
317 323 mctx is the context to be merged into the working copy
318 324 actx is the context of the common ancestor
319 325
320 326 Return a tuple of counts (updated, merged, removed, unresolved) that
321 327 describes how many files were affected by the update.
322 328 """
323 329
324 330 updated, merged, removed, unresolved = 0, 0, 0, 0
325 331 ms = mergestate(repo)
326 332 ms.reset(wctx.p1().node())
327 333 moves = []
328 334 actions.sort(key=actionkey)
329 335
330 336 # prescan for merges
331 337 for a in actions:
332 338 f, m = a[:2]
333 339 if m == "m": # merge
334 340 f2, fd, move = a[2:]
335 341 if fd == '.hgsubstate': # merged internally
336 342 continue
337 343 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
338 344 fcl = wctx[f]
339 345 fco = mctx[f2]
340 346 if mctx == actx: # backwards, use working dir parent as ancestor
341 347 if fcl.parents():
342 348 fca = fcl.p1()
343 349 else:
344 350 fca = repo.filectx(f, fileid=nullrev)
345 351 else:
346 352 fca = fcl.ancestor(fco, actx)
347 353 if not fca:
348 354 fca = repo.filectx(f, fileid=nullrev)
349 355 ms.add(fcl, fco, fca, fd)
350 356 if f != fd and move:
351 357 moves.append(f)
352 358
353 359 audit = repo.wopener.audit
354 360
355 361 # remove renamed files after safely stored
356 362 for f in moves:
357 363 if os.path.lexists(repo.wjoin(f)):
358 364 repo.ui.debug("removing %s\n" % f)
359 365 audit(f)
360 366 util.unlinkpath(repo.wjoin(f))
361 367
362 368 numupdates = len(actions)
363 369 for i, a in enumerate(actions):
364 370 f, m = a[:2]
365 371 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
366 372 unit=_('files'))
367 373 if m == "r": # remove
368 374 repo.ui.note(_("removing %s\n") % f)
369 375 audit(f)
370 376 if f == '.hgsubstate': # subrepo states need updating
371 377 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
372 378 try:
373 379 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
374 380 except OSError, inst:
375 381 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 382 (f, inst.strerror))
377 383 removed += 1
378 384 elif m == "m": # merge
379 385 if fd == '.hgsubstate': # subrepo states need updating
380 386 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
381 387 overwrite)
382 388 continue
383 389 f2, fd, move = a[2:]
384 390 audit(fd)
385 391 r = ms.resolve(fd, wctx, mctx)
386 392 if r is not None and r > 0:
387 393 unresolved += 1
388 394 else:
389 395 if r is None:
390 396 updated += 1
391 397 else:
392 398 merged += 1
393 399 elif m == "g": # get
394 400 flags = a[2]
395 401 repo.ui.note(_("getting %s\n") % f)
396 402 repo.wwrite(f, mctx.filectx(f).data(), flags)
397 403 updated += 1
398 404 if f == '.hgsubstate': # subrepo states need updating
399 405 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
400 406 elif m == "d": # directory rename
401 407 f2, fd, flags = a[2:]
402 408 if f:
403 409 repo.ui.note(_("moving %s to %s\n") % (f, fd))
404 410 audit(f)
405 411 repo.wwrite(fd, wctx.filectx(f).data(), flags)
406 412 util.unlinkpath(repo.wjoin(f))
407 413 if f2:
408 414 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
409 415 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
410 416 updated += 1
411 417 elif m == "dr": # divergent renames
412 418 fl = a[2]
413 419 repo.ui.warn(_("note: possible conflict - %s was renamed "
414 420 "multiple times to:\n") % f)
415 421 for nf in fl:
416 422 repo.ui.warn(" %s\n" % nf)
417 423 elif m == "rd": # rename and delete
418 424 fl = a[2]
419 425 repo.ui.warn(_("note: possible conflict - %s was deleted "
420 426 "and renamed to:\n") % f)
421 427 for nf in fl:
422 428 repo.ui.warn(" %s\n" % nf)
423 429 elif m == "e": # exec
424 430 flags = a[2]
425 431 audit(f)
426 432 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
427 433 updated += 1
428 434 ms.commit()
429 435 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
430 436
431 437 return updated, merged, removed, unresolved
432 438
433 439 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
434 440 "Calculate the actions needed to merge mctx into tctx"
435 441 actions = []
436 442 folding = not util.checkcase(repo.path)
437 443 if folding:
438 444 # collision check is not needed for clean update
439 445 if (not branchmerge and
440 446 (force or not tctx.dirty(missing=True, branch=False))):
441 447 _checkcollision(mctx, None)
442 448 else:
443 449 _checkcollision(mctx, (tctx, ancestor))
444 450 if not force:
445 451 _checkunknown(repo, tctx, mctx)
446 452 if tctx.rev() is None:
447 453 actions += _forgetremoved(tctx, mctx, branchmerge)
448 454 actions += manifestmerge(repo, tctx, mctx,
449 455 ancestor,
450 456 force and not branchmerge,
451 457 partial)
452 458 return actions
453 459
454 460 def recordupdates(repo, actions, branchmerge):
455 461 "record merge actions to the dirstate"
456 462
457 463 for a in actions:
458 464 f, m = a[:2]
459 465 if m == "r": # remove
460 466 if branchmerge:
461 467 repo.dirstate.remove(f)
462 468 else:
463 469 repo.dirstate.drop(f)
464 470 elif m == "a": # re-add
465 471 if not branchmerge:
466 472 repo.dirstate.add(f)
467 473 elif m == "f": # forget
468 474 repo.dirstate.drop(f)
469 475 elif m == "e": # exec change
470 476 repo.dirstate.normallookup(f)
471 477 elif m == "g": # get
472 478 if branchmerge:
473 479 repo.dirstate.otherparent(f)
474 480 else:
475 481 repo.dirstate.normal(f)
476 482 elif m == "m": # merge
477 483 f2, fd, move = a[2:]
478 484 if branchmerge:
479 485 # We've done a branch merge, mark this file as merged
480 486 # so that we properly record the merger later
481 487 repo.dirstate.merge(fd)
482 488 if f != f2: # copy/rename
483 489 if move:
484 490 repo.dirstate.remove(f)
485 491 if f != fd:
486 492 repo.dirstate.copy(f, fd)
487 493 else:
488 494 repo.dirstate.copy(f2, fd)
489 495 else:
490 496 # We've update-merged a locally modified file, so
491 497 # we set the dirstate to emulate a normal checkout
492 498 # of that file some time in the past. Thus our
493 499 # merge will appear as a normal local file
494 500 # modification.
495 501 if f2 == fd: # file not locally copied/moved
496 502 repo.dirstate.normallookup(fd)
497 503 if move:
498 504 repo.dirstate.drop(f)
499 505 elif m == "d": # directory rename
500 506 f2, fd, flag = a[2:]
501 507 if not f2 and f not in repo.dirstate:
502 508 # untracked file moved
503 509 continue
504 510 if branchmerge:
505 511 repo.dirstate.add(fd)
506 512 if f:
507 513 repo.dirstate.remove(f)
508 514 repo.dirstate.copy(f, fd)
509 515 if f2:
510 516 repo.dirstate.copy(f2, fd)
511 517 else:
512 518 repo.dirstate.normal(fd)
513 519 if f:
514 520 repo.dirstate.drop(f)
515 521
516 522 def update(repo, node, branchmerge, force, partial, ancestor=None,
517 523 mergeancestor=False):
518 524 """
519 525 Perform a merge between the working directory and the given node
520 526
521 527 node = the node to update to, or None if unspecified
522 528 branchmerge = whether to merge between branches
523 529 force = whether to force branch merging or file overwriting
524 530 partial = a function to filter file lists (dirstate not updated)
525 531 mergeancestor = if false, merging with an ancestor (fast-forward)
526 532 is only allowed between different named branches. This flag
527 533 is used by rebase extension as a temporary fix and should be
528 534 avoided in general.
529 535
530 536 The table below shows all the behaviors of the update command
531 537 given the -c and -C or no options, whether the working directory
532 538 is dirty, whether a revision is specified, and the relationship of
533 539 the parent rev to the target rev (linear, on the same named
534 540 branch, or on another named branch).
535 541
536 542 This logic is tested by test-update-branches.t.
537 543
538 544 -c -C dirty rev | linear same cross
539 545 n n n n | ok (1) x
540 546 n n n y | ok ok ok
541 547 n n y * | merge (2) (2)
542 548 n y * * | --- discard ---
543 549 y n y * | --- (3) ---
544 550 y n n * | --- ok ---
545 551 y y * * | --- (4) ---
546 552
547 553 x = can't happen
548 554 * = don't-care
549 555 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
550 556 2 = abort: crosses branches (use 'hg merge' to merge or
551 557 use 'hg update -C' to discard changes)
552 558 3 = abort: uncommitted local changes
553 559 4 = incompatible options (checked in commands.py)
554 560
555 561 Return the same tuple as applyupdates().
556 562 """
557 563
558 564 onode = node
559 565 wlock = repo.wlock()
560 566 try:
561 567 wc = repo[None]
562 568 if node is None:
563 569 # tip of current branch
564 570 try:
565 571 node = repo.branchtip(wc.branch())
566 572 except error.RepoLookupError:
567 573 if wc.branch() == "default": # no default branch!
568 574 node = repo.lookup("tip") # update to tip
569 575 else:
570 576 raise util.Abort(_("branch %s not found") % wc.branch())
571 577 overwrite = force and not branchmerge
572 578 pl = wc.parents()
573 579 p1, p2 = pl[0], repo[node]
574 580 if ancestor:
575 581 pa = repo[ancestor]
576 582 else:
577 583 pa = p1.ancestor(p2)
578 584
579 585 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
580 586
581 587 ### check phase
582 588 if not overwrite and len(pl) > 1:
583 589 raise util.Abort(_("outstanding uncommitted merges"))
584 590 if branchmerge:
585 591 if pa == p2:
586 592 raise util.Abort(_("merging with a working directory ancestor"
587 593 " has no effect"))
588 594 elif pa == p1:
589 595 if not mergeancestor and p1.branch() == p2.branch():
590 596 raise util.Abort(_("nothing to merge"),
591 597 hint=_("use 'hg update' "
592 598 "or check 'hg heads'"))
593 599 if not force and (wc.files() or wc.deleted()):
594 600 raise util.Abort(_("outstanding uncommitted changes"),
595 601 hint=_("use 'hg status' to list changes"))
596 602 for s in sorted(wc.substate):
597 603 if wc.sub(s).dirty():
598 604 raise util.Abort(_("outstanding uncommitted changes in "
599 605 "subrepository '%s'") % s)
600 606
601 607 elif not overwrite:
602 608 if pa == p1 or pa == p2: # linear
603 609 pass # all good
604 610 elif wc.dirty(missing=True):
605 611 raise util.Abort(_("crosses branches (merge branches or use"
606 612 " --clean to discard changes)"))
607 613 elif onode is None:
608 614 raise util.Abort(_("crosses branches (merge branches or update"
609 615 " --check to force update)"))
610 616 else:
611 617 # Allow jumping branches if clean and specific rev given
612 618 pa = p1
613 619
614 620 ### calculate phase
615 621 actions = calculateupdates(repo, wc, p2, pa,
616 622 branchmerge, force, partial)
617 623
618 624 ### apply phase
619 625 if not branchmerge: # just jump to the new rev
620 626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
621 627 if not partial:
622 628 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
623 629
624 630 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
625 631
626 632 if not partial:
627 633 repo.setparents(fp1, fp2)
628 634 recordupdates(repo, actions, branchmerge)
629 635 if not branchmerge:
630 636 repo.dirstate.setbranch(p2.branch())
631 637 finally:
632 638 wlock.release()
633 639
634 640 if not partial:
635 641 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
636 642 return stats
General Comments 0
You need to be logged in to leave comments. Login now