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