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