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