##// END OF EJS Templates
merge: .hgsubstate is special as merge destination, not as merge source
Mads Kiilerich -
r18332:6ba58ab7 default
parent child Browse files
Show More
@@ -1,651 +1,651
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 if f == '.hgsubstate': # merged internally
345 if fd == '.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 if f == '.hgsubstate': # subrepo states need updating
391 if fd == '.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 405 elif m == "g": # get
406 406 flags = a[2]
407 407 repo.ui.note(_("getting %s\n") % f)
408 408 t = mctx.filectx(f).data()
409 409 repo.wwrite(f, t, flags)
410 410 t = None
411 411 updated += 1
412 412 if f == '.hgsubstate': # subrepo states need updating
413 413 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
414 414 elif m == "d": # directory rename
415 415 f2, fd, flags = a[2:]
416 416 if f:
417 417 repo.ui.note(_("moving %s to %s\n") % (f, fd))
418 418 audit(f)
419 419 t = wctx.filectx(f).data()
420 420 repo.wwrite(fd, t, flags)
421 421 util.unlinkpath(repo.wjoin(f))
422 422 if f2:
423 423 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
424 424 t = mctx.filectx(f2).data()
425 425 repo.wwrite(fd, t, flags)
426 426 updated += 1
427 427 elif m == "dr": # divergent renames
428 428 fl = a[2]
429 429 repo.ui.warn(_("note: possible conflict - %s was renamed "
430 430 "multiple times to:\n") % f)
431 431 for nf in fl:
432 432 repo.ui.warn(" %s\n" % nf)
433 433 elif m == "rd": # rename and delete
434 434 fl = a[2]
435 435 repo.ui.warn(_("note: possible conflict - %s was deleted "
436 436 "and renamed to:\n") % f)
437 437 for nf in fl:
438 438 repo.ui.warn(" %s\n" % nf)
439 439 elif m == "e": # exec
440 440 flags = a[2]
441 441 audit(f)
442 442 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
443 443 ms.commit()
444 444 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
445 445
446 446 return updated, merged, removed, unresolved
447 447
448 448 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
449 449 "Calculate the actions needed to merge mctx into tctx"
450 450 actions = []
451 451 folding = not util.checkcase(repo.path)
452 452 if folding:
453 453 # collision check is not needed for clean update
454 454 if (not branchmerge and
455 455 (force or not tctx.dirty(missing=True, branch=False))):
456 456 _checkcollision(mctx, None)
457 457 else:
458 458 _checkcollision(mctx, (tctx, ancestor))
459 459 if not force:
460 460 _checkunknown(repo, tctx, mctx)
461 461 if tctx.rev() is None:
462 462 actions += _forgetremoved(tctx, mctx, branchmerge)
463 463 actions += manifestmerge(repo, tctx, mctx,
464 464 ancestor,
465 465 force and not branchmerge,
466 466 partial)
467 467 return actions
468 468
469 469 def recordupdates(repo, actions, branchmerge):
470 470 "record merge actions to the dirstate"
471 471
472 472 for a in actions:
473 473 f, m = a[:2]
474 474 if m == "r": # remove
475 475 if branchmerge:
476 476 repo.dirstate.remove(f)
477 477 else:
478 478 repo.dirstate.drop(f)
479 479 elif m == "a": # re-add
480 480 if not branchmerge:
481 481 repo.dirstate.add(f)
482 482 elif m == "f": # forget
483 483 repo.dirstate.drop(f)
484 484 elif m == "e": # exec change
485 485 repo.dirstate.normallookup(f)
486 486 elif m == "g": # get
487 487 if branchmerge:
488 488 repo.dirstate.otherparent(f)
489 489 else:
490 490 repo.dirstate.normal(f)
491 491 elif m == "m": # merge
492 492 f2, fd, flag, move = a[2:]
493 493 if branchmerge:
494 494 # We've done a branch merge, mark this file as merged
495 495 # so that we properly record the merger later
496 496 repo.dirstate.merge(fd)
497 497 if f != f2: # copy/rename
498 498 if move:
499 499 repo.dirstate.remove(f)
500 500 if f != fd:
501 501 repo.dirstate.copy(f, fd)
502 502 else:
503 503 repo.dirstate.copy(f2, fd)
504 504 else:
505 505 # We've update-merged a locally modified file, so
506 506 # we set the dirstate to emulate a normal checkout
507 507 # of that file some time in the past. Thus our
508 508 # merge will appear as a normal local file
509 509 # modification.
510 510 if f2 == fd: # file not locally copied/moved
511 511 repo.dirstate.normallookup(fd)
512 512 if move:
513 513 repo.dirstate.drop(f)
514 514 elif m == "d": # directory rename
515 515 f2, fd, flag = a[2:]
516 516 if not f2 and f not in repo.dirstate:
517 517 # untracked file moved
518 518 continue
519 519 if branchmerge:
520 520 repo.dirstate.add(fd)
521 521 if f:
522 522 repo.dirstate.remove(f)
523 523 repo.dirstate.copy(f, fd)
524 524 if f2:
525 525 repo.dirstate.copy(f2, fd)
526 526 else:
527 527 repo.dirstate.normal(fd)
528 528 if f:
529 529 repo.dirstate.drop(f)
530 530
531 531 def update(repo, node, branchmerge, force, partial, ancestor=None,
532 532 mergeancestor=False):
533 533 """
534 534 Perform a merge between the working directory and the given node
535 535
536 536 node = the node to update to, or None if unspecified
537 537 branchmerge = whether to merge between branches
538 538 force = whether to force branch merging or file overwriting
539 539 partial = a function to filter file lists (dirstate not updated)
540 540 mergeancestor = if false, merging with an ancestor (fast-forward)
541 541 is only allowed between different named branches. This flag
542 542 is used by rebase extension as a temporary fix and should be
543 543 avoided in general.
544 544
545 545 The table below shows all the behaviors of the update command
546 546 given the -c and -C or no options, whether the working directory
547 547 is dirty, whether a revision is specified, and the relationship of
548 548 the parent rev to the target rev (linear, on the same named
549 549 branch, or on another named branch).
550 550
551 551 This logic is tested by test-update-branches.t.
552 552
553 553 -c -C dirty rev | linear same cross
554 554 n n n n | ok (1) x
555 555 n n n y | ok ok ok
556 556 n n y * | merge (2) (2)
557 557 n y * * | --- discard ---
558 558 y n y * | --- (3) ---
559 559 y n n * | --- ok ---
560 560 y y * * | --- (4) ---
561 561
562 562 x = can't happen
563 563 * = don't-care
564 564 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
565 565 2 = abort: crosses branches (use 'hg merge' to merge or
566 566 use 'hg update -C' to discard changes)
567 567 3 = abort: uncommitted local changes
568 568 4 = incompatible options (checked in commands.py)
569 569
570 570 Return the same tuple as applyupdates().
571 571 """
572 572
573 573 onode = node
574 574 wlock = repo.wlock()
575 575 try:
576 576 wc = repo[None]
577 577 if node is None:
578 578 # tip of current branch
579 579 try:
580 580 node = repo.branchtip(wc.branch())
581 581 except error.RepoLookupError:
582 582 if wc.branch() == "default": # no default branch!
583 583 node = repo.lookup("tip") # update to tip
584 584 else:
585 585 raise util.Abort(_("branch %s not found") % wc.branch())
586 586 overwrite = force and not branchmerge
587 587 pl = wc.parents()
588 588 p1, p2 = pl[0], repo[node]
589 589 if ancestor:
590 590 pa = repo[ancestor]
591 591 else:
592 592 pa = p1.ancestor(p2)
593 593
594 594 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
595 595
596 596 ### check phase
597 597 if not overwrite and len(pl) > 1:
598 598 raise util.Abort(_("outstanding uncommitted merges"))
599 599 if branchmerge:
600 600 if pa == p2:
601 601 raise util.Abort(_("merging with a working directory ancestor"
602 602 " has no effect"))
603 603 elif pa == p1:
604 604 if not mergeancestor and p1.branch() == p2.branch():
605 605 raise util.Abort(_("nothing to merge"),
606 606 hint=_("use 'hg update' "
607 607 "or check 'hg heads'"))
608 608 if not force and (wc.files() or wc.deleted()):
609 609 raise util.Abort(_("outstanding uncommitted changes"),
610 610 hint=_("use 'hg status' to list changes"))
611 611 for s in wc.substate:
612 612 if wc.sub(s).dirty():
613 613 raise util.Abort(_("outstanding uncommitted changes in "
614 614 "subrepository '%s'") % s)
615 615
616 616 elif not overwrite:
617 617 if pa == p1 or pa == p2: # linear
618 618 pass # all good
619 619 elif wc.dirty(missing=True):
620 620 raise util.Abort(_("crosses branches (merge branches or use"
621 621 " --clean to discard changes)"))
622 622 elif onode is None:
623 623 raise util.Abort(_("crosses branches (merge branches or update"
624 624 " --check to force update)"))
625 625 else:
626 626 # Allow jumping branches if clean and specific rev given
627 627 pa = p1
628 628
629 629 ### calculate phase
630 630 actions = calculateupdates(repo, wc, p2, pa,
631 631 branchmerge, force, partial)
632 632
633 633 ### apply phase
634 634 if not branchmerge: # just jump to the new rev
635 635 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
636 636 if not partial:
637 637 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
638 638
639 639 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
640 640
641 641 if not partial:
642 642 repo.setparents(fp1, fp2)
643 643 recordupdates(repo, actions, branchmerge)
644 644 if not branchmerge:
645 645 repo.dirstate.setbranch(p2.branch())
646 646 finally:
647 647 wlock.release()
648 648
649 649 if not partial:
650 650 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
651 651 return stats
General Comments 0
You need to be logged in to leave comments. Login now