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