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