##// END OF EJS Templates
merge: fix crash on criss cross merge with dir move and delete (issue5020)...
Mads Kiilerich -
r30856:41f6af50 stable
parent child Browse files
Show More
@@ -1,1701 +1,1716 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 hashlib
12 12 import os
13 13 import shutil
14 14 import struct
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 addednodeid,
19 19 bin,
20 20 hex,
21 21 modifiednodeid,
22 22 nullhex,
23 23 nullid,
24 24 nullrev,
25 25 )
26 26 from . import (
27 27 copies,
28 28 error,
29 29 filemerge,
30 30 obsolete,
31 31 pycompat,
32 32 scmutil,
33 33 subrepo,
34 34 util,
35 35 worker,
36 36 )
37 37
38 38 _pack = struct.pack
39 39 _unpack = struct.unpack
40 40
41 41 def _droponode(data):
42 42 # used for compatibility for v1
43 43 bits = data.split('\0')
44 44 bits = bits[:-2] + bits[-1:]
45 45 return '\0'.join(bits)
46 46
47 47 class mergestate(object):
48 48 '''track 3-way merge state of individual files
49 49
50 50 The merge state is stored on disk when needed. Two files are used: one with
51 51 an old format (version 1), and one with a new format (version 2). Version 2
52 52 stores a superset of the data in version 1, including new kinds of records
53 53 in the future. For more about the new format, see the documentation for
54 54 `_readrecordsv2`.
55 55
56 56 Each record can contain arbitrary content, and has an associated type. This
57 57 `type` should be a letter. If `type` is uppercase, the record is mandatory:
58 58 versions of Mercurial that don't support it should abort. If `type` is
59 59 lowercase, the record can be safely ignored.
60 60
61 61 Currently known records:
62 62
63 63 L: the node of the "local" part of the merge (hexified version)
64 64 O: the node of the "other" part of the merge (hexified version)
65 65 F: a file to be merged entry
66 66 C: a change/delete or delete/change conflict
67 67 D: a file that the external merge driver will merge internally
68 68 (experimental)
69 69 m: the external merge driver defined for this merge plus its run state
70 70 (experimental)
71 71 f: a (filename, dictionary) tuple of optional values for a given file
72 72 X: unsupported mandatory record type (used in tests)
73 73 x: unsupported advisory record type (used in tests)
74 74 l: the labels for the parts of the merge.
75 75
76 76 Merge driver run states (experimental):
77 77 u: driver-resolved files unmarked -- needs to be run next time we're about
78 78 to resolve or commit
79 79 m: driver-resolved files marked -- only needs to be run before commit
80 80 s: success/skipped -- does not need to be run any more
81 81
82 82 '''
83 83 statepathv1 = 'merge/state'
84 84 statepathv2 = 'merge/state2'
85 85
86 86 @staticmethod
87 87 def clean(repo, node=None, other=None, labels=None):
88 88 """Initialize a brand new merge state, removing any existing state on
89 89 disk."""
90 90 ms = mergestate(repo)
91 91 ms.reset(node, other, labels)
92 92 return ms
93 93
94 94 @staticmethod
95 95 def read(repo):
96 96 """Initialize the merge state, reading it from disk."""
97 97 ms = mergestate(repo)
98 98 ms._read()
99 99 return ms
100 100
101 101 def __init__(self, repo):
102 102 """Initialize the merge state.
103 103
104 104 Do not use this directly! Instead call read() or clean()."""
105 105 self._repo = repo
106 106 self._dirty = False
107 107 self._labels = None
108 108
109 109 def reset(self, node=None, other=None, labels=None):
110 110 self._state = {}
111 111 self._stateextras = {}
112 112 self._local = None
113 113 self._other = None
114 114 self._labels = labels
115 115 for var in ('localctx', 'otherctx'):
116 116 if var in vars(self):
117 117 delattr(self, var)
118 118 if node:
119 119 self._local = node
120 120 self._other = other
121 121 self._readmergedriver = None
122 122 if self.mergedriver:
123 123 self._mdstate = 's'
124 124 else:
125 125 self._mdstate = 'u'
126 126 shutil.rmtree(self._repo.join('merge'), True)
127 127 self._results = {}
128 128 self._dirty = False
129 129
130 130 def _read(self):
131 131 """Analyse each record content to restore a serialized state from disk
132 132
133 133 This function process "record" entry produced by the de-serialization
134 134 of on disk file.
135 135 """
136 136 self._state = {}
137 137 self._stateextras = {}
138 138 self._local = None
139 139 self._other = None
140 140 for var in ('localctx', 'otherctx'):
141 141 if var in vars(self):
142 142 delattr(self, var)
143 143 self._readmergedriver = None
144 144 self._mdstate = 's'
145 145 unsupported = set()
146 146 records = self._readrecords()
147 147 for rtype, record in records:
148 148 if rtype == 'L':
149 149 self._local = bin(record)
150 150 elif rtype == 'O':
151 151 self._other = bin(record)
152 152 elif rtype == 'm':
153 153 bits = record.split('\0', 1)
154 154 mdstate = bits[1]
155 155 if len(mdstate) != 1 or mdstate not in 'ums':
156 156 # the merge driver should be idempotent, so just rerun it
157 157 mdstate = 'u'
158 158
159 159 self._readmergedriver = bits[0]
160 160 self._mdstate = mdstate
161 161 elif rtype in 'FDC':
162 162 bits = record.split('\0')
163 163 self._state[bits[0]] = bits[1:]
164 164 elif rtype == 'f':
165 165 filename, rawextras = record.split('\0', 1)
166 166 extraparts = rawextras.split('\0')
167 167 extras = {}
168 168 i = 0
169 169 while i < len(extraparts):
170 170 extras[extraparts[i]] = extraparts[i + 1]
171 171 i += 2
172 172
173 173 self._stateextras[filename] = extras
174 174 elif rtype == 'l':
175 175 labels = record.split('\0', 2)
176 176 self._labels = [l for l in labels if len(l) > 0]
177 177 elif not rtype.islower():
178 178 unsupported.add(rtype)
179 179 self._results = {}
180 180 self._dirty = False
181 181
182 182 if unsupported:
183 183 raise error.UnsupportedMergeRecords(unsupported)
184 184
185 185 def _readrecords(self):
186 186 """Read merge state from disk and return a list of record (TYPE, data)
187 187
188 188 We read data from both v1 and v2 files and decide which one to use.
189 189
190 190 V1 has been used by version prior to 2.9.1 and contains less data than
191 191 v2. We read both versions and check if no data in v2 contradicts
192 192 v1. If there is not contradiction we can safely assume that both v1
193 193 and v2 were written at the same time and use the extract data in v2. If
194 194 there is contradiction we ignore v2 content as we assume an old version
195 195 of Mercurial has overwritten the mergestate file and left an old v2
196 196 file around.
197 197
198 198 returns list of record [(TYPE, data), ...]"""
199 199 v1records = self._readrecordsv1()
200 200 v2records = self._readrecordsv2()
201 201 if self._v1v2match(v1records, v2records):
202 202 return v2records
203 203 else:
204 204 # v1 file is newer than v2 file, use it
205 205 # we have to infer the "other" changeset of the merge
206 206 # we cannot do better than that with v1 of the format
207 207 mctx = self._repo[None].parents()[-1]
208 208 v1records.append(('O', mctx.hex()))
209 209 # add place holder "other" file node information
210 210 # nobody is using it yet so we do no need to fetch the data
211 211 # if mctx was wrong `mctx[bits[-2]]` may fails.
212 212 for idx, r in enumerate(v1records):
213 213 if r[0] == 'F':
214 214 bits = r[1].split('\0')
215 215 bits.insert(-2, '')
216 216 v1records[idx] = (r[0], '\0'.join(bits))
217 217 return v1records
218 218
219 219 def _v1v2match(self, v1records, v2records):
220 220 oldv2 = set() # old format version of v2 record
221 221 for rec in v2records:
222 222 if rec[0] == 'L':
223 223 oldv2.add(rec)
224 224 elif rec[0] == 'F':
225 225 # drop the onode data (not contained in v1)
226 226 oldv2.add(('F', _droponode(rec[1])))
227 227 for rec in v1records:
228 228 if rec not in oldv2:
229 229 return False
230 230 else:
231 231 return True
232 232
233 233 def _readrecordsv1(self):
234 234 """read on disk merge state for version 1 file
235 235
236 236 returns list of record [(TYPE, data), ...]
237 237
238 238 Note: the "F" data from this file are one entry short
239 239 (no "other file node" entry)
240 240 """
241 241 records = []
242 242 try:
243 243 f = self._repo.vfs(self.statepathv1)
244 244 for i, l in enumerate(f):
245 245 if i == 0:
246 246 records.append(('L', l[:-1]))
247 247 else:
248 248 records.append(('F', l[:-1]))
249 249 f.close()
250 250 except IOError as err:
251 251 if err.errno != errno.ENOENT:
252 252 raise
253 253 return records
254 254
255 255 def _readrecordsv2(self):
256 256 """read on disk merge state for version 2 file
257 257
258 258 This format is a list of arbitrary records of the form:
259 259
260 260 [type][length][content]
261 261
262 262 `type` is a single character, `length` is a 4 byte integer, and
263 263 `content` is an arbitrary byte sequence of length `length`.
264 264
265 265 Mercurial versions prior to 3.7 have a bug where if there are
266 266 unsupported mandatory merge records, attempting to clear out the merge
267 267 state with hg update --clean or similar aborts. The 't' record type
268 268 works around that by writing out what those versions treat as an
269 269 advisory record, but later versions interpret as special: the first
270 270 character is the 'real' record type and everything onwards is the data.
271 271
272 272 Returns list of records [(TYPE, data), ...]."""
273 273 records = []
274 274 try:
275 275 f = self._repo.vfs(self.statepathv2)
276 276 data = f.read()
277 277 off = 0
278 278 end = len(data)
279 279 while off < end:
280 280 rtype = data[off]
281 281 off += 1
282 282 length = _unpack('>I', data[off:(off + 4)])[0]
283 283 off += 4
284 284 record = data[off:(off + length)]
285 285 off += length
286 286 if rtype == 't':
287 287 rtype, record = record[0], record[1:]
288 288 records.append((rtype, record))
289 289 f.close()
290 290 except IOError as err:
291 291 if err.errno != errno.ENOENT:
292 292 raise
293 293 return records
294 294
295 295 @util.propertycache
296 296 def mergedriver(self):
297 297 # protect against the following:
298 298 # - A configures a malicious merge driver in their hgrc, then
299 299 # pauses the merge
300 300 # - A edits their hgrc to remove references to the merge driver
301 301 # - A gives a copy of their entire repo, including .hg, to B
302 302 # - B inspects .hgrc and finds it to be clean
303 303 # - B then continues the merge and the malicious merge driver
304 304 # gets invoked
305 305 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
306 306 if (self._readmergedriver is not None
307 307 and self._readmergedriver != configmergedriver):
308 308 raise error.ConfigError(
309 309 _("merge driver changed since merge started"),
310 310 hint=_("revert merge driver change or abort merge"))
311 311
312 312 return configmergedriver
313 313
314 314 @util.propertycache
315 315 def localctx(self):
316 316 if self._local is None:
317 317 raise RuntimeError("localctx accessed but self._local isn't set")
318 318 return self._repo[self._local]
319 319
320 320 @util.propertycache
321 321 def otherctx(self):
322 322 if self._other is None:
323 323 raise RuntimeError("otherctx accessed but self._other isn't set")
324 324 return self._repo[self._other]
325 325
326 326 def active(self):
327 327 """Whether mergestate is active.
328 328
329 329 Returns True if there appears to be mergestate. This is a rough proxy
330 330 for "is a merge in progress."
331 331 """
332 332 # Check local variables before looking at filesystem for performance
333 333 # reasons.
334 334 return bool(self._local) or bool(self._state) or \
335 335 self._repo.vfs.exists(self.statepathv1) or \
336 336 self._repo.vfs.exists(self.statepathv2)
337 337
338 338 def commit(self):
339 339 """Write current state on disk (if necessary)"""
340 340 if self._dirty:
341 341 records = self._makerecords()
342 342 self._writerecords(records)
343 343 self._dirty = False
344 344
345 345 def _makerecords(self):
346 346 records = []
347 347 records.append(('L', hex(self._local)))
348 348 records.append(('O', hex(self._other)))
349 349 if self.mergedriver:
350 350 records.append(('m', '\0'.join([
351 351 self.mergedriver, self._mdstate])))
352 352 for d, v in self._state.iteritems():
353 353 if v[0] == 'd':
354 354 records.append(('D', '\0'.join([d] + v)))
355 355 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
356 356 # older versions of Mercurial
357 357 elif v[1] == nullhex or v[6] == nullhex:
358 358 records.append(('C', '\0'.join([d] + v)))
359 359 else:
360 360 records.append(('F', '\0'.join([d] + v)))
361 361 for filename, extras in sorted(self._stateextras.iteritems()):
362 362 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
363 363 extras.iteritems())
364 364 records.append(('f', '%s\0%s' % (filename, rawextras)))
365 365 if self._labels is not None:
366 366 labels = '\0'.join(self._labels)
367 367 records.append(('l', labels))
368 368 return records
369 369
370 370 def _writerecords(self, records):
371 371 """Write current state on disk (both v1 and v2)"""
372 372 self._writerecordsv1(records)
373 373 self._writerecordsv2(records)
374 374
375 375 def _writerecordsv1(self, records):
376 376 """Write current state on disk in a version 1 file"""
377 377 f = self._repo.vfs(self.statepathv1, 'w')
378 378 irecords = iter(records)
379 379 lrecords = next(irecords)
380 380 assert lrecords[0] == 'L'
381 381 f.write(hex(self._local) + '\n')
382 382 for rtype, data in irecords:
383 383 if rtype == 'F':
384 384 f.write('%s\n' % _droponode(data))
385 385 f.close()
386 386
387 387 def _writerecordsv2(self, records):
388 388 """Write current state on disk in a version 2 file
389 389
390 390 See the docstring for _readrecordsv2 for why we use 't'."""
391 391 # these are the records that all version 2 clients can read
392 392 whitelist = 'LOF'
393 393 f = self._repo.vfs(self.statepathv2, 'w')
394 394 for key, data in records:
395 395 assert len(key) == 1
396 396 if key not in whitelist:
397 397 key, data = 't', '%s%s' % (key, data)
398 398 format = '>sI%is' % len(data)
399 399 f.write(_pack(format, key, len(data), data))
400 400 f.close()
401 401
402 402 def add(self, fcl, fco, fca, fd):
403 403 """add a new (potentially?) conflicting file the merge state
404 404 fcl: file context for local,
405 405 fco: file context for remote,
406 406 fca: file context for ancestors,
407 407 fd: file path of the resulting merge.
408 408
409 409 note: also write the local version to the `.hg/merge` directory.
410 410 """
411 411 if fcl.isabsent():
412 412 hash = nullhex
413 413 else:
414 414 hash = hashlib.sha1(fcl.path()).hexdigest()
415 415 self._repo.vfs.write('merge/' + hash, fcl.data())
416 416 self._state[fd] = ['u', hash, fcl.path(),
417 417 fca.path(), hex(fca.filenode()),
418 418 fco.path(), hex(fco.filenode()),
419 419 fcl.flags()]
420 420 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
421 421 self._dirty = True
422 422
423 423 def __contains__(self, dfile):
424 424 return dfile in self._state
425 425
426 426 def __getitem__(self, dfile):
427 427 return self._state[dfile][0]
428 428
429 429 def __iter__(self):
430 430 return iter(sorted(self._state))
431 431
432 432 def files(self):
433 433 return self._state.keys()
434 434
435 435 def mark(self, dfile, state):
436 436 self._state[dfile][0] = state
437 437 self._dirty = True
438 438
439 439 def mdstate(self):
440 440 return self._mdstate
441 441
442 442 def unresolved(self):
443 443 """Obtain the paths of unresolved files."""
444 444
445 445 for f, entry in self._state.items():
446 446 if entry[0] == 'u':
447 447 yield f
448 448
449 449 def driverresolved(self):
450 450 """Obtain the paths of driver-resolved files."""
451 451
452 452 for f, entry in self._state.items():
453 453 if entry[0] == 'd':
454 454 yield f
455 455
456 456 def extras(self, filename):
457 457 return self._stateextras.setdefault(filename, {})
458 458
459 459 def _resolve(self, preresolve, dfile, wctx):
460 460 """rerun merge process for file path `dfile`"""
461 461 if self[dfile] in 'rd':
462 462 return True, 0
463 463 stateentry = self._state[dfile]
464 464 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
465 465 octx = self._repo[self._other]
466 466 extras = self.extras(dfile)
467 467 anccommitnode = extras.get('ancestorlinknode')
468 468 if anccommitnode:
469 469 actx = self._repo[anccommitnode]
470 470 else:
471 471 actx = None
472 472 fcd = self._filectxorabsent(hash, wctx, dfile)
473 473 fco = self._filectxorabsent(onode, octx, ofile)
474 474 # TODO: move this to filectxorabsent
475 475 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
476 476 # "premerge" x flags
477 477 flo = fco.flags()
478 478 fla = fca.flags()
479 479 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
480 480 if fca.node() == nullid and flags != flo:
481 481 if preresolve:
482 482 self._repo.ui.warn(
483 483 _('warning: cannot merge flags for %s '
484 484 'without common ancestor - keeping local flags\n')
485 485 % afile)
486 486 elif flags == fla:
487 487 flags = flo
488 488 if preresolve:
489 489 # restore local
490 490 if hash != nullhex:
491 491 f = self._repo.vfs('merge/' + hash)
492 492 self._repo.wwrite(dfile, f.read(), flags)
493 493 f.close()
494 494 else:
495 495 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
496 496 complete, r, deleted = filemerge.premerge(self._repo, self._local,
497 497 lfile, fcd, fco, fca,
498 498 labels=self._labels)
499 499 else:
500 500 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
501 501 lfile, fcd, fco, fca,
502 502 labels=self._labels)
503 503 if r is None:
504 504 # no real conflict
505 505 del self._state[dfile]
506 506 self._stateextras.pop(dfile, None)
507 507 self._dirty = True
508 508 elif not r:
509 509 self.mark(dfile, 'r')
510 510
511 511 if complete:
512 512 action = None
513 513 if deleted:
514 514 if fcd.isabsent():
515 515 # dc: local picked. Need to drop if present, which may
516 516 # happen on re-resolves.
517 517 action = 'f'
518 518 else:
519 519 # cd: remote picked (or otherwise deleted)
520 520 action = 'r'
521 521 else:
522 522 if fcd.isabsent(): # dc: remote picked
523 523 action = 'g'
524 524 elif fco.isabsent(): # cd: local picked
525 525 if dfile in self.localctx:
526 526 action = 'am'
527 527 else:
528 528 action = 'a'
529 529 # else: regular merges (no action necessary)
530 530 self._results[dfile] = r, action
531 531
532 532 return complete, r
533 533
534 534 def _filectxorabsent(self, hexnode, ctx, f):
535 535 if hexnode == nullhex:
536 536 return filemerge.absentfilectx(ctx, f)
537 537 else:
538 538 return ctx[f]
539 539
540 540 def preresolve(self, dfile, wctx):
541 541 """run premerge process for dfile
542 542
543 543 Returns whether the merge is complete, and the exit code."""
544 544 return self._resolve(True, dfile, wctx)
545 545
546 546 def resolve(self, dfile, wctx):
547 547 """run merge process (assuming premerge was run) for dfile
548 548
549 549 Returns the exit code of the merge."""
550 550 return self._resolve(False, dfile, wctx)[1]
551 551
552 552 def counts(self):
553 553 """return counts for updated, merged and removed files in this
554 554 session"""
555 555 updated, merged, removed = 0, 0, 0
556 556 for r, action in self._results.itervalues():
557 557 if r is None:
558 558 updated += 1
559 559 elif r == 0:
560 560 if action == 'r':
561 561 removed += 1
562 562 else:
563 563 merged += 1
564 564 return updated, merged, removed
565 565
566 566 def unresolvedcount(self):
567 567 """get unresolved count for this merge (persistent)"""
568 568 return len([True for f, entry in self._state.iteritems()
569 569 if entry[0] == 'u'])
570 570
571 571 def actions(self):
572 572 """return lists of actions to perform on the dirstate"""
573 573 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
574 574 for f, (r, action) in self._results.iteritems():
575 575 if action is not None:
576 576 actions[action].append((f, None, "merge result"))
577 577 return actions
578 578
579 579 def recordactions(self):
580 580 """record remove/add/get actions in the dirstate"""
581 581 branchmerge = self._repo.dirstate.p2() != nullid
582 582 recordupdates(self._repo, self.actions(), branchmerge)
583 583
584 584 def queueremove(self, f):
585 585 """queues a file to be removed from the dirstate
586 586
587 587 Meant for use by custom merge drivers."""
588 588 self._results[f] = 0, 'r'
589 589
590 590 def queueadd(self, f):
591 591 """queues a file to be added to the dirstate
592 592
593 593 Meant for use by custom merge drivers."""
594 594 self._results[f] = 0, 'a'
595 595
596 596 def queueget(self, f):
597 597 """queues a file to be marked modified in the dirstate
598 598
599 599 Meant for use by custom merge drivers."""
600 600 self._results[f] = 0, 'g'
601 601
602 602 def _getcheckunknownconfig(repo, section, name):
603 603 config = repo.ui.config(section, name, default='abort')
604 604 valid = ['abort', 'ignore', 'warn']
605 605 if config not in valid:
606 606 validstr = ', '.join(["'" + v + "'" for v in valid])
607 607 raise error.ConfigError(_("%s.%s not valid "
608 608 "('%s' is none of %s)")
609 609 % (section, name, config, validstr))
610 610 return config
611 611
612 612 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
613 613 if f2 is None:
614 614 f2 = f
615 615 return (repo.wvfs.audit.check(f)
616 616 and repo.wvfs.isfileorlink(f)
617 617 and repo.dirstate.normalize(f) not in repo.dirstate
618 618 and mctx[f2].cmp(wctx[f]))
619 619
620 620 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
621 621 """
622 622 Considers any actions that care about the presence of conflicting unknown
623 623 files. For some actions, the result is to abort; for others, it is to
624 624 choose a different action.
625 625 """
626 626 conflicts = set()
627 627 warnconflicts = set()
628 628 abortconflicts = set()
629 629 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
630 630 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
631 631 if not force:
632 632 def collectconflicts(conflicts, config):
633 633 if config == 'abort':
634 634 abortconflicts.update(conflicts)
635 635 elif config == 'warn':
636 636 warnconflicts.update(conflicts)
637 637
638 638 for f, (m, args, msg) in actions.iteritems():
639 639 if m in ('c', 'dc'):
640 640 if _checkunknownfile(repo, wctx, mctx, f):
641 641 conflicts.add(f)
642 642 elif m == 'dg':
643 643 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
644 644 conflicts.add(f)
645 645
646 646 ignoredconflicts = set([c for c in conflicts
647 647 if repo.dirstate._ignore(c)])
648 648 unknownconflicts = conflicts - ignoredconflicts
649 649 collectconflicts(ignoredconflicts, ignoredconfig)
650 650 collectconflicts(unknownconflicts, unknownconfig)
651 651 else:
652 652 for f, (m, args, msg) in actions.iteritems():
653 653 if m == 'cm':
654 654 fl2, anc = args
655 655 different = _checkunknownfile(repo, wctx, mctx, f)
656 656 if repo.dirstate._ignore(f):
657 657 config = ignoredconfig
658 658 else:
659 659 config = unknownconfig
660 660
661 661 # The behavior when force is True is described by this table:
662 662 # config different mergeforce | action backup
663 663 # * n * | get n
664 664 # * y y | merge -
665 665 # abort y n | merge - (1)
666 666 # warn y n | warn + get y
667 667 # ignore y n | get y
668 668 #
669 669 # (1) this is probably the wrong behavior here -- we should
670 670 # probably abort, but some actions like rebases currently
671 671 # don't like an abort happening in the middle of
672 672 # merge.update.
673 673 if not different:
674 674 actions[f] = ('g', (fl2, False), "remote created")
675 675 elif mergeforce or config == 'abort':
676 676 actions[f] = ('m', (f, f, None, False, anc),
677 677 "remote differs from untracked local")
678 678 elif config == 'abort':
679 679 abortconflicts.add(f)
680 680 else:
681 681 if config == 'warn':
682 682 warnconflicts.add(f)
683 683 actions[f] = ('g', (fl2, True), "remote created")
684 684
685 685 for f in sorted(abortconflicts):
686 686 repo.ui.warn(_("%s: untracked file differs\n") % f)
687 687 if abortconflicts:
688 688 raise error.Abort(_("untracked files in working directory "
689 689 "differ from files in requested revision"))
690 690
691 691 for f in sorted(warnconflicts):
692 692 repo.ui.warn(_("%s: replacing untracked file\n") % f)
693 693
694 694 for f, (m, args, msg) in actions.iteritems():
695 695 backup = f in conflicts
696 696 if m == 'c':
697 697 flags, = args
698 698 actions[f] = ('g', (flags, backup), msg)
699 699
700 700 def _forgetremoved(wctx, mctx, branchmerge):
701 701 """
702 702 Forget removed files
703 703
704 704 If we're jumping between revisions (as opposed to merging), and if
705 705 neither the working directory nor the target rev has the file,
706 706 then we need to remove it from the dirstate, to prevent the
707 707 dirstate from listing the file when it is no longer in the
708 708 manifest.
709 709
710 710 If we're merging, and the other revision has removed a file
711 711 that is not present in the working directory, we need to mark it
712 712 as removed.
713 713 """
714 714
715 715 actions = {}
716 716 m = 'f'
717 717 if branchmerge:
718 718 m = 'r'
719 719 for f in wctx.deleted():
720 720 if f not in mctx:
721 721 actions[f] = m, None, "forget deleted"
722 722
723 723 if not branchmerge:
724 724 for f in wctx.removed():
725 725 if f not in mctx:
726 726 actions[f] = 'f', None, "forget removed"
727 727
728 728 return actions
729 729
730 730 def _checkcollision(repo, wmf, actions):
731 731 # build provisional merged manifest up
732 732 pmmf = set(wmf)
733 733
734 734 if actions:
735 735 # k, dr, e and rd are no-op
736 736 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
737 737 for f, args, msg in actions[m]:
738 738 pmmf.add(f)
739 739 for f, args, msg in actions['r']:
740 740 pmmf.discard(f)
741 741 for f, args, msg in actions['dm']:
742 742 f2, flags = args
743 743 pmmf.discard(f2)
744 744 pmmf.add(f)
745 745 for f, args, msg in actions['dg']:
746 746 pmmf.add(f)
747 747 for f, args, msg in actions['m']:
748 748 f1, f2, fa, move, anc = args
749 749 if move:
750 750 pmmf.discard(f1)
751 751 pmmf.add(f)
752 752
753 753 # check case-folding collision in provisional merged manifest
754 754 foldmap = {}
755 755 for f in sorted(pmmf):
756 756 fold = util.normcase(f)
757 757 if fold in foldmap:
758 758 raise error.Abort(_("case-folding collision between %s and %s")
759 759 % (f, foldmap[fold]))
760 760 foldmap[fold] = f
761 761
762 762 # check case-folding of directories
763 763 foldprefix = unfoldprefix = lastfull = ''
764 764 for fold, f in sorted(foldmap.items()):
765 765 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
766 766 # the folded prefix matches but actual casing is different
767 767 raise error.Abort(_("case-folding collision between "
768 768 "%s and directory of %s") % (lastfull, f))
769 769 foldprefix = fold + '/'
770 770 unfoldprefix = f + '/'
771 771 lastfull = f
772 772
773 773 def driverpreprocess(repo, ms, wctx, labels=None):
774 774 """run the preprocess step of the merge driver, if any
775 775
776 776 This is currently not implemented -- it's an extension point."""
777 777 return True
778 778
779 779 def driverconclude(repo, ms, wctx, labels=None):
780 780 """run the conclude step of the merge driver, if any
781 781
782 782 This is currently not implemented -- it's an extension point."""
783 783 return True
784 784
785 785 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
786 786 acceptremote, followcopies):
787 787 """
788 788 Merge wctx and p2 with ancestor pa and generate merge action list
789 789
790 790 branchmerge and force are as passed in to update
791 791 matcher = matcher to filter file lists
792 792 acceptremote = accept the incoming changes without prompting
793 793 """
794 794 if matcher is not None and matcher.always():
795 795 matcher = None
796 796
797 797 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
798 798
799 799 # manifests fetched in order are going to be faster, so prime the caches
800 800 [x.manifest() for x in
801 801 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
802 802
803 803 if followcopies:
804 804 ret = copies.mergecopies(repo, wctx, p2, pa)
805 805 copy, movewithdir, diverge, renamedelete, dirmove = ret
806 806
807 807 repo.ui.note(_("resolving manifests\n"))
808 808 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
809 809 % (bool(branchmerge), bool(force), bool(matcher)))
810 810 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
811 811
812 812 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
813 813 copied = set(copy.values())
814 814 copied.update(movewithdir.values())
815 815
816 816 if '.hgsubstate' in m1:
817 817 # check whether sub state is modified
818 818 if any(wctx.sub(s).dirty() for s in wctx.substate):
819 819 m1['.hgsubstate'] = modifiednodeid
820 820
821 821 # Compare manifests
822 822 if matcher is not None:
823 823 m1 = m1.matches(matcher)
824 824 m2 = m2.matches(matcher)
825 825 diff = m1.diff(m2)
826 826
827 827 actions = {}
828 828 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
829 829 if n1 and n2: # file exists on both local and remote side
830 830 if f not in ma:
831 831 fa = copy.get(f, None)
832 832 if fa is not None:
833 833 actions[f] = ('m', (f, f, fa, False, pa.node()),
834 834 "both renamed from " + fa)
835 835 else:
836 836 actions[f] = ('m', (f, f, None, False, pa.node()),
837 837 "both created")
838 838 else:
839 839 a = ma[f]
840 840 fla = ma.flags(f)
841 841 nol = 'l' not in fl1 + fl2 + fla
842 842 if n2 == a and fl2 == fla:
843 843 actions[f] = ('k' , (), "remote unchanged")
844 844 elif n1 == a and fl1 == fla: # local unchanged - use remote
845 845 if n1 == n2: # optimization: keep local content
846 846 actions[f] = ('e', (fl2,), "update permissions")
847 847 else:
848 848 actions[f] = ('g', (fl2, False), "remote is newer")
849 849 elif nol and n2 == a: # remote only changed 'x'
850 850 actions[f] = ('e', (fl2,), "update permissions")
851 851 elif nol and n1 == a: # local only changed 'x'
852 852 actions[f] = ('g', (fl1, False), "remote is newer")
853 853 else: # both changed something
854 854 actions[f] = ('m', (f, f, f, False, pa.node()),
855 855 "versions differ")
856 856 elif n1: # file exists only on local side
857 857 if f in copied:
858 858 pass # we'll deal with it on m2 side
859 859 elif f in movewithdir: # directory rename, move local
860 860 f2 = movewithdir[f]
861 861 if f2 in m2:
862 862 actions[f2] = ('m', (f, f2, None, True, pa.node()),
863 863 "remote directory rename, both created")
864 864 else:
865 865 actions[f2] = ('dm', (f, fl1),
866 866 "remote directory rename - move from " + f)
867 867 elif f in copy:
868 868 f2 = copy[f]
869 869 actions[f] = ('m', (f, f2, f2, False, pa.node()),
870 870 "local copied/moved from " + f2)
871 871 elif f in ma: # clean, a different, no remote
872 872 if n1 != ma[f]:
873 873 if acceptremote:
874 874 actions[f] = ('r', None, "remote delete")
875 875 else:
876 876 actions[f] = ('cd', (f, None, f, False, pa.node()),
877 877 "prompt changed/deleted")
878 878 elif n1 == addednodeid:
879 879 # This extra 'a' is added by working copy manifest to mark
880 880 # the file as locally added. We should forget it instead of
881 881 # deleting it.
882 882 actions[f] = ('f', None, "remote deleted")
883 883 else:
884 884 actions[f] = ('r', None, "other deleted")
885 885 elif n2: # file exists only on remote side
886 886 if f in copied:
887 887 pass # we'll deal with it on m1 side
888 888 elif f in movewithdir:
889 889 f2 = movewithdir[f]
890 890 if f2 in m1:
891 891 actions[f2] = ('m', (f2, f, None, False, pa.node()),
892 892 "local directory rename, both created")
893 893 else:
894 894 actions[f2] = ('dg', (f, fl2),
895 895 "local directory rename - get from " + f)
896 896 elif f in copy:
897 897 f2 = copy[f]
898 898 if f2 in m2:
899 899 actions[f] = ('m', (f2, f, f2, False, pa.node()),
900 900 "remote copied from " + f2)
901 901 else:
902 902 actions[f] = ('m', (f2, f, f2, True, pa.node()),
903 903 "remote moved from " + f2)
904 904 elif f not in ma:
905 905 # local unknown, remote created: the logic is described by the
906 906 # following table:
907 907 #
908 908 # force branchmerge different | action
909 909 # n * * | create
910 910 # y n * | create
911 911 # y y n | create
912 912 # y y y | merge
913 913 #
914 914 # Checking whether the files are different is expensive, so we
915 915 # don't do that when we can avoid it.
916 916 if not force:
917 917 actions[f] = ('c', (fl2,), "remote created")
918 918 elif not branchmerge:
919 919 actions[f] = ('c', (fl2,), "remote created")
920 920 else:
921 921 actions[f] = ('cm', (fl2, pa.node()),
922 922 "remote created, get or merge")
923 923 elif n2 != ma[f]:
924 924 df = None
925 925 for d in dirmove:
926 926 if f.startswith(d):
927 927 # new file added in a directory that was moved
928 928 df = dirmove[d] + f[len(d):]
929 929 break
930 930 if df in m1:
931 931 actions[df] = ('m', (df, f, f, False, pa.node()),
932 932 "local directory rename - respect move from " + f)
933 933 elif acceptremote:
934 934 actions[f] = ('c', (fl2,), "remote recreating")
935 935 else:
936 936 actions[f] = ('dc', (None, f, f, False, pa.node()),
937 937 "prompt deleted/changed")
938 938
939 939 return actions, diverge, renamedelete
940 940
941 941 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
942 942 """Resolves false conflicts where the nodeid changed but the content
943 943 remained the same."""
944 944
945 945 for f, (m, args, msg) in actions.items():
946 946 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
947 947 # local did change but ended up with same content
948 948 actions[f] = 'r', None, "prompt same"
949 949 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
950 950 # remote did change but ended up with same content
951 951 del actions[f] # don't get = keep local deleted
952 952
953 953 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
954 954 acceptremote, followcopies, matcher=None,
955 955 mergeforce=False):
956 956 "Calculate the actions needed to merge mctx into wctx using ancestors"
957 957 if len(ancestors) == 1: # default
958 958 actions, diverge, renamedelete = manifestmerge(
959 959 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
960 960 acceptremote, followcopies)
961 961 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
962 962
963 963 else: # only when merge.preferancestor=* - the default
964 964 repo.ui.note(
965 965 _("note: merging %s and %s using bids from ancestors %s\n") %
966 966 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
967 967
968 968 # Call for bids
969 969 fbids = {} # mapping filename to bids (action method to list af actions)
970 970 diverge, renamedelete = None, None
971 971 for ancestor in ancestors:
972 972 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
973 973 actions, diverge1, renamedelete1 = manifestmerge(
974 974 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
975 975 acceptremote, followcopies)
976 976 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
977 977
978 978 # Track the shortest set of warning on the theory that bid
979 979 # merge will correctly incorporate more information
980 980 if diverge is None or len(diverge1) < len(diverge):
981 981 diverge = diverge1
982 982 if renamedelete is None or len(renamedelete) < len(renamedelete1):
983 983 renamedelete = renamedelete1
984 984
985 985 for f, a in sorted(actions.iteritems()):
986 986 m, args, msg = a
987 987 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
988 988 if f in fbids:
989 989 d = fbids[f]
990 990 if m in d:
991 991 d[m].append(a)
992 992 else:
993 993 d[m] = [a]
994 994 else:
995 995 fbids[f] = {m: [a]}
996 996
997 997 # Pick the best bid for each file
998 998 repo.ui.note(_('\nauction for merging merge bids\n'))
999 999 actions = {}
1000 dms = [] # filenames that have dm actions
1000 1001 for f, bids in sorted(fbids.items()):
1001 1002 # bids is a mapping from action method to list af actions
1002 1003 # Consensus?
1003 1004 if len(bids) == 1: # all bids are the same kind of method
1004 1005 m, l = bids.items()[0]
1005 1006 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1006 1007 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1007 1008 actions[f] = l[0]
1009 if m == 'dm':
1010 dms.append(f)
1008 1011 continue
1009 1012 # If keep is an option, just do it.
1010 1013 if 'k' in bids:
1011 1014 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1012 1015 actions[f] = bids['k'][0]
1013 1016 continue
1014 1017 # If there are gets and they all agree [how could they not?], do it.
1015 1018 if 'g' in bids:
1016 1019 ga0 = bids['g'][0]
1017 1020 if all(a == ga0 for a in bids['g'][1:]):
1018 1021 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1019 1022 actions[f] = ga0
1020 1023 continue
1021 1024 # TODO: Consider other simple actions such as mode changes
1022 1025 # Handle inefficient democrazy.
1023 1026 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1024 1027 for m, l in sorted(bids.items()):
1025 1028 for _f, args, msg in l:
1026 1029 repo.ui.note(' %s -> %s\n' % (msg, m))
1027 1030 # Pick random action. TODO: Instead, prompt user when resolving
1028 1031 m, l = bids.items()[0]
1029 1032 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1030 1033 (f, m))
1031 1034 actions[f] = l[0]
1035 if m == 'dm':
1036 dms.append(f)
1032 1037 continue
1038 # Work around 'dm' that can cause multiple actions for the same file
1039 for f in dms:
1040 dm, (f0, flags), msg = actions[f]
1041 assert dm == 'dm', dm
1042 m, args, msg = actions[f0]
1043 if m == 'r':
1044 # We have one bid for removing a file and another for moving it.
1045 # These two could be merged as first move and then delete ...
1046 # but instead drop moving and just delete.
1047 del actions[f]
1033 1048 repo.ui.note(_('end of auction\n\n'))
1034 1049
1035 1050 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1036 1051
1037 1052 if wctx.rev() is None:
1038 1053 fractions = _forgetremoved(wctx, mctx, branchmerge)
1039 1054 actions.update(fractions)
1040 1055
1041 1056 return actions, diverge, renamedelete
1042 1057
1043 1058 def batchremove(repo, actions):
1044 1059 """apply removes to the working directory
1045 1060
1046 1061 yields tuples for progress updates
1047 1062 """
1048 1063 verbose = repo.ui.verbose
1049 1064 unlink = util.unlinkpath
1050 1065 wjoin = repo.wjoin
1051 1066 audit = repo.wvfs.audit
1052 1067 try:
1053 1068 cwd = pycompat.getcwd()
1054 1069 except OSError as err:
1055 1070 if err.errno != errno.ENOENT:
1056 1071 raise
1057 1072 cwd = None
1058 1073 i = 0
1059 1074 for f, args, msg in actions:
1060 1075 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1061 1076 if verbose:
1062 1077 repo.ui.note(_("removing %s\n") % f)
1063 1078 audit(f)
1064 1079 try:
1065 1080 unlink(wjoin(f), ignoremissing=True)
1066 1081 except OSError as inst:
1067 1082 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1068 1083 (f, inst.strerror))
1069 1084 if i == 100:
1070 1085 yield i, f
1071 1086 i = 0
1072 1087 i += 1
1073 1088 if i > 0:
1074 1089 yield i, f
1075 1090 if cwd:
1076 1091 # cwd was present before we started to remove files
1077 1092 # let's check if it is present after we removed them
1078 1093 try:
1079 1094 pycompat.getcwd()
1080 1095 except OSError as err:
1081 1096 if err.errno != errno.ENOENT:
1082 1097 raise
1083 1098 # Print a warning if cwd was deleted
1084 1099 repo.ui.warn(_("current directory was removed\n"
1085 1100 "(consider changing to repo root: %s)\n") %
1086 1101 repo.root)
1087 1102
1088 1103 def batchget(repo, mctx, actions):
1089 1104 """apply gets to the working directory
1090 1105
1091 1106 mctx is the context to get from
1092 1107
1093 1108 yields tuples for progress updates
1094 1109 """
1095 1110 verbose = repo.ui.verbose
1096 1111 fctx = mctx.filectx
1097 1112 wwrite = repo.wwrite
1098 1113 ui = repo.ui
1099 1114 i = 0
1100 1115 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1101 1116 for f, (flags, backup), msg in actions:
1102 1117 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1103 1118 if verbose:
1104 1119 repo.ui.note(_("getting %s\n") % f)
1105 1120
1106 1121 if backup:
1107 1122 absf = repo.wjoin(f)
1108 1123 orig = scmutil.origpath(ui, repo, absf)
1109 1124 try:
1110 1125 if repo.wvfs.isfileorlink(f):
1111 1126 util.rename(absf, orig)
1112 1127 except OSError as e:
1113 1128 if e.errno != errno.ENOENT:
1114 1129 raise
1115 1130
1116 1131 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1117 1132 repo.wvfs.removedirs(f)
1118 1133 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1119 1134 if i == 100:
1120 1135 yield i, f
1121 1136 i = 0
1122 1137 i += 1
1123 1138 if i > 0:
1124 1139 yield i, f
1125 1140
1126 1141 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1127 1142 """apply the merge action list to the working directory
1128 1143
1129 1144 wctx is the working copy context
1130 1145 mctx is the context to be merged into the working copy
1131 1146
1132 1147 Return a tuple of counts (updated, merged, removed, unresolved) that
1133 1148 describes how many files were affected by the update.
1134 1149 """
1135 1150
1136 1151 updated, merged, removed = 0, 0, 0
1137 1152 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1138 1153 moves = []
1139 1154 for m, l in actions.items():
1140 1155 l.sort()
1141 1156
1142 1157 # 'cd' and 'dc' actions are treated like other merge conflicts
1143 1158 mergeactions = sorted(actions['cd'])
1144 1159 mergeactions.extend(sorted(actions['dc']))
1145 1160 mergeactions.extend(actions['m'])
1146 1161 for f, args, msg in mergeactions:
1147 1162 f1, f2, fa, move, anc = args
1148 1163 if f == '.hgsubstate': # merged internally
1149 1164 continue
1150 1165 if f1 is None:
1151 1166 fcl = filemerge.absentfilectx(wctx, fa)
1152 1167 else:
1153 1168 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1154 1169 fcl = wctx[f1]
1155 1170 if f2 is None:
1156 1171 fco = filemerge.absentfilectx(mctx, fa)
1157 1172 else:
1158 1173 fco = mctx[f2]
1159 1174 actx = repo[anc]
1160 1175 if fa in actx:
1161 1176 fca = actx[fa]
1162 1177 else:
1163 1178 # TODO: move to absentfilectx
1164 1179 fca = repo.filectx(f1, fileid=nullrev)
1165 1180 ms.add(fcl, fco, fca, f)
1166 1181 if f1 != f and move:
1167 1182 moves.append(f1)
1168 1183
1169 1184 audit = repo.wvfs.audit
1170 1185 _updating = _('updating')
1171 1186 _files = _('files')
1172 1187 progress = repo.ui.progress
1173 1188
1174 1189 # remove renamed files after safely stored
1175 1190 for f in moves:
1176 1191 if os.path.lexists(repo.wjoin(f)):
1177 1192 repo.ui.debug("removing %s\n" % f)
1178 1193 audit(f)
1179 1194 util.unlinkpath(repo.wjoin(f))
1180 1195
1181 1196 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1182 1197
1183 1198 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1184 1199 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1185 1200
1186 1201 # remove in parallel (must come first)
1187 1202 z = 0
1188 1203 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1189 1204 for i, item in prog:
1190 1205 z += i
1191 1206 progress(_updating, z, item=item, total=numupdates, unit=_files)
1192 1207 removed = len(actions['r'])
1193 1208
1194 1209 # get in parallel
1195 1210 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1196 1211 for i, item in prog:
1197 1212 z += i
1198 1213 progress(_updating, z, item=item, total=numupdates, unit=_files)
1199 1214 updated = len(actions['g'])
1200 1215
1201 1216 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1202 1217 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1203 1218
1204 1219 # forget (manifest only, just log it) (must come first)
1205 1220 for f, args, msg in actions['f']:
1206 1221 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1207 1222 z += 1
1208 1223 progress(_updating, z, item=f, total=numupdates, unit=_files)
1209 1224
1210 1225 # re-add (manifest only, just log it)
1211 1226 for f, args, msg in actions['a']:
1212 1227 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1213 1228 z += 1
1214 1229 progress(_updating, z, item=f, total=numupdates, unit=_files)
1215 1230
1216 1231 # re-add/mark as modified (manifest only, just log it)
1217 1232 for f, args, msg in actions['am']:
1218 1233 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1219 1234 z += 1
1220 1235 progress(_updating, z, item=f, total=numupdates, unit=_files)
1221 1236
1222 1237 # keep (noop, just log it)
1223 1238 for f, args, msg in actions['k']:
1224 1239 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1225 1240 # no progress
1226 1241
1227 1242 # directory rename, move local
1228 1243 for f, args, msg in actions['dm']:
1229 1244 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1230 1245 z += 1
1231 1246 progress(_updating, z, item=f, total=numupdates, unit=_files)
1232 1247 f0, flags = args
1233 1248 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1234 1249 audit(f)
1235 1250 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1236 1251 util.unlinkpath(repo.wjoin(f0))
1237 1252 updated += 1
1238 1253
1239 1254 # local directory rename, get
1240 1255 for f, args, msg in actions['dg']:
1241 1256 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1242 1257 z += 1
1243 1258 progress(_updating, z, item=f, total=numupdates, unit=_files)
1244 1259 f0, flags = args
1245 1260 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1246 1261 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1247 1262 updated += 1
1248 1263
1249 1264 # exec
1250 1265 for f, args, msg in actions['e']:
1251 1266 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1252 1267 z += 1
1253 1268 progress(_updating, z, item=f, total=numupdates, unit=_files)
1254 1269 flags, = args
1255 1270 audit(f)
1256 1271 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1257 1272 updated += 1
1258 1273
1259 1274 # the ordering is important here -- ms.mergedriver will raise if the merge
1260 1275 # driver has changed, and we want to be able to bypass it when overwrite is
1261 1276 # True
1262 1277 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1263 1278
1264 1279 if usemergedriver:
1265 1280 ms.commit()
1266 1281 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1267 1282 # the driver might leave some files unresolved
1268 1283 unresolvedf = set(ms.unresolved())
1269 1284 if not proceed:
1270 1285 # XXX setting unresolved to at least 1 is a hack to make sure we
1271 1286 # error out
1272 1287 return updated, merged, removed, max(len(unresolvedf), 1)
1273 1288 newactions = []
1274 1289 for f, args, msg in mergeactions:
1275 1290 if f in unresolvedf:
1276 1291 newactions.append((f, args, msg))
1277 1292 mergeactions = newactions
1278 1293
1279 1294 # premerge
1280 1295 tocomplete = []
1281 1296 for f, args, msg in mergeactions:
1282 1297 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1283 1298 z += 1
1284 1299 progress(_updating, z, item=f, total=numupdates, unit=_files)
1285 1300 if f == '.hgsubstate': # subrepo states need updating
1286 1301 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1287 1302 overwrite, labels)
1288 1303 continue
1289 1304 audit(f)
1290 1305 complete, r = ms.preresolve(f, wctx)
1291 1306 if not complete:
1292 1307 numupdates += 1
1293 1308 tocomplete.append((f, args, msg))
1294 1309
1295 1310 # merge
1296 1311 for f, args, msg in tocomplete:
1297 1312 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1298 1313 z += 1
1299 1314 progress(_updating, z, item=f, total=numupdates, unit=_files)
1300 1315 ms.resolve(f, wctx)
1301 1316
1302 1317 ms.commit()
1303 1318
1304 1319 unresolved = ms.unresolvedcount()
1305 1320
1306 1321 if usemergedriver and not unresolved and ms.mdstate() != 's':
1307 1322 if not driverconclude(repo, ms, wctx, labels=labels):
1308 1323 # XXX setting unresolved to at least 1 is a hack to make sure we
1309 1324 # error out
1310 1325 unresolved = max(unresolved, 1)
1311 1326
1312 1327 ms.commit()
1313 1328
1314 1329 msupdated, msmerged, msremoved = ms.counts()
1315 1330 updated += msupdated
1316 1331 merged += msmerged
1317 1332 removed += msremoved
1318 1333
1319 1334 extraactions = ms.actions()
1320 1335 if extraactions:
1321 1336 mfiles = set(a[0] for a in actions['m'])
1322 1337 for k, acts in extraactions.iteritems():
1323 1338 actions[k].extend(acts)
1324 1339 # Remove these files from actions['m'] as well. This is important
1325 1340 # because in recordupdates, files in actions['m'] are processed
1326 1341 # after files in other actions, and the merge driver might add
1327 1342 # files to those actions via extraactions above. This can lead to a
1328 1343 # file being recorded twice, with poor results. This is especially
1329 1344 # problematic for actions['r'] (currently only possible with the
1330 1345 # merge driver in the initial merge process; interrupted merges
1331 1346 # don't go through this flow).
1332 1347 #
1333 1348 # The real fix here is to have indexes by both file and action so
1334 1349 # that when the action for a file is changed it is automatically
1335 1350 # reflected in the other action lists. But that involves a more
1336 1351 # complex data structure, so this will do for now.
1337 1352 #
1338 1353 # We don't need to do the same operation for 'dc' and 'cd' because
1339 1354 # those lists aren't consulted again.
1340 1355 mfiles.difference_update(a[0] for a in acts)
1341 1356
1342 1357 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1343 1358
1344 1359 progress(_updating, None, total=numupdates, unit=_files)
1345 1360
1346 1361 return updated, merged, removed, unresolved
1347 1362
1348 1363 def recordupdates(repo, actions, branchmerge):
1349 1364 "record merge actions to the dirstate"
1350 1365 # remove (must come first)
1351 1366 for f, args, msg in actions.get('r', []):
1352 1367 if branchmerge:
1353 1368 repo.dirstate.remove(f)
1354 1369 else:
1355 1370 repo.dirstate.drop(f)
1356 1371
1357 1372 # forget (must come first)
1358 1373 for f, args, msg in actions.get('f', []):
1359 1374 repo.dirstate.drop(f)
1360 1375
1361 1376 # re-add
1362 1377 for f, args, msg in actions.get('a', []):
1363 1378 repo.dirstate.add(f)
1364 1379
1365 1380 # re-add/mark as modified
1366 1381 for f, args, msg in actions.get('am', []):
1367 1382 if branchmerge:
1368 1383 repo.dirstate.normallookup(f)
1369 1384 else:
1370 1385 repo.dirstate.add(f)
1371 1386
1372 1387 # exec change
1373 1388 for f, args, msg in actions.get('e', []):
1374 1389 repo.dirstate.normallookup(f)
1375 1390
1376 1391 # keep
1377 1392 for f, args, msg in actions.get('k', []):
1378 1393 pass
1379 1394
1380 1395 # get
1381 1396 for f, args, msg in actions.get('g', []):
1382 1397 if branchmerge:
1383 1398 repo.dirstate.otherparent(f)
1384 1399 else:
1385 1400 repo.dirstate.normal(f)
1386 1401
1387 1402 # merge
1388 1403 for f, args, msg in actions.get('m', []):
1389 1404 f1, f2, fa, move, anc = args
1390 1405 if branchmerge:
1391 1406 # We've done a branch merge, mark this file as merged
1392 1407 # so that we properly record the merger later
1393 1408 repo.dirstate.merge(f)
1394 1409 if f1 != f2: # copy/rename
1395 1410 if move:
1396 1411 repo.dirstate.remove(f1)
1397 1412 if f1 != f:
1398 1413 repo.dirstate.copy(f1, f)
1399 1414 else:
1400 1415 repo.dirstate.copy(f2, f)
1401 1416 else:
1402 1417 # We've update-merged a locally modified file, so
1403 1418 # we set the dirstate to emulate a normal checkout
1404 1419 # of that file some time in the past. Thus our
1405 1420 # merge will appear as a normal local file
1406 1421 # modification.
1407 1422 if f2 == f: # file not locally copied/moved
1408 1423 repo.dirstate.normallookup(f)
1409 1424 if move:
1410 1425 repo.dirstate.drop(f1)
1411 1426
1412 1427 # directory rename, move local
1413 1428 for f, args, msg in actions.get('dm', []):
1414 1429 f0, flag = args
1415 1430 if branchmerge:
1416 1431 repo.dirstate.add(f)
1417 1432 repo.dirstate.remove(f0)
1418 1433 repo.dirstate.copy(f0, f)
1419 1434 else:
1420 1435 repo.dirstate.normal(f)
1421 1436 repo.dirstate.drop(f0)
1422 1437
1423 1438 # directory rename, get
1424 1439 for f, args, msg in actions.get('dg', []):
1425 1440 f0, flag = args
1426 1441 if branchmerge:
1427 1442 repo.dirstate.add(f)
1428 1443 repo.dirstate.copy(f0, f)
1429 1444 else:
1430 1445 repo.dirstate.normal(f)
1431 1446
1432 1447 def update(repo, node, branchmerge, force, ancestor=None,
1433 1448 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1434 1449 """
1435 1450 Perform a merge between the working directory and the given node
1436 1451
1437 1452 node = the node to update to, or None if unspecified
1438 1453 branchmerge = whether to merge between branches
1439 1454 force = whether to force branch merging or file overwriting
1440 1455 matcher = a matcher to filter file lists (dirstate not updated)
1441 1456 mergeancestor = whether it is merging with an ancestor. If true,
1442 1457 we should accept the incoming changes for any prompts that occur.
1443 1458 If false, merging with an ancestor (fast-forward) is only allowed
1444 1459 between different named branches. This flag is used by rebase extension
1445 1460 as a temporary fix and should be avoided in general.
1446 1461 labels = labels to use for base, local and other
1447 1462 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1448 1463 this is True, then 'force' should be True as well.
1449 1464
1450 1465 The table below shows all the behaviors of the update command
1451 1466 given the -c and -C or no options, whether the working directory
1452 1467 is dirty, whether a revision is specified, and the relationship of
1453 1468 the parent rev to the target rev (linear, on the same named
1454 1469 branch, or on another named branch).
1455 1470
1456 1471 This logic is tested by test-update-branches.t.
1457 1472
1458 1473 -c -C dirty rev | linear same cross
1459 1474 n n n n | ok (1) x
1460 1475 n n n y | ok ok ok
1461 1476 n n y n | merge (2) (2)
1462 1477 n n y y | merge (3) (3)
1463 1478 n y * * | discard discard discard
1464 1479 y n y * | (4) (4) (4)
1465 1480 y n n * | ok ok ok
1466 1481 y y * * | (5) (5) (5)
1467 1482
1468 1483 x = can't happen
1469 1484 * = don't-care
1470 1485 1 = abort: not a linear update (merge or update --check to force update)
1471 1486 2 = abort: uncommitted changes (commit and merge, or update --clean to
1472 1487 discard changes)
1473 1488 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1474 1489 4 = abort: uncommitted changes (checked in commands.py)
1475 1490 5 = incompatible options (checked in commands.py)
1476 1491
1477 1492 Return the same tuple as applyupdates().
1478 1493 """
1479 1494
1480 1495 onode = node
1481 1496 # If we're doing a partial update, we need to skip updating
1482 1497 # the dirstate, so make a note of any partial-ness to the
1483 1498 # update here.
1484 1499 if matcher is None or matcher.always():
1485 1500 partial = False
1486 1501 else:
1487 1502 partial = True
1488 1503 with repo.wlock():
1489 1504 wc = repo[None]
1490 1505 pl = wc.parents()
1491 1506 p1 = pl[0]
1492 1507 pas = [None]
1493 1508 if ancestor is not None:
1494 1509 pas = [repo[ancestor]]
1495 1510
1496 1511 overwrite = force and not branchmerge
1497 1512
1498 1513 p2 = repo[node]
1499 1514 if pas[0] is None:
1500 1515 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1501 1516 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1502 1517 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1503 1518 else:
1504 1519 pas = [p1.ancestor(p2, warn=branchmerge)]
1505 1520
1506 1521 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1507 1522
1508 1523 ### check phase
1509 1524 if not overwrite:
1510 1525 if len(pl) > 1:
1511 1526 raise error.Abort(_("outstanding uncommitted merge"))
1512 1527 ms = mergestate.read(repo)
1513 1528 if list(ms.unresolved()):
1514 1529 raise error.Abort(_("outstanding merge conflicts"))
1515 1530 if branchmerge:
1516 1531 if pas == [p2]:
1517 1532 raise error.Abort(_("merging with a working directory ancestor"
1518 1533 " has no effect"))
1519 1534 elif pas == [p1]:
1520 1535 if not mergeancestor and p1.branch() == p2.branch():
1521 1536 raise error.Abort(_("nothing to merge"),
1522 1537 hint=_("use 'hg update' "
1523 1538 "or check 'hg heads'"))
1524 1539 if not force and (wc.files() or wc.deleted()):
1525 1540 raise error.Abort(_("uncommitted changes"),
1526 1541 hint=_("use 'hg status' to list changes"))
1527 1542 for s in sorted(wc.substate):
1528 1543 wc.sub(s).bailifchanged()
1529 1544
1530 1545 elif not overwrite:
1531 1546 if p1 == p2: # no-op update
1532 1547 # call the hooks and exit early
1533 1548 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1534 1549 repo.hook('update', parent1=xp2, parent2='', error=0)
1535 1550 return 0, 0, 0, 0
1536 1551
1537 1552 if pas not in ([p1], [p2]): # nonlinear
1538 1553 dirty = wc.dirty(missing=True)
1539 1554 if dirty or onode is None:
1540 1555 # Branching is a bit strange to ensure we do the minimal
1541 1556 # amount of call to obsolete.background.
1542 1557 foreground = obsolete.foreground(repo, [p1.node()])
1543 1558 # note: the <node> variable contains a random identifier
1544 1559 if repo[node].node() in foreground:
1545 1560 pas = [p1] # allow updating to successors
1546 1561 elif dirty:
1547 1562 msg = _("uncommitted changes")
1548 1563 if onode is None:
1549 1564 hint = _("commit and merge, or update --clean to"
1550 1565 " discard changes")
1551 1566 else:
1552 1567 hint = _("commit or update --clean to discard"
1553 1568 " changes")
1554 1569 raise error.Abort(msg, hint=hint)
1555 1570 else: # node is none
1556 1571 msg = _("not a linear update")
1557 1572 hint = _("merge or update --check to force update")
1558 1573 raise error.Abort(msg, hint=hint)
1559 1574 else:
1560 1575 # Allow jumping branches if clean and specific rev given
1561 1576 pas = [p1]
1562 1577
1563 1578 # deprecated config: merge.followcopies
1564 1579 followcopies = repo.ui.configbool('merge', 'followcopies', True)
1565 1580 if overwrite:
1566 1581 pas = [wc]
1567 1582 followcopies = False
1568 1583 elif pas == [p2]: # backwards
1569 1584 pas = [p1]
1570 1585 elif not pas[0]:
1571 1586 followcopies = False
1572 1587 if not branchmerge and not wc.dirty(missing=True):
1573 1588 followcopies = False
1574 1589
1575 1590 ### calculate phase
1576 1591 actionbyfile, diverge, renamedelete = calculateupdates(
1577 1592 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1578 1593 followcopies, matcher=matcher, mergeforce=mergeforce)
1579 1594
1580 1595 # Prompt and create actions. Most of this is in the resolve phase
1581 1596 # already, but we can't handle .hgsubstate in filemerge or
1582 1597 # subrepo.submerge yet so we have to keep prompting for it.
1583 1598 if '.hgsubstate' in actionbyfile:
1584 1599 f = '.hgsubstate'
1585 1600 m, args, msg = actionbyfile[f]
1586 1601 prompts = filemerge.partextras(labels)
1587 1602 prompts['f'] = f
1588 1603 if m == 'cd':
1589 1604 if repo.ui.promptchoice(
1590 1605 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1591 1606 "use (c)hanged version or (d)elete?"
1592 1607 "$$ &Changed $$ &Delete") % prompts, 0):
1593 1608 actionbyfile[f] = ('r', None, "prompt delete")
1594 1609 elif f in p1:
1595 1610 actionbyfile[f] = ('am', None, "prompt keep")
1596 1611 else:
1597 1612 actionbyfile[f] = ('a', None, "prompt keep")
1598 1613 elif m == 'dc':
1599 1614 f1, f2, fa, move, anc = args
1600 1615 flags = p2[f2].flags()
1601 1616 if repo.ui.promptchoice(
1602 1617 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1603 1618 "use (c)hanged version or leave (d)eleted?"
1604 1619 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1605 1620 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1606 1621 else:
1607 1622 del actionbyfile[f]
1608 1623
1609 1624 # Convert to dictionary-of-lists format
1610 1625 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1611 1626 for f, (m, args, msg) in actionbyfile.iteritems():
1612 1627 if m not in actions:
1613 1628 actions[m] = []
1614 1629 actions[m].append((f, args, msg))
1615 1630
1616 1631 if not util.fscasesensitive(repo.path):
1617 1632 # check collision between files only in p2 for clean update
1618 1633 if (not branchmerge and
1619 1634 (force or not wc.dirty(missing=True, branch=False))):
1620 1635 _checkcollision(repo, p2.manifest(), None)
1621 1636 else:
1622 1637 _checkcollision(repo, wc.manifest(), actions)
1623 1638
1624 1639 # divergent renames
1625 1640 for f, fl in sorted(diverge.iteritems()):
1626 1641 repo.ui.warn(_("note: possible conflict - %s was renamed "
1627 1642 "multiple times to:\n") % f)
1628 1643 for nf in fl:
1629 1644 repo.ui.warn(" %s\n" % nf)
1630 1645
1631 1646 # rename and delete
1632 1647 for f, fl in sorted(renamedelete.iteritems()):
1633 1648 repo.ui.warn(_("note: possible conflict - %s was deleted "
1634 1649 "and renamed to:\n") % f)
1635 1650 for nf in fl:
1636 1651 repo.ui.warn(" %s\n" % nf)
1637 1652
1638 1653 ### apply phase
1639 1654 if not branchmerge: # just jump to the new rev
1640 1655 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1641 1656 if not partial:
1642 1657 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1643 1658 # note that we're in the middle of an update
1644 1659 repo.vfs.write('updatestate', p2.hex())
1645 1660
1646 1661 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1647 1662
1648 1663 if not partial:
1649 1664 repo.dirstate.beginparentchange()
1650 1665 repo.setparents(fp1, fp2)
1651 1666 recordupdates(repo, actions, branchmerge)
1652 1667 # update completed, clear state
1653 1668 util.unlink(repo.join('updatestate'))
1654 1669
1655 1670 if not branchmerge:
1656 1671 repo.dirstate.setbranch(p2.branch())
1657 1672 repo.dirstate.endparentchange()
1658 1673
1659 1674 if not partial:
1660 1675 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1661 1676 return stats
1662 1677
1663 1678 def graft(repo, ctx, pctx, labels, keepparent=False):
1664 1679 """Do a graft-like merge.
1665 1680
1666 1681 This is a merge where the merge ancestor is chosen such that one
1667 1682 or more changesets are grafted onto the current changeset. In
1668 1683 addition to the merge, this fixes up the dirstate to include only
1669 1684 a single parent (if keepparent is False) and tries to duplicate any
1670 1685 renames/copies appropriately.
1671 1686
1672 1687 ctx - changeset to rebase
1673 1688 pctx - merge base, usually ctx.p1()
1674 1689 labels - merge labels eg ['local', 'graft']
1675 1690 keepparent - keep second parent if any
1676 1691
1677 1692 """
1678 1693 # If we're grafting a descendant onto an ancestor, be sure to pass
1679 1694 # mergeancestor=True to update. This does two things: 1) allows the merge if
1680 1695 # the destination is the same as the parent of the ctx (so we can use graft
1681 1696 # to copy commits), and 2) informs update that the incoming changes are
1682 1697 # newer than the destination so it doesn't prompt about "remote changed foo
1683 1698 # which local deleted".
1684 1699 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1685 1700
1686 1701 stats = update(repo, ctx.node(), True, True, pctx.node(),
1687 1702 mergeancestor=mergeancestor, labels=labels)
1688 1703
1689 1704 pother = nullid
1690 1705 parents = ctx.parents()
1691 1706 if keepparent and len(parents) == 2 and pctx in parents:
1692 1707 parents.remove(pctx)
1693 1708 pother = parents[0].node()
1694 1709
1695 1710 repo.dirstate.beginparentchange()
1696 1711 repo.setparents(repo['.'].node(), pother)
1697 1712 repo.dirstate.write(repo.currenttransaction())
1698 1713 # fix up dirstate for copies and renames
1699 1714 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1700 1715 repo.dirstate.endparentchange()
1701 1716 return stats
@@ -1,365 +1,500 b''
1 1 Criss cross merging
2 2
3 3 $ hg init criss-cross
4 4 $ cd criss-cross
5 5 $ echo '0 base' > f1
6 6 $ echo '0 base' > f2
7 7 $ hg ci -Aqm '0 base'
8 8
9 9 $ echo '1 first change' > f1
10 10 $ hg ci -m '1 first change f1'
11 11
12 12 $ hg up -qr0
13 13 $ echo '2 first change' > f2
14 $ mkdir d1
15 $ echo '0 base' > d1/f3
16 $ echo '0 base' > d1/f4
17 $ hg add -q d1
14 18 $ hg ci -qm '2 first change f2'
15 19
16 20 $ hg merge -qr 1
21 $ hg rm d1/f3
22 $ hg mv -q d1 d2
17 23 $ hg ci -m '3 merge'
18 24
19 25 $ hg up -qr2
20 26 $ hg merge -qr1
21 27 $ hg ci -qm '4 merge'
22 28
23 29 $ echo '5 second change' > f1
24 30 $ hg ci -m '5 second change f1'
25 31
26 32 $ hg up -r3
27 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
33 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
28 34 $ echo '6 second change' > f2
29 35 $ hg ci -m '6 second change f2'
30 36
31 37 $ hg log -G
32 @ changeset: 6:3b08d01b0ab5
38 @ changeset: 6:6373bbfdae1d
33 39 | tag: tip
34 | parent: 3:cf89f02107e5
40 | parent: 3:c202c8af058d
35 41 | user: test
36 42 | date: Thu Jan 01 00:00:00 1970 +0000
37 43 | summary: 6 second change f2
38 44 |
39 | o changeset: 5:adfe50279922
45 | o changeset: 5:e673248094b1
40 46 | | user: test
41 47 | | date: Thu Jan 01 00:00:00 1970 +0000
42 48 | | summary: 5 second change f1
43 49 | |
44 | o changeset: 4:7d3e55501ae6
45 | |\ parent: 2:40663881a6dd
50 | o changeset: 4:177f58377c06
51 | |\ parent: 2:d1d156401c1b
46 52 | | | parent: 1:0f6b37dbe527
47 53 | | | user: test
48 54 | | | date: Thu Jan 01 00:00:00 1970 +0000
49 55 | | | summary: 4 merge
50 56 | | |
51 o---+ changeset: 3:cf89f02107e5
52 | | | parent: 2:40663881a6dd
57 o---+ changeset: 3:c202c8af058d
58 | | | parent: 2:d1d156401c1b
53 59 |/ / parent: 1:0f6b37dbe527
54 60 | | user: test
55 61 | | date: Thu Jan 01 00:00:00 1970 +0000
56 62 | | summary: 3 merge
57 63 | |
58 | o changeset: 2:40663881a6dd
64 | o changeset: 2:d1d156401c1b
59 65 | | parent: 0:40494bf2444c
60 66 | | user: test
61 67 | | date: Thu Jan 01 00:00:00 1970 +0000
62 68 | | summary: 2 first change f2
63 69 | |
64 70 o | changeset: 1:0f6b37dbe527
65 71 |/ user: test
66 72 | date: Thu Jan 01 00:00:00 1970 +0000
67 73 | summary: 1 first change f1
68 74 |
69 75 o changeset: 0:40494bf2444c
70 76 user: test
71 77 date: Thu Jan 01 00:00:00 1970 +0000
72 78 summary: 0 base
73 79
74 80
75 81 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor='!'
76 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
77 alternatively, use --config merge.preferancestor=40663881a6dd
82 note: using 0f6b37dbe527 as ancestor of 6373bbfdae1d and e673248094b1
83 alternatively, use --config merge.preferancestor=d1d156401c1b
78 84 searching for copies back to rev 3
85 unmatched files in local:
86 d2/f4
87 unmatched files in other:
88 d1/f3
89 d1/f4
90 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
91 src: 'd1/f4' -> dst: 'd2/f4'
92 checking for directory renames
93 discovered dir src: 'd1/' -> dst: 'd2/'
94 pending file src: 'd1/f3' -> dst: 'd2/f3'
95 pending file src: 'd1/f4' -> dst: 'd2/f4'
79 96 resolving manifests
80 97 branchmerge: True, force: False, partial: False
81 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
98 ancestor: 0f6b37dbe527, local: 6373bbfdae1d+, remote: e673248094b1
99 preserving d2/f4 for resolve of d2/f4
82 100 preserving f2 for resolve of f2
83 101 f1: remote is newer -> g
84 102 getting f1
103 d2/f3: local directory rename - get from d1/f3 -> dg
104 getting d1/f3 to d2/f3
105 d2/f4: local directory rename, both created -> m (premerge)
85 106 f2: versions differ -> m (premerge)
86 107 picked tool ':dump' for f2 (binary False symlink False changedelete False)
87 108 merging f2
88 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
109 my f2@6373bbfdae1d+ other f2@e673248094b1 ancestor f2@0f6b37dbe527
89 110 f2: versions differ -> m (merge)
90 111 picked tool ':dump' for f2 (binary False symlink False changedelete False)
91 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@0f6b37dbe527
92 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
112 my f2@6373bbfdae1d+ other f2@e673248094b1 ancestor f2@0f6b37dbe527
113 3 files updated, 0 files merged, 0 files removed, 1 files unresolved
93 114 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
94 115 [1]
95 116
96 $ f --dump *
117 $ f --dump --recurse *
118 d2: directory with 2 files
119 d2/f3:
120 >>>
121 0 base
122 <<<
123 d2/f4:
124 >>>
125 0 base
126 <<<
97 127 f1:
98 128 >>>
99 129 5 second change
100 130 <<<
101 131 f2:
102 132 >>>
103 133 6 second change
104 134 <<<
105 135 f2.base:
106 136 >>>
107 137 0 base
108 138 <<<
109 139 f2.local:
110 140 >>>
111 141 6 second change
112 142 <<<
113 143 f2.orig:
114 144 >>>
115 145 6 second change
116 146 <<<
117 147 f2.other:
118 148 >>>
119 149 2 first change
120 150 <<<
121 151
122 152 $ hg up -qC .
123 153 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
124 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
125 alternatively, use --config merge.preferancestor=0f6b37dbe527
154 note: using 0f6b37dbe527 as ancestor of 6373bbfdae1d and e673248094b1
155 alternatively, use --config merge.preferancestor=d1d156401c1b
126 156 resolving manifests
127 merging f1
128 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
157 getting f1
158 getting d1/f3 to d2/f3
159 merging f2
160 3 files updated, 0 files merged, 0 files removed, 1 files unresolved
129 161 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
130 162 [1]
131 163
132 164 Redo merge with merge.preferancestor="*" to enable bid merge
133 165
134 166 $ rm f*
135 167 $ hg up -qC .
136 168 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor="*"
137 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
169 note: merging 6373bbfdae1d+ and e673248094b1 using bids from ancestors 0f6b37dbe527 and d1d156401c1b
138 170
139 171 calculating bids for ancestor 0f6b37dbe527
140 172 searching for copies back to rev 3
173 unmatched files in local:
174 d2/f4
175 unmatched files in other:
176 d1/f3
177 d1/f4
178 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
179 src: 'd1/f4' -> dst: 'd2/f4'
180 checking for directory renames
181 discovered dir src: 'd1/' -> dst: 'd2/'
182 pending file src: 'd1/f3' -> dst: 'd2/f3'
183 pending file src: 'd1/f4' -> dst: 'd2/f4'
141 184 resolving manifests
142 185 branchmerge: True, force: False, partial: False
143 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
186 ancestor: 0f6b37dbe527, local: 6373bbfdae1d+, remote: e673248094b1
187 d2/f3: local directory rename - get from d1/f3 -> dg
188 d2/f4: local directory rename, both created -> m
144 189 f1: remote is newer -> g
145 190 f2: versions differ -> m
146 191
147 calculating bids for ancestor 40663881a6dd
192 calculating bids for ancestor d1d156401c1b
148 193 searching for copies back to rev 3
194 unmatched files in local:
195 d2/f4
196 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
197 src: 'd1/f4' -> dst: 'd2/f4'
198 checking for directory renames
199 discovered dir src: 'd1/' -> dst: 'd2/'
149 200 resolving manifests
150 201 branchmerge: True, force: False, partial: False
151 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
202 ancestor: d1d156401c1b, local: 6373bbfdae1d+, remote: e673248094b1
152 203 f1: versions differ -> m
153 204 f2: remote unchanged -> k
154 205
155 206 auction for merging merge bids
207 d2/f3: consensus for dg
208 d2/f4: consensus for m
156 209 f1: picking 'get' action
157 210 f2: picking 'keep' action
158 211 end of auction
159 212
213 preserving d2/f4 for resolve of d2/f4
160 214 f1: remote is newer -> g
161 215 getting f1
162 216 f2: remote unchanged -> k
163 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
217 d2/f3: local directory rename - get from d1/f3 -> dg
218 getting d1/f3 to d2/f3
219 d2/f4: local directory rename, both created -> m (premerge)
220 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 221 (branch merge, don't forget to commit)
165 222
166 $ f --dump *
223 $ f --dump --recurse *
224 d2: directory with 2 files
225 d2/f3:
226 >>>
227 0 base
228 <<<
229 d2/f4:
230 >>>
231 0 base
232 <<<
167 233 f1:
168 234 >>>
169 235 5 second change
170 236 <<<
171 237 f2:
172 238 >>>
173 239 6 second change
174 240 <<<
175 241
176 242
177 243 The other way around:
178 244
179 245 $ hg up -C -r5
180 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
246 4 files updated, 0 files merged, 1 files removed, 0 files unresolved
181 247 $ hg merge -v --debug --config merge.preferancestor="*"
182 note: merging adfe50279922+ and 3b08d01b0ab5 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
248 note: merging e673248094b1+ and 6373bbfdae1d using bids from ancestors 0f6b37dbe527 and d1d156401c1b
183 249
184 250 calculating bids for ancestor 0f6b37dbe527
185 251 searching for copies back to rev 3
252 unmatched files in local:
253 d1/f3
254 d1/f4
255 unmatched files in other:
256 d2/f4
257 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
258 src: 'd1/f4' -> dst: 'd2/f4'
259 checking for directory renames
260 discovered dir src: 'd1/' -> dst: 'd2/'
261 pending file src: 'd1/f3' -> dst: 'd2/f3'
262 pending file src: 'd1/f4' -> dst: 'd2/f4'
186 263 resolving manifests
187 264 branchmerge: True, force: False, partial: False
188 ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
265 ancestor: 0f6b37dbe527, local: e673248094b1+, remote: 6373bbfdae1d
266 d2/f3: remote directory rename - move from d1/f3 -> dm
267 d2/f4: remote directory rename, both created -> m
189 268 f1: remote unchanged -> k
190 269 f2: versions differ -> m
191 270
192 calculating bids for ancestor 40663881a6dd
271 calculating bids for ancestor d1d156401c1b
193 272 searching for copies back to rev 3
273 unmatched files in other:
274 d2/f4
275 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
276 src: 'd1/f4' -> dst: 'd2/f4'
277 checking for directory renames
278 discovered dir src: 'd1/' -> dst: 'd2/'
194 279 resolving manifests
195 280 branchmerge: True, force: False, partial: False
196 ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
281 ancestor: d1d156401c1b, local: e673248094b1+, remote: 6373bbfdae1d
282 d1/f3: other deleted -> r
283 d1/f4: other deleted -> r
284 d2/f4: remote created -> g
197 285 f1: versions differ -> m
198 286 f2: remote is newer -> g
199 287
200 288 auction for merging merge bids
289 d1/f3: consensus for r
290 d1/f4: consensus for r
291 d2/f3: consensus for dm
292 d2/f4: picking 'get' action
201 293 f1: picking 'keep' action
202 294 f2: picking 'get' action
203 295 end of auction
204 296
297 d1/f3: other deleted -> r
298 removing d1/f3
299 d1/f4: other deleted -> r
300 removing d1/f4
301 d2/f4: remote created -> g
302 getting d2/f4
205 303 f2: remote is newer -> g
206 304 getting f2
207 305 f1: remote unchanged -> k
208 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
306 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
209 307 (branch merge, don't forget to commit)
210 308
211 $ f --dump *
309 $ f --dump --recurse *
310 d2: directory with 2 files
311 d2/f3:
312 >>>
313 0 base
314 <<<
315 d2/f4:
316 >>>
317 0 base
318 <<<
212 319 f1:
213 320 >>>
214 321 5 second change
215 322 <<<
216 323 f2:
217 324 >>>
218 325 6 second change
219 326 <<<
220 327
221 328 Verify how the output looks and and how verbose it is:
222 329
223 330 $ hg up -qC
224 331 $ hg merge
225 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
332 2 files updated, 0 files merged, 2 files removed, 0 files unresolved
226 333 (branch merge, don't forget to commit)
227 334
228 335 $ hg up -qC tip
229 336 $ hg merge -v
230 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
337 note: merging 6373bbfdae1d+ and e673248094b1 using bids from ancestors 0f6b37dbe527 and d1d156401c1b
231 338
232 339 calculating bids for ancestor 0f6b37dbe527
233 340 resolving manifests
234 341
235 calculating bids for ancestor 40663881a6dd
342 calculating bids for ancestor d1d156401c1b
236 343 resolving manifests
237 344
238 345 auction for merging merge bids
346 d2/f3: consensus for dg
347 d2/f4: consensus for m
239 348 f1: picking 'get' action
240 349 f2: picking 'keep' action
241 350 end of auction
242 351
243 352 getting f1
244 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
353 getting d1/f3 to d2/f3
354 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
245 355 (branch merge, don't forget to commit)
246 356
247 357 $ hg up -qC
248 358 $ hg merge -v --debug --config merge.preferancestor="*"
249 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
359 note: merging 6373bbfdae1d+ and e673248094b1 using bids from ancestors 0f6b37dbe527 and d1d156401c1b
250 360
251 361 calculating bids for ancestor 0f6b37dbe527
252 362 searching for copies back to rev 3
363 unmatched files in local:
364 d2/f4
365 unmatched files in other:
366 d1/f3
367 d1/f4
368 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
369 src: 'd1/f4' -> dst: 'd2/f4'
370 checking for directory renames
371 discovered dir src: 'd1/' -> dst: 'd2/'
372 pending file src: 'd1/f3' -> dst: 'd2/f3'
373 pending file src: 'd1/f4' -> dst: 'd2/f4'
253 374 resolving manifests
254 375 branchmerge: True, force: False, partial: False
255 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
376 ancestor: 0f6b37dbe527, local: 6373bbfdae1d+, remote: e673248094b1
377 d2/f3: local directory rename - get from d1/f3 -> dg
378 d2/f4: local directory rename, both created -> m
256 379 f1: remote is newer -> g
257 380 f2: versions differ -> m
258 381
259 calculating bids for ancestor 40663881a6dd
382 calculating bids for ancestor d1d156401c1b
260 383 searching for copies back to rev 3
384 unmatched files in local:
385 d2/f4
386 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
387 src: 'd1/f4' -> dst: 'd2/f4'
388 checking for directory renames
389 discovered dir src: 'd1/' -> dst: 'd2/'
261 390 resolving manifests
262 391 branchmerge: True, force: False, partial: False
263 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
392 ancestor: d1d156401c1b, local: 6373bbfdae1d+, remote: e673248094b1
264 393 f1: versions differ -> m
265 394 f2: remote unchanged -> k
266 395
267 396 auction for merging merge bids
397 d2/f3: consensus for dg
398 d2/f4: consensus for m
268 399 f1: picking 'get' action
269 400 f2: picking 'keep' action
270 401 end of auction
271 402
403 preserving d2/f4 for resolve of d2/f4
272 404 f1: remote is newer -> g
273 405 getting f1
274 406 f2: remote unchanged -> k
275 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 d2/f3: local directory rename - get from d1/f3 -> dg
408 getting d1/f3 to d2/f3
409 d2/f4: local directory rename, both created -> m (premerge)
410 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
276 411 (branch merge, don't forget to commit)
277 412
278 413 $ cd ..
279 414
280 415 http://stackoverflow.com/questions/9350005/how-do-i-specify-a-merge-base-to-use-in-a-hg-merge/9430810
281 416
282 417 $ hg init ancestor-merging
283 418 $ cd ancestor-merging
284 419 $ echo a > x
285 420 $ hg commit -A -m a x
286 421 $ hg update -q 0
287 422 $ echo b >> x
288 423 $ hg commit -m b
289 424 $ hg update -q 0
290 425 $ echo c >> x
291 426 $ hg commit -qm c
292 427 $ hg update -q 1
293 428 $ hg merge -q --tool internal:local 2
294 429 $ echo c >> x
295 430 $ hg commit -m bc
296 431 $ hg update -q 2
297 432 $ hg merge -q --tool internal:local 1
298 433 $ echo b >> x
299 434 $ hg commit -qm cb
300 435
301 436 $ hg merge --config merge.preferancestor='!'
302 437 note: using 70008a2163f6 as ancestor of 0d355fdef312 and 4b8b546a3eef
303 438 alternatively, use --config merge.preferancestor=b211bbc6eb3c
304 439 merging x
305 440 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
306 441 (branch merge, don't forget to commit)
307 442 $ cat x
308 443 a
309 444 c
310 445 b
311 446 c
312 447
313 448 $ hg up -qC .
314 449
315 450 $ hg merge --config merge.preferancestor=b211bbc6eb3c
316 451 note: using b211bbc6eb3c as ancestor of 0d355fdef312 and 4b8b546a3eef
317 452 alternatively, use --config merge.preferancestor=70008a2163f6
318 453 merging x
319 454 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
320 455 (branch merge, don't forget to commit)
321 456 $ cat x
322 457 a
323 458 b
324 459 c
325 460 b
326 461
327 462 $ hg up -qC .
328 463
329 464 $ hg merge -v --config merge.preferancestor="*"
330 465 note: merging 0d355fdef312+ and 4b8b546a3eef using bids from ancestors 70008a2163f6 and b211bbc6eb3c
331 466
332 467 calculating bids for ancestor 70008a2163f6
333 468 resolving manifests
334 469
335 470 calculating bids for ancestor b211bbc6eb3c
336 471 resolving manifests
337 472
338 473 auction for merging merge bids
339 474 x: multiple bids for merge action:
340 475 versions differ -> m
341 476 versions differ -> m
342 477 x: ambiguous merge - picked m action
343 478 end of auction
344 479
345 480 merging x
346 481 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
347 482 (branch merge, don't forget to commit)
348 483 $ cat x
349 484 a
350 485 c
351 486 b
352 487 c
353 488
354 489 Verify that the old context ancestor works with / despite preferancestor:
355 490
356 491 $ hg log -r 'ancestor(head())' --config merge.preferancestor=1 -T '{rev}\n'
357 492 1
358 493 $ hg log -r 'ancestor(head())' --config merge.preferancestor=2 -T '{rev}\n'
359 494 2
360 495 $ hg log -r 'ancestor(head())' --config merge.preferancestor=3 -T '{rev}\n'
361 496 1
362 497 $ hg log -r 'ancestor(head())' --config merge.preferancestor='1337 * - 2' -T '{rev}\n'
363 498 2
364 499
365 500 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now