##// END OF EJS Templates
merge: add a 'keepconflictparent' argument to graft...
Boris Feld -
r38513:d4be8ea8 default
parent child Browse files
Show More
@@ -1,2234 +1,2240 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 .thirdparty import (
26 26 attr,
27 27 )
28 28 from . import (
29 29 copies,
30 30 error,
31 31 filemerge,
32 32 match as matchmod,
33 33 obsutil,
34 34 pycompat,
35 35 scmutil,
36 36 subrepoutil,
37 37 util,
38 38 worker,
39 39 )
40 40
41 41 _pack = struct.pack
42 42 _unpack = struct.unpack
43 43
44 44 def _droponode(data):
45 45 # used for compatibility for v1
46 46 bits = data.split('\0')
47 47 bits = bits[:-2] + bits[-1:]
48 48 return '\0'.join(bits)
49 49
50 50 # Merge state record types. See ``mergestate`` docs for more.
51 51 RECORD_LOCAL = b'L'
52 52 RECORD_OTHER = b'O'
53 53 RECORD_MERGED = b'F'
54 54 RECORD_CHANGEDELETE_CONFLICT = b'C'
55 55 RECORD_MERGE_DRIVER_MERGE = b'D'
56 56 RECORD_PATH_CONFLICT = b'P'
57 57 RECORD_MERGE_DRIVER_STATE = b'm'
58 58 RECORD_FILE_VALUES = b'f'
59 59 RECORD_LABELS = b'l'
60 60 RECORD_OVERRIDE = b't'
61 61 RECORD_UNSUPPORTED_MANDATORY = b'X'
62 62 RECORD_UNSUPPORTED_ADVISORY = b'x'
63 63
64 64 MERGE_DRIVER_STATE_UNMARKED = b'u'
65 65 MERGE_DRIVER_STATE_MARKED = b'm'
66 66 MERGE_DRIVER_STATE_SUCCESS = b's'
67 67
68 68 MERGE_RECORD_UNRESOLVED = b'u'
69 69 MERGE_RECORD_RESOLVED = b'r'
70 70 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
71 71 MERGE_RECORD_RESOLVED_PATH = b'pr'
72 72 MERGE_RECORD_DRIVER_RESOLVED = b'd'
73 73
74 74 ACTION_FORGET = b'f'
75 75 ACTION_REMOVE = b'r'
76 76 ACTION_ADD = b'a'
77 77 ACTION_GET = b'g'
78 78 ACTION_PATH_CONFLICT = b'p'
79 79 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
80 80 ACTION_ADD_MODIFIED = b'am'
81 81 ACTION_CREATED = b'c'
82 82 ACTION_DELETED_CHANGED = b'dc'
83 83 ACTION_CHANGED_DELETED = b'cd'
84 84 ACTION_MERGE = b'm'
85 85 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
86 86 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
87 87 ACTION_KEEP = b'k'
88 88 ACTION_EXEC = b'e'
89 89 ACTION_CREATED_MERGE = b'cm'
90 90
91 91 class mergestate(object):
92 92 '''track 3-way merge state of individual files
93 93
94 94 The merge state is stored on disk when needed. Two files are used: one with
95 95 an old format (version 1), and one with a new format (version 2). Version 2
96 96 stores a superset of the data in version 1, including new kinds of records
97 97 in the future. For more about the new format, see the documentation for
98 98 `_readrecordsv2`.
99 99
100 100 Each record can contain arbitrary content, and has an associated type. This
101 101 `type` should be a letter. If `type` is uppercase, the record is mandatory:
102 102 versions of Mercurial that don't support it should abort. If `type` is
103 103 lowercase, the record can be safely ignored.
104 104
105 105 Currently known records:
106 106
107 107 L: the node of the "local" part of the merge (hexified version)
108 108 O: the node of the "other" part of the merge (hexified version)
109 109 F: a file to be merged entry
110 110 C: a change/delete or delete/change conflict
111 111 D: a file that the external merge driver will merge internally
112 112 (experimental)
113 113 P: a path conflict (file vs directory)
114 114 m: the external merge driver defined for this merge plus its run state
115 115 (experimental)
116 116 f: a (filename, dictionary) tuple of optional values for a given file
117 117 X: unsupported mandatory record type (used in tests)
118 118 x: unsupported advisory record type (used in tests)
119 119 l: the labels for the parts of the merge.
120 120
121 121 Merge driver run states (experimental):
122 122 u: driver-resolved files unmarked -- needs to be run next time we're about
123 123 to resolve or commit
124 124 m: driver-resolved files marked -- only needs to be run before commit
125 125 s: success/skipped -- does not need to be run any more
126 126
127 127 Merge record states (stored in self._state, indexed by filename):
128 128 u: unresolved conflict
129 129 r: resolved conflict
130 130 pu: unresolved path conflict (file conflicts with directory)
131 131 pr: resolved path conflict
132 132 d: driver-resolved conflict
133 133
134 134 The resolve command transitions between 'u' and 'r' for conflicts and
135 135 'pu' and 'pr' for path conflicts.
136 136 '''
137 137 statepathv1 = 'merge/state'
138 138 statepathv2 = 'merge/state2'
139 139
140 140 @staticmethod
141 141 def clean(repo, node=None, other=None, labels=None):
142 142 """Initialize a brand new merge state, removing any existing state on
143 143 disk."""
144 144 ms = mergestate(repo)
145 145 ms.reset(node, other, labels)
146 146 return ms
147 147
148 148 @staticmethod
149 149 def read(repo):
150 150 """Initialize the merge state, reading it from disk."""
151 151 ms = mergestate(repo)
152 152 ms._read()
153 153 return ms
154 154
155 155 def __init__(self, repo):
156 156 """Initialize the merge state.
157 157
158 158 Do not use this directly! Instead call read() or clean()."""
159 159 self._repo = repo
160 160 self._dirty = False
161 161 self._labels = None
162 162
163 163 def reset(self, node=None, other=None, labels=None):
164 164 self._state = {}
165 165 self._stateextras = {}
166 166 self._local = None
167 167 self._other = None
168 168 self._labels = labels
169 169 for var in ('localctx', 'otherctx'):
170 170 if var in vars(self):
171 171 delattr(self, var)
172 172 if node:
173 173 self._local = node
174 174 self._other = other
175 175 self._readmergedriver = None
176 176 if self.mergedriver:
177 177 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
178 178 else:
179 179 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
180 180 shutil.rmtree(self._repo.vfs.join('merge'), True)
181 181 self._results = {}
182 182 self._dirty = False
183 183
184 184 def _read(self):
185 185 """Analyse each record content to restore a serialized state from disk
186 186
187 187 This function process "record" entry produced by the de-serialization
188 188 of on disk file.
189 189 """
190 190 self._state = {}
191 191 self._stateextras = {}
192 192 self._local = None
193 193 self._other = None
194 194 for var in ('localctx', 'otherctx'):
195 195 if var in vars(self):
196 196 delattr(self, var)
197 197 self._readmergedriver = None
198 198 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
199 199 unsupported = set()
200 200 records = self._readrecords()
201 201 for rtype, record in records:
202 202 if rtype == RECORD_LOCAL:
203 203 self._local = bin(record)
204 204 elif rtype == RECORD_OTHER:
205 205 self._other = bin(record)
206 206 elif rtype == RECORD_MERGE_DRIVER_STATE:
207 207 bits = record.split('\0', 1)
208 208 mdstate = bits[1]
209 209 if len(mdstate) != 1 or mdstate not in (
210 210 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
211 211 MERGE_DRIVER_STATE_SUCCESS):
212 212 # the merge driver should be idempotent, so just rerun it
213 213 mdstate = MERGE_DRIVER_STATE_UNMARKED
214 214
215 215 self._readmergedriver = bits[0]
216 216 self._mdstate = mdstate
217 217 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
218 218 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
219 219 bits = record.split('\0')
220 220 self._state[bits[0]] = bits[1:]
221 221 elif rtype == RECORD_FILE_VALUES:
222 222 filename, rawextras = record.split('\0', 1)
223 223 extraparts = rawextras.split('\0')
224 224 extras = {}
225 225 i = 0
226 226 while i < len(extraparts):
227 227 extras[extraparts[i]] = extraparts[i + 1]
228 228 i += 2
229 229
230 230 self._stateextras[filename] = extras
231 231 elif rtype == RECORD_LABELS:
232 232 labels = record.split('\0', 2)
233 233 self._labels = [l for l in labels if len(l) > 0]
234 234 elif not rtype.islower():
235 235 unsupported.add(rtype)
236 236 self._results = {}
237 237 self._dirty = False
238 238
239 239 if unsupported:
240 240 raise error.UnsupportedMergeRecords(unsupported)
241 241
242 242 def _readrecords(self):
243 243 """Read merge state from disk and return a list of record (TYPE, data)
244 244
245 245 We read data from both v1 and v2 files and decide which one to use.
246 246
247 247 V1 has been used by version prior to 2.9.1 and contains less data than
248 248 v2. We read both versions and check if no data in v2 contradicts
249 249 v1. If there is not contradiction we can safely assume that both v1
250 250 and v2 were written at the same time and use the extract data in v2. If
251 251 there is contradiction we ignore v2 content as we assume an old version
252 252 of Mercurial has overwritten the mergestate file and left an old v2
253 253 file around.
254 254
255 255 returns list of record [(TYPE, data), ...]"""
256 256 v1records = self._readrecordsv1()
257 257 v2records = self._readrecordsv2()
258 258 if self._v1v2match(v1records, v2records):
259 259 return v2records
260 260 else:
261 261 # v1 file is newer than v2 file, use it
262 262 # we have to infer the "other" changeset of the merge
263 263 # we cannot do better than that with v1 of the format
264 264 mctx = self._repo[None].parents()[-1]
265 265 v1records.append((RECORD_OTHER, mctx.hex()))
266 266 # add place holder "other" file node information
267 267 # nobody is using it yet so we do no need to fetch the data
268 268 # if mctx was wrong `mctx[bits[-2]]` may fails.
269 269 for idx, r in enumerate(v1records):
270 270 if r[0] == RECORD_MERGED:
271 271 bits = r[1].split('\0')
272 272 bits.insert(-2, '')
273 273 v1records[idx] = (r[0], '\0'.join(bits))
274 274 return v1records
275 275
276 276 def _v1v2match(self, v1records, v2records):
277 277 oldv2 = set() # old format version of v2 record
278 278 for rec in v2records:
279 279 if rec[0] == RECORD_LOCAL:
280 280 oldv2.add(rec)
281 281 elif rec[0] == RECORD_MERGED:
282 282 # drop the onode data (not contained in v1)
283 283 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
284 284 for rec in v1records:
285 285 if rec not in oldv2:
286 286 return False
287 287 else:
288 288 return True
289 289
290 290 def _readrecordsv1(self):
291 291 """read on disk merge state for version 1 file
292 292
293 293 returns list of record [(TYPE, data), ...]
294 294
295 295 Note: the "F" data from this file are one entry short
296 296 (no "other file node" entry)
297 297 """
298 298 records = []
299 299 try:
300 300 f = self._repo.vfs(self.statepathv1)
301 301 for i, l in enumerate(f):
302 302 if i == 0:
303 303 records.append((RECORD_LOCAL, l[:-1]))
304 304 else:
305 305 records.append((RECORD_MERGED, l[:-1]))
306 306 f.close()
307 307 except IOError as err:
308 308 if err.errno != errno.ENOENT:
309 309 raise
310 310 return records
311 311
312 312 def _readrecordsv2(self):
313 313 """read on disk merge state for version 2 file
314 314
315 315 This format is a list of arbitrary records of the form:
316 316
317 317 [type][length][content]
318 318
319 319 `type` is a single character, `length` is a 4 byte integer, and
320 320 `content` is an arbitrary byte sequence of length `length`.
321 321
322 322 Mercurial versions prior to 3.7 have a bug where if there are
323 323 unsupported mandatory merge records, attempting to clear out the merge
324 324 state with hg update --clean or similar aborts. The 't' record type
325 325 works around that by writing out what those versions treat as an
326 326 advisory record, but later versions interpret as special: the first
327 327 character is the 'real' record type and everything onwards is the data.
328 328
329 329 Returns list of records [(TYPE, data), ...]."""
330 330 records = []
331 331 try:
332 332 f = self._repo.vfs(self.statepathv2)
333 333 data = f.read()
334 334 off = 0
335 335 end = len(data)
336 336 while off < end:
337 337 rtype = data[off:off + 1]
338 338 off += 1
339 339 length = _unpack('>I', data[off:(off + 4)])[0]
340 340 off += 4
341 341 record = data[off:(off + length)]
342 342 off += length
343 343 if rtype == RECORD_OVERRIDE:
344 344 rtype, record = record[0:1], record[1:]
345 345 records.append((rtype, record))
346 346 f.close()
347 347 except IOError as err:
348 348 if err.errno != errno.ENOENT:
349 349 raise
350 350 return records
351 351
352 352 @util.propertycache
353 353 def mergedriver(self):
354 354 # protect against the following:
355 355 # - A configures a malicious merge driver in their hgrc, then
356 356 # pauses the merge
357 357 # - A edits their hgrc to remove references to the merge driver
358 358 # - A gives a copy of their entire repo, including .hg, to B
359 359 # - B inspects .hgrc and finds it to be clean
360 360 # - B then continues the merge and the malicious merge driver
361 361 # gets invoked
362 362 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
363 363 if (self._readmergedriver is not None
364 364 and self._readmergedriver != configmergedriver):
365 365 raise error.ConfigError(
366 366 _("merge driver changed since merge started"),
367 367 hint=_("revert merge driver change or abort merge"))
368 368
369 369 return configmergedriver
370 370
371 371 @util.propertycache
372 372 def localctx(self):
373 373 if self._local is None:
374 374 msg = "localctx accessed but self._local isn't set"
375 375 raise error.ProgrammingError(msg)
376 376 return self._repo[self._local]
377 377
378 378 @util.propertycache
379 379 def otherctx(self):
380 380 if self._other is None:
381 381 msg = "otherctx accessed but self._other isn't set"
382 382 raise error.ProgrammingError(msg)
383 383 return self._repo[self._other]
384 384
385 385 def active(self):
386 386 """Whether mergestate is active.
387 387
388 388 Returns True if there appears to be mergestate. This is a rough proxy
389 389 for "is a merge in progress."
390 390 """
391 391 # Check local variables before looking at filesystem for performance
392 392 # reasons.
393 393 return bool(self._local) or bool(self._state) or \
394 394 self._repo.vfs.exists(self.statepathv1) or \
395 395 self._repo.vfs.exists(self.statepathv2)
396 396
397 397 def commit(self):
398 398 """Write current state on disk (if necessary)"""
399 399 if self._dirty:
400 400 records = self._makerecords()
401 401 self._writerecords(records)
402 402 self._dirty = False
403 403
404 404 def _makerecords(self):
405 405 records = []
406 406 records.append((RECORD_LOCAL, hex(self._local)))
407 407 records.append((RECORD_OTHER, hex(self._other)))
408 408 if self.mergedriver:
409 409 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
410 410 self.mergedriver, self._mdstate])))
411 411 # Write out state items. In all cases, the value of the state map entry
412 412 # is written as the contents of the record. The record type depends on
413 413 # the type of state that is stored, and capital-letter records are used
414 414 # to prevent older versions of Mercurial that do not support the feature
415 415 # from loading them.
416 416 for filename, v in self._state.iteritems():
417 417 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
418 418 # Driver-resolved merge. These are stored in 'D' records.
419 419 records.append((RECORD_MERGE_DRIVER_MERGE,
420 420 '\0'.join([filename] + v)))
421 421 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
422 422 MERGE_RECORD_RESOLVED_PATH):
423 423 # Path conflicts. These are stored in 'P' records. The current
424 424 # resolution state ('pu' or 'pr') is stored within the record.
425 425 records.append((RECORD_PATH_CONFLICT,
426 426 '\0'.join([filename] + v)))
427 427 elif v[1] == nullhex or v[6] == nullhex:
428 428 # Change/Delete or Delete/Change conflicts. These are stored in
429 429 # 'C' records. v[1] is the local file, and is nullhex when the
430 430 # file is deleted locally ('dc'). v[6] is the remote file, and
431 431 # is nullhex when the file is deleted remotely ('cd').
432 432 records.append((RECORD_CHANGEDELETE_CONFLICT,
433 433 '\0'.join([filename] + v)))
434 434 else:
435 435 # Normal files. These are stored in 'F' records.
436 436 records.append((RECORD_MERGED,
437 437 '\0'.join([filename] + v)))
438 438 for filename, extras in sorted(self._stateextras.iteritems()):
439 439 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
440 440 extras.iteritems())
441 441 records.append((RECORD_FILE_VALUES,
442 442 '%s\0%s' % (filename, rawextras)))
443 443 if self._labels is not None:
444 444 labels = '\0'.join(self._labels)
445 445 records.append((RECORD_LABELS, labels))
446 446 return records
447 447
448 448 def _writerecords(self, records):
449 449 """Write current state on disk (both v1 and v2)"""
450 450 self._writerecordsv1(records)
451 451 self._writerecordsv2(records)
452 452
453 453 def _writerecordsv1(self, records):
454 454 """Write current state on disk in a version 1 file"""
455 455 f = self._repo.vfs(self.statepathv1, 'wb')
456 456 irecords = iter(records)
457 457 lrecords = next(irecords)
458 458 assert lrecords[0] == RECORD_LOCAL
459 459 f.write(hex(self._local) + '\n')
460 460 for rtype, data in irecords:
461 461 if rtype == RECORD_MERGED:
462 462 f.write('%s\n' % _droponode(data))
463 463 f.close()
464 464
465 465 def _writerecordsv2(self, records):
466 466 """Write current state on disk in a version 2 file
467 467
468 468 See the docstring for _readrecordsv2 for why we use 't'."""
469 469 # these are the records that all version 2 clients can read
470 470 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
471 471 f = self._repo.vfs(self.statepathv2, 'wb')
472 472 for key, data in records:
473 473 assert len(key) == 1
474 474 if key not in allowlist:
475 475 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
476 476 format = '>sI%is' % len(data)
477 477 f.write(_pack(format, key, len(data), data))
478 478 f.close()
479 479
480 480 def add(self, fcl, fco, fca, fd):
481 481 """add a new (potentially?) conflicting file the merge state
482 482 fcl: file context for local,
483 483 fco: file context for remote,
484 484 fca: file context for ancestors,
485 485 fd: file path of the resulting merge.
486 486
487 487 note: also write the local version to the `.hg/merge` directory.
488 488 """
489 489 if fcl.isabsent():
490 490 hash = nullhex
491 491 else:
492 492 hash = hex(hashlib.sha1(fcl.path()).digest())
493 493 self._repo.vfs.write('merge/' + hash, fcl.data())
494 494 self._state[fd] = [MERGE_RECORD_UNRESOLVED, hash, fcl.path(),
495 495 fca.path(), hex(fca.filenode()),
496 496 fco.path(), hex(fco.filenode()),
497 497 fcl.flags()]
498 498 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
499 499 self._dirty = True
500 500
501 501 def addpath(self, path, frename, forigin):
502 502 """add a new conflicting path to the merge state
503 503 path: the path that conflicts
504 504 frename: the filename the conflicting file was renamed to
505 505 forigin: origin of the file ('l' or 'r' for local/remote)
506 506 """
507 507 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
508 508 self._dirty = True
509 509
510 510 def __contains__(self, dfile):
511 511 return dfile in self._state
512 512
513 513 def __getitem__(self, dfile):
514 514 return self._state[dfile][0]
515 515
516 516 def __iter__(self):
517 517 return iter(sorted(self._state))
518 518
519 519 def files(self):
520 520 return self._state.keys()
521 521
522 522 def mark(self, dfile, state):
523 523 self._state[dfile][0] = state
524 524 self._dirty = True
525 525
526 526 def mdstate(self):
527 527 return self._mdstate
528 528
529 529 def unresolved(self):
530 530 """Obtain the paths of unresolved files."""
531 531
532 532 for f, entry in self._state.iteritems():
533 533 if entry[0] in (MERGE_RECORD_UNRESOLVED,
534 534 MERGE_RECORD_UNRESOLVED_PATH):
535 535 yield f
536 536
537 537 def driverresolved(self):
538 538 """Obtain the paths of driver-resolved files."""
539 539
540 540 for f, entry in self._state.items():
541 541 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
542 542 yield f
543 543
544 544 def extras(self, filename):
545 545 return self._stateextras.setdefault(filename, {})
546 546
547 547 def _resolve(self, preresolve, dfile, wctx):
548 548 """rerun merge process for file path `dfile`"""
549 549 if self[dfile] in (MERGE_RECORD_RESOLVED,
550 550 MERGE_RECORD_DRIVER_RESOLVED):
551 551 return True, 0
552 552 stateentry = self._state[dfile]
553 553 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
554 554 octx = self._repo[self._other]
555 555 extras = self.extras(dfile)
556 556 anccommitnode = extras.get('ancestorlinknode')
557 557 if anccommitnode:
558 558 actx = self._repo[anccommitnode]
559 559 else:
560 560 actx = None
561 561 fcd = self._filectxorabsent(hash, wctx, dfile)
562 562 fco = self._filectxorabsent(onode, octx, ofile)
563 563 # TODO: move this to filectxorabsent
564 564 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
565 565 # "premerge" x flags
566 566 flo = fco.flags()
567 567 fla = fca.flags()
568 568 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
569 569 if fca.node() == nullid and flags != flo:
570 570 if preresolve:
571 571 self._repo.ui.warn(
572 572 _('warning: cannot merge flags for %s '
573 573 'without common ancestor - keeping local flags\n')
574 574 % afile)
575 575 elif flags == fla:
576 576 flags = flo
577 577 if preresolve:
578 578 # restore local
579 579 if hash != nullhex:
580 580 f = self._repo.vfs('merge/' + hash)
581 581 wctx[dfile].write(f.read(), flags)
582 582 f.close()
583 583 else:
584 584 wctx[dfile].remove(ignoremissing=True)
585 585 complete, r, deleted = filemerge.premerge(self._repo, wctx,
586 586 self._local, lfile, fcd,
587 587 fco, fca,
588 588 labels=self._labels)
589 589 else:
590 590 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
591 591 self._local, lfile, fcd,
592 592 fco, fca,
593 593 labels=self._labels)
594 594 if r is None:
595 595 # no real conflict
596 596 del self._state[dfile]
597 597 self._stateextras.pop(dfile, None)
598 598 self._dirty = True
599 599 elif not r:
600 600 self.mark(dfile, MERGE_RECORD_RESOLVED)
601 601
602 602 if complete:
603 603 action = None
604 604 if deleted:
605 605 if fcd.isabsent():
606 606 # dc: local picked. Need to drop if present, which may
607 607 # happen on re-resolves.
608 608 action = ACTION_FORGET
609 609 else:
610 610 # cd: remote picked (or otherwise deleted)
611 611 action = ACTION_REMOVE
612 612 else:
613 613 if fcd.isabsent(): # dc: remote picked
614 614 action = ACTION_GET
615 615 elif fco.isabsent(): # cd: local picked
616 616 if dfile in self.localctx:
617 617 action = ACTION_ADD_MODIFIED
618 618 else:
619 619 action = ACTION_ADD
620 620 # else: regular merges (no action necessary)
621 621 self._results[dfile] = r, action
622 622
623 623 return complete, r
624 624
625 625 def _filectxorabsent(self, hexnode, ctx, f):
626 626 if hexnode == nullhex:
627 627 return filemerge.absentfilectx(ctx, f)
628 628 else:
629 629 return ctx[f]
630 630
631 631 def preresolve(self, dfile, wctx):
632 632 """run premerge process for dfile
633 633
634 634 Returns whether the merge is complete, and the exit code."""
635 635 return self._resolve(True, dfile, wctx)
636 636
637 637 def resolve(self, dfile, wctx):
638 638 """run merge process (assuming premerge was run) for dfile
639 639
640 640 Returns the exit code of the merge."""
641 641 return self._resolve(False, dfile, wctx)[1]
642 642
643 643 def counts(self):
644 644 """return counts for updated, merged and removed files in this
645 645 session"""
646 646 updated, merged, removed = 0, 0, 0
647 647 for r, action in self._results.itervalues():
648 648 if r is None:
649 649 updated += 1
650 650 elif r == 0:
651 651 if action == ACTION_REMOVE:
652 652 removed += 1
653 653 else:
654 654 merged += 1
655 655 return updated, merged, removed
656 656
657 657 def unresolvedcount(self):
658 658 """get unresolved count for this merge (persistent)"""
659 659 return len(list(self.unresolved()))
660 660
661 661 def actions(self):
662 662 """return lists of actions to perform on the dirstate"""
663 663 actions = {
664 664 ACTION_REMOVE: [],
665 665 ACTION_FORGET: [],
666 666 ACTION_ADD: [],
667 667 ACTION_ADD_MODIFIED: [],
668 668 ACTION_GET: [],
669 669 }
670 670 for f, (r, action) in self._results.iteritems():
671 671 if action is not None:
672 672 actions[action].append((f, None, "merge result"))
673 673 return actions
674 674
675 675 def recordactions(self):
676 676 """record remove/add/get actions in the dirstate"""
677 677 branchmerge = self._repo.dirstate.p2() != nullid
678 678 recordupdates(self._repo, self.actions(), branchmerge)
679 679
680 680 def queueremove(self, f):
681 681 """queues a file to be removed from the dirstate
682 682
683 683 Meant for use by custom merge drivers."""
684 684 self._results[f] = 0, ACTION_REMOVE
685 685
686 686 def queueadd(self, f):
687 687 """queues a file to be added to the dirstate
688 688
689 689 Meant for use by custom merge drivers."""
690 690 self._results[f] = 0, ACTION_ADD
691 691
692 692 def queueget(self, f):
693 693 """queues a file to be marked modified in the dirstate
694 694
695 695 Meant for use by custom merge drivers."""
696 696 self._results[f] = 0, ACTION_GET
697 697
698 698 def _getcheckunknownconfig(repo, section, name):
699 699 config = repo.ui.config(section, name)
700 700 valid = ['abort', 'ignore', 'warn']
701 701 if config not in valid:
702 702 validstr = ', '.join(["'" + v + "'" for v in valid])
703 703 raise error.ConfigError(_("%s.%s not valid "
704 704 "('%s' is none of %s)")
705 705 % (section, name, config, validstr))
706 706 return config
707 707
708 708 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
709 709 if wctx.isinmemory():
710 710 # Nothing to do in IMM because nothing in the "working copy" can be an
711 711 # unknown file.
712 712 #
713 713 # Note that we should bail out here, not in ``_checkunknownfiles()``,
714 714 # because that function does other useful work.
715 715 return False
716 716
717 717 if f2 is None:
718 718 f2 = f
719 719 return (repo.wvfs.audit.check(f)
720 720 and repo.wvfs.isfileorlink(f)
721 721 and repo.dirstate.normalize(f) not in repo.dirstate
722 722 and mctx[f2].cmp(wctx[f]))
723 723
724 724 class _unknowndirschecker(object):
725 725 """
726 726 Look for any unknown files or directories that may have a path conflict
727 727 with a file. If any path prefix of the file exists as a file or link,
728 728 then it conflicts. If the file itself is a directory that contains any
729 729 file that is not tracked, then it conflicts.
730 730
731 731 Returns the shortest path at which a conflict occurs, or None if there is
732 732 no conflict.
733 733 """
734 734 def __init__(self):
735 735 # A set of paths known to be good. This prevents repeated checking of
736 736 # dirs. It will be updated with any new dirs that are checked and found
737 737 # to be safe.
738 738 self._unknowndircache = set()
739 739
740 740 # A set of paths that are known to be absent. This prevents repeated
741 741 # checking of subdirectories that are known not to exist. It will be
742 742 # updated with any new dirs that are checked and found to be absent.
743 743 self._missingdircache = set()
744 744
745 745 def __call__(self, repo, wctx, f):
746 746 if wctx.isinmemory():
747 747 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
748 748 return False
749 749
750 750 # Check for path prefixes that exist as unknown files.
751 751 for p in reversed(list(util.finddirs(f))):
752 752 if p in self._missingdircache:
753 753 return
754 754 if p in self._unknowndircache:
755 755 continue
756 756 if repo.wvfs.audit.check(p):
757 757 if (repo.wvfs.isfileorlink(p)
758 758 and repo.dirstate.normalize(p) not in repo.dirstate):
759 759 return p
760 760 if not repo.wvfs.lexists(p):
761 761 self._missingdircache.add(p)
762 762 return
763 763 self._unknowndircache.add(p)
764 764
765 765 # Check if the file conflicts with a directory containing unknown files.
766 766 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
767 767 # Does the directory contain any files that are not in the dirstate?
768 768 for p, dirs, files in repo.wvfs.walk(f):
769 769 for fn in files:
770 770 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
771 771 relf = repo.dirstate.normalize(relf, isknown=True)
772 772 if relf not in repo.dirstate:
773 773 return f
774 774 return None
775 775
776 776 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
777 777 """
778 778 Considers any actions that care about the presence of conflicting unknown
779 779 files. For some actions, the result is to abort; for others, it is to
780 780 choose a different action.
781 781 """
782 782 fileconflicts = set()
783 783 pathconflicts = set()
784 784 warnconflicts = set()
785 785 abortconflicts = set()
786 786 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
787 787 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
788 788 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
789 789 if not force:
790 790 def collectconflicts(conflicts, config):
791 791 if config == 'abort':
792 792 abortconflicts.update(conflicts)
793 793 elif config == 'warn':
794 794 warnconflicts.update(conflicts)
795 795
796 796 checkunknowndirs = _unknowndirschecker()
797 797 for f, (m, args, msg) in actions.iteritems():
798 798 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
799 799 if _checkunknownfile(repo, wctx, mctx, f):
800 800 fileconflicts.add(f)
801 801 elif pathconfig and f not in wctx:
802 802 path = checkunknowndirs(repo, wctx, f)
803 803 if path is not None:
804 804 pathconflicts.add(path)
805 805 elif m == ACTION_LOCAL_DIR_RENAME_GET:
806 806 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
807 807 fileconflicts.add(f)
808 808
809 809 allconflicts = fileconflicts | pathconflicts
810 810 ignoredconflicts = set([c for c in allconflicts
811 811 if repo.dirstate._ignore(c)])
812 812 unknownconflicts = allconflicts - ignoredconflicts
813 813 collectconflicts(ignoredconflicts, ignoredconfig)
814 814 collectconflicts(unknownconflicts, unknownconfig)
815 815 else:
816 816 for f, (m, args, msg) in actions.iteritems():
817 817 if m == ACTION_CREATED_MERGE:
818 818 fl2, anc = args
819 819 different = _checkunknownfile(repo, wctx, mctx, f)
820 820 if repo.dirstate._ignore(f):
821 821 config = ignoredconfig
822 822 else:
823 823 config = unknownconfig
824 824
825 825 # The behavior when force is True is described by this table:
826 826 # config different mergeforce | action backup
827 827 # * n * | get n
828 828 # * y y | merge -
829 829 # abort y n | merge - (1)
830 830 # warn y n | warn + get y
831 831 # ignore y n | get y
832 832 #
833 833 # (1) this is probably the wrong behavior here -- we should
834 834 # probably abort, but some actions like rebases currently
835 835 # don't like an abort happening in the middle of
836 836 # merge.update.
837 837 if not different:
838 838 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
839 839 elif mergeforce or config == 'abort':
840 840 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
841 841 'remote differs from untracked local')
842 842 elif config == 'abort':
843 843 abortconflicts.add(f)
844 844 else:
845 845 if config == 'warn':
846 846 warnconflicts.add(f)
847 847 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
848 848
849 849 for f in sorted(abortconflicts):
850 850 warn = repo.ui.warn
851 851 if f in pathconflicts:
852 852 if repo.wvfs.isfileorlink(f):
853 853 warn(_("%s: untracked file conflicts with directory\n") % f)
854 854 else:
855 855 warn(_("%s: untracked directory conflicts with file\n") % f)
856 856 else:
857 857 warn(_("%s: untracked file differs\n") % f)
858 858 if abortconflicts:
859 859 raise error.Abort(_("untracked files in working directory "
860 860 "differ from files in requested revision"))
861 861
862 862 for f in sorted(warnconflicts):
863 863 if repo.wvfs.isfileorlink(f):
864 864 repo.ui.warn(_("%s: replacing untracked file\n") % f)
865 865 else:
866 866 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
867 867
868 868 for f, (m, args, msg) in actions.iteritems():
869 869 if m == ACTION_CREATED:
870 870 backup = (f in fileconflicts or f in pathconflicts or
871 871 any(p in pathconflicts for p in util.finddirs(f)))
872 872 flags, = args
873 873 actions[f] = (ACTION_GET, (flags, backup), msg)
874 874
875 875 def _forgetremoved(wctx, mctx, branchmerge):
876 876 """
877 877 Forget removed files
878 878
879 879 If we're jumping between revisions (as opposed to merging), and if
880 880 neither the working directory nor the target rev has the file,
881 881 then we need to remove it from the dirstate, to prevent the
882 882 dirstate from listing the file when it is no longer in the
883 883 manifest.
884 884
885 885 If we're merging, and the other revision has removed a file
886 886 that is not present in the working directory, we need to mark it
887 887 as removed.
888 888 """
889 889
890 890 actions = {}
891 891 m = ACTION_FORGET
892 892 if branchmerge:
893 893 m = ACTION_REMOVE
894 894 for f in wctx.deleted():
895 895 if f not in mctx:
896 896 actions[f] = m, None, "forget deleted"
897 897
898 898 if not branchmerge:
899 899 for f in wctx.removed():
900 900 if f not in mctx:
901 901 actions[f] = ACTION_FORGET, None, "forget removed"
902 902
903 903 return actions
904 904
905 905 def _checkcollision(repo, wmf, actions):
906 906 """
907 907 Check for case-folding collisions.
908 908 """
909 909
910 910 # If the repo is narrowed, filter out files outside the narrowspec.
911 911 narrowmatch = repo.narrowmatch()
912 912 if not narrowmatch.always():
913 913 wmf = wmf.matches(narrowmatch)
914 914 if actions:
915 915 narrowactions = {}
916 916 for m, actionsfortype in actions.iteritems():
917 917 narrowactions[m] = []
918 918 for (f, args, msg) in actionsfortype:
919 919 if narrowmatch(f):
920 920 narrowactions[m].append((f, args, msg))
921 921 actions = narrowactions
922 922
923 923 # build provisional merged manifest up
924 924 pmmf = set(wmf)
925 925
926 926 if actions:
927 927 # KEEP and EXEC are no-op
928 928 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
929 929 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
930 930 for f, args, msg in actions[m]:
931 931 pmmf.add(f)
932 932 for f, args, msg in actions[ACTION_REMOVE]:
933 933 pmmf.discard(f)
934 934 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
935 935 f2, flags = args
936 936 pmmf.discard(f2)
937 937 pmmf.add(f)
938 938 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
939 939 pmmf.add(f)
940 940 for f, args, msg in actions[ACTION_MERGE]:
941 941 f1, f2, fa, move, anc = args
942 942 if move:
943 943 pmmf.discard(f1)
944 944 pmmf.add(f)
945 945
946 946 # check case-folding collision in provisional merged manifest
947 947 foldmap = {}
948 948 for f in pmmf:
949 949 fold = util.normcase(f)
950 950 if fold in foldmap:
951 951 raise error.Abort(_("case-folding collision between %s and %s")
952 952 % (f, foldmap[fold]))
953 953 foldmap[fold] = f
954 954
955 955 # check case-folding of directories
956 956 foldprefix = unfoldprefix = lastfull = ''
957 957 for fold, f in sorted(foldmap.items()):
958 958 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
959 959 # the folded prefix matches but actual casing is different
960 960 raise error.Abort(_("case-folding collision between "
961 961 "%s and directory of %s") % (lastfull, f))
962 962 foldprefix = fold + '/'
963 963 unfoldprefix = f + '/'
964 964 lastfull = f
965 965
966 966 def driverpreprocess(repo, ms, wctx, labels=None):
967 967 """run the preprocess step of the merge driver, if any
968 968
969 969 This is currently not implemented -- it's an extension point."""
970 970 return True
971 971
972 972 def driverconclude(repo, ms, wctx, labels=None):
973 973 """run the conclude step of the merge driver, if any
974 974
975 975 This is currently not implemented -- it's an extension point."""
976 976 return True
977 977
978 978 def _filesindirs(repo, manifest, dirs):
979 979 """
980 980 Generator that yields pairs of all the files in the manifest that are found
981 981 inside the directories listed in dirs, and which directory they are found
982 982 in.
983 983 """
984 984 for f in manifest:
985 985 for p in util.finddirs(f):
986 986 if p in dirs:
987 987 yield f, p
988 988 break
989 989
990 990 def checkpathconflicts(repo, wctx, mctx, actions):
991 991 """
992 992 Check if any actions introduce path conflicts in the repository, updating
993 993 actions to record or handle the path conflict accordingly.
994 994 """
995 995 mf = wctx.manifest()
996 996
997 997 # The set of local files that conflict with a remote directory.
998 998 localconflicts = set()
999 999
1000 1000 # The set of directories that conflict with a remote file, and so may cause
1001 1001 # conflicts if they still contain any files after the merge.
1002 1002 remoteconflicts = set()
1003 1003
1004 1004 # The set of directories that appear as both a file and a directory in the
1005 1005 # remote manifest. These indicate an invalid remote manifest, which
1006 1006 # can't be updated to cleanly.
1007 1007 invalidconflicts = set()
1008 1008
1009 1009 # The set of directories that contain files that are being created.
1010 1010 createdfiledirs = set()
1011 1011
1012 1012 # The set of files deleted by all the actions.
1013 1013 deletedfiles = set()
1014 1014
1015 1015 for f, (m, args, msg) in actions.items():
1016 1016 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1017 1017 ACTION_CREATED_MERGE):
1018 1018 # This action may create a new local file.
1019 1019 createdfiledirs.update(util.finddirs(f))
1020 1020 if mf.hasdir(f):
1021 1021 # The file aliases a local directory. This might be ok if all
1022 1022 # the files in the local directory are being deleted. This
1023 1023 # will be checked once we know what all the deleted files are.
1024 1024 remoteconflicts.add(f)
1025 1025 # Track the names of all deleted files.
1026 1026 if m == ACTION_REMOVE:
1027 1027 deletedfiles.add(f)
1028 1028 if m == ACTION_MERGE:
1029 1029 f1, f2, fa, move, anc = args
1030 1030 if move:
1031 1031 deletedfiles.add(f1)
1032 1032 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1033 1033 f2, flags = args
1034 1034 deletedfiles.add(f2)
1035 1035
1036 1036 # Check all directories that contain created files for path conflicts.
1037 1037 for p in createdfiledirs:
1038 1038 if p in mf:
1039 1039 if p in mctx:
1040 1040 # A file is in a directory which aliases both a local
1041 1041 # and a remote file. This is an internal inconsistency
1042 1042 # within the remote manifest.
1043 1043 invalidconflicts.add(p)
1044 1044 else:
1045 1045 # A file is in a directory which aliases a local file.
1046 1046 # We will need to rename the local file.
1047 1047 localconflicts.add(p)
1048 1048 if p in actions and actions[p][0] in (ACTION_CREATED,
1049 1049 ACTION_DELETED_CHANGED,
1050 1050 ACTION_MERGE,
1051 1051 ACTION_CREATED_MERGE):
1052 1052 # The file is in a directory which aliases a remote file.
1053 1053 # This is an internal inconsistency within the remote
1054 1054 # manifest.
1055 1055 invalidconflicts.add(p)
1056 1056
1057 1057 # Rename all local conflicting files that have not been deleted.
1058 1058 for p in localconflicts:
1059 1059 if p not in deletedfiles:
1060 1060 ctxname = bytes(wctx).rstrip('+')
1061 1061 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1062 1062 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1063 1063 'local path conflict')
1064 1064 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1065 1065 'path conflict')
1066 1066
1067 1067 if remoteconflicts:
1068 1068 # Check if all files in the conflicting directories have been removed.
1069 1069 ctxname = bytes(mctx).rstrip('+')
1070 1070 for f, p in _filesindirs(repo, mf, remoteconflicts):
1071 1071 if f not in deletedfiles:
1072 1072 m, args, msg = actions[p]
1073 1073 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1074 1074 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1075 1075 # Action was merge, just update target.
1076 1076 actions[pnew] = (m, args, msg)
1077 1077 else:
1078 1078 # Action was create, change to renamed get action.
1079 1079 fl = args[0]
1080 1080 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1081 1081 'remote path conflict')
1082 1082 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1083 1083 'path conflict')
1084 1084 remoteconflicts.remove(p)
1085 1085 break
1086 1086
1087 1087 if invalidconflicts:
1088 1088 for p in invalidconflicts:
1089 1089 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1090 1090 raise error.Abort(_("destination manifest contains path conflicts"))
1091 1091
1092 1092 def _filternarrowactions(narrowmatch, branchmerge, actions):
1093 1093 """
1094 1094 Filters out actions that can ignored because the repo is narrowed.
1095 1095
1096 1096 Raise an exception if the merge cannot be completed because the repo is
1097 1097 narrowed.
1098 1098 """
1099 1099 nooptypes = set(['k']) # TODO: handle with nonconflicttypes
1100 1100 nonconflicttypes = set('a am c cm f g r e'.split())
1101 1101 # We mutate the items in the dict during iteration, so iterate
1102 1102 # over a copy.
1103 1103 for f, action in list(actions.items()):
1104 1104 if narrowmatch(f):
1105 1105 pass
1106 1106 elif not branchmerge:
1107 1107 del actions[f] # just updating, ignore changes outside clone
1108 1108 elif action[0] in nooptypes:
1109 1109 del actions[f] # merge does not affect file
1110 1110 elif action[0] in nonconflicttypes:
1111 1111 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1112 1112 'which is not yet supported') % f,
1113 1113 hint=_('merging in the other direction '
1114 1114 'may work'))
1115 1115 else:
1116 1116 raise error.Abort(_('conflict in file \'%s\' is outside '
1117 1117 'narrow clone') % f)
1118 1118
1119 1119 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1120 1120 acceptremote, followcopies, forcefulldiff=False):
1121 1121 """
1122 1122 Merge wctx and p2 with ancestor pa and generate merge action list
1123 1123
1124 1124 branchmerge and force are as passed in to update
1125 1125 matcher = matcher to filter file lists
1126 1126 acceptremote = accept the incoming changes without prompting
1127 1127 """
1128 1128 if matcher is not None and matcher.always():
1129 1129 matcher = None
1130 1130
1131 1131 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1132 1132
1133 1133 # manifests fetched in order are going to be faster, so prime the caches
1134 1134 [x.manifest() for x in
1135 1135 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1136 1136
1137 1137 if followcopies:
1138 1138 ret = copies.mergecopies(repo, wctx, p2, pa)
1139 1139 copy, movewithdir, diverge, renamedelete, dirmove = ret
1140 1140
1141 1141 boolbm = pycompat.bytestr(bool(branchmerge))
1142 1142 boolf = pycompat.bytestr(bool(force))
1143 1143 boolm = pycompat.bytestr(bool(matcher))
1144 1144 repo.ui.note(_("resolving manifests\n"))
1145 1145 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1146 1146 % (boolbm, boolf, boolm))
1147 1147 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1148 1148
1149 1149 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1150 1150 copied = set(copy.values())
1151 1151 copied.update(movewithdir.values())
1152 1152
1153 1153 if '.hgsubstate' in m1 and wctx.rev() is None:
1154 1154 # Check whether sub state is modified, and overwrite the manifest
1155 1155 # to flag the change. If wctx is a committed revision, we shouldn't
1156 1156 # care for the dirty state of the working directory.
1157 1157 if any(wctx.sub(s).dirty() for s in wctx.substate):
1158 1158 m1['.hgsubstate'] = modifiednodeid
1159 1159
1160 1160 # Don't use m2-vs-ma optimization if:
1161 1161 # - ma is the same as m1 or m2, which we're just going to diff again later
1162 1162 # - The caller specifically asks for a full diff, which is useful during bid
1163 1163 # merge.
1164 1164 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1165 1165 # Identify which files are relevant to the merge, so we can limit the
1166 1166 # total m1-vs-m2 diff to just those files. This has significant
1167 1167 # performance benefits in large repositories.
1168 1168 relevantfiles = set(ma.diff(m2).keys())
1169 1169
1170 1170 # For copied and moved files, we need to add the source file too.
1171 1171 for copykey, copyvalue in copy.iteritems():
1172 1172 if copyvalue in relevantfiles:
1173 1173 relevantfiles.add(copykey)
1174 1174 for movedirkey in movewithdir:
1175 1175 relevantfiles.add(movedirkey)
1176 1176 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1177 1177 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1178 1178
1179 1179 diff = m1.diff(m2, match=matcher)
1180 1180
1181 1181 if matcher is None:
1182 1182 matcher = matchmod.always('', '')
1183 1183
1184 1184 actions = {}
1185 1185 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1186 1186 if n1 and n2: # file exists on both local and remote side
1187 1187 if f not in ma:
1188 1188 fa = copy.get(f, None)
1189 1189 if fa is not None:
1190 1190 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1191 1191 'both renamed from %s' % fa)
1192 1192 else:
1193 1193 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1194 1194 'both created')
1195 1195 else:
1196 1196 a = ma[f]
1197 1197 fla = ma.flags(f)
1198 1198 nol = 'l' not in fl1 + fl2 + fla
1199 1199 if n2 == a and fl2 == fla:
1200 1200 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1201 1201 elif n1 == a and fl1 == fla: # local unchanged - use remote
1202 1202 if n1 == n2: # optimization: keep local content
1203 1203 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1204 1204 else:
1205 1205 actions[f] = (ACTION_GET, (fl2, False),
1206 1206 'remote is newer')
1207 1207 elif nol and n2 == a: # remote only changed 'x'
1208 1208 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1209 1209 elif nol and n1 == a: # local only changed 'x'
1210 1210 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1211 1211 else: # both changed something
1212 1212 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1213 1213 'versions differ')
1214 1214 elif n1: # file exists only on local side
1215 1215 if f in copied:
1216 1216 pass # we'll deal with it on m2 side
1217 1217 elif f in movewithdir: # directory rename, move local
1218 1218 f2 = movewithdir[f]
1219 1219 if f2 in m2:
1220 1220 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1221 1221 'remote directory rename, both created')
1222 1222 else:
1223 1223 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1224 1224 'remote directory rename - move from %s' % f)
1225 1225 elif f in copy:
1226 1226 f2 = copy[f]
1227 1227 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1228 1228 'local copied/moved from %s' % f2)
1229 1229 elif f in ma: # clean, a different, no remote
1230 1230 if n1 != ma[f]:
1231 1231 if acceptremote:
1232 1232 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1233 1233 else:
1234 1234 actions[f] = (ACTION_CHANGED_DELETED,
1235 1235 (f, None, f, False, pa.node()),
1236 1236 'prompt changed/deleted')
1237 1237 elif n1 == addednodeid:
1238 1238 # This extra 'a' is added by working copy manifest to mark
1239 1239 # the file as locally added. We should forget it instead of
1240 1240 # deleting it.
1241 1241 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1242 1242 else:
1243 1243 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1244 1244 elif n2: # file exists only on remote side
1245 1245 if f in copied:
1246 1246 pass # we'll deal with it on m1 side
1247 1247 elif f in movewithdir:
1248 1248 f2 = movewithdir[f]
1249 1249 if f2 in m1:
1250 1250 actions[f2] = (ACTION_MERGE,
1251 1251 (f2, f, None, False, pa.node()),
1252 1252 'local directory rename, both created')
1253 1253 else:
1254 1254 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1255 1255 'local directory rename - get from %s' % f)
1256 1256 elif f in copy:
1257 1257 f2 = copy[f]
1258 1258 if f2 in m2:
1259 1259 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1260 1260 'remote copied from %s' % f2)
1261 1261 else:
1262 1262 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1263 1263 'remote moved from %s' % f2)
1264 1264 elif f not in ma:
1265 1265 # local unknown, remote created: the logic is described by the
1266 1266 # following table:
1267 1267 #
1268 1268 # force branchmerge different | action
1269 1269 # n * * | create
1270 1270 # y n * | create
1271 1271 # y y n | create
1272 1272 # y y y | merge
1273 1273 #
1274 1274 # Checking whether the files are different is expensive, so we
1275 1275 # don't do that when we can avoid it.
1276 1276 if not force:
1277 1277 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1278 1278 elif not branchmerge:
1279 1279 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1280 1280 else:
1281 1281 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1282 1282 'remote created, get or merge')
1283 1283 elif n2 != ma[f]:
1284 1284 df = None
1285 1285 for d in dirmove:
1286 1286 if f.startswith(d):
1287 1287 # new file added in a directory that was moved
1288 1288 df = dirmove[d] + f[len(d):]
1289 1289 break
1290 1290 if df is not None and df in m1:
1291 1291 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1292 1292 'local directory rename - respect move '
1293 1293 'from %s' % f)
1294 1294 elif acceptremote:
1295 1295 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1296 1296 else:
1297 1297 actions[f] = (ACTION_DELETED_CHANGED,
1298 1298 (None, f, f, False, pa.node()),
1299 1299 'prompt deleted/changed')
1300 1300
1301 1301 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1302 1302 # If we are merging, look for path conflicts.
1303 1303 checkpathconflicts(repo, wctx, p2, actions)
1304 1304
1305 1305 narrowmatch = repo.narrowmatch()
1306 1306 if not narrowmatch.always():
1307 1307 # Updates "actions" in place
1308 1308 _filternarrowactions(narrowmatch, branchmerge, actions)
1309 1309
1310 1310 return actions, diverge, renamedelete
1311 1311
1312 1312 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1313 1313 """Resolves false conflicts where the nodeid changed but the content
1314 1314 remained the same."""
1315 1315 # We force a copy of actions.items() because we're going to mutate
1316 1316 # actions as we resolve trivial conflicts.
1317 1317 for f, (m, args, msg) in list(actions.items()):
1318 1318 if (m == ACTION_CHANGED_DELETED and f in ancestor
1319 1319 and not wctx[f].cmp(ancestor[f])):
1320 1320 # local did change but ended up with same content
1321 1321 actions[f] = ACTION_REMOVE, None, 'prompt same'
1322 1322 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1323 1323 and not mctx[f].cmp(ancestor[f])):
1324 1324 # remote did change but ended up with same content
1325 1325 del actions[f] # don't get = keep local deleted
1326 1326
1327 1327 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1328 1328 acceptremote, followcopies, matcher=None,
1329 1329 mergeforce=False):
1330 1330 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1331 1331 # Avoid cycle.
1332 1332 from . import sparse
1333 1333
1334 1334 if len(ancestors) == 1: # default
1335 1335 actions, diverge, renamedelete = manifestmerge(
1336 1336 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1337 1337 acceptremote, followcopies)
1338 1338 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1339 1339
1340 1340 else: # only when merge.preferancestor=* - the default
1341 1341 repo.ui.note(
1342 1342 _("note: merging %s and %s using bids from ancestors %s\n") %
1343 1343 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1344 1344 for anc in ancestors)))
1345 1345
1346 1346 # Call for bids
1347 1347 fbids = {} # mapping filename to bids (action method to list af actions)
1348 1348 diverge, renamedelete = None, None
1349 1349 for ancestor in ancestors:
1350 1350 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1351 1351 actions, diverge1, renamedelete1 = manifestmerge(
1352 1352 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1353 1353 acceptremote, followcopies, forcefulldiff=True)
1354 1354 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1355 1355
1356 1356 # Track the shortest set of warning on the theory that bid
1357 1357 # merge will correctly incorporate more information
1358 1358 if diverge is None or len(diverge1) < len(diverge):
1359 1359 diverge = diverge1
1360 1360 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1361 1361 renamedelete = renamedelete1
1362 1362
1363 1363 for f, a in sorted(actions.iteritems()):
1364 1364 m, args, msg = a
1365 1365 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1366 1366 if f in fbids:
1367 1367 d = fbids[f]
1368 1368 if m in d:
1369 1369 d[m].append(a)
1370 1370 else:
1371 1371 d[m] = [a]
1372 1372 else:
1373 1373 fbids[f] = {m: [a]}
1374 1374
1375 1375 # Pick the best bid for each file
1376 1376 repo.ui.note(_('\nauction for merging merge bids\n'))
1377 1377 actions = {}
1378 1378 dms = [] # filenames that have dm actions
1379 1379 for f, bids in sorted(fbids.items()):
1380 1380 # bids is a mapping from action method to list af actions
1381 1381 # Consensus?
1382 1382 if len(bids) == 1: # all bids are the same kind of method
1383 1383 m, l = list(bids.items())[0]
1384 1384 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1385 1385 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1386 1386 actions[f] = l[0]
1387 1387 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1388 1388 dms.append(f)
1389 1389 continue
1390 1390 # If keep is an option, just do it.
1391 1391 if ACTION_KEEP in bids:
1392 1392 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1393 1393 actions[f] = bids[ACTION_KEEP][0]
1394 1394 continue
1395 1395 # If there are gets and they all agree [how could they not?], do it.
1396 1396 if ACTION_GET in bids:
1397 1397 ga0 = bids[ACTION_GET][0]
1398 1398 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1399 1399 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1400 1400 actions[f] = ga0
1401 1401 continue
1402 1402 # TODO: Consider other simple actions such as mode changes
1403 1403 # Handle inefficient democrazy.
1404 1404 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1405 1405 for m, l in sorted(bids.items()):
1406 1406 for _f, args, msg in l:
1407 1407 repo.ui.note(' %s -> %s\n' % (msg, m))
1408 1408 # Pick random action. TODO: Instead, prompt user when resolving
1409 1409 m, l = list(bids.items())[0]
1410 1410 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1411 1411 (f, m))
1412 1412 actions[f] = l[0]
1413 1413 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1414 1414 dms.append(f)
1415 1415 continue
1416 1416 # Work around 'dm' that can cause multiple actions for the same file
1417 1417 for f in dms:
1418 1418 dm, (f0, flags), msg = actions[f]
1419 1419 assert dm == ACTION_DIR_RENAME_MOVE_LOCAL, dm
1420 1420 if f0 in actions and actions[f0][0] == ACTION_REMOVE:
1421 1421 # We have one bid for removing a file and another for moving it.
1422 1422 # These two could be merged as first move and then delete ...
1423 1423 # but instead drop moving and just delete.
1424 1424 del actions[f]
1425 1425 repo.ui.note(_('end of auction\n\n'))
1426 1426
1427 1427 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1428 1428
1429 1429 if wctx.rev() is None:
1430 1430 fractions = _forgetremoved(wctx, mctx, branchmerge)
1431 1431 actions.update(fractions)
1432 1432
1433 1433 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1434 1434 actions)
1435 1435
1436 1436 return prunedactions, diverge, renamedelete
1437 1437
1438 1438 def _getcwd():
1439 1439 try:
1440 1440 return pycompat.getcwd()
1441 1441 except OSError as err:
1442 1442 if err.errno == errno.ENOENT:
1443 1443 return None
1444 1444 raise
1445 1445
1446 1446 def batchremove(repo, wctx, actions):
1447 1447 """apply removes to the working directory
1448 1448
1449 1449 yields tuples for progress updates
1450 1450 """
1451 1451 verbose = repo.ui.verbose
1452 1452 cwd = _getcwd()
1453 1453 i = 0
1454 1454 for f, args, msg in actions:
1455 1455 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1456 1456 if verbose:
1457 1457 repo.ui.note(_("removing %s\n") % f)
1458 1458 wctx[f].audit()
1459 1459 try:
1460 1460 wctx[f].remove(ignoremissing=True)
1461 1461 except OSError as inst:
1462 1462 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1463 1463 (f, inst.strerror))
1464 1464 if i == 100:
1465 1465 yield i, f
1466 1466 i = 0
1467 1467 i += 1
1468 1468 if i > 0:
1469 1469 yield i, f
1470 1470
1471 1471 if cwd and not _getcwd():
1472 1472 # cwd was removed in the course of removing files; print a helpful
1473 1473 # warning.
1474 1474 repo.ui.warn(_("current directory was removed\n"
1475 1475 "(consider changing to repo root: %s)\n") % repo.root)
1476 1476
1477 1477 def batchget(repo, mctx, wctx, actions):
1478 1478 """apply gets to the working directory
1479 1479
1480 1480 mctx is the context to get from
1481 1481
1482 1482 yields tuples for progress updates
1483 1483 """
1484 1484 verbose = repo.ui.verbose
1485 1485 fctx = mctx.filectx
1486 1486 ui = repo.ui
1487 1487 i = 0
1488 1488 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1489 1489 for f, (flags, backup), msg in actions:
1490 1490 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1491 1491 if verbose:
1492 1492 repo.ui.note(_("getting %s\n") % f)
1493 1493
1494 1494 if backup:
1495 1495 # If a file or directory exists with the same name, back that
1496 1496 # up. Otherwise, look to see if there is a file that conflicts
1497 1497 # with a directory this file is in, and if so, back that up.
1498 1498 absf = repo.wjoin(f)
1499 1499 if not repo.wvfs.lexists(f):
1500 1500 for p in util.finddirs(f):
1501 1501 if repo.wvfs.isfileorlink(p):
1502 1502 absf = repo.wjoin(p)
1503 1503 break
1504 1504 orig = scmutil.origpath(ui, repo, absf)
1505 1505 if repo.wvfs.lexists(absf):
1506 1506 util.rename(absf, orig)
1507 1507 wctx[f].clearunknown()
1508 1508 atomictemp = ui.configbool("experimental", "update.atomic-file")
1509 1509 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1510 1510 atomictemp=atomictemp)
1511 1511 if i == 100:
1512 1512 yield i, f
1513 1513 i = 0
1514 1514 i += 1
1515 1515 if i > 0:
1516 1516 yield i, f
1517 1517
1518 1518 def _prefetchfiles(repo, ctx, actions):
1519 1519 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1520 1520 of merge actions. ``ctx`` is the context being merged in."""
1521 1521
1522 1522 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1523 1523 # don't touch the context to be merged in. 'cd' is skipped, because
1524 1524 # changed/deleted never resolves to something from the remote side.
1525 1525 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1526 1526 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1527 1527 prefetch = scmutil.prefetchfiles
1528 1528 matchfiles = scmutil.matchfiles
1529 1529 prefetch(repo, [ctx.rev()],
1530 1530 matchfiles(repo,
1531 1531 [f for sublist in oplist for f, args, msg in sublist]))
1532 1532
1533 1533 @attr.s(frozen=True)
1534 1534 class updateresult(object):
1535 1535 updatedcount = attr.ib()
1536 1536 mergedcount = attr.ib()
1537 1537 removedcount = attr.ib()
1538 1538 unresolvedcount = attr.ib()
1539 1539
1540 1540 def isempty(self):
1541 1541 return (not self.updatedcount and not self.mergedcount
1542 1542 and not self.removedcount and not self.unresolvedcount)
1543 1543
1544 1544 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1545 1545 """apply the merge action list to the working directory
1546 1546
1547 1547 wctx is the working copy context
1548 1548 mctx is the context to be merged into the working copy
1549 1549
1550 1550 Return a tuple of counts (updated, merged, removed, unresolved) that
1551 1551 describes how many files were affected by the update.
1552 1552 """
1553 1553
1554 1554 _prefetchfiles(repo, mctx, actions)
1555 1555
1556 1556 updated, merged, removed = 0, 0, 0
1557 1557 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1558 1558 moves = []
1559 1559 for m, l in actions.items():
1560 1560 l.sort()
1561 1561
1562 1562 # 'cd' and 'dc' actions are treated like other merge conflicts
1563 1563 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1564 1564 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1565 1565 mergeactions.extend(actions[ACTION_MERGE])
1566 1566 for f, args, msg in mergeactions:
1567 1567 f1, f2, fa, move, anc = args
1568 1568 if f == '.hgsubstate': # merged internally
1569 1569 continue
1570 1570 if f1 is None:
1571 1571 fcl = filemerge.absentfilectx(wctx, fa)
1572 1572 else:
1573 1573 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1574 1574 fcl = wctx[f1]
1575 1575 if f2 is None:
1576 1576 fco = filemerge.absentfilectx(mctx, fa)
1577 1577 else:
1578 1578 fco = mctx[f2]
1579 1579 actx = repo[anc]
1580 1580 if fa in actx:
1581 1581 fca = actx[fa]
1582 1582 else:
1583 1583 # TODO: move to absentfilectx
1584 1584 fca = repo.filectx(f1, fileid=nullrev)
1585 1585 ms.add(fcl, fco, fca, f)
1586 1586 if f1 != f and move:
1587 1587 moves.append(f1)
1588 1588
1589 1589 # remove renamed files after safely stored
1590 1590 for f in moves:
1591 1591 if wctx[f].lexists():
1592 1592 repo.ui.debug("removing %s\n" % f)
1593 1593 wctx[f].audit()
1594 1594 wctx[f].remove()
1595 1595
1596 1596 numupdates = sum(len(l) for m, l in actions.items()
1597 1597 if m != ACTION_KEEP)
1598 1598 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1599 1599 total=numupdates)
1600 1600
1601 1601 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1602 1602 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1603 1603
1604 1604 # record path conflicts
1605 1605 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1606 1606 f1, fo = args
1607 1607 s = repo.ui.status
1608 1608 s(_("%s: path conflict - a file or link has the same name as a "
1609 1609 "directory\n") % f)
1610 1610 if fo == 'l':
1611 1611 s(_("the local file has been renamed to %s\n") % f1)
1612 1612 else:
1613 1613 s(_("the remote file has been renamed to %s\n") % f1)
1614 1614 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1615 1615 ms.addpath(f, f1, fo)
1616 1616 progress.increment(item=f)
1617 1617
1618 1618 # When merging in-memory, we can't support worker processes, so set the
1619 1619 # per-item cost at 0 in that case.
1620 1620 cost = 0 if wctx.isinmemory() else 0.001
1621 1621
1622 1622 # remove in parallel (must come before resolving path conflicts and getting)
1623 1623 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1624 1624 actions[ACTION_REMOVE])
1625 1625 for i, item in prog:
1626 1626 progress.increment(step=i, item=item)
1627 1627 removed = len(actions[ACTION_REMOVE])
1628 1628
1629 1629 # resolve path conflicts (must come before getting)
1630 1630 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1631 1631 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1632 1632 f0, = args
1633 1633 if wctx[f0].lexists():
1634 1634 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1635 1635 wctx[f].audit()
1636 1636 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1637 1637 wctx[f0].remove()
1638 1638 progress.increment(item=f)
1639 1639
1640 1640 # get in parallel
1641 1641 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1642 1642 actions[ACTION_GET])
1643 1643 for i, item in prog:
1644 1644 progress.increment(step=i, item=item)
1645 1645 updated = len(actions[ACTION_GET])
1646 1646
1647 1647 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1648 1648 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1649 1649
1650 1650 # forget (manifest only, just log it) (must come first)
1651 1651 for f, args, msg in actions[ACTION_FORGET]:
1652 1652 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1653 1653 progress.increment(item=f)
1654 1654
1655 1655 # re-add (manifest only, just log it)
1656 1656 for f, args, msg in actions[ACTION_ADD]:
1657 1657 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1658 1658 progress.increment(item=f)
1659 1659
1660 1660 # re-add/mark as modified (manifest only, just log it)
1661 1661 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1662 1662 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1663 1663 progress.increment(item=f)
1664 1664
1665 1665 # keep (noop, just log it)
1666 1666 for f, args, msg in actions[ACTION_KEEP]:
1667 1667 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1668 1668 # no progress
1669 1669
1670 1670 # directory rename, move local
1671 1671 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1672 1672 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1673 1673 progress.increment(item=f)
1674 1674 f0, flags = args
1675 1675 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1676 1676 wctx[f].audit()
1677 1677 wctx[f].write(wctx.filectx(f0).data(), flags)
1678 1678 wctx[f0].remove()
1679 1679 updated += 1
1680 1680
1681 1681 # local directory rename, get
1682 1682 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1683 1683 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1684 1684 progress.increment(item=f)
1685 1685 f0, flags = args
1686 1686 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1687 1687 wctx[f].write(mctx.filectx(f0).data(), flags)
1688 1688 updated += 1
1689 1689
1690 1690 # exec
1691 1691 for f, args, msg in actions[ACTION_EXEC]:
1692 1692 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1693 1693 progress.increment(item=f)
1694 1694 flags, = args
1695 1695 wctx[f].audit()
1696 1696 wctx[f].setflags('l' in flags, 'x' in flags)
1697 1697 updated += 1
1698 1698
1699 1699 # the ordering is important here -- ms.mergedriver will raise if the merge
1700 1700 # driver has changed, and we want to be able to bypass it when overwrite is
1701 1701 # True
1702 1702 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1703 1703
1704 1704 if usemergedriver:
1705 1705 if wctx.isinmemory():
1706 1706 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1707 1707 "support mergedriver")
1708 1708 ms.commit()
1709 1709 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1710 1710 # the driver might leave some files unresolved
1711 1711 unresolvedf = set(ms.unresolved())
1712 1712 if not proceed:
1713 1713 # XXX setting unresolved to at least 1 is a hack to make sure we
1714 1714 # error out
1715 1715 return updateresult(updated, merged, removed,
1716 1716 max(len(unresolvedf), 1))
1717 1717 newactions = []
1718 1718 for f, args, msg in mergeactions:
1719 1719 if f in unresolvedf:
1720 1720 newactions.append((f, args, msg))
1721 1721 mergeactions = newactions
1722 1722
1723 1723 try:
1724 1724 # premerge
1725 1725 tocomplete = []
1726 1726 for f, args, msg in mergeactions:
1727 1727 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1728 1728 progress.increment(item=f)
1729 1729 if f == '.hgsubstate': # subrepo states need updating
1730 1730 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1731 1731 overwrite, labels)
1732 1732 continue
1733 1733 wctx[f].audit()
1734 1734 complete, r = ms.preresolve(f, wctx)
1735 1735 if not complete:
1736 1736 numupdates += 1
1737 1737 tocomplete.append((f, args, msg))
1738 1738
1739 1739 # merge
1740 1740 for f, args, msg in tocomplete:
1741 1741 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1742 1742 progress.increment(item=f, total=numupdates)
1743 1743 ms.resolve(f, wctx)
1744 1744
1745 1745 finally:
1746 1746 ms.commit()
1747 1747
1748 1748 unresolved = ms.unresolvedcount()
1749 1749
1750 1750 if (usemergedriver and not unresolved
1751 1751 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1752 1752 if not driverconclude(repo, ms, wctx, labels=labels):
1753 1753 # XXX setting unresolved to at least 1 is a hack to make sure we
1754 1754 # error out
1755 1755 unresolved = max(unresolved, 1)
1756 1756
1757 1757 ms.commit()
1758 1758
1759 1759 msupdated, msmerged, msremoved = ms.counts()
1760 1760 updated += msupdated
1761 1761 merged += msmerged
1762 1762 removed += msremoved
1763 1763
1764 1764 extraactions = ms.actions()
1765 1765 if extraactions:
1766 1766 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1767 1767 for k, acts in extraactions.iteritems():
1768 1768 actions[k].extend(acts)
1769 1769 # Remove these files from actions[ACTION_MERGE] as well. This is
1770 1770 # important because in recordupdates, files in actions[ACTION_MERGE]
1771 1771 # are processed after files in other actions, and the merge driver
1772 1772 # might add files to those actions via extraactions above. This can
1773 1773 # lead to a file being recorded twice, with poor results. This is
1774 1774 # especially problematic for actions[ACTION_REMOVE] (currently only
1775 1775 # possible with the merge driver in the initial merge process;
1776 1776 # interrupted merges don't go through this flow).
1777 1777 #
1778 1778 # The real fix here is to have indexes by both file and action so
1779 1779 # that when the action for a file is changed it is automatically
1780 1780 # reflected in the other action lists. But that involves a more
1781 1781 # complex data structure, so this will do for now.
1782 1782 #
1783 1783 # We don't need to do the same operation for 'dc' and 'cd' because
1784 1784 # those lists aren't consulted again.
1785 1785 mfiles.difference_update(a[0] for a in acts)
1786 1786
1787 1787 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1788 1788 if a[0] in mfiles]
1789 1789
1790 1790 progress.complete()
1791 1791 return updateresult(updated, merged, removed, unresolved)
1792 1792
1793 1793 def recordupdates(repo, actions, branchmerge):
1794 1794 "record merge actions to the dirstate"
1795 1795 # remove (must come first)
1796 1796 for f, args, msg in actions.get(ACTION_REMOVE, []):
1797 1797 if branchmerge:
1798 1798 repo.dirstate.remove(f)
1799 1799 else:
1800 1800 repo.dirstate.drop(f)
1801 1801
1802 1802 # forget (must come first)
1803 1803 for f, args, msg in actions.get(ACTION_FORGET, []):
1804 1804 repo.dirstate.drop(f)
1805 1805
1806 1806 # resolve path conflicts
1807 1807 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1808 1808 f0, = args
1809 1809 origf0 = repo.dirstate.copied(f0) or f0
1810 1810 repo.dirstate.add(f)
1811 1811 repo.dirstate.copy(origf0, f)
1812 1812 if f0 == origf0:
1813 1813 repo.dirstate.remove(f0)
1814 1814 else:
1815 1815 repo.dirstate.drop(f0)
1816 1816
1817 1817 # re-add
1818 1818 for f, args, msg in actions.get(ACTION_ADD, []):
1819 1819 repo.dirstate.add(f)
1820 1820
1821 1821 # re-add/mark as modified
1822 1822 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1823 1823 if branchmerge:
1824 1824 repo.dirstate.normallookup(f)
1825 1825 else:
1826 1826 repo.dirstate.add(f)
1827 1827
1828 1828 # exec change
1829 1829 for f, args, msg in actions.get(ACTION_EXEC, []):
1830 1830 repo.dirstate.normallookup(f)
1831 1831
1832 1832 # keep
1833 1833 for f, args, msg in actions.get(ACTION_KEEP, []):
1834 1834 pass
1835 1835
1836 1836 # get
1837 1837 for f, args, msg in actions.get(ACTION_GET, []):
1838 1838 if branchmerge:
1839 1839 repo.dirstate.otherparent(f)
1840 1840 else:
1841 1841 repo.dirstate.normal(f)
1842 1842
1843 1843 # merge
1844 1844 for f, args, msg in actions.get(ACTION_MERGE, []):
1845 1845 f1, f2, fa, move, anc = args
1846 1846 if branchmerge:
1847 1847 # We've done a branch merge, mark this file as merged
1848 1848 # so that we properly record the merger later
1849 1849 repo.dirstate.merge(f)
1850 1850 if f1 != f2: # copy/rename
1851 1851 if move:
1852 1852 repo.dirstate.remove(f1)
1853 1853 if f1 != f:
1854 1854 repo.dirstate.copy(f1, f)
1855 1855 else:
1856 1856 repo.dirstate.copy(f2, f)
1857 1857 else:
1858 1858 # We've update-merged a locally modified file, so
1859 1859 # we set the dirstate to emulate a normal checkout
1860 1860 # of that file some time in the past. Thus our
1861 1861 # merge will appear as a normal local file
1862 1862 # modification.
1863 1863 if f2 == f: # file not locally copied/moved
1864 1864 repo.dirstate.normallookup(f)
1865 1865 if move:
1866 1866 repo.dirstate.drop(f1)
1867 1867
1868 1868 # directory rename, move local
1869 1869 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1870 1870 f0, flag = args
1871 1871 if branchmerge:
1872 1872 repo.dirstate.add(f)
1873 1873 repo.dirstate.remove(f0)
1874 1874 repo.dirstate.copy(f0, f)
1875 1875 else:
1876 1876 repo.dirstate.normal(f)
1877 1877 repo.dirstate.drop(f0)
1878 1878
1879 1879 # directory rename, get
1880 1880 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1881 1881 f0, flag = args
1882 1882 if branchmerge:
1883 1883 repo.dirstate.add(f)
1884 1884 repo.dirstate.copy(f0, f)
1885 1885 else:
1886 1886 repo.dirstate.normal(f)
1887 1887
1888 1888 def update(repo, node, branchmerge, force, ancestor=None,
1889 1889 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1890 1890 updatecheck=None, wc=None):
1891 1891 """
1892 1892 Perform a merge between the working directory and the given node
1893 1893
1894 1894 node = the node to update to
1895 1895 branchmerge = whether to merge between branches
1896 1896 force = whether to force branch merging or file overwriting
1897 1897 matcher = a matcher to filter file lists (dirstate not updated)
1898 1898 mergeancestor = whether it is merging with an ancestor. If true,
1899 1899 we should accept the incoming changes for any prompts that occur.
1900 1900 If false, merging with an ancestor (fast-forward) is only allowed
1901 1901 between different named branches. This flag is used by rebase extension
1902 1902 as a temporary fix and should be avoided in general.
1903 1903 labels = labels to use for base, local and other
1904 1904 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1905 1905 this is True, then 'force' should be True as well.
1906 1906
1907 1907 The table below shows all the behaviors of the update command given the
1908 1908 -c/--check and -C/--clean or no options, whether the working directory is
1909 1909 dirty, whether a revision is specified, and the relationship of the parent
1910 1910 rev to the target rev (linear or not). Match from top first. The -n
1911 1911 option doesn't exist on the command line, but represents the
1912 1912 experimental.updatecheck=noconflict option.
1913 1913
1914 1914 This logic is tested by test-update-branches.t.
1915 1915
1916 1916 -c -C -n -m dirty rev linear | result
1917 1917 y y * * * * * | (1)
1918 1918 y * y * * * * | (1)
1919 1919 y * * y * * * | (1)
1920 1920 * y y * * * * | (1)
1921 1921 * y * y * * * | (1)
1922 1922 * * y y * * * | (1)
1923 1923 * * * * * n n | x
1924 1924 * * * * n * * | ok
1925 1925 n n n n y * y | merge
1926 1926 n n n n y y n | (2)
1927 1927 n n n y y * * | merge
1928 1928 n n y n y * * | merge if no conflict
1929 1929 n y n n y * * | discard
1930 1930 y n n n y * * | (3)
1931 1931
1932 1932 x = can't happen
1933 1933 * = don't-care
1934 1934 1 = incompatible options (checked in commands.py)
1935 1935 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1936 1936 3 = abort: uncommitted changes (checked in commands.py)
1937 1937
1938 1938 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1939 1939 to repo[None] if None is passed.
1940 1940
1941 1941 Return the same tuple as applyupdates().
1942 1942 """
1943 1943 # Avoid cycle.
1944 1944 from . import sparse
1945 1945
1946 1946 # This function used to find the default destination if node was None, but
1947 1947 # that's now in destutil.py.
1948 1948 assert node is not None
1949 1949 if not branchmerge and not force:
1950 1950 # TODO: remove the default once all callers that pass branchmerge=False
1951 1951 # and force=False pass a value for updatecheck. We may want to allow
1952 1952 # updatecheck='abort' to better suppport some of these callers.
1953 1953 if updatecheck is None:
1954 1954 updatecheck = 'linear'
1955 1955 assert updatecheck in ('none', 'linear', 'noconflict')
1956 1956 # If we're doing a partial update, we need to skip updating
1957 1957 # the dirstate, so make a note of any partial-ness to the
1958 1958 # update here.
1959 1959 if matcher is None or matcher.always():
1960 1960 partial = False
1961 1961 else:
1962 1962 partial = True
1963 1963 with repo.wlock():
1964 1964 if wc is None:
1965 1965 wc = repo[None]
1966 1966 pl = wc.parents()
1967 1967 p1 = pl[0]
1968 1968 pas = [None]
1969 1969 if ancestor is not None:
1970 1970 pas = [repo[ancestor]]
1971 1971
1972 1972 overwrite = force and not branchmerge
1973 1973
1974 1974 p2 = repo[node]
1975 1975 if pas[0] is None:
1976 1976 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1977 1977 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1978 1978 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1979 1979 else:
1980 1980 pas = [p1.ancestor(p2, warn=branchmerge)]
1981 1981
1982 1982 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1983 1983
1984 1984 ### check phase
1985 1985 if not overwrite:
1986 1986 if len(pl) > 1:
1987 1987 raise error.Abort(_("outstanding uncommitted merge"))
1988 1988 ms = mergestate.read(repo)
1989 1989 if list(ms.unresolved()):
1990 1990 raise error.Abort(_("outstanding merge conflicts"))
1991 1991 if branchmerge:
1992 1992 if pas == [p2]:
1993 1993 raise error.Abort(_("merging with a working directory ancestor"
1994 1994 " has no effect"))
1995 1995 elif pas == [p1]:
1996 1996 if not mergeancestor and wc.branch() == p2.branch():
1997 1997 raise error.Abort(_("nothing to merge"),
1998 1998 hint=_("use 'hg update' "
1999 1999 "or check 'hg heads'"))
2000 2000 if not force and (wc.files() or wc.deleted()):
2001 2001 raise error.Abort(_("uncommitted changes"),
2002 2002 hint=_("use 'hg status' to list changes"))
2003 2003 if not wc.isinmemory():
2004 2004 for s in sorted(wc.substate):
2005 2005 wc.sub(s).bailifchanged()
2006 2006
2007 2007 elif not overwrite:
2008 2008 if p1 == p2: # no-op update
2009 2009 # call the hooks and exit early
2010 2010 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2011 2011 repo.hook('update', parent1=xp2, parent2='', error=0)
2012 2012 return updateresult(0, 0, 0, 0)
2013 2013
2014 2014 if (updatecheck == 'linear' and
2015 2015 pas not in ([p1], [p2])): # nonlinear
2016 2016 dirty = wc.dirty(missing=True)
2017 2017 if dirty:
2018 2018 # Branching is a bit strange to ensure we do the minimal
2019 2019 # amount of call to obsutil.foreground.
2020 2020 foreground = obsutil.foreground(repo, [p1.node()])
2021 2021 # note: the <node> variable contains a random identifier
2022 2022 if repo[node].node() in foreground:
2023 2023 pass # allow updating to successors
2024 2024 else:
2025 2025 msg = _("uncommitted changes")
2026 2026 hint = _("commit or update --clean to discard changes")
2027 2027 raise error.UpdateAbort(msg, hint=hint)
2028 2028 else:
2029 2029 # Allow jumping branches if clean and specific rev given
2030 2030 pass
2031 2031
2032 2032 if overwrite:
2033 2033 pas = [wc]
2034 2034 elif not branchmerge:
2035 2035 pas = [p1]
2036 2036
2037 2037 # deprecated config: merge.followcopies
2038 2038 followcopies = repo.ui.configbool('merge', 'followcopies')
2039 2039 if overwrite:
2040 2040 followcopies = False
2041 2041 elif not pas[0]:
2042 2042 followcopies = False
2043 2043 if not branchmerge and not wc.dirty(missing=True):
2044 2044 followcopies = False
2045 2045
2046 2046 ### calculate phase
2047 2047 actionbyfile, diverge, renamedelete = calculateupdates(
2048 2048 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2049 2049 followcopies, matcher=matcher, mergeforce=mergeforce)
2050 2050
2051 2051 if updatecheck == 'noconflict':
2052 2052 for f, (m, args, msg) in actionbyfile.iteritems():
2053 2053 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2054 2054 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2055 2055 msg = _("conflicting changes")
2056 2056 hint = _("commit or update --clean to discard changes")
2057 2057 raise error.Abort(msg, hint=hint)
2058 2058
2059 2059 # Prompt and create actions. Most of this is in the resolve phase
2060 2060 # already, but we can't handle .hgsubstate in filemerge or
2061 2061 # subrepoutil.submerge yet so we have to keep prompting for it.
2062 2062 if '.hgsubstate' in actionbyfile:
2063 2063 f = '.hgsubstate'
2064 2064 m, args, msg = actionbyfile[f]
2065 2065 prompts = filemerge.partextras(labels)
2066 2066 prompts['f'] = f
2067 2067 if m == ACTION_CHANGED_DELETED:
2068 2068 if repo.ui.promptchoice(
2069 2069 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2070 2070 "use (c)hanged version or (d)elete?"
2071 2071 "$$ &Changed $$ &Delete") % prompts, 0):
2072 2072 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2073 2073 elif f in p1:
2074 2074 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2075 2075 else:
2076 2076 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2077 2077 elif m == ACTION_DELETED_CHANGED:
2078 2078 f1, f2, fa, move, anc = args
2079 2079 flags = p2[f2].flags()
2080 2080 if repo.ui.promptchoice(
2081 2081 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2082 2082 "use (c)hanged version or leave (d)eleted?"
2083 2083 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2084 2084 actionbyfile[f] = (ACTION_GET, (flags, False),
2085 2085 'prompt recreating')
2086 2086 else:
2087 2087 del actionbyfile[f]
2088 2088
2089 2089 # Convert to dictionary-of-lists format
2090 2090 actions = dict((m, [])
2091 2091 for m in (
2092 2092 ACTION_ADD,
2093 2093 ACTION_ADD_MODIFIED,
2094 2094 ACTION_FORGET,
2095 2095 ACTION_GET,
2096 2096 ACTION_CHANGED_DELETED,
2097 2097 ACTION_DELETED_CHANGED,
2098 2098 ACTION_REMOVE,
2099 2099 ACTION_DIR_RENAME_MOVE_LOCAL,
2100 2100 ACTION_LOCAL_DIR_RENAME_GET,
2101 2101 ACTION_MERGE,
2102 2102 ACTION_EXEC,
2103 2103 ACTION_KEEP,
2104 2104 ACTION_PATH_CONFLICT,
2105 2105 ACTION_PATH_CONFLICT_RESOLVE))
2106 2106 for f, (m, args, msg) in actionbyfile.iteritems():
2107 2107 if m not in actions:
2108 2108 actions[m] = []
2109 2109 actions[m].append((f, args, msg))
2110 2110
2111 2111 if not util.fscasesensitive(repo.path):
2112 2112 # check collision between files only in p2 for clean update
2113 2113 if (not branchmerge and
2114 2114 (force or not wc.dirty(missing=True, branch=False))):
2115 2115 _checkcollision(repo, p2.manifest(), None)
2116 2116 else:
2117 2117 _checkcollision(repo, wc.manifest(), actions)
2118 2118
2119 2119 # divergent renames
2120 2120 for f, fl in sorted(diverge.iteritems()):
2121 2121 repo.ui.warn(_("note: possible conflict - %s was renamed "
2122 2122 "multiple times to:\n") % f)
2123 2123 for nf in fl:
2124 2124 repo.ui.warn(" %s\n" % nf)
2125 2125
2126 2126 # rename and delete
2127 2127 for f, fl in sorted(renamedelete.iteritems()):
2128 2128 repo.ui.warn(_("note: possible conflict - %s was deleted "
2129 2129 "and renamed to:\n") % f)
2130 2130 for nf in fl:
2131 2131 repo.ui.warn(" %s\n" % nf)
2132 2132
2133 2133 ### apply phase
2134 2134 if not branchmerge: # just jump to the new rev
2135 2135 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2136 2136 if not partial and not wc.isinmemory():
2137 2137 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2138 2138 # note that we're in the middle of an update
2139 2139 repo.vfs.write('updatestate', p2.hex())
2140 2140
2141 2141 # Advertise fsmonitor when its presence could be useful.
2142 2142 #
2143 2143 # We only advertise when performing an update from an empty working
2144 2144 # directory. This typically only occurs during initial clone.
2145 2145 #
2146 2146 # We give users a mechanism to disable the warning in case it is
2147 2147 # annoying.
2148 2148 #
2149 2149 # We only allow on Linux and MacOS because that's where fsmonitor is
2150 2150 # considered stable.
2151 2151 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2152 2152 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2153 2153 'warn_update_file_count')
2154 2154 try:
2155 2155 # avoid cycle: extensions -> cmdutil -> merge
2156 2156 from . import extensions
2157 2157 extensions.find('fsmonitor')
2158 2158 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2159 2159 # We intentionally don't look at whether fsmonitor has disabled
2160 2160 # itself because a) fsmonitor may have already printed a warning
2161 2161 # b) we only care about the config state here.
2162 2162 except KeyError:
2163 2163 fsmonitorenabled = False
2164 2164
2165 2165 if (fsmonitorwarning
2166 2166 and not fsmonitorenabled
2167 2167 and p1.node() == nullid
2168 2168 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2169 2169 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2170 2170 repo.ui.warn(
2171 2171 _('(warning: large working directory being used without '
2172 2172 'fsmonitor enabled; enable fsmonitor to improve performance; '
2173 2173 'see "hg help -e fsmonitor")\n'))
2174 2174
2175 2175 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
2176 2176
2177 2177 if not partial and not wc.isinmemory():
2178 2178 with repo.dirstate.parentchange():
2179 2179 repo.setparents(fp1, fp2)
2180 2180 recordupdates(repo, actions, branchmerge)
2181 2181 # update completed, clear state
2182 2182 util.unlink(repo.vfs.join('updatestate'))
2183 2183
2184 2184 if not branchmerge:
2185 2185 repo.dirstate.setbranch(p2.branch())
2186 2186
2187 2187 # If we're updating to a location, clean up any stale temporary includes
2188 2188 # (ex: this happens during hg rebase --abort).
2189 2189 if not branchmerge:
2190 2190 sparse.prunetemporaryincludes(repo)
2191 2191
2192 2192 if not partial:
2193 2193 repo.hook('update', parent1=xp1, parent2=xp2,
2194 2194 error=stats.unresolvedcount)
2195 2195 return stats
2196 2196
2197 def graft(repo, ctx, pctx, labels, keepparent=False):
2197 def graft(repo, ctx, pctx, labels, keepparent=False,
2198 keepconflictparent=False):
2198 2199 """Do a graft-like merge.
2199 2200
2200 2201 This is a merge where the merge ancestor is chosen such that one
2201 2202 or more changesets are grafted onto the current changeset. In
2202 2203 addition to the merge, this fixes up the dirstate to include only
2203 2204 a single parent (if keepparent is False) and tries to duplicate any
2204 2205 renames/copies appropriately.
2205 2206
2206 2207 ctx - changeset to rebase
2207 2208 pctx - merge base, usually ctx.p1()
2208 2209 labels - merge labels eg ['local', 'graft']
2209 2210 keepparent - keep second parent if any
2211 keepparent - if unresolved, keep parent used for the merge
2210 2212
2211 2213 """
2212 2214 # If we're grafting a descendant onto an ancestor, be sure to pass
2213 2215 # mergeancestor=True to update. This does two things: 1) allows the merge if
2214 2216 # the destination is the same as the parent of the ctx (so we can use graft
2215 2217 # to copy commits), and 2) informs update that the incoming changes are
2216 2218 # newer than the destination so it doesn't prompt about "remote changed foo
2217 2219 # which local deleted".
2218 2220 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2219 2221
2220 2222 stats = update(repo, ctx.node(), True, True, pctx.node(),
2221 2223 mergeancestor=mergeancestor, labels=labels)
2222 2224
2223 pother = nullid
2224 parents = ctx.parents()
2225 if keepparent and len(parents) == 2 and pctx in parents:
2226 parents.remove(pctx)
2227 pother = parents[0].node()
2225
2226 if keepconflictparent and stats.unresolvedcount:
2227 pother = ctx.node()
2228 else:
2229 pother = nullid
2230 parents = ctx.parents()
2231 if keepparent and len(parents) == 2 and pctx in parents:
2232 parents.remove(pctx)
2233 pother = parents[0].node()
2228 2234
2229 2235 with repo.dirstate.parentchange():
2230 2236 repo.setparents(repo['.'].node(), pother)
2231 2237 repo.dirstate.write(repo.currenttransaction())
2232 2238 # fix up dirstate for copies and renames
2233 2239 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2234 2240 return stats
General Comments 0
You need to be logged in to leave comments. Login now