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