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