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