##// END OF EJS Templates
merge: let bid merge work on the file->action dict...
Martin von Zweigbergk -
r23638:09be050c default
parent child Browse files
Show More
@@ -1,1183 +1,1182 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 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 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
379 379
380 380 # manifests fetched in order are going to be faster, so prime the caches
381 381 [x.manifest() for x in
382 382 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
383 383
384 384 if followcopies:
385 385 ret = copies.mergecopies(repo, wctx, p2, pa)
386 386 copy, movewithdir, diverge, renamedelete = ret
387 387
388 388 repo.ui.note(_("resolving manifests\n"))
389 389 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
390 390 % (bool(branchmerge), bool(force), bool(partial)))
391 391 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
392 392
393 393 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
394 394 copied = set(copy.values())
395 395 copied.update(movewithdir.values())
396 396
397 397 if '.hgsubstate' in m1:
398 398 # check whether sub state is modified
399 399 for s in sorted(wctx.substate):
400 400 if wctx.sub(s).dirty():
401 401 m1['.hgsubstate'] += '+'
402 402 break
403 403
404 404 aborts = []
405 405 # Compare manifests
406 406 diff = m1.diff(m2)
407 407
408 408 actions = {}
409 409 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
410 410 if partial and not partial(f):
411 411 continue
412 412 if n1 and n2: # file exists on both local and remote side
413 413 if f not in ma:
414 414 fa = copy.get(f, None)
415 415 if fa is not None:
416 416 actions[f] = ('m', (f, f, fa, False, pa.node()),
417 417 "both renamed from " + fa)
418 418 else:
419 419 actions[f] = ('m', (f, f, None, False, pa.node()),
420 420 "both created")
421 421 else:
422 422 a = ma[f]
423 423 fla = ma.flags(f)
424 424 nol = 'l' not in fl1 + fl2 + fla
425 425 if n2 == a and fl2 == fla:
426 426 actions[f] = ('k' , (), "remote unchanged")
427 427 elif n1 == a and fl1 == fla: # local unchanged - use remote
428 428 if n1 == n2: # optimization: keep local content
429 429 actions[f] = ('e', (fl2,), "update permissions")
430 430 else:
431 431 actions[f] = ('g', (fl2,), "remote is newer")
432 432 elif nol and n2 == a: # remote only changed 'x'
433 433 actions[f] = ('e', (fl2,), "update permissions")
434 434 elif nol and n1 == a: # local only changed 'x'
435 435 actions[f] = ('g', (fl1,), "remote is newer")
436 436 else: # both changed something
437 437 actions[f] = ('m', (f, f, f, False, pa.node()),
438 438 "versions differ")
439 439 elif n1: # file exists only on local side
440 440 if f in copied:
441 441 pass # we'll deal with it on m2 side
442 442 elif f in movewithdir: # directory rename, move local
443 443 f2 = movewithdir[f]
444 444 if f2 in m2:
445 445 actions[f2] = ('m', (f, f2, None, True, pa.node()),
446 446 "remote directory rename, both created")
447 447 else:
448 448 actions[f2] = ('dm', (f, fl1),
449 449 "remote directory rename - move from " + f)
450 450 elif f in copy:
451 451 f2 = copy[f]
452 452 actions[f] = ('m', (f, f2, f2, False, pa.node()),
453 453 "local copied/moved from " + f2)
454 454 elif f in ma: # clean, a different, no remote
455 455 if n1 != ma[f]:
456 456 if acceptremote:
457 457 actions[f] = ('r', None, "remote delete")
458 458 else:
459 459 actions[f] = ('cd', None, "prompt changed/deleted")
460 460 elif n1[20:] == 'a':
461 461 # This extra 'a' is added by working copy manifest to mark
462 462 # the file as locally added. We should forget it instead of
463 463 # deleting it.
464 464 actions[f] = ('f', None, "remote deleted")
465 465 else:
466 466 actions[f] = ('r', None, "other deleted")
467 467 elif n2: # file exists only on remote side
468 468 if f in copied:
469 469 pass # we'll deal with it on m1 side
470 470 elif f in movewithdir:
471 471 f2 = movewithdir[f]
472 472 if f2 in m1:
473 473 actions[f2] = ('m', (f2, f, None, False, pa.node()),
474 474 "local directory rename, both created")
475 475 else:
476 476 actions[f2] = ('dg', (f, fl2),
477 477 "local directory rename - get from " + f)
478 478 elif f in copy:
479 479 f2 = copy[f]
480 480 if f2 in m2:
481 481 actions[f] = ('m', (f2, f, f2, False, pa.node()),
482 482 "remote copied from " + f2)
483 483 else:
484 484 actions[f] = ('m', (f2, f, f2, True, pa.node()),
485 485 "remote moved from " + f2)
486 486 elif f not in ma:
487 487 # local unknown, remote created: the logic is described by the
488 488 # following table:
489 489 #
490 490 # force branchmerge different | action
491 491 # n * n | get
492 492 # n * y | abort
493 493 # y n * | get
494 494 # y y n | get
495 495 # y y y | merge
496 496 #
497 497 # Checking whether the files are different is expensive, so we
498 498 # don't do that when we can avoid it.
499 499 if force and not branchmerge:
500 500 actions[f] = ('g', (fl2,), "remote created")
501 501 else:
502 502 different = _checkunknownfile(repo, wctx, p2, f)
503 503 if force and branchmerge and different:
504 504 actions[f] = ('m', (f, f, None, False, pa.node()),
505 505 "remote differs from untracked local")
506 506 elif not force and different:
507 507 aborts.append((f, 'ud'))
508 508 else:
509 509 actions[f] = ('g', (fl2,), "remote created")
510 510 elif n2 != ma[f]:
511 511 different = _checkunknownfile(repo, wctx, p2, f)
512 512 if not force and different:
513 513 aborts.append((f, 'ud'))
514 514 else:
515 515 if acceptremote:
516 516 actions[f] = ('g', (fl2,), "remote recreating")
517 517 else:
518 518 actions[f] = ('dc', (fl2,), "prompt deleted/changed")
519 519
520 520 for f, m in sorted(aborts):
521 521 if m == 'ud':
522 522 repo.ui.warn(_("%s: untracked file differs\n") % f)
523 523 else: assert False, m
524 524 if aborts:
525 525 raise util.Abort(_("untracked files in working directory differ "
526 526 "from files in requested revision"))
527 527
528 # Convert to dictionary-of-lists format
529 actionbyfile = actions
530 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
531 for f, (m, args, msg) in actionbyfile.iteritems():
532 actions[m].append((f, args, msg))
533
534 528 return actions, diverge, renamedelete
535 529
536 530 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
537 531 """Resolves false conflicts where the nodeid changed but the content
538 532 remained the same."""
539 533
540 534 cdactions = []
541 535 for action in actions['cd']:
542 536 f = action[0]
543 537 if f in ancestor and not wctx[f].cmp(ancestor[f]):
544 538 # local did change but ended up with same content
545 539 actions['r'].append((f, None, "prompt same"))
546 540 else:
547 541 cdactions.append(action)
548 542 actions['cd'] = cdactions
549 543
550 544 dcactions = []
551 545 for action in actions['dc']:
552 546 f = action[0]
553 547 if f in ancestor and not mctx[f].cmp(ancestor[f]):
554 548 # remote did change but ended up with same content
555 549 pass # don't get = keep local deleted
556 550 else:
557 551 dcactions.append(action)
558 552 actions['dc'] = dcactions
559 553
560 554 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
561 555 acceptremote, followcopies):
562 556 "Calculate the actions needed to merge mctx into wctx using ancestors"
563 557
564 558 if len(ancestors) == 1: # default
565 559 actions, diverge, renamedelete = manifestmerge(
566 560 repo, wctx, mctx, ancestors[0], branchmerge, force, partial,
567 561 acceptremote, followcopies)
568 562
569 563 else: # only when merge.preferancestor=* - the default
570 564 repo.ui.note(
571 565 _("note: merging %s and %s using bids from ancestors %s\n") %
572 566 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
573 567
574 568 # Call for bids
575 569 fbids = {} # mapping filename to bids (action method to list af actions)
576 570 diverge, renamedelete = None, None
577 571 for ancestor in ancestors:
578 572 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
579 573 actions, diverge1, renamedelete1 = manifestmerge(
580 574 repo, wctx, mctx, ancestor, branchmerge, force, partial,
581 575 acceptremote, followcopies)
582 576 if diverge is None: # and renamedelete is None.
583 577 # Arbitrarily pick warnings from first iteration
584 578 diverge = diverge1
585 579 renamedelete = renamedelete1
586 for m, l in sorted(actions.items()):
587 for a in l:
588 f, args, msg = a
589 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
590 if f in fbids:
591 d = fbids[f]
592 if m in d:
593 d[m].append(a)
594 else:
595 d[m] = [a]
580 for f, a in sorted(actions.iteritems()):
581 m, args, msg = a
582 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
583 if f in fbids:
584 d = fbids[f]
585 if m in d:
586 d[m].append(a)
596 587 else:
597 fbids[f] = {m: [a]}
588 d[m] = [a]
589 else:
590 fbids[f] = {m: [a]}
598 591
599 592 # Pick the best bid for each file
600 593 repo.ui.note(_('\nauction for merging merge bids\n'))
601 actions = dict((m, []) for m in actions.keys())
594 actions = {}
602 595 for f, bids in sorted(fbids.items()):
603 596 # bids is a mapping from action method to list af actions
604 597 # Consensus?
605 598 if len(bids) == 1: # all bids are the same kind of method
606 599 m, l = bids.items()[0]
607 600 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
608 601 repo.ui.note(" %s: consensus for %s\n" % (f, m))
609 actions[m].append(l[0])
602 actions[f] = l[0]
610 603 continue
611 604 # If keep is an option, just do it.
612 605 if 'k' in bids:
613 606 repo.ui.note(" %s: picking 'keep' action\n" % f)
614 actions['k'].append(bids['k'][0])
607 actions[f] = bids['k'][0]
615 608 continue
616 609 # If there are gets and they all agree [how could they not?], do it.
617 610 if 'g' in bids:
618 611 ga0 = bids['g'][0]
619 612 if util.all(a == ga0 for a in bids['g'][1:]):
620 613 repo.ui.note(" %s: picking 'get' action\n" % f)
621 actions['g'].append(ga0)
614 actions[f] = ga0
622 615 continue
623 616 # TODO: Consider other simple actions such as mode changes
624 617 # Handle inefficient democrazy.
625 618 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
626 619 for m, l in sorted(bids.items()):
627 620 for _f, args, msg in l:
628 621 repo.ui.note(' %s -> %s\n' % (msg, m))
629 622 # Pick random action. TODO: Instead, prompt user when resolving
630 623 m, l = bids.items()[0]
631 624 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
632 625 (f, m))
633 actions[m].append(l[0])
626 actions[f] = l[0]
634 627 continue
635 628 repo.ui.note(_('end of auction\n\n'))
636 629
630 # Convert to dictionary-of-lists format
631 actionbyfile = actions
632 actions = dict((m, []) for m in 'a f g cd dc r dm dg m e k'.split())
633 for f, (m, args, msg) in actionbyfile.iteritems():
634 actions[m].append((f, args, msg))
635
637 636 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
638 637
639 638 if wctx.rev() is None:
640 639 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
641 640 actions['r'].extend(ractions)
642 641 actions['f'].extend(factions)
643 642
644 643 return actions, diverge, renamedelete
645 644
646 645 def batchremove(repo, actions):
647 646 """apply removes to the working directory
648 647
649 648 yields tuples for progress updates
650 649 """
651 650 verbose = repo.ui.verbose
652 651 unlink = util.unlinkpath
653 652 wjoin = repo.wjoin
654 653 audit = repo.wopener.audit
655 654 i = 0
656 655 for f, args, msg in actions:
657 656 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
658 657 if verbose:
659 658 repo.ui.note(_("removing %s\n") % f)
660 659 audit(f)
661 660 try:
662 661 unlink(wjoin(f), ignoremissing=True)
663 662 except OSError, inst:
664 663 repo.ui.warn(_("update failed to remove %s: %s!\n") %
665 664 (f, inst.strerror))
666 665 if i == 100:
667 666 yield i, f
668 667 i = 0
669 668 i += 1
670 669 if i > 0:
671 670 yield i, f
672 671
673 672 def batchget(repo, mctx, actions):
674 673 """apply gets to the working directory
675 674
676 675 mctx is the context to get from
677 676
678 677 yields tuples for progress updates
679 678 """
680 679 verbose = repo.ui.verbose
681 680 fctx = mctx.filectx
682 681 wwrite = repo.wwrite
683 682 i = 0
684 683 for f, args, msg in actions:
685 684 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
686 685 if verbose:
687 686 repo.ui.note(_("getting %s\n") % f)
688 687 wwrite(f, fctx(f).data(), args[0])
689 688 if i == 100:
690 689 yield i, f
691 690 i = 0
692 691 i += 1
693 692 if i > 0:
694 693 yield i, f
695 694
696 695 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
697 696 """apply the merge action list to the working directory
698 697
699 698 wctx is the working copy context
700 699 mctx is the context to be merged into the working copy
701 700
702 701 Return a tuple of counts (updated, merged, removed, unresolved) that
703 702 describes how many files were affected by the update.
704 703 """
705 704
706 705 updated, merged, removed, unresolved = 0, 0, 0, 0
707 706 ms = mergestate(repo)
708 707 ms.reset(wctx.p1().node(), mctx.node())
709 708 moves = []
710 709 for m, l in actions.items():
711 710 l.sort()
712 711
713 712 # prescan for merges
714 713 for f, args, msg in actions['m']:
715 714 f1, f2, fa, move, anc = args
716 715 if f == '.hgsubstate': # merged internally
717 716 continue
718 717 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
719 718 fcl = wctx[f1]
720 719 fco = mctx[f2]
721 720 actx = repo[anc]
722 721 if fa in actx:
723 722 fca = actx[fa]
724 723 else:
725 724 fca = repo.filectx(f1, fileid=nullrev)
726 725 ms.add(fcl, fco, fca, f)
727 726 if f1 != f and move:
728 727 moves.append(f1)
729 728
730 729 audit = repo.wopener.audit
731 730 _updating = _('updating')
732 731 _files = _('files')
733 732 progress = repo.ui.progress
734 733
735 734 # remove renamed files after safely stored
736 735 for f in moves:
737 736 if os.path.lexists(repo.wjoin(f)):
738 737 repo.ui.debug("removing %s\n" % f)
739 738 audit(f)
740 739 util.unlinkpath(repo.wjoin(f))
741 740
742 741 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
743 742
744 743 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
745 744 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
746 745
747 746 # remove in parallel (must come first)
748 747 z = 0
749 748 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
750 749 for i, item in prog:
751 750 z += i
752 751 progress(_updating, z, item=item, total=numupdates, unit=_files)
753 752 removed = len(actions['r'])
754 753
755 754 # get in parallel
756 755 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
757 756 for i, item in prog:
758 757 z += i
759 758 progress(_updating, z, item=item, total=numupdates, unit=_files)
760 759 updated = len(actions['g'])
761 760
762 761 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
763 762 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
764 763
765 764 # forget (manifest only, just log it) (must come first)
766 765 for f, args, msg in actions['f']:
767 766 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
768 767 z += 1
769 768 progress(_updating, z, item=f, total=numupdates, unit=_files)
770 769
771 770 # re-add (manifest only, just log it)
772 771 for f, args, msg in actions['a']:
773 772 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
774 773 z += 1
775 774 progress(_updating, z, item=f, total=numupdates, unit=_files)
776 775
777 776 # keep (noop, just log it)
778 777 for f, args, msg in actions['k']:
779 778 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
780 779 # no progress
781 780
782 781 # merge
783 782 for f, args, msg in actions['m']:
784 783 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
785 784 z += 1
786 785 progress(_updating, z, item=f, total=numupdates, unit=_files)
787 786 if f == '.hgsubstate': # subrepo states need updating
788 787 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
789 788 overwrite)
790 789 continue
791 790 audit(f)
792 791 r = ms.resolve(f, wctx, labels=labels)
793 792 if r is not None and r > 0:
794 793 unresolved += 1
795 794 else:
796 795 if r is None:
797 796 updated += 1
798 797 else:
799 798 merged += 1
800 799
801 800 # directory rename, move local
802 801 for f, args, msg in actions['dm']:
803 802 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
804 803 z += 1
805 804 progress(_updating, z, item=f, total=numupdates, unit=_files)
806 805 f0, flags = args
807 806 repo.ui.note(_("moving %s to %s\n") % (f0, f))
808 807 audit(f)
809 808 repo.wwrite(f, wctx.filectx(f0).data(), flags)
810 809 util.unlinkpath(repo.wjoin(f0))
811 810 updated += 1
812 811
813 812 # local directory rename, get
814 813 for f, args, msg in actions['dg']:
815 814 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
816 815 z += 1
817 816 progress(_updating, z, item=f, total=numupdates, unit=_files)
818 817 f0, flags = args
819 818 repo.ui.note(_("getting %s to %s\n") % (f0, f))
820 819 repo.wwrite(f, mctx.filectx(f0).data(), flags)
821 820 updated += 1
822 821
823 822 # exec
824 823 for f, args, msg in actions['e']:
825 824 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
826 825 z += 1
827 826 progress(_updating, z, item=f, total=numupdates, unit=_files)
828 827 flags, = args
829 828 audit(f)
830 829 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
831 830 updated += 1
832 831
833 832 ms.commit()
834 833 progress(_updating, None, total=numupdates, unit=_files)
835 834
836 835 return updated, merged, removed, unresolved
837 836
838 837 def recordupdates(repo, actions, branchmerge):
839 838 "record merge actions to the dirstate"
840 839 # remove (must come first)
841 840 for f, args, msg in actions['r']:
842 841 if branchmerge:
843 842 repo.dirstate.remove(f)
844 843 else:
845 844 repo.dirstate.drop(f)
846 845
847 846 # forget (must come first)
848 847 for f, args, msg in actions['f']:
849 848 repo.dirstate.drop(f)
850 849
851 850 # re-add
852 851 for f, args, msg in actions['a']:
853 852 if not branchmerge:
854 853 repo.dirstate.add(f)
855 854
856 855 # exec change
857 856 for f, args, msg in actions['e']:
858 857 repo.dirstate.normallookup(f)
859 858
860 859 # keep
861 860 for f, args, msg in actions['k']:
862 861 pass
863 862
864 863 # get
865 864 for f, args, msg in actions['g']:
866 865 if branchmerge:
867 866 repo.dirstate.otherparent(f)
868 867 else:
869 868 repo.dirstate.normal(f)
870 869
871 870 # merge
872 871 for f, args, msg in actions['m']:
873 872 f1, f2, fa, move, anc = args
874 873 if branchmerge:
875 874 # We've done a branch merge, mark this file as merged
876 875 # so that we properly record the merger later
877 876 repo.dirstate.merge(f)
878 877 if f1 != f2: # copy/rename
879 878 if move:
880 879 repo.dirstate.remove(f1)
881 880 if f1 != f:
882 881 repo.dirstate.copy(f1, f)
883 882 else:
884 883 repo.dirstate.copy(f2, f)
885 884 else:
886 885 # We've update-merged a locally modified file, so
887 886 # we set the dirstate to emulate a normal checkout
888 887 # of that file some time in the past. Thus our
889 888 # merge will appear as a normal local file
890 889 # modification.
891 890 if f2 == f: # file not locally copied/moved
892 891 repo.dirstate.normallookup(f)
893 892 if move:
894 893 repo.dirstate.drop(f1)
895 894
896 895 # directory rename, move local
897 896 for f, args, msg in actions['dm']:
898 897 f0, flag = args
899 898 if branchmerge:
900 899 repo.dirstate.add(f)
901 900 repo.dirstate.remove(f0)
902 901 repo.dirstate.copy(f0, f)
903 902 else:
904 903 repo.dirstate.normal(f)
905 904 repo.dirstate.drop(f0)
906 905
907 906 # directory rename, get
908 907 for f, args, msg in actions['dg']:
909 908 f0, flag = args
910 909 if branchmerge:
911 910 repo.dirstate.add(f)
912 911 repo.dirstate.copy(f0, f)
913 912 else:
914 913 repo.dirstate.normal(f)
915 914
916 915 def update(repo, node, branchmerge, force, partial, ancestor=None,
917 916 mergeancestor=False, labels=None):
918 917 """
919 918 Perform a merge between the working directory and the given node
920 919
921 920 node = the node to update to, or None if unspecified
922 921 branchmerge = whether to merge between branches
923 922 force = whether to force branch merging or file overwriting
924 923 partial = a function to filter file lists (dirstate not updated)
925 924 mergeancestor = whether it is merging with an ancestor. If true,
926 925 we should accept the incoming changes for any prompts that occur.
927 926 If false, merging with an ancestor (fast-forward) is only allowed
928 927 between different named branches. This flag is used by rebase extension
929 928 as a temporary fix and should be avoided in general.
930 929
931 930 The table below shows all the behaviors of the update command
932 931 given the -c and -C or no options, whether the working directory
933 932 is dirty, whether a revision is specified, and the relationship of
934 933 the parent rev to the target rev (linear, on the same named
935 934 branch, or on another named branch).
936 935
937 936 This logic is tested by test-update-branches.t.
938 937
939 938 -c -C dirty rev | linear same cross
940 939 n n n n | ok (1) x
941 940 n n n y | ok ok ok
942 941 n n y n | merge (2) (2)
943 942 n n y y | merge (3) (3)
944 943 n y * * | --- discard ---
945 944 y n y * | --- (4) ---
946 945 y n n * | --- ok ---
947 946 y y * * | --- (5) ---
948 947
949 948 x = can't happen
950 949 * = don't-care
951 950 1 = abort: not a linear update (merge or update --check to force update)
952 951 2 = abort: uncommitted changes (commit and merge, or update --clean to
953 952 discard changes)
954 953 3 = abort: uncommitted changes (commit or update --clean to discard changes)
955 954 4 = abort: uncommitted changes (checked in commands.py)
956 955 5 = incompatible options (checked in commands.py)
957 956
958 957 Return the same tuple as applyupdates().
959 958 """
960 959
961 960 onode = node
962 961 wlock = repo.wlock()
963 962 try:
964 963 wc = repo[None]
965 964 pl = wc.parents()
966 965 p1 = pl[0]
967 966 pas = [None]
968 967 if ancestor is not None:
969 968 pas = [repo[ancestor]]
970 969
971 970 if node is None:
972 971 # Here is where we should consider bookmarks, divergent bookmarks,
973 972 # foreground changesets (successors), and tip of current branch;
974 973 # but currently we are only checking the branch tips.
975 974 try:
976 975 node = repo.branchtip(wc.branch())
977 976 except errormod.RepoLookupError:
978 977 if wc.branch() == 'default': # no default branch!
979 978 node = repo.lookup('tip') # update to tip
980 979 else:
981 980 raise util.Abort(_("branch %s not found") % wc.branch())
982 981
983 982 if p1.obsolete() and not p1.children():
984 983 # allow updating to successors
985 984 successors = obsolete.successorssets(repo, p1.node())
986 985
987 986 # behavior of certain cases is as follows,
988 987 #
989 988 # divergent changesets: update to highest rev, similar to what
990 989 # is currently done when there are more than one head
991 990 # (i.e. 'tip')
992 991 #
993 992 # replaced changesets: same as divergent except we know there
994 993 # is no conflict
995 994 #
996 995 # pruned changeset: no update is done; though, we could
997 996 # consider updating to the first non-obsolete parent,
998 997 # similar to what is current done for 'hg prune'
999 998
1000 999 if successors:
1001 1000 # flatten the list here handles both divergent (len > 1)
1002 1001 # and the usual case (len = 1)
1003 1002 successors = [n for sub in successors for n in sub]
1004 1003
1005 1004 # get the max revision for the given successors set,
1006 1005 # i.e. the 'tip' of a set
1007 1006 node = repo.revs('max(%ln)', successors).first()
1008 1007 pas = [p1]
1009 1008
1010 1009 overwrite = force and not branchmerge
1011 1010
1012 1011 p2 = repo[node]
1013 1012 if pas[0] is None:
1014 1013 if repo.ui.config('merge', 'preferancestor', '*') == '*':
1015 1014 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1016 1015 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1017 1016 else:
1018 1017 pas = [p1.ancestor(p2, warn=branchmerge)]
1019 1018
1020 1019 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1021 1020
1022 1021 ### check phase
1023 1022 if not overwrite and len(pl) > 1:
1024 1023 raise util.Abort(_("outstanding uncommitted merge"))
1025 1024 if branchmerge:
1026 1025 if pas == [p2]:
1027 1026 raise util.Abort(_("merging with a working directory ancestor"
1028 1027 " has no effect"))
1029 1028 elif pas == [p1]:
1030 1029 if not mergeancestor and p1.branch() == p2.branch():
1031 1030 raise util.Abort(_("nothing to merge"),
1032 1031 hint=_("use 'hg update' "
1033 1032 "or check 'hg heads'"))
1034 1033 if not force and (wc.files() or wc.deleted()):
1035 1034 raise util.Abort(_("uncommitted changes"),
1036 1035 hint=_("use 'hg status' to list changes"))
1037 1036 for s in sorted(wc.substate):
1038 1037 if wc.sub(s).dirty():
1039 1038 raise util.Abort(_("uncommitted changes in "
1040 1039 "subrepository '%s'") % s)
1041 1040
1042 1041 elif not overwrite:
1043 1042 if p1 == p2: # no-op update
1044 1043 # call the hooks and exit early
1045 1044 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1046 1045 repo.hook('update', parent1=xp2, parent2='', error=0)
1047 1046 return 0, 0, 0, 0
1048 1047
1049 1048 if pas not in ([p1], [p2]): # nonlinear
1050 1049 dirty = wc.dirty(missing=True)
1051 1050 if dirty or onode is None:
1052 1051 # Branching is a bit strange to ensure we do the minimal
1053 1052 # amount of call to obsolete.background.
1054 1053 foreground = obsolete.foreground(repo, [p1.node()])
1055 1054 # note: the <node> variable contains a random identifier
1056 1055 if repo[node].node() in foreground:
1057 1056 pas = [p1] # allow updating to successors
1058 1057 elif dirty:
1059 1058 msg = _("uncommitted changes")
1060 1059 if onode is None:
1061 1060 hint = _("commit and merge, or update --clean to"
1062 1061 " discard changes")
1063 1062 else:
1064 1063 hint = _("commit or update --clean to discard"
1065 1064 " changes")
1066 1065 raise util.Abort(msg, hint=hint)
1067 1066 else: # node is none
1068 1067 msg = _("not a linear update")
1069 1068 hint = _("merge or update --check to force update")
1070 1069 raise util.Abort(msg, hint=hint)
1071 1070 else:
1072 1071 # Allow jumping branches if clean and specific rev given
1073 1072 pas = [p1]
1074 1073
1075 1074 followcopies = False
1076 1075 if overwrite:
1077 1076 pas = [wc]
1078 1077 elif pas == [p2]: # backwards
1079 1078 pas = [wc.p1()]
1080 1079 elif not branchmerge and not wc.dirty(missing=True):
1081 1080 pass
1082 1081 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1083 1082 followcopies = True
1084 1083
1085 1084 ### calculate phase
1086 1085 actions, diverge, renamedelete = calculateupdates(
1087 1086 repo, wc, p2, pas, branchmerge, force, partial, mergeancestor,
1088 1087 followcopies)
1089 1088
1090 1089 if not util.checkcase(repo.path):
1091 1090 # check collision between files only in p2 for clean update
1092 1091 if (not branchmerge and
1093 1092 (force or not wc.dirty(missing=True, branch=False))):
1094 1093 _checkcollision(repo, p2.manifest(), None)
1095 1094 else:
1096 1095 _checkcollision(repo, wc.manifest(), actions)
1097 1096
1098 1097 # Prompt and create actions. TODO: Move this towards resolve phase.
1099 1098 for f, args, msg in sorted(actions['cd']):
1100 1099 if repo.ui.promptchoice(
1101 1100 _("local changed %s which remote deleted\n"
1102 1101 "use (c)hanged version or (d)elete?"
1103 1102 "$$ &Changed $$ &Delete") % f, 0):
1104 1103 actions['r'].append((f, None, "prompt delete"))
1105 1104 else:
1106 1105 actions['a'].append((f, None, "prompt keep"))
1107 1106 del actions['cd'][:]
1108 1107
1109 1108 for f, args, msg in sorted(actions['dc']):
1110 1109 flags, = args
1111 1110 if repo.ui.promptchoice(
1112 1111 _("remote changed %s which local deleted\n"
1113 1112 "use (c)hanged version or leave (d)eleted?"
1114 1113 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1115 1114 actions['g'].append((f, (flags,), "prompt recreating"))
1116 1115 del actions['dc'][:]
1117 1116
1118 1117 ### apply phase
1119 1118 if not branchmerge: # just jump to the new rev
1120 1119 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1121 1120 if not partial:
1122 1121 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1123 1122 # note that we're in the middle of an update
1124 1123 repo.vfs.write('updatestate', p2.hex())
1125 1124
1126 1125 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1127 1126
1128 1127 # divergent renames
1129 1128 for f, fl in sorted(diverge.iteritems()):
1130 1129 repo.ui.warn(_("note: possible conflict - %s was renamed "
1131 1130 "multiple times to:\n") % f)
1132 1131 for nf in fl:
1133 1132 repo.ui.warn(" %s\n" % nf)
1134 1133
1135 1134 # rename and delete
1136 1135 for f, fl in sorted(renamedelete.iteritems()):
1137 1136 repo.ui.warn(_("note: possible conflict - %s was deleted "
1138 1137 "and renamed to:\n") % f)
1139 1138 for nf in fl:
1140 1139 repo.ui.warn(" %s\n" % nf)
1141 1140
1142 1141 if not partial:
1143 1142 repo.dirstate.beginparentchange()
1144 1143 repo.setparents(fp1, fp2)
1145 1144 recordupdates(repo, actions, branchmerge)
1146 1145 # update completed, clear state
1147 1146 util.unlink(repo.join('updatestate'))
1148 1147
1149 1148 if not branchmerge:
1150 1149 repo.dirstate.setbranch(p2.branch())
1151 1150 repo.dirstate.endparentchange()
1152 1151 finally:
1153 1152 wlock.release()
1154 1153
1155 1154 if not partial:
1156 1155 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1157 1156 return stats
1158 1157
1159 1158 def graft(repo, ctx, pctx, labels):
1160 1159 """Do a graft-like merge.
1161 1160
1162 1161 This is a merge where the merge ancestor is chosen such that one
1163 1162 or more changesets are grafted onto the current changeset. In
1164 1163 addition to the merge, this fixes up the dirstate to include only
1165 1164 a single parent and tries to duplicate any renames/copies
1166 1165 appropriately.
1167 1166
1168 1167 ctx - changeset to rebase
1169 1168 pctx - merge base, usually ctx.p1()
1170 1169 labels - merge labels eg ['local', 'graft']
1171 1170
1172 1171 """
1173 1172
1174 1173 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1175 1174 labels=labels)
1176 1175 # drop the second merge parent
1177 1176 repo.dirstate.beginparentchange()
1178 1177 repo.setparents(repo['.'].node(), nullid)
1179 1178 repo.dirstate.write()
1180 1179 # fix up dirstate for copies and renames
1181 1180 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1182 1181 repo.dirstate.endparentchange()
1183 1182 return stats
@@ -1,354 +1,354 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 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
28 28 $ echo '6 second change' > f2
29 29 $ hg ci -m '6 second change f2'
30 30
31 31 $ hg log -G
32 32 @ changeset: 6:3b08d01b0ab5
33 33 | tag: tip
34 34 | parent: 3:cf89f02107e5
35 35 | user: test
36 36 | date: Thu Jan 01 00:00:00 1970 +0000
37 37 | summary: 6 second change f2
38 38 |
39 39 | o changeset: 5:adfe50279922
40 40 | | user: test
41 41 | | date: Thu Jan 01 00:00:00 1970 +0000
42 42 | | summary: 5 second change f1
43 43 | |
44 44 | o changeset: 4:7d3e55501ae6
45 45 | |\ parent: 2:40663881a6dd
46 46 | | | parent: 1:0f6b37dbe527
47 47 | | | user: test
48 48 | | | date: Thu Jan 01 00:00:00 1970 +0000
49 49 | | | summary: 4 merge
50 50 | | |
51 51 o---+ changeset: 3:cf89f02107e5
52 52 | | | parent: 2:40663881a6dd
53 53 |/ / parent: 1:0f6b37dbe527
54 54 | | user: test
55 55 | | date: Thu Jan 01 00:00:00 1970 +0000
56 56 | | summary: 3 merge
57 57 | |
58 58 | o changeset: 2:40663881a6dd
59 59 | | parent: 0:40494bf2444c
60 60 | | user: test
61 61 | | date: Thu Jan 01 00:00:00 1970 +0000
62 62 | | summary: 2 first change f2
63 63 | |
64 64 o | changeset: 1:0f6b37dbe527
65 65 |/ user: test
66 66 | date: Thu Jan 01 00:00:00 1970 +0000
67 67 | summary: 1 first change f1
68 68 |
69 69 o changeset: 0:40494bf2444c
70 70 user: test
71 71 date: Thu Jan 01 00:00:00 1970 +0000
72 72 summary: 0 base
73 73
74 74
75 75 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor='!'
76 76 note: using 0f6b37dbe527 as ancestor of 3b08d01b0ab5 and adfe50279922
77 77 alternatively, use --config merge.preferancestor=40663881a6dd
78 78 searching for copies back to rev 3
79 79 resolving manifests
80 80 branchmerge: True, force: False, partial: False
81 81 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
82 82 preserving f2 for resolve of f2
83 83 f1: remote is newer -> g
84 84 getting f1
85 85 updating: f1 1/2 files (50.00%)
86 86 f2: versions differ -> m
87 87 updating: f2 2/2 files (100.00%)
88 88 picked tool 'internal:dump' for f2 (binary False symlink False)
89 89 merging f2
90 90 my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c
91 91 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
92 92 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
93 93 [1]
94 94
95 95 $ head *
96 96 ==> f1 <==
97 97 5 second change
98 98
99 99 ==> f2 <==
100 100 6 second change
101 101
102 102 ==> f2.base <==
103 103 0 base
104 104
105 105 ==> f2.local <==
106 106 6 second change
107 107
108 108 ==> f2.orig <==
109 109 6 second change
110 110
111 111 ==> f2.other <==
112 112 2 first change
113 113
114 114 $ hg up -qC .
115 115 $ hg merge -v --tool internal:dump 5 --config merge.preferancestor="null 40663881 3b08d"
116 116 note: using 40663881a6dd as ancestor of 3b08d01b0ab5 and adfe50279922
117 117 alternatively, use --config merge.preferancestor=0f6b37dbe527
118 118 resolving manifests
119 119 merging f1
120 120 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
121 121 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
122 122 [1]
123 123
124 124 Redo merge with merge.preferancestor="*" to enable bid merge
125 125
126 126 $ rm f*
127 127 $ hg up -qC .
128 128 $ hg merge -v --debug --tool internal:dump 5 --config merge.preferancestor="*"
129 129 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
130 130
131 131 calculating bids for ancestor 0f6b37dbe527
132 132 searching for copies back to rev 3
133 133 resolving manifests
134 134 branchmerge: True, force: False, partial: False
135 135 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
136 136 f1: remote is newer -> g
137 137 f2: versions differ -> m
138 138
139 139 calculating bids for ancestor 40663881a6dd
140 140 searching for copies back to rev 3
141 141 resolving manifests
142 142 branchmerge: True, force: False, partial: False
143 143 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
144 f1: versions differ -> m
144 145 f2: remote unchanged -> k
145 f1: versions differ -> m
146 146
147 147 auction for merging merge bids
148 148 f1: picking 'get' action
149 149 f2: picking 'keep' action
150 150 end of auction
151 151
152 152 f1: remote is newer -> g
153 153 getting f1
154 154 updating: f1 1/1 files (100.00%)
155 155 f2: remote unchanged -> k
156 156 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
157 157 (branch merge, don't forget to commit)
158 158
159 159 $ head *
160 160 ==> f1 <==
161 161 5 second change
162 162
163 163 ==> f2 <==
164 164 6 second change
165 165
166 166
167 167 The other way around:
168 168
169 169 $ hg up -C -r5
170 170 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 171 $ hg merge -v --debug --config merge.preferancestor="*"
172 172 note: merging adfe50279922+ and 3b08d01b0ab5 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
173 173
174 174 calculating bids for ancestor 0f6b37dbe527
175 175 searching for copies back to rev 3
176 176 resolving manifests
177 177 branchmerge: True, force: False, partial: False
178 178 ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5
179 179 f1: remote unchanged -> k
180 180 f2: versions differ -> m
181 181
182 182 calculating bids for ancestor 40663881a6dd
183 183 searching for copies back to rev 3
184 184 resolving manifests
185 185 branchmerge: True, force: False, partial: False
186 186 ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5
187 f1: versions differ -> m
187 188 f2: remote is newer -> g
188 f1: versions differ -> m
189 189
190 190 auction for merging merge bids
191 191 f1: picking 'keep' action
192 192 f2: picking 'get' action
193 193 end of auction
194 194
195 195 f2: remote is newer -> g
196 196 getting f2
197 197 updating: f2 1/1 files (100.00%)
198 198 f1: remote unchanged -> k
199 199 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
200 200 (branch merge, don't forget to commit)
201 201
202 202 $ head *
203 203 ==> f1 <==
204 204 5 second change
205 205
206 206 ==> f2 <==
207 207 6 second change
208 208
209 209 Verify how the output looks and and how verbose it is:
210 210
211 211 $ hg up -qC
212 212 $ hg merge
213 213 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
214 214 (branch merge, don't forget to commit)
215 215
216 216 $ hg up -qC
217 217 $ hg merge -v
218 218 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
219 219
220 220 calculating bids for ancestor 0f6b37dbe527
221 221 resolving manifests
222 222
223 223 calculating bids for ancestor 40663881a6dd
224 224 resolving manifests
225 225
226 226 auction for merging merge bids
227 227 f1: picking 'get' action
228 228 f2: picking 'keep' action
229 229 end of auction
230 230
231 231 getting f1
232 232 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
233 233 (branch merge, don't forget to commit)
234 234
235 235 $ hg up -qC
236 236 $ hg merge -v --debug --config merge.preferancestor="*"
237 237 note: merging 3b08d01b0ab5+ and adfe50279922 using bids from ancestors 0f6b37dbe527 and 40663881a6dd
238 238
239 239 calculating bids for ancestor 0f6b37dbe527
240 240 searching for copies back to rev 3
241 241 resolving manifests
242 242 branchmerge: True, force: False, partial: False
243 243 ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922
244 244 f1: remote is newer -> g
245 245 f2: versions differ -> m
246 246
247 247 calculating bids for ancestor 40663881a6dd
248 248 searching for copies back to rev 3
249 249 resolving manifests
250 250 branchmerge: True, force: False, partial: False
251 251 ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922
252 f1: versions differ -> m
252 253 f2: remote unchanged -> k
253 f1: versions differ -> m
254 254
255 255 auction for merging merge bids
256 256 f1: picking 'get' action
257 257 f2: picking 'keep' action
258 258 end of auction
259 259
260 260 f1: remote is newer -> g
261 261 getting f1
262 262 updating: f1 1/1 files (100.00%)
263 263 f2: remote unchanged -> k
264 264 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
265 265 (branch merge, don't forget to commit)
266 266
267 267 $ cd ..
268 268
269 269 http://stackoverflow.com/questions/9350005/how-do-i-specify-a-merge-base-to-use-in-a-hg-merge/9430810
270 270
271 271 $ hg init ancestor-merging
272 272 $ cd ancestor-merging
273 273 $ echo a > x
274 274 $ hg commit -A -m a x
275 275 $ hg update -q 0
276 276 $ echo b >> x
277 277 $ hg commit -m b
278 278 $ hg update -q 0
279 279 $ echo c >> x
280 280 $ hg commit -qm c
281 281 $ hg update -q 1
282 282 $ hg merge -q --tool internal:local 2
283 283 $ echo c >> x
284 284 $ hg commit -m bc
285 285 $ hg update -q 2
286 286 $ hg merge -q --tool internal:local 1
287 287 $ echo b >> x
288 288 $ hg commit -qm cb
289 289
290 290 $ hg merge --config merge.preferancestor='!'
291 291 note: using 70008a2163f6 as ancestor of 0d355fdef312 and 4b8b546a3eef
292 292 alternatively, use --config merge.preferancestor=b211bbc6eb3c
293 293 merging x
294 294 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
295 295 (branch merge, don't forget to commit)
296 296 $ cat x
297 297 a
298 298 c
299 299 b
300 300 c
301 301
302 302 $ hg up -qC .
303 303
304 304 $ hg merge --config merge.preferancestor=b211bbc6eb3c
305 305 note: using b211bbc6eb3c as ancestor of 0d355fdef312 and 4b8b546a3eef
306 306 alternatively, use --config merge.preferancestor=70008a2163f6
307 307 merging x
308 308 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
309 309 (branch merge, don't forget to commit)
310 310 $ cat x
311 311 a
312 312 b
313 313 c
314 314 b
315 315
316 316 $ hg up -qC .
317 317
318 318 $ hg merge -v --config merge.preferancestor="*"
319 319 note: merging 0d355fdef312+ and 4b8b546a3eef using bids from ancestors 70008a2163f6 and b211bbc6eb3c
320 320
321 321 calculating bids for ancestor 70008a2163f6
322 322 resolving manifests
323 323
324 324 calculating bids for ancestor b211bbc6eb3c
325 325 resolving manifests
326 326
327 327 auction for merging merge bids
328 328 x: multiple bids for merge action:
329 329 versions differ -> m
330 330 versions differ -> m
331 331 x: ambiguous merge - picked m action
332 332 end of auction
333 333
334 334 merging x
335 335 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
336 336 (branch merge, don't forget to commit)
337 337 $ cat x
338 338 a
339 339 c
340 340 b
341 341 c
342 342
343 343 Verify that the old context ancestor works with / despite preferancestor:
344 344
345 345 $ hg log -r 'ancestor(head())' --config merge.preferancestor=1 -T '{rev}\n'
346 346 1
347 347 $ hg log -r 'ancestor(head())' --config merge.preferancestor=2 -T '{rev}\n'
348 348 2
349 349 $ hg log -r 'ancestor(head())' --config merge.preferancestor=3 -T '{rev}\n'
350 350 1
351 351 $ hg log -r 'ancestor(head())' --config merge.preferancestor='1337 * - 2' -T '{rev}\n'
352 352 2
353 353
354 354 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now