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