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