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