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