##// END OF EJS Templates
merge: remove "case" comments...
Mads Kiilerich -
r18339:aadefcee default
parent child Browse files
Show More
@@ -1,636 +1,636
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 p1.substate:
226 226 if p1.sub(s).dirty():
227 227 m1['.hgsubstate'] += "+"
228 228 break
229 229
230 230 # Compare manifests
231 231 for f, n in m1.iteritems():
232 232 if partial and not partial(f):
233 233 continue
234 234 if f in m2:
235 235 n2 = m2[f]
236 236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 237 nol = 'l' not in fl1 + fl2 + fla
238 238 a = ma.get(f, nullid)
239 239 if n == n2 and fl1 == fl2:
240 240 pass # same - keep local
241 241 elif n2 == a and fl2 == fla:
242 242 pass # remote unchanged - keep local
243 243 elif n == a and fl1 == fla: # local unchanged - use remote
244 244 if n == n2: # optimization: keep local content
245 245 act("update permissions", "e", f, fl2)
246 246 else:
247 247 act("remote is newer", "g", f, fl2)
248 248 elif nol and n2 == a: # remote only changed 'x'
249 249 act("update permissions", "e", f, fl2)
250 250 elif nol and n == a: # local only changed 'x'
251 251 act("remote is newer", "g", f, fl)
252 252 else: # both changed something
253 253 act("versions differ", "m", f, f, f, False)
254 254 elif f in copied: # files we'll deal with on m2 side
255 255 pass
256 256 elif f in movewithdir: # directory rename
257 257 f2 = movewithdir[f]
258 258 act("remote renamed directory to " + f2, "d", f, None, f2,
259 259 m1.flags(f))
260 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
260 elif f in copy:
261 261 f2 = copy[f]
262 262 act("local copied/moved to " + f2, "m", f, f2, f, False)
263 263 elif f in ma: # clean, a different, no remote
264 264 if n != ma[f]:
265 265 if repo.ui.promptchoice(
266 266 _(" local changed %s which remote deleted\n"
267 267 "use (c)hanged version or (d)elete?") % f,
268 268 (_("&Changed"), _("&Delete")), 0):
269 269 act("prompt delete", "r", f)
270 270 else:
271 271 act("prompt keep", "a", f)
272 272 elif n[20:] == "a": # added, no remote
273 273 act("remote deleted", "f", f)
274 274 else:
275 275 act("other deleted", "r", f)
276 276
277 277 for f, n in m2.iteritems():
278 278 if partial and not partial(f):
279 279 continue
280 280 if f in m1 or f in copied: # files already visited
281 281 continue
282 282 if f in movewithdir:
283 283 f2 = movewithdir[f]
284 284 act("local renamed directory to " + f2, "d", None, f, f2,
285 285 m2.flags(f))
286 286 elif f in copy:
287 287 f2 = copy[f]
288 if f2 in m2: # rename case 1, A/A,B/A
288 if f2 in m2:
289 289 act("remote copied to " + f, "m",
290 290 f2, f, f, False)
291 else: # case 3,20 A/B/A
291 else:
292 292 act("remote moved to " + f, "m",
293 293 f2, f, f, True)
294 294 elif f not in ma:
295 295 if (not overwrite
296 296 and _checkunknownfile(repo, p1, p2, f)):
297 297 act("remote differs from untracked local",
298 298 "m", f, f, f, False)
299 299 else:
300 300 act("remote created", "g", f, m2.flags(f))
301 301 elif n != ma[f]:
302 302 if repo.ui.promptchoice(
303 303 _("remote changed %s which local deleted\n"
304 304 "use (c)hanged version or leave (d)eleted?") % f,
305 305 (_("&Changed"), _("&Deleted")), 0) == 0:
306 306 act("prompt recreating", "g", f, m2.flags(f))
307 307
308 308 return actions
309 309
310 310 def actionkey(a):
311 311 return a[1] == "r" and -1 or 0, a
312 312
313 313 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
314 314 """apply the merge action list to the working directory
315 315
316 316 wctx is the working copy context
317 317 mctx is the context to be merged into the working copy
318 318 actx is the context of the common ancestor
319 319
320 320 Return a tuple of counts (updated, merged, removed, unresolved) that
321 321 describes how many files were affected by the update.
322 322 """
323 323
324 324 updated, merged, removed, unresolved = 0, 0, 0, 0
325 325 ms = mergestate(repo)
326 326 ms.reset(wctx.p1().node())
327 327 moves = []
328 328 actions.sort(key=actionkey)
329 329
330 330 # prescan for merges
331 331 for a in actions:
332 332 f, m = a[:2]
333 333 if m == "m": # merge
334 334 f2, fd, move = a[2:]
335 335 if fd == '.hgsubstate': # merged internally
336 336 continue
337 337 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
338 338 fcl = wctx[f]
339 339 fco = mctx[f2]
340 340 if mctx == actx: # backwards, use working dir parent as ancestor
341 341 if fcl.parents():
342 342 fca = fcl.p1()
343 343 else:
344 344 fca = repo.filectx(f, fileid=nullrev)
345 345 else:
346 346 fca = fcl.ancestor(fco, actx)
347 347 if not fca:
348 348 fca = repo.filectx(f, fileid=nullrev)
349 349 ms.add(fcl, fco, fca, fd)
350 350 if f != fd and move:
351 351 moves.append(f)
352 352
353 353 audit = repo.wopener.audit
354 354
355 355 # remove renamed files after safely stored
356 356 for f in moves:
357 357 if os.path.lexists(repo.wjoin(f)):
358 358 repo.ui.debug("removing %s\n" % f)
359 359 audit(f)
360 360 util.unlinkpath(repo.wjoin(f))
361 361
362 362 numupdates = len(actions)
363 363 for i, a in enumerate(actions):
364 364 f, m = a[:2]
365 365 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
366 366 unit=_('files'))
367 367 if m == "r": # remove
368 368 repo.ui.note(_("removing %s\n") % f)
369 369 audit(f)
370 370 if f == '.hgsubstate': # subrepo states need updating
371 371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
372 372 try:
373 373 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
374 374 except OSError, inst:
375 375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 376 (f, inst.strerror))
377 377 removed += 1
378 378 elif m == "m": # merge
379 379 if fd == '.hgsubstate': # subrepo states need updating
380 380 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
381 381 overwrite)
382 382 continue
383 383 f2, fd, move = a[2:]
384 384 audit(fd)
385 385 r = ms.resolve(fd, wctx, mctx)
386 386 if r is not None and r > 0:
387 387 unresolved += 1
388 388 else:
389 389 if r is None:
390 390 updated += 1
391 391 else:
392 392 merged += 1
393 393 elif m == "g": # get
394 394 flags = a[2]
395 395 repo.ui.note(_("getting %s\n") % f)
396 396 repo.wwrite(f, mctx.filectx(f).data(), flags)
397 397 updated += 1
398 398 if f == '.hgsubstate': # subrepo states need updating
399 399 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
400 400 elif m == "d": # directory rename
401 401 f2, fd, flags = a[2:]
402 402 if f:
403 403 repo.ui.note(_("moving %s to %s\n") % (f, fd))
404 404 audit(f)
405 405 repo.wwrite(fd, wctx.filectx(f).data(), flags)
406 406 util.unlinkpath(repo.wjoin(f))
407 407 if f2:
408 408 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
409 409 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
410 410 updated += 1
411 411 elif m == "dr": # divergent renames
412 412 fl = a[2]
413 413 repo.ui.warn(_("note: possible conflict - %s was renamed "
414 414 "multiple times to:\n") % f)
415 415 for nf in fl:
416 416 repo.ui.warn(" %s\n" % nf)
417 417 elif m == "rd": # rename and delete
418 418 fl = a[2]
419 419 repo.ui.warn(_("note: possible conflict - %s was deleted "
420 420 "and renamed to:\n") % f)
421 421 for nf in fl:
422 422 repo.ui.warn(" %s\n" % nf)
423 423 elif m == "e": # exec
424 424 flags = a[2]
425 425 audit(f)
426 426 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
427 427 updated += 1
428 428 ms.commit()
429 429 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
430 430
431 431 return updated, merged, removed, unresolved
432 432
433 433 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
434 434 "Calculate the actions needed to merge mctx into tctx"
435 435 actions = []
436 436 folding = not util.checkcase(repo.path)
437 437 if folding:
438 438 # collision check is not needed for clean update
439 439 if (not branchmerge and
440 440 (force or not tctx.dirty(missing=True, branch=False))):
441 441 _checkcollision(mctx, None)
442 442 else:
443 443 _checkcollision(mctx, (tctx, ancestor))
444 444 if not force:
445 445 _checkunknown(repo, tctx, mctx)
446 446 if tctx.rev() is None:
447 447 actions += _forgetremoved(tctx, mctx, branchmerge)
448 448 actions += manifestmerge(repo, tctx, mctx,
449 449 ancestor,
450 450 force and not branchmerge,
451 451 partial)
452 452 return actions
453 453
454 454 def recordupdates(repo, actions, branchmerge):
455 455 "record merge actions to the dirstate"
456 456
457 457 for a in actions:
458 458 f, m = a[:2]
459 459 if m == "r": # remove
460 460 if branchmerge:
461 461 repo.dirstate.remove(f)
462 462 else:
463 463 repo.dirstate.drop(f)
464 464 elif m == "a": # re-add
465 465 if not branchmerge:
466 466 repo.dirstate.add(f)
467 467 elif m == "f": # forget
468 468 repo.dirstate.drop(f)
469 469 elif m == "e": # exec change
470 470 repo.dirstate.normallookup(f)
471 471 elif m == "g": # get
472 472 if branchmerge:
473 473 repo.dirstate.otherparent(f)
474 474 else:
475 475 repo.dirstate.normal(f)
476 476 elif m == "m": # merge
477 477 f2, fd, move = a[2:]
478 478 if branchmerge:
479 479 # We've done a branch merge, mark this file as merged
480 480 # so that we properly record the merger later
481 481 repo.dirstate.merge(fd)
482 482 if f != f2: # copy/rename
483 483 if move:
484 484 repo.dirstate.remove(f)
485 485 if f != fd:
486 486 repo.dirstate.copy(f, fd)
487 487 else:
488 488 repo.dirstate.copy(f2, fd)
489 489 else:
490 490 # We've update-merged a locally modified file, so
491 491 # we set the dirstate to emulate a normal checkout
492 492 # of that file some time in the past. Thus our
493 493 # merge will appear as a normal local file
494 494 # modification.
495 495 if f2 == fd: # file not locally copied/moved
496 496 repo.dirstate.normallookup(fd)
497 497 if move:
498 498 repo.dirstate.drop(f)
499 499 elif m == "d": # directory rename
500 500 f2, fd, flag = a[2:]
501 501 if not f2 and f not in repo.dirstate:
502 502 # untracked file moved
503 503 continue
504 504 if branchmerge:
505 505 repo.dirstate.add(fd)
506 506 if f:
507 507 repo.dirstate.remove(f)
508 508 repo.dirstate.copy(f, fd)
509 509 if f2:
510 510 repo.dirstate.copy(f2, fd)
511 511 else:
512 512 repo.dirstate.normal(fd)
513 513 if f:
514 514 repo.dirstate.drop(f)
515 515
516 516 def update(repo, node, branchmerge, force, partial, ancestor=None,
517 517 mergeancestor=False):
518 518 """
519 519 Perform a merge between the working directory and the given node
520 520
521 521 node = the node to update to, or None if unspecified
522 522 branchmerge = whether to merge between branches
523 523 force = whether to force branch merging or file overwriting
524 524 partial = a function to filter file lists (dirstate not updated)
525 525 mergeancestor = if false, merging with an ancestor (fast-forward)
526 526 is only allowed between different named branches. This flag
527 527 is used by rebase extension as a temporary fix and should be
528 528 avoided in general.
529 529
530 530 The table below shows all the behaviors of the update command
531 531 given the -c and -C or no options, whether the working directory
532 532 is dirty, whether a revision is specified, and the relationship of
533 533 the parent rev to the target rev (linear, on the same named
534 534 branch, or on another named branch).
535 535
536 536 This logic is tested by test-update-branches.t.
537 537
538 538 -c -C dirty rev | linear same cross
539 539 n n n n | ok (1) x
540 540 n n n y | ok ok ok
541 541 n n y * | merge (2) (2)
542 542 n y * * | --- discard ---
543 543 y n y * | --- (3) ---
544 544 y n n * | --- ok ---
545 545 y y * * | --- (4) ---
546 546
547 547 x = can't happen
548 548 * = don't-care
549 549 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
550 550 2 = abort: crosses branches (use 'hg merge' to merge or
551 551 use 'hg update -C' to discard changes)
552 552 3 = abort: uncommitted local changes
553 553 4 = incompatible options (checked in commands.py)
554 554
555 555 Return the same tuple as applyupdates().
556 556 """
557 557
558 558 onode = node
559 559 wlock = repo.wlock()
560 560 try:
561 561 wc = repo[None]
562 562 if node is None:
563 563 # tip of current branch
564 564 try:
565 565 node = repo.branchtip(wc.branch())
566 566 except error.RepoLookupError:
567 567 if wc.branch() == "default": # no default branch!
568 568 node = repo.lookup("tip") # update to tip
569 569 else:
570 570 raise util.Abort(_("branch %s not found") % wc.branch())
571 571 overwrite = force and not branchmerge
572 572 pl = wc.parents()
573 573 p1, p2 = pl[0], repo[node]
574 574 if ancestor:
575 575 pa = repo[ancestor]
576 576 else:
577 577 pa = p1.ancestor(p2)
578 578
579 579 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
580 580
581 581 ### check phase
582 582 if not overwrite and len(pl) > 1:
583 583 raise util.Abort(_("outstanding uncommitted merges"))
584 584 if branchmerge:
585 585 if pa == p2:
586 586 raise util.Abort(_("merging with a working directory ancestor"
587 587 " has no effect"))
588 588 elif pa == p1:
589 589 if not mergeancestor and p1.branch() == p2.branch():
590 590 raise util.Abort(_("nothing to merge"),
591 591 hint=_("use 'hg update' "
592 592 "or check 'hg heads'"))
593 593 if not force and (wc.files() or wc.deleted()):
594 594 raise util.Abort(_("outstanding uncommitted changes"),
595 595 hint=_("use 'hg status' to list changes"))
596 596 for s in wc.substate:
597 597 if wc.sub(s).dirty():
598 598 raise util.Abort(_("outstanding uncommitted changes in "
599 599 "subrepository '%s'") % s)
600 600
601 601 elif not overwrite:
602 602 if pa == p1 or pa == p2: # linear
603 603 pass # all good
604 604 elif wc.dirty(missing=True):
605 605 raise util.Abort(_("crosses branches (merge branches or use"
606 606 " --clean to discard changes)"))
607 607 elif onode is None:
608 608 raise util.Abort(_("crosses branches (merge branches or update"
609 609 " --check to force update)"))
610 610 else:
611 611 # Allow jumping branches if clean and specific rev given
612 612 pa = p1
613 613
614 614 ### calculate phase
615 615 actions = calculateupdates(repo, wc, p2, pa,
616 616 branchmerge, force, partial)
617 617
618 618 ### apply phase
619 619 if not branchmerge: # just jump to the new rev
620 620 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
621 621 if not partial:
622 622 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
623 623
624 624 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
625 625
626 626 if not partial:
627 627 repo.setparents(fp1, fp2)
628 628 recordupdates(repo, actions, branchmerge)
629 629 if not branchmerge:
630 630 repo.dirstate.setbranch(p2.branch())
631 631 finally:
632 632 wlock.release()
633 633
634 634 if not partial:
635 635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
636 636 return stats
General Comments 0
You need to be logged in to leave comments. Login now