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