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