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