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