##// END OF EJS Templates
merge: migrate to context manager for changing dirstate parents
Augie Fackler -
r32351:c568c187 default
parent child Browse files
Show More
@@ -1,1751 +1,1749
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())]
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 810 repo.ui.note(_("resolving manifests\n"))
811 811 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
812 812 % (bool(branchmerge), bool(force), bool(matcher)))
813 813 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
814 814
815 815 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
816 816 copied = set(copy.values())
817 817 copied.update(movewithdir.values())
818 818
819 819 if '.hgsubstate' in m1:
820 820 # check whether sub state is modified
821 821 if any(wctx.sub(s).dirty() for s in wctx.substate):
822 822 m1['.hgsubstate'] = modifiednodeid
823 823
824 824 # Don't use m2-vs-ma optimization if:
825 825 # - ma is the same as m1 or m2, which we're just going to diff again later
826 826 # - The matcher is set already, so we can't override it
827 827 # - The caller specifically asks for a full diff, which is useful during bid
828 828 # merge.
829 829 if (pa not in ([wctx, p2] + wctx.parents()) and
830 830 matcher is None and not forcefulldiff):
831 831 # Identify which files are relevant to the merge, so we can limit the
832 832 # total m1-vs-m2 diff to just those files. This has significant
833 833 # performance benefits in large repositories.
834 834 relevantfiles = set(ma.diff(m2).keys())
835 835
836 836 # For copied and moved files, we need to add the source file too.
837 837 for copykey, copyvalue in copy.iteritems():
838 838 if copyvalue in relevantfiles:
839 839 relevantfiles.add(copykey)
840 840 for movedirkey in movewithdir.iterkeys():
841 841 relevantfiles.add(movedirkey)
842 842 matcher = scmutil.matchfiles(repo, relevantfiles)
843 843
844 844 diff = m1.diff(m2, match=matcher)
845 845
846 846 if matcher is None:
847 847 matcher = matchmod.always('', '')
848 848
849 849 actions = {}
850 850 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
851 851 if n1 and n2: # file exists on both local and remote side
852 852 if f not in ma:
853 853 fa = copy.get(f, None)
854 854 if fa is not None:
855 855 actions[f] = ('m', (f, f, fa, False, pa.node()),
856 856 "both renamed from " + fa)
857 857 else:
858 858 actions[f] = ('m', (f, f, None, False, pa.node()),
859 859 "both created")
860 860 else:
861 861 a = ma[f]
862 862 fla = ma.flags(f)
863 863 nol = 'l' not in fl1 + fl2 + fla
864 864 if n2 == a and fl2 == fla:
865 865 actions[f] = ('k' , (), "remote unchanged")
866 866 elif n1 == a and fl1 == fla: # local unchanged - use remote
867 867 if n1 == n2: # optimization: keep local content
868 868 actions[f] = ('e', (fl2,), "update permissions")
869 869 else:
870 870 actions[f] = ('g', (fl2, False), "remote is newer")
871 871 elif nol and n2 == a: # remote only changed 'x'
872 872 actions[f] = ('e', (fl2,), "update permissions")
873 873 elif nol and n1 == a: # local only changed 'x'
874 874 actions[f] = ('g', (fl1, False), "remote is newer")
875 875 else: # both changed something
876 876 actions[f] = ('m', (f, f, f, False, pa.node()),
877 877 "versions differ")
878 878 elif n1: # file exists only on local side
879 879 if f in copied:
880 880 pass # we'll deal with it on m2 side
881 881 elif f in movewithdir: # directory rename, move local
882 882 f2 = movewithdir[f]
883 883 if f2 in m2:
884 884 actions[f2] = ('m', (f, f2, None, True, pa.node()),
885 885 "remote directory rename, both created")
886 886 else:
887 887 actions[f2] = ('dm', (f, fl1),
888 888 "remote directory rename - move from " + f)
889 889 elif f in copy:
890 890 f2 = copy[f]
891 891 actions[f] = ('m', (f, f2, f2, False, pa.node()),
892 892 "local copied/moved from " + f2)
893 893 elif f in ma: # clean, a different, no remote
894 894 if n1 != ma[f]:
895 895 if acceptremote:
896 896 actions[f] = ('r', None, "remote delete")
897 897 else:
898 898 actions[f] = ('cd', (f, None, f, False, pa.node()),
899 899 "prompt changed/deleted")
900 900 elif n1 == addednodeid:
901 901 # This extra 'a' is added by working copy manifest to mark
902 902 # the file as locally added. We should forget it instead of
903 903 # deleting it.
904 904 actions[f] = ('f', None, "remote deleted")
905 905 else:
906 906 actions[f] = ('r', None, "other deleted")
907 907 elif n2: # file exists only on remote side
908 908 if f in copied:
909 909 pass # we'll deal with it on m1 side
910 910 elif f in movewithdir:
911 911 f2 = movewithdir[f]
912 912 if f2 in m1:
913 913 actions[f2] = ('m', (f2, f, None, False, pa.node()),
914 914 "local directory rename, both created")
915 915 else:
916 916 actions[f2] = ('dg', (f, fl2),
917 917 "local directory rename - get from " + f)
918 918 elif f in copy:
919 919 f2 = copy[f]
920 920 if f2 in m2:
921 921 actions[f] = ('m', (f2, f, f2, False, pa.node()),
922 922 "remote copied from " + f2)
923 923 else:
924 924 actions[f] = ('m', (f2, f, f2, True, pa.node()),
925 925 "remote moved from " + f2)
926 926 elif f not in ma:
927 927 # local unknown, remote created: the logic is described by the
928 928 # following table:
929 929 #
930 930 # force branchmerge different | action
931 931 # n * * | create
932 932 # y n * | create
933 933 # y y n | create
934 934 # y y y | merge
935 935 #
936 936 # Checking whether the files are different is expensive, so we
937 937 # don't do that when we can avoid it.
938 938 if not force:
939 939 actions[f] = ('c', (fl2,), "remote created")
940 940 elif not branchmerge:
941 941 actions[f] = ('c', (fl2,), "remote created")
942 942 else:
943 943 actions[f] = ('cm', (fl2, pa.node()),
944 944 "remote created, get or merge")
945 945 elif n2 != ma[f]:
946 946 df = None
947 947 for d in dirmove:
948 948 if f.startswith(d):
949 949 # new file added in a directory that was moved
950 950 df = dirmove[d] + f[len(d):]
951 951 break
952 952 if df is not None and df in m1:
953 953 actions[df] = ('m', (df, f, f, False, pa.node()),
954 954 "local directory rename - respect move from " + f)
955 955 elif acceptremote:
956 956 actions[f] = ('c', (fl2,), "remote recreating")
957 957 else:
958 958 actions[f] = ('dc', (None, f, f, False, pa.node()),
959 959 "prompt deleted/changed")
960 960
961 961 return actions, diverge, renamedelete
962 962
963 963 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
964 964 """Resolves false conflicts where the nodeid changed but the content
965 965 remained the same."""
966 966
967 967 for f, (m, args, msg) in actions.items():
968 968 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
969 969 # local did change but ended up with same content
970 970 actions[f] = 'r', None, "prompt same"
971 971 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
972 972 # remote did change but ended up with same content
973 973 del actions[f] # don't get = keep local deleted
974 974
975 975 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
976 976 acceptremote, followcopies, matcher=None,
977 977 mergeforce=False):
978 978 "Calculate the actions needed to merge mctx into wctx using ancestors"
979 979 if len(ancestors) == 1: # default
980 980 actions, diverge, renamedelete = manifestmerge(
981 981 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
982 982 acceptremote, followcopies)
983 983 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
984 984
985 985 else: # only when merge.preferancestor=* - the default
986 986 repo.ui.note(
987 987 _("note: merging %s and %s using bids from ancestors %s\n") %
988 988 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
989 989
990 990 # Call for bids
991 991 fbids = {} # mapping filename to bids (action method to list af actions)
992 992 diverge, renamedelete = None, None
993 993 for ancestor in ancestors:
994 994 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
995 995 actions, diverge1, renamedelete1 = manifestmerge(
996 996 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
997 997 acceptremote, followcopies, forcefulldiff=True)
998 998 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
999 999
1000 1000 # Track the shortest set of warning on the theory that bid
1001 1001 # merge will correctly incorporate more information
1002 1002 if diverge is None or len(diverge1) < len(diverge):
1003 1003 diverge = diverge1
1004 1004 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1005 1005 renamedelete = renamedelete1
1006 1006
1007 1007 for f, a in sorted(actions.iteritems()):
1008 1008 m, args, msg = a
1009 1009 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1010 1010 if f in fbids:
1011 1011 d = fbids[f]
1012 1012 if m in d:
1013 1013 d[m].append(a)
1014 1014 else:
1015 1015 d[m] = [a]
1016 1016 else:
1017 1017 fbids[f] = {m: [a]}
1018 1018
1019 1019 # Pick the best bid for each file
1020 1020 repo.ui.note(_('\nauction for merging merge bids\n'))
1021 1021 actions = {}
1022 1022 dms = [] # filenames that have dm actions
1023 1023 for f, bids in sorted(fbids.items()):
1024 1024 # bids is a mapping from action method to list af actions
1025 1025 # Consensus?
1026 1026 if len(bids) == 1: # all bids are the same kind of method
1027 1027 m, l = bids.items()[0]
1028 1028 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1029 1029 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1030 1030 actions[f] = l[0]
1031 1031 if m == 'dm':
1032 1032 dms.append(f)
1033 1033 continue
1034 1034 # If keep is an option, just do it.
1035 1035 if 'k' in bids:
1036 1036 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1037 1037 actions[f] = bids['k'][0]
1038 1038 continue
1039 1039 # If there are gets and they all agree [how could they not?], do it.
1040 1040 if 'g' in bids:
1041 1041 ga0 = bids['g'][0]
1042 1042 if all(a == ga0 for a in bids['g'][1:]):
1043 1043 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1044 1044 actions[f] = ga0
1045 1045 continue
1046 1046 # TODO: Consider other simple actions such as mode changes
1047 1047 # Handle inefficient democrazy.
1048 1048 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1049 1049 for m, l in sorted(bids.items()):
1050 1050 for _f, args, msg in l:
1051 1051 repo.ui.note(' %s -> %s\n' % (msg, m))
1052 1052 # Pick random action. TODO: Instead, prompt user when resolving
1053 1053 m, l = bids.items()[0]
1054 1054 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1055 1055 (f, m))
1056 1056 actions[f] = l[0]
1057 1057 if m == 'dm':
1058 1058 dms.append(f)
1059 1059 continue
1060 1060 # Work around 'dm' that can cause multiple actions for the same file
1061 1061 for f in dms:
1062 1062 dm, (f0, flags), msg = actions[f]
1063 1063 assert dm == 'dm', dm
1064 1064 if f0 in actions and actions[f0][0] == 'r':
1065 1065 # We have one bid for removing a file and another for moving it.
1066 1066 # These two could be merged as first move and then delete ...
1067 1067 # but instead drop moving and just delete.
1068 1068 del actions[f]
1069 1069 repo.ui.note(_('end of auction\n\n'))
1070 1070
1071 1071 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1072 1072
1073 1073 if wctx.rev() is None:
1074 1074 fractions = _forgetremoved(wctx, mctx, branchmerge)
1075 1075 actions.update(fractions)
1076 1076
1077 1077 return actions, diverge, renamedelete
1078 1078
1079 1079 def batchremove(repo, actions):
1080 1080 """apply removes to the working directory
1081 1081
1082 1082 yields tuples for progress updates
1083 1083 """
1084 1084 verbose = repo.ui.verbose
1085 1085 unlinkpath = repo.wvfs.unlinkpath
1086 1086 audit = repo.wvfs.audit
1087 1087 try:
1088 1088 cwd = pycompat.getcwd()
1089 1089 except OSError as err:
1090 1090 if err.errno != errno.ENOENT:
1091 1091 raise
1092 1092 cwd = None
1093 1093 i = 0
1094 1094 for f, args, msg in actions:
1095 1095 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1096 1096 if verbose:
1097 1097 repo.ui.note(_("removing %s\n") % f)
1098 1098 audit(f)
1099 1099 try:
1100 1100 unlinkpath(f, ignoremissing=True)
1101 1101 except OSError as inst:
1102 1102 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1103 1103 (f, inst.strerror))
1104 1104 if i == 100:
1105 1105 yield i, f
1106 1106 i = 0
1107 1107 i += 1
1108 1108 if i > 0:
1109 1109 yield i, f
1110 1110 if cwd:
1111 1111 # cwd was present before we started to remove files
1112 1112 # let's check if it is present after we removed them
1113 1113 try:
1114 1114 pycompat.getcwd()
1115 1115 except OSError as err:
1116 1116 if err.errno != errno.ENOENT:
1117 1117 raise
1118 1118 # Print a warning if cwd was deleted
1119 1119 repo.ui.warn(_("current directory was removed\n"
1120 1120 "(consider changing to repo root: %s)\n") %
1121 1121 repo.root)
1122 1122
1123 1123 def batchget(repo, mctx, actions):
1124 1124 """apply gets to the working directory
1125 1125
1126 1126 mctx is the context to get from
1127 1127
1128 1128 yields tuples for progress updates
1129 1129 """
1130 1130 verbose = repo.ui.verbose
1131 1131 fctx = mctx.filectx
1132 1132 wwrite = repo.wwrite
1133 1133 ui = repo.ui
1134 1134 i = 0
1135 1135 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1136 1136 for f, (flags, backup), msg in actions:
1137 1137 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1138 1138 if verbose:
1139 1139 repo.ui.note(_("getting %s\n") % f)
1140 1140
1141 1141 if backup:
1142 1142 absf = repo.wjoin(f)
1143 1143 orig = scmutil.origpath(ui, repo, absf)
1144 1144 try:
1145 1145 if repo.wvfs.isfileorlink(f):
1146 1146 util.rename(absf, orig)
1147 1147 except OSError as e:
1148 1148 if e.errno != errno.ENOENT:
1149 1149 raise
1150 1150
1151 1151 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1152 1152 repo.wvfs.removedirs(f)
1153 1153 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1154 1154 if i == 100:
1155 1155 yield i, f
1156 1156 i = 0
1157 1157 i += 1
1158 1158 if i > 0:
1159 1159 yield i, f
1160 1160
1161 1161 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1162 1162 """apply the merge action list to the working directory
1163 1163
1164 1164 wctx is the working copy context
1165 1165 mctx is the context to be merged into the working copy
1166 1166
1167 1167 Return a tuple of counts (updated, merged, removed, unresolved) that
1168 1168 describes how many files were affected by the update.
1169 1169 """
1170 1170
1171 1171 updated, merged, removed = 0, 0, 0
1172 1172 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1173 1173 moves = []
1174 1174 for m, l in actions.items():
1175 1175 l.sort()
1176 1176
1177 1177 # 'cd' and 'dc' actions are treated like other merge conflicts
1178 1178 mergeactions = sorted(actions['cd'])
1179 1179 mergeactions.extend(sorted(actions['dc']))
1180 1180 mergeactions.extend(actions['m'])
1181 1181 for f, args, msg in mergeactions:
1182 1182 f1, f2, fa, move, anc = args
1183 1183 if f == '.hgsubstate': # merged internally
1184 1184 continue
1185 1185 if f1 is None:
1186 1186 fcl = filemerge.absentfilectx(wctx, fa)
1187 1187 else:
1188 1188 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1189 1189 fcl = wctx[f1]
1190 1190 if f2 is None:
1191 1191 fco = filemerge.absentfilectx(mctx, fa)
1192 1192 else:
1193 1193 fco = mctx[f2]
1194 1194 actx = repo[anc]
1195 1195 if fa in actx:
1196 1196 fca = actx[fa]
1197 1197 else:
1198 1198 # TODO: move to absentfilectx
1199 1199 fca = repo.filectx(f1, fileid=nullrev)
1200 1200 ms.add(fcl, fco, fca, f)
1201 1201 if f1 != f and move:
1202 1202 moves.append(f1)
1203 1203
1204 1204 audit = repo.wvfs.audit
1205 1205 _updating = _('updating')
1206 1206 _files = _('files')
1207 1207 progress = repo.ui.progress
1208 1208
1209 1209 # remove renamed files after safely stored
1210 1210 for f in moves:
1211 1211 if os.path.lexists(repo.wjoin(f)):
1212 1212 repo.ui.debug("removing %s\n" % f)
1213 1213 audit(f)
1214 1214 repo.wvfs.unlinkpath(f)
1215 1215
1216 1216 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1217 1217
1218 1218 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1219 1219 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1220 1220
1221 1221 # remove in parallel (must come first)
1222 1222 z = 0
1223 1223 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1224 1224 for i, item in prog:
1225 1225 z += i
1226 1226 progress(_updating, z, item=item, total=numupdates, unit=_files)
1227 1227 removed = len(actions['r'])
1228 1228
1229 1229 # get in parallel
1230 1230 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1231 1231 for i, item in prog:
1232 1232 z += i
1233 1233 progress(_updating, z, item=item, total=numupdates, unit=_files)
1234 1234 updated = len(actions['g'])
1235 1235
1236 1236 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1237 1237 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1238 1238
1239 1239 # forget (manifest only, just log it) (must come first)
1240 1240 for f, args, msg in actions['f']:
1241 1241 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1242 1242 z += 1
1243 1243 progress(_updating, z, item=f, total=numupdates, unit=_files)
1244 1244
1245 1245 # re-add (manifest only, just log it)
1246 1246 for f, args, msg in actions['a']:
1247 1247 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1248 1248 z += 1
1249 1249 progress(_updating, z, item=f, total=numupdates, unit=_files)
1250 1250
1251 1251 # re-add/mark as modified (manifest only, just log it)
1252 1252 for f, args, msg in actions['am']:
1253 1253 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1254 1254 z += 1
1255 1255 progress(_updating, z, item=f, total=numupdates, unit=_files)
1256 1256
1257 1257 # keep (noop, just log it)
1258 1258 for f, args, msg in actions['k']:
1259 1259 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1260 1260 # no progress
1261 1261
1262 1262 # directory rename, move local
1263 1263 for f, args, msg in actions['dm']:
1264 1264 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1265 1265 z += 1
1266 1266 progress(_updating, z, item=f, total=numupdates, unit=_files)
1267 1267 f0, flags = args
1268 1268 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1269 1269 audit(f)
1270 1270 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1271 1271 repo.wvfs.unlinkpath(f0)
1272 1272 updated += 1
1273 1273
1274 1274 # local directory rename, get
1275 1275 for f, args, msg in actions['dg']:
1276 1276 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1277 1277 z += 1
1278 1278 progress(_updating, z, item=f, total=numupdates, unit=_files)
1279 1279 f0, flags = args
1280 1280 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1281 1281 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1282 1282 updated += 1
1283 1283
1284 1284 # exec
1285 1285 for f, args, msg in actions['e']:
1286 1286 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1287 1287 z += 1
1288 1288 progress(_updating, z, item=f, total=numupdates, unit=_files)
1289 1289 flags, = args
1290 1290 audit(f)
1291 1291 repo.wvfs.setflags(f, 'l' in flags, 'x' in flags)
1292 1292 updated += 1
1293 1293
1294 1294 # the ordering is important here -- ms.mergedriver will raise if the merge
1295 1295 # driver has changed, and we want to be able to bypass it when overwrite is
1296 1296 # True
1297 1297 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1298 1298
1299 1299 if usemergedriver:
1300 1300 ms.commit()
1301 1301 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1302 1302 # the driver might leave some files unresolved
1303 1303 unresolvedf = set(ms.unresolved())
1304 1304 if not proceed:
1305 1305 # XXX setting unresolved to at least 1 is a hack to make sure we
1306 1306 # error out
1307 1307 return updated, merged, removed, max(len(unresolvedf), 1)
1308 1308 newactions = []
1309 1309 for f, args, msg in mergeactions:
1310 1310 if f in unresolvedf:
1311 1311 newactions.append((f, args, msg))
1312 1312 mergeactions = newactions
1313 1313
1314 1314 # premerge
1315 1315 tocomplete = []
1316 1316 for f, args, msg in mergeactions:
1317 1317 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1318 1318 z += 1
1319 1319 progress(_updating, z, item=f, total=numupdates, unit=_files)
1320 1320 if f == '.hgsubstate': # subrepo states need updating
1321 1321 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1322 1322 overwrite, labels)
1323 1323 continue
1324 1324 audit(f)
1325 1325 complete, r = ms.preresolve(f, wctx)
1326 1326 if not complete:
1327 1327 numupdates += 1
1328 1328 tocomplete.append((f, args, msg))
1329 1329
1330 1330 # merge
1331 1331 for f, args, msg in tocomplete:
1332 1332 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1333 1333 z += 1
1334 1334 progress(_updating, z, item=f, total=numupdates, unit=_files)
1335 1335 ms.resolve(f, wctx)
1336 1336
1337 1337 ms.commit()
1338 1338
1339 1339 unresolved = ms.unresolvedcount()
1340 1340
1341 1341 if usemergedriver and not unresolved and ms.mdstate() != 's':
1342 1342 if not driverconclude(repo, ms, wctx, labels=labels):
1343 1343 # XXX setting unresolved to at least 1 is a hack to make sure we
1344 1344 # error out
1345 1345 unresolved = max(unresolved, 1)
1346 1346
1347 1347 ms.commit()
1348 1348
1349 1349 msupdated, msmerged, msremoved = ms.counts()
1350 1350 updated += msupdated
1351 1351 merged += msmerged
1352 1352 removed += msremoved
1353 1353
1354 1354 extraactions = ms.actions()
1355 1355 if extraactions:
1356 1356 mfiles = set(a[0] for a in actions['m'])
1357 1357 for k, acts in extraactions.iteritems():
1358 1358 actions[k].extend(acts)
1359 1359 # Remove these files from actions['m'] as well. This is important
1360 1360 # because in recordupdates, files in actions['m'] are processed
1361 1361 # after files in other actions, and the merge driver might add
1362 1362 # files to those actions via extraactions above. This can lead to a
1363 1363 # file being recorded twice, with poor results. This is especially
1364 1364 # problematic for actions['r'] (currently only possible with the
1365 1365 # merge driver in the initial merge process; interrupted merges
1366 1366 # don't go through this flow).
1367 1367 #
1368 1368 # The real fix here is to have indexes by both file and action so
1369 1369 # that when the action for a file is changed it is automatically
1370 1370 # reflected in the other action lists. But that involves a more
1371 1371 # complex data structure, so this will do for now.
1372 1372 #
1373 1373 # We don't need to do the same operation for 'dc' and 'cd' because
1374 1374 # those lists aren't consulted again.
1375 1375 mfiles.difference_update(a[0] for a in acts)
1376 1376
1377 1377 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1378 1378
1379 1379 progress(_updating, None, total=numupdates, unit=_files)
1380 1380
1381 1381 return updated, merged, removed, unresolved
1382 1382
1383 1383 def recordupdates(repo, actions, branchmerge):
1384 1384 "record merge actions to the dirstate"
1385 1385 # remove (must come first)
1386 1386 for f, args, msg in actions.get('r', []):
1387 1387 if branchmerge:
1388 1388 repo.dirstate.remove(f)
1389 1389 else:
1390 1390 repo.dirstate.drop(f)
1391 1391
1392 1392 # forget (must come first)
1393 1393 for f, args, msg in actions.get('f', []):
1394 1394 repo.dirstate.drop(f)
1395 1395
1396 1396 # re-add
1397 1397 for f, args, msg in actions.get('a', []):
1398 1398 repo.dirstate.add(f)
1399 1399
1400 1400 # re-add/mark as modified
1401 1401 for f, args, msg in actions.get('am', []):
1402 1402 if branchmerge:
1403 1403 repo.dirstate.normallookup(f)
1404 1404 else:
1405 1405 repo.dirstate.add(f)
1406 1406
1407 1407 # exec change
1408 1408 for f, args, msg in actions.get('e', []):
1409 1409 repo.dirstate.normallookup(f)
1410 1410
1411 1411 # keep
1412 1412 for f, args, msg in actions.get('k', []):
1413 1413 pass
1414 1414
1415 1415 # get
1416 1416 for f, args, msg in actions.get('g', []):
1417 1417 if branchmerge:
1418 1418 repo.dirstate.otherparent(f)
1419 1419 else:
1420 1420 repo.dirstate.normal(f)
1421 1421
1422 1422 # merge
1423 1423 for f, args, msg in actions.get('m', []):
1424 1424 f1, f2, fa, move, anc = args
1425 1425 if branchmerge:
1426 1426 # We've done a branch merge, mark this file as merged
1427 1427 # so that we properly record the merger later
1428 1428 repo.dirstate.merge(f)
1429 1429 if f1 != f2: # copy/rename
1430 1430 if move:
1431 1431 repo.dirstate.remove(f1)
1432 1432 if f1 != f:
1433 1433 repo.dirstate.copy(f1, f)
1434 1434 else:
1435 1435 repo.dirstate.copy(f2, f)
1436 1436 else:
1437 1437 # We've update-merged a locally modified file, so
1438 1438 # we set the dirstate to emulate a normal checkout
1439 1439 # of that file some time in the past. Thus our
1440 1440 # merge will appear as a normal local file
1441 1441 # modification.
1442 1442 if f2 == f: # file not locally copied/moved
1443 1443 repo.dirstate.normallookup(f)
1444 1444 if move:
1445 1445 repo.dirstate.drop(f1)
1446 1446
1447 1447 # directory rename, move local
1448 1448 for f, args, msg in actions.get('dm', []):
1449 1449 f0, flag = args
1450 1450 if branchmerge:
1451 1451 repo.dirstate.add(f)
1452 1452 repo.dirstate.remove(f0)
1453 1453 repo.dirstate.copy(f0, f)
1454 1454 else:
1455 1455 repo.dirstate.normal(f)
1456 1456 repo.dirstate.drop(f0)
1457 1457
1458 1458 # directory rename, get
1459 1459 for f, args, msg in actions.get('dg', []):
1460 1460 f0, flag = args
1461 1461 if branchmerge:
1462 1462 repo.dirstate.add(f)
1463 1463 repo.dirstate.copy(f0, f)
1464 1464 else:
1465 1465 repo.dirstate.normal(f)
1466 1466
1467 1467 def update(repo, node, branchmerge, force, ancestor=None,
1468 1468 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1469 1469 updatecheck=None):
1470 1470 """
1471 1471 Perform a merge between the working directory and the given node
1472 1472
1473 1473 node = the node to update to
1474 1474 branchmerge = whether to merge between branches
1475 1475 force = whether to force branch merging or file overwriting
1476 1476 matcher = a matcher to filter file lists (dirstate not updated)
1477 1477 mergeancestor = whether it is merging with an ancestor. If true,
1478 1478 we should accept the incoming changes for any prompts that occur.
1479 1479 If false, merging with an ancestor (fast-forward) is only allowed
1480 1480 between different named branches. This flag is used by rebase extension
1481 1481 as a temporary fix and should be avoided in general.
1482 1482 labels = labels to use for base, local and other
1483 1483 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1484 1484 this is True, then 'force' should be True as well.
1485 1485
1486 1486 The table below shows all the behaviors of the update command
1487 1487 given the -c and -C or no options, whether the working directory
1488 1488 is dirty, whether a revision is specified, and the relationship of
1489 1489 the parent rev to the target rev (linear or not). Match from top first. The
1490 1490 -n option doesn't exist on the command line, but represents the
1491 1491 experimental.updatecheck=noconflict option.
1492 1492
1493 1493 This logic is tested by test-update-branches.t.
1494 1494
1495 1495 -c -C -n -m dirty rev linear | result
1496 1496 y y * * * * * | (1)
1497 1497 y * y * * * * | (1)
1498 1498 y * * y * * * | (1)
1499 1499 * y y * * * * | (1)
1500 1500 * y * y * * * | (1)
1501 1501 * * y y * * * | (1)
1502 1502 * * * * * n n | x
1503 1503 * * * * n * * | ok
1504 1504 n n n n y * y | merge
1505 1505 n n n n y y n | (2)
1506 1506 n n n y y * * | merge
1507 1507 n n y n y * * | merge if no conflict
1508 1508 n y n n y * * | discard
1509 1509 y n n n y * * | (3)
1510 1510
1511 1511 x = can't happen
1512 1512 * = don't-care
1513 1513 1 = incompatible options (checked in commands.py)
1514 1514 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1515 1515 3 = abort: uncommitted changes (checked in commands.py)
1516 1516
1517 1517 Return the same tuple as applyupdates().
1518 1518 """
1519 1519
1520 1520 # This function used to find the default destination if node was None, but
1521 1521 # that's now in destutil.py.
1522 1522 assert node is not None
1523 1523 if not branchmerge and not force:
1524 1524 # TODO: remove the default once all callers that pass branchmerge=False
1525 1525 # and force=False pass a value for updatecheck. We may want to allow
1526 1526 # updatecheck='abort' to better suppport some of these callers.
1527 1527 if updatecheck is None:
1528 1528 updatecheck = 'linear'
1529 1529 assert updatecheck in ('none', 'linear', 'noconflict')
1530 1530 # If we're doing a partial update, we need to skip updating
1531 1531 # the dirstate, so make a note of any partial-ness to the
1532 1532 # update here.
1533 1533 if matcher is None or matcher.always():
1534 1534 partial = False
1535 1535 else:
1536 1536 partial = True
1537 1537 with repo.wlock():
1538 1538 wc = repo[None]
1539 1539 pl = wc.parents()
1540 1540 p1 = pl[0]
1541 1541 pas = [None]
1542 1542 if ancestor is not None:
1543 1543 pas = [repo[ancestor]]
1544 1544
1545 1545 overwrite = force and not branchmerge
1546 1546
1547 1547 p2 = repo[node]
1548 1548 if pas[0] is None:
1549 1549 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1550 1550 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1551 1551 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1552 1552 else:
1553 1553 pas = [p1.ancestor(p2, warn=branchmerge)]
1554 1554
1555 1555 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1556 1556
1557 1557 ### check phase
1558 1558 if not overwrite:
1559 1559 if len(pl) > 1:
1560 1560 raise error.Abort(_("outstanding uncommitted merge"))
1561 1561 ms = mergestate.read(repo)
1562 1562 if list(ms.unresolved()):
1563 1563 raise error.Abort(_("outstanding merge conflicts"))
1564 1564 if branchmerge:
1565 1565 if pas == [p2]:
1566 1566 raise error.Abort(_("merging with a working directory ancestor"
1567 1567 " has no effect"))
1568 1568 elif pas == [p1]:
1569 1569 if not mergeancestor and wc.branch() == p2.branch():
1570 1570 raise error.Abort(_("nothing to merge"),
1571 1571 hint=_("use 'hg update' "
1572 1572 "or check 'hg heads'"))
1573 1573 if not force and (wc.files() or wc.deleted()):
1574 1574 raise error.Abort(_("uncommitted changes"),
1575 1575 hint=_("use 'hg status' to list changes"))
1576 1576 for s in sorted(wc.substate):
1577 1577 wc.sub(s).bailifchanged()
1578 1578
1579 1579 elif not overwrite:
1580 1580 if p1 == p2: # no-op update
1581 1581 # call the hooks and exit early
1582 1582 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1583 1583 repo.hook('update', parent1=xp2, parent2='', error=0)
1584 1584 return 0, 0, 0, 0
1585 1585
1586 1586 if (updatecheck == 'linear' and
1587 1587 pas not in ([p1], [p2])): # nonlinear
1588 1588 dirty = wc.dirty(missing=True)
1589 1589 if dirty:
1590 1590 # Branching is a bit strange to ensure we do the minimal
1591 1591 # amount of call to obsolete.foreground.
1592 1592 foreground = obsolete.foreground(repo, [p1.node()])
1593 1593 # note: the <node> variable contains a random identifier
1594 1594 if repo[node].node() in foreground:
1595 1595 pass # allow updating to successors
1596 1596 else:
1597 1597 msg = _("uncommitted changes")
1598 1598 hint = _("commit or update --clean to discard changes")
1599 1599 raise error.UpdateAbort(msg, hint=hint)
1600 1600 else:
1601 1601 # Allow jumping branches if clean and specific rev given
1602 1602 pass
1603 1603
1604 1604 if overwrite:
1605 1605 pas = [wc]
1606 1606 elif not branchmerge:
1607 1607 pas = [p1]
1608 1608
1609 1609 # deprecated config: merge.followcopies
1610 1610 followcopies = repo.ui.configbool('merge', 'followcopies', True)
1611 1611 if overwrite:
1612 1612 followcopies = False
1613 1613 elif not pas[0]:
1614 1614 followcopies = False
1615 1615 if not branchmerge and not wc.dirty(missing=True):
1616 1616 followcopies = False
1617 1617
1618 1618 ### calculate phase
1619 1619 actionbyfile, diverge, renamedelete = calculateupdates(
1620 1620 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1621 1621 followcopies, matcher=matcher, mergeforce=mergeforce)
1622 1622
1623 1623 if updatecheck == 'noconflict':
1624 1624 for f, (m, args, msg) in actionbyfile.iteritems():
1625 1625 if m not in ('g', 'k', 'e', 'r'):
1626 1626 msg = _("conflicting changes")
1627 1627 hint = _("commit or update --clean to discard changes")
1628 1628 raise error.Abort(msg, hint=hint)
1629 1629
1630 1630 # Prompt and create actions. Most of this is in the resolve phase
1631 1631 # already, but we can't handle .hgsubstate in filemerge or
1632 1632 # subrepo.submerge yet so we have to keep prompting for it.
1633 1633 if '.hgsubstate' in actionbyfile:
1634 1634 f = '.hgsubstate'
1635 1635 m, args, msg = actionbyfile[f]
1636 1636 prompts = filemerge.partextras(labels)
1637 1637 prompts['f'] = f
1638 1638 if m == 'cd':
1639 1639 if repo.ui.promptchoice(
1640 1640 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1641 1641 "use (c)hanged version or (d)elete?"
1642 1642 "$$ &Changed $$ &Delete") % prompts, 0):
1643 1643 actionbyfile[f] = ('r', None, "prompt delete")
1644 1644 elif f in p1:
1645 1645 actionbyfile[f] = ('am', None, "prompt keep")
1646 1646 else:
1647 1647 actionbyfile[f] = ('a', None, "prompt keep")
1648 1648 elif m == 'dc':
1649 1649 f1, f2, fa, move, anc = args
1650 1650 flags = p2[f2].flags()
1651 1651 if repo.ui.promptchoice(
1652 1652 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1653 1653 "use (c)hanged version or leave (d)eleted?"
1654 1654 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1655 1655 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1656 1656 else:
1657 1657 del actionbyfile[f]
1658 1658
1659 1659 # Convert to dictionary-of-lists format
1660 1660 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1661 1661 for f, (m, args, msg) in actionbyfile.iteritems():
1662 1662 if m not in actions:
1663 1663 actions[m] = []
1664 1664 actions[m].append((f, args, msg))
1665 1665
1666 1666 if not util.fscasesensitive(repo.path):
1667 1667 # check collision between files only in p2 for clean update
1668 1668 if (not branchmerge and
1669 1669 (force or not wc.dirty(missing=True, branch=False))):
1670 1670 _checkcollision(repo, p2.manifest(), None)
1671 1671 else:
1672 1672 _checkcollision(repo, wc.manifest(), actions)
1673 1673
1674 1674 # divergent renames
1675 1675 for f, fl in sorted(diverge.iteritems()):
1676 1676 repo.ui.warn(_("note: possible conflict - %s was renamed "
1677 1677 "multiple times to:\n") % f)
1678 1678 for nf in fl:
1679 1679 repo.ui.warn(" %s\n" % nf)
1680 1680
1681 1681 # rename and delete
1682 1682 for f, fl in sorted(renamedelete.iteritems()):
1683 1683 repo.ui.warn(_("note: possible conflict - %s was deleted "
1684 1684 "and renamed to:\n") % f)
1685 1685 for nf in fl:
1686 1686 repo.ui.warn(" %s\n" % nf)
1687 1687
1688 1688 ### apply phase
1689 1689 if not branchmerge: # just jump to the new rev
1690 1690 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1691 1691 if not partial:
1692 1692 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1693 1693 # note that we're in the middle of an update
1694 1694 repo.vfs.write('updatestate', p2.hex())
1695 1695
1696 1696 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1697 1697
1698 1698 if not partial:
1699 repo.dirstate.beginparentchange()
1700 repo.setparents(fp1, fp2)
1701 recordupdates(repo, actions, branchmerge)
1702 # update completed, clear state
1703 util.unlink(repo.vfs.join('updatestate'))
1699 with repo.dirstate.parentchange():
1700 repo.setparents(fp1, fp2)
1701 recordupdates(repo, actions, branchmerge)
1702 # update completed, clear state
1703 util.unlink(repo.vfs.join('updatestate'))
1704 1704
1705 if not branchmerge:
1706 repo.dirstate.setbranch(p2.branch())
1707 repo.dirstate.endparentchange()
1705 if not branchmerge:
1706 repo.dirstate.setbranch(p2.branch())
1708 1707
1709 1708 if not partial:
1710 1709 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1711 1710 return stats
1712 1711
1713 1712 def graft(repo, ctx, pctx, labels, keepparent=False):
1714 1713 """Do a graft-like merge.
1715 1714
1716 1715 This is a merge where the merge ancestor is chosen such that one
1717 1716 or more changesets are grafted onto the current changeset. In
1718 1717 addition to the merge, this fixes up the dirstate to include only
1719 1718 a single parent (if keepparent is False) and tries to duplicate any
1720 1719 renames/copies appropriately.
1721 1720
1722 1721 ctx - changeset to rebase
1723 1722 pctx - merge base, usually ctx.p1()
1724 1723 labels - merge labels eg ['local', 'graft']
1725 1724 keepparent - keep second parent if any
1726 1725
1727 1726 """
1728 1727 # If we're grafting a descendant onto an ancestor, be sure to pass
1729 1728 # mergeancestor=True to update. This does two things: 1) allows the merge if
1730 1729 # the destination is the same as the parent of the ctx (so we can use graft
1731 1730 # to copy commits), and 2) informs update that the incoming changes are
1732 1731 # newer than the destination so it doesn't prompt about "remote changed foo
1733 1732 # which local deleted".
1734 1733 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1735 1734
1736 1735 stats = update(repo, ctx.node(), True, True, pctx.node(),
1737 1736 mergeancestor=mergeancestor, labels=labels)
1738 1737
1739 1738 pother = nullid
1740 1739 parents = ctx.parents()
1741 1740 if keepparent and len(parents) == 2 and pctx in parents:
1742 1741 parents.remove(pctx)
1743 1742 pother = parents[0].node()
1744 1743
1745 repo.dirstate.beginparentchange()
1746 repo.setparents(repo['.'].node(), pother)
1747 repo.dirstate.write(repo.currenttransaction())
1748 # fix up dirstate for copies and renames
1749 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1750 repo.dirstate.endparentchange()
1744 with repo.dirstate.parentchange():
1745 repo.setparents(repo['.'].node(), pother)
1746 repo.dirstate.write(repo.currenttransaction())
1747 # fix up dirstate for copies and renames
1748 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1751 1749 return stats
General Comments 0
You need to be logged in to leave comments. Login now