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