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