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