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