##// END OF EJS Templates
merge: migrate to scmutil.backuppath()...
Martin von Zweigbergk -
r41741:8c8121c3 default draft
parent child Browse files
Show More
@@ -1,2320 +1,2319 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 = set([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 = set(['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 1383 dms = [] # filenames that have dm actions
1384 1384 for f, bids in sorted(fbids.items()):
1385 1385 # bids is a mapping from action method to list af actions
1386 1386 # Consensus?
1387 1387 if len(bids) == 1: # all bids are the same kind of method
1388 1388 m, l = list(bids.items())[0]
1389 1389 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1390 1390 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1391 1391 actions[f] = l[0]
1392 1392 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1393 1393 dms.append(f)
1394 1394 continue
1395 1395 # If keep is an option, just do it.
1396 1396 if ACTION_KEEP in bids:
1397 1397 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1398 1398 actions[f] = bids[ACTION_KEEP][0]
1399 1399 continue
1400 1400 # If there are gets and they all agree [how could they not?], do it.
1401 1401 if ACTION_GET in bids:
1402 1402 ga0 = bids[ACTION_GET][0]
1403 1403 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1404 1404 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1405 1405 actions[f] = ga0
1406 1406 continue
1407 1407 # TODO: Consider other simple actions such as mode changes
1408 1408 # Handle inefficient democrazy.
1409 1409 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1410 1410 for m, l in sorted(bids.items()):
1411 1411 for _f, args, msg in l:
1412 1412 repo.ui.note(' %s -> %s\n' % (msg, m))
1413 1413 # Pick random action. TODO: Instead, prompt user when resolving
1414 1414 m, l = list(bids.items())[0]
1415 1415 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1416 1416 (f, m))
1417 1417 actions[f] = l[0]
1418 1418 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1419 1419 dms.append(f)
1420 1420 continue
1421 1421 # Work around 'dm' that can cause multiple actions for the same file
1422 1422 for f in dms:
1423 1423 dm, (f0, flags), msg = actions[f]
1424 1424 assert dm == ACTION_DIR_RENAME_MOVE_LOCAL, dm
1425 1425 if f0 in actions and actions[f0][0] == ACTION_REMOVE:
1426 1426 # We have one bid for removing a file and another for moving it.
1427 1427 # These two could be merged as first move and then delete ...
1428 1428 # but instead drop moving and just delete.
1429 1429 del actions[f]
1430 1430 repo.ui.note(_('end of auction\n\n'))
1431 1431
1432 1432 if wctx.rev() is None:
1433 1433 fractions = _forgetremoved(wctx, mctx, branchmerge)
1434 1434 actions.update(fractions)
1435 1435
1436 1436 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1437 1437 actions)
1438 1438 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1439 1439
1440 1440 return prunedactions, diverge, renamedelete
1441 1441
1442 1442 def _getcwd():
1443 1443 try:
1444 1444 return encoding.getcwd()
1445 1445 except OSError as err:
1446 1446 if err.errno == errno.ENOENT:
1447 1447 return None
1448 1448 raise
1449 1449
1450 1450 def batchremove(repo, wctx, actions):
1451 1451 """apply removes to the working directory
1452 1452
1453 1453 yields tuples for progress updates
1454 1454 """
1455 1455 verbose = repo.ui.verbose
1456 1456 cwd = _getcwd()
1457 1457 i = 0
1458 1458 for f, args, msg in actions:
1459 1459 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1460 1460 if verbose:
1461 1461 repo.ui.note(_("removing %s\n") % f)
1462 1462 wctx[f].audit()
1463 1463 try:
1464 1464 wctx[f].remove(ignoremissing=True)
1465 1465 except OSError as inst:
1466 1466 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1467 1467 (f, inst.strerror))
1468 1468 if i == 100:
1469 1469 yield i, f
1470 1470 i = 0
1471 1471 i += 1
1472 1472 if i > 0:
1473 1473 yield i, f
1474 1474
1475 1475 if cwd and not _getcwd():
1476 1476 # cwd was removed in the course of removing files; print a helpful
1477 1477 # warning.
1478 1478 repo.ui.warn(_("current directory was removed\n"
1479 1479 "(consider changing to repo root: %s)\n") % repo.root)
1480 1480
1481 1481 def batchget(repo, mctx, wctx, actions):
1482 1482 """apply gets to the working directory
1483 1483
1484 1484 mctx is the context to get from
1485 1485
1486 1486 yields tuples for progress updates
1487 1487 """
1488 1488 verbose = repo.ui.verbose
1489 1489 fctx = mctx.filectx
1490 1490 ui = repo.ui
1491 1491 i = 0
1492 1492 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1493 1493 for f, (flags, backup), msg in actions:
1494 1494 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1495 1495 if verbose:
1496 1496 repo.ui.note(_("getting %s\n") % f)
1497 1497
1498 1498 if backup:
1499 1499 # If a file or directory exists with the same name, back that
1500 1500 # up. Otherwise, look to see if there is a file that conflicts
1501 1501 # with a directory this file is in, and if so, back that up.
1502 1502 conflicting = f
1503 1503 if not repo.wvfs.lexists(f):
1504 1504 for p in util.finddirs(f):
1505 1505 if repo.wvfs.isfileorlink(p):
1506 1506 conflicting = p
1507 1507 break
1508 1508 if repo.wvfs.lexists(conflicting):
1509 absf = repo.wjoin(conflicting)
1510 orig = scmutil.origpath(ui, repo, absf)
1511 util.rename(absf, orig)
1509 orig = scmutil.backuppath(ui, repo, conflicting)
1510 util.rename(repo.wjoin(conflicting), orig)
1512 1511 wctx[f].clearunknown()
1513 1512 atomictemp = ui.configbool("experimental", "update.atomic-file")
1514 1513 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1515 1514 atomictemp=atomictemp)
1516 1515 if i == 100:
1517 1516 yield i, f
1518 1517 i = 0
1519 1518 i += 1
1520 1519 if i > 0:
1521 1520 yield i, f
1522 1521
1523 1522 def _prefetchfiles(repo, ctx, actions):
1524 1523 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1525 1524 of merge actions. ``ctx`` is the context being merged in."""
1526 1525
1527 1526 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1528 1527 # don't touch the context to be merged in. 'cd' is skipped, because
1529 1528 # changed/deleted never resolves to something from the remote side.
1530 1529 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1531 1530 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1532 1531 prefetch = scmutil.prefetchfiles
1533 1532 matchfiles = scmutil.matchfiles
1534 1533 prefetch(repo, [ctx.rev()],
1535 1534 matchfiles(repo,
1536 1535 [f for sublist in oplist for f, args, msg in sublist]))
1537 1536
1538 1537 @attr.s(frozen=True)
1539 1538 class updateresult(object):
1540 1539 updatedcount = attr.ib()
1541 1540 mergedcount = attr.ib()
1542 1541 removedcount = attr.ib()
1543 1542 unresolvedcount = attr.ib()
1544 1543
1545 1544 def isempty(self):
1546 1545 return not (self.updatedcount or self.mergedcount
1547 1546 or self.removedcount or self.unresolvedcount)
1548 1547
1549 1548 def emptyactions():
1550 1549 """create an actions dict, to be populated and passed to applyupdates()"""
1551 1550 return dict((m, [])
1552 1551 for m in (
1553 1552 ACTION_ADD,
1554 1553 ACTION_ADD_MODIFIED,
1555 1554 ACTION_FORGET,
1556 1555 ACTION_GET,
1557 1556 ACTION_CHANGED_DELETED,
1558 1557 ACTION_DELETED_CHANGED,
1559 1558 ACTION_REMOVE,
1560 1559 ACTION_DIR_RENAME_MOVE_LOCAL,
1561 1560 ACTION_LOCAL_DIR_RENAME_GET,
1562 1561 ACTION_MERGE,
1563 1562 ACTION_EXEC,
1564 1563 ACTION_KEEP,
1565 1564 ACTION_PATH_CONFLICT,
1566 1565 ACTION_PATH_CONFLICT_RESOLVE))
1567 1566
1568 1567 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1569 1568 """apply the merge action list to the working directory
1570 1569
1571 1570 wctx is the working copy context
1572 1571 mctx is the context to be merged into the working copy
1573 1572
1574 1573 Return a tuple of counts (updated, merged, removed, unresolved) that
1575 1574 describes how many files were affected by the update.
1576 1575 """
1577 1576
1578 1577 _prefetchfiles(repo, mctx, actions)
1579 1578
1580 1579 updated, merged, removed = 0, 0, 0
1581 1580 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1582 1581 moves = []
1583 1582 for m, l in actions.items():
1584 1583 l.sort()
1585 1584
1586 1585 # 'cd' and 'dc' actions are treated like other merge conflicts
1587 1586 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1588 1587 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1589 1588 mergeactions.extend(actions[ACTION_MERGE])
1590 1589 for f, args, msg in mergeactions:
1591 1590 f1, f2, fa, move, anc = args
1592 1591 if f == '.hgsubstate': # merged internally
1593 1592 continue
1594 1593 if f1 is None:
1595 1594 fcl = filemerge.absentfilectx(wctx, fa)
1596 1595 else:
1597 1596 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1598 1597 fcl = wctx[f1]
1599 1598 if f2 is None:
1600 1599 fco = filemerge.absentfilectx(mctx, fa)
1601 1600 else:
1602 1601 fco = mctx[f2]
1603 1602 actx = repo[anc]
1604 1603 if fa in actx:
1605 1604 fca = actx[fa]
1606 1605 else:
1607 1606 # TODO: move to absentfilectx
1608 1607 fca = repo.filectx(f1, fileid=nullrev)
1609 1608 ms.add(fcl, fco, fca, f)
1610 1609 if f1 != f and move:
1611 1610 moves.append(f1)
1612 1611
1613 1612 # remove renamed files after safely stored
1614 1613 for f in moves:
1615 1614 if wctx[f].lexists():
1616 1615 repo.ui.debug("removing %s\n" % f)
1617 1616 wctx[f].audit()
1618 1617 wctx[f].remove()
1619 1618
1620 1619 numupdates = sum(len(l) for m, l in actions.items()
1621 1620 if m != ACTION_KEEP)
1622 1621 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1623 1622 total=numupdates)
1624 1623
1625 1624 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1626 1625 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1627 1626
1628 1627 # record path conflicts
1629 1628 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1630 1629 f1, fo = args
1631 1630 s = repo.ui.status
1632 1631 s(_("%s: path conflict - a file or link has the same name as a "
1633 1632 "directory\n") % f)
1634 1633 if fo == 'l':
1635 1634 s(_("the local file has been renamed to %s\n") % f1)
1636 1635 else:
1637 1636 s(_("the remote file has been renamed to %s\n") % f1)
1638 1637 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1639 1638 ms.addpath(f, f1, fo)
1640 1639 progress.increment(item=f)
1641 1640
1642 1641 # When merging in-memory, we can't support worker processes, so set the
1643 1642 # per-item cost at 0 in that case.
1644 1643 cost = 0 if wctx.isinmemory() else 0.001
1645 1644
1646 1645 # remove in parallel (must come before resolving path conflicts and getting)
1647 1646 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1648 1647 actions[ACTION_REMOVE])
1649 1648 for i, item in prog:
1650 1649 progress.increment(step=i, item=item)
1651 1650 removed = len(actions[ACTION_REMOVE])
1652 1651
1653 1652 # resolve path conflicts (must come before getting)
1654 1653 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1655 1654 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1656 1655 f0, = args
1657 1656 if wctx[f0].lexists():
1658 1657 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1659 1658 wctx[f].audit()
1660 1659 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1661 1660 wctx[f0].remove()
1662 1661 progress.increment(item=f)
1663 1662
1664 1663 # get in parallel.
1665 1664 threadsafe = repo.ui.configbool('experimental',
1666 1665 'worker.wdir-get-thread-safe')
1667 1666 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1668 1667 actions[ACTION_GET],
1669 1668 threadsafe=threadsafe)
1670 1669 for i, item in prog:
1671 1670 progress.increment(step=i, item=item)
1672 1671 updated = len(actions[ACTION_GET])
1673 1672
1674 1673 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1675 1674 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1676 1675
1677 1676 # forget (manifest only, just log it) (must come first)
1678 1677 for f, args, msg in actions[ACTION_FORGET]:
1679 1678 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1680 1679 progress.increment(item=f)
1681 1680
1682 1681 # re-add (manifest only, just log it)
1683 1682 for f, args, msg in actions[ACTION_ADD]:
1684 1683 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1685 1684 progress.increment(item=f)
1686 1685
1687 1686 # re-add/mark as modified (manifest only, just log it)
1688 1687 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1689 1688 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1690 1689 progress.increment(item=f)
1691 1690
1692 1691 # keep (noop, just log it)
1693 1692 for f, args, msg in actions[ACTION_KEEP]:
1694 1693 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1695 1694 # no progress
1696 1695
1697 1696 # directory rename, move local
1698 1697 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1699 1698 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1700 1699 progress.increment(item=f)
1701 1700 f0, flags = args
1702 1701 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1703 1702 wctx[f].audit()
1704 1703 wctx[f].write(wctx.filectx(f0).data(), flags)
1705 1704 wctx[f0].remove()
1706 1705 updated += 1
1707 1706
1708 1707 # local directory rename, get
1709 1708 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1710 1709 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1711 1710 progress.increment(item=f)
1712 1711 f0, flags = args
1713 1712 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1714 1713 wctx[f].write(mctx.filectx(f0).data(), flags)
1715 1714 updated += 1
1716 1715
1717 1716 # exec
1718 1717 for f, args, msg in actions[ACTION_EXEC]:
1719 1718 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1720 1719 progress.increment(item=f)
1721 1720 flags, = args
1722 1721 wctx[f].audit()
1723 1722 wctx[f].setflags('l' in flags, 'x' in flags)
1724 1723 updated += 1
1725 1724
1726 1725 # the ordering is important here -- ms.mergedriver will raise if the merge
1727 1726 # driver has changed, and we want to be able to bypass it when overwrite is
1728 1727 # True
1729 1728 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1730 1729
1731 1730 if usemergedriver:
1732 1731 if wctx.isinmemory():
1733 1732 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1734 1733 "support mergedriver")
1735 1734 ms.commit()
1736 1735 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1737 1736 # the driver might leave some files unresolved
1738 1737 unresolvedf = set(ms.unresolved())
1739 1738 if not proceed:
1740 1739 # XXX setting unresolved to at least 1 is a hack to make sure we
1741 1740 # error out
1742 1741 return updateresult(updated, merged, removed,
1743 1742 max(len(unresolvedf), 1))
1744 1743 newactions = []
1745 1744 for f, args, msg in mergeactions:
1746 1745 if f in unresolvedf:
1747 1746 newactions.append((f, args, msg))
1748 1747 mergeactions = newactions
1749 1748
1750 1749 try:
1751 1750 # premerge
1752 1751 tocomplete = []
1753 1752 for f, args, msg in mergeactions:
1754 1753 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1755 1754 progress.increment(item=f)
1756 1755 if f == '.hgsubstate': # subrepo states need updating
1757 1756 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1758 1757 overwrite, labels)
1759 1758 continue
1760 1759 wctx[f].audit()
1761 1760 complete, r = ms.preresolve(f, wctx)
1762 1761 if not complete:
1763 1762 numupdates += 1
1764 1763 tocomplete.append((f, args, msg))
1765 1764
1766 1765 # merge
1767 1766 for f, args, msg in tocomplete:
1768 1767 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1769 1768 progress.increment(item=f, total=numupdates)
1770 1769 ms.resolve(f, wctx)
1771 1770
1772 1771 finally:
1773 1772 ms.commit()
1774 1773
1775 1774 unresolved = ms.unresolvedcount()
1776 1775
1777 1776 if (usemergedriver and not unresolved
1778 1777 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1779 1778 if not driverconclude(repo, ms, wctx, labels=labels):
1780 1779 # XXX setting unresolved to at least 1 is a hack to make sure we
1781 1780 # error out
1782 1781 unresolved = max(unresolved, 1)
1783 1782
1784 1783 ms.commit()
1785 1784
1786 1785 msupdated, msmerged, msremoved = ms.counts()
1787 1786 updated += msupdated
1788 1787 merged += msmerged
1789 1788 removed += msremoved
1790 1789
1791 1790 extraactions = ms.actions()
1792 1791 if extraactions:
1793 1792 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1794 1793 for k, acts in extraactions.iteritems():
1795 1794 actions[k].extend(acts)
1796 1795 # Remove these files from actions[ACTION_MERGE] as well. This is
1797 1796 # important because in recordupdates, files in actions[ACTION_MERGE]
1798 1797 # are processed after files in other actions, and the merge driver
1799 1798 # might add files to those actions via extraactions above. This can
1800 1799 # lead to a file being recorded twice, with poor results. This is
1801 1800 # especially problematic for actions[ACTION_REMOVE] (currently only
1802 1801 # possible with the merge driver in the initial merge process;
1803 1802 # interrupted merges don't go through this flow).
1804 1803 #
1805 1804 # The real fix here is to have indexes by both file and action so
1806 1805 # that when the action for a file is changed it is automatically
1807 1806 # reflected in the other action lists. But that involves a more
1808 1807 # complex data structure, so this will do for now.
1809 1808 #
1810 1809 # We don't need to do the same operation for 'dc' and 'cd' because
1811 1810 # those lists aren't consulted again.
1812 1811 mfiles.difference_update(a[0] for a in acts)
1813 1812
1814 1813 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1815 1814 if a[0] in mfiles]
1816 1815
1817 1816 progress.complete()
1818 1817 return updateresult(updated, merged, removed, unresolved)
1819 1818
1820 1819 def recordupdates(repo, actions, branchmerge):
1821 1820 "record merge actions to the dirstate"
1822 1821 # remove (must come first)
1823 1822 for f, args, msg in actions.get(ACTION_REMOVE, []):
1824 1823 if branchmerge:
1825 1824 repo.dirstate.remove(f)
1826 1825 else:
1827 1826 repo.dirstate.drop(f)
1828 1827
1829 1828 # forget (must come first)
1830 1829 for f, args, msg in actions.get(ACTION_FORGET, []):
1831 1830 repo.dirstate.drop(f)
1832 1831
1833 1832 # resolve path conflicts
1834 1833 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1835 1834 f0, = args
1836 1835 origf0 = repo.dirstate.copied(f0) or f0
1837 1836 repo.dirstate.add(f)
1838 1837 repo.dirstate.copy(origf0, f)
1839 1838 if f0 == origf0:
1840 1839 repo.dirstate.remove(f0)
1841 1840 else:
1842 1841 repo.dirstate.drop(f0)
1843 1842
1844 1843 # re-add
1845 1844 for f, args, msg in actions.get(ACTION_ADD, []):
1846 1845 repo.dirstate.add(f)
1847 1846
1848 1847 # re-add/mark as modified
1849 1848 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1850 1849 if branchmerge:
1851 1850 repo.dirstate.normallookup(f)
1852 1851 else:
1853 1852 repo.dirstate.add(f)
1854 1853
1855 1854 # exec change
1856 1855 for f, args, msg in actions.get(ACTION_EXEC, []):
1857 1856 repo.dirstate.normallookup(f)
1858 1857
1859 1858 # keep
1860 1859 for f, args, msg in actions.get(ACTION_KEEP, []):
1861 1860 pass
1862 1861
1863 1862 # get
1864 1863 for f, args, msg in actions.get(ACTION_GET, []):
1865 1864 if branchmerge:
1866 1865 repo.dirstate.otherparent(f)
1867 1866 else:
1868 1867 repo.dirstate.normal(f)
1869 1868
1870 1869 # merge
1871 1870 for f, args, msg in actions.get(ACTION_MERGE, []):
1872 1871 f1, f2, fa, move, anc = args
1873 1872 if branchmerge:
1874 1873 # We've done a branch merge, mark this file as merged
1875 1874 # so that we properly record the merger later
1876 1875 repo.dirstate.merge(f)
1877 1876 if f1 != f2: # copy/rename
1878 1877 if move:
1879 1878 repo.dirstate.remove(f1)
1880 1879 if f1 != f:
1881 1880 repo.dirstate.copy(f1, f)
1882 1881 else:
1883 1882 repo.dirstate.copy(f2, f)
1884 1883 else:
1885 1884 # We've update-merged a locally modified file, so
1886 1885 # we set the dirstate to emulate a normal checkout
1887 1886 # of that file some time in the past. Thus our
1888 1887 # merge will appear as a normal local file
1889 1888 # modification.
1890 1889 if f2 == f: # file not locally copied/moved
1891 1890 repo.dirstate.normallookup(f)
1892 1891 if move:
1893 1892 repo.dirstate.drop(f1)
1894 1893
1895 1894 # directory rename, move local
1896 1895 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1897 1896 f0, flag = args
1898 1897 if branchmerge:
1899 1898 repo.dirstate.add(f)
1900 1899 repo.dirstate.remove(f0)
1901 1900 repo.dirstate.copy(f0, f)
1902 1901 else:
1903 1902 repo.dirstate.normal(f)
1904 1903 repo.dirstate.drop(f0)
1905 1904
1906 1905 # directory rename, get
1907 1906 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1908 1907 f0, flag = args
1909 1908 if branchmerge:
1910 1909 repo.dirstate.add(f)
1911 1910 repo.dirstate.copy(f0, f)
1912 1911 else:
1913 1912 repo.dirstate.normal(f)
1914 1913
1915 1914 def update(repo, node, branchmerge, force, ancestor=None,
1916 1915 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1917 1916 updatecheck=None, wc=None):
1918 1917 """
1919 1918 Perform a merge between the working directory and the given node
1920 1919
1921 1920 node = the node to update to
1922 1921 branchmerge = whether to merge between branches
1923 1922 force = whether to force branch merging or file overwriting
1924 1923 matcher = a matcher to filter file lists (dirstate not updated)
1925 1924 mergeancestor = whether it is merging with an ancestor. If true,
1926 1925 we should accept the incoming changes for any prompts that occur.
1927 1926 If false, merging with an ancestor (fast-forward) is only allowed
1928 1927 between different named branches. This flag is used by rebase extension
1929 1928 as a temporary fix and should be avoided in general.
1930 1929 labels = labels to use for base, local and other
1931 1930 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1932 1931 this is True, then 'force' should be True as well.
1933 1932
1934 1933 The table below shows all the behaviors of the update command given the
1935 1934 -c/--check and -C/--clean or no options, whether the working directory is
1936 1935 dirty, whether a revision is specified, and the relationship of the parent
1937 1936 rev to the target rev (linear or not). Match from top first. The -n
1938 1937 option doesn't exist on the command line, but represents the
1939 1938 experimental.updatecheck=noconflict option.
1940 1939
1941 1940 This logic is tested by test-update-branches.t.
1942 1941
1943 1942 -c -C -n -m dirty rev linear | result
1944 1943 y y * * * * * | (1)
1945 1944 y * y * * * * | (1)
1946 1945 y * * y * * * | (1)
1947 1946 * y y * * * * | (1)
1948 1947 * y * y * * * | (1)
1949 1948 * * y y * * * | (1)
1950 1949 * * * * * n n | x
1951 1950 * * * * n * * | ok
1952 1951 n n n n y * y | merge
1953 1952 n n n n y y n | (2)
1954 1953 n n n y y * * | merge
1955 1954 n n y n y * * | merge if no conflict
1956 1955 n y n n y * * | discard
1957 1956 y n n n y * * | (3)
1958 1957
1959 1958 x = can't happen
1960 1959 * = don't-care
1961 1960 1 = incompatible options (checked in commands.py)
1962 1961 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1963 1962 3 = abort: uncommitted changes (checked in commands.py)
1964 1963
1965 1964 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1966 1965 to repo[None] if None is passed.
1967 1966
1968 1967 Return the same tuple as applyupdates().
1969 1968 """
1970 1969 # Avoid cycle.
1971 1970 from . import sparse
1972 1971
1973 1972 # This function used to find the default destination if node was None, but
1974 1973 # that's now in destutil.py.
1975 1974 assert node is not None
1976 1975 if not branchmerge and not force:
1977 1976 # TODO: remove the default once all callers that pass branchmerge=False
1978 1977 # and force=False pass a value for updatecheck. We may want to allow
1979 1978 # updatecheck='abort' to better suppport some of these callers.
1980 1979 if updatecheck is None:
1981 1980 updatecheck = 'linear'
1982 1981 assert updatecheck in ('none', 'linear', 'noconflict')
1983 1982 # If we're doing a partial update, we need to skip updating
1984 1983 # the dirstate, so make a note of any partial-ness to the
1985 1984 # update here.
1986 1985 if matcher is None or matcher.always():
1987 1986 partial = False
1988 1987 else:
1989 1988 partial = True
1990 1989 with repo.wlock():
1991 1990 if wc is None:
1992 1991 wc = repo[None]
1993 1992 pl = wc.parents()
1994 1993 p1 = pl[0]
1995 1994 pas = [None]
1996 1995 if ancestor is not None:
1997 1996 pas = [repo[ancestor]]
1998 1997
1999 1998 overwrite = force and not branchmerge
2000 1999
2001 2000 p2 = repo[node]
2002 2001 if pas[0] is None:
2003 2002 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
2004 2003 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
2005 2004 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
2006 2005 else:
2007 2006 pas = [p1.ancestor(p2, warn=branchmerge)]
2008 2007
2009 2008 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
2010 2009
2011 2010 ### check phase
2012 2011 if not overwrite:
2013 2012 if len(pl) > 1:
2014 2013 raise error.Abort(_("outstanding uncommitted merge"))
2015 2014 ms = mergestate.read(repo)
2016 2015 if list(ms.unresolved()):
2017 2016 raise error.Abort(_("outstanding merge conflicts"))
2018 2017 if branchmerge:
2019 2018 if pas == [p2]:
2020 2019 raise error.Abort(_("merging with a working directory ancestor"
2021 2020 " has no effect"))
2022 2021 elif pas == [p1]:
2023 2022 if not mergeancestor and wc.branch() == p2.branch():
2024 2023 raise error.Abort(_("nothing to merge"),
2025 2024 hint=_("use 'hg update' "
2026 2025 "or check 'hg heads'"))
2027 2026 if not force and (wc.files() or wc.deleted()):
2028 2027 raise error.Abort(_("uncommitted changes"),
2029 2028 hint=_("use 'hg status' to list changes"))
2030 2029 if not wc.isinmemory():
2031 2030 for s in sorted(wc.substate):
2032 2031 wc.sub(s).bailifchanged()
2033 2032
2034 2033 elif not overwrite:
2035 2034 if p1 == p2: # no-op update
2036 2035 # call the hooks and exit early
2037 2036 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2038 2037 repo.hook('update', parent1=xp2, parent2='', error=0)
2039 2038 return updateresult(0, 0, 0, 0)
2040 2039
2041 2040 if (updatecheck == 'linear' and
2042 2041 pas not in ([p1], [p2])): # nonlinear
2043 2042 dirty = wc.dirty(missing=True)
2044 2043 if dirty:
2045 2044 # Branching is a bit strange to ensure we do the minimal
2046 2045 # amount of call to obsutil.foreground.
2047 2046 foreground = obsutil.foreground(repo, [p1.node()])
2048 2047 # note: the <node> variable contains a random identifier
2049 2048 if repo[node].node() in foreground:
2050 2049 pass # allow updating to successors
2051 2050 else:
2052 2051 msg = _("uncommitted changes")
2053 2052 hint = _("commit or update --clean to discard changes")
2054 2053 raise error.UpdateAbort(msg, hint=hint)
2055 2054 else:
2056 2055 # Allow jumping branches if clean and specific rev given
2057 2056 pass
2058 2057
2059 2058 if overwrite:
2060 2059 pas = [wc]
2061 2060 elif not branchmerge:
2062 2061 pas = [p1]
2063 2062
2064 2063 # deprecated config: merge.followcopies
2065 2064 followcopies = repo.ui.configbool('merge', 'followcopies')
2066 2065 if overwrite:
2067 2066 followcopies = False
2068 2067 elif not pas[0]:
2069 2068 followcopies = False
2070 2069 if not branchmerge and not wc.dirty(missing=True):
2071 2070 followcopies = False
2072 2071
2073 2072 ### calculate phase
2074 2073 actionbyfile, diverge, renamedelete = calculateupdates(
2075 2074 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2076 2075 followcopies, matcher=matcher, mergeforce=mergeforce)
2077 2076
2078 2077 if updatecheck == 'noconflict':
2079 2078 for f, (m, args, msg) in actionbyfile.iteritems():
2080 2079 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2081 2080 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2082 2081 msg = _("conflicting changes")
2083 2082 hint = _("commit or update --clean to discard changes")
2084 2083 raise error.Abort(msg, hint=hint)
2085 2084
2086 2085 # Prompt and create actions. Most of this is in the resolve phase
2087 2086 # already, but we can't handle .hgsubstate in filemerge or
2088 2087 # subrepoutil.submerge yet so we have to keep prompting for it.
2089 2088 if '.hgsubstate' in actionbyfile:
2090 2089 f = '.hgsubstate'
2091 2090 m, args, msg = actionbyfile[f]
2092 2091 prompts = filemerge.partextras(labels)
2093 2092 prompts['f'] = f
2094 2093 if m == ACTION_CHANGED_DELETED:
2095 2094 if repo.ui.promptchoice(
2096 2095 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2097 2096 "use (c)hanged version or (d)elete?"
2098 2097 "$$ &Changed $$ &Delete") % prompts, 0):
2099 2098 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2100 2099 elif f in p1:
2101 2100 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2102 2101 else:
2103 2102 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2104 2103 elif m == ACTION_DELETED_CHANGED:
2105 2104 f1, f2, fa, move, anc = args
2106 2105 flags = p2[f2].flags()
2107 2106 if repo.ui.promptchoice(
2108 2107 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2109 2108 "use (c)hanged version or leave (d)eleted?"
2110 2109 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2111 2110 actionbyfile[f] = (ACTION_GET, (flags, False),
2112 2111 'prompt recreating')
2113 2112 else:
2114 2113 del actionbyfile[f]
2115 2114
2116 2115 # Convert to dictionary-of-lists format
2117 2116 actions = emptyactions()
2118 2117 for f, (m, args, msg) in actionbyfile.iteritems():
2119 2118 if m not in actions:
2120 2119 actions[m] = []
2121 2120 actions[m].append((f, args, msg))
2122 2121
2123 2122 if not util.fscasesensitive(repo.path):
2124 2123 # check collision between files only in p2 for clean update
2125 2124 if (not branchmerge and
2126 2125 (force or not wc.dirty(missing=True, branch=False))):
2127 2126 _checkcollision(repo, p2.manifest(), None)
2128 2127 else:
2129 2128 _checkcollision(repo, wc.manifest(), actions)
2130 2129
2131 2130 # divergent renames
2132 2131 for f, fl in sorted(diverge.iteritems()):
2133 2132 repo.ui.warn(_("note: possible conflict - %s was renamed "
2134 2133 "multiple times to:\n") % f)
2135 2134 for nf in fl:
2136 2135 repo.ui.warn(" %s\n" % nf)
2137 2136
2138 2137 # rename and delete
2139 2138 for f, fl in sorted(renamedelete.iteritems()):
2140 2139 repo.ui.warn(_("note: possible conflict - %s was deleted "
2141 2140 "and renamed to:\n") % f)
2142 2141 for nf in fl:
2143 2142 repo.ui.warn(" %s\n" % nf)
2144 2143
2145 2144 ### apply phase
2146 2145 if not branchmerge: # just jump to the new rev
2147 2146 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2148 2147 if not partial and not wc.isinmemory():
2149 2148 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2150 2149 # note that we're in the middle of an update
2151 2150 repo.vfs.write('updatestate', p2.hex())
2152 2151
2153 2152 # Advertise fsmonitor when its presence could be useful.
2154 2153 #
2155 2154 # We only advertise when performing an update from an empty working
2156 2155 # directory. This typically only occurs during initial clone.
2157 2156 #
2158 2157 # We give users a mechanism to disable the warning in case it is
2159 2158 # annoying.
2160 2159 #
2161 2160 # We only allow on Linux and MacOS because that's where fsmonitor is
2162 2161 # considered stable.
2163 2162 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2164 2163 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2165 2164 'warn_update_file_count')
2166 2165 try:
2167 2166 # avoid cycle: extensions -> cmdutil -> merge
2168 2167 from . import extensions
2169 2168 extensions.find('fsmonitor')
2170 2169 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2171 2170 # We intentionally don't look at whether fsmonitor has disabled
2172 2171 # itself because a) fsmonitor may have already printed a warning
2173 2172 # b) we only care about the config state here.
2174 2173 except KeyError:
2175 2174 fsmonitorenabled = False
2176 2175
2177 2176 if (fsmonitorwarning
2178 2177 and not fsmonitorenabled
2179 2178 and p1.node() == nullid
2180 2179 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2181 2180 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2182 2181 repo.ui.warn(
2183 2182 _('(warning: large working directory being used without '
2184 2183 'fsmonitor enabled; enable fsmonitor to improve performance; '
2185 2184 'see "hg help -e fsmonitor")\n'))
2186 2185
2187 2186 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
2188 2187
2189 2188 if not partial and not wc.isinmemory():
2190 2189 with repo.dirstate.parentchange():
2191 2190 repo.setparents(fp1, fp2)
2192 2191 recordupdates(repo, actions, branchmerge)
2193 2192 # update completed, clear state
2194 2193 util.unlink(repo.vfs.join('updatestate'))
2195 2194
2196 2195 if not branchmerge:
2197 2196 repo.dirstate.setbranch(p2.branch())
2198 2197
2199 2198 # If we're updating to a location, clean up any stale temporary includes
2200 2199 # (ex: this happens during hg rebase --abort).
2201 2200 if not branchmerge:
2202 2201 sparse.prunetemporaryincludes(repo)
2203 2202
2204 2203 if not partial:
2205 2204 repo.hook('update', parent1=xp1, parent2=xp2,
2206 2205 error=stats.unresolvedcount)
2207 2206 return stats
2208 2207
2209 2208 def graft(repo, ctx, pctx, labels, keepparent=False,
2210 2209 keepconflictparent=False):
2211 2210 """Do a graft-like merge.
2212 2211
2213 2212 This is a merge where the merge ancestor is chosen such that one
2214 2213 or more changesets are grafted onto the current changeset. In
2215 2214 addition to the merge, this fixes up the dirstate to include only
2216 2215 a single parent (if keepparent is False) and tries to duplicate any
2217 2216 renames/copies appropriately.
2218 2217
2219 2218 ctx - changeset to rebase
2220 2219 pctx - merge base, usually ctx.p1()
2221 2220 labels - merge labels eg ['local', 'graft']
2222 2221 keepparent - keep second parent if any
2223 2222 keepparent - if unresolved, keep parent used for the merge
2224 2223
2225 2224 """
2226 2225 # If we're grafting a descendant onto an ancestor, be sure to pass
2227 2226 # mergeancestor=True to update. This does two things: 1) allows the merge if
2228 2227 # the destination is the same as the parent of the ctx (so we can use graft
2229 2228 # to copy commits), and 2) informs update that the incoming changes are
2230 2229 # newer than the destination so it doesn't prompt about "remote changed foo
2231 2230 # which local deleted".
2232 2231 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2233 2232
2234 2233 stats = update(repo, ctx.node(), True, True, pctx.node(),
2235 2234 mergeancestor=mergeancestor, labels=labels)
2236 2235
2237 2236
2238 2237 if keepconflictparent and stats.unresolvedcount:
2239 2238 pother = ctx.node()
2240 2239 else:
2241 2240 pother = nullid
2242 2241 parents = ctx.parents()
2243 2242 if keepparent and len(parents) == 2 and pctx in parents:
2244 2243 parents.remove(pctx)
2245 2244 pother = parents[0].node()
2246 2245
2247 2246 with repo.dirstate.parentchange():
2248 2247 repo.setparents(repo['.'].node(), pother)
2249 2248 repo.dirstate.write(repo.currenttransaction())
2250 2249 # fix up dirstate for copies and renames
2251 2250 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2252 2251 return stats
2253 2252
2254 2253 def purge(repo, matcher, ignored=False, removeemptydirs=True,
2255 2254 removefiles=True, abortonerror=False, noop=False):
2256 2255 """Purge the working directory of untracked files.
2257 2256
2258 2257 ``matcher`` is a matcher configured to scan the working directory -
2259 2258 potentially a subset.
2260 2259
2261 2260 ``ignored`` controls whether ignored files should also be purged.
2262 2261
2263 2262 ``removeemptydirs`` controls whether empty directories should be removed.
2264 2263
2265 2264 ``removefiles`` controls whether files are removed.
2266 2265
2267 2266 ``abortonerror`` causes an exception to be raised if an error occurs
2268 2267 deleting a file or directory.
2269 2268
2270 2269 ``noop`` controls whether to actually remove files. If not defined, actions
2271 2270 will be taken.
2272 2271
2273 2272 Returns an iterable of relative paths in the working directory that were
2274 2273 or would be removed.
2275 2274 """
2276 2275
2277 2276 def remove(removefn, path):
2278 2277 try:
2279 2278 removefn(path)
2280 2279 except OSError:
2281 2280 m = _('%s cannot be removed') % path
2282 2281 if abortonerror:
2283 2282 raise error.Abort(m)
2284 2283 else:
2285 2284 repo.ui.warn(_('warning: %s\n') % m)
2286 2285
2287 2286 # There's no API to copy a matcher. So mutate the passed matcher and
2288 2287 # restore it when we're done.
2289 2288 oldexplicitdir = matcher.explicitdir
2290 2289 oldtraversedir = matcher.traversedir
2291 2290
2292 2291 res = []
2293 2292
2294 2293 try:
2295 2294 if removeemptydirs:
2296 2295 directories = []
2297 2296 matcher.explicitdir = matcher.traversedir = directories.append
2298 2297
2299 2298 status = repo.status(match=matcher, ignored=ignored, unknown=True)
2300 2299
2301 2300 if removefiles:
2302 2301 for f in sorted(status.unknown + status.ignored):
2303 2302 if not noop:
2304 2303 repo.ui.note(_('removing file %s\n') % f)
2305 2304 remove(repo.wvfs.unlink, f)
2306 2305 res.append(f)
2307 2306
2308 2307 if removeemptydirs:
2309 2308 for f in sorted(directories, reverse=True):
2310 2309 if matcher(f) and not repo.wvfs.listdir(f):
2311 2310 if not noop:
2312 2311 repo.ui.note(_('removing directory %s\n') % f)
2313 2312 remove(repo.wvfs.rmdir, f)
2314 2313 res.append(f)
2315 2314
2316 2315 return res
2317 2316
2318 2317 finally:
2319 2318 matcher.explicitdir = oldexplicitdir
2320 2319 matcher.traversedir = oldtraversedir
General Comments 0
You need to be logged in to leave comments. Login now