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