##// END OF EJS Templates
merge.mergestate: add a way to get the merge driver state...
Siddharth Agarwal -
r26766:a83110fa default
parent child Browse files
Show More
@@ -1,1289 +1,1292 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 __future__ import absolute_import
9 9
10 10 import errno
11 11 import os
12 12 import shutil
13 13 import struct
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 bin,
18 18 hex,
19 19 nullid,
20 20 nullrev,
21 21 )
22 22 from . import (
23 23 copies,
24 24 destutil,
25 25 error,
26 26 filemerge,
27 27 obsolete,
28 28 subrepo,
29 29 util,
30 30 worker,
31 31 )
32 32
33 33 _pack = struct.pack
34 34 _unpack = struct.unpack
35 35
36 36 def _droponode(data):
37 37 # used for compatibility for v1
38 38 bits = data.split('\0')
39 39 bits = bits[:-2] + bits[-1:]
40 40 return '\0'.join(bits)
41 41
42 42 class mergestate(object):
43 43 '''track 3-way merge state of individual files
44 44
45 45 it is stored on disk when needed. Two file are used, one with an old
46 46 format, one with a new format. Both contains similar data, but the new
47 47 format can store new kinds of field.
48 48
49 49 Current new format is a list of arbitrary record of the form:
50 50
51 51 [type][length][content]
52 52
53 53 Type is a single character, length is a 4 bytes integer, content is an
54 54 arbitrary suites of bytes of length `length`.
55 55
56 56 Type should be a letter. Capital letter are mandatory record, Mercurial
57 57 should abort if they are unknown. lower case record can be safely ignored.
58 58
59 59 Currently known record:
60 60
61 61 L: the node of the "local" part of the merge (hexified version)
62 62 O: the node of the "other" part of the merge (hexified version)
63 63 F: a file to be merged entry
64 64 D: a file that the external merge driver will merge internally
65 65 (experimental)
66 66 m: the external merge driver defined for this merge plus its run state
67 67 (experimental)
68 68
69 69 Merge driver run states (experimental):
70 70 u: driver-resolved files unmarked -- needs to be run next time we're about
71 71 to resolve or commit
72 72 m: driver-resolved files marked -- only needs to be run before commit
73 73 s: success/skipped -- does not need to be run any more
74 74 '''
75 75 statepathv1 = 'merge/state'
76 76 statepathv2 = 'merge/state2'
77 77
78 78 def __init__(self, repo):
79 79 self._repo = repo
80 80 self._dirty = False
81 81 self._read()
82 82
83 83 def reset(self, node=None, other=None):
84 84 self._state = {}
85 85 self._local = None
86 86 self._other = None
87 87 if 'otherctx' in vars(self):
88 88 del self.otherctx
89 89 if node:
90 90 self._local = node
91 91 self._other = other
92 92 self._mdstate = 'u'
93 93 shutil.rmtree(self._repo.join('merge'), True)
94 94 self._dirty = False
95 95
96 96 def _read(self):
97 97 """Analyse each record content to restore a serialized state from disk
98 98
99 99 This function process "record" entry produced by the de-serialization
100 100 of on disk file.
101 101 """
102 102 self._state = {}
103 103 self._local = None
104 104 self._other = None
105 105 self._mdstate = 'u'
106 106 if 'otherctx' in vars(self):
107 107 del self.otherctx
108 108 records = self._readrecords()
109 109 for rtype, record in records:
110 110 if rtype == 'L':
111 111 self._local = bin(record)
112 112 elif rtype == 'O':
113 113 self._other = bin(record)
114 114 elif rtype == 'm':
115 115 bits = record.split('\0', 1)
116 116 mdstate = bits[1]
117 117 if len(mdstate) != 1 or mdstate not in 'ums':
118 118 # the merge driver should be idempotent, so just rerun it
119 119 mdstate = 'u'
120 120
121 121 # protect against the following:
122 122 # - A configures a malicious merge driver in their hgrc, then
123 123 # pauses the merge
124 124 # - A edits their hgrc to remove references to the merge driver
125 125 # - A gives a copy of their entire repo, including .hg, to B
126 126 # - B inspects .hgrc and finds it to be clean
127 127 # - B then continues the merge and the malicious merge driver
128 128 # gets invoked
129 129 if self.mergedriver != bits[0]:
130 130 raise error.ConfigError(
131 131 _("merge driver changed since merge started"),
132 132 hint=_("revert merge driver change or abort merge"))
133 133 self._mdstate = mdstate
134 134 elif rtype in 'FD':
135 135 bits = record.split('\0')
136 136 self._state[bits[0]] = bits[1:]
137 137 elif not rtype.islower():
138 138 raise error.Abort(_('unsupported merge state record: %s')
139 139 % rtype)
140 140 self._dirty = False
141 141
142 142 def _readrecords(self):
143 143 """Read merge state from disk and return a list of record (TYPE, data)
144 144
145 145 We read data from both v1 and v2 files and decide which one to use.
146 146
147 147 V1 has been used by version prior to 2.9.1 and contains less data than
148 148 v2. We read both versions and check if no data in v2 contradicts
149 149 v1. If there is not contradiction we can safely assume that both v1
150 150 and v2 were written at the same time and use the extract data in v2. If
151 151 there is contradiction we ignore v2 content as we assume an old version
152 152 of Mercurial has overwritten the mergestate file and left an old v2
153 153 file around.
154 154
155 155 returns list of record [(TYPE, data), ...]"""
156 156 v1records = self._readrecordsv1()
157 157 v2records = self._readrecordsv2()
158 158 if self._v1v2match(v1records, v2records):
159 159 return v2records
160 160 else:
161 161 # v1 file is newer than v2 file, use it
162 162 # we have to infer the "other" changeset of the merge
163 163 # we cannot do better than that with v1 of the format
164 164 mctx = self._repo[None].parents()[-1]
165 165 v1records.append(('O', mctx.hex()))
166 166 # add place holder "other" file node information
167 167 # nobody is using it yet so we do no need to fetch the data
168 168 # if mctx was wrong `mctx[bits[-2]]` may fails.
169 169 for idx, r in enumerate(v1records):
170 170 if r[0] == 'F':
171 171 bits = r[1].split('\0')
172 172 bits.insert(-2, '')
173 173 v1records[idx] = (r[0], '\0'.join(bits))
174 174 return v1records
175 175
176 176 def _v1v2match(self, v1records, v2records):
177 177 oldv2 = set() # old format version of v2 record
178 178 for rec in v2records:
179 179 if rec[0] == 'L':
180 180 oldv2.add(rec)
181 181 elif rec[0] == 'F':
182 182 # drop the onode data (not contained in v1)
183 183 oldv2.add(('F', _droponode(rec[1])))
184 184 for rec in v1records:
185 185 if rec not in oldv2:
186 186 return False
187 187 else:
188 188 return True
189 189
190 190 def _readrecordsv1(self):
191 191 """read on disk merge state for version 1 file
192 192
193 193 returns list of record [(TYPE, data), ...]
194 194
195 195 Note: the "F" data from this file are one entry short
196 196 (no "other file node" entry)
197 197 """
198 198 records = []
199 199 try:
200 200 f = self._repo.vfs(self.statepathv1)
201 201 for i, l in enumerate(f):
202 202 if i == 0:
203 203 records.append(('L', l[:-1]))
204 204 else:
205 205 records.append(('F', l[:-1]))
206 206 f.close()
207 207 except IOError as err:
208 208 if err.errno != errno.ENOENT:
209 209 raise
210 210 return records
211 211
212 212 def _readrecordsv2(self):
213 213 """read on disk merge state for version 2 file
214 214
215 215 returns list of record [(TYPE, data), ...]
216 216 """
217 217 records = []
218 218 try:
219 219 f = self._repo.vfs(self.statepathv2)
220 220 data = f.read()
221 221 off = 0
222 222 end = len(data)
223 223 while off < end:
224 224 rtype = data[off]
225 225 off += 1
226 226 length = _unpack('>I', data[off:(off + 4)])[0]
227 227 off += 4
228 228 record = data[off:(off + length)]
229 229 off += length
230 230 records.append((rtype, record))
231 231 f.close()
232 232 except IOError as err:
233 233 if err.errno != errno.ENOENT:
234 234 raise
235 235 return records
236 236
237 237 @util.propertycache
238 238 def mergedriver(self):
239 239 return self._repo.ui.config('experimental', 'mergedriver')
240 240 @util.propertycache
241 241 def otherctx(self):
242 242 return self._repo[self._other]
243 243
244 244 def active(self):
245 245 """Whether mergestate is active.
246 246
247 247 Returns True if there appears to be mergestate. This is a rough proxy
248 248 for "is a merge in progress."
249 249 """
250 250 # Check local variables before looking at filesystem for performance
251 251 # reasons.
252 252 return bool(self._local) or bool(self._state) or \
253 253 self._repo.vfs.exists(self.statepathv1) or \
254 254 self._repo.vfs.exists(self.statepathv2)
255 255
256 256 def commit(self):
257 257 """Write current state on disk (if necessary)"""
258 258 if self._dirty:
259 259 records = []
260 260 records.append(('L', hex(self._local)))
261 261 records.append(('O', hex(self._other)))
262 262 if self.mergedriver:
263 263 records.append(('m', '\0'.join([
264 264 self.mergedriver, self._mdstate])))
265 265 for d, v in self._state.iteritems():
266 266 if v[0] == 'd':
267 267 records.append(('D', '\0'.join([d] + v)))
268 268 else:
269 269 records.append(('F', '\0'.join([d] + v)))
270 270 self._writerecords(records)
271 271 self._dirty = False
272 272
273 273 def _writerecords(self, records):
274 274 """Write current state on disk (both v1 and v2)"""
275 275 self._writerecordsv1(records)
276 276 self._writerecordsv2(records)
277 277
278 278 def _writerecordsv1(self, records):
279 279 """Write current state on disk in a version 1 file"""
280 280 f = self._repo.vfs(self.statepathv1, 'w')
281 281 irecords = iter(records)
282 282 lrecords = irecords.next()
283 283 assert lrecords[0] == 'L'
284 284 f.write(hex(self._local) + '\n')
285 285 for rtype, data in irecords:
286 286 if rtype == 'F':
287 287 f.write('%s\n' % _droponode(data))
288 288 f.close()
289 289
290 290 def _writerecordsv2(self, records):
291 291 """Write current state on disk in a version 2 file"""
292 292 f = self._repo.vfs(self.statepathv2, 'w')
293 293 for key, data in records:
294 294 assert len(key) == 1
295 295 format = '>sI%is' % len(data)
296 296 f.write(_pack(format, key, len(data), data))
297 297 f.close()
298 298
299 299 def add(self, fcl, fco, fca, fd):
300 300 """add a new (potentially?) conflicting file the merge state
301 301 fcl: file context for local,
302 302 fco: file context for remote,
303 303 fca: file context for ancestors,
304 304 fd: file path of the resulting merge.
305 305
306 306 note: also write the local version to the `.hg/merge` directory.
307 307 """
308 308 hash = util.sha1(fcl.path()).hexdigest()
309 309 self._repo.vfs.write('merge/' + hash, fcl.data())
310 310 self._state[fd] = ['u', hash, fcl.path(),
311 311 fca.path(), hex(fca.filenode()),
312 312 fco.path(), hex(fco.filenode()),
313 313 fcl.flags()]
314 314 self._dirty = True
315 315
316 316 def __contains__(self, dfile):
317 317 return dfile in self._state
318 318
319 319 def __getitem__(self, dfile):
320 320 return self._state[dfile][0]
321 321
322 322 def __iter__(self):
323 323 return iter(sorted(self._state))
324 324
325 325 def files(self):
326 326 return self._state.keys()
327 327
328 328 def mark(self, dfile, state):
329 329 self._state[dfile][0] = state
330 330 self._dirty = True
331 331
332 def mdstate(self):
333 return self._mdstate
334
332 335 def unresolved(self):
333 336 """Obtain the paths of unresolved files."""
334 337
335 338 for f, entry in self._state.items():
336 339 if entry[0] == 'u':
337 340 yield f
338 341
339 342 def driverresolved(self):
340 343 """Obtain the paths of driver-resolved files."""
341 344
342 345 for f, entry in self._state.items():
343 346 if entry[0] == 'd':
344 347 yield f
345 348
346 349 def _resolve(self, preresolve, dfile, wctx, labels=None):
347 350 """rerun merge process for file path `dfile`"""
348 351 if self[dfile] in 'rd':
349 352 return True, 0
350 353 stateentry = self._state[dfile]
351 354 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
352 355 octx = self._repo[self._other]
353 356 fcd = wctx[dfile]
354 357 fco = octx[ofile]
355 358 fca = self._repo.filectx(afile, fileid=anode)
356 359 # "premerge" x flags
357 360 flo = fco.flags()
358 361 fla = fca.flags()
359 362 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
360 363 if fca.node() == nullid:
361 364 if preresolve:
362 365 self._repo.ui.warn(
363 366 _('warning: cannot merge flags for %s\n') % afile)
364 367 elif flags == fla:
365 368 flags = flo
366 369 if preresolve:
367 370 # restore local
368 371 f = self._repo.vfs('merge/' + hash)
369 372 self._repo.wwrite(dfile, f.read(), flags)
370 373 f.close()
371 374 complete, r = filemerge.premerge(self._repo, self._local, lfile,
372 375 fcd, fco, fca, labels=labels)
373 376 else:
374 377 complete, r = filemerge.filemerge(self._repo, self._local, lfile,
375 378 fcd, fco, fca, labels=labels)
376 379 if r is None:
377 380 # no real conflict
378 381 del self._state[dfile]
379 382 self._dirty = True
380 383 elif not r:
381 384 self.mark(dfile, 'r')
382 385 return complete, r
383 386
384 387 def preresolve(self, dfile, wctx, labels=None):
385 388 return self._resolve(True, dfile, wctx, labels=labels)
386 389
387 390 def resolve(self, dfile, wctx, labels=None):
388 391 """rerun merge process for file path `dfile`"""
389 392 return self._resolve(False, dfile, wctx, labels=labels)[1]
390 393
391 394 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
392 395 if f2 is None:
393 396 f2 = f
394 397 return (os.path.isfile(repo.wjoin(f))
395 398 and repo.wvfs.audit.check(f)
396 399 and repo.dirstate.normalize(f) not in repo.dirstate
397 400 and mctx[f2].cmp(wctx[f]))
398 401
399 402 def _checkunknownfiles(repo, wctx, mctx, force, actions):
400 403 """
401 404 Considers any actions that care about the presence of conflicting unknown
402 405 files. For some actions, the result is to abort; for others, it is to
403 406 choose a different action.
404 407 """
405 408 aborts = []
406 409 if not force:
407 410 for f, (m, args, msg) in actions.iteritems():
408 411 if m in ('c', 'dc'):
409 412 if _checkunknownfile(repo, wctx, mctx, f):
410 413 aborts.append(f)
411 414 elif m == 'dg':
412 415 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
413 416 aborts.append(f)
414 417
415 418 for f in sorted(aborts):
416 419 repo.ui.warn(_("%s: untracked file differs\n") % f)
417 420 if aborts:
418 421 raise error.Abort(_("untracked files in working directory differ "
419 422 "from files in requested revision"))
420 423
421 424 for f, (m, args, msg) in actions.iteritems():
422 425 if m == 'c':
423 426 actions[f] = ('g', args, msg)
424 427 elif m == 'cm':
425 428 fl2, anc = args
426 429 different = _checkunknownfile(repo, wctx, mctx, f)
427 430 if different:
428 431 actions[f] = ('m', (f, f, None, False, anc),
429 432 "remote differs from untracked local")
430 433 else:
431 434 actions[f] = ('g', (fl2,), "remote created")
432 435
433 436 def _forgetremoved(wctx, mctx, branchmerge):
434 437 """
435 438 Forget removed files
436 439
437 440 If we're jumping between revisions (as opposed to merging), and if
438 441 neither the working directory nor the target rev has the file,
439 442 then we need to remove it from the dirstate, to prevent the
440 443 dirstate from listing the file when it is no longer in the
441 444 manifest.
442 445
443 446 If we're merging, and the other revision has removed a file
444 447 that is not present in the working directory, we need to mark it
445 448 as removed.
446 449 """
447 450
448 451 actions = {}
449 452 m = 'f'
450 453 if branchmerge:
451 454 m = 'r'
452 455 for f in wctx.deleted():
453 456 if f not in mctx:
454 457 actions[f] = m, None, "forget deleted"
455 458
456 459 if not branchmerge:
457 460 for f in wctx.removed():
458 461 if f not in mctx:
459 462 actions[f] = 'f', None, "forget removed"
460 463
461 464 return actions
462 465
463 466 def _checkcollision(repo, wmf, actions):
464 467 # build provisional merged manifest up
465 468 pmmf = set(wmf)
466 469
467 470 if actions:
468 471 # k, dr, e and rd are no-op
469 472 for m in 'a', 'f', 'g', 'cd', 'dc':
470 473 for f, args, msg in actions[m]:
471 474 pmmf.add(f)
472 475 for f, args, msg in actions['r']:
473 476 pmmf.discard(f)
474 477 for f, args, msg in actions['dm']:
475 478 f2, flags = args
476 479 pmmf.discard(f2)
477 480 pmmf.add(f)
478 481 for f, args, msg in actions['dg']:
479 482 pmmf.add(f)
480 483 for f, args, msg in actions['m']:
481 484 f1, f2, fa, move, anc = args
482 485 if move:
483 486 pmmf.discard(f1)
484 487 pmmf.add(f)
485 488
486 489 # check case-folding collision in provisional merged manifest
487 490 foldmap = {}
488 491 for f in sorted(pmmf):
489 492 fold = util.normcase(f)
490 493 if fold in foldmap:
491 494 raise error.Abort(_("case-folding collision between %s and %s")
492 495 % (f, foldmap[fold]))
493 496 foldmap[fold] = f
494 497
495 498 # check case-folding of directories
496 499 foldprefix = unfoldprefix = lastfull = ''
497 500 for fold, f in sorted(foldmap.items()):
498 501 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
499 502 # the folded prefix matches but actual casing is different
500 503 raise error.Abort(_("case-folding collision between "
501 504 "%s and directory of %s") % (lastfull, f))
502 505 foldprefix = fold + '/'
503 506 unfoldprefix = f + '/'
504 507 lastfull = f
505 508
506 509 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
507 510 acceptremote, followcopies):
508 511 """
509 512 Merge p1 and p2 with ancestor pa and generate merge action list
510 513
511 514 branchmerge and force are as passed in to update
512 515 partial = function to filter file lists
513 516 acceptremote = accept the incoming changes without prompting
514 517 """
515 518
516 519 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
517 520
518 521 # manifests fetched in order are going to be faster, so prime the caches
519 522 [x.manifest() for x in
520 523 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
521 524
522 525 if followcopies:
523 526 ret = copies.mergecopies(repo, wctx, p2, pa)
524 527 copy, movewithdir, diverge, renamedelete = ret
525 528
526 529 repo.ui.note(_("resolving manifests\n"))
527 530 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
528 531 % (bool(branchmerge), bool(force), bool(partial)))
529 532 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
530 533
531 534 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
532 535 copied = set(copy.values())
533 536 copied.update(movewithdir.values())
534 537
535 538 if '.hgsubstate' in m1:
536 539 # check whether sub state is modified
537 540 for s in sorted(wctx.substate):
538 541 if wctx.sub(s).dirty():
539 542 m1['.hgsubstate'] += '+'
540 543 break
541 544
542 545 # Compare manifests
543 546 diff = m1.diff(m2)
544 547
545 548 actions = {}
546 549 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
547 550 if partial and not partial(f):
548 551 continue
549 552 if n1 and n2: # file exists on both local and remote side
550 553 if f not in ma:
551 554 fa = copy.get(f, None)
552 555 if fa is not None:
553 556 actions[f] = ('m', (f, f, fa, False, pa.node()),
554 557 "both renamed from " + fa)
555 558 else:
556 559 actions[f] = ('m', (f, f, None, False, pa.node()),
557 560 "both created")
558 561 else:
559 562 a = ma[f]
560 563 fla = ma.flags(f)
561 564 nol = 'l' not in fl1 + fl2 + fla
562 565 if n2 == a and fl2 == fla:
563 566 actions[f] = ('k' , (), "remote unchanged")
564 567 elif n1 == a and fl1 == fla: # local unchanged - use remote
565 568 if n1 == n2: # optimization: keep local content
566 569 actions[f] = ('e', (fl2,), "update permissions")
567 570 else:
568 571 actions[f] = ('g', (fl2,), "remote is newer")
569 572 elif nol and n2 == a: # remote only changed 'x'
570 573 actions[f] = ('e', (fl2,), "update permissions")
571 574 elif nol and n1 == a: # local only changed 'x'
572 575 actions[f] = ('g', (fl1,), "remote is newer")
573 576 else: # both changed something
574 577 actions[f] = ('m', (f, f, f, False, pa.node()),
575 578 "versions differ")
576 579 elif n1: # file exists only on local side
577 580 if f in copied:
578 581 pass # we'll deal with it on m2 side
579 582 elif f in movewithdir: # directory rename, move local
580 583 f2 = movewithdir[f]
581 584 if f2 in m2:
582 585 actions[f2] = ('m', (f, f2, None, True, pa.node()),
583 586 "remote directory rename, both created")
584 587 else:
585 588 actions[f2] = ('dm', (f, fl1),
586 589 "remote directory rename - move from " + f)
587 590 elif f in copy:
588 591 f2 = copy[f]
589 592 actions[f] = ('m', (f, f2, f2, False, pa.node()),
590 593 "local copied/moved from " + f2)
591 594 elif f in ma: # clean, a different, no remote
592 595 if n1 != ma[f]:
593 596 if acceptremote:
594 597 actions[f] = ('r', None, "remote delete")
595 598 else:
596 599 actions[f] = ('cd', None, "prompt changed/deleted")
597 600 elif n1[20:] == 'a':
598 601 # This extra 'a' is added by working copy manifest to mark
599 602 # the file as locally added. We should forget it instead of
600 603 # deleting it.
601 604 actions[f] = ('f', None, "remote deleted")
602 605 else:
603 606 actions[f] = ('r', None, "other deleted")
604 607 elif n2: # file exists only on remote side
605 608 if f in copied:
606 609 pass # we'll deal with it on m1 side
607 610 elif f in movewithdir:
608 611 f2 = movewithdir[f]
609 612 if f2 in m1:
610 613 actions[f2] = ('m', (f2, f, None, False, pa.node()),
611 614 "local directory rename, both created")
612 615 else:
613 616 actions[f2] = ('dg', (f, fl2),
614 617 "local directory rename - get from " + f)
615 618 elif f in copy:
616 619 f2 = copy[f]
617 620 if f2 in m2:
618 621 actions[f] = ('m', (f2, f, f2, False, pa.node()),
619 622 "remote copied from " + f2)
620 623 else:
621 624 actions[f] = ('m', (f2, f, f2, True, pa.node()),
622 625 "remote moved from " + f2)
623 626 elif f not in ma:
624 627 # local unknown, remote created: the logic is described by the
625 628 # following table:
626 629 #
627 630 # force branchmerge different | action
628 631 # n * * | create
629 632 # y n * | create
630 633 # y y n | create
631 634 # y y y | merge
632 635 #
633 636 # Checking whether the files are different is expensive, so we
634 637 # don't do that when we can avoid it.
635 638 if not force:
636 639 actions[f] = ('c', (fl2,), "remote created")
637 640 elif not branchmerge:
638 641 actions[f] = ('c', (fl2,), "remote created")
639 642 else:
640 643 actions[f] = ('cm', (fl2, pa.node()),
641 644 "remote created, get or merge")
642 645 elif n2 != ma[f]:
643 646 if acceptremote:
644 647 actions[f] = ('c', (fl2,), "remote recreating")
645 648 else:
646 649 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
647 650
648 651 return actions, diverge, renamedelete
649 652
650 653 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
651 654 """Resolves false conflicts where the nodeid changed but the content
652 655 remained the same."""
653 656
654 657 for f, (m, args, msg) in actions.items():
655 658 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
656 659 # local did change but ended up with same content
657 660 actions[f] = 'r', None, "prompt same"
658 661 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
659 662 # remote did change but ended up with same content
660 663 del actions[f] # don't get = keep local deleted
661 664
662 665 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
663 666 acceptremote, followcopies):
664 667 "Calculate the actions needed to merge mctx into wctx using ancestors"
665 668
666 669 if len(ancestors) == 1: # default
667 670 actions, diverge, renamedelete = manifestmerge(
668 671 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
669 672 acceptremote, followcopies)
670 673 _checkunknownfiles(repo, wctx, mctx, force, actions)
671 674
672 675 else: # only when merge.preferancestor=* - the default
673 676 repo.ui.note(
674 677 _("note: merging %s and %s using bids from ancestors %s\n") %
675 678 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
676 679
677 680 # Call for bids
678 681 fbids = {} # mapping filename to bids (action method to list af actions)
679 682 diverge, renamedelete = None, None
680 683 for ancestor in ancestors:
681 684 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
682 685 actions, diverge1, renamedelete1 = manifestmerge(
683 686 repo, wctx, mctx, ancestor, branchmerge, force, partial,
684 687 acceptremote, followcopies)
685 688 _checkunknownfiles(repo, wctx, mctx, force, actions)
686 689
687 690 # Track the shortest set of warning on the theory that bid
688 691 # merge will correctly incorporate more information
689 692 if diverge is None or len(diverge1) < len(diverge):
690 693 diverge = diverge1
691 694 if renamedelete is None or len(renamedelete) < len(renamedelete1):
692 695 renamedelete = renamedelete1
693 696
694 697 for f, a in sorted(actions.iteritems()):
695 698 m, args, msg = a
696 699 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
697 700 if f in fbids:
698 701 d = fbids[f]
699 702 if m in d:
700 703 d[m].append(a)
701 704 else:
702 705 d[m] = [a]
703 706 else:
704 707 fbids[f] = {m: [a]}
705 708
706 709 # Pick the best bid for each file
707 710 repo.ui.note(_('\nauction for merging merge bids\n'))
708 711 actions = {}
709 712 for f, bids in sorted(fbids.items()):
710 713 # bids is a mapping from action method to list af actions
711 714 # Consensus?
712 715 if len(bids) == 1: # all bids are the same kind of method
713 716 m, l = bids.items()[0]
714 717 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
715 718 repo.ui.note(" %s: consensus for %s\n" % (f, m))
716 719 actions[f] = l[0]
717 720 continue
718 721 # If keep is an option, just do it.
719 722 if 'k' in bids:
720 723 repo.ui.note(" %s: picking 'keep' action\n" % f)
721 724 actions[f] = bids['k'][0]
722 725 continue
723 726 # If there are gets and they all agree [how could they not?], do it.
724 727 if 'g' in bids:
725 728 ga0 = bids['g'][0]
726 729 if all(a == ga0 for a in bids['g'][1:]):
727 730 repo.ui.note(" %s: picking 'get' action\n" % f)
728 731 actions[f] = ga0
729 732 continue
730 733 # TODO: Consider other simple actions such as mode changes
731 734 # Handle inefficient democrazy.
732 735 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
733 736 for m, l in sorted(bids.items()):
734 737 for _f, args, msg in l:
735 738 repo.ui.note(' %s -> %s\n' % (msg, m))
736 739 # Pick random action. TODO: Instead, prompt user when resolving
737 740 m, l = bids.items()[0]
738 741 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
739 742 (f, m))
740 743 actions[f] = l[0]
741 744 continue
742 745 repo.ui.note(_('end of auction\n\n'))
743 746
744 747 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
745 748
746 749 if wctx.rev() is None:
747 750 fractions = _forgetremoved(wctx, mctx, branchmerge)
748 751 actions.update(fractions)
749 752
750 753 return actions, diverge, renamedelete
751 754
752 755 def batchremove(repo, actions):
753 756 """apply removes to the working directory
754 757
755 758 yields tuples for progress updates
756 759 """
757 760 verbose = repo.ui.verbose
758 761 unlink = util.unlinkpath
759 762 wjoin = repo.wjoin
760 763 audit = repo.wvfs.audit
761 764 i = 0
762 765 for f, args, msg in actions:
763 766 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
764 767 if verbose:
765 768 repo.ui.note(_("removing %s\n") % f)
766 769 audit(f)
767 770 try:
768 771 unlink(wjoin(f), ignoremissing=True)
769 772 except OSError as inst:
770 773 repo.ui.warn(_("update failed to remove %s: %s!\n") %
771 774 (f, inst.strerror))
772 775 if i == 100:
773 776 yield i, f
774 777 i = 0
775 778 i += 1
776 779 if i > 0:
777 780 yield i, f
778 781
779 782 def batchget(repo, mctx, actions):
780 783 """apply gets to the working directory
781 784
782 785 mctx is the context to get from
783 786
784 787 yields tuples for progress updates
785 788 """
786 789 verbose = repo.ui.verbose
787 790 fctx = mctx.filectx
788 791 wwrite = repo.wwrite
789 792 i = 0
790 793 for f, args, msg in actions:
791 794 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
792 795 if verbose:
793 796 repo.ui.note(_("getting %s\n") % f)
794 797 wwrite(f, fctx(f).data(), args[0])
795 798 if i == 100:
796 799 yield i, f
797 800 i = 0
798 801 i += 1
799 802 if i > 0:
800 803 yield i, f
801 804
802 805 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
803 806 """apply the merge action list to the working directory
804 807
805 808 wctx is the working copy context
806 809 mctx is the context to be merged into the working copy
807 810
808 811 Return a tuple of counts (updated, merged, removed, unresolved) that
809 812 describes how many files were affected by the update.
810 813 """
811 814
812 815 updated, merged, removed, unresolved = 0, 0, 0, 0
813 816 ms = mergestate(repo)
814 817 ms.reset(wctx.p1().node(), mctx.node())
815 818 moves = []
816 819 for m, l in actions.items():
817 820 l.sort()
818 821
819 822 # prescan for merges
820 823 for f, args, msg in actions['m']:
821 824 f1, f2, fa, move, anc = args
822 825 if f == '.hgsubstate': # merged internally
823 826 continue
824 827 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
825 828 fcl = wctx[f1]
826 829 fco = mctx[f2]
827 830 actx = repo[anc]
828 831 if fa in actx:
829 832 fca = actx[fa]
830 833 else:
831 834 fca = repo.filectx(f1, fileid=nullrev)
832 835 ms.add(fcl, fco, fca, f)
833 836 if f1 != f and move:
834 837 moves.append(f1)
835 838
836 839 audit = repo.wvfs.audit
837 840 _updating = _('updating')
838 841 _files = _('files')
839 842 progress = repo.ui.progress
840 843
841 844 # remove renamed files after safely stored
842 845 for f in moves:
843 846 if os.path.lexists(repo.wjoin(f)):
844 847 repo.ui.debug("removing %s\n" % f)
845 848 audit(f)
846 849 util.unlinkpath(repo.wjoin(f))
847 850
848 851 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
849 852
850 853 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
851 854 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
852 855
853 856 # remove in parallel (must come first)
854 857 z = 0
855 858 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
856 859 for i, item in prog:
857 860 z += i
858 861 progress(_updating, z, item=item, total=numupdates, unit=_files)
859 862 removed = len(actions['r'])
860 863
861 864 # get in parallel
862 865 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
863 866 for i, item in prog:
864 867 z += i
865 868 progress(_updating, z, item=item, total=numupdates, unit=_files)
866 869 updated = len(actions['g'])
867 870
868 871 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
869 872 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
870 873
871 874 # forget (manifest only, just log it) (must come first)
872 875 for f, args, msg in actions['f']:
873 876 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
874 877 z += 1
875 878 progress(_updating, z, item=f, total=numupdates, unit=_files)
876 879
877 880 # re-add (manifest only, just log it)
878 881 for f, args, msg in actions['a']:
879 882 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
880 883 z += 1
881 884 progress(_updating, z, item=f, total=numupdates, unit=_files)
882 885
883 886 # keep (noop, just log it)
884 887 for f, args, msg in actions['k']:
885 888 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
886 889 # no progress
887 890
888 891 # directory rename, move local
889 892 for f, args, msg in actions['dm']:
890 893 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
891 894 z += 1
892 895 progress(_updating, z, item=f, total=numupdates, unit=_files)
893 896 f0, flags = args
894 897 repo.ui.note(_("moving %s to %s\n") % (f0, f))
895 898 audit(f)
896 899 repo.wwrite(f, wctx.filectx(f0).data(), flags)
897 900 util.unlinkpath(repo.wjoin(f0))
898 901 updated += 1
899 902
900 903 # local directory rename, get
901 904 for f, args, msg in actions['dg']:
902 905 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
903 906 z += 1
904 907 progress(_updating, z, item=f, total=numupdates, unit=_files)
905 908 f0, flags = args
906 909 repo.ui.note(_("getting %s to %s\n") % (f0, f))
907 910 repo.wwrite(f, mctx.filectx(f0).data(), flags)
908 911 updated += 1
909 912
910 913 # exec
911 914 for f, args, msg in actions['e']:
912 915 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
913 916 z += 1
914 917 progress(_updating, z, item=f, total=numupdates, unit=_files)
915 918 flags, = args
916 919 audit(f)
917 920 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
918 921 updated += 1
919 922
920 923 # premerge
921 924 tocomplete = []
922 925 for f, args, msg in actions['m']:
923 926 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
924 927 z += 1
925 928 progress(_updating, z, item=f, total=numupdates, unit=_files)
926 929 if f == '.hgsubstate': # subrepo states need updating
927 930 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
928 931 overwrite)
929 932 continue
930 933 audit(f)
931 934 complete, r = ms.preresolve(f, wctx, labels=labels)
932 935 if complete:
933 936 if r is not None and r > 0:
934 937 unresolved += 1
935 938 else:
936 939 if r is None:
937 940 updated += 1
938 941 else:
939 942 merged += 1
940 943 else:
941 944 numupdates += 1
942 945 tocomplete.append((f, args, msg))
943 946
944 947 # merge
945 948 for f, args, msg in tocomplete:
946 949 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
947 950 z += 1
948 951 progress(_updating, z, item=f, total=numupdates, unit=_files)
949 952 r = ms.resolve(f, wctx, labels=labels)
950 953 if r is not None and r > 0:
951 954 unresolved += 1
952 955 else:
953 956 if r is None:
954 957 updated += 1
955 958 else:
956 959 merged += 1
957 960
958 961 ms.commit()
959 962 progress(_updating, None, total=numupdates, unit=_files)
960 963
961 964 return updated, merged, removed, unresolved
962 965
963 966 def recordupdates(repo, actions, branchmerge):
964 967 "record merge actions to the dirstate"
965 968 # remove (must come first)
966 969 for f, args, msg in actions['r']:
967 970 if branchmerge:
968 971 repo.dirstate.remove(f)
969 972 else:
970 973 repo.dirstate.drop(f)
971 974
972 975 # forget (must come first)
973 976 for f, args, msg in actions['f']:
974 977 repo.dirstate.drop(f)
975 978
976 979 # re-add
977 980 for f, args, msg in actions['a']:
978 981 if not branchmerge:
979 982 repo.dirstate.add(f)
980 983
981 984 # exec change
982 985 for f, args, msg in actions['e']:
983 986 repo.dirstate.normallookup(f)
984 987
985 988 # keep
986 989 for f, args, msg in actions['k']:
987 990 pass
988 991
989 992 # get
990 993 for f, args, msg in actions['g']:
991 994 if branchmerge:
992 995 repo.dirstate.otherparent(f)
993 996 else:
994 997 repo.dirstate.normal(f)
995 998
996 999 # merge
997 1000 for f, args, msg in actions['m']:
998 1001 f1, f2, fa, move, anc = args
999 1002 if branchmerge:
1000 1003 # We've done a branch merge, mark this file as merged
1001 1004 # so that we properly record the merger later
1002 1005 repo.dirstate.merge(f)
1003 1006 if f1 != f2: # copy/rename
1004 1007 if move:
1005 1008 repo.dirstate.remove(f1)
1006 1009 if f1 != f:
1007 1010 repo.dirstate.copy(f1, f)
1008 1011 else:
1009 1012 repo.dirstate.copy(f2, f)
1010 1013 else:
1011 1014 # We've update-merged a locally modified file, so
1012 1015 # we set the dirstate to emulate a normal checkout
1013 1016 # of that file some time in the past. Thus our
1014 1017 # merge will appear as a normal local file
1015 1018 # modification.
1016 1019 if f2 == f: # file not locally copied/moved
1017 1020 repo.dirstate.normallookup(f)
1018 1021 if move:
1019 1022 repo.dirstate.drop(f1)
1020 1023
1021 1024 # directory rename, move local
1022 1025 for f, args, msg in actions['dm']:
1023 1026 f0, flag = args
1024 1027 if branchmerge:
1025 1028 repo.dirstate.add(f)
1026 1029 repo.dirstate.remove(f0)
1027 1030 repo.dirstate.copy(f0, f)
1028 1031 else:
1029 1032 repo.dirstate.normal(f)
1030 1033 repo.dirstate.drop(f0)
1031 1034
1032 1035 # directory rename, get
1033 1036 for f, args, msg in actions['dg']:
1034 1037 f0, flag = args
1035 1038 if branchmerge:
1036 1039 repo.dirstate.add(f)
1037 1040 repo.dirstate.copy(f0, f)
1038 1041 else:
1039 1042 repo.dirstate.normal(f)
1040 1043
1041 1044 def update(repo, node, branchmerge, force, partial, ancestor=None,
1042 1045 mergeancestor=False, labels=None):
1043 1046 """
1044 1047 Perform a merge between the working directory and the given node
1045 1048
1046 1049 node = the node to update to, or None if unspecified
1047 1050 branchmerge = whether to merge between branches
1048 1051 force = whether to force branch merging or file overwriting
1049 1052 partial = a function to filter file lists (dirstate not updated)
1050 1053 mergeancestor = whether it is merging with an ancestor. If true,
1051 1054 we should accept the incoming changes for any prompts that occur.
1052 1055 If false, merging with an ancestor (fast-forward) is only allowed
1053 1056 between different named branches. This flag is used by rebase extension
1054 1057 as a temporary fix and should be avoided in general.
1055 1058
1056 1059 The table below shows all the behaviors of the update command
1057 1060 given the -c and -C or no options, whether the working directory
1058 1061 is dirty, whether a revision is specified, and the relationship of
1059 1062 the parent rev to the target rev (linear, on the same named
1060 1063 branch, or on another named branch).
1061 1064
1062 1065 This logic is tested by test-update-branches.t.
1063 1066
1064 1067 -c -C dirty rev | linear same cross
1065 1068 n n n n | ok (1) x
1066 1069 n n n y | ok ok ok
1067 1070 n n y n | merge (2) (2)
1068 1071 n n y y | merge (3) (3)
1069 1072 n y * * | --- discard ---
1070 1073 y n y * | --- (4) ---
1071 1074 y n n * | --- ok ---
1072 1075 y y * * | --- (5) ---
1073 1076
1074 1077 x = can't happen
1075 1078 * = don't-care
1076 1079 1 = abort: not a linear update (merge or update --check to force update)
1077 1080 2 = abort: uncommitted changes (commit and merge, or update --clean to
1078 1081 discard changes)
1079 1082 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1080 1083 4 = abort: uncommitted changes (checked in commands.py)
1081 1084 5 = incompatible options (checked in commands.py)
1082 1085
1083 1086 Return the same tuple as applyupdates().
1084 1087 """
1085 1088
1086 1089 onode = node
1087 1090 wlock = repo.wlock()
1088 1091 try:
1089 1092 wc = repo[None]
1090 1093 pl = wc.parents()
1091 1094 p1 = pl[0]
1092 1095 pas = [None]
1093 1096 if ancestor is not None:
1094 1097 pas = [repo[ancestor]]
1095 1098
1096 1099 if node is None:
1097 1100 if (repo.ui.configbool('devel', 'all-warnings')
1098 1101 or repo.ui.configbool('devel', 'oldapi')):
1099 1102 repo.ui.develwarn('update with no target')
1100 1103 rev, _mark, _act = destutil.destupdate(repo)
1101 1104 node = repo[rev].node()
1102 1105
1103 1106 overwrite = force and not branchmerge
1104 1107
1105 1108 p2 = repo[node]
1106 1109 if pas[0] is None:
1107 1110 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1108 1111 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1109 1112 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1110 1113 else:
1111 1114 pas = [p1.ancestor(p2, warn=branchmerge)]
1112 1115
1113 1116 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1114 1117
1115 1118 ### check phase
1116 1119 if not overwrite and len(pl) > 1:
1117 1120 raise error.Abort(_("outstanding uncommitted merge"))
1118 1121 if branchmerge:
1119 1122 if pas == [p2]:
1120 1123 raise error.Abort(_("merging with a working directory ancestor"
1121 1124 " has no effect"))
1122 1125 elif pas == [p1]:
1123 1126 if not mergeancestor and p1.branch() == p2.branch():
1124 1127 raise error.Abort(_("nothing to merge"),
1125 1128 hint=_("use 'hg update' "
1126 1129 "or check 'hg heads'"))
1127 1130 if not force and (wc.files() or wc.deleted()):
1128 1131 raise error.Abort(_("uncommitted changes"),
1129 1132 hint=_("use 'hg status' to list changes"))
1130 1133 for s in sorted(wc.substate):
1131 1134 wc.sub(s).bailifchanged()
1132 1135
1133 1136 elif not overwrite:
1134 1137 if p1 == p2: # no-op update
1135 1138 # call the hooks and exit early
1136 1139 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1137 1140 repo.hook('update', parent1=xp2, parent2='', error=0)
1138 1141 return 0, 0, 0, 0
1139 1142
1140 1143 if pas not in ([p1], [p2]): # nonlinear
1141 1144 dirty = wc.dirty(missing=True)
1142 1145 if dirty or onode is None:
1143 1146 # Branching is a bit strange to ensure we do the minimal
1144 1147 # amount of call to obsolete.background.
1145 1148 foreground = obsolete.foreground(repo, [p1.node()])
1146 1149 # note: the <node> variable contains a random identifier
1147 1150 if repo[node].node() in foreground:
1148 1151 pas = [p1] # allow updating to successors
1149 1152 elif dirty:
1150 1153 msg = _("uncommitted changes")
1151 1154 if onode is None:
1152 1155 hint = _("commit and merge, or update --clean to"
1153 1156 " discard changes")
1154 1157 else:
1155 1158 hint = _("commit or update --clean to discard"
1156 1159 " changes")
1157 1160 raise error.Abort(msg, hint=hint)
1158 1161 else: # node is none
1159 1162 msg = _("not a linear update")
1160 1163 hint = _("merge or update --check to force update")
1161 1164 raise error.Abort(msg, hint=hint)
1162 1165 else:
1163 1166 # Allow jumping branches if clean and specific rev given
1164 1167 pas = [p1]
1165 1168
1166 1169 # deprecated config: merge.followcopies
1167 1170 followcopies = False
1168 1171 if overwrite:
1169 1172 pas = [wc]
1170 1173 elif pas == [p2]: # backwards
1171 1174 pas = [wc.p1()]
1172 1175 elif not branchmerge and not wc.dirty(missing=True):
1173 1176 pass
1174 1177 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1175 1178 followcopies = True
1176 1179
1177 1180 ### calculate phase
1178 1181 actionbyfile, diverge, renamedelete = calculateupdates(
1179 1182 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1180 1183 followcopies)
1181 1184 # Convert to dictionary-of-lists format
1182 1185 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
1183 1186 for f, (m, args, msg) in actionbyfile.iteritems():
1184 1187 if m not in actions:
1185 1188 actions[m] = []
1186 1189 actions[m].append((f, args, msg))
1187 1190
1188 1191 if not util.checkcase(repo.path):
1189 1192 # check collision between files only in p2 for clean update
1190 1193 if (not branchmerge and
1191 1194 (force or not wc.dirty(missing=True, branch=False))):
1192 1195 _checkcollision(repo, p2.manifest(), None)
1193 1196 else:
1194 1197 _checkcollision(repo, wc.manifest(), actions)
1195 1198
1196 1199 # Prompt and create actions. TODO: Move this towards resolve phase.
1197 1200 for f, args, msg in sorted(actions['cd']):
1198 1201 if repo.ui.promptchoice(
1199 1202 _("local changed %s which remote deleted\n"
1200 1203 "use (c)hanged version or (d)elete?"
1201 1204 "$$ &Changed $$ &Delete") % f, 0):
1202 1205 actions['r'].append((f, None, "prompt delete"))
1203 1206 else:
1204 1207 actions['a'].append((f, None, "prompt keep"))
1205 1208 del actions['cd'][:]
1206 1209
1207 1210 for f, args, msg in sorted(actions['dc']):
1208 1211 flags, = args
1209 1212 if repo.ui.promptchoice(
1210 1213 _("remote changed %s which local deleted\n"
1211 1214 "use (c)hanged version or leave (d)eleted?"
1212 1215 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1213 1216 actions['g'].append((f, (flags,), "prompt recreating"))
1214 1217 del actions['dc'][:]
1215 1218
1216 1219 ### apply phase
1217 1220 if not branchmerge: # just jump to the new rev
1218 1221 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1219 1222 if not partial:
1220 1223 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1221 1224 # note that we're in the middle of an update
1222 1225 repo.vfs.write('updatestate', p2.hex())
1223 1226
1224 1227 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1225 1228
1226 1229 # divergent renames
1227 1230 for f, fl in sorted(diverge.iteritems()):
1228 1231 repo.ui.warn(_("note: possible conflict - %s was renamed "
1229 1232 "multiple times to:\n") % f)
1230 1233 for nf in fl:
1231 1234 repo.ui.warn(" %s\n" % nf)
1232 1235
1233 1236 # rename and delete
1234 1237 for f, fl in sorted(renamedelete.iteritems()):
1235 1238 repo.ui.warn(_("note: possible conflict - %s was deleted "
1236 1239 "and renamed to:\n") % f)
1237 1240 for nf in fl:
1238 1241 repo.ui.warn(" %s\n" % nf)
1239 1242
1240 1243 if not partial:
1241 1244 repo.dirstate.beginparentchange()
1242 1245 repo.setparents(fp1, fp2)
1243 1246 recordupdates(repo, actions, branchmerge)
1244 1247 # update completed, clear state
1245 1248 util.unlink(repo.join('updatestate'))
1246 1249
1247 1250 if not branchmerge:
1248 1251 repo.dirstate.setbranch(p2.branch())
1249 1252 repo.dirstate.endparentchange()
1250 1253 finally:
1251 1254 wlock.release()
1252 1255
1253 1256 if not partial:
1254 1257 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1255 1258 return stats
1256 1259
1257 1260 def graft(repo, ctx, pctx, labels):
1258 1261 """Do a graft-like merge.
1259 1262
1260 1263 This is a merge where the merge ancestor is chosen such that one
1261 1264 or more changesets are grafted onto the current changeset. In
1262 1265 addition to the merge, this fixes up the dirstate to include only
1263 1266 a single parent and tries to duplicate any renames/copies
1264 1267 appropriately.
1265 1268
1266 1269 ctx - changeset to rebase
1267 1270 pctx - merge base, usually ctx.p1()
1268 1271 labels - merge labels eg ['local', 'graft']
1269 1272
1270 1273 """
1271 1274 # If we're grafting a descendant onto an ancestor, be sure to pass
1272 1275 # mergeancestor=True to update. This does two things: 1) allows the merge if
1273 1276 # the destination is the same as the parent of the ctx (so we can use graft
1274 1277 # to copy commits), and 2) informs update that the incoming changes are
1275 1278 # newer than the destination so it doesn't prompt about "remote changed foo
1276 1279 # which local deleted".
1277 1280 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1278 1281
1279 1282 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1280 1283 mergeancestor=mergeancestor, labels=labels)
1281 1284
1282 1285 # drop the second merge parent
1283 1286 repo.dirstate.beginparentchange()
1284 1287 repo.setparents(repo['.'].node(), nullid)
1285 1288 repo.dirstate.write(repo.currenttransaction())
1286 1289 # fix up dirstate for copies and renames
1287 1290 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1288 1291 repo.dirstate.endparentchange()
1289 1292 return stats
General Comments 0
You need to be logged in to leave comments. Login now