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