##// END OF EJS Templates
py3: convert bool variables to bytes before passing into ui.debug()...
Pulkit Goyal -
r32641:49e1e5ac default
parent child Browse files
Show More
@@ -1,1748 +1,1751 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 hashlib
12 12 import os
13 13 import shutil
14 14 import struct
15 15
16 16 from .i18n import _
17 17 from .node import (
18 18 addednodeid,
19 19 bin,
20 20 hex,
21 21 modifiednodeid,
22 22 nullhex,
23 23 nullid,
24 24 nullrev,
25 25 )
26 26 from . import (
27 27 copies,
28 28 error,
29 29 filemerge,
30 30 match as matchmod,
31 31 obsolete,
32 32 pycompat,
33 33 scmutil,
34 34 subrepo,
35 35 util,
36 36 worker,
37 37 )
38 38
39 39 _pack = struct.pack
40 40 _unpack = struct.unpack
41 41
42 42 def _droponode(data):
43 43 # used for compatibility for v1
44 44 bits = data.split('\0')
45 45 bits = bits[:-2] + bits[-1:]
46 46 return '\0'.join(bits)
47 47
48 48 class mergestate(object):
49 49 '''track 3-way merge state of individual files
50 50
51 51 The merge state is stored on disk when needed. Two files are used: one with
52 52 an old format (version 1), and one with a new format (version 2). Version 2
53 53 stores a superset of the data in version 1, including new kinds of records
54 54 in the future. For more about the new format, see the documentation for
55 55 `_readrecordsv2`.
56 56
57 57 Each record can contain arbitrary content, and has an associated type. This
58 58 `type` should be a letter. If `type` is uppercase, the record is mandatory:
59 59 versions of Mercurial that don't support it should abort. If `type` is
60 60 lowercase, the record can be safely ignored.
61 61
62 62 Currently known records:
63 63
64 64 L: the node of the "local" part of the merge (hexified version)
65 65 O: the node of the "other" part of the merge (hexified version)
66 66 F: a file to be merged entry
67 67 C: a change/delete or delete/change conflict
68 68 D: a file that the external merge driver will merge internally
69 69 (experimental)
70 70 m: the external merge driver defined for this merge plus its run state
71 71 (experimental)
72 72 f: a (filename, dictionary) tuple of optional values for a given file
73 73 X: unsupported mandatory record type (used in tests)
74 74 x: unsupported advisory record type (used in tests)
75 75 l: the labels for the parts of the merge.
76 76
77 77 Merge driver run states (experimental):
78 78 u: driver-resolved files unmarked -- needs to be run next time we're about
79 79 to resolve or commit
80 80 m: driver-resolved files marked -- only needs to be run before commit
81 81 s: success/skipped -- does not need to be run any more
82 82
83 83 '''
84 84 statepathv1 = 'merge/state'
85 85 statepathv2 = 'merge/state2'
86 86
87 87 @staticmethod
88 88 def clean(repo, node=None, other=None, labels=None):
89 89 """Initialize a brand new merge state, removing any existing state on
90 90 disk."""
91 91 ms = mergestate(repo)
92 92 ms.reset(node, other, labels)
93 93 return ms
94 94
95 95 @staticmethod
96 96 def read(repo):
97 97 """Initialize the merge state, reading it from disk."""
98 98 ms = mergestate(repo)
99 99 ms._read()
100 100 return ms
101 101
102 102 def __init__(self, repo):
103 103 """Initialize the merge state.
104 104
105 105 Do not use this directly! Instead call read() or clean()."""
106 106 self._repo = repo
107 107 self._dirty = False
108 108 self._labels = None
109 109
110 110 def reset(self, node=None, other=None, labels=None):
111 111 self._state = {}
112 112 self._stateextras = {}
113 113 self._local = None
114 114 self._other = None
115 115 self._labels = labels
116 116 for var in ('localctx', 'otherctx'):
117 117 if var in vars(self):
118 118 delattr(self, var)
119 119 if node:
120 120 self._local = node
121 121 self._other = other
122 122 self._readmergedriver = None
123 123 if self.mergedriver:
124 124 self._mdstate = 's'
125 125 else:
126 126 self._mdstate = 'u'
127 127 shutil.rmtree(self._repo.vfs.join('merge'), True)
128 128 self._results = {}
129 129 self._dirty = False
130 130
131 131 def _read(self):
132 132 """Analyse each record content to restore a serialized state from disk
133 133
134 134 This function process "record" entry produced by the de-serialization
135 135 of on disk file.
136 136 """
137 137 self._state = {}
138 138 self._stateextras = {}
139 139 self._local = None
140 140 self._other = None
141 141 for var in ('localctx', 'otherctx'):
142 142 if var in vars(self):
143 143 delattr(self, var)
144 144 self._readmergedriver = None
145 145 self._mdstate = 's'
146 146 unsupported = set()
147 147 records = self._readrecords()
148 148 for rtype, record in records:
149 149 if rtype == 'L':
150 150 self._local = bin(record)
151 151 elif rtype == 'O':
152 152 self._other = bin(record)
153 153 elif rtype == 'm':
154 154 bits = record.split('\0', 1)
155 155 mdstate = bits[1]
156 156 if len(mdstate) != 1 or mdstate not in 'ums':
157 157 # the merge driver should be idempotent, so just rerun it
158 158 mdstate = 'u'
159 159
160 160 self._readmergedriver = bits[0]
161 161 self._mdstate = mdstate
162 162 elif rtype in 'FDC':
163 163 bits = record.split('\0')
164 164 self._state[bits[0]] = bits[1:]
165 165 elif rtype == 'f':
166 166 filename, rawextras = record.split('\0', 1)
167 167 extraparts = rawextras.split('\0')
168 168 extras = {}
169 169 i = 0
170 170 while i < len(extraparts):
171 171 extras[extraparts[i]] = extraparts[i + 1]
172 172 i += 2
173 173
174 174 self._stateextras[filename] = extras
175 175 elif rtype == 'l':
176 176 labels = record.split('\0', 2)
177 177 self._labels = [l for l in labels if len(l) > 0]
178 178 elif not rtype.islower():
179 179 unsupported.add(rtype)
180 180 self._results = {}
181 181 self._dirty = False
182 182
183 183 if unsupported:
184 184 raise error.UnsupportedMergeRecords(unsupported)
185 185
186 186 def _readrecords(self):
187 187 """Read merge state from disk and return a list of record (TYPE, data)
188 188
189 189 We read data from both v1 and v2 files and decide which one to use.
190 190
191 191 V1 has been used by version prior to 2.9.1 and contains less data than
192 192 v2. We read both versions and check if no data in v2 contradicts
193 193 v1. If there is not contradiction we can safely assume that both v1
194 194 and v2 were written at the same time and use the extract data in v2. If
195 195 there is contradiction we ignore v2 content as we assume an old version
196 196 of Mercurial has overwritten the mergestate file and left an old v2
197 197 file around.
198 198
199 199 returns list of record [(TYPE, data), ...]"""
200 200 v1records = self._readrecordsv1()
201 201 v2records = self._readrecordsv2()
202 202 if self._v1v2match(v1records, v2records):
203 203 return v2records
204 204 else:
205 205 # v1 file is newer than v2 file, use it
206 206 # we have to infer the "other" changeset of the merge
207 207 # we cannot do better than that with v1 of the format
208 208 mctx = self._repo[None].parents()[-1]
209 209 v1records.append(('O', mctx.hex()))
210 210 # add place holder "other" file node information
211 211 # nobody is using it yet so we do no need to fetch the data
212 212 # if mctx was wrong `mctx[bits[-2]]` may fails.
213 213 for idx, r in enumerate(v1records):
214 214 if r[0] == 'F':
215 215 bits = r[1].split('\0')
216 216 bits.insert(-2, '')
217 217 v1records[idx] = (r[0], '\0'.join(bits))
218 218 return v1records
219 219
220 220 def _v1v2match(self, v1records, v2records):
221 221 oldv2 = set() # old format version of v2 record
222 222 for rec in v2records:
223 223 if rec[0] == 'L':
224 224 oldv2.add(rec)
225 225 elif rec[0] == 'F':
226 226 # drop the onode data (not contained in v1)
227 227 oldv2.add(('F', _droponode(rec[1])))
228 228 for rec in v1records:
229 229 if rec not in oldv2:
230 230 return False
231 231 else:
232 232 return True
233 233
234 234 def _readrecordsv1(self):
235 235 """read on disk merge state for version 1 file
236 236
237 237 returns list of record [(TYPE, data), ...]
238 238
239 239 Note: the "F" data from this file are one entry short
240 240 (no "other file node" entry)
241 241 """
242 242 records = []
243 243 try:
244 244 f = self._repo.vfs(self.statepathv1)
245 245 for i, l in enumerate(f):
246 246 if i == 0:
247 247 records.append(('L', l[:-1]))
248 248 else:
249 249 records.append(('F', l[:-1]))
250 250 f.close()
251 251 except IOError as err:
252 252 if err.errno != errno.ENOENT:
253 253 raise
254 254 return records
255 255
256 256 def _readrecordsv2(self):
257 257 """read on disk merge state for version 2 file
258 258
259 259 This format is a list of arbitrary records of the form:
260 260
261 261 [type][length][content]
262 262
263 263 `type` is a single character, `length` is a 4 byte integer, and
264 264 `content` is an arbitrary byte sequence of length `length`.
265 265
266 266 Mercurial versions prior to 3.7 have a bug where if there are
267 267 unsupported mandatory merge records, attempting to clear out the merge
268 268 state with hg update --clean or similar aborts. The 't' record type
269 269 works around that by writing out what those versions treat as an
270 270 advisory record, but later versions interpret as special: the first
271 271 character is the 'real' record type and everything onwards is the data.
272 272
273 273 Returns list of records [(TYPE, data), ...]."""
274 274 records = []
275 275 try:
276 276 f = self._repo.vfs(self.statepathv2)
277 277 data = f.read()
278 278 off = 0
279 279 end = len(data)
280 280 while off < end:
281 281 rtype = data[off]
282 282 off += 1
283 283 length = _unpack('>I', data[off:(off + 4)])[0]
284 284 off += 4
285 285 record = data[off:(off + length)]
286 286 off += length
287 287 if rtype == 't':
288 288 rtype, record = record[0], record[1:]
289 289 records.append((rtype, record))
290 290 f.close()
291 291 except IOError as err:
292 292 if err.errno != errno.ENOENT:
293 293 raise
294 294 return records
295 295
296 296 @util.propertycache
297 297 def mergedriver(self):
298 298 # protect against the following:
299 299 # - A configures a malicious merge driver in their hgrc, then
300 300 # pauses the merge
301 301 # - A edits their hgrc to remove references to the merge driver
302 302 # - A gives a copy of their entire repo, including .hg, to B
303 303 # - B inspects .hgrc and finds it to be clean
304 304 # - B then continues the merge and the malicious merge driver
305 305 # gets invoked
306 306 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
307 307 if (self._readmergedriver is not None
308 308 and self._readmergedriver != configmergedriver):
309 309 raise error.ConfigError(
310 310 _("merge driver changed since merge started"),
311 311 hint=_("revert merge driver change or abort merge"))
312 312
313 313 return configmergedriver
314 314
315 315 @util.propertycache
316 316 def localctx(self):
317 317 if self._local is None:
318 318 msg = "localctx accessed but self._local isn't set"
319 319 raise error.ProgrammingError(msg)
320 320 return self._repo[self._local]
321 321
322 322 @util.propertycache
323 323 def otherctx(self):
324 324 if self._other is None:
325 325 msg = "otherctx accessed but self._other isn't set"
326 326 raise error.ProgrammingError(msg)
327 327 return self._repo[self._other]
328 328
329 329 def active(self):
330 330 """Whether mergestate is active.
331 331
332 332 Returns True if there appears to be mergestate. This is a rough proxy
333 333 for "is a merge in progress."
334 334 """
335 335 # Check local variables before looking at filesystem for performance
336 336 # reasons.
337 337 return bool(self._local) or bool(self._state) or \
338 338 self._repo.vfs.exists(self.statepathv1) or \
339 339 self._repo.vfs.exists(self.statepathv2)
340 340
341 341 def commit(self):
342 342 """Write current state on disk (if necessary)"""
343 343 if self._dirty:
344 344 records = self._makerecords()
345 345 self._writerecords(records)
346 346 self._dirty = False
347 347
348 348 def _makerecords(self):
349 349 records = []
350 350 records.append(('L', hex(self._local)))
351 351 records.append(('O', hex(self._other)))
352 352 if self.mergedriver:
353 353 records.append(('m', '\0'.join([
354 354 self.mergedriver, self._mdstate])))
355 355 for d, v in self._state.iteritems():
356 356 if v[0] == 'd':
357 357 records.append(('D', '\0'.join([d] + v)))
358 358 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
359 359 # older versions of Mercurial
360 360 elif v[1] == nullhex or v[6] == nullhex:
361 361 records.append(('C', '\0'.join([d] + v)))
362 362 else:
363 363 records.append(('F', '\0'.join([d] + v)))
364 364 for filename, extras in sorted(self._stateextras.iteritems()):
365 365 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
366 366 extras.iteritems())
367 367 records.append(('f', '%s\0%s' % (filename, rawextras)))
368 368 if self._labels is not None:
369 369 labels = '\0'.join(self._labels)
370 370 records.append(('l', labels))
371 371 return records
372 372
373 373 def _writerecords(self, records):
374 374 """Write current state on disk (both v1 and v2)"""
375 375 self._writerecordsv1(records)
376 376 self._writerecordsv2(records)
377 377
378 378 def _writerecordsv1(self, records):
379 379 """Write current state on disk in a version 1 file"""
380 380 f = self._repo.vfs(self.statepathv1, 'w')
381 381 irecords = iter(records)
382 382 lrecords = next(irecords)
383 383 assert lrecords[0] == 'L'
384 384 f.write(hex(self._local) + '\n')
385 385 for rtype, data in irecords:
386 386 if rtype == 'F':
387 387 f.write('%s\n' % _droponode(data))
388 388 f.close()
389 389
390 390 def _writerecordsv2(self, records):
391 391 """Write current state on disk in a version 2 file
392 392
393 393 See the docstring for _readrecordsv2 for why we use 't'."""
394 394 # these are the records that all version 2 clients can read
395 395 whitelist = 'LOF'
396 396 f = self._repo.vfs(self.statepathv2, 'w')
397 397 for key, data in records:
398 398 assert len(key) == 1
399 399 if key not in whitelist:
400 400 key, data = 't', '%s%s' % (key, data)
401 401 format = '>sI%is' % len(data)
402 402 f.write(_pack(format, key, len(data), data))
403 403 f.close()
404 404
405 405 def add(self, fcl, fco, fca, fd):
406 406 """add a new (potentially?) conflicting file the merge state
407 407 fcl: file context for local,
408 408 fco: file context for remote,
409 409 fca: file context for ancestors,
410 410 fd: file path of the resulting merge.
411 411
412 412 note: also write the local version to the `.hg/merge` directory.
413 413 """
414 414 if fcl.isabsent():
415 415 hash = nullhex
416 416 else:
417 417 hash = hashlib.sha1(fcl.path()).hexdigest()
418 418 self._repo.vfs.write('merge/' + hash, fcl.data())
419 419 self._state[fd] = ['u', hash, fcl.path(),
420 420 fca.path(), hex(fca.filenode()),
421 421 fco.path(), hex(fco.filenode()),
422 422 fcl.flags()]
423 423 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
424 424 self._dirty = True
425 425
426 426 def __contains__(self, dfile):
427 427 return dfile in self._state
428 428
429 429 def __getitem__(self, dfile):
430 430 return self._state[dfile][0]
431 431
432 432 def __iter__(self):
433 433 return iter(sorted(self._state))
434 434
435 435 def files(self):
436 436 return self._state.keys()
437 437
438 438 def mark(self, dfile, state):
439 439 self._state[dfile][0] = state
440 440 self._dirty = True
441 441
442 442 def mdstate(self):
443 443 return self._mdstate
444 444
445 445 def unresolved(self):
446 446 """Obtain the paths of unresolved files."""
447 447
448 448 for f, entry in self._state.items():
449 449 if entry[0] == 'u':
450 450 yield f
451 451
452 452 def driverresolved(self):
453 453 """Obtain the paths of driver-resolved files."""
454 454
455 455 for f, entry in self._state.items():
456 456 if entry[0] == 'd':
457 457 yield f
458 458
459 459 def extras(self, filename):
460 460 return self._stateextras.setdefault(filename, {})
461 461
462 462 def _resolve(self, preresolve, dfile, wctx):
463 463 """rerun merge process for file path `dfile`"""
464 464 if self[dfile] in 'rd':
465 465 return True, 0
466 466 stateentry = self._state[dfile]
467 467 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
468 468 octx = self._repo[self._other]
469 469 extras = self.extras(dfile)
470 470 anccommitnode = extras.get('ancestorlinknode')
471 471 if anccommitnode:
472 472 actx = self._repo[anccommitnode]
473 473 else:
474 474 actx = None
475 475 fcd = self._filectxorabsent(hash, wctx, dfile)
476 476 fco = self._filectxorabsent(onode, octx, ofile)
477 477 # TODO: move this to filectxorabsent
478 478 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
479 479 # "premerge" x flags
480 480 flo = fco.flags()
481 481 fla = fca.flags()
482 482 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
483 483 if fca.node() == nullid and flags != flo:
484 484 if preresolve:
485 485 self._repo.ui.warn(
486 486 _('warning: cannot merge flags for %s '
487 487 'without common ancestor - keeping local flags\n')
488 488 % afile)
489 489 elif flags == fla:
490 490 flags = flo
491 491 if preresolve:
492 492 # restore local
493 493 if hash != nullhex:
494 494 f = self._repo.vfs('merge/' + hash)
495 495 self._repo.wwrite(dfile, f.read(), flags)
496 496 f.close()
497 497 else:
498 498 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
499 499 complete, r, deleted = filemerge.premerge(self._repo, self._local,
500 500 lfile, fcd, fco, fca,
501 501 labels=self._labels)
502 502 else:
503 503 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
504 504 lfile, fcd, fco, fca,
505 505 labels=self._labels)
506 506 if r is None:
507 507 # no real conflict
508 508 del self._state[dfile]
509 509 self._stateextras.pop(dfile, None)
510 510 self._dirty = True
511 511 elif not r:
512 512 self.mark(dfile, 'r')
513 513
514 514 if complete:
515 515 action = None
516 516 if deleted:
517 517 if fcd.isabsent():
518 518 # dc: local picked. Need to drop if present, which may
519 519 # happen on re-resolves.
520 520 action = 'f'
521 521 else:
522 522 # cd: remote picked (or otherwise deleted)
523 523 action = 'r'
524 524 else:
525 525 if fcd.isabsent(): # dc: remote picked
526 526 action = 'g'
527 527 elif fco.isabsent(): # cd: local picked
528 528 if dfile in self.localctx:
529 529 action = 'am'
530 530 else:
531 531 action = 'a'
532 532 # else: regular merges (no action necessary)
533 533 self._results[dfile] = r, action
534 534
535 535 return complete, r
536 536
537 537 def _filectxorabsent(self, hexnode, ctx, f):
538 538 if hexnode == nullhex:
539 539 return filemerge.absentfilectx(ctx, f)
540 540 else:
541 541 return ctx[f]
542 542
543 543 def preresolve(self, dfile, wctx):
544 544 """run premerge process for dfile
545 545
546 546 Returns whether the merge is complete, and the exit code."""
547 547 return self._resolve(True, dfile, wctx)
548 548
549 549 def resolve(self, dfile, wctx):
550 550 """run merge process (assuming premerge was run) for dfile
551 551
552 552 Returns the exit code of the merge."""
553 553 return self._resolve(False, dfile, wctx)[1]
554 554
555 555 def counts(self):
556 556 """return counts for updated, merged and removed files in this
557 557 session"""
558 558 updated, merged, removed = 0, 0, 0
559 559 for r, action in self._results.itervalues():
560 560 if r is None:
561 561 updated += 1
562 562 elif r == 0:
563 563 if action == 'r':
564 564 removed += 1
565 565 else:
566 566 merged += 1
567 567 return updated, merged, removed
568 568
569 569 def unresolvedcount(self):
570 570 """get unresolved count for this merge (persistent)"""
571 571 return len([True for f, entry in self._state.iteritems()
572 572 if entry[0] == 'u'])
573 573
574 574 def actions(self):
575 575 """return lists of actions to perform on the dirstate"""
576 576 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
577 577 for f, (r, action) in self._results.iteritems():
578 578 if action is not None:
579 579 actions[action].append((f, None, "merge result"))
580 580 return actions
581 581
582 582 def recordactions(self):
583 583 """record remove/add/get actions in the dirstate"""
584 584 branchmerge = self._repo.dirstate.p2() != nullid
585 585 recordupdates(self._repo, self.actions(), branchmerge)
586 586
587 587 def queueremove(self, f):
588 588 """queues a file to be removed from the dirstate
589 589
590 590 Meant for use by custom merge drivers."""
591 591 self._results[f] = 0, 'r'
592 592
593 593 def queueadd(self, f):
594 594 """queues a file to be added to the dirstate
595 595
596 596 Meant for use by custom merge drivers."""
597 597 self._results[f] = 0, 'a'
598 598
599 599 def queueget(self, f):
600 600 """queues a file to be marked modified in the dirstate
601 601
602 602 Meant for use by custom merge drivers."""
603 603 self._results[f] = 0, 'g'
604 604
605 605 def _getcheckunknownconfig(repo, section, name):
606 606 config = repo.ui.config(section, name, default='abort')
607 607 valid = ['abort', 'ignore', 'warn']
608 608 if config not in valid:
609 609 validstr = ', '.join(["'" + v + "'" for v in valid])
610 610 raise error.ConfigError(_("%s.%s not valid "
611 611 "('%s' is none of %s)")
612 612 % (section, name, config, validstr))
613 613 return config
614 614
615 615 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
616 616 if f2 is None:
617 617 f2 = f
618 618 return (repo.wvfs.audit.check(f)
619 619 and repo.wvfs.isfileorlink(f)
620 620 and repo.dirstate.normalize(f) not in repo.dirstate
621 621 and mctx[f2].cmp(wctx[f]))
622 622
623 623 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
624 624 """
625 625 Considers any actions that care about the presence of conflicting unknown
626 626 files. For some actions, the result is to abort; for others, it is to
627 627 choose a different action.
628 628 """
629 629 conflicts = set()
630 630 warnconflicts = set()
631 631 abortconflicts = set()
632 632 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
633 633 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
634 634 if not force:
635 635 def collectconflicts(conflicts, config):
636 636 if config == 'abort':
637 637 abortconflicts.update(conflicts)
638 638 elif config == 'warn':
639 639 warnconflicts.update(conflicts)
640 640
641 641 for f, (m, args, msg) in actions.iteritems():
642 642 if m in ('c', 'dc'):
643 643 if _checkunknownfile(repo, wctx, mctx, f):
644 644 conflicts.add(f)
645 645 elif m == 'dg':
646 646 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
647 647 conflicts.add(f)
648 648
649 649 ignoredconflicts = set([c for c in conflicts
650 650 if repo.dirstate._ignore(c)])
651 651 unknownconflicts = conflicts - ignoredconflicts
652 652 collectconflicts(ignoredconflicts, ignoredconfig)
653 653 collectconflicts(unknownconflicts, unknownconfig)
654 654 else:
655 655 for f, (m, args, msg) in actions.iteritems():
656 656 if m == 'cm':
657 657 fl2, anc = args
658 658 different = _checkunknownfile(repo, wctx, mctx, f)
659 659 if repo.dirstate._ignore(f):
660 660 config = ignoredconfig
661 661 else:
662 662 config = unknownconfig
663 663
664 664 # The behavior when force is True is described by this table:
665 665 # config different mergeforce | action backup
666 666 # * n * | get n
667 667 # * y y | merge -
668 668 # abort y n | merge - (1)
669 669 # warn y n | warn + get y
670 670 # ignore y n | get y
671 671 #
672 672 # (1) this is probably the wrong behavior here -- we should
673 673 # probably abort, but some actions like rebases currently
674 674 # don't like an abort happening in the middle of
675 675 # merge.update.
676 676 if not different:
677 677 actions[f] = ('g', (fl2, False), "remote created")
678 678 elif mergeforce or config == 'abort':
679 679 actions[f] = ('m', (f, f, None, False, anc),
680 680 "remote differs from untracked local")
681 681 elif config == 'abort':
682 682 abortconflicts.add(f)
683 683 else:
684 684 if config == 'warn':
685 685 warnconflicts.add(f)
686 686 actions[f] = ('g', (fl2, True), "remote created")
687 687
688 688 for f in sorted(abortconflicts):
689 689 repo.ui.warn(_("%s: untracked file differs\n") % f)
690 690 if abortconflicts:
691 691 raise error.Abort(_("untracked files in working directory "
692 692 "differ from files in requested revision"))
693 693
694 694 for f in sorted(warnconflicts):
695 695 repo.ui.warn(_("%s: replacing untracked file\n") % f)
696 696
697 697 for f, (m, args, msg) in actions.iteritems():
698 698 backup = f in conflicts
699 699 if m == 'c':
700 700 flags, = args
701 701 actions[f] = ('g', (flags, backup), msg)
702 702
703 703 def _forgetremoved(wctx, mctx, branchmerge):
704 704 """
705 705 Forget removed files
706 706
707 707 If we're jumping between revisions (as opposed to merging), and if
708 708 neither the working directory nor the target rev has the file,
709 709 then we need to remove it from the dirstate, to prevent the
710 710 dirstate from listing the file when it is no longer in the
711 711 manifest.
712 712
713 713 If we're merging, and the other revision has removed a file
714 714 that is not present in the working directory, we need to mark it
715 715 as removed.
716 716 """
717 717
718 718 actions = {}
719 719 m = 'f'
720 720 if branchmerge:
721 721 m = 'r'
722 722 for f in wctx.deleted():
723 723 if f not in mctx:
724 724 actions[f] = m, None, "forget deleted"
725 725
726 726 if not branchmerge:
727 727 for f in wctx.removed():
728 728 if f not in mctx:
729 729 actions[f] = 'f', None, "forget removed"
730 730
731 731 return actions
732 732
733 733 def _checkcollision(repo, wmf, actions):
734 734 # build provisional merged manifest up
735 735 pmmf = set(wmf)
736 736
737 737 if actions:
738 738 # k, dr, e and rd are no-op
739 739 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
740 740 for f, args, msg in actions[m]:
741 741 pmmf.add(f)
742 742 for f, args, msg in actions['r']:
743 743 pmmf.discard(f)
744 744 for f, args, msg in actions['dm']:
745 745 f2, flags = args
746 746 pmmf.discard(f2)
747 747 pmmf.add(f)
748 748 for f, args, msg in actions['dg']:
749 749 pmmf.add(f)
750 750 for f, args, msg in actions['m']:
751 751 f1, f2, fa, move, anc = args
752 752 if move:
753 753 pmmf.discard(f1)
754 754 pmmf.add(f)
755 755
756 756 # check case-folding collision in provisional merged manifest
757 757 foldmap = {}
758 758 for f in sorted(pmmf):
759 759 fold = util.normcase(f)
760 760 if fold in foldmap:
761 761 raise error.Abort(_("case-folding collision between %s and %s")
762 762 % (f, foldmap[fold]))
763 763 foldmap[fold] = f
764 764
765 765 # check case-folding of directories
766 766 foldprefix = unfoldprefix = lastfull = ''
767 767 for fold, f in sorted(foldmap.items()):
768 768 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
769 769 # the folded prefix matches but actual casing is different
770 770 raise error.Abort(_("case-folding collision between "
771 771 "%s and directory of %s") % (lastfull, f))
772 772 foldprefix = fold + '/'
773 773 unfoldprefix = f + '/'
774 774 lastfull = f
775 775
776 776 def driverpreprocess(repo, ms, wctx, labels=None):
777 777 """run the preprocess step of the merge driver, if any
778 778
779 779 This is currently not implemented -- it's an extension point."""
780 780 return True
781 781
782 782 def driverconclude(repo, ms, wctx, labels=None):
783 783 """run the conclude step of the merge driver, if any
784 784
785 785 This is currently not implemented -- it's an extension point."""
786 786 return True
787 787
788 788 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
789 789 acceptremote, followcopies, forcefulldiff=False):
790 790 """
791 791 Merge wctx and p2 with ancestor pa and generate merge action list
792 792
793 793 branchmerge and force are as passed in to update
794 794 matcher = matcher to filter file lists
795 795 acceptremote = accept the incoming changes without prompting
796 796 """
797 797 if matcher is not None and matcher.always():
798 798 matcher = None
799 799
800 800 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
801 801
802 802 # manifests fetched in order are going to be faster, so prime the caches
803 803 [x.manifest() for x in
804 804 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev() or -1)]
805 805
806 806 if followcopies:
807 807 ret = copies.mergecopies(repo, wctx, p2, pa)
808 808 copy, movewithdir, diverge, renamedelete, dirmove = ret
809 809
810 boolbm = pycompat.bytestr(bool(branchmerge))
811 boolf = pycompat.bytestr(bool(force))
812 boolm = pycompat.bytestr(bool(matcher))
810 813 repo.ui.note(_("resolving manifests\n"))
811 814 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
812 % (bool(branchmerge), bool(force), bool(matcher)))
815 % (boolbm, boolf, boolm))
813 816 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
814 817
815 818 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
816 819 copied = set(copy.values())
817 820 copied.update(movewithdir.values())
818 821
819 822 if '.hgsubstate' in m1:
820 823 # check whether sub state is modified
821 824 if any(wctx.sub(s).dirty() for s in wctx.substate):
822 825 m1['.hgsubstate'] = modifiednodeid
823 826
824 827 # Don't use m2-vs-ma optimization if:
825 828 # - ma is the same as m1 or m2, which we're just going to diff again later
826 829 # - The caller specifically asks for a full diff, which is useful during bid
827 830 # merge.
828 831 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
829 832 # Identify which files are relevant to the merge, so we can limit the
830 833 # total m1-vs-m2 diff to just those files. This has significant
831 834 # performance benefits in large repositories.
832 835 relevantfiles = set(ma.diff(m2).keys())
833 836
834 837 # For copied and moved files, we need to add the source file too.
835 838 for copykey, copyvalue in copy.iteritems():
836 839 if copyvalue in relevantfiles:
837 840 relevantfiles.add(copykey)
838 841 for movedirkey in movewithdir.iterkeys():
839 842 relevantfiles.add(movedirkey)
840 843 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
841 844 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
842 845
843 846 diff = m1.diff(m2, match=matcher)
844 847
845 848 if matcher is None:
846 849 matcher = matchmod.always('', '')
847 850
848 851 actions = {}
849 852 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
850 853 if n1 and n2: # file exists on both local and remote side
851 854 if f not in ma:
852 855 fa = copy.get(f, None)
853 856 if fa is not None:
854 857 actions[f] = ('m', (f, f, fa, False, pa.node()),
855 858 "both renamed from " + fa)
856 859 else:
857 860 actions[f] = ('m', (f, f, None, False, pa.node()),
858 861 "both created")
859 862 else:
860 863 a = ma[f]
861 864 fla = ma.flags(f)
862 865 nol = 'l' not in fl1 + fl2 + fla
863 866 if n2 == a and fl2 == fla:
864 867 actions[f] = ('k' , (), "remote unchanged")
865 868 elif n1 == a and fl1 == fla: # local unchanged - use remote
866 869 if n1 == n2: # optimization: keep local content
867 870 actions[f] = ('e', (fl2,), "update permissions")
868 871 else:
869 872 actions[f] = ('g', (fl2, False), "remote is newer")
870 873 elif nol and n2 == a: # remote only changed 'x'
871 874 actions[f] = ('e', (fl2,), "update permissions")
872 875 elif nol and n1 == a: # local only changed 'x'
873 876 actions[f] = ('g', (fl1, False), "remote is newer")
874 877 else: # both changed something
875 878 actions[f] = ('m', (f, f, f, False, pa.node()),
876 879 "versions differ")
877 880 elif n1: # file exists only on local side
878 881 if f in copied:
879 882 pass # we'll deal with it on m2 side
880 883 elif f in movewithdir: # directory rename, move local
881 884 f2 = movewithdir[f]
882 885 if f2 in m2:
883 886 actions[f2] = ('m', (f, f2, None, True, pa.node()),
884 887 "remote directory rename, both created")
885 888 else:
886 889 actions[f2] = ('dm', (f, fl1),
887 890 "remote directory rename - move from " + f)
888 891 elif f in copy:
889 892 f2 = copy[f]
890 893 actions[f] = ('m', (f, f2, f2, False, pa.node()),
891 894 "local copied/moved from " + f2)
892 895 elif f in ma: # clean, a different, no remote
893 896 if n1 != ma[f]:
894 897 if acceptremote:
895 898 actions[f] = ('r', None, "remote delete")
896 899 else:
897 900 actions[f] = ('cd', (f, None, f, False, pa.node()),
898 901 "prompt changed/deleted")
899 902 elif n1 == addednodeid:
900 903 # This extra 'a' is added by working copy manifest to mark
901 904 # the file as locally added. We should forget it instead of
902 905 # deleting it.
903 906 actions[f] = ('f', None, "remote deleted")
904 907 else:
905 908 actions[f] = ('r', None, "other deleted")
906 909 elif n2: # file exists only on remote side
907 910 if f in copied:
908 911 pass # we'll deal with it on m1 side
909 912 elif f in movewithdir:
910 913 f2 = movewithdir[f]
911 914 if f2 in m1:
912 915 actions[f2] = ('m', (f2, f, None, False, pa.node()),
913 916 "local directory rename, both created")
914 917 else:
915 918 actions[f2] = ('dg', (f, fl2),
916 919 "local directory rename - get from " + f)
917 920 elif f in copy:
918 921 f2 = copy[f]
919 922 if f2 in m2:
920 923 actions[f] = ('m', (f2, f, f2, False, pa.node()),
921 924 "remote copied from " + f2)
922 925 else:
923 926 actions[f] = ('m', (f2, f, f2, True, pa.node()),
924 927 "remote moved from " + f2)
925 928 elif f not in ma:
926 929 # local unknown, remote created: the logic is described by the
927 930 # following table:
928 931 #
929 932 # force branchmerge different | action
930 933 # n * * | create
931 934 # y n * | create
932 935 # y y n | create
933 936 # y y y | merge
934 937 #
935 938 # Checking whether the files are different is expensive, so we
936 939 # don't do that when we can avoid it.
937 940 if not force:
938 941 actions[f] = ('c', (fl2,), "remote created")
939 942 elif not branchmerge:
940 943 actions[f] = ('c', (fl2,), "remote created")
941 944 else:
942 945 actions[f] = ('cm', (fl2, pa.node()),
943 946 "remote created, get or merge")
944 947 elif n2 != ma[f]:
945 948 df = None
946 949 for d in dirmove:
947 950 if f.startswith(d):
948 951 # new file added in a directory that was moved
949 952 df = dirmove[d] + f[len(d):]
950 953 break
951 954 if df is not None and df in m1:
952 955 actions[df] = ('m', (df, f, f, False, pa.node()),
953 956 "local directory rename - respect move from " + f)
954 957 elif acceptremote:
955 958 actions[f] = ('c', (fl2,), "remote recreating")
956 959 else:
957 960 actions[f] = ('dc', (None, f, f, False, pa.node()),
958 961 "prompt deleted/changed")
959 962
960 963 return actions, diverge, renamedelete
961 964
962 965 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
963 966 """Resolves false conflicts where the nodeid changed but the content
964 967 remained the same."""
965 968
966 969 for f, (m, args, msg) in actions.items():
967 970 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
968 971 # local did change but ended up with same content
969 972 actions[f] = 'r', None, "prompt same"
970 973 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
971 974 # remote did change but ended up with same content
972 975 del actions[f] # don't get = keep local deleted
973 976
974 977 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
975 978 acceptremote, followcopies, matcher=None,
976 979 mergeforce=False):
977 980 "Calculate the actions needed to merge mctx into wctx using ancestors"
978 981 if len(ancestors) == 1: # default
979 982 actions, diverge, renamedelete = manifestmerge(
980 983 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
981 984 acceptremote, followcopies)
982 985 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
983 986
984 987 else: # only when merge.preferancestor=* - the default
985 988 repo.ui.note(
986 989 _("note: merging %s and %s using bids from ancestors %s\n") %
987 990 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
988 991
989 992 # Call for bids
990 993 fbids = {} # mapping filename to bids (action method to list af actions)
991 994 diverge, renamedelete = None, None
992 995 for ancestor in ancestors:
993 996 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
994 997 actions, diverge1, renamedelete1 = manifestmerge(
995 998 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
996 999 acceptremote, followcopies, forcefulldiff=True)
997 1000 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
998 1001
999 1002 # Track the shortest set of warning on the theory that bid
1000 1003 # merge will correctly incorporate more information
1001 1004 if diverge is None or len(diverge1) < len(diverge):
1002 1005 diverge = diverge1
1003 1006 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1004 1007 renamedelete = renamedelete1
1005 1008
1006 1009 for f, a in sorted(actions.iteritems()):
1007 1010 m, args, msg = a
1008 1011 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1009 1012 if f in fbids:
1010 1013 d = fbids[f]
1011 1014 if m in d:
1012 1015 d[m].append(a)
1013 1016 else:
1014 1017 d[m] = [a]
1015 1018 else:
1016 1019 fbids[f] = {m: [a]}
1017 1020
1018 1021 # Pick the best bid for each file
1019 1022 repo.ui.note(_('\nauction for merging merge bids\n'))
1020 1023 actions = {}
1021 1024 dms = [] # filenames that have dm actions
1022 1025 for f, bids in sorted(fbids.items()):
1023 1026 # bids is a mapping from action method to list af actions
1024 1027 # Consensus?
1025 1028 if len(bids) == 1: # all bids are the same kind of method
1026 1029 m, l = bids.items()[0]
1027 1030 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1028 1031 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1029 1032 actions[f] = l[0]
1030 1033 if m == 'dm':
1031 1034 dms.append(f)
1032 1035 continue
1033 1036 # If keep is an option, just do it.
1034 1037 if 'k' in bids:
1035 1038 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1036 1039 actions[f] = bids['k'][0]
1037 1040 continue
1038 1041 # If there are gets and they all agree [how could they not?], do it.
1039 1042 if 'g' in bids:
1040 1043 ga0 = bids['g'][0]
1041 1044 if all(a == ga0 for a in bids['g'][1:]):
1042 1045 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1043 1046 actions[f] = ga0
1044 1047 continue
1045 1048 # TODO: Consider other simple actions such as mode changes
1046 1049 # Handle inefficient democrazy.
1047 1050 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1048 1051 for m, l in sorted(bids.items()):
1049 1052 for _f, args, msg in l:
1050 1053 repo.ui.note(' %s -> %s\n' % (msg, m))
1051 1054 # Pick random action. TODO: Instead, prompt user when resolving
1052 1055 m, l = bids.items()[0]
1053 1056 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1054 1057 (f, m))
1055 1058 actions[f] = l[0]
1056 1059 if m == 'dm':
1057 1060 dms.append(f)
1058 1061 continue
1059 1062 # Work around 'dm' that can cause multiple actions for the same file
1060 1063 for f in dms:
1061 1064 dm, (f0, flags), msg = actions[f]
1062 1065 assert dm == 'dm', dm
1063 1066 if f0 in actions and actions[f0][0] == 'r':
1064 1067 # We have one bid for removing a file and another for moving it.
1065 1068 # These two could be merged as first move and then delete ...
1066 1069 # but instead drop moving and just delete.
1067 1070 del actions[f]
1068 1071 repo.ui.note(_('end of auction\n\n'))
1069 1072
1070 1073 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1071 1074
1072 1075 if wctx.rev() is None:
1073 1076 fractions = _forgetremoved(wctx, mctx, branchmerge)
1074 1077 actions.update(fractions)
1075 1078
1076 1079 return actions, diverge, renamedelete
1077 1080
1078 1081 def batchremove(repo, actions):
1079 1082 """apply removes to the working directory
1080 1083
1081 1084 yields tuples for progress updates
1082 1085 """
1083 1086 verbose = repo.ui.verbose
1084 1087 unlinkpath = repo.wvfs.unlinkpath
1085 1088 audit = repo.wvfs.audit
1086 1089 try:
1087 1090 cwd = pycompat.getcwd()
1088 1091 except OSError as err:
1089 1092 if err.errno != errno.ENOENT:
1090 1093 raise
1091 1094 cwd = None
1092 1095 i = 0
1093 1096 for f, args, msg in actions:
1094 1097 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1095 1098 if verbose:
1096 1099 repo.ui.note(_("removing %s\n") % f)
1097 1100 audit(f)
1098 1101 try:
1099 1102 unlinkpath(f, ignoremissing=True)
1100 1103 except OSError as inst:
1101 1104 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1102 1105 (f, inst.strerror))
1103 1106 if i == 100:
1104 1107 yield i, f
1105 1108 i = 0
1106 1109 i += 1
1107 1110 if i > 0:
1108 1111 yield i, f
1109 1112 if cwd:
1110 1113 # cwd was present before we started to remove files
1111 1114 # let's check if it is present after we removed them
1112 1115 try:
1113 1116 pycompat.getcwd()
1114 1117 except OSError as err:
1115 1118 if err.errno != errno.ENOENT:
1116 1119 raise
1117 1120 # Print a warning if cwd was deleted
1118 1121 repo.ui.warn(_("current directory was removed\n"
1119 1122 "(consider changing to repo root: %s)\n") %
1120 1123 repo.root)
1121 1124
1122 1125 def batchget(repo, mctx, actions):
1123 1126 """apply gets to the working directory
1124 1127
1125 1128 mctx is the context to get from
1126 1129
1127 1130 yields tuples for progress updates
1128 1131 """
1129 1132 verbose = repo.ui.verbose
1130 1133 fctx = mctx.filectx
1131 1134 wwrite = repo.wwrite
1132 1135 ui = repo.ui
1133 1136 i = 0
1134 1137 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1135 1138 for f, (flags, backup), msg in actions:
1136 1139 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1137 1140 if verbose:
1138 1141 repo.ui.note(_("getting %s\n") % f)
1139 1142
1140 1143 if backup:
1141 1144 absf = repo.wjoin(f)
1142 1145 orig = scmutil.origpath(ui, repo, absf)
1143 1146 try:
1144 1147 if repo.wvfs.isfileorlink(f):
1145 1148 util.rename(absf, orig)
1146 1149 except OSError as e:
1147 1150 if e.errno != errno.ENOENT:
1148 1151 raise
1149 1152
1150 1153 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1151 1154 repo.wvfs.removedirs(f)
1152 1155 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1153 1156 if i == 100:
1154 1157 yield i, f
1155 1158 i = 0
1156 1159 i += 1
1157 1160 if i > 0:
1158 1161 yield i, f
1159 1162
1160 1163 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1161 1164 """apply the merge action list to the working directory
1162 1165
1163 1166 wctx is the working copy context
1164 1167 mctx is the context to be merged into the working copy
1165 1168
1166 1169 Return a tuple of counts (updated, merged, removed, unresolved) that
1167 1170 describes how many files were affected by the update.
1168 1171 """
1169 1172
1170 1173 updated, merged, removed = 0, 0, 0
1171 1174 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1172 1175 moves = []
1173 1176 for m, l in actions.items():
1174 1177 l.sort()
1175 1178
1176 1179 # 'cd' and 'dc' actions are treated like other merge conflicts
1177 1180 mergeactions = sorted(actions['cd'])
1178 1181 mergeactions.extend(sorted(actions['dc']))
1179 1182 mergeactions.extend(actions['m'])
1180 1183 for f, args, msg in mergeactions:
1181 1184 f1, f2, fa, move, anc = args
1182 1185 if f == '.hgsubstate': # merged internally
1183 1186 continue
1184 1187 if f1 is None:
1185 1188 fcl = filemerge.absentfilectx(wctx, fa)
1186 1189 else:
1187 1190 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1188 1191 fcl = wctx[f1]
1189 1192 if f2 is None:
1190 1193 fco = filemerge.absentfilectx(mctx, fa)
1191 1194 else:
1192 1195 fco = mctx[f2]
1193 1196 actx = repo[anc]
1194 1197 if fa in actx:
1195 1198 fca = actx[fa]
1196 1199 else:
1197 1200 # TODO: move to absentfilectx
1198 1201 fca = repo.filectx(f1, fileid=nullrev)
1199 1202 ms.add(fcl, fco, fca, f)
1200 1203 if f1 != f and move:
1201 1204 moves.append(f1)
1202 1205
1203 1206 audit = repo.wvfs.audit
1204 1207 _updating = _('updating')
1205 1208 _files = _('files')
1206 1209 progress = repo.ui.progress
1207 1210
1208 1211 # remove renamed files after safely stored
1209 1212 for f in moves:
1210 1213 if os.path.lexists(repo.wjoin(f)):
1211 1214 repo.ui.debug("removing %s\n" % f)
1212 1215 audit(f)
1213 1216 repo.wvfs.unlinkpath(f)
1214 1217
1215 1218 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1216 1219
1217 1220 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1218 1221 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1219 1222
1220 1223 # remove in parallel (must come first)
1221 1224 z = 0
1222 1225 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1223 1226 for i, item in prog:
1224 1227 z += i
1225 1228 progress(_updating, z, item=item, total=numupdates, unit=_files)
1226 1229 removed = len(actions['r'])
1227 1230
1228 1231 # get in parallel
1229 1232 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1230 1233 for i, item in prog:
1231 1234 z += i
1232 1235 progress(_updating, z, item=item, total=numupdates, unit=_files)
1233 1236 updated = len(actions['g'])
1234 1237
1235 1238 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1236 1239 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1237 1240
1238 1241 # forget (manifest only, just log it) (must come first)
1239 1242 for f, args, msg in actions['f']:
1240 1243 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1241 1244 z += 1
1242 1245 progress(_updating, z, item=f, total=numupdates, unit=_files)
1243 1246
1244 1247 # re-add (manifest only, just log it)
1245 1248 for f, args, msg in actions['a']:
1246 1249 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1247 1250 z += 1
1248 1251 progress(_updating, z, item=f, total=numupdates, unit=_files)
1249 1252
1250 1253 # re-add/mark as modified (manifest only, just log it)
1251 1254 for f, args, msg in actions['am']:
1252 1255 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1253 1256 z += 1
1254 1257 progress(_updating, z, item=f, total=numupdates, unit=_files)
1255 1258
1256 1259 # keep (noop, just log it)
1257 1260 for f, args, msg in actions['k']:
1258 1261 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1259 1262 # no progress
1260 1263
1261 1264 # directory rename, move local
1262 1265 for f, args, msg in actions['dm']:
1263 1266 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1264 1267 z += 1
1265 1268 progress(_updating, z, item=f, total=numupdates, unit=_files)
1266 1269 f0, flags = args
1267 1270 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1268 1271 audit(f)
1269 1272 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1270 1273 repo.wvfs.unlinkpath(f0)
1271 1274 updated += 1
1272 1275
1273 1276 # local directory rename, get
1274 1277 for f, args, msg in actions['dg']:
1275 1278 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1276 1279 z += 1
1277 1280 progress(_updating, z, item=f, total=numupdates, unit=_files)
1278 1281 f0, flags = args
1279 1282 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1280 1283 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1281 1284 updated += 1
1282 1285
1283 1286 # exec
1284 1287 for f, args, msg in actions['e']:
1285 1288 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1286 1289 z += 1
1287 1290 progress(_updating, z, item=f, total=numupdates, unit=_files)
1288 1291 flags, = args
1289 1292 audit(f)
1290 1293 repo.wvfs.setflags(f, 'l' in flags, 'x' in flags)
1291 1294 updated += 1
1292 1295
1293 1296 # the ordering is important here -- ms.mergedriver will raise if the merge
1294 1297 # driver has changed, and we want to be able to bypass it when overwrite is
1295 1298 # True
1296 1299 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1297 1300
1298 1301 if usemergedriver:
1299 1302 ms.commit()
1300 1303 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1301 1304 # the driver might leave some files unresolved
1302 1305 unresolvedf = set(ms.unresolved())
1303 1306 if not proceed:
1304 1307 # XXX setting unresolved to at least 1 is a hack to make sure we
1305 1308 # error out
1306 1309 return updated, merged, removed, max(len(unresolvedf), 1)
1307 1310 newactions = []
1308 1311 for f, args, msg in mergeactions:
1309 1312 if f in unresolvedf:
1310 1313 newactions.append((f, args, msg))
1311 1314 mergeactions = newactions
1312 1315
1313 1316 # premerge
1314 1317 tocomplete = []
1315 1318 for f, args, msg in mergeactions:
1316 1319 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1317 1320 z += 1
1318 1321 progress(_updating, z, item=f, total=numupdates, unit=_files)
1319 1322 if f == '.hgsubstate': # subrepo states need updating
1320 1323 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1321 1324 overwrite, labels)
1322 1325 continue
1323 1326 audit(f)
1324 1327 complete, r = ms.preresolve(f, wctx)
1325 1328 if not complete:
1326 1329 numupdates += 1
1327 1330 tocomplete.append((f, args, msg))
1328 1331
1329 1332 # merge
1330 1333 for f, args, msg in tocomplete:
1331 1334 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1332 1335 z += 1
1333 1336 progress(_updating, z, item=f, total=numupdates, unit=_files)
1334 1337 ms.resolve(f, wctx)
1335 1338
1336 1339 ms.commit()
1337 1340
1338 1341 unresolved = ms.unresolvedcount()
1339 1342
1340 1343 if usemergedriver and not unresolved and ms.mdstate() != 's':
1341 1344 if not driverconclude(repo, ms, wctx, labels=labels):
1342 1345 # XXX setting unresolved to at least 1 is a hack to make sure we
1343 1346 # error out
1344 1347 unresolved = max(unresolved, 1)
1345 1348
1346 1349 ms.commit()
1347 1350
1348 1351 msupdated, msmerged, msremoved = ms.counts()
1349 1352 updated += msupdated
1350 1353 merged += msmerged
1351 1354 removed += msremoved
1352 1355
1353 1356 extraactions = ms.actions()
1354 1357 if extraactions:
1355 1358 mfiles = set(a[0] for a in actions['m'])
1356 1359 for k, acts in extraactions.iteritems():
1357 1360 actions[k].extend(acts)
1358 1361 # Remove these files from actions['m'] as well. This is important
1359 1362 # because in recordupdates, files in actions['m'] are processed
1360 1363 # after files in other actions, and the merge driver might add
1361 1364 # files to those actions via extraactions above. This can lead to a
1362 1365 # file being recorded twice, with poor results. This is especially
1363 1366 # problematic for actions['r'] (currently only possible with the
1364 1367 # merge driver in the initial merge process; interrupted merges
1365 1368 # don't go through this flow).
1366 1369 #
1367 1370 # The real fix here is to have indexes by both file and action so
1368 1371 # that when the action for a file is changed it is automatically
1369 1372 # reflected in the other action lists. But that involves a more
1370 1373 # complex data structure, so this will do for now.
1371 1374 #
1372 1375 # We don't need to do the same operation for 'dc' and 'cd' because
1373 1376 # those lists aren't consulted again.
1374 1377 mfiles.difference_update(a[0] for a in acts)
1375 1378
1376 1379 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1377 1380
1378 1381 progress(_updating, None, total=numupdates, unit=_files)
1379 1382
1380 1383 return updated, merged, removed, unresolved
1381 1384
1382 1385 def recordupdates(repo, actions, branchmerge):
1383 1386 "record merge actions to the dirstate"
1384 1387 # remove (must come first)
1385 1388 for f, args, msg in actions.get('r', []):
1386 1389 if branchmerge:
1387 1390 repo.dirstate.remove(f)
1388 1391 else:
1389 1392 repo.dirstate.drop(f)
1390 1393
1391 1394 # forget (must come first)
1392 1395 for f, args, msg in actions.get('f', []):
1393 1396 repo.dirstate.drop(f)
1394 1397
1395 1398 # re-add
1396 1399 for f, args, msg in actions.get('a', []):
1397 1400 repo.dirstate.add(f)
1398 1401
1399 1402 # re-add/mark as modified
1400 1403 for f, args, msg in actions.get('am', []):
1401 1404 if branchmerge:
1402 1405 repo.dirstate.normallookup(f)
1403 1406 else:
1404 1407 repo.dirstate.add(f)
1405 1408
1406 1409 # exec change
1407 1410 for f, args, msg in actions.get('e', []):
1408 1411 repo.dirstate.normallookup(f)
1409 1412
1410 1413 # keep
1411 1414 for f, args, msg in actions.get('k', []):
1412 1415 pass
1413 1416
1414 1417 # get
1415 1418 for f, args, msg in actions.get('g', []):
1416 1419 if branchmerge:
1417 1420 repo.dirstate.otherparent(f)
1418 1421 else:
1419 1422 repo.dirstate.normal(f)
1420 1423
1421 1424 # merge
1422 1425 for f, args, msg in actions.get('m', []):
1423 1426 f1, f2, fa, move, anc = args
1424 1427 if branchmerge:
1425 1428 # We've done a branch merge, mark this file as merged
1426 1429 # so that we properly record the merger later
1427 1430 repo.dirstate.merge(f)
1428 1431 if f1 != f2: # copy/rename
1429 1432 if move:
1430 1433 repo.dirstate.remove(f1)
1431 1434 if f1 != f:
1432 1435 repo.dirstate.copy(f1, f)
1433 1436 else:
1434 1437 repo.dirstate.copy(f2, f)
1435 1438 else:
1436 1439 # We've update-merged a locally modified file, so
1437 1440 # we set the dirstate to emulate a normal checkout
1438 1441 # of that file some time in the past. Thus our
1439 1442 # merge will appear as a normal local file
1440 1443 # modification.
1441 1444 if f2 == f: # file not locally copied/moved
1442 1445 repo.dirstate.normallookup(f)
1443 1446 if move:
1444 1447 repo.dirstate.drop(f1)
1445 1448
1446 1449 # directory rename, move local
1447 1450 for f, args, msg in actions.get('dm', []):
1448 1451 f0, flag = args
1449 1452 if branchmerge:
1450 1453 repo.dirstate.add(f)
1451 1454 repo.dirstate.remove(f0)
1452 1455 repo.dirstate.copy(f0, f)
1453 1456 else:
1454 1457 repo.dirstate.normal(f)
1455 1458 repo.dirstate.drop(f0)
1456 1459
1457 1460 # directory rename, get
1458 1461 for f, args, msg in actions.get('dg', []):
1459 1462 f0, flag = args
1460 1463 if branchmerge:
1461 1464 repo.dirstate.add(f)
1462 1465 repo.dirstate.copy(f0, f)
1463 1466 else:
1464 1467 repo.dirstate.normal(f)
1465 1468
1466 1469 def update(repo, node, branchmerge, force, ancestor=None,
1467 1470 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1468 1471 updatecheck=None):
1469 1472 """
1470 1473 Perform a merge between the working directory and the given node
1471 1474
1472 1475 node = the node to update to
1473 1476 branchmerge = whether to merge between branches
1474 1477 force = whether to force branch merging or file overwriting
1475 1478 matcher = a matcher to filter file lists (dirstate not updated)
1476 1479 mergeancestor = whether it is merging with an ancestor. If true,
1477 1480 we should accept the incoming changes for any prompts that occur.
1478 1481 If false, merging with an ancestor (fast-forward) is only allowed
1479 1482 between different named branches. This flag is used by rebase extension
1480 1483 as a temporary fix and should be avoided in general.
1481 1484 labels = labels to use for base, local and other
1482 1485 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1483 1486 this is True, then 'force' should be True as well.
1484 1487
1485 1488 The table below shows all the behaviors of the update command
1486 1489 given the -c and -C or no options, whether the working directory
1487 1490 is dirty, whether a revision is specified, and the relationship of
1488 1491 the parent rev to the target rev (linear or not). Match from top first. The
1489 1492 -n option doesn't exist on the command line, but represents the
1490 1493 experimental.updatecheck=noconflict option.
1491 1494
1492 1495 This logic is tested by test-update-branches.t.
1493 1496
1494 1497 -c -C -n -m dirty rev linear | result
1495 1498 y y * * * * * | (1)
1496 1499 y * y * * * * | (1)
1497 1500 y * * y * * * | (1)
1498 1501 * y y * * * * | (1)
1499 1502 * y * y * * * | (1)
1500 1503 * * y y * * * | (1)
1501 1504 * * * * * n n | x
1502 1505 * * * * n * * | ok
1503 1506 n n n n y * y | merge
1504 1507 n n n n y y n | (2)
1505 1508 n n n y y * * | merge
1506 1509 n n y n y * * | merge if no conflict
1507 1510 n y n n y * * | discard
1508 1511 y n n n y * * | (3)
1509 1512
1510 1513 x = can't happen
1511 1514 * = don't-care
1512 1515 1 = incompatible options (checked in commands.py)
1513 1516 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1514 1517 3 = abort: uncommitted changes (checked in commands.py)
1515 1518
1516 1519 Return the same tuple as applyupdates().
1517 1520 """
1518 1521
1519 1522 # This function used to find the default destination if node was None, but
1520 1523 # that's now in destutil.py.
1521 1524 assert node is not None
1522 1525 if not branchmerge and not force:
1523 1526 # TODO: remove the default once all callers that pass branchmerge=False
1524 1527 # and force=False pass a value for updatecheck. We may want to allow
1525 1528 # updatecheck='abort' to better suppport some of these callers.
1526 1529 if updatecheck is None:
1527 1530 updatecheck = 'linear'
1528 1531 assert updatecheck in ('none', 'linear', 'noconflict')
1529 1532 # If we're doing a partial update, we need to skip updating
1530 1533 # the dirstate, so make a note of any partial-ness to the
1531 1534 # update here.
1532 1535 if matcher is None or matcher.always():
1533 1536 partial = False
1534 1537 else:
1535 1538 partial = True
1536 1539 with repo.wlock():
1537 1540 wc = repo[None]
1538 1541 pl = wc.parents()
1539 1542 p1 = pl[0]
1540 1543 pas = [None]
1541 1544 if ancestor is not None:
1542 1545 pas = [repo[ancestor]]
1543 1546
1544 1547 overwrite = force and not branchmerge
1545 1548
1546 1549 p2 = repo[node]
1547 1550 if pas[0] is None:
1548 1551 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1549 1552 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1550 1553 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1551 1554 else:
1552 1555 pas = [p1.ancestor(p2, warn=branchmerge)]
1553 1556
1554 1557 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1555 1558
1556 1559 ### check phase
1557 1560 if not overwrite:
1558 1561 if len(pl) > 1:
1559 1562 raise error.Abort(_("outstanding uncommitted merge"))
1560 1563 ms = mergestate.read(repo)
1561 1564 if list(ms.unresolved()):
1562 1565 raise error.Abort(_("outstanding merge conflicts"))
1563 1566 if branchmerge:
1564 1567 if pas == [p2]:
1565 1568 raise error.Abort(_("merging with a working directory ancestor"
1566 1569 " has no effect"))
1567 1570 elif pas == [p1]:
1568 1571 if not mergeancestor and wc.branch() == p2.branch():
1569 1572 raise error.Abort(_("nothing to merge"),
1570 1573 hint=_("use 'hg update' "
1571 1574 "or check 'hg heads'"))
1572 1575 if not force and (wc.files() or wc.deleted()):
1573 1576 raise error.Abort(_("uncommitted changes"),
1574 1577 hint=_("use 'hg status' to list changes"))
1575 1578 for s in sorted(wc.substate):
1576 1579 wc.sub(s).bailifchanged()
1577 1580
1578 1581 elif not overwrite:
1579 1582 if p1 == p2: # no-op update
1580 1583 # call the hooks and exit early
1581 1584 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1582 1585 repo.hook('update', parent1=xp2, parent2='', error=0)
1583 1586 return 0, 0, 0, 0
1584 1587
1585 1588 if (updatecheck == 'linear' and
1586 1589 pas not in ([p1], [p2])): # nonlinear
1587 1590 dirty = wc.dirty(missing=True)
1588 1591 if dirty:
1589 1592 # Branching is a bit strange to ensure we do the minimal
1590 1593 # amount of call to obsolete.foreground.
1591 1594 foreground = obsolete.foreground(repo, [p1.node()])
1592 1595 # note: the <node> variable contains a random identifier
1593 1596 if repo[node].node() in foreground:
1594 1597 pass # allow updating to successors
1595 1598 else:
1596 1599 msg = _("uncommitted changes")
1597 1600 hint = _("commit or update --clean to discard changes")
1598 1601 raise error.UpdateAbort(msg, hint=hint)
1599 1602 else:
1600 1603 # Allow jumping branches if clean and specific rev given
1601 1604 pass
1602 1605
1603 1606 if overwrite:
1604 1607 pas = [wc]
1605 1608 elif not branchmerge:
1606 1609 pas = [p1]
1607 1610
1608 1611 # deprecated config: merge.followcopies
1609 1612 followcopies = repo.ui.configbool('merge', 'followcopies', True)
1610 1613 if overwrite:
1611 1614 followcopies = False
1612 1615 elif not pas[0]:
1613 1616 followcopies = False
1614 1617 if not branchmerge and not wc.dirty(missing=True):
1615 1618 followcopies = False
1616 1619
1617 1620 ### calculate phase
1618 1621 actionbyfile, diverge, renamedelete = calculateupdates(
1619 1622 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1620 1623 followcopies, matcher=matcher, mergeforce=mergeforce)
1621 1624
1622 1625 if updatecheck == 'noconflict':
1623 1626 for f, (m, args, msg) in actionbyfile.iteritems():
1624 1627 if m not in ('g', 'k', 'e', 'r'):
1625 1628 msg = _("conflicting changes")
1626 1629 hint = _("commit or update --clean to discard changes")
1627 1630 raise error.Abort(msg, hint=hint)
1628 1631
1629 1632 # Prompt and create actions. Most of this is in the resolve phase
1630 1633 # already, but we can't handle .hgsubstate in filemerge or
1631 1634 # subrepo.submerge yet so we have to keep prompting for it.
1632 1635 if '.hgsubstate' in actionbyfile:
1633 1636 f = '.hgsubstate'
1634 1637 m, args, msg = actionbyfile[f]
1635 1638 prompts = filemerge.partextras(labels)
1636 1639 prompts['f'] = f
1637 1640 if m == 'cd':
1638 1641 if repo.ui.promptchoice(
1639 1642 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1640 1643 "use (c)hanged version or (d)elete?"
1641 1644 "$$ &Changed $$ &Delete") % prompts, 0):
1642 1645 actionbyfile[f] = ('r', None, "prompt delete")
1643 1646 elif f in p1:
1644 1647 actionbyfile[f] = ('am', None, "prompt keep")
1645 1648 else:
1646 1649 actionbyfile[f] = ('a', None, "prompt keep")
1647 1650 elif m == 'dc':
1648 1651 f1, f2, fa, move, anc = args
1649 1652 flags = p2[f2].flags()
1650 1653 if repo.ui.promptchoice(
1651 1654 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1652 1655 "use (c)hanged version or leave (d)eleted?"
1653 1656 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1654 1657 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1655 1658 else:
1656 1659 del actionbyfile[f]
1657 1660
1658 1661 # Convert to dictionary-of-lists format
1659 1662 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1660 1663 for f, (m, args, msg) in actionbyfile.iteritems():
1661 1664 if m not in actions:
1662 1665 actions[m] = []
1663 1666 actions[m].append((f, args, msg))
1664 1667
1665 1668 if not util.fscasesensitive(repo.path):
1666 1669 # check collision between files only in p2 for clean update
1667 1670 if (not branchmerge and
1668 1671 (force or not wc.dirty(missing=True, branch=False))):
1669 1672 _checkcollision(repo, p2.manifest(), None)
1670 1673 else:
1671 1674 _checkcollision(repo, wc.manifest(), actions)
1672 1675
1673 1676 # divergent renames
1674 1677 for f, fl in sorted(diverge.iteritems()):
1675 1678 repo.ui.warn(_("note: possible conflict - %s was renamed "
1676 1679 "multiple times to:\n") % f)
1677 1680 for nf in fl:
1678 1681 repo.ui.warn(" %s\n" % nf)
1679 1682
1680 1683 # rename and delete
1681 1684 for f, fl in sorted(renamedelete.iteritems()):
1682 1685 repo.ui.warn(_("note: possible conflict - %s was deleted "
1683 1686 "and renamed to:\n") % f)
1684 1687 for nf in fl:
1685 1688 repo.ui.warn(" %s\n" % nf)
1686 1689
1687 1690 ### apply phase
1688 1691 if not branchmerge: # just jump to the new rev
1689 1692 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1690 1693 if not partial:
1691 1694 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1692 1695 # note that we're in the middle of an update
1693 1696 repo.vfs.write('updatestate', p2.hex())
1694 1697
1695 1698 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1696 1699
1697 1700 if not partial:
1698 1701 with repo.dirstate.parentchange():
1699 1702 repo.setparents(fp1, fp2)
1700 1703 recordupdates(repo, actions, branchmerge)
1701 1704 # update completed, clear state
1702 1705 util.unlink(repo.vfs.join('updatestate'))
1703 1706
1704 1707 if not branchmerge:
1705 1708 repo.dirstate.setbranch(p2.branch())
1706 1709
1707 1710 if not partial:
1708 1711 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1709 1712 return stats
1710 1713
1711 1714 def graft(repo, ctx, pctx, labels, keepparent=False):
1712 1715 """Do a graft-like merge.
1713 1716
1714 1717 This is a merge where the merge ancestor is chosen such that one
1715 1718 or more changesets are grafted onto the current changeset. In
1716 1719 addition to the merge, this fixes up the dirstate to include only
1717 1720 a single parent (if keepparent is False) and tries to duplicate any
1718 1721 renames/copies appropriately.
1719 1722
1720 1723 ctx - changeset to rebase
1721 1724 pctx - merge base, usually ctx.p1()
1722 1725 labels - merge labels eg ['local', 'graft']
1723 1726 keepparent - keep second parent if any
1724 1727
1725 1728 """
1726 1729 # If we're grafting a descendant onto an ancestor, be sure to pass
1727 1730 # mergeancestor=True to update. This does two things: 1) allows the merge if
1728 1731 # the destination is the same as the parent of the ctx (so we can use graft
1729 1732 # to copy commits), and 2) informs update that the incoming changes are
1730 1733 # newer than the destination so it doesn't prompt about "remote changed foo
1731 1734 # which local deleted".
1732 1735 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1733 1736
1734 1737 stats = update(repo, ctx.node(), True, True, pctx.node(),
1735 1738 mergeancestor=mergeancestor, labels=labels)
1736 1739
1737 1740 pother = nullid
1738 1741 parents = ctx.parents()
1739 1742 if keepparent and len(parents) == 2 and pctx in parents:
1740 1743 parents.remove(pctx)
1741 1744 pother = parents[0].node()
1742 1745
1743 1746 with repo.dirstate.parentchange():
1744 1747 repo.setparents(repo['.'].node(), pother)
1745 1748 repo.dirstate.write(repo.currenttransaction())
1746 1749 # fix up dirstate for copies and renames
1747 1750 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1748 1751 return stats
General Comments 0
You need to be logged in to leave comments. Login now