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