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