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