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