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