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