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