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