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