##// END OF EJS Templates
rebase: change and standarize template for rebase's one-line summary...
Martin von Zweigbergk -
r46356:f90a5c21 default
parent child Browse files
Show More

The requested changes are too big and content was truncated. Show full diff

@@ -1,2269 +1,2264 b''
1 1 # rebase.py - rebasing feature for mercurial
2 2 #
3 3 # Copyright 2008 Stefano Tortarolo <stefano.tortarolo at gmail dot 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 '''command to move sets of revisions to a different ancestor
9 9
10 10 This extension lets you rebase changesets in an existing Mercurial
11 11 repository.
12 12
13 13 For more information:
14 14 https://mercurial-scm.org/wiki/RebaseExtension
15 15 '''
16 16
17 17 from __future__ import absolute_import
18 18
19 19 import errno
20 20 import os
21 21
22 22 from mercurial.i18n import _
23 23 from mercurial.node import (
24 24 nullrev,
25 25 short,
26 26 )
27 27 from mercurial.pycompat import open
28 28 from mercurial import (
29 29 bookmarks,
30 30 cmdutil,
31 31 commands,
32 32 copies,
33 33 destutil,
34 34 dirstateguard,
35 35 error,
36 36 extensions,
37 37 merge as mergemod,
38 38 mergestate as mergestatemod,
39 39 mergeutil,
40 40 node as nodemod,
41 41 obsolete,
42 42 obsutil,
43 43 patch,
44 44 phases,
45 45 pycompat,
46 46 registrar,
47 47 repair,
48 48 revset,
49 49 revsetlang,
50 50 rewriteutil,
51 51 scmutil,
52 52 smartset,
53 53 state as statemod,
54 54 util,
55 55 )
56 56
57 57 # The following constants are used throughout the rebase module. The ordering of
58 58 # their values must be maintained.
59 59
60 60 # Indicates that a revision needs to be rebased
61 61 revtodo = -1
62 62 revtodostr = b'-1'
63 63
64 64 # legacy revstates no longer needed in current code
65 65 # -2: nullmerge, -3: revignored, -4: revprecursor, -5: revpruned
66 66 legacystates = {b'-2', b'-3', b'-4', b'-5'}
67 67
68 68 cmdtable = {}
69 69 command = registrar.command(cmdtable)
70 70 # Note for extension authors: ONLY specify testedwith = 'ships-with-hg-core' for
71 71 # extensions which SHIP WITH MERCURIAL. Non-mainline extensions should
72 72 # be specifying the version(s) of Mercurial they are tested with, or
73 73 # leave the attribute unspecified.
74 74 testedwith = b'ships-with-hg-core'
75 75
76 76
77 77 def _nothingtorebase():
78 78 return 1
79 79
80 80
81 81 def _savegraft(ctx, extra):
82 82 s = ctx.extra().get(b'source', None)
83 83 if s is not None:
84 84 extra[b'source'] = s
85 85 s = ctx.extra().get(b'intermediate-source', None)
86 86 if s is not None:
87 87 extra[b'intermediate-source'] = s
88 88
89 89
90 90 def _savebranch(ctx, extra):
91 91 extra[b'branch'] = ctx.branch()
92 92
93 93
94 94 def _destrebase(repo, sourceset, destspace=None):
95 95 """small wrapper around destmerge to pass the right extra args
96 96
97 97 Please wrap destutil.destmerge instead."""
98 98 return destutil.destmerge(
99 99 repo,
100 100 action=b'rebase',
101 101 sourceset=sourceset,
102 102 onheadcheck=False,
103 103 destspace=destspace,
104 104 )
105 105
106 106
107 107 revsetpredicate = registrar.revsetpredicate()
108 108
109 109
110 110 @revsetpredicate(b'_destrebase')
111 111 def _revsetdestrebase(repo, subset, x):
112 112 # ``_rebasedefaultdest()``
113 113
114 114 # default destination for rebase.
115 115 # # XXX: Currently private because I expect the signature to change.
116 116 # # XXX: - bailing out in case of ambiguity vs returning all data.
117 117 # i18n: "_rebasedefaultdest" is a keyword
118 118 sourceset = None
119 119 if x is not None:
120 120 sourceset = revset.getset(repo, smartset.fullreposet(repo), x)
121 121 return subset & smartset.baseset([_destrebase(repo, sourceset)])
122 122
123 123
124 124 @revsetpredicate(b'_destautoorphanrebase')
125 125 def _revsetdestautoorphanrebase(repo, subset, x):
126 126 # ``_destautoorphanrebase()``
127 127
128 128 # automatic rebase destination for a single orphan revision.
129 129 unfi = repo.unfiltered()
130 130 obsoleted = unfi.revs(b'obsolete()')
131 131
132 132 src = revset.getset(repo, subset, x).first()
133 133
134 134 # Empty src or already obsoleted - Do not return a destination
135 135 if not src or src in obsoleted:
136 136 return smartset.baseset()
137 137 dests = destutil.orphanpossibledestination(repo, src)
138 138 if len(dests) > 1:
139 139 raise error.Abort(
140 140 _(b"ambiguous automatic rebase: %r could end up on any of %r")
141 141 % (src, dests)
142 142 )
143 143 # We have zero or one destination, so we can just return here.
144 144 return smartset.baseset(dests)
145 145
146 146
147 147 def _ctxdesc(ctx):
148 148 """short description for a context"""
149 labels_spec = b'join(filter(namespaces % "{ifeq(namespace, "branches", "", join(names, " "))}"), " ")'
150 spec = b'{rev}:{node|short} "{desc|firstline}"{if(%s, " ({%s})")}' % (
151 labels_spec,
152 labels_spec,
153 )
154 149 return cmdutil.format_changeset_summary(
155 ctx.repo().ui, ctx, command=b'rebase', default_spec=spec
150 ctx.repo().ui, ctx, command=b'rebase'
156 151 )
157 152
158 153
159 154 class rebaseruntime(object):
160 155 """This class is a container for rebase runtime state"""
161 156
162 157 def __init__(self, repo, ui, inmemory=False, dryrun=False, opts=None):
163 158 if opts is None:
164 159 opts = {}
165 160
166 161 # prepared: whether we have rebasestate prepared or not. Currently it
167 162 # decides whether "self.repo" is unfiltered or not.
168 163 # The rebasestate has explicit hash to hash instructions not depending
169 164 # on visibility. If rebasestate exists (in-memory or on-disk), use
170 165 # unfiltered repo to avoid visibility issues.
171 166 # Before knowing rebasestate (i.e. when starting a new rebase (not
172 167 # --continue or --abort)), the original repo should be used so
173 168 # visibility-dependent revsets are correct.
174 169 self.prepared = False
175 170 self.resume = False
176 171 self._repo = repo
177 172
178 173 self.ui = ui
179 174 self.opts = opts
180 175 self.originalwd = None
181 176 self.external = nullrev
182 177 # Mapping between the old revision id and either what is the new rebased
183 178 # revision or what needs to be done with the old revision. The state
184 179 # dict will be what contains most of the rebase progress state.
185 180 self.state = {}
186 181 self.activebookmark = None
187 182 self.destmap = {}
188 183 self.skipped = set()
189 184
190 185 self.collapsef = opts.get(b'collapse', False)
191 186 self.collapsemsg = cmdutil.logmessage(ui, opts)
192 187 self.date = opts.get(b'date', None)
193 188
194 189 e = opts.get(b'extrafn') # internal, used by e.g. hgsubversion
195 190 self.extrafns = [_savegraft]
196 191 if e:
197 192 self.extrafns = [e]
198 193
199 194 self.backupf = ui.configbool(b'rewrite', b'backup-bundle')
200 195 self.keepf = opts.get(b'keep', False)
201 196 self.keepbranchesf = opts.get(b'keepbranches', False)
202 197 self.skipemptysuccessorf = rewriteutil.skip_empty_successor(
203 198 repo.ui, b'rebase'
204 199 )
205 200 self.obsoletenotrebased = {}
206 201 self.obsoletewithoutsuccessorindestination = set()
207 202 self.inmemory = inmemory
208 203 self.dryrun = dryrun
209 204 self.stateobj = statemod.cmdstate(repo, b'rebasestate')
210 205
211 206 @property
212 207 def repo(self):
213 208 if self.prepared:
214 209 return self._repo.unfiltered()
215 210 else:
216 211 return self._repo
217 212
218 213 def storestatus(self, tr=None):
219 214 """Store the current status to allow recovery"""
220 215 if tr:
221 216 tr.addfilegenerator(
222 217 b'rebasestate',
223 218 (b'rebasestate',),
224 219 self._writestatus,
225 220 location=b'plain',
226 221 )
227 222 else:
228 223 with self.repo.vfs(b"rebasestate", b"w") as f:
229 224 self._writestatus(f)
230 225
231 226 def _writestatus(self, f):
232 227 repo = self.repo
233 228 assert repo.filtername is None
234 229 f.write(repo[self.originalwd].hex() + b'\n')
235 230 # was "dest". we now write dest per src root below.
236 231 f.write(b'\n')
237 232 f.write(repo[self.external].hex() + b'\n')
238 233 f.write(b'%d\n' % int(self.collapsef))
239 234 f.write(b'%d\n' % int(self.keepf))
240 235 f.write(b'%d\n' % int(self.keepbranchesf))
241 236 f.write(b'%s\n' % (self.activebookmark or b''))
242 237 destmap = self.destmap
243 238 for d, v in pycompat.iteritems(self.state):
244 239 oldrev = repo[d].hex()
245 240 if v >= 0:
246 241 newrev = repo[v].hex()
247 242 else:
248 243 newrev = b"%d" % v
249 244 destnode = repo[destmap[d]].hex()
250 245 f.write(b"%s:%s:%s\n" % (oldrev, newrev, destnode))
251 246 repo.ui.debug(b'rebase status stored\n')
252 247
253 248 def restorestatus(self):
254 249 """Restore a previously stored status"""
255 250 if not self.stateobj.exists():
256 251 cmdutil.wrongtooltocontinue(self.repo, _(b'rebase'))
257 252
258 253 data = self._read()
259 254 self.repo.ui.debug(b'rebase status resumed\n')
260 255
261 256 self.originalwd = data[b'originalwd']
262 257 self.destmap = data[b'destmap']
263 258 self.state = data[b'state']
264 259 self.skipped = data[b'skipped']
265 260 self.collapsef = data[b'collapse']
266 261 self.keepf = data[b'keep']
267 262 self.keepbranchesf = data[b'keepbranches']
268 263 self.external = data[b'external']
269 264 self.activebookmark = data[b'activebookmark']
270 265
271 266 def _read(self):
272 267 self.prepared = True
273 268 repo = self.repo
274 269 assert repo.filtername is None
275 270 data = {
276 271 b'keepbranches': None,
277 272 b'collapse': None,
278 273 b'activebookmark': None,
279 274 b'external': nullrev,
280 275 b'keep': None,
281 276 b'originalwd': None,
282 277 }
283 278 legacydest = None
284 279 state = {}
285 280 destmap = {}
286 281
287 282 if True:
288 283 f = repo.vfs(b"rebasestate")
289 284 for i, l in enumerate(f.read().splitlines()):
290 285 if i == 0:
291 286 data[b'originalwd'] = repo[l].rev()
292 287 elif i == 1:
293 288 # this line should be empty in newer version. but legacy
294 289 # clients may still use it
295 290 if l:
296 291 legacydest = repo[l].rev()
297 292 elif i == 2:
298 293 data[b'external'] = repo[l].rev()
299 294 elif i == 3:
300 295 data[b'collapse'] = bool(int(l))
301 296 elif i == 4:
302 297 data[b'keep'] = bool(int(l))
303 298 elif i == 5:
304 299 data[b'keepbranches'] = bool(int(l))
305 300 elif i == 6 and not (len(l) == 81 and b':' in l):
306 301 # line 6 is a recent addition, so for backwards
307 302 # compatibility check that the line doesn't look like the
308 303 # oldrev:newrev lines
309 304 data[b'activebookmark'] = l
310 305 else:
311 306 args = l.split(b':')
312 307 oldrev = repo[args[0]].rev()
313 308 newrev = args[1]
314 309 if newrev in legacystates:
315 310 continue
316 311 if len(args) > 2:
317 312 destrev = repo[args[2]].rev()
318 313 else:
319 314 destrev = legacydest
320 315 destmap[oldrev] = destrev
321 316 if newrev == revtodostr:
322 317 state[oldrev] = revtodo
323 318 # Legacy compat special case
324 319 else:
325 320 state[oldrev] = repo[newrev].rev()
326 321
327 322 if data[b'keepbranches'] is None:
328 323 raise error.Abort(_(b'.hg/rebasestate is incomplete'))
329 324
330 325 data[b'destmap'] = destmap
331 326 data[b'state'] = state
332 327 skipped = set()
333 328 # recompute the set of skipped revs
334 329 if not data[b'collapse']:
335 330 seen = set(destmap.values())
336 331 for old, new in sorted(state.items()):
337 332 if new != revtodo and new in seen:
338 333 skipped.add(old)
339 334 seen.add(new)
340 335 data[b'skipped'] = skipped
341 336 repo.ui.debug(
342 337 b'computed skipped revs: %s\n'
343 338 % (b' '.join(b'%d' % r for r in sorted(skipped)) or b'')
344 339 )
345 340
346 341 return data
347 342
348 343 def _handleskippingobsolete(self, obsoleterevs, destmap):
349 344 """Compute structures necessary for skipping obsolete revisions
350 345
351 346 obsoleterevs: iterable of all obsolete revisions in rebaseset
352 347 destmap: {srcrev: destrev} destination revisions
353 348 """
354 349 self.obsoletenotrebased = {}
355 350 if not self.ui.configbool(b'experimental', b'rebaseskipobsolete'):
356 351 return
357 352 obsoleteset = set(obsoleterevs)
358 353 (
359 354 self.obsoletenotrebased,
360 355 self.obsoletewithoutsuccessorindestination,
361 356 obsoleteextinctsuccessors,
362 357 ) = _computeobsoletenotrebased(self.repo, obsoleteset, destmap)
363 358 skippedset = set(self.obsoletenotrebased)
364 359 skippedset.update(self.obsoletewithoutsuccessorindestination)
365 360 skippedset.update(obsoleteextinctsuccessors)
366 361 _checkobsrebase(self.repo, self.ui, obsoleteset, skippedset)
367 362
368 363 def _prepareabortorcontinue(
369 364 self, isabort, backup=True, suppwarns=False, dryrun=False, confirm=False
370 365 ):
371 366 self.resume = True
372 367 try:
373 368 self.restorestatus()
374 369 self.collapsemsg = restorecollapsemsg(self.repo, isabort)
375 370 except error.RepoLookupError:
376 371 if isabort:
377 372 clearstatus(self.repo)
378 373 clearcollapsemsg(self.repo)
379 374 self.repo.ui.warn(
380 375 _(
381 376 b'rebase aborted (no revision is removed,'
382 377 b' only broken state is cleared)\n'
383 378 )
384 379 )
385 380 return 0
386 381 else:
387 382 msg = _(b'cannot continue inconsistent rebase')
388 383 hint = _(b'use "hg rebase --abort" to clear broken state')
389 384 raise error.Abort(msg, hint=hint)
390 385
391 386 if isabort:
392 387 backup = backup and self.backupf
393 388 return self._abort(
394 389 backup=backup,
395 390 suppwarns=suppwarns,
396 391 dryrun=dryrun,
397 392 confirm=confirm,
398 393 )
399 394
400 395 def _preparenewrebase(self, destmap):
401 396 if not destmap:
402 397 return _nothingtorebase()
403 398
404 399 rebaseset = destmap.keys()
405 400 if not self.keepf:
406 401 try:
407 402 rewriteutil.precheck(self.repo, rebaseset, action=b'rebase')
408 403 except error.Abort as e:
409 404 if e.hint is None:
410 405 e.hint = _(b'use --keep to keep original changesets')
411 406 raise e
412 407
413 408 result = buildstate(self.repo, destmap, self.collapsef)
414 409
415 410 if not result:
416 411 # Empty state built, nothing to rebase
417 412 self.ui.status(_(b'nothing to rebase\n'))
418 413 return _nothingtorebase()
419 414
420 415 (self.originalwd, self.destmap, self.state) = result
421 416 if self.collapsef:
422 417 dests = set(self.destmap.values())
423 418 if len(dests) != 1:
424 419 raise error.Abort(
425 420 _(b'--collapse does not work with multiple destinations')
426 421 )
427 422 destrev = next(iter(dests))
428 423 destancestors = self.repo.changelog.ancestors(
429 424 [destrev], inclusive=True
430 425 )
431 426 self.external = externalparent(self.repo, self.state, destancestors)
432 427
433 428 for destrev in sorted(set(destmap.values())):
434 429 dest = self.repo[destrev]
435 430 if dest.closesbranch() and not self.keepbranchesf:
436 431 self.ui.status(_(b'reopening closed branch head %s\n') % dest)
437 432
438 433 self.prepared = True
439 434
440 435 def _assignworkingcopy(self):
441 436 if self.inmemory:
442 437 from mercurial.context import overlayworkingctx
443 438
444 439 self.wctx = overlayworkingctx(self.repo)
445 440 self.repo.ui.debug(b"rebasing in memory\n")
446 441 else:
447 442 self.wctx = self.repo[None]
448 443 self.repo.ui.debug(b"rebasing on disk\n")
449 444 self.repo.ui.log(
450 445 b"rebase",
451 446 b"using in-memory rebase: %r\n",
452 447 self.inmemory,
453 448 rebase_imm_used=self.inmemory,
454 449 )
455 450
456 451 def _performrebase(self, tr):
457 452 self._assignworkingcopy()
458 453 repo, ui = self.repo, self.ui
459 454 if self.keepbranchesf:
460 455 # insert _savebranch at the start of extrafns so if
461 456 # there's a user-provided extrafn it can clobber branch if
462 457 # desired
463 458 self.extrafns.insert(0, _savebranch)
464 459 if self.collapsef:
465 460 branches = set()
466 461 for rev in self.state:
467 462 branches.add(repo[rev].branch())
468 463 if len(branches) > 1:
469 464 raise error.Abort(
470 465 _(b'cannot collapse multiple named branches')
471 466 )
472 467
473 468 # Calculate self.obsoletenotrebased
474 469 obsrevs = _filterobsoleterevs(self.repo, self.state)
475 470 self._handleskippingobsolete(obsrevs, self.destmap)
476 471
477 472 # Keep track of the active bookmarks in order to reset them later
478 473 self.activebookmark = self.activebookmark or repo._activebookmark
479 474 if self.activebookmark:
480 475 bookmarks.deactivate(repo)
481 476
482 477 # Store the state before we begin so users can run 'hg rebase --abort'
483 478 # if we fail before the transaction closes.
484 479 self.storestatus()
485 480 if tr:
486 481 # When using single transaction, store state when transaction
487 482 # commits.
488 483 self.storestatus(tr)
489 484
490 485 cands = [k for k, v in pycompat.iteritems(self.state) if v == revtodo]
491 486 p = repo.ui.makeprogress(
492 487 _(b"rebasing"), unit=_(b'changesets'), total=len(cands)
493 488 )
494 489
495 490 def progress(ctx):
496 491 p.increment(item=(b"%d:%s" % (ctx.rev(), ctx)))
497 492
498 493 allowdivergence = self.ui.configbool(
499 494 b'experimental', b'evolution.allowdivergence'
500 495 )
501 496 for subset in sortsource(self.destmap):
502 497 sortedrevs = self.repo.revs(b'sort(%ld, -topo)', subset)
503 498 if not allowdivergence:
504 499 sortedrevs -= self.repo.revs(
505 500 b'descendants(%ld) and not %ld',
506 501 self.obsoletewithoutsuccessorindestination,
507 502 self.obsoletewithoutsuccessorindestination,
508 503 )
509 504 for rev in sortedrevs:
510 505 self._rebasenode(tr, rev, allowdivergence, progress)
511 506 p.complete()
512 507 ui.note(_(b'rebase merging completed\n'))
513 508
514 509 def _concludenode(self, rev, editor, commitmsg=None):
515 510 '''Commit the wd changes with parents p1 and p2.
516 511
517 512 Reuse commit info from rev but also store useful information in extra.
518 513 Return node of committed revision.'''
519 514 repo = self.repo
520 515 ctx = repo[rev]
521 516 if commitmsg is None:
522 517 commitmsg = ctx.description()
523 518 date = self.date
524 519 if date is None:
525 520 date = ctx.date()
526 521 extra = {b'rebase_source': ctx.hex()}
527 522 for c in self.extrafns:
528 523 c(ctx, extra)
529 524 destphase = max(ctx.phase(), phases.draft)
530 525 overrides = {
531 526 (b'phases', b'new-commit'): destphase,
532 527 (b'ui', b'allowemptycommit'): not self.skipemptysuccessorf,
533 528 }
534 529 with repo.ui.configoverride(overrides, b'rebase'):
535 530 if self.inmemory:
536 531 newnode = commitmemorynode(
537 532 repo,
538 533 wctx=self.wctx,
539 534 extra=extra,
540 535 commitmsg=commitmsg,
541 536 editor=editor,
542 537 user=ctx.user(),
543 538 date=date,
544 539 )
545 540 else:
546 541 newnode = commitnode(
547 542 repo,
548 543 extra=extra,
549 544 commitmsg=commitmsg,
550 545 editor=editor,
551 546 user=ctx.user(),
552 547 date=date,
553 548 )
554 549
555 550 return newnode
556 551
557 552 def _rebasenode(self, tr, rev, allowdivergence, progressfn):
558 553 repo, ui, opts = self.repo, self.ui, self.opts
559 554 ctx = repo[rev]
560 555 desc = _ctxdesc(ctx)
561 556 if self.state[rev] == rev:
562 557 ui.status(_(b'already rebased %s\n') % desc)
563 558 elif (
564 559 not allowdivergence
565 560 and rev in self.obsoletewithoutsuccessorindestination
566 561 ):
567 562 msg = (
568 563 _(
569 564 b'note: not rebasing %s and its descendants as '
570 565 b'this would cause divergence\n'
571 566 )
572 567 % desc
573 568 )
574 569 repo.ui.status(msg)
575 570 self.skipped.add(rev)
576 571 elif rev in self.obsoletenotrebased:
577 572 succ = self.obsoletenotrebased[rev]
578 573 if succ is None:
579 574 msg = _(b'note: not rebasing %s, it has no successor\n') % desc
580 575 else:
581 576 succdesc = _ctxdesc(repo[succ])
582 577 msg = _(
583 578 b'note: not rebasing %s, already in destination as %s\n'
584 579 ) % (desc, succdesc)
585 580 repo.ui.status(msg)
586 581 # Make clearrebased aware state[rev] is not a true successor
587 582 self.skipped.add(rev)
588 583 # Record rev as moved to its desired destination in self.state.
589 584 # This helps bookmark and working parent movement.
590 585 dest = max(
591 586 adjustdest(repo, rev, self.destmap, self.state, self.skipped)
592 587 )
593 588 self.state[rev] = dest
594 589 elif self.state[rev] == revtodo:
595 590 ui.status(_(b'rebasing %s\n') % desc)
596 591 progressfn(ctx)
597 592 p1, p2, base = defineparents(
598 593 repo,
599 594 rev,
600 595 self.destmap,
601 596 self.state,
602 597 self.skipped,
603 598 self.obsoletenotrebased,
604 599 )
605 600 if self.resume and self.wctx.p1().rev() == p1:
606 601 repo.ui.debug(b'resuming interrupted rebase\n')
607 602 self.resume = False
608 603 else:
609 604 overrides = {(b'ui', b'forcemerge'): opts.get(b'tool', b'')}
610 605 with ui.configoverride(overrides, b'rebase'):
611 606 try:
612 607 rebasenode(
613 608 repo,
614 609 rev,
615 610 p1,
616 611 p2,
617 612 base,
618 613 self.collapsef,
619 614 wctx=self.wctx,
620 615 )
621 616 except error.InMemoryMergeConflictsError:
622 617 if self.dryrun:
623 618 raise error.ConflictResolutionRequired(b'rebase')
624 619 if self.collapsef:
625 620 # TODO: Make the overlayworkingctx reflected
626 621 # in the working copy here instead of re-raising
627 622 # so the entire rebase operation is retried.
628 623 raise
629 624 ui.status(
630 625 _(
631 626 b"hit merge conflicts; rebasing that "
632 627 b"commit again in the working copy\n"
633 628 )
634 629 )
635 630 cmdutil.bailifchanged(repo)
636 631 self.inmemory = False
637 632 self._assignworkingcopy()
638 633 mergemod.update(repo[p1], wc=self.wctx)
639 634 rebasenode(
640 635 repo,
641 636 rev,
642 637 p1,
643 638 p2,
644 639 base,
645 640 self.collapsef,
646 641 wctx=self.wctx,
647 642 )
648 643 if not self.collapsef:
649 644 merging = p2 != nullrev
650 645 editform = cmdutil.mergeeditform(merging, b'rebase')
651 646 editor = cmdutil.getcommiteditor(
652 647 editform=editform, **pycompat.strkwargs(opts)
653 648 )
654 649 # We need to set parents again here just in case we're continuing
655 650 # a rebase started with an old hg version (before 9c9cfecd4600),
656 651 # because those old versions would have left us with two dirstate
657 652 # parents, and we don't want to create a merge commit here (unless
658 653 # we're rebasing a merge commit).
659 654 self.wctx.setparents(repo[p1].node(), repo[p2].node())
660 655 newnode = self._concludenode(rev, editor)
661 656 else:
662 657 # Skip commit if we are collapsing
663 658 newnode = None
664 659 # Update the state
665 660 if newnode is not None:
666 661 self.state[rev] = repo[newnode].rev()
667 662 ui.debug(b'rebased as %s\n' % short(newnode))
668 663 if repo[newnode].isempty():
669 664 ui.warn(
670 665 _(
671 666 b'note: created empty successor for %s, its '
672 667 b'destination already has all its changes\n'
673 668 )
674 669 % desc
675 670 )
676 671 else:
677 672 if not self.collapsef:
678 673 ui.warn(
679 674 _(
680 675 b'note: not rebasing %s, its destination already '
681 676 b'has all its changes\n'
682 677 )
683 678 % desc
684 679 )
685 680 self.skipped.add(rev)
686 681 self.state[rev] = p1
687 682 ui.debug(b'next revision set to %d\n' % p1)
688 683 else:
689 684 ui.status(
690 685 _(b'already rebased %s as %s\n') % (desc, repo[self.state[rev]])
691 686 )
692 687 if not tr:
693 688 # When not using single transaction, store state after each
694 689 # commit is completely done. On InterventionRequired, we thus
695 690 # won't store the status. Instead, we'll hit the "len(parents) == 2"
696 691 # case and realize that the commit was in progress.
697 692 self.storestatus()
698 693
699 694 def _finishrebase(self):
700 695 repo, ui, opts = self.repo, self.ui, self.opts
701 696 fm = ui.formatter(b'rebase', opts)
702 697 fm.startitem()
703 698 if self.collapsef:
704 699 p1, p2, _base = defineparents(
705 700 repo,
706 701 min(self.state),
707 702 self.destmap,
708 703 self.state,
709 704 self.skipped,
710 705 self.obsoletenotrebased,
711 706 )
712 707 editopt = opts.get(b'edit')
713 708 editform = b'rebase.collapse'
714 709 if self.collapsemsg:
715 710 commitmsg = self.collapsemsg
716 711 else:
717 712 commitmsg = b'Collapsed revision'
718 713 for rebased in sorted(self.state):
719 714 if rebased not in self.skipped:
720 715 commitmsg += b'\n* %s' % repo[rebased].description()
721 716 editopt = True
722 717 editor = cmdutil.getcommiteditor(edit=editopt, editform=editform)
723 718 revtoreuse = max(self.state)
724 719
725 720 self.wctx.setparents(repo[p1].node(), repo[self.external].node())
726 721 newnode = self._concludenode(
727 722 revtoreuse, editor, commitmsg=commitmsg
728 723 )
729 724
730 725 if newnode is not None:
731 726 newrev = repo[newnode].rev()
732 727 for oldrev in self.state:
733 728 self.state[oldrev] = newrev
734 729
735 730 if b'qtip' in repo.tags():
736 731 updatemq(repo, self.state, self.skipped, **pycompat.strkwargs(opts))
737 732
738 733 # restore original working directory
739 734 # (we do this before stripping)
740 735 newwd = self.state.get(self.originalwd, self.originalwd)
741 736 if newwd < 0:
742 737 # original directory is a parent of rebase set root or ignored
743 738 newwd = self.originalwd
744 739 if newwd not in [c.rev() for c in repo[None].parents()]:
745 740 ui.note(_(b"update back to initial working directory parent\n"))
746 741 mergemod.update(repo[newwd])
747 742
748 743 collapsedas = None
749 744 if self.collapsef and not self.keepf:
750 745 collapsedas = newnode
751 746 clearrebased(
752 747 ui,
753 748 repo,
754 749 self.destmap,
755 750 self.state,
756 751 self.skipped,
757 752 collapsedas,
758 753 self.keepf,
759 754 fm=fm,
760 755 backup=self.backupf,
761 756 )
762 757
763 758 clearstatus(repo)
764 759 clearcollapsemsg(repo)
765 760
766 761 ui.note(_(b"rebase completed\n"))
767 762 util.unlinkpath(repo.sjoin(b'undo'), ignoremissing=True)
768 763 if self.skipped:
769 764 skippedlen = len(self.skipped)
770 765 ui.note(_(b"%d revisions have been skipped\n") % skippedlen)
771 766 fm.end()
772 767
773 768 if (
774 769 self.activebookmark
775 770 and self.activebookmark in repo._bookmarks
776 771 and repo[b'.'].node() == repo._bookmarks[self.activebookmark]
777 772 ):
778 773 bookmarks.activate(repo, self.activebookmark)
779 774
780 775 def _abort(self, backup=True, suppwarns=False, dryrun=False, confirm=False):
781 776 '''Restore the repository to its original state.'''
782 777
783 778 repo = self.repo
784 779 try:
785 780 # If the first commits in the rebased set get skipped during the
786 781 # rebase, their values within the state mapping will be the dest
787 782 # rev id. The rebased list must must not contain the dest rev
788 783 # (issue4896)
789 784 rebased = [
790 785 s
791 786 for r, s in self.state.items()
792 787 if s >= 0 and s != r and s != self.destmap[r]
793 788 ]
794 789 immutable = [d for d in rebased if not repo[d].mutable()]
795 790 cleanup = True
796 791 if immutable:
797 792 repo.ui.warn(
798 793 _(b"warning: can't clean up public changesets %s\n")
799 794 % b', '.join(bytes(repo[r]) for r in immutable),
800 795 hint=_(b"see 'hg help phases' for details"),
801 796 )
802 797 cleanup = False
803 798
804 799 descendants = set()
805 800 if rebased:
806 801 descendants = set(repo.changelog.descendants(rebased))
807 802 if descendants - set(rebased):
808 803 repo.ui.warn(
809 804 _(
810 805 b"warning: new changesets detected on "
811 806 b"destination branch, can't strip\n"
812 807 )
813 808 )
814 809 cleanup = False
815 810
816 811 if cleanup:
817 812 if rebased:
818 813 strippoints = [
819 814 c.node() for c in repo.set(b'roots(%ld)', rebased)
820 815 ]
821 816
822 817 updateifonnodes = set(rebased)
823 818 updateifonnodes.update(self.destmap.values())
824 819
825 820 if not dryrun and not confirm:
826 821 updateifonnodes.add(self.originalwd)
827 822
828 823 shouldupdate = repo[b'.'].rev() in updateifonnodes
829 824
830 825 # Update away from the rebase if necessary
831 826 if shouldupdate:
832 827 mergemod.clean_update(repo[self.originalwd])
833 828
834 829 # Strip from the first rebased revision
835 830 if rebased:
836 831 repair.strip(repo.ui, repo, strippoints, backup=backup)
837 832
838 833 if self.activebookmark and self.activebookmark in repo._bookmarks:
839 834 bookmarks.activate(repo, self.activebookmark)
840 835
841 836 finally:
842 837 clearstatus(repo)
843 838 clearcollapsemsg(repo)
844 839 if not suppwarns:
845 840 repo.ui.warn(_(b'rebase aborted\n'))
846 841 return 0
847 842
848 843
849 844 @command(
850 845 b'rebase',
851 846 [
852 847 (
853 848 b's',
854 849 b'source',
855 850 [],
856 851 _(b'rebase the specified changesets and their descendants'),
857 852 _(b'REV'),
858 853 ),
859 854 (
860 855 b'b',
861 856 b'base',
862 857 [],
863 858 _(b'rebase everything from branching point of specified changeset'),
864 859 _(b'REV'),
865 860 ),
866 861 (b'r', b'rev', [], _(b'rebase these revisions'), _(b'REV')),
867 862 (
868 863 b'd',
869 864 b'dest',
870 865 b'',
871 866 _(b'rebase onto the specified changeset'),
872 867 _(b'REV'),
873 868 ),
874 869 (b'', b'collapse', False, _(b'collapse the rebased changesets')),
875 870 (
876 871 b'm',
877 872 b'message',
878 873 b'',
879 874 _(b'use text as collapse commit message'),
880 875 _(b'TEXT'),
881 876 ),
882 877 (b'e', b'edit', False, _(b'invoke editor on commit messages')),
883 878 (
884 879 b'l',
885 880 b'logfile',
886 881 b'',
887 882 _(b'read collapse commit message from file'),
888 883 _(b'FILE'),
889 884 ),
890 885 (b'k', b'keep', False, _(b'keep original changesets')),
891 886 (b'', b'keepbranches', False, _(b'keep original branch names')),
892 887 (b'D', b'detach', False, _(b'(DEPRECATED)')),
893 888 (b'i', b'interactive', False, _(b'(DEPRECATED)')),
894 889 (b't', b'tool', b'', _(b'specify merge tool')),
895 890 (b'', b'stop', False, _(b'stop interrupted rebase')),
896 891 (b'c', b'continue', False, _(b'continue an interrupted rebase')),
897 892 (b'a', b'abort', False, _(b'abort an interrupted rebase')),
898 893 (
899 894 b'',
900 895 b'auto-orphans',
901 896 b'',
902 897 _(
903 898 b'automatically rebase orphan revisions '
904 899 b'in the specified revset (EXPERIMENTAL)'
905 900 ),
906 901 ),
907 902 ]
908 903 + cmdutil.dryrunopts
909 904 + cmdutil.formatteropts
910 905 + cmdutil.confirmopts,
911 906 _(b'[[-s REV]... | [-b REV]... | [-r REV]...] [-d REV] [OPTION]...'),
912 907 helpcategory=command.CATEGORY_CHANGE_MANAGEMENT,
913 908 )
914 909 def rebase(ui, repo, **opts):
915 910 """move changeset (and descendants) to a different branch
916 911
917 912 Rebase uses repeated merging to graft changesets from one part of
918 913 history (the source) onto another (the destination). This can be
919 914 useful for linearizing *local* changes relative to a master
920 915 development tree.
921 916
922 917 Published commits cannot be rebased (see :hg:`help phases`).
923 918 To copy commits, see :hg:`help graft`.
924 919
925 920 If you don't specify a destination changeset (``-d/--dest``), rebase
926 921 will use the same logic as :hg:`merge` to pick a destination. if
927 922 the current branch contains exactly one other head, the other head
928 923 is merged with by default. Otherwise, an explicit revision with
929 924 which to merge with must be provided. (destination changeset is not
930 925 modified by rebasing, but new changesets are added as its
931 926 descendants.)
932 927
933 928 Here are the ways to select changesets:
934 929
935 930 1. Explicitly select them using ``--rev``.
936 931
937 932 2. Use ``--source`` to select a root changeset and include all of its
938 933 descendants.
939 934
940 935 3. Use ``--base`` to select a changeset; rebase will find ancestors
941 936 and their descendants which are not also ancestors of the destination.
942 937
943 938 4. If you do not specify any of ``--rev``, ``--source``, or ``--base``,
944 939 rebase will use ``--base .`` as above.
945 940
946 941 If ``--source`` or ``--rev`` is used, special names ``SRC`` and ``ALLSRC``
947 942 can be used in ``--dest``. Destination would be calculated per source
948 943 revision with ``SRC`` substituted by that single source revision and
949 944 ``ALLSRC`` substituted by all source revisions.
950 945
951 946 Rebase will destroy original changesets unless you use ``--keep``.
952 947 It will also move your bookmarks (even if you do).
953 948
954 949 Some changesets may be dropped if they do not contribute changes
955 950 (e.g. merges from the destination branch).
956 951
957 952 Unlike ``merge``, rebase will do nothing if you are at the branch tip of
958 953 a named branch with two heads. You will need to explicitly specify source
959 954 and/or destination.
960 955
961 956 If you need to use a tool to automate merge/conflict decisions, you
962 957 can specify one with ``--tool``, see :hg:`help merge-tools`.
963 958 As a caveat: the tool will not be used to mediate when a file was
964 959 deleted, there is no hook presently available for this.
965 960
966 961 If a rebase is interrupted to manually resolve a conflict, it can be
967 962 continued with --continue/-c, aborted with --abort/-a, or stopped with
968 963 --stop.
969 964
970 965 .. container:: verbose
971 966
972 967 Examples:
973 968
974 969 - move "local changes" (current commit back to branching point)
975 970 to the current branch tip after a pull::
976 971
977 972 hg rebase
978 973
979 974 - move a single changeset to the stable branch::
980 975
981 976 hg rebase -r 5f493448 -d stable
982 977
983 978 - splice a commit and all its descendants onto another part of history::
984 979
985 980 hg rebase --source c0c3 --dest 4cf9
986 981
987 982 - rebase everything on a branch marked by a bookmark onto the
988 983 default branch::
989 984
990 985 hg rebase --base myfeature --dest default
991 986
992 987 - collapse a sequence of changes into a single commit::
993 988
994 989 hg rebase --collapse -r 1520:1525 -d .
995 990
996 991 - move a named branch while preserving its name::
997 992
998 993 hg rebase -r "branch(featureX)" -d 1.3 --keepbranches
999 994
1000 995 - stabilize orphaned changesets so history looks linear::
1001 996
1002 997 hg rebase -r 'orphan()-obsolete()'\
1003 998 -d 'first(max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::) +\
1004 999 max(::((roots(ALLSRC) & ::SRC)^)-obsolete()))'
1005 1000
1006 1001 Configuration Options:
1007 1002
1008 1003 You can make rebase require a destination if you set the following config
1009 1004 option::
1010 1005
1011 1006 [commands]
1012 1007 rebase.requiredest = True
1013 1008
1014 1009 By default, rebase will close the transaction after each commit. For
1015 1010 performance purposes, you can configure rebase to use a single transaction
1016 1011 across the entire rebase. WARNING: This setting introduces a significant
1017 1012 risk of losing the work you've done in a rebase if the rebase aborts
1018 1013 unexpectedly::
1019 1014
1020 1015 [rebase]
1021 1016 singletransaction = True
1022 1017
1023 1018 By default, rebase writes to the working copy, but you can configure it to
1024 1019 run in-memory for better performance. When the rebase is not moving the
1025 1020 parent(s) of the working copy (AKA the "currently checked out changesets"),
1026 1021 this may also allow it to run even if the working copy is dirty::
1027 1022
1028 1023 [rebase]
1029 1024 experimental.inmemory = True
1030 1025
1031 1026 Return Values:
1032 1027
1033 1028 Returns 0 on success, 1 if nothing to rebase or there are
1034 1029 unresolved conflicts.
1035 1030
1036 1031 """
1037 1032 opts = pycompat.byteskwargs(opts)
1038 1033 inmemory = ui.configbool(b'rebase', b'experimental.inmemory')
1039 1034 action = cmdutil.check_at_most_one_arg(opts, b'abort', b'stop', b'continue')
1040 1035 if action:
1041 1036 cmdutil.check_incompatible_arguments(
1042 1037 opts, action, [b'confirm', b'dry_run']
1043 1038 )
1044 1039 cmdutil.check_incompatible_arguments(
1045 1040 opts, action, [b'rev', b'source', b'base', b'dest']
1046 1041 )
1047 1042 cmdutil.check_at_most_one_arg(opts, b'confirm', b'dry_run')
1048 1043 cmdutil.check_at_most_one_arg(opts, b'rev', b'source', b'base')
1049 1044
1050 1045 if action or repo.currenttransaction() is not None:
1051 1046 # in-memory rebase is not compatible with resuming rebases.
1052 1047 # (Or if it is run within a transaction, since the restart logic can
1053 1048 # fail the entire transaction.)
1054 1049 inmemory = False
1055 1050
1056 1051 if opts.get(b'auto_orphans'):
1057 1052 disallowed_opts = set(opts) - {b'auto_orphans'}
1058 1053 cmdutil.check_incompatible_arguments(
1059 1054 opts, b'auto_orphans', disallowed_opts
1060 1055 )
1061 1056
1062 1057 userrevs = list(repo.revs(opts.get(b'auto_orphans')))
1063 1058 opts[b'rev'] = [revsetlang.formatspec(b'%ld and orphan()', userrevs)]
1064 1059 opts[b'dest'] = b'_destautoorphanrebase(SRC)'
1065 1060
1066 1061 if opts.get(b'dry_run') or opts.get(b'confirm'):
1067 1062 return _dryrunrebase(ui, repo, action, opts)
1068 1063 elif action == b'stop':
1069 1064 rbsrt = rebaseruntime(repo, ui)
1070 1065 with repo.wlock(), repo.lock():
1071 1066 rbsrt.restorestatus()
1072 1067 if rbsrt.collapsef:
1073 1068 raise error.Abort(_(b"cannot stop in --collapse session"))
1074 1069 allowunstable = obsolete.isenabled(repo, obsolete.allowunstableopt)
1075 1070 if not (rbsrt.keepf or allowunstable):
1076 1071 raise error.Abort(
1077 1072 _(
1078 1073 b"cannot remove original changesets with"
1079 1074 b" unrebased descendants"
1080 1075 ),
1081 1076 hint=_(
1082 1077 b'either enable obsmarkers to allow unstable '
1083 1078 b'revisions or use --keep to keep original '
1084 1079 b'changesets'
1085 1080 ),
1086 1081 )
1087 1082 # update to the current working revision
1088 1083 # to clear interrupted merge
1089 1084 mergemod.clean_update(repo[rbsrt.originalwd])
1090 1085 rbsrt._finishrebase()
1091 1086 return 0
1092 1087 elif inmemory:
1093 1088 try:
1094 1089 # in-memory merge doesn't support conflicts, so if we hit any, abort
1095 1090 # and re-run as an on-disk merge.
1096 1091 overrides = {(b'rebase', b'singletransaction'): True}
1097 1092 with ui.configoverride(overrides, b'rebase'):
1098 1093 return _dorebase(ui, repo, action, opts, inmemory=inmemory)
1099 1094 except error.InMemoryMergeConflictsError:
1100 1095 ui.warn(
1101 1096 _(
1102 1097 b'hit merge conflicts; re-running rebase without in-memory'
1103 1098 b' merge\n'
1104 1099 )
1105 1100 )
1106 1101 clearstatus(repo)
1107 1102 clearcollapsemsg(repo)
1108 1103 return _dorebase(ui, repo, action, opts, inmemory=False)
1109 1104 else:
1110 1105 return _dorebase(ui, repo, action, opts)
1111 1106
1112 1107
1113 1108 def _dryrunrebase(ui, repo, action, opts):
1114 1109 rbsrt = rebaseruntime(repo, ui, inmemory=True, dryrun=True, opts=opts)
1115 1110 confirm = opts.get(b'confirm')
1116 1111 if confirm:
1117 1112 ui.status(_(b'starting in-memory rebase\n'))
1118 1113 else:
1119 1114 ui.status(
1120 1115 _(b'starting dry-run rebase; repository will not be changed\n')
1121 1116 )
1122 1117 with repo.wlock(), repo.lock():
1123 1118 needsabort = True
1124 1119 try:
1125 1120 overrides = {(b'rebase', b'singletransaction'): True}
1126 1121 with ui.configoverride(overrides, b'rebase'):
1127 1122 _origrebase(
1128 1123 ui, repo, action, opts, rbsrt,
1129 1124 )
1130 1125 except error.ConflictResolutionRequired:
1131 1126 ui.status(_(b'hit a merge conflict\n'))
1132 1127 return 1
1133 1128 except error.Abort:
1134 1129 needsabort = False
1135 1130 raise
1136 1131 else:
1137 1132 if confirm:
1138 1133 ui.status(_(b'rebase completed successfully\n'))
1139 1134 if not ui.promptchoice(_(b'apply changes (yn)?$$ &Yes $$ &No')):
1140 1135 # finish unfinished rebase
1141 1136 rbsrt._finishrebase()
1142 1137 else:
1143 1138 rbsrt._prepareabortorcontinue(
1144 1139 isabort=True,
1145 1140 backup=False,
1146 1141 suppwarns=True,
1147 1142 confirm=confirm,
1148 1143 )
1149 1144 needsabort = False
1150 1145 else:
1151 1146 ui.status(
1152 1147 _(
1153 1148 b'dry-run rebase completed successfully; run without'
1154 1149 b' -n/--dry-run to perform this rebase\n'
1155 1150 )
1156 1151 )
1157 1152 return 0
1158 1153 finally:
1159 1154 if needsabort:
1160 1155 # no need to store backup in case of dryrun
1161 1156 rbsrt._prepareabortorcontinue(
1162 1157 isabort=True,
1163 1158 backup=False,
1164 1159 suppwarns=True,
1165 1160 dryrun=opts.get(b'dry_run'),
1166 1161 )
1167 1162
1168 1163
1169 1164 def _dorebase(ui, repo, action, opts, inmemory=False):
1170 1165 rbsrt = rebaseruntime(repo, ui, inmemory, opts=opts)
1171 1166 return _origrebase(ui, repo, action, opts, rbsrt)
1172 1167
1173 1168
1174 1169 def _origrebase(ui, repo, action, opts, rbsrt):
1175 1170 assert action != b'stop'
1176 1171 with repo.wlock(), repo.lock():
1177 1172 if opts.get(b'interactive'):
1178 1173 try:
1179 1174 if extensions.find(b'histedit'):
1180 1175 enablehistedit = b''
1181 1176 except KeyError:
1182 1177 enablehistedit = b" --config extensions.histedit="
1183 1178 help = b"hg%s help -e histedit" % enablehistedit
1184 1179 msg = (
1185 1180 _(
1186 1181 b"interactive history editing is supported by the "
1187 1182 b"'histedit' extension (see \"%s\")"
1188 1183 )
1189 1184 % help
1190 1185 )
1191 1186 raise error.Abort(msg)
1192 1187
1193 1188 if rbsrt.collapsemsg and not rbsrt.collapsef:
1194 1189 raise error.Abort(_(b'message can only be specified with collapse'))
1195 1190
1196 1191 if action:
1197 1192 if rbsrt.collapsef:
1198 1193 raise error.Abort(
1199 1194 _(b'cannot use collapse with continue or abort')
1200 1195 )
1201 1196 if action == b'abort' and opts.get(b'tool', False):
1202 1197 ui.warn(_(b'tool option will be ignored\n'))
1203 1198 if action == b'continue':
1204 1199 ms = mergestatemod.mergestate.read(repo)
1205 1200 mergeutil.checkunresolved(ms)
1206 1201
1207 1202 retcode = rbsrt._prepareabortorcontinue(
1208 1203 isabort=(action == b'abort')
1209 1204 )
1210 1205 if retcode is not None:
1211 1206 return retcode
1212 1207 else:
1213 1208 # search default destination in this space
1214 1209 # used in the 'hg pull --rebase' case, see issue 5214.
1215 1210 destspace = opts.get(b'_destspace')
1216 1211 destmap = _definedestmap(
1217 1212 ui,
1218 1213 repo,
1219 1214 rbsrt.inmemory,
1220 1215 opts.get(b'dest', None),
1221 1216 opts.get(b'source', []),
1222 1217 opts.get(b'base', []),
1223 1218 opts.get(b'rev', []),
1224 1219 destspace=destspace,
1225 1220 )
1226 1221 retcode = rbsrt._preparenewrebase(destmap)
1227 1222 if retcode is not None:
1228 1223 return retcode
1229 1224 storecollapsemsg(repo, rbsrt.collapsemsg)
1230 1225
1231 1226 tr = None
1232 1227
1233 1228 singletr = ui.configbool(b'rebase', b'singletransaction')
1234 1229 if singletr:
1235 1230 tr = repo.transaction(b'rebase')
1236 1231
1237 1232 # If `rebase.singletransaction` is enabled, wrap the entire operation in
1238 1233 # one transaction here. Otherwise, transactions are obtained when
1239 1234 # committing each node, which is slower but allows partial success.
1240 1235 with util.acceptintervention(tr):
1241 1236 # Same logic for the dirstate guard, except we don't create one when
1242 1237 # rebasing in-memory (it's not needed).
1243 1238 dsguard = None
1244 1239 if singletr and not rbsrt.inmemory:
1245 1240 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1246 1241 with util.acceptintervention(dsguard):
1247 1242 rbsrt._performrebase(tr)
1248 1243 if not rbsrt.dryrun:
1249 1244 rbsrt._finishrebase()
1250 1245
1251 1246
1252 1247 def _definedestmap(ui, repo, inmemory, destf, srcf, basef, revf, destspace):
1253 1248 """use revisions argument to define destmap {srcrev: destrev}"""
1254 1249 if revf is None:
1255 1250 revf = []
1256 1251
1257 1252 # destspace is here to work around issues with `hg pull --rebase` see
1258 1253 # issue5214 for details
1259 1254
1260 1255 cmdutil.checkunfinished(repo)
1261 1256 if not inmemory:
1262 1257 cmdutil.bailifchanged(repo)
1263 1258
1264 1259 if ui.configbool(b'commands', b'rebase.requiredest') and not destf:
1265 1260 raise error.Abort(
1266 1261 _(b'you must specify a destination'),
1267 1262 hint=_(b'use: hg rebase -d REV'),
1268 1263 )
1269 1264
1270 1265 dest = None
1271 1266
1272 1267 if revf:
1273 1268 rebaseset = scmutil.revrange(repo, revf)
1274 1269 if not rebaseset:
1275 1270 ui.status(_(b'empty "rev" revision set - nothing to rebase\n'))
1276 1271 return None
1277 1272 elif srcf:
1278 1273 src = scmutil.revrange(repo, srcf)
1279 1274 if not src:
1280 1275 ui.status(_(b'empty "source" revision set - nothing to rebase\n'))
1281 1276 return None
1282 1277 # `+ (%ld)` to work around `wdir()::` being empty
1283 1278 rebaseset = repo.revs(b'(%ld):: + (%ld)', src, src)
1284 1279 else:
1285 1280 base = scmutil.revrange(repo, basef or [b'.'])
1286 1281 if not base:
1287 1282 ui.status(
1288 1283 _(b'empty "base" revision set - ' b"can't compute rebase set\n")
1289 1284 )
1290 1285 return None
1291 1286 if destf:
1292 1287 # --base does not support multiple destinations
1293 1288 dest = scmutil.revsingle(repo, destf)
1294 1289 else:
1295 1290 dest = repo[_destrebase(repo, base, destspace=destspace)]
1296 1291 destf = bytes(dest)
1297 1292
1298 1293 roots = [] # selected children of branching points
1299 1294 bpbase = {} # {branchingpoint: [origbase]}
1300 1295 for b in base: # group bases by branching points
1301 1296 bp = repo.revs(b'ancestor(%d, %d)', b, dest.rev()).first()
1302 1297 bpbase[bp] = bpbase.get(bp, []) + [b]
1303 1298 if None in bpbase:
1304 1299 # emulate the old behavior, showing "nothing to rebase" (a better
1305 1300 # behavior may be abort with "cannot find branching point" error)
1306 1301 bpbase.clear()
1307 1302 for bp, bs in pycompat.iteritems(bpbase): # calculate roots
1308 1303 roots += list(repo.revs(b'children(%d) & ancestors(%ld)', bp, bs))
1309 1304
1310 1305 rebaseset = repo.revs(b'%ld::', roots)
1311 1306
1312 1307 if not rebaseset:
1313 1308 # transform to list because smartsets are not comparable to
1314 1309 # lists. This should be improved to honor laziness of
1315 1310 # smartset.
1316 1311 if list(base) == [dest.rev()]:
1317 1312 if basef:
1318 1313 ui.status(
1319 1314 _(
1320 1315 b'nothing to rebase - %s is both "base"'
1321 1316 b' and destination\n'
1322 1317 )
1323 1318 % dest
1324 1319 )
1325 1320 else:
1326 1321 ui.status(
1327 1322 _(
1328 1323 b'nothing to rebase - working directory '
1329 1324 b'parent is also destination\n'
1330 1325 )
1331 1326 )
1332 1327 elif not repo.revs(b'%ld - ::%d', base, dest.rev()):
1333 1328 if basef:
1334 1329 ui.status(
1335 1330 _(
1336 1331 b'nothing to rebase - "base" %s is '
1337 1332 b'already an ancestor of destination '
1338 1333 b'%s\n'
1339 1334 )
1340 1335 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1341 1336 )
1342 1337 else:
1343 1338 ui.status(
1344 1339 _(
1345 1340 b'nothing to rebase - working '
1346 1341 b'directory parent is already an '
1347 1342 b'ancestor of destination %s\n'
1348 1343 )
1349 1344 % dest
1350 1345 )
1351 1346 else: # can it happen?
1352 1347 ui.status(
1353 1348 _(b'nothing to rebase from %s to %s\n')
1354 1349 % (b'+'.join(bytes(repo[r]) for r in base), dest)
1355 1350 )
1356 1351 return None
1357 1352
1358 1353 if nodemod.wdirrev in rebaseset:
1359 1354 raise error.Abort(_(b'cannot rebase the working copy'))
1360 1355 rebasingwcp = repo[b'.'].rev() in rebaseset
1361 1356 ui.log(
1362 1357 b"rebase",
1363 1358 b"rebasing working copy parent: %r\n",
1364 1359 rebasingwcp,
1365 1360 rebase_rebasing_wcp=rebasingwcp,
1366 1361 )
1367 1362 if inmemory and rebasingwcp:
1368 1363 # Check these since we did not before.
1369 1364 cmdutil.checkunfinished(repo)
1370 1365 cmdutil.bailifchanged(repo)
1371 1366
1372 1367 if not destf:
1373 1368 dest = repo[_destrebase(repo, rebaseset, destspace=destspace)]
1374 1369 destf = bytes(dest)
1375 1370
1376 1371 allsrc = revsetlang.formatspec(b'%ld', rebaseset)
1377 1372 alias = {b'ALLSRC': allsrc}
1378 1373
1379 1374 if dest is None:
1380 1375 try:
1381 1376 # fast path: try to resolve dest without SRC alias
1382 1377 dest = scmutil.revsingle(repo, destf, localalias=alias)
1383 1378 except error.RepoLookupError:
1384 1379 # multi-dest path: resolve dest for each SRC separately
1385 1380 destmap = {}
1386 1381 for r in rebaseset:
1387 1382 alias[b'SRC'] = revsetlang.formatspec(b'%d', r)
1388 1383 # use repo.anyrevs instead of scmutil.revsingle because we
1389 1384 # don't want to abort if destset is empty.
1390 1385 destset = repo.anyrevs([destf], user=True, localalias=alias)
1391 1386 size = len(destset)
1392 1387 if size == 1:
1393 1388 destmap[r] = destset.first()
1394 1389 elif size == 0:
1395 1390 ui.note(_(b'skipping %s - empty destination\n') % repo[r])
1396 1391 else:
1397 1392 raise error.Abort(
1398 1393 _(b'rebase destination for %s is not unique') % repo[r]
1399 1394 )
1400 1395
1401 1396 if dest is not None:
1402 1397 # single-dest case: assign dest to each rev in rebaseset
1403 1398 destrev = dest.rev()
1404 1399 destmap = {r: destrev for r in rebaseset} # {srcrev: destrev}
1405 1400
1406 1401 if not destmap:
1407 1402 ui.status(_(b'nothing to rebase - empty destination\n'))
1408 1403 return None
1409 1404
1410 1405 return destmap
1411 1406
1412 1407
1413 1408 def externalparent(repo, state, destancestors):
1414 1409 """Return the revision that should be used as the second parent
1415 1410 when the revisions in state is collapsed on top of destancestors.
1416 1411 Abort if there is more than one parent.
1417 1412 """
1418 1413 parents = set()
1419 1414 source = min(state)
1420 1415 for rev in state:
1421 1416 if rev == source:
1422 1417 continue
1423 1418 for p in repo[rev].parents():
1424 1419 if p.rev() not in state and p.rev() not in destancestors:
1425 1420 parents.add(p.rev())
1426 1421 if not parents:
1427 1422 return nullrev
1428 1423 if len(parents) == 1:
1429 1424 return parents.pop()
1430 1425 raise error.Abort(
1431 1426 _(
1432 1427 b'unable to collapse on top of %d, there is more '
1433 1428 b'than one external parent: %s'
1434 1429 )
1435 1430 % (max(destancestors), b', '.join(b"%d" % p for p in sorted(parents)))
1436 1431 )
1437 1432
1438 1433
1439 1434 def commitmemorynode(repo, wctx, editor, extra, user, date, commitmsg):
1440 1435 '''Commit the memory changes with parents p1 and p2.
1441 1436 Return node of committed revision.'''
1442 1437 # By convention, ``extra['branch']`` (set by extrafn) clobbers
1443 1438 # ``branch`` (used when passing ``--keepbranches``).
1444 1439 branch = None
1445 1440 if b'branch' in extra:
1446 1441 branch = extra[b'branch']
1447 1442
1448 1443 # FIXME: We call _compact() because it's required to correctly detect
1449 1444 # changed files. This was added to fix a regression shortly before the 5.5
1450 1445 # release. A proper fix will be done in the default branch.
1451 1446 wctx._compact()
1452 1447 memctx = wctx.tomemctx(
1453 1448 commitmsg,
1454 1449 date=date,
1455 1450 extra=extra,
1456 1451 user=user,
1457 1452 branch=branch,
1458 1453 editor=editor,
1459 1454 )
1460 1455 if memctx.isempty() and not repo.ui.configbool(b'ui', b'allowemptycommit'):
1461 1456 return None
1462 1457 commitres = repo.commitctx(memctx)
1463 1458 wctx.clean() # Might be reused
1464 1459 return commitres
1465 1460
1466 1461
1467 1462 def commitnode(repo, editor, extra, user, date, commitmsg):
1468 1463 '''Commit the wd changes with parents p1 and p2.
1469 1464 Return node of committed revision.'''
1470 1465 dsguard = util.nullcontextmanager()
1471 1466 if not repo.ui.configbool(b'rebase', b'singletransaction'):
1472 1467 dsguard = dirstateguard.dirstateguard(repo, b'rebase')
1473 1468 with dsguard:
1474 1469 # Commit might fail if unresolved files exist
1475 1470 newnode = repo.commit(
1476 1471 text=commitmsg, user=user, date=date, extra=extra, editor=editor
1477 1472 )
1478 1473
1479 1474 repo.dirstate.setbranch(repo[newnode].branch())
1480 1475 return newnode
1481 1476
1482 1477
1483 1478 def rebasenode(repo, rev, p1, p2, base, collapse, wctx):
1484 1479 """Rebase a single revision rev on top of p1 using base as merge ancestor"""
1485 1480 # Merge phase
1486 1481 # Update to destination and merge it with local
1487 1482 p1ctx = repo[p1]
1488 1483 if wctx.isinmemory():
1489 1484 wctx.setbase(p1ctx)
1490 1485 else:
1491 1486 if repo[b'.'].rev() != p1:
1492 1487 repo.ui.debug(b" update to %d:%s\n" % (p1, p1ctx))
1493 1488 mergemod.clean_update(p1ctx)
1494 1489 else:
1495 1490 repo.ui.debug(b" already in destination\n")
1496 1491 # This is, alas, necessary to invalidate workingctx's manifest cache,
1497 1492 # as well as other data we litter on it in other places.
1498 1493 wctx = repo[None]
1499 1494 repo.dirstate.write(repo.currenttransaction())
1500 1495 ctx = repo[rev]
1501 1496 repo.ui.debug(b" merge against %d:%s\n" % (rev, ctx))
1502 1497 if base is not None:
1503 1498 repo.ui.debug(b" detach base %d:%s\n" % (base, repo[base]))
1504 1499
1505 1500 # See explanation in merge.graft()
1506 1501 mergeancestor = repo.changelog.isancestor(p1ctx.node(), ctx.node())
1507 1502 stats = mergemod._update(
1508 1503 repo,
1509 1504 rev,
1510 1505 branchmerge=True,
1511 1506 force=True,
1512 1507 ancestor=base,
1513 1508 mergeancestor=mergeancestor,
1514 1509 labels=[b'dest', b'source'],
1515 1510 wc=wctx,
1516 1511 )
1517 1512 wctx.setparents(p1ctx.node(), repo[p2].node())
1518 1513 if collapse:
1519 1514 copies.graftcopies(wctx, ctx, p1ctx)
1520 1515 else:
1521 1516 # If we're not using --collapse, we need to
1522 1517 # duplicate copies between the revision we're
1523 1518 # rebasing and its first parent.
1524 1519 copies.graftcopies(wctx, ctx, ctx.p1())
1525 1520
1526 1521 if stats.unresolvedcount > 0:
1527 1522 if wctx.isinmemory():
1528 1523 raise error.InMemoryMergeConflictsError()
1529 1524 else:
1530 1525 raise error.ConflictResolutionRequired(b'rebase')
1531 1526
1532 1527
1533 1528 def adjustdest(repo, rev, destmap, state, skipped):
1534 1529 r"""adjust rebase destination given the current rebase state
1535 1530
1536 1531 rev is what is being rebased. Return a list of two revs, which are the
1537 1532 adjusted destinations for rev's p1 and p2, respectively. If a parent is
1538 1533 nullrev, return dest without adjustment for it.
1539 1534
1540 1535 For example, when doing rebasing B+E to F, C to G, rebase will first move B
1541 1536 to B1, and E's destination will be adjusted from F to B1.
1542 1537
1543 1538 B1 <- written during rebasing B
1544 1539 |
1545 1540 F <- original destination of B, E
1546 1541 |
1547 1542 | E <- rev, which is being rebased
1548 1543 | |
1549 1544 | D <- prev, one parent of rev being checked
1550 1545 | |
1551 1546 | x <- skipped, ex. no successor or successor in (::dest)
1552 1547 | |
1553 1548 | C <- rebased as C', different destination
1554 1549 | |
1555 1550 | B <- rebased as B1 C'
1556 1551 |/ |
1557 1552 A G <- destination of C, different
1558 1553
1559 1554 Another example about merge changeset, rebase -r C+G+H -d K, rebase will
1560 1555 first move C to C1, G to G1, and when it's checking H, the adjusted
1561 1556 destinations will be [C1, G1].
1562 1557
1563 1558 H C1 G1
1564 1559 /| | /
1565 1560 F G |/
1566 1561 K | | -> K
1567 1562 | C D |
1568 1563 | |/ |
1569 1564 | B | ...
1570 1565 |/ |/
1571 1566 A A
1572 1567
1573 1568 Besides, adjust dest according to existing rebase information. For example,
1574 1569
1575 1570 B C D B needs to be rebased on top of C, C needs to be rebased on top
1576 1571 \|/ of D. We will rebase C first.
1577 1572 A
1578 1573
1579 1574 C' After rebasing C, when considering B's destination, use C'
1580 1575 | instead of the original C.
1581 1576 B D
1582 1577 \ /
1583 1578 A
1584 1579 """
1585 1580 # pick already rebased revs with same dest from state as interesting source
1586 1581 dest = destmap[rev]
1587 1582 source = [
1588 1583 s
1589 1584 for s, d in state.items()
1590 1585 if d > 0 and destmap[s] == dest and s not in skipped
1591 1586 ]
1592 1587
1593 1588 result = []
1594 1589 for prev in repo.changelog.parentrevs(rev):
1595 1590 adjusted = dest
1596 1591 if prev != nullrev:
1597 1592 candidate = repo.revs(b'max(%ld and (::%d))', source, prev).first()
1598 1593 if candidate is not None:
1599 1594 adjusted = state[candidate]
1600 1595 if adjusted == dest and dest in state:
1601 1596 adjusted = state[dest]
1602 1597 if adjusted == revtodo:
1603 1598 # sortsource should produce an order that makes this impossible
1604 1599 raise error.ProgrammingError(
1605 1600 b'rev %d should be rebased already at this time' % dest
1606 1601 )
1607 1602 result.append(adjusted)
1608 1603 return result
1609 1604
1610 1605
1611 1606 def _checkobsrebase(repo, ui, rebaseobsrevs, rebaseobsskipped):
1612 1607 """
1613 1608 Abort if rebase will create divergence or rebase is noop because of markers
1614 1609
1615 1610 `rebaseobsrevs`: set of obsolete revision in source
1616 1611 `rebaseobsskipped`: set of revisions from source skipped because they have
1617 1612 successors in destination or no non-obsolete successor.
1618 1613 """
1619 1614 # Obsolete node with successors not in dest leads to divergence
1620 1615 divergenceok = ui.configbool(b'experimental', b'evolution.allowdivergence')
1621 1616 divergencebasecandidates = rebaseobsrevs - rebaseobsskipped
1622 1617
1623 1618 if divergencebasecandidates and not divergenceok:
1624 1619 divhashes = (bytes(repo[r]) for r in divergencebasecandidates)
1625 1620 msg = _(b"this rebase will cause divergences from: %s")
1626 1621 h = _(
1627 1622 b"to force the rebase please set "
1628 1623 b"experimental.evolution.allowdivergence=True"
1629 1624 )
1630 1625 raise error.Abort(msg % (b",".join(divhashes),), hint=h)
1631 1626
1632 1627
1633 1628 def successorrevs(unfi, rev):
1634 1629 """yield revision numbers for successors of rev"""
1635 1630 assert unfi.filtername is None
1636 1631 get_rev = unfi.changelog.index.get_rev
1637 1632 for s in obsutil.allsuccessors(unfi.obsstore, [unfi[rev].node()]):
1638 1633 r = get_rev(s)
1639 1634 if r is not None:
1640 1635 yield r
1641 1636
1642 1637
1643 1638 def defineparents(repo, rev, destmap, state, skipped, obsskipped):
1644 1639 """Return new parents and optionally a merge base for rev being rebased
1645 1640
1646 1641 The destination specified by "dest" cannot always be used directly because
1647 1642 previously rebase result could affect destination. For example,
1648 1643
1649 1644 D E rebase -r C+D+E -d B
1650 1645 |/ C will be rebased to C'
1651 1646 B C D's new destination will be C' instead of B
1652 1647 |/ E's new destination will be C' instead of B
1653 1648 A
1654 1649
1655 1650 The new parents of a merge is slightly more complicated. See the comment
1656 1651 block below.
1657 1652 """
1658 1653 # use unfiltered changelog since successorrevs may return filtered nodes
1659 1654 assert repo.filtername is None
1660 1655 cl = repo.changelog
1661 1656 isancestor = cl.isancestorrev
1662 1657
1663 1658 dest = destmap[rev]
1664 1659 oldps = repo.changelog.parentrevs(rev) # old parents
1665 1660 newps = [nullrev, nullrev] # new parents
1666 1661 dests = adjustdest(repo, rev, destmap, state, skipped)
1667 1662 bases = list(oldps) # merge base candidates, initially just old parents
1668 1663
1669 1664 if all(r == nullrev for r in oldps[1:]):
1670 1665 # For non-merge changeset, just move p to adjusted dest as requested.
1671 1666 newps[0] = dests[0]
1672 1667 else:
1673 1668 # For merge changeset, if we move p to dests[i] unconditionally, both
1674 1669 # parents may change and the end result looks like "the merge loses a
1675 1670 # parent", which is a surprise. This is a limit because "--dest" only
1676 1671 # accepts one dest per src.
1677 1672 #
1678 1673 # Therefore, only move p with reasonable conditions (in this order):
1679 1674 # 1. use dest, if dest is a descendent of (p or one of p's successors)
1680 1675 # 2. use p's rebased result, if p is rebased (state[p] > 0)
1681 1676 #
1682 1677 # Comparing with adjustdest, the logic here does some additional work:
1683 1678 # 1. decide which parents will not be moved towards dest
1684 1679 # 2. if the above decision is "no", should a parent still be moved
1685 1680 # because it was rebased?
1686 1681 #
1687 1682 # For example:
1688 1683 #
1689 1684 # C # "rebase -r C -d D" is an error since none of the parents
1690 1685 # /| # can be moved. "rebase -r B+C -d D" will move C's parent
1691 1686 # A B D # B (using rule "2."), since B will be rebased.
1692 1687 #
1693 1688 # The loop tries to be not rely on the fact that a Mercurial node has
1694 1689 # at most 2 parents.
1695 1690 for i, p in enumerate(oldps):
1696 1691 np = p # new parent
1697 1692 if any(isancestor(x, dests[i]) for x in successorrevs(repo, p)):
1698 1693 np = dests[i]
1699 1694 elif p in state and state[p] > 0:
1700 1695 np = state[p]
1701 1696
1702 1697 # If one parent becomes an ancestor of the other, drop the ancestor
1703 1698 for j, x in enumerate(newps[:i]):
1704 1699 if x == nullrev:
1705 1700 continue
1706 1701 if isancestor(np, x): # CASE-1
1707 1702 np = nullrev
1708 1703 elif isancestor(x, np): # CASE-2
1709 1704 newps[j] = np
1710 1705 np = nullrev
1711 1706 # New parents forming an ancestor relationship does not
1712 1707 # mean the old parents have a similar relationship. Do not
1713 1708 # set bases[x] to nullrev.
1714 1709 bases[j], bases[i] = bases[i], bases[j]
1715 1710
1716 1711 newps[i] = np
1717 1712
1718 1713 # "rebasenode" updates to new p1, and the old p1 will be used as merge
1719 1714 # base. If only p2 changes, merging using unchanged p1 as merge base is
1720 1715 # suboptimal. Therefore swap parents to make the merge sane.
1721 1716 if newps[1] != nullrev and oldps[0] == newps[0]:
1722 1717 assert len(newps) == 2 and len(oldps) == 2
1723 1718 newps.reverse()
1724 1719 bases.reverse()
1725 1720
1726 1721 # No parent change might be an error because we fail to make rev a
1727 1722 # descendent of requested dest. This can happen, for example:
1728 1723 #
1729 1724 # C # rebase -r C -d D
1730 1725 # /| # None of A and B will be changed to D and rebase fails.
1731 1726 # A B D
1732 1727 if set(newps) == set(oldps) and dest not in newps:
1733 1728 raise error.Abort(
1734 1729 _(
1735 1730 b'cannot rebase %d:%s without '
1736 1731 b'moving at least one of its parents'
1737 1732 )
1738 1733 % (rev, repo[rev])
1739 1734 )
1740 1735
1741 1736 # Source should not be ancestor of dest. The check here guarantees it's
1742 1737 # impossible. With multi-dest, the initial check does not cover complex
1743 1738 # cases since we don't have abstractions to dry-run rebase cheaply.
1744 1739 if any(p != nullrev and isancestor(rev, p) for p in newps):
1745 1740 raise error.Abort(_(b'source is ancestor of destination'))
1746 1741
1747 1742 # Check if the merge will contain unwanted changes. That may happen if
1748 1743 # there are multiple special (non-changelog ancestor) merge bases, which
1749 1744 # cannot be handled well by the 3-way merge algorithm. For example:
1750 1745 #
1751 1746 # F
1752 1747 # /|
1753 1748 # D E # "rebase -r D+E+F -d Z", when rebasing F, if "D" was chosen
1754 1749 # | | # as merge base, the difference between D and F will include
1755 1750 # B C # C, so the rebased F will contain C surprisingly. If "E" was
1756 1751 # |/ # chosen, the rebased F will contain B.
1757 1752 # A Z
1758 1753 #
1759 1754 # But our merge base candidates (D and E in above case) could still be
1760 1755 # better than the default (ancestor(F, Z) == null). Therefore still
1761 1756 # pick one (so choose p1 above).
1762 1757 if sum(1 for b in set(bases) if b != nullrev and b not in newps) > 1:
1763 1758 unwanted = [None, None] # unwanted[i]: unwanted revs if choose bases[i]
1764 1759 for i, base in enumerate(bases):
1765 1760 if base == nullrev or base in newps:
1766 1761 continue
1767 1762 # Revisions in the side (not chosen as merge base) branch that
1768 1763 # might contain "surprising" contents
1769 1764 other_bases = set(bases) - {base}
1770 1765 siderevs = list(
1771 1766 repo.revs(b'(%ld %% (%d+%d))', other_bases, base, dest)
1772 1767 )
1773 1768
1774 1769 # If those revisions are covered by rebaseset, the result is good.
1775 1770 # A merge in rebaseset would be considered to cover its ancestors.
1776 1771 if siderevs:
1777 1772 rebaseset = [
1778 1773 r for r, d in state.items() if d > 0 and r not in obsskipped
1779 1774 ]
1780 1775 merges = [
1781 1776 r for r in rebaseset if cl.parentrevs(r)[1] != nullrev
1782 1777 ]
1783 1778 unwanted[i] = list(
1784 1779 repo.revs(
1785 1780 b'%ld - (::%ld) - %ld', siderevs, merges, rebaseset
1786 1781 )
1787 1782 )
1788 1783
1789 1784 if any(revs is not None for revs in unwanted):
1790 1785 # Choose a merge base that has a minimal number of unwanted revs.
1791 1786 l, i = min(
1792 1787 (len(revs), i)
1793 1788 for i, revs in enumerate(unwanted)
1794 1789 if revs is not None
1795 1790 )
1796 1791
1797 1792 # The merge will include unwanted revisions. Abort now. Revisit this if
1798 1793 # we have a more advanced merge algorithm that handles multiple bases.
1799 1794 if l > 0:
1800 1795 unwanteddesc = _(b' or ').join(
1801 1796 (
1802 1797 b', '.join(b'%d:%s' % (r, repo[r]) for r in revs)
1803 1798 for revs in unwanted
1804 1799 if revs is not None
1805 1800 )
1806 1801 )
1807 1802 raise error.Abort(
1808 1803 _(b'rebasing %d:%s will include unwanted changes from %s')
1809 1804 % (rev, repo[rev], unwanteddesc)
1810 1805 )
1811 1806
1812 1807 # newps[0] should match merge base if possible. Currently, if newps[i]
1813 1808 # is nullrev, the only case is newps[i] and newps[j] (j < i), one is
1814 1809 # the other's ancestor. In that case, it's fine to not swap newps here.
1815 1810 # (see CASE-1 and CASE-2 above)
1816 1811 if i != 0:
1817 1812 if newps[i] != nullrev:
1818 1813 newps[0], newps[i] = newps[i], newps[0]
1819 1814 bases[0], bases[i] = bases[i], bases[0]
1820 1815
1821 1816 # "rebasenode" updates to new p1, use the corresponding merge base.
1822 1817 base = bases[0]
1823 1818
1824 1819 repo.ui.debug(b" future parents are %d and %d\n" % tuple(newps))
1825 1820
1826 1821 return newps[0], newps[1], base
1827 1822
1828 1823
1829 1824 def isagitpatch(repo, patchname):
1830 1825 """Return true if the given patch is in git format"""
1831 1826 mqpatch = os.path.join(repo.mq.path, patchname)
1832 1827 for line in patch.linereader(open(mqpatch, b'rb')):
1833 1828 if line.startswith(b'diff --git'):
1834 1829 return True
1835 1830 return False
1836 1831
1837 1832
1838 1833 def updatemq(repo, state, skipped, **opts):
1839 1834 """Update rebased mq patches - finalize and then import them"""
1840 1835 mqrebase = {}
1841 1836 mq = repo.mq
1842 1837 original_series = mq.fullseries[:]
1843 1838 skippedpatches = set()
1844 1839
1845 1840 for p in mq.applied:
1846 1841 rev = repo[p.node].rev()
1847 1842 if rev in state:
1848 1843 repo.ui.debug(
1849 1844 b'revision %d is an mq patch (%s), finalize it.\n'
1850 1845 % (rev, p.name)
1851 1846 )
1852 1847 mqrebase[rev] = (p.name, isagitpatch(repo, p.name))
1853 1848 else:
1854 1849 # Applied but not rebased, not sure this should happen
1855 1850 skippedpatches.add(p.name)
1856 1851
1857 1852 if mqrebase:
1858 1853 mq.finish(repo, mqrebase.keys())
1859 1854
1860 1855 # We must start import from the newest revision
1861 1856 for rev in sorted(mqrebase, reverse=True):
1862 1857 if rev not in skipped:
1863 1858 name, isgit = mqrebase[rev]
1864 1859 repo.ui.note(
1865 1860 _(b'updating mq patch %s to %d:%s\n')
1866 1861 % (name, state[rev], repo[state[rev]])
1867 1862 )
1868 1863 mq.qimport(
1869 1864 repo,
1870 1865 (),
1871 1866 patchname=name,
1872 1867 git=isgit,
1873 1868 rev=[b"%d" % state[rev]],
1874 1869 )
1875 1870 else:
1876 1871 # Rebased and skipped
1877 1872 skippedpatches.add(mqrebase[rev][0])
1878 1873
1879 1874 # Patches were either applied and rebased and imported in
1880 1875 # order, applied and removed or unapplied. Discard the removed
1881 1876 # ones while preserving the original series order and guards.
1882 1877 newseries = [
1883 1878 s
1884 1879 for s in original_series
1885 1880 if mq.guard_re.split(s, 1)[0] not in skippedpatches
1886 1881 ]
1887 1882 mq.fullseries[:] = newseries
1888 1883 mq.seriesdirty = True
1889 1884 mq.savedirty()
1890 1885
1891 1886
1892 1887 def storecollapsemsg(repo, collapsemsg):
1893 1888 """Store the collapse message to allow recovery"""
1894 1889 collapsemsg = collapsemsg or b''
1895 1890 f = repo.vfs(b"last-message.txt", b"w")
1896 1891 f.write(b"%s\n" % collapsemsg)
1897 1892 f.close()
1898 1893
1899 1894
1900 1895 def clearcollapsemsg(repo):
1901 1896 """Remove collapse message file"""
1902 1897 repo.vfs.unlinkpath(b"last-message.txt", ignoremissing=True)
1903 1898
1904 1899
1905 1900 def restorecollapsemsg(repo, isabort):
1906 1901 """Restore previously stored collapse message"""
1907 1902 try:
1908 1903 f = repo.vfs(b"last-message.txt")
1909 1904 collapsemsg = f.readline().strip()
1910 1905 f.close()
1911 1906 except IOError as err:
1912 1907 if err.errno != errno.ENOENT:
1913 1908 raise
1914 1909 if isabort:
1915 1910 # Oh well, just abort like normal
1916 1911 collapsemsg = b''
1917 1912 else:
1918 1913 raise error.Abort(_(b'missing .hg/last-message.txt for rebase'))
1919 1914 return collapsemsg
1920 1915
1921 1916
1922 1917 def clearstatus(repo):
1923 1918 """Remove the status files"""
1924 1919 # Make sure the active transaction won't write the state file
1925 1920 tr = repo.currenttransaction()
1926 1921 if tr:
1927 1922 tr.removefilegenerator(b'rebasestate')
1928 1923 repo.vfs.unlinkpath(b"rebasestate", ignoremissing=True)
1929 1924
1930 1925
1931 1926 def sortsource(destmap):
1932 1927 """yield source revisions in an order that we only rebase things once
1933 1928
1934 1929 If source and destination overlaps, we should filter out revisions
1935 1930 depending on other revisions which hasn't been rebased yet.
1936 1931
1937 1932 Yield a sorted list of revisions each time.
1938 1933
1939 1934 For example, when rebasing A to B, B to C. This function yields [B], then
1940 1935 [A], indicating B needs to be rebased first.
1941 1936
1942 1937 Raise if there is a cycle so the rebase is impossible.
1943 1938 """
1944 1939 srcset = set(destmap)
1945 1940 while srcset:
1946 1941 srclist = sorted(srcset)
1947 1942 result = []
1948 1943 for r in srclist:
1949 1944 if destmap[r] not in srcset:
1950 1945 result.append(r)
1951 1946 if not result:
1952 1947 raise error.Abort(_(b'source and destination form a cycle'))
1953 1948 srcset -= set(result)
1954 1949 yield result
1955 1950
1956 1951
1957 1952 def buildstate(repo, destmap, collapse):
1958 1953 '''Define which revisions are going to be rebased and where
1959 1954
1960 1955 repo: repo
1961 1956 destmap: {srcrev: destrev}
1962 1957 '''
1963 1958 rebaseset = destmap.keys()
1964 1959 originalwd = repo[b'.'].rev()
1965 1960
1966 1961 # This check isn't strictly necessary, since mq detects commits over an
1967 1962 # applied patch. But it prevents messing up the working directory when
1968 1963 # a partially completed rebase is blocked by mq.
1969 1964 if b'qtip' in repo.tags():
1970 1965 mqapplied = {repo[s.node].rev() for s in repo.mq.applied}
1971 1966 if set(destmap.values()) & mqapplied:
1972 1967 raise error.Abort(_(b'cannot rebase onto an applied mq patch'))
1973 1968
1974 1969 # Get "cycle" error early by exhausting the generator.
1975 1970 sortedsrc = list(sortsource(destmap)) # a list of sorted revs
1976 1971 if not sortedsrc:
1977 1972 raise error.Abort(_(b'no matching revisions'))
1978 1973
1979 1974 # Only check the first batch of revisions to rebase not depending on other
1980 1975 # rebaseset. This means "source is ancestor of destination" for the second
1981 1976 # (and following) batches of revisions are not checked here. We rely on
1982 1977 # "defineparents" to do that check.
1983 1978 roots = list(repo.set(b'roots(%ld)', sortedsrc[0]))
1984 1979 if not roots:
1985 1980 raise error.Abort(_(b'no matching revisions'))
1986 1981
1987 1982 def revof(r):
1988 1983 return r.rev()
1989 1984
1990 1985 roots = sorted(roots, key=revof)
1991 1986 state = dict.fromkeys(rebaseset, revtodo)
1992 1987 emptyrebase = len(sortedsrc) == 1
1993 1988 for root in roots:
1994 1989 dest = repo[destmap[root.rev()]]
1995 1990 commonbase = root.ancestor(dest)
1996 1991 if commonbase == root:
1997 1992 raise error.Abort(_(b'source is ancestor of destination'))
1998 1993 if commonbase == dest:
1999 1994 wctx = repo[None]
2000 1995 if dest == wctx.p1():
2001 1996 # when rebasing to '.', it will use the current wd branch name
2002 1997 samebranch = root.branch() == wctx.branch()
2003 1998 else:
2004 1999 samebranch = root.branch() == dest.branch()
2005 2000 if not collapse and samebranch and dest in root.parents():
2006 2001 # mark the revision as done by setting its new revision
2007 2002 # equal to its old (current) revisions
2008 2003 state[root.rev()] = root.rev()
2009 2004 repo.ui.debug(b'source is a child of destination\n')
2010 2005 continue
2011 2006
2012 2007 emptyrebase = False
2013 2008 repo.ui.debug(b'rebase onto %s starting from %s\n' % (dest, root))
2014 2009 if emptyrebase:
2015 2010 return None
2016 2011 for rev in sorted(state):
2017 2012 parents = [p for p in repo.changelog.parentrevs(rev) if p != nullrev]
2018 2013 # if all parents of this revision are done, then so is this revision
2019 2014 if parents and all((state.get(p) == p for p in parents)):
2020 2015 state[rev] = rev
2021 2016 return originalwd, destmap, state
2022 2017
2023 2018
2024 2019 def clearrebased(
2025 2020 ui,
2026 2021 repo,
2027 2022 destmap,
2028 2023 state,
2029 2024 skipped,
2030 2025 collapsedas=None,
2031 2026 keepf=False,
2032 2027 fm=None,
2033 2028 backup=True,
2034 2029 ):
2035 2030 """dispose of rebased revision at the end of the rebase
2036 2031
2037 2032 If `collapsedas` is not None, the rebase was a collapse whose result if the
2038 2033 `collapsedas` node.
2039 2034
2040 2035 If `keepf` is not True, the rebase has --keep set and no nodes should be
2041 2036 removed (but bookmarks still need to be moved).
2042 2037
2043 2038 If `backup` is False, no backup will be stored when stripping rebased
2044 2039 revisions.
2045 2040 """
2046 2041 tonode = repo.changelog.node
2047 2042 replacements = {}
2048 2043 moves = {}
2049 2044 stripcleanup = not obsolete.isenabled(repo, obsolete.createmarkersopt)
2050 2045
2051 2046 collapsednodes = []
2052 2047 for rev, newrev in sorted(state.items()):
2053 2048 if newrev >= 0 and newrev != rev:
2054 2049 oldnode = tonode(rev)
2055 2050 newnode = collapsedas or tonode(newrev)
2056 2051 moves[oldnode] = newnode
2057 2052 succs = None
2058 2053 if rev in skipped:
2059 2054 if stripcleanup or not repo[rev].obsolete():
2060 2055 succs = ()
2061 2056 elif collapsedas:
2062 2057 collapsednodes.append(oldnode)
2063 2058 else:
2064 2059 succs = (newnode,)
2065 2060 if succs is not None:
2066 2061 replacements[(oldnode,)] = succs
2067 2062 if collapsednodes:
2068 2063 replacements[tuple(collapsednodes)] = (collapsedas,)
2069 2064 if fm:
2070 2065 hf = fm.hexfunc
2071 2066 fl = fm.formatlist
2072 2067 fd = fm.formatdict
2073 2068 changes = {}
2074 2069 for oldns, newn in pycompat.iteritems(replacements):
2075 2070 for oldn in oldns:
2076 2071 changes[hf(oldn)] = fl([hf(n) for n in newn], name=b'node')
2077 2072 nodechanges = fd(changes, key=b"oldnode", value=b"newnodes")
2078 2073 fm.data(nodechanges=nodechanges)
2079 2074 if keepf:
2080 2075 replacements = {}
2081 2076 scmutil.cleanupnodes(repo, replacements, b'rebase', moves, backup=backup)
2082 2077
2083 2078
2084 2079 def pullrebase(orig, ui, repo, *args, **opts):
2085 2080 """Call rebase after pull if the latter has been invoked with --rebase"""
2086 2081 if opts.get('rebase'):
2087 2082 if ui.configbool(b'commands', b'rebase.requiredest'):
2088 2083 msg = _(b'rebase destination required by configuration')
2089 2084 hint = _(b'use hg pull followed by hg rebase -d DEST')
2090 2085 raise error.Abort(msg, hint=hint)
2091 2086
2092 2087 with repo.wlock(), repo.lock():
2093 2088 if opts.get('update'):
2094 2089 del opts['update']
2095 2090 ui.debug(
2096 2091 b'--update and --rebase are not compatible, ignoring '
2097 2092 b'the update flag\n'
2098 2093 )
2099 2094
2100 2095 cmdutil.checkunfinished(repo, skipmerge=True)
2101 2096 cmdutil.bailifchanged(
2102 2097 repo,
2103 2098 hint=_(
2104 2099 b'cannot pull with rebase: '
2105 2100 b'please commit or shelve your changes first'
2106 2101 ),
2107 2102 )
2108 2103
2109 2104 revsprepull = len(repo)
2110 2105 origpostincoming = commands.postincoming
2111 2106
2112 2107 def _dummy(*args, **kwargs):
2113 2108 pass
2114 2109
2115 2110 commands.postincoming = _dummy
2116 2111 try:
2117 2112 ret = orig(ui, repo, *args, **opts)
2118 2113 finally:
2119 2114 commands.postincoming = origpostincoming
2120 2115 revspostpull = len(repo)
2121 2116 if revspostpull > revsprepull:
2122 2117 # --rev option from pull conflict with rebase own --rev
2123 2118 # dropping it
2124 2119 if 'rev' in opts:
2125 2120 del opts['rev']
2126 2121 # positional argument from pull conflicts with rebase's own
2127 2122 # --source.
2128 2123 if 'source' in opts:
2129 2124 del opts['source']
2130 2125 # revsprepull is the len of the repo, not revnum of tip.
2131 2126 destspace = list(repo.changelog.revs(start=revsprepull))
2132 2127 opts['_destspace'] = destspace
2133 2128 try:
2134 2129 rebase(ui, repo, **opts)
2135 2130 except error.NoMergeDestAbort:
2136 2131 # we can maybe update instead
2137 2132 rev, _a, _b = destutil.destupdate(repo)
2138 2133 if rev == repo[b'.'].rev():
2139 2134 ui.status(_(b'nothing to rebase\n'))
2140 2135 else:
2141 2136 ui.status(_(b'nothing to rebase - updating instead\n'))
2142 2137 # not passing argument to get the bare update behavior
2143 2138 # with warning and trumpets
2144 2139 commands.update(ui, repo)
2145 2140 else:
2146 2141 if opts.get('tool'):
2147 2142 raise error.Abort(_(b'--tool can only be used with --rebase'))
2148 2143 ret = orig(ui, repo, *args, **opts)
2149 2144
2150 2145 return ret
2151 2146
2152 2147
2153 2148 def _filterobsoleterevs(repo, revs):
2154 2149 """returns a set of the obsolete revisions in revs"""
2155 2150 return {r for r in revs if repo[r].obsolete()}
2156 2151
2157 2152
2158 2153 def _computeobsoletenotrebased(repo, rebaseobsrevs, destmap):
2159 2154 """Return (obsoletenotrebased, obsoletewithoutsuccessorindestination).
2160 2155
2161 2156 `obsoletenotrebased` is a mapping mapping obsolete => successor for all
2162 2157 obsolete nodes to be rebased given in `rebaseobsrevs`.
2163 2158
2164 2159 `obsoletewithoutsuccessorindestination` is a set with obsolete revisions
2165 2160 without a successor in destination.
2166 2161
2167 2162 `obsoleteextinctsuccessors` is a set of obsolete revisions with only
2168 2163 obsolete successors.
2169 2164 """
2170 2165 obsoletenotrebased = {}
2171 2166 obsoletewithoutsuccessorindestination = set()
2172 2167 obsoleteextinctsuccessors = set()
2173 2168
2174 2169 assert repo.filtername is None
2175 2170 cl = repo.changelog
2176 2171 get_rev = cl.index.get_rev
2177 2172 extinctrevs = set(repo.revs(b'extinct()'))
2178 2173 for srcrev in rebaseobsrevs:
2179 2174 srcnode = cl.node(srcrev)
2180 2175 # XXX: more advanced APIs are required to handle split correctly
2181 2176 successors = set(obsutil.allsuccessors(repo.obsstore, [srcnode]))
2182 2177 # obsutil.allsuccessors includes node itself
2183 2178 successors.remove(srcnode)
2184 2179 succrevs = {get_rev(s) for s in successors}
2185 2180 succrevs.discard(None)
2186 2181 if succrevs.issubset(extinctrevs):
2187 2182 # all successors are extinct
2188 2183 obsoleteextinctsuccessors.add(srcrev)
2189 2184 if not successors:
2190 2185 # no successor
2191 2186 obsoletenotrebased[srcrev] = None
2192 2187 else:
2193 2188 dstrev = destmap[srcrev]
2194 2189 for succrev in succrevs:
2195 2190 if cl.isancestorrev(succrev, dstrev):
2196 2191 obsoletenotrebased[srcrev] = succrev
2197 2192 break
2198 2193 else:
2199 2194 # If 'srcrev' has a successor in rebase set but none in
2200 2195 # destination (which would be catched above), we shall skip it
2201 2196 # and its descendants to avoid divergence.
2202 2197 if srcrev in extinctrevs or any(s in destmap for s in succrevs):
2203 2198 obsoletewithoutsuccessorindestination.add(srcrev)
2204 2199
2205 2200 return (
2206 2201 obsoletenotrebased,
2207 2202 obsoletewithoutsuccessorindestination,
2208 2203 obsoleteextinctsuccessors,
2209 2204 )
2210 2205
2211 2206
2212 2207 def abortrebase(ui, repo):
2213 2208 with repo.wlock(), repo.lock():
2214 2209 rbsrt = rebaseruntime(repo, ui)
2215 2210 rbsrt._prepareabortorcontinue(isabort=True)
2216 2211
2217 2212
2218 2213 def continuerebase(ui, repo):
2219 2214 with repo.wlock(), repo.lock():
2220 2215 rbsrt = rebaseruntime(repo, ui)
2221 2216 ms = mergestatemod.mergestate.read(repo)
2222 2217 mergeutil.checkunresolved(ms)
2223 2218 retcode = rbsrt._prepareabortorcontinue(isabort=False)
2224 2219 if retcode is not None:
2225 2220 return retcode
2226 2221 rbsrt._performrebase(None)
2227 2222 rbsrt._finishrebase()
2228 2223
2229 2224
2230 2225 def summaryhook(ui, repo):
2231 2226 if not repo.vfs.exists(b'rebasestate'):
2232 2227 return
2233 2228 try:
2234 2229 rbsrt = rebaseruntime(repo, ui, {})
2235 2230 rbsrt.restorestatus()
2236 2231 state = rbsrt.state
2237 2232 except error.RepoLookupError:
2238 2233 # i18n: column positioning for "hg summary"
2239 2234 msg = _(b'rebase: (use "hg rebase --abort" to clear broken state)\n')
2240 2235 ui.write(msg)
2241 2236 return
2242 2237 numrebased = len([i for i in pycompat.itervalues(state) if i >= 0])
2243 2238 # i18n: column positioning for "hg summary"
2244 2239 ui.write(
2245 2240 _(b'rebase: %s, %s (rebase --continue)\n')
2246 2241 % (
2247 2242 ui.label(_(b'%d rebased'), b'rebase.rebased') % numrebased,
2248 2243 ui.label(_(b'%d remaining'), b'rebase.remaining')
2249 2244 % (len(state) - numrebased),
2250 2245 )
2251 2246 )
2252 2247
2253 2248
2254 2249 def uisetup(ui):
2255 2250 # Replace pull with a decorator to provide --rebase option
2256 2251 entry = extensions.wrapcommand(commands.table, b'pull', pullrebase)
2257 2252 entry[1].append(
2258 2253 (b'', b'rebase', None, _(b"rebase working directory to branch head"))
2259 2254 )
2260 2255 entry[1].append((b't', b'tool', b'', _(b"specify merge tool for rebase")))
2261 2256 cmdutil.summaryhooks.add(b'rebase', summaryhook)
2262 2257 statemod.addunfinished(
2263 2258 b'rebase',
2264 2259 fname=b'rebasestate',
2265 2260 stopflag=True,
2266 2261 continueflag=True,
2267 2262 abortfunc=abortrebase,
2268 2263 continuefunc=continuerebase,
2269 2264 )
@@ -1,3890 +1,3898 b''
1 1 # cmdutil.py - help for command processing in mercurial
2 2 #
3 3 # Copyright 2005-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 copy as copymod
11 11 import errno
12 12 import os
13 13 import re
14 14
15 15 from .i18n import _
16 16 from .node import (
17 17 hex,
18 18 nullid,
19 19 short,
20 20 )
21 21 from .pycompat import (
22 22 getattr,
23 23 open,
24 24 setattr,
25 25 )
26 26 from .thirdparty import attr
27 27
28 28 from . import (
29 29 bookmarks,
30 30 changelog,
31 31 copies,
32 32 crecord as crecordmod,
33 33 dirstateguard,
34 34 encoding,
35 35 error,
36 36 formatter,
37 37 logcmdutil,
38 38 match as matchmod,
39 39 merge as mergemod,
40 40 mergestate as mergestatemod,
41 41 mergeutil,
42 42 obsolete,
43 43 patch,
44 44 pathutil,
45 45 phases,
46 46 pycompat,
47 47 repair,
48 48 revlog,
49 49 rewriteutil,
50 50 scmutil,
51 51 state as statemod,
52 52 subrepoutil,
53 53 templatekw,
54 54 templater,
55 55 util,
56 56 vfs as vfsmod,
57 57 )
58 58
59 59 from .utils import (
60 60 dateutil,
61 61 stringutil,
62 62 )
63 63
64 64 if pycompat.TYPE_CHECKING:
65 65 from typing import (
66 66 Any,
67 67 Dict,
68 68 )
69 69
70 70 for t in (Any, Dict):
71 71 assert t
72 72
73 73 stringio = util.stringio
74 74
75 75 # templates of common command options
76 76
77 77 dryrunopts = [
78 78 (b'n', b'dry-run', None, _(b'do not perform actions, just print output')),
79 79 ]
80 80
81 81 confirmopts = [
82 82 (b'', b'confirm', None, _(b'ask before applying actions')),
83 83 ]
84 84
85 85 remoteopts = [
86 86 (b'e', b'ssh', b'', _(b'specify ssh command to use'), _(b'CMD')),
87 87 (
88 88 b'',
89 89 b'remotecmd',
90 90 b'',
91 91 _(b'specify hg command to run on the remote side'),
92 92 _(b'CMD'),
93 93 ),
94 94 (
95 95 b'',
96 96 b'insecure',
97 97 None,
98 98 _(b'do not verify server certificate (ignoring web.cacerts config)'),
99 99 ),
100 100 ]
101 101
102 102 walkopts = [
103 103 (
104 104 b'I',
105 105 b'include',
106 106 [],
107 107 _(b'include names matching the given patterns'),
108 108 _(b'PATTERN'),
109 109 ),
110 110 (
111 111 b'X',
112 112 b'exclude',
113 113 [],
114 114 _(b'exclude names matching the given patterns'),
115 115 _(b'PATTERN'),
116 116 ),
117 117 ]
118 118
119 119 commitopts = [
120 120 (b'm', b'message', b'', _(b'use text as commit message'), _(b'TEXT')),
121 121 (b'l', b'logfile', b'', _(b'read commit message from file'), _(b'FILE')),
122 122 ]
123 123
124 124 commitopts2 = [
125 125 (
126 126 b'd',
127 127 b'date',
128 128 b'',
129 129 _(b'record the specified date as commit date'),
130 130 _(b'DATE'),
131 131 ),
132 132 (
133 133 b'u',
134 134 b'user',
135 135 b'',
136 136 _(b'record the specified user as committer'),
137 137 _(b'USER'),
138 138 ),
139 139 ]
140 140
141 141 commitopts3 = [
142 142 (b'D', b'currentdate', None, _(b'record the current date as commit date')),
143 143 (b'U', b'currentuser', None, _(b'record the current user as committer')),
144 144 ]
145 145
146 146 formatteropts = [
147 147 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
148 148 ]
149 149
150 150 templateopts = [
151 151 (
152 152 b'',
153 153 b'style',
154 154 b'',
155 155 _(b'display using template map file (DEPRECATED)'),
156 156 _(b'STYLE'),
157 157 ),
158 158 (b'T', b'template', b'', _(b'display with template'), _(b'TEMPLATE')),
159 159 ]
160 160
161 161 logopts = [
162 162 (b'p', b'patch', None, _(b'show patch')),
163 163 (b'g', b'git', None, _(b'use git extended diff format')),
164 164 (b'l', b'limit', b'', _(b'limit number of changes displayed'), _(b'NUM')),
165 165 (b'M', b'no-merges', None, _(b'do not show merges')),
166 166 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
167 167 (b'G', b'graph', None, _(b"show the revision DAG")),
168 168 ] + templateopts
169 169
170 170 diffopts = [
171 171 (b'a', b'text', None, _(b'treat all files as text')),
172 172 (
173 173 b'g',
174 174 b'git',
175 175 None,
176 176 _(b'use git extended diff format (DEFAULT: diff.git)'),
177 177 ),
178 178 (b'', b'binary', None, _(b'generate binary diffs in git mode (default)')),
179 179 (b'', b'nodates', None, _(b'omit dates from diff headers')),
180 180 ]
181 181
182 182 diffwsopts = [
183 183 (
184 184 b'w',
185 185 b'ignore-all-space',
186 186 None,
187 187 _(b'ignore white space when comparing lines'),
188 188 ),
189 189 (
190 190 b'b',
191 191 b'ignore-space-change',
192 192 None,
193 193 _(b'ignore changes in the amount of white space'),
194 194 ),
195 195 (
196 196 b'B',
197 197 b'ignore-blank-lines',
198 198 None,
199 199 _(b'ignore changes whose lines are all blank'),
200 200 ),
201 201 (
202 202 b'Z',
203 203 b'ignore-space-at-eol',
204 204 None,
205 205 _(b'ignore changes in whitespace at EOL'),
206 206 ),
207 207 ]
208 208
209 209 diffopts2 = (
210 210 [
211 211 (b'', b'noprefix', None, _(b'omit a/ and b/ prefixes from filenames')),
212 212 (
213 213 b'p',
214 214 b'show-function',
215 215 None,
216 216 _(
217 217 b'show which function each change is in (DEFAULT: diff.showfunc)'
218 218 ),
219 219 ),
220 220 (b'', b'reverse', None, _(b'produce a diff that undoes the changes')),
221 221 ]
222 222 + diffwsopts
223 223 + [
224 224 (
225 225 b'U',
226 226 b'unified',
227 227 b'',
228 228 _(b'number of lines of context to show'),
229 229 _(b'NUM'),
230 230 ),
231 231 (b'', b'stat', None, _(b'output diffstat-style summary of changes')),
232 232 (
233 233 b'',
234 234 b'root',
235 235 b'',
236 236 _(b'produce diffs relative to subdirectory'),
237 237 _(b'DIR'),
238 238 ),
239 239 ]
240 240 )
241 241
242 242 mergetoolopts = [
243 243 (b't', b'tool', b'', _(b'specify merge tool'), _(b'TOOL')),
244 244 ]
245 245
246 246 similarityopts = [
247 247 (
248 248 b's',
249 249 b'similarity',
250 250 b'',
251 251 _(b'guess renamed files by similarity (0<=s<=100)'),
252 252 _(b'SIMILARITY'),
253 253 )
254 254 ]
255 255
256 256 subrepoopts = [(b'S', b'subrepos', None, _(b'recurse into subrepositories'))]
257 257
258 258 debugrevlogopts = [
259 259 (b'c', b'changelog', False, _(b'open changelog')),
260 260 (b'm', b'manifest', False, _(b'open manifest')),
261 261 (b'', b'dir', b'', _(b'open directory manifest')),
262 262 ]
263 263
264 264 # special string such that everything below this line will be ingored in the
265 265 # editor text
266 266 _linebelow = b"^HG: ------------------------ >8 ------------------------$"
267 267
268 268
269 269 def check_at_most_one_arg(opts, *args):
270 270 """abort if more than one of the arguments are in opts
271 271
272 272 Returns the unique argument or None if none of them were specified.
273 273 """
274 274
275 275 def to_display(name):
276 276 return pycompat.sysbytes(name).replace(b'_', b'-')
277 277
278 278 previous = None
279 279 for x in args:
280 280 if opts.get(x):
281 281 if previous:
282 282 raise error.Abort(
283 283 _(b'cannot specify both --%s and --%s')
284 284 % (to_display(previous), to_display(x))
285 285 )
286 286 previous = x
287 287 return previous
288 288
289 289
290 290 def check_incompatible_arguments(opts, first, others):
291 291 """abort if the first argument is given along with any of the others
292 292
293 293 Unlike check_at_most_one_arg(), `others` are not mutually exclusive
294 294 among themselves, and they're passed as a single collection.
295 295 """
296 296 for other in others:
297 297 check_at_most_one_arg(opts, first, other)
298 298
299 299
300 300 def resolvecommitoptions(ui, opts):
301 301 """modify commit options dict to handle related options
302 302
303 303 The return value indicates that ``rewrite.update-timestamp`` is the reason
304 304 the ``date`` option is set.
305 305 """
306 306 check_at_most_one_arg(opts, b'date', b'currentdate')
307 307 check_at_most_one_arg(opts, b'user', b'currentuser')
308 308
309 309 datemaydiffer = False # date-only change should be ignored?
310 310
311 311 if opts.get(b'currentdate'):
312 312 opts[b'date'] = b'%d %d' % dateutil.makedate()
313 313 elif (
314 314 not opts.get(b'date')
315 315 and ui.configbool(b'rewrite', b'update-timestamp')
316 316 and opts.get(b'currentdate') is None
317 317 ):
318 318 opts[b'date'] = b'%d %d' % dateutil.makedate()
319 319 datemaydiffer = True
320 320
321 321 if opts.get(b'currentuser'):
322 322 opts[b'user'] = ui.username()
323 323
324 324 return datemaydiffer
325 325
326 326
327 327 def checknotesize(ui, opts):
328 328 """ make sure note is of valid format """
329 329
330 330 note = opts.get(b'note')
331 331 if not note:
332 332 return
333 333
334 334 if len(note) > 255:
335 335 raise error.Abort(_(b"cannot store a note of more than 255 bytes"))
336 336 if b'\n' in note:
337 337 raise error.Abort(_(b"note cannot contain a newline"))
338 338
339 339
340 340 def ishunk(x):
341 341 hunkclasses = (crecordmod.uihunk, patch.recordhunk)
342 342 return isinstance(x, hunkclasses)
343 343
344 344
345 345 def newandmodified(chunks, originalchunks):
346 346 newlyaddedandmodifiedfiles = set()
347 347 alsorestore = set()
348 348 for chunk in chunks:
349 349 if (
350 350 ishunk(chunk)
351 351 and chunk.header.isnewfile()
352 352 and chunk not in originalchunks
353 353 ):
354 354 newlyaddedandmodifiedfiles.add(chunk.header.filename())
355 355 alsorestore.update(
356 356 set(chunk.header.files()) - {chunk.header.filename()}
357 357 )
358 358 return newlyaddedandmodifiedfiles, alsorestore
359 359
360 360
361 361 def parsealiases(cmd):
362 362 return cmd.split(b"|")
363 363
364 364
365 365 def setupwrapcolorwrite(ui):
366 366 # wrap ui.write so diff output can be labeled/colorized
367 367 def wrapwrite(orig, *args, **kw):
368 368 label = kw.pop('label', b'')
369 369 for chunk, l in patch.difflabel(lambda: args):
370 370 orig(chunk, label=label + l)
371 371
372 372 oldwrite = ui.write
373 373
374 374 def wrap(*args, **kwargs):
375 375 return wrapwrite(oldwrite, *args, **kwargs)
376 376
377 377 setattr(ui, 'write', wrap)
378 378 return oldwrite
379 379
380 380
381 381 def filterchunks(ui, originalhunks, usecurses, testfile, match, operation=None):
382 382 try:
383 383 if usecurses:
384 384 if testfile:
385 385 recordfn = crecordmod.testdecorator(
386 386 testfile, crecordmod.testchunkselector
387 387 )
388 388 else:
389 389 recordfn = crecordmod.chunkselector
390 390
391 391 return crecordmod.filterpatch(
392 392 ui, originalhunks, recordfn, operation
393 393 )
394 394 except crecordmod.fallbackerror as e:
395 395 ui.warn(b'%s\n' % e)
396 396 ui.warn(_(b'falling back to text mode\n'))
397 397
398 398 return patch.filterpatch(ui, originalhunks, match, operation)
399 399
400 400
401 401 def recordfilter(ui, originalhunks, match, operation=None):
402 402 """ Prompts the user to filter the originalhunks and return a list of
403 403 selected hunks.
404 404 *operation* is used for to build ui messages to indicate the user what
405 405 kind of filtering they are doing: reverting, committing, shelving, etc.
406 406 (see patch.filterpatch).
407 407 """
408 408 usecurses = crecordmod.checkcurses(ui)
409 409 testfile = ui.config(b'experimental', b'crecordtest')
410 410 oldwrite = setupwrapcolorwrite(ui)
411 411 try:
412 412 newchunks, newopts = filterchunks(
413 413 ui, originalhunks, usecurses, testfile, match, operation
414 414 )
415 415 finally:
416 416 ui.write = oldwrite
417 417 return newchunks, newopts
418 418
419 419
420 420 def dorecord(
421 421 ui, repo, commitfunc, cmdsuggest, backupall, filterfn, *pats, **opts
422 422 ):
423 423 opts = pycompat.byteskwargs(opts)
424 424 if not ui.interactive():
425 425 if cmdsuggest:
426 426 msg = _(b'running non-interactively, use %s instead') % cmdsuggest
427 427 else:
428 428 msg = _(b'running non-interactively')
429 429 raise error.Abort(msg)
430 430
431 431 # make sure username is set before going interactive
432 432 if not opts.get(b'user'):
433 433 ui.username() # raise exception, username not provided
434 434
435 435 def recordfunc(ui, repo, message, match, opts):
436 436 """This is generic record driver.
437 437
438 438 Its job is to interactively filter local changes, and
439 439 accordingly prepare working directory into a state in which the
440 440 job can be delegated to a non-interactive commit command such as
441 441 'commit' or 'qrefresh'.
442 442
443 443 After the actual job is done by non-interactive command, the
444 444 working directory is restored to its original state.
445 445
446 446 In the end we'll record interesting changes, and everything else
447 447 will be left in place, so the user can continue working.
448 448 """
449 449 if not opts.get(b'interactive-unshelve'):
450 450 checkunfinished(repo, commit=True)
451 451 wctx = repo[None]
452 452 merge = len(wctx.parents()) > 1
453 453 if merge:
454 454 raise error.Abort(
455 455 _(
456 456 b'cannot partially commit a merge '
457 457 b'(use "hg commit" instead)'
458 458 )
459 459 )
460 460
461 461 def fail(f, msg):
462 462 raise error.Abort(b'%s: %s' % (f, msg))
463 463
464 464 force = opts.get(b'force')
465 465 if not force:
466 466 match = matchmod.badmatch(match, fail)
467 467
468 468 status = repo.status(match=match)
469 469
470 470 overrides = {(b'ui', b'commitsubrepos'): True}
471 471
472 472 with repo.ui.configoverride(overrides, b'record'):
473 473 # subrepoutil.precommit() modifies the status
474 474 tmpstatus = scmutil.status(
475 475 copymod.copy(status.modified),
476 476 copymod.copy(status.added),
477 477 copymod.copy(status.removed),
478 478 copymod.copy(status.deleted),
479 479 copymod.copy(status.unknown),
480 480 copymod.copy(status.ignored),
481 481 copymod.copy(status.clean), # pytype: disable=wrong-arg-count
482 482 )
483 483
484 484 # Force allows -X subrepo to skip the subrepo.
485 485 subs, commitsubs, newstate = subrepoutil.precommit(
486 486 repo.ui, wctx, tmpstatus, match, force=True
487 487 )
488 488 for s in subs:
489 489 if s in commitsubs:
490 490 dirtyreason = wctx.sub(s).dirtyreason(True)
491 491 raise error.Abort(dirtyreason)
492 492
493 493 if not force:
494 494 repo.checkcommitpatterns(wctx, match, status, fail)
495 495 diffopts = patch.difffeatureopts(
496 496 ui,
497 497 opts=opts,
498 498 whitespace=True,
499 499 section=b'commands',
500 500 configprefix=b'commit.interactive.',
501 501 )
502 502 diffopts.nodates = True
503 503 diffopts.git = True
504 504 diffopts.showfunc = True
505 505 originaldiff = patch.diff(repo, changes=status, opts=diffopts)
506 506 originalchunks = patch.parsepatch(originaldiff)
507 507 match = scmutil.match(repo[None], pats)
508 508
509 509 # 1. filter patch, since we are intending to apply subset of it
510 510 try:
511 511 chunks, newopts = filterfn(ui, originalchunks, match)
512 512 except error.PatchError as err:
513 513 raise error.Abort(_(b'error parsing patch: %s') % err)
514 514 opts.update(newopts)
515 515
516 516 # We need to keep a backup of files that have been newly added and
517 517 # modified during the recording process because there is a previous
518 518 # version without the edit in the workdir. We also will need to restore
519 519 # files that were the sources of renames so that the patch application
520 520 # works.
521 521 newlyaddedandmodifiedfiles, alsorestore = newandmodified(
522 522 chunks, originalchunks
523 523 )
524 524 contenders = set()
525 525 for h in chunks:
526 526 try:
527 527 contenders.update(set(h.files()))
528 528 except AttributeError:
529 529 pass
530 530
531 531 changed = status.modified + status.added + status.removed
532 532 newfiles = [f for f in changed if f in contenders]
533 533 if not newfiles:
534 534 ui.status(_(b'no changes to record\n'))
535 535 return 0
536 536
537 537 modified = set(status.modified)
538 538
539 539 # 2. backup changed files, so we can restore them in the end
540 540
541 541 if backupall:
542 542 tobackup = changed
543 543 else:
544 544 tobackup = [
545 545 f
546 546 for f in newfiles
547 547 if f in modified or f in newlyaddedandmodifiedfiles
548 548 ]
549 549 backups = {}
550 550 if tobackup:
551 551 backupdir = repo.vfs.join(b'record-backups')
552 552 try:
553 553 os.mkdir(backupdir)
554 554 except OSError as err:
555 555 if err.errno != errno.EEXIST:
556 556 raise
557 557 try:
558 558 # backup continues
559 559 for f in tobackup:
560 560 fd, tmpname = pycompat.mkstemp(
561 561 prefix=os.path.basename(f) + b'.', dir=backupdir
562 562 )
563 563 os.close(fd)
564 564 ui.debug(b'backup %r as %r\n' % (f, tmpname))
565 565 util.copyfile(repo.wjoin(f), tmpname, copystat=True)
566 566 backups[f] = tmpname
567 567
568 568 fp = stringio()
569 569 for c in chunks:
570 570 fname = c.filename()
571 571 if fname in backups:
572 572 c.write(fp)
573 573 dopatch = fp.tell()
574 574 fp.seek(0)
575 575
576 576 # 2.5 optionally review / modify patch in text editor
577 577 if opts.get(b'review', False):
578 578 patchtext = (
579 579 crecordmod.diffhelptext
580 580 + crecordmod.patchhelptext
581 581 + fp.read()
582 582 )
583 583 reviewedpatch = ui.edit(
584 584 patchtext, b"", action=b"diff", repopath=repo.path
585 585 )
586 586 fp.truncate(0)
587 587 fp.write(reviewedpatch)
588 588 fp.seek(0)
589 589
590 590 [os.unlink(repo.wjoin(c)) for c in newlyaddedandmodifiedfiles]
591 591 # 3a. apply filtered patch to clean repo (clean)
592 592 if backups:
593 593 m = scmutil.matchfiles(repo, set(backups.keys()) | alsorestore)
594 594 mergemod.revert_to(repo[b'.'], matcher=m)
595 595
596 596 # 3b. (apply)
597 597 if dopatch:
598 598 try:
599 599 ui.debug(b'applying patch\n')
600 600 ui.debug(fp.getvalue())
601 601 patch.internalpatch(ui, repo, fp, 1, eolmode=None)
602 602 except error.PatchError as err:
603 603 raise error.Abort(pycompat.bytestr(err))
604 604 del fp
605 605
606 606 # 4. We prepared working directory according to filtered
607 607 # patch. Now is the time to delegate the job to
608 608 # commit/qrefresh or the like!
609 609
610 610 # Make all of the pathnames absolute.
611 611 newfiles = [repo.wjoin(nf) for nf in newfiles]
612 612 return commitfunc(ui, repo, *newfiles, **pycompat.strkwargs(opts))
613 613 finally:
614 614 # 5. finally restore backed-up files
615 615 try:
616 616 dirstate = repo.dirstate
617 617 for realname, tmpname in pycompat.iteritems(backups):
618 618 ui.debug(b'restoring %r to %r\n' % (tmpname, realname))
619 619
620 620 if dirstate[realname] == b'n':
621 621 # without normallookup, restoring timestamp
622 622 # may cause partially committed files
623 623 # to be treated as unmodified
624 624 dirstate.normallookup(realname)
625 625
626 626 # copystat=True here and above are a hack to trick any
627 627 # editors that have f open that we haven't modified them.
628 628 #
629 629 # Also note that this racy as an editor could notice the
630 630 # file's mtime before we've finished writing it.
631 631 util.copyfile(tmpname, repo.wjoin(realname), copystat=True)
632 632 os.unlink(tmpname)
633 633 if tobackup:
634 634 os.rmdir(backupdir)
635 635 except OSError:
636 636 pass
637 637
638 638 def recordinwlock(ui, repo, message, match, opts):
639 639 with repo.wlock():
640 640 return recordfunc(ui, repo, message, match, opts)
641 641
642 642 return commit(ui, repo, recordinwlock, pats, opts)
643 643
644 644
645 645 class dirnode(object):
646 646 """
647 647 Represent a directory in user working copy with information required for
648 648 the purpose of tersing its status.
649 649
650 650 path is the path to the directory, without a trailing '/'
651 651
652 652 statuses is a set of statuses of all files in this directory (this includes
653 653 all the files in all the subdirectories too)
654 654
655 655 files is a list of files which are direct child of this directory
656 656
657 657 subdirs is a dictionary of sub-directory name as the key and it's own
658 658 dirnode object as the value
659 659 """
660 660
661 661 def __init__(self, dirpath):
662 662 self.path = dirpath
663 663 self.statuses = set()
664 664 self.files = []
665 665 self.subdirs = {}
666 666
667 667 def _addfileindir(self, filename, status):
668 668 """Add a file in this directory as a direct child."""
669 669 self.files.append((filename, status))
670 670
671 671 def addfile(self, filename, status):
672 672 """
673 673 Add a file to this directory or to its direct parent directory.
674 674
675 675 If the file is not direct child of this directory, we traverse to the
676 676 directory of which this file is a direct child of and add the file
677 677 there.
678 678 """
679 679
680 680 # the filename contains a path separator, it means it's not the direct
681 681 # child of this directory
682 682 if b'/' in filename:
683 683 subdir, filep = filename.split(b'/', 1)
684 684
685 685 # does the dirnode object for subdir exists
686 686 if subdir not in self.subdirs:
687 687 subdirpath = pathutil.join(self.path, subdir)
688 688 self.subdirs[subdir] = dirnode(subdirpath)
689 689
690 690 # try adding the file in subdir
691 691 self.subdirs[subdir].addfile(filep, status)
692 692
693 693 else:
694 694 self._addfileindir(filename, status)
695 695
696 696 if status not in self.statuses:
697 697 self.statuses.add(status)
698 698
699 699 def iterfilepaths(self):
700 700 """Yield (status, path) for files directly under this directory."""
701 701 for f, st in self.files:
702 702 yield st, pathutil.join(self.path, f)
703 703
704 704 def tersewalk(self, terseargs):
705 705 """
706 706 Yield (status, path) obtained by processing the status of this
707 707 dirnode.
708 708
709 709 terseargs is the string of arguments passed by the user with `--terse`
710 710 flag.
711 711
712 712 Following are the cases which can happen:
713 713
714 714 1) All the files in the directory (including all the files in its
715 715 subdirectories) share the same status and the user has asked us to terse
716 716 that status. -> yield (status, dirpath). dirpath will end in '/'.
717 717
718 718 2) Otherwise, we do following:
719 719
720 720 a) Yield (status, filepath) for all the files which are in this
721 721 directory (only the ones in this directory, not the subdirs)
722 722
723 723 b) Recurse the function on all the subdirectories of this
724 724 directory
725 725 """
726 726
727 727 if len(self.statuses) == 1:
728 728 onlyst = self.statuses.pop()
729 729
730 730 # Making sure we terse only when the status abbreviation is
731 731 # passed as terse argument
732 732 if onlyst in terseargs:
733 733 yield onlyst, self.path + b'/'
734 734 return
735 735
736 736 # add the files to status list
737 737 for st, fpath in self.iterfilepaths():
738 738 yield st, fpath
739 739
740 740 # recurse on the subdirs
741 741 for dirobj in self.subdirs.values():
742 742 for st, fpath in dirobj.tersewalk(terseargs):
743 743 yield st, fpath
744 744
745 745
746 746 def tersedir(statuslist, terseargs):
747 747 """
748 748 Terse the status if all the files in a directory shares the same status.
749 749
750 750 statuslist is scmutil.status() object which contains a list of files for
751 751 each status.
752 752 terseargs is string which is passed by the user as the argument to `--terse`
753 753 flag.
754 754
755 755 The function makes a tree of objects of dirnode class, and at each node it
756 756 stores the information required to know whether we can terse a certain
757 757 directory or not.
758 758 """
759 759 # the order matters here as that is used to produce final list
760 760 allst = (b'm', b'a', b'r', b'd', b'u', b'i', b'c')
761 761
762 762 # checking the argument validity
763 763 for s in pycompat.bytestr(terseargs):
764 764 if s not in allst:
765 765 raise error.Abort(_(b"'%s' not recognized") % s)
766 766
767 767 # creating a dirnode object for the root of the repo
768 768 rootobj = dirnode(b'')
769 769 pstatus = (
770 770 b'modified',
771 771 b'added',
772 772 b'deleted',
773 773 b'clean',
774 774 b'unknown',
775 775 b'ignored',
776 776 b'removed',
777 777 )
778 778
779 779 tersedict = {}
780 780 for attrname in pstatus:
781 781 statuschar = attrname[0:1]
782 782 for f in getattr(statuslist, attrname):
783 783 rootobj.addfile(f, statuschar)
784 784 tersedict[statuschar] = []
785 785
786 786 # we won't be tersing the root dir, so add files in it
787 787 for st, fpath in rootobj.iterfilepaths():
788 788 tersedict[st].append(fpath)
789 789
790 790 # process each sub-directory and build tersedict
791 791 for subdir in rootobj.subdirs.values():
792 792 for st, f in subdir.tersewalk(terseargs):
793 793 tersedict[st].append(f)
794 794
795 795 tersedlist = []
796 796 for st in allst:
797 797 tersedict[st].sort()
798 798 tersedlist.append(tersedict[st])
799 799
800 800 return scmutil.status(*tersedlist)
801 801
802 802
803 803 def _commentlines(raw):
804 804 '''Surround lineswith a comment char and a new line'''
805 805 lines = raw.splitlines()
806 806 commentedlines = [b'# %s' % line for line in lines]
807 807 return b'\n'.join(commentedlines) + b'\n'
808 808
809 809
810 810 @attr.s(frozen=True)
811 811 class morestatus(object):
812 812 reporoot = attr.ib()
813 813 unfinishedop = attr.ib()
814 814 unfinishedmsg = attr.ib()
815 815 activemerge = attr.ib()
816 816 unresolvedpaths = attr.ib()
817 817 _formattedpaths = attr.ib(init=False, default=set())
818 818 _label = b'status.morestatus'
819 819
820 820 def formatfile(self, path, fm):
821 821 self._formattedpaths.add(path)
822 822 if self.activemerge and path in self.unresolvedpaths:
823 823 fm.data(unresolved=True)
824 824
825 825 def formatfooter(self, fm):
826 826 if self.unfinishedop or self.unfinishedmsg:
827 827 fm.startitem()
828 828 fm.data(itemtype=b'morestatus')
829 829
830 830 if self.unfinishedop:
831 831 fm.data(unfinished=self.unfinishedop)
832 832 statemsg = (
833 833 _(b'The repository is in an unfinished *%s* state.')
834 834 % self.unfinishedop
835 835 )
836 836 fm.plain(b'%s\n' % _commentlines(statemsg), label=self._label)
837 837 if self.unfinishedmsg:
838 838 fm.data(unfinishedmsg=self.unfinishedmsg)
839 839
840 840 # May also start new data items.
841 841 self._formatconflicts(fm)
842 842
843 843 if self.unfinishedmsg:
844 844 fm.plain(
845 845 b'%s\n' % _commentlines(self.unfinishedmsg), label=self._label
846 846 )
847 847
848 848 def _formatconflicts(self, fm):
849 849 if not self.activemerge:
850 850 return
851 851
852 852 if self.unresolvedpaths:
853 853 mergeliststr = b'\n'.join(
854 854 [
855 855 b' %s'
856 856 % util.pathto(self.reporoot, encoding.getcwd(), path)
857 857 for path in self.unresolvedpaths
858 858 ]
859 859 )
860 860 msg = (
861 861 _(
862 862 '''Unresolved merge conflicts:
863 863
864 864 %s
865 865
866 866 To mark files as resolved: hg resolve --mark FILE'''
867 867 )
868 868 % mergeliststr
869 869 )
870 870
871 871 # If any paths with unresolved conflicts were not previously
872 872 # formatted, output them now.
873 873 for f in self.unresolvedpaths:
874 874 if f in self._formattedpaths:
875 875 # Already output.
876 876 continue
877 877 fm.startitem()
878 878 # We can't claim to know the status of the file - it may just
879 879 # have been in one of the states that were not requested for
880 880 # display, so it could be anything.
881 881 fm.data(itemtype=b'file', path=f, unresolved=True)
882 882
883 883 else:
884 884 msg = _(b'No unresolved merge conflicts.')
885 885
886 886 fm.plain(b'%s\n' % _commentlines(msg), label=self._label)
887 887
888 888
889 889 def readmorestatus(repo):
890 890 """Returns a morestatus object if the repo has unfinished state."""
891 891 statetuple = statemod.getrepostate(repo)
892 892 mergestate = mergestatemod.mergestate.read(repo)
893 893 activemerge = mergestate.active()
894 894 if not statetuple and not activemerge:
895 895 return None
896 896
897 897 unfinishedop = unfinishedmsg = unresolved = None
898 898 if statetuple:
899 899 unfinishedop, unfinishedmsg = statetuple
900 900 if activemerge:
901 901 unresolved = sorted(mergestate.unresolved())
902 902 return morestatus(
903 903 repo.root, unfinishedop, unfinishedmsg, activemerge, unresolved
904 904 )
905 905
906 906
907 907 def findpossible(cmd, table, strict=False):
908 908 """
909 909 Return cmd -> (aliases, command table entry)
910 910 for each matching command.
911 911 Return debug commands (or their aliases) only if no normal command matches.
912 912 """
913 913 choice = {}
914 914 debugchoice = {}
915 915
916 916 if cmd in table:
917 917 # short-circuit exact matches, "log" alias beats "log|history"
918 918 keys = [cmd]
919 919 else:
920 920 keys = table.keys()
921 921
922 922 allcmds = []
923 923 for e in keys:
924 924 aliases = parsealiases(e)
925 925 allcmds.extend(aliases)
926 926 found = None
927 927 if cmd in aliases:
928 928 found = cmd
929 929 elif not strict:
930 930 for a in aliases:
931 931 if a.startswith(cmd):
932 932 found = a
933 933 break
934 934 if found is not None:
935 935 if aliases[0].startswith(b"debug") or found.startswith(b"debug"):
936 936 debugchoice[found] = (aliases, table[e])
937 937 else:
938 938 choice[found] = (aliases, table[e])
939 939
940 940 if not choice and debugchoice:
941 941 choice = debugchoice
942 942
943 943 return choice, allcmds
944 944
945 945
946 946 def findcmd(cmd, table, strict=True):
947 947 """Return (aliases, command table entry) for command string."""
948 948 choice, allcmds = findpossible(cmd, table, strict)
949 949
950 950 if cmd in choice:
951 951 return choice[cmd]
952 952
953 953 if len(choice) > 1:
954 954 clist = sorted(choice)
955 955 raise error.AmbiguousCommand(cmd, clist)
956 956
957 957 if choice:
958 958 return list(choice.values())[0]
959 959
960 960 raise error.UnknownCommand(cmd, allcmds)
961 961
962 962
963 963 def changebranch(ui, repo, revs, label, opts):
964 964 """ Change the branch name of given revs to label """
965 965
966 966 with repo.wlock(), repo.lock(), repo.transaction(b'branches'):
967 967 # abort in case of uncommitted merge or dirty wdir
968 968 bailifchanged(repo)
969 969 revs = scmutil.revrange(repo, revs)
970 970 if not revs:
971 971 raise error.Abort(b"empty revision set")
972 972 roots = repo.revs(b'roots(%ld)', revs)
973 973 if len(roots) > 1:
974 974 raise error.Abort(
975 975 _(b"cannot change branch of non-linear revisions")
976 976 )
977 977 rewriteutil.precheck(repo, revs, b'change branch of')
978 978
979 979 root = repo[roots.first()]
980 980 rpb = {parent.branch() for parent in root.parents()}
981 981 if (
982 982 not opts.get(b'force')
983 983 and label not in rpb
984 984 and label in repo.branchmap()
985 985 ):
986 986 raise error.Abort(_(b"a branch of the same name already exists"))
987 987
988 988 if repo.revs(b'obsolete() and %ld', revs):
989 989 raise error.Abort(
990 990 _(b"cannot change branch of a obsolete changeset")
991 991 )
992 992
993 993 # make sure only topological heads
994 994 if repo.revs(b'heads(%ld) - head()', revs):
995 995 raise error.Abort(_(b"cannot change branch in middle of a stack"))
996 996
997 997 replacements = {}
998 998 # avoid import cycle mercurial.cmdutil -> mercurial.context ->
999 999 # mercurial.subrepo -> mercurial.cmdutil
1000 1000 from . import context
1001 1001
1002 1002 for rev in revs:
1003 1003 ctx = repo[rev]
1004 1004 oldbranch = ctx.branch()
1005 1005 # check if ctx has same branch
1006 1006 if oldbranch == label:
1007 1007 continue
1008 1008
1009 1009 def filectxfn(repo, newctx, path):
1010 1010 try:
1011 1011 return ctx[path]
1012 1012 except error.ManifestLookupError:
1013 1013 return None
1014 1014
1015 1015 ui.debug(
1016 1016 b"changing branch of '%s' from '%s' to '%s'\n"
1017 1017 % (hex(ctx.node()), oldbranch, label)
1018 1018 )
1019 1019 extra = ctx.extra()
1020 1020 extra[b'branch_change'] = hex(ctx.node())
1021 1021 # While changing branch of set of linear commits, make sure that
1022 1022 # we base our commits on new parent rather than old parent which
1023 1023 # was obsoleted while changing the branch
1024 1024 p1 = ctx.p1().node()
1025 1025 p2 = ctx.p2().node()
1026 1026 if p1 in replacements:
1027 1027 p1 = replacements[p1][0]
1028 1028 if p2 in replacements:
1029 1029 p2 = replacements[p2][0]
1030 1030
1031 1031 mc = context.memctx(
1032 1032 repo,
1033 1033 (p1, p2),
1034 1034 ctx.description(),
1035 1035 ctx.files(),
1036 1036 filectxfn,
1037 1037 user=ctx.user(),
1038 1038 date=ctx.date(),
1039 1039 extra=extra,
1040 1040 branch=label,
1041 1041 )
1042 1042
1043 1043 newnode = repo.commitctx(mc)
1044 1044 replacements[ctx.node()] = (newnode,)
1045 1045 ui.debug(b'new node id is %s\n' % hex(newnode))
1046 1046
1047 1047 # create obsmarkers and move bookmarks
1048 1048 scmutil.cleanupnodes(
1049 1049 repo, replacements, b'branch-change', fixphase=True
1050 1050 )
1051 1051
1052 1052 # move the working copy too
1053 1053 wctx = repo[None]
1054 1054 # in-progress merge is a bit too complex for now.
1055 1055 if len(wctx.parents()) == 1:
1056 1056 newid = replacements.get(wctx.p1().node())
1057 1057 if newid is not None:
1058 1058 # avoid import cycle mercurial.cmdutil -> mercurial.hg ->
1059 1059 # mercurial.cmdutil
1060 1060 from . import hg
1061 1061
1062 1062 hg.update(repo, newid[0], quietempty=True)
1063 1063
1064 1064 ui.status(_(b"changed branch on %d changesets\n") % len(replacements))
1065 1065
1066 1066
1067 1067 def findrepo(p):
1068 1068 while not os.path.isdir(os.path.join(p, b".hg")):
1069 1069 oldp, p = p, os.path.dirname(p)
1070 1070 if p == oldp:
1071 1071 return None
1072 1072
1073 1073 return p
1074 1074
1075 1075
1076 1076 def bailifchanged(repo, merge=True, hint=None):
1077 1077 """ enforce the precondition that working directory must be clean.
1078 1078
1079 1079 'merge' can be set to false if a pending uncommitted merge should be
1080 1080 ignored (such as when 'update --check' runs).
1081 1081
1082 1082 'hint' is the usual hint given to Abort exception.
1083 1083 """
1084 1084
1085 1085 if merge and repo.dirstate.p2() != nullid:
1086 1086 raise error.Abort(_(b'outstanding uncommitted merge'), hint=hint)
1087 1087 st = repo.status()
1088 1088 if st.modified or st.added or st.removed or st.deleted:
1089 1089 raise error.Abort(_(b'uncommitted changes'), hint=hint)
1090 1090 ctx = repo[None]
1091 1091 for s in sorted(ctx.substate):
1092 1092 ctx.sub(s).bailifchanged(hint=hint)
1093 1093
1094 1094
1095 1095 def logmessage(ui, opts):
1096 1096 """ get the log message according to -m and -l option """
1097 1097
1098 1098 check_at_most_one_arg(opts, b'message', b'logfile')
1099 1099
1100 1100 message = opts.get(b'message')
1101 1101 logfile = opts.get(b'logfile')
1102 1102
1103 1103 if not message and logfile:
1104 1104 try:
1105 1105 if isstdiofilename(logfile):
1106 1106 message = ui.fin.read()
1107 1107 else:
1108 1108 message = b'\n'.join(util.readfile(logfile).splitlines())
1109 1109 except IOError as inst:
1110 1110 raise error.Abort(
1111 1111 _(b"can't read commit message '%s': %s")
1112 1112 % (logfile, encoding.strtolocal(inst.strerror))
1113 1113 )
1114 1114 return message
1115 1115
1116 1116
1117 1117 def mergeeditform(ctxorbool, baseformname):
1118 1118 """return appropriate editform name (referencing a committemplate)
1119 1119
1120 1120 'ctxorbool' is either a ctx to be committed, or a bool indicating whether
1121 1121 merging is committed.
1122 1122
1123 1123 This returns baseformname with '.merge' appended if it is a merge,
1124 1124 otherwise '.normal' is appended.
1125 1125 """
1126 1126 if isinstance(ctxorbool, bool):
1127 1127 if ctxorbool:
1128 1128 return baseformname + b".merge"
1129 1129 elif len(ctxorbool.parents()) > 1:
1130 1130 return baseformname + b".merge"
1131 1131
1132 1132 return baseformname + b".normal"
1133 1133
1134 1134
1135 1135 def getcommiteditor(
1136 1136 edit=False, finishdesc=None, extramsg=None, editform=b'', **opts
1137 1137 ):
1138 1138 """get appropriate commit message editor according to '--edit' option
1139 1139
1140 1140 'finishdesc' is a function to be called with edited commit message
1141 1141 (= 'description' of the new changeset) just after editing, but
1142 1142 before checking empty-ness. It should return actual text to be
1143 1143 stored into history. This allows to change description before
1144 1144 storing.
1145 1145
1146 1146 'extramsg' is a extra message to be shown in the editor instead of
1147 1147 'Leave message empty to abort commit' line. 'HG: ' prefix and EOL
1148 1148 is automatically added.
1149 1149
1150 1150 'editform' is a dot-separated list of names, to distinguish
1151 1151 the purpose of commit text editing.
1152 1152
1153 1153 'getcommiteditor' returns 'commitforceeditor' regardless of
1154 1154 'edit', if one of 'finishdesc' or 'extramsg' is specified, because
1155 1155 they are specific for usage in MQ.
1156 1156 """
1157 1157 if edit or finishdesc or extramsg:
1158 1158 return lambda r, c, s: commitforceeditor(
1159 1159 r, c, s, finishdesc=finishdesc, extramsg=extramsg, editform=editform
1160 1160 )
1161 1161 elif editform:
1162 1162 return lambda r, c, s: commiteditor(r, c, s, editform=editform)
1163 1163 else:
1164 1164 return commiteditor
1165 1165
1166 1166
1167 1167 def _escapecommandtemplate(tmpl):
1168 1168 parts = []
1169 1169 for typ, start, end in templater.scantemplate(tmpl, raw=True):
1170 1170 if typ == b'string':
1171 1171 parts.append(stringutil.escapestr(tmpl[start:end]))
1172 1172 else:
1173 1173 parts.append(tmpl[start:end])
1174 1174 return b''.join(parts)
1175 1175
1176 1176
1177 1177 def rendercommandtemplate(ui, tmpl, props):
1178 1178 r"""Expand a literal template 'tmpl' in a way suitable for command line
1179 1179
1180 1180 '\' in outermost string is not taken as an escape character because it
1181 1181 is a directory separator on Windows.
1182 1182
1183 1183 >>> from . import ui as uimod
1184 1184 >>> ui = uimod.ui()
1185 1185 >>> rendercommandtemplate(ui, b'c:\\{path}', {b'path': b'foo'})
1186 1186 'c:\\foo'
1187 1187 >>> rendercommandtemplate(ui, b'{"c:\\{path}"}', {'path': b'foo'})
1188 1188 'c:{path}'
1189 1189 """
1190 1190 if not tmpl:
1191 1191 return tmpl
1192 1192 t = formatter.maketemplater(ui, _escapecommandtemplate(tmpl))
1193 1193 return t.renderdefault(props)
1194 1194
1195 1195
1196 1196 def rendertemplate(ctx, tmpl, props=None):
1197 1197 """Expand a literal template 'tmpl' byte-string against one changeset
1198 1198
1199 1199 Each props item must be a stringify-able value or a callable returning
1200 1200 such value, i.e. no bare list nor dict should be passed.
1201 1201 """
1202 1202 repo = ctx.repo()
1203 1203 tres = formatter.templateresources(repo.ui, repo)
1204 1204 t = formatter.maketemplater(
1205 1205 repo.ui, tmpl, defaults=templatekw.keywords, resources=tres
1206 1206 )
1207 1207 mapping = {b'ctx': ctx}
1208 1208 if props:
1209 1209 mapping.update(props)
1210 1210 return t.renderdefault(mapping)
1211 1211
1212 1212
1213 1213 def format_changeset_summary(ui, ctx, command=None, default_spec=None):
1214 1214 """Format a changeset summary (one line)."""
1215 1215 spec = None
1216 1216 if command:
1217 1217 spec = ui.config(
1218 1218 b'command-templates', b'oneline-summary.%s' % command, None
1219 1219 )
1220 1220 if not spec:
1221 1221 spec = ui.config(b'command-templates', b'oneline-summary')
1222 1222 if not spec:
1223 1223 spec = default_spec
1224 1224 if not spec:
1225 # TODO: Pick a default we can agree on. This isn't used yet.
1226 raise error.ProgrammingError(b"no default one-line summary defined yet")
1225 spec = (
1226 b'{separate(" ", '
1227 b'label("log.changeset", "{rev}:{node|short}")'
1228 b', '
1229 b'label("log.tag", tags)'
1230 b', '
1231 b'label("log.bookmark", bookmarks)'
1232 b')} '
1233 b'"{label("log.desc", desc|firstline)}"'
1234 )
1227 1235 text = rendertemplate(ctx, spec)
1228 1236 return text.split(b'\n')[0]
1229 1237
1230 1238
1231 1239 def _buildfntemplate(pat, total=None, seqno=None, revwidth=None, pathname=None):
1232 1240 r"""Convert old-style filename format string to template string
1233 1241
1234 1242 >>> _buildfntemplate(b'foo-%b-%n.patch', seqno=0)
1235 1243 'foo-{reporoot|basename}-{seqno}.patch'
1236 1244 >>> _buildfntemplate(b'%R{tags % "{tag}"}%H')
1237 1245 '{rev}{tags % "{tag}"}{node}'
1238 1246
1239 1247 '\' in outermost strings has to be escaped because it is a directory
1240 1248 separator on Windows:
1241 1249
1242 1250 >>> _buildfntemplate(b'c:\\tmp\\%R\\%n.patch', seqno=0)
1243 1251 'c:\\\\tmp\\\\{rev}\\\\{seqno}.patch'
1244 1252 >>> _buildfntemplate(b'\\\\foo\\bar.patch')
1245 1253 '\\\\\\\\foo\\\\bar.patch'
1246 1254 >>> _buildfntemplate(b'\\{tags % "{tag}"}')
1247 1255 '\\\\{tags % "{tag}"}'
1248 1256
1249 1257 but inner strings follow the template rules (i.e. '\' is taken as an
1250 1258 escape character):
1251 1259
1252 1260 >>> _buildfntemplate(br'{"c:\tmp"}', seqno=0)
1253 1261 '{"c:\\tmp"}'
1254 1262 """
1255 1263 expander = {
1256 1264 b'H': b'{node}',
1257 1265 b'R': b'{rev}',
1258 1266 b'h': b'{node|short}',
1259 1267 b'm': br'{sub(r"[^\w]", "_", desc|firstline)}',
1260 1268 b'r': b'{if(revwidth, pad(rev, revwidth, "0", left=True), rev)}',
1261 1269 b'%': b'%',
1262 1270 b'b': b'{reporoot|basename}',
1263 1271 }
1264 1272 if total is not None:
1265 1273 expander[b'N'] = b'{total}'
1266 1274 if seqno is not None:
1267 1275 expander[b'n'] = b'{seqno}'
1268 1276 if total is not None and seqno is not None:
1269 1277 expander[b'n'] = b'{pad(seqno, total|stringify|count, "0", left=True)}'
1270 1278 if pathname is not None:
1271 1279 expander[b's'] = b'{pathname|basename}'
1272 1280 expander[b'd'] = b'{if(pathname|dirname, pathname|dirname, ".")}'
1273 1281 expander[b'p'] = b'{pathname}'
1274 1282
1275 1283 newname = []
1276 1284 for typ, start, end in templater.scantemplate(pat, raw=True):
1277 1285 if typ != b'string':
1278 1286 newname.append(pat[start:end])
1279 1287 continue
1280 1288 i = start
1281 1289 while i < end:
1282 1290 n = pat.find(b'%', i, end)
1283 1291 if n < 0:
1284 1292 newname.append(stringutil.escapestr(pat[i:end]))
1285 1293 break
1286 1294 newname.append(stringutil.escapestr(pat[i:n]))
1287 1295 if n + 2 > end:
1288 1296 raise error.Abort(
1289 1297 _(b"incomplete format spec in output filename")
1290 1298 )
1291 1299 c = pat[n + 1 : n + 2]
1292 1300 i = n + 2
1293 1301 try:
1294 1302 newname.append(expander[c])
1295 1303 except KeyError:
1296 1304 raise error.Abort(
1297 1305 _(b"invalid format spec '%%%s' in output filename") % c
1298 1306 )
1299 1307 return b''.join(newname)
1300 1308
1301 1309
1302 1310 def makefilename(ctx, pat, **props):
1303 1311 if not pat:
1304 1312 return pat
1305 1313 tmpl = _buildfntemplate(pat, **props)
1306 1314 # BUG: alias expansion shouldn't be made against template fragments
1307 1315 # rewritten from %-format strings, but we have no easy way to partially
1308 1316 # disable the expansion.
1309 1317 return rendertemplate(ctx, tmpl, pycompat.byteskwargs(props))
1310 1318
1311 1319
1312 1320 def isstdiofilename(pat):
1313 1321 """True if the given pat looks like a filename denoting stdin/stdout"""
1314 1322 return not pat or pat == b'-'
1315 1323
1316 1324
1317 1325 class _unclosablefile(object):
1318 1326 def __init__(self, fp):
1319 1327 self._fp = fp
1320 1328
1321 1329 def close(self):
1322 1330 pass
1323 1331
1324 1332 def __iter__(self):
1325 1333 return iter(self._fp)
1326 1334
1327 1335 def __getattr__(self, attr):
1328 1336 return getattr(self._fp, attr)
1329 1337
1330 1338 def __enter__(self):
1331 1339 return self
1332 1340
1333 1341 def __exit__(self, exc_type, exc_value, exc_tb):
1334 1342 pass
1335 1343
1336 1344
1337 1345 def makefileobj(ctx, pat, mode=b'wb', **props):
1338 1346 writable = mode not in (b'r', b'rb')
1339 1347
1340 1348 if isstdiofilename(pat):
1341 1349 repo = ctx.repo()
1342 1350 if writable:
1343 1351 fp = repo.ui.fout
1344 1352 else:
1345 1353 fp = repo.ui.fin
1346 1354 return _unclosablefile(fp)
1347 1355 fn = makefilename(ctx, pat, **props)
1348 1356 return open(fn, mode)
1349 1357
1350 1358
1351 1359 def openstorage(repo, cmd, file_, opts, returnrevlog=False):
1352 1360 """opens the changelog, manifest, a filelog or a given revlog"""
1353 1361 cl = opts[b'changelog']
1354 1362 mf = opts[b'manifest']
1355 1363 dir = opts[b'dir']
1356 1364 msg = None
1357 1365 if cl and mf:
1358 1366 msg = _(b'cannot specify --changelog and --manifest at the same time')
1359 1367 elif cl and dir:
1360 1368 msg = _(b'cannot specify --changelog and --dir at the same time')
1361 1369 elif cl or mf or dir:
1362 1370 if file_:
1363 1371 msg = _(b'cannot specify filename with --changelog or --manifest')
1364 1372 elif not repo:
1365 1373 msg = _(
1366 1374 b'cannot specify --changelog or --manifest or --dir '
1367 1375 b'without a repository'
1368 1376 )
1369 1377 if msg:
1370 1378 raise error.Abort(msg)
1371 1379
1372 1380 r = None
1373 1381 if repo:
1374 1382 if cl:
1375 1383 r = repo.unfiltered().changelog
1376 1384 elif dir:
1377 1385 if not scmutil.istreemanifest(repo):
1378 1386 raise error.Abort(
1379 1387 _(
1380 1388 b"--dir can only be used on repos with "
1381 1389 b"treemanifest enabled"
1382 1390 )
1383 1391 )
1384 1392 if not dir.endswith(b'/'):
1385 1393 dir = dir + b'/'
1386 1394 dirlog = repo.manifestlog.getstorage(dir)
1387 1395 if len(dirlog):
1388 1396 r = dirlog
1389 1397 elif mf:
1390 1398 r = repo.manifestlog.getstorage(b'')
1391 1399 elif file_:
1392 1400 filelog = repo.file(file_)
1393 1401 if len(filelog):
1394 1402 r = filelog
1395 1403
1396 1404 # Not all storage may be revlogs. If requested, try to return an actual
1397 1405 # revlog instance.
1398 1406 if returnrevlog:
1399 1407 if isinstance(r, revlog.revlog):
1400 1408 pass
1401 1409 elif util.safehasattr(r, b'_revlog'):
1402 1410 r = r._revlog # pytype: disable=attribute-error
1403 1411 elif r is not None:
1404 1412 raise error.Abort(_(b'%r does not appear to be a revlog') % r)
1405 1413
1406 1414 if not r:
1407 1415 if not returnrevlog:
1408 1416 raise error.Abort(_(b'cannot give path to non-revlog'))
1409 1417
1410 1418 if not file_:
1411 1419 raise error.CommandError(cmd, _(b'invalid arguments'))
1412 1420 if not os.path.isfile(file_):
1413 1421 raise error.Abort(_(b"revlog '%s' not found") % file_)
1414 1422 r = revlog.revlog(
1415 1423 vfsmod.vfs(encoding.getcwd(), audit=False), file_[:-2] + b".i"
1416 1424 )
1417 1425 return r
1418 1426
1419 1427
1420 1428 def openrevlog(repo, cmd, file_, opts):
1421 1429 """Obtain a revlog backing storage of an item.
1422 1430
1423 1431 This is similar to ``openstorage()`` except it always returns a revlog.
1424 1432
1425 1433 In most cases, a caller cares about the main storage object - not the
1426 1434 revlog backing it. Therefore, this function should only be used by code
1427 1435 that needs to examine low-level revlog implementation details. e.g. debug
1428 1436 commands.
1429 1437 """
1430 1438 return openstorage(repo, cmd, file_, opts, returnrevlog=True)
1431 1439
1432 1440
1433 1441 def copy(ui, repo, pats, opts, rename=False):
1434 1442 check_incompatible_arguments(opts, b'forget', [b'dry_run'])
1435 1443
1436 1444 # called with the repo lock held
1437 1445 #
1438 1446 # hgsep => pathname that uses "/" to separate directories
1439 1447 # ossep => pathname that uses os.sep to separate directories
1440 1448 cwd = repo.getcwd()
1441 1449 targets = {}
1442 1450 forget = opts.get(b"forget")
1443 1451 after = opts.get(b"after")
1444 1452 dryrun = opts.get(b"dry_run")
1445 1453 rev = opts.get(b'at_rev')
1446 1454 if rev:
1447 1455 if not forget and not after:
1448 1456 # TODO: Remove this restriction and make it also create the copy
1449 1457 # targets (and remove the rename source if rename==True).
1450 1458 raise error.Abort(_(b'--at-rev requires --after'))
1451 1459 ctx = scmutil.revsingle(repo, rev)
1452 1460 if len(ctx.parents()) > 1:
1453 1461 raise error.Abort(_(b'cannot mark/unmark copy in merge commit'))
1454 1462 else:
1455 1463 ctx = repo[None]
1456 1464
1457 1465 pctx = ctx.p1()
1458 1466
1459 1467 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
1460 1468
1461 1469 if forget:
1462 1470 if ctx.rev() is None:
1463 1471 new_ctx = ctx
1464 1472 else:
1465 1473 if len(ctx.parents()) > 1:
1466 1474 raise error.Abort(_(b'cannot unmark copy in merge commit'))
1467 1475 # avoid cycle context -> subrepo -> cmdutil
1468 1476 from . import context
1469 1477
1470 1478 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1471 1479 new_ctx = context.overlayworkingctx(repo)
1472 1480 new_ctx.setbase(ctx.p1())
1473 1481 mergemod.graft(repo, ctx, wctx=new_ctx)
1474 1482
1475 1483 match = scmutil.match(ctx, pats, opts)
1476 1484
1477 1485 current_copies = ctx.p1copies()
1478 1486 current_copies.update(ctx.p2copies())
1479 1487
1480 1488 uipathfn = scmutil.getuipathfn(repo)
1481 1489 for f in ctx.walk(match):
1482 1490 if f in current_copies:
1483 1491 new_ctx[f].markcopied(None)
1484 1492 elif match.exact(f):
1485 1493 ui.warn(
1486 1494 _(
1487 1495 b'%s: not unmarking as copy - file is not marked as copied\n'
1488 1496 )
1489 1497 % uipathfn(f)
1490 1498 )
1491 1499
1492 1500 if ctx.rev() is not None:
1493 1501 with repo.lock():
1494 1502 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1495 1503 new_node = mem_ctx.commit()
1496 1504
1497 1505 if repo.dirstate.p1() == ctx.node():
1498 1506 with repo.dirstate.parentchange():
1499 1507 scmutil.movedirstate(repo, repo[new_node])
1500 1508 replacements = {ctx.node(): [new_node]}
1501 1509 scmutil.cleanupnodes(
1502 1510 repo, replacements, b'uncopy', fixphase=True
1503 1511 )
1504 1512
1505 1513 return
1506 1514
1507 1515 pats = scmutil.expandpats(pats)
1508 1516 if not pats:
1509 1517 raise error.Abort(_(b'no source or destination specified'))
1510 1518 if len(pats) == 1:
1511 1519 raise error.Abort(_(b'no destination specified'))
1512 1520 dest = pats.pop()
1513 1521
1514 1522 def walkpat(pat):
1515 1523 srcs = []
1516 1524 # TODO: Inline and simplify the non-working-copy version of this code
1517 1525 # since it shares very little with the working-copy version of it.
1518 1526 ctx_to_walk = ctx if ctx.rev() is None else pctx
1519 1527 m = scmutil.match(ctx_to_walk, [pat], opts, globbed=True)
1520 1528 for abs in ctx_to_walk.walk(m):
1521 1529 rel = uipathfn(abs)
1522 1530 exact = m.exact(abs)
1523 1531 if abs not in ctx:
1524 1532 if abs in pctx:
1525 1533 if not after:
1526 1534 if exact:
1527 1535 ui.warn(
1528 1536 _(
1529 1537 b'%s: not copying - file has been marked '
1530 1538 b'for remove\n'
1531 1539 )
1532 1540 % rel
1533 1541 )
1534 1542 continue
1535 1543 else:
1536 1544 if exact:
1537 1545 ui.warn(
1538 1546 _(b'%s: not copying - file is not managed\n') % rel
1539 1547 )
1540 1548 continue
1541 1549
1542 1550 # abs: hgsep
1543 1551 # rel: ossep
1544 1552 srcs.append((abs, rel, exact))
1545 1553 return srcs
1546 1554
1547 1555 if ctx.rev() is not None:
1548 1556 rewriteutil.precheck(repo, [ctx.rev()], b'uncopy')
1549 1557 absdest = pathutil.canonpath(repo.root, cwd, dest)
1550 1558 if ctx.hasdir(absdest):
1551 1559 raise error.Abort(
1552 1560 _(b'%s: --at-rev does not support a directory as destination')
1553 1561 % uipathfn(absdest)
1554 1562 )
1555 1563 if absdest not in ctx:
1556 1564 raise error.Abort(
1557 1565 _(b'%s: copy destination does not exist in %s')
1558 1566 % (uipathfn(absdest), ctx)
1559 1567 )
1560 1568
1561 1569 # avoid cycle context -> subrepo -> cmdutil
1562 1570 from . import context
1563 1571
1564 1572 copylist = []
1565 1573 for pat in pats:
1566 1574 srcs = walkpat(pat)
1567 1575 if not srcs:
1568 1576 continue
1569 1577 for abs, rel, exact in srcs:
1570 1578 copylist.append(abs)
1571 1579
1572 1580 if not copylist:
1573 1581 raise error.Abort(_(b'no files to copy'))
1574 1582 # TODO: Add support for `hg cp --at-rev . foo bar dir` and
1575 1583 # `hg cp --at-rev . dir1 dir2`, preferably unifying the code with the
1576 1584 # existing functions below.
1577 1585 if len(copylist) != 1:
1578 1586 raise error.Abort(_(b'--at-rev requires a single source'))
1579 1587
1580 1588 new_ctx = context.overlayworkingctx(repo)
1581 1589 new_ctx.setbase(ctx.p1())
1582 1590 mergemod.graft(repo, ctx, wctx=new_ctx)
1583 1591
1584 1592 new_ctx.markcopied(absdest, copylist[0])
1585 1593
1586 1594 with repo.lock():
1587 1595 mem_ctx = new_ctx.tomemctx_for_amend(ctx)
1588 1596 new_node = mem_ctx.commit()
1589 1597
1590 1598 if repo.dirstate.p1() == ctx.node():
1591 1599 with repo.dirstate.parentchange():
1592 1600 scmutil.movedirstate(repo, repo[new_node])
1593 1601 replacements = {ctx.node(): [new_node]}
1594 1602 scmutil.cleanupnodes(repo, replacements, b'copy', fixphase=True)
1595 1603
1596 1604 return
1597 1605
1598 1606 # abssrc: hgsep
1599 1607 # relsrc: ossep
1600 1608 # otarget: ossep
1601 1609 def copyfile(abssrc, relsrc, otarget, exact):
1602 1610 abstarget = pathutil.canonpath(repo.root, cwd, otarget)
1603 1611 if b'/' in abstarget:
1604 1612 # We cannot normalize abstarget itself, this would prevent
1605 1613 # case only renames, like a => A.
1606 1614 abspath, absname = abstarget.rsplit(b'/', 1)
1607 1615 abstarget = repo.dirstate.normalize(abspath) + b'/' + absname
1608 1616 reltarget = repo.pathto(abstarget, cwd)
1609 1617 target = repo.wjoin(abstarget)
1610 1618 src = repo.wjoin(abssrc)
1611 1619 state = repo.dirstate[abstarget]
1612 1620
1613 1621 scmutil.checkportable(ui, abstarget)
1614 1622
1615 1623 # check for collisions
1616 1624 prevsrc = targets.get(abstarget)
1617 1625 if prevsrc is not None:
1618 1626 ui.warn(
1619 1627 _(b'%s: not overwriting - %s collides with %s\n')
1620 1628 % (
1621 1629 reltarget,
1622 1630 repo.pathto(abssrc, cwd),
1623 1631 repo.pathto(prevsrc, cwd),
1624 1632 )
1625 1633 )
1626 1634 return True # report a failure
1627 1635
1628 1636 # check for overwrites
1629 1637 exists = os.path.lexists(target)
1630 1638 samefile = False
1631 1639 if exists and abssrc != abstarget:
1632 1640 if repo.dirstate.normalize(abssrc) == repo.dirstate.normalize(
1633 1641 abstarget
1634 1642 ):
1635 1643 if not rename:
1636 1644 ui.warn(_(b"%s: can't copy - same file\n") % reltarget)
1637 1645 return True # report a failure
1638 1646 exists = False
1639 1647 samefile = True
1640 1648
1641 1649 if not after and exists or after and state in b'mn':
1642 1650 if not opts[b'force']:
1643 1651 if state in b'mn':
1644 1652 msg = _(b'%s: not overwriting - file already committed\n')
1645 1653 if after:
1646 1654 flags = b'--after --force'
1647 1655 else:
1648 1656 flags = b'--force'
1649 1657 if rename:
1650 1658 hint = (
1651 1659 _(
1652 1660 b"('hg rename %s' to replace the file by "
1653 1661 b'recording a rename)\n'
1654 1662 )
1655 1663 % flags
1656 1664 )
1657 1665 else:
1658 1666 hint = (
1659 1667 _(
1660 1668 b"('hg copy %s' to replace the file by "
1661 1669 b'recording a copy)\n'
1662 1670 )
1663 1671 % flags
1664 1672 )
1665 1673 else:
1666 1674 msg = _(b'%s: not overwriting - file exists\n')
1667 1675 if rename:
1668 1676 hint = _(
1669 1677 b"('hg rename --after' to record the rename)\n"
1670 1678 )
1671 1679 else:
1672 1680 hint = _(b"('hg copy --after' to record the copy)\n")
1673 1681 ui.warn(msg % reltarget)
1674 1682 ui.warn(hint)
1675 1683 return True # report a failure
1676 1684
1677 1685 if after:
1678 1686 if not exists:
1679 1687 if rename:
1680 1688 ui.warn(
1681 1689 _(b'%s: not recording move - %s does not exist\n')
1682 1690 % (relsrc, reltarget)
1683 1691 )
1684 1692 else:
1685 1693 ui.warn(
1686 1694 _(b'%s: not recording copy - %s does not exist\n')
1687 1695 % (relsrc, reltarget)
1688 1696 )
1689 1697 return True # report a failure
1690 1698 elif not dryrun:
1691 1699 try:
1692 1700 if exists:
1693 1701 os.unlink(target)
1694 1702 targetdir = os.path.dirname(target) or b'.'
1695 1703 if not os.path.isdir(targetdir):
1696 1704 os.makedirs(targetdir)
1697 1705 if samefile:
1698 1706 tmp = target + b"~hgrename"
1699 1707 os.rename(src, tmp)
1700 1708 os.rename(tmp, target)
1701 1709 else:
1702 1710 # Preserve stat info on renames, not on copies; this matches
1703 1711 # Linux CLI behavior.
1704 1712 util.copyfile(src, target, copystat=rename)
1705 1713 srcexists = True
1706 1714 except IOError as inst:
1707 1715 if inst.errno == errno.ENOENT:
1708 1716 ui.warn(_(b'%s: deleted in working directory\n') % relsrc)
1709 1717 srcexists = False
1710 1718 else:
1711 1719 ui.warn(
1712 1720 _(b'%s: cannot copy - %s\n')
1713 1721 % (relsrc, encoding.strtolocal(inst.strerror))
1714 1722 )
1715 1723 return True # report a failure
1716 1724
1717 1725 if ui.verbose or not exact:
1718 1726 if rename:
1719 1727 ui.status(_(b'moving %s to %s\n') % (relsrc, reltarget))
1720 1728 else:
1721 1729 ui.status(_(b'copying %s to %s\n') % (relsrc, reltarget))
1722 1730
1723 1731 targets[abstarget] = abssrc
1724 1732
1725 1733 # fix up dirstate
1726 1734 scmutil.dirstatecopy(
1727 1735 ui, repo, ctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd
1728 1736 )
1729 1737 if rename and not dryrun:
1730 1738 if not after and srcexists and not samefile:
1731 1739 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
1732 1740 repo.wvfs.unlinkpath(abssrc, rmdir=rmdir)
1733 1741 ctx.forget([abssrc])
1734 1742
1735 1743 # pat: ossep
1736 1744 # dest ossep
1737 1745 # srcs: list of (hgsep, hgsep, ossep, bool)
1738 1746 # return: function that takes hgsep and returns ossep
1739 1747 def targetpathfn(pat, dest, srcs):
1740 1748 if os.path.isdir(pat):
1741 1749 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1742 1750 abspfx = util.localpath(abspfx)
1743 1751 if destdirexists:
1744 1752 striplen = len(os.path.split(abspfx)[0])
1745 1753 else:
1746 1754 striplen = len(abspfx)
1747 1755 if striplen:
1748 1756 striplen += len(pycompat.ossep)
1749 1757 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1750 1758 elif destdirexists:
1751 1759 res = lambda p: os.path.join(
1752 1760 dest, os.path.basename(util.localpath(p))
1753 1761 )
1754 1762 else:
1755 1763 res = lambda p: dest
1756 1764 return res
1757 1765
1758 1766 # pat: ossep
1759 1767 # dest ossep
1760 1768 # srcs: list of (hgsep, hgsep, ossep, bool)
1761 1769 # return: function that takes hgsep and returns ossep
1762 1770 def targetpathafterfn(pat, dest, srcs):
1763 1771 if matchmod.patkind(pat):
1764 1772 # a mercurial pattern
1765 1773 res = lambda p: os.path.join(
1766 1774 dest, os.path.basename(util.localpath(p))
1767 1775 )
1768 1776 else:
1769 1777 abspfx = pathutil.canonpath(repo.root, cwd, pat)
1770 1778 if len(abspfx) < len(srcs[0][0]):
1771 1779 # A directory. Either the target path contains the last
1772 1780 # component of the source path or it does not.
1773 1781 def evalpath(striplen):
1774 1782 score = 0
1775 1783 for s in srcs:
1776 1784 t = os.path.join(dest, util.localpath(s[0])[striplen:])
1777 1785 if os.path.lexists(t):
1778 1786 score += 1
1779 1787 return score
1780 1788
1781 1789 abspfx = util.localpath(abspfx)
1782 1790 striplen = len(abspfx)
1783 1791 if striplen:
1784 1792 striplen += len(pycompat.ossep)
1785 1793 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
1786 1794 score = evalpath(striplen)
1787 1795 striplen1 = len(os.path.split(abspfx)[0])
1788 1796 if striplen1:
1789 1797 striplen1 += len(pycompat.ossep)
1790 1798 if evalpath(striplen1) > score:
1791 1799 striplen = striplen1
1792 1800 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
1793 1801 else:
1794 1802 # a file
1795 1803 if destdirexists:
1796 1804 res = lambda p: os.path.join(
1797 1805 dest, os.path.basename(util.localpath(p))
1798 1806 )
1799 1807 else:
1800 1808 res = lambda p: dest
1801 1809 return res
1802 1810
1803 1811 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
1804 1812 if not destdirexists:
1805 1813 if len(pats) > 1 or matchmod.patkind(pats[0]):
1806 1814 raise error.Abort(
1807 1815 _(
1808 1816 b'with multiple sources, destination must be an '
1809 1817 b'existing directory'
1810 1818 )
1811 1819 )
1812 1820 if util.endswithsep(dest):
1813 1821 raise error.Abort(_(b'destination %s is not a directory') % dest)
1814 1822
1815 1823 tfn = targetpathfn
1816 1824 if after:
1817 1825 tfn = targetpathafterfn
1818 1826 copylist = []
1819 1827 for pat in pats:
1820 1828 srcs = walkpat(pat)
1821 1829 if not srcs:
1822 1830 continue
1823 1831 copylist.append((tfn(pat, dest, srcs), srcs))
1824 1832 if not copylist:
1825 1833 raise error.Abort(_(b'no files to copy'))
1826 1834
1827 1835 errors = 0
1828 1836 for targetpath, srcs in copylist:
1829 1837 for abssrc, relsrc, exact in srcs:
1830 1838 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
1831 1839 errors += 1
1832 1840
1833 1841 return errors != 0
1834 1842
1835 1843
1836 1844 ## facility to let extension process additional data into an import patch
1837 1845 # list of identifier to be executed in order
1838 1846 extrapreimport = [] # run before commit
1839 1847 extrapostimport = [] # run after commit
1840 1848 # mapping from identifier to actual import function
1841 1849 #
1842 1850 # 'preimport' are run before the commit is made and are provided the following
1843 1851 # arguments:
1844 1852 # - repo: the localrepository instance,
1845 1853 # - patchdata: data extracted from patch header (cf m.patch.patchheadermap),
1846 1854 # - extra: the future extra dictionary of the changeset, please mutate it,
1847 1855 # - opts: the import options.
1848 1856 # XXX ideally, we would just pass an ctx ready to be computed, that would allow
1849 1857 # mutation of in memory commit and more. Feel free to rework the code to get
1850 1858 # there.
1851 1859 extrapreimportmap = {}
1852 1860 # 'postimport' are run after the commit is made and are provided the following
1853 1861 # argument:
1854 1862 # - ctx: the changectx created by import.
1855 1863 extrapostimportmap = {}
1856 1864
1857 1865
1858 1866 def tryimportone(ui, repo, patchdata, parents, opts, msgs, updatefunc):
1859 1867 """Utility function used by commands.import to import a single patch
1860 1868
1861 1869 This function is explicitly defined here to help the evolve extension to
1862 1870 wrap this part of the import logic.
1863 1871
1864 1872 The API is currently a bit ugly because it a simple code translation from
1865 1873 the import command. Feel free to make it better.
1866 1874
1867 1875 :patchdata: a dictionary containing parsed patch data (such as from
1868 1876 ``patch.extract()``)
1869 1877 :parents: nodes that will be parent of the created commit
1870 1878 :opts: the full dict of option passed to the import command
1871 1879 :msgs: list to save commit message to.
1872 1880 (used in case we need to save it when failing)
1873 1881 :updatefunc: a function that update a repo to a given node
1874 1882 updatefunc(<repo>, <node>)
1875 1883 """
1876 1884 # avoid cycle context -> subrepo -> cmdutil
1877 1885 from . import context
1878 1886
1879 1887 tmpname = patchdata.get(b'filename')
1880 1888 message = patchdata.get(b'message')
1881 1889 user = opts.get(b'user') or patchdata.get(b'user')
1882 1890 date = opts.get(b'date') or patchdata.get(b'date')
1883 1891 branch = patchdata.get(b'branch')
1884 1892 nodeid = patchdata.get(b'nodeid')
1885 1893 p1 = patchdata.get(b'p1')
1886 1894 p2 = patchdata.get(b'p2')
1887 1895
1888 1896 nocommit = opts.get(b'no_commit')
1889 1897 importbranch = opts.get(b'import_branch')
1890 1898 update = not opts.get(b'bypass')
1891 1899 strip = opts[b"strip"]
1892 1900 prefix = opts[b"prefix"]
1893 1901 sim = float(opts.get(b'similarity') or 0)
1894 1902
1895 1903 if not tmpname:
1896 1904 return None, None, False
1897 1905
1898 1906 rejects = False
1899 1907
1900 1908 cmdline_message = logmessage(ui, opts)
1901 1909 if cmdline_message:
1902 1910 # pickup the cmdline msg
1903 1911 message = cmdline_message
1904 1912 elif message:
1905 1913 # pickup the patch msg
1906 1914 message = message.strip()
1907 1915 else:
1908 1916 # launch the editor
1909 1917 message = None
1910 1918 ui.debug(b'message:\n%s\n' % (message or b''))
1911 1919
1912 1920 if len(parents) == 1:
1913 1921 parents.append(repo[nullid])
1914 1922 if opts.get(b'exact'):
1915 1923 if not nodeid or not p1:
1916 1924 raise error.Abort(_(b'not a Mercurial patch'))
1917 1925 p1 = repo[p1]
1918 1926 p2 = repo[p2 or nullid]
1919 1927 elif p2:
1920 1928 try:
1921 1929 p1 = repo[p1]
1922 1930 p2 = repo[p2]
1923 1931 # Without any options, consider p2 only if the
1924 1932 # patch is being applied on top of the recorded
1925 1933 # first parent.
1926 1934 if p1 != parents[0]:
1927 1935 p1 = parents[0]
1928 1936 p2 = repo[nullid]
1929 1937 except error.RepoError:
1930 1938 p1, p2 = parents
1931 1939 if p2.node() == nullid:
1932 1940 ui.warn(
1933 1941 _(
1934 1942 b"warning: import the patch as a normal revision\n"
1935 1943 b"(use --exact to import the patch as a merge)\n"
1936 1944 )
1937 1945 )
1938 1946 else:
1939 1947 p1, p2 = parents
1940 1948
1941 1949 n = None
1942 1950 if update:
1943 1951 if p1 != parents[0]:
1944 1952 updatefunc(repo, p1.node())
1945 1953 if p2 != parents[1]:
1946 1954 repo.setparents(p1.node(), p2.node())
1947 1955
1948 1956 if opts.get(b'exact') or importbranch:
1949 1957 repo.dirstate.setbranch(branch or b'default')
1950 1958
1951 1959 partial = opts.get(b'partial', False)
1952 1960 files = set()
1953 1961 try:
1954 1962 patch.patch(
1955 1963 ui,
1956 1964 repo,
1957 1965 tmpname,
1958 1966 strip=strip,
1959 1967 prefix=prefix,
1960 1968 files=files,
1961 1969 eolmode=None,
1962 1970 similarity=sim / 100.0,
1963 1971 )
1964 1972 except error.PatchError as e:
1965 1973 if not partial:
1966 1974 raise error.Abort(pycompat.bytestr(e))
1967 1975 if partial:
1968 1976 rejects = True
1969 1977
1970 1978 files = list(files)
1971 1979 if nocommit:
1972 1980 if message:
1973 1981 msgs.append(message)
1974 1982 else:
1975 1983 if opts.get(b'exact') or p2:
1976 1984 # If you got here, you either use --force and know what
1977 1985 # you are doing or used --exact or a merge patch while
1978 1986 # being updated to its first parent.
1979 1987 m = None
1980 1988 else:
1981 1989 m = scmutil.matchfiles(repo, files or [])
1982 1990 editform = mergeeditform(repo[None], b'import.normal')
1983 1991 if opts.get(b'exact'):
1984 1992 editor = None
1985 1993 else:
1986 1994 editor = getcommiteditor(
1987 1995 editform=editform, **pycompat.strkwargs(opts)
1988 1996 )
1989 1997 extra = {}
1990 1998 for idfunc in extrapreimport:
1991 1999 extrapreimportmap[idfunc](repo, patchdata, extra, opts)
1992 2000 overrides = {}
1993 2001 if partial:
1994 2002 overrides[(b'ui', b'allowemptycommit')] = True
1995 2003 if opts.get(b'secret'):
1996 2004 overrides[(b'phases', b'new-commit')] = b'secret'
1997 2005 with repo.ui.configoverride(overrides, b'import'):
1998 2006 n = repo.commit(
1999 2007 message, user, date, match=m, editor=editor, extra=extra
2000 2008 )
2001 2009 for idfunc in extrapostimport:
2002 2010 extrapostimportmap[idfunc](repo[n])
2003 2011 else:
2004 2012 if opts.get(b'exact') or importbranch:
2005 2013 branch = branch or b'default'
2006 2014 else:
2007 2015 branch = p1.branch()
2008 2016 store = patch.filestore()
2009 2017 try:
2010 2018 files = set()
2011 2019 try:
2012 2020 patch.patchrepo(
2013 2021 ui,
2014 2022 repo,
2015 2023 p1,
2016 2024 store,
2017 2025 tmpname,
2018 2026 strip,
2019 2027 prefix,
2020 2028 files,
2021 2029 eolmode=None,
2022 2030 )
2023 2031 except error.PatchError as e:
2024 2032 raise error.Abort(stringutil.forcebytestr(e))
2025 2033 if opts.get(b'exact'):
2026 2034 editor = None
2027 2035 else:
2028 2036 editor = getcommiteditor(editform=b'import.bypass')
2029 2037 memctx = context.memctx(
2030 2038 repo,
2031 2039 (p1.node(), p2.node()),
2032 2040 message,
2033 2041 files=files,
2034 2042 filectxfn=store,
2035 2043 user=user,
2036 2044 date=date,
2037 2045 branch=branch,
2038 2046 editor=editor,
2039 2047 )
2040 2048
2041 2049 overrides = {}
2042 2050 if opts.get(b'secret'):
2043 2051 overrides[(b'phases', b'new-commit')] = b'secret'
2044 2052 with repo.ui.configoverride(overrides, b'import'):
2045 2053 n = memctx.commit()
2046 2054 finally:
2047 2055 store.close()
2048 2056 if opts.get(b'exact') and nocommit:
2049 2057 # --exact with --no-commit is still useful in that it does merge
2050 2058 # and branch bits
2051 2059 ui.warn(_(b"warning: can't check exact import with --no-commit\n"))
2052 2060 elif opts.get(b'exact') and (not n or hex(n) != nodeid):
2053 2061 raise error.Abort(_(b'patch is damaged or loses information'))
2054 2062 msg = _(b'applied to working directory')
2055 2063 if n:
2056 2064 # i18n: refers to a short changeset id
2057 2065 msg = _(b'created %s') % short(n)
2058 2066 return msg, n, rejects
2059 2067
2060 2068
2061 2069 # facility to let extensions include additional data in an exported patch
2062 2070 # list of identifiers to be executed in order
2063 2071 extraexport = []
2064 2072 # mapping from identifier to actual export function
2065 2073 # function as to return a string to be added to the header or None
2066 2074 # it is given two arguments (sequencenumber, changectx)
2067 2075 extraexportmap = {}
2068 2076
2069 2077
2070 2078 def _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts):
2071 2079 node = scmutil.binnode(ctx)
2072 2080 parents = [p.node() for p in ctx.parents() if p]
2073 2081 branch = ctx.branch()
2074 2082 if switch_parent:
2075 2083 parents.reverse()
2076 2084
2077 2085 if parents:
2078 2086 prev = parents[0]
2079 2087 else:
2080 2088 prev = nullid
2081 2089
2082 2090 fm.context(ctx=ctx)
2083 2091 fm.plain(b'# HG changeset patch\n')
2084 2092 fm.write(b'user', b'# User %s\n', ctx.user())
2085 2093 fm.plain(b'# Date %d %d\n' % ctx.date())
2086 2094 fm.write(b'date', b'# %s\n', fm.formatdate(ctx.date()))
2087 2095 fm.condwrite(
2088 2096 branch and branch != b'default', b'branch', b'# Branch %s\n', branch
2089 2097 )
2090 2098 fm.write(b'node', b'# Node ID %s\n', hex(node))
2091 2099 fm.plain(b'# Parent %s\n' % hex(prev))
2092 2100 if len(parents) > 1:
2093 2101 fm.plain(b'# Parent %s\n' % hex(parents[1]))
2094 2102 fm.data(parents=fm.formatlist(pycompat.maplist(hex, parents), name=b'node'))
2095 2103
2096 2104 # TODO: redesign extraexportmap function to support formatter
2097 2105 for headerid in extraexport:
2098 2106 header = extraexportmap[headerid](seqno, ctx)
2099 2107 if header is not None:
2100 2108 fm.plain(b'# %s\n' % header)
2101 2109
2102 2110 fm.write(b'desc', b'%s\n', ctx.description().rstrip())
2103 2111 fm.plain(b'\n')
2104 2112
2105 2113 if fm.isplain():
2106 2114 chunkiter = patch.diffui(repo, prev, node, match, opts=diffopts)
2107 2115 for chunk, label in chunkiter:
2108 2116 fm.plain(chunk, label=label)
2109 2117 else:
2110 2118 chunkiter = patch.diff(repo, prev, node, match, opts=diffopts)
2111 2119 # TODO: make it structured?
2112 2120 fm.data(diff=b''.join(chunkiter))
2113 2121
2114 2122
2115 2123 def _exportfile(repo, revs, fm, dest, switch_parent, diffopts, match):
2116 2124 """Export changesets to stdout or a single file"""
2117 2125 for seqno, rev in enumerate(revs, 1):
2118 2126 ctx = repo[rev]
2119 2127 if not dest.startswith(b'<'):
2120 2128 repo.ui.note(b"%s\n" % dest)
2121 2129 fm.startitem()
2122 2130 _exportsingle(repo, ctx, fm, match, switch_parent, seqno, diffopts)
2123 2131
2124 2132
2125 2133 def _exportfntemplate(
2126 2134 repo, revs, basefm, fntemplate, switch_parent, diffopts, match
2127 2135 ):
2128 2136 """Export changesets to possibly multiple files"""
2129 2137 total = len(revs)
2130 2138 revwidth = max(len(str(rev)) for rev in revs)
2131 2139 filemap = util.sortdict() # filename: [(seqno, rev), ...]
2132 2140
2133 2141 for seqno, rev in enumerate(revs, 1):
2134 2142 ctx = repo[rev]
2135 2143 dest = makefilename(
2136 2144 ctx, fntemplate, total=total, seqno=seqno, revwidth=revwidth
2137 2145 )
2138 2146 filemap.setdefault(dest, []).append((seqno, rev))
2139 2147
2140 2148 for dest in filemap:
2141 2149 with formatter.maybereopen(basefm, dest) as fm:
2142 2150 repo.ui.note(b"%s\n" % dest)
2143 2151 for seqno, rev in filemap[dest]:
2144 2152 fm.startitem()
2145 2153 ctx = repo[rev]
2146 2154 _exportsingle(
2147 2155 repo, ctx, fm, match, switch_parent, seqno, diffopts
2148 2156 )
2149 2157
2150 2158
2151 2159 def _prefetchchangedfiles(repo, revs, match):
2152 2160 allfiles = set()
2153 2161 for rev in revs:
2154 2162 for file in repo[rev].files():
2155 2163 if not match or match(file):
2156 2164 allfiles.add(file)
2157 2165 match = scmutil.matchfiles(repo, allfiles)
2158 2166 revmatches = [(rev, match) for rev in revs]
2159 2167 scmutil.prefetchfiles(repo, revmatches)
2160 2168
2161 2169
2162 2170 def export(
2163 2171 repo,
2164 2172 revs,
2165 2173 basefm,
2166 2174 fntemplate=b'hg-%h.patch',
2167 2175 switch_parent=False,
2168 2176 opts=None,
2169 2177 match=None,
2170 2178 ):
2171 2179 '''export changesets as hg patches
2172 2180
2173 2181 Args:
2174 2182 repo: The repository from which we're exporting revisions.
2175 2183 revs: A list of revisions to export as revision numbers.
2176 2184 basefm: A formatter to which patches should be written.
2177 2185 fntemplate: An optional string to use for generating patch file names.
2178 2186 switch_parent: If True, show diffs against second parent when not nullid.
2179 2187 Default is false, which always shows diff against p1.
2180 2188 opts: diff options to use for generating the patch.
2181 2189 match: If specified, only export changes to files matching this matcher.
2182 2190
2183 2191 Returns:
2184 2192 Nothing.
2185 2193
2186 2194 Side Effect:
2187 2195 "HG Changeset Patch" data is emitted to one of the following
2188 2196 destinations:
2189 2197 fntemplate specified: Each rev is written to a unique file named using
2190 2198 the given template.
2191 2199 Otherwise: All revs will be written to basefm.
2192 2200 '''
2193 2201 _prefetchchangedfiles(repo, revs, match)
2194 2202
2195 2203 if not fntemplate:
2196 2204 _exportfile(
2197 2205 repo, revs, basefm, b'<unnamed>', switch_parent, opts, match
2198 2206 )
2199 2207 else:
2200 2208 _exportfntemplate(
2201 2209 repo, revs, basefm, fntemplate, switch_parent, opts, match
2202 2210 )
2203 2211
2204 2212
2205 2213 def exportfile(repo, revs, fp, switch_parent=False, opts=None, match=None):
2206 2214 """Export changesets to the given file stream"""
2207 2215 _prefetchchangedfiles(repo, revs, match)
2208 2216
2209 2217 dest = getattr(fp, 'name', b'<unnamed>')
2210 2218 with formatter.formatter(repo.ui, fp, b'export', {}) as fm:
2211 2219 _exportfile(repo, revs, fm, dest, switch_parent, opts, match)
2212 2220
2213 2221
2214 2222 def showmarker(fm, marker, index=None):
2215 2223 """utility function to display obsolescence marker in a readable way
2216 2224
2217 2225 To be used by debug function."""
2218 2226 if index is not None:
2219 2227 fm.write(b'index', b'%i ', index)
2220 2228 fm.write(b'prednode', b'%s ', hex(marker.prednode()))
2221 2229 succs = marker.succnodes()
2222 2230 fm.condwrite(
2223 2231 succs,
2224 2232 b'succnodes',
2225 2233 b'%s ',
2226 2234 fm.formatlist(map(hex, succs), name=b'node'),
2227 2235 )
2228 2236 fm.write(b'flag', b'%X ', marker.flags())
2229 2237 parents = marker.parentnodes()
2230 2238 if parents is not None:
2231 2239 fm.write(
2232 2240 b'parentnodes',
2233 2241 b'{%s} ',
2234 2242 fm.formatlist(map(hex, parents), name=b'node', sep=b', '),
2235 2243 )
2236 2244 fm.write(b'date', b'(%s) ', fm.formatdate(marker.date()))
2237 2245 meta = marker.metadata().copy()
2238 2246 meta.pop(b'date', None)
2239 2247 smeta = pycompat.rapply(pycompat.maybebytestr, meta)
2240 2248 fm.write(
2241 2249 b'metadata', b'{%s}', fm.formatdict(smeta, fmt=b'%r: %r', sep=b', ')
2242 2250 )
2243 2251 fm.plain(b'\n')
2244 2252
2245 2253
2246 2254 def finddate(ui, repo, date):
2247 2255 """Find the tipmost changeset that matches the given date spec"""
2248 2256 mrevs = repo.revs(b'date(%s)', date)
2249 2257 try:
2250 2258 rev = mrevs.max()
2251 2259 except ValueError:
2252 2260 raise error.Abort(_(b"revision matching date not found"))
2253 2261
2254 2262 ui.status(
2255 2263 _(b"found revision %d from %s\n")
2256 2264 % (rev, dateutil.datestr(repo[rev].date()))
2257 2265 )
2258 2266 return b'%d' % rev
2259 2267
2260 2268
2261 2269 def add(ui, repo, match, prefix, uipathfn, explicitonly, **opts):
2262 2270 bad = []
2263 2271
2264 2272 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2265 2273 names = []
2266 2274 wctx = repo[None]
2267 2275 cca = None
2268 2276 abort, warn = scmutil.checkportabilityalert(ui)
2269 2277 if abort or warn:
2270 2278 cca = scmutil.casecollisionauditor(ui, abort, repo.dirstate)
2271 2279
2272 2280 match = repo.narrowmatch(match, includeexact=True)
2273 2281 badmatch = matchmod.badmatch(match, badfn)
2274 2282 dirstate = repo.dirstate
2275 2283 # We don't want to just call wctx.walk here, since it would return a lot of
2276 2284 # clean files, which we aren't interested in and takes time.
2277 2285 for f in sorted(
2278 2286 dirstate.walk(
2279 2287 badmatch,
2280 2288 subrepos=sorted(wctx.substate),
2281 2289 unknown=True,
2282 2290 ignored=False,
2283 2291 full=False,
2284 2292 )
2285 2293 ):
2286 2294 exact = match.exact(f)
2287 2295 if exact or not explicitonly and f not in wctx and repo.wvfs.lexists(f):
2288 2296 if cca:
2289 2297 cca(f)
2290 2298 names.append(f)
2291 2299 if ui.verbose or not exact:
2292 2300 ui.status(
2293 2301 _(b'adding %s\n') % uipathfn(f), label=b'ui.addremove.added'
2294 2302 )
2295 2303
2296 2304 for subpath in sorted(wctx.substate):
2297 2305 sub = wctx.sub(subpath)
2298 2306 try:
2299 2307 submatch = matchmod.subdirmatcher(subpath, match)
2300 2308 subprefix = repo.wvfs.reljoin(prefix, subpath)
2301 2309 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2302 2310 if opts.get('subrepos'):
2303 2311 bad.extend(
2304 2312 sub.add(ui, submatch, subprefix, subuipathfn, False, **opts)
2305 2313 )
2306 2314 else:
2307 2315 bad.extend(
2308 2316 sub.add(ui, submatch, subprefix, subuipathfn, True, **opts)
2309 2317 )
2310 2318 except error.LookupError:
2311 2319 ui.status(
2312 2320 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2313 2321 )
2314 2322
2315 2323 if not opts.get('dry_run'):
2316 2324 rejected = wctx.add(names, prefix)
2317 2325 bad.extend(f for f in rejected if f in match.files())
2318 2326 return bad
2319 2327
2320 2328
2321 2329 def addwebdirpath(repo, serverpath, webconf):
2322 2330 webconf[serverpath] = repo.root
2323 2331 repo.ui.debug(b'adding %s = %s\n' % (serverpath, repo.root))
2324 2332
2325 2333 for r in repo.revs(b'filelog("path:.hgsub")'):
2326 2334 ctx = repo[r]
2327 2335 for subpath in ctx.substate:
2328 2336 ctx.sub(subpath).addwebdirpath(serverpath, webconf)
2329 2337
2330 2338
2331 2339 def forget(
2332 2340 ui, repo, match, prefix, uipathfn, explicitonly, dryrun, interactive
2333 2341 ):
2334 2342 if dryrun and interactive:
2335 2343 raise error.Abort(_(b"cannot specify both --dry-run and --interactive"))
2336 2344 bad = []
2337 2345 badfn = lambda x, y: bad.append(x) or match.bad(x, y)
2338 2346 wctx = repo[None]
2339 2347 forgot = []
2340 2348
2341 2349 s = repo.status(match=matchmod.badmatch(match, badfn), clean=True)
2342 2350 forget = sorted(s.modified + s.added + s.deleted + s.clean)
2343 2351 if explicitonly:
2344 2352 forget = [f for f in forget if match.exact(f)]
2345 2353
2346 2354 for subpath in sorted(wctx.substate):
2347 2355 sub = wctx.sub(subpath)
2348 2356 submatch = matchmod.subdirmatcher(subpath, match)
2349 2357 subprefix = repo.wvfs.reljoin(prefix, subpath)
2350 2358 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2351 2359 try:
2352 2360 subbad, subforgot = sub.forget(
2353 2361 submatch,
2354 2362 subprefix,
2355 2363 subuipathfn,
2356 2364 dryrun=dryrun,
2357 2365 interactive=interactive,
2358 2366 )
2359 2367 bad.extend([subpath + b'/' + f for f in subbad])
2360 2368 forgot.extend([subpath + b'/' + f for f in subforgot])
2361 2369 except error.LookupError:
2362 2370 ui.status(
2363 2371 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2364 2372 )
2365 2373
2366 2374 if not explicitonly:
2367 2375 for f in match.files():
2368 2376 if f not in repo.dirstate and not repo.wvfs.isdir(f):
2369 2377 if f not in forgot:
2370 2378 if repo.wvfs.exists(f):
2371 2379 # Don't complain if the exact case match wasn't given.
2372 2380 # But don't do this until after checking 'forgot', so
2373 2381 # that subrepo files aren't normalized, and this op is
2374 2382 # purely from data cached by the status walk above.
2375 2383 if repo.dirstate.normalize(f) in repo.dirstate:
2376 2384 continue
2377 2385 ui.warn(
2378 2386 _(
2379 2387 b'not removing %s: '
2380 2388 b'file is already untracked\n'
2381 2389 )
2382 2390 % uipathfn(f)
2383 2391 )
2384 2392 bad.append(f)
2385 2393
2386 2394 if interactive:
2387 2395 responses = _(
2388 2396 b'[Ynsa?]'
2389 2397 b'$$ &Yes, forget this file'
2390 2398 b'$$ &No, skip this file'
2391 2399 b'$$ &Skip remaining files'
2392 2400 b'$$ Include &all remaining files'
2393 2401 b'$$ &? (display help)'
2394 2402 )
2395 2403 for filename in forget[:]:
2396 2404 r = ui.promptchoice(
2397 2405 _(b'forget %s %s') % (uipathfn(filename), responses)
2398 2406 )
2399 2407 if r == 4: # ?
2400 2408 while r == 4:
2401 2409 for c, t in ui.extractchoices(responses)[1]:
2402 2410 ui.write(b'%s - %s\n' % (c, encoding.lower(t)))
2403 2411 r = ui.promptchoice(
2404 2412 _(b'forget %s %s') % (uipathfn(filename), responses)
2405 2413 )
2406 2414 if r == 0: # yes
2407 2415 continue
2408 2416 elif r == 1: # no
2409 2417 forget.remove(filename)
2410 2418 elif r == 2: # Skip
2411 2419 fnindex = forget.index(filename)
2412 2420 del forget[fnindex:]
2413 2421 break
2414 2422 elif r == 3: # All
2415 2423 break
2416 2424
2417 2425 for f in forget:
2418 2426 if ui.verbose or not match.exact(f) or interactive:
2419 2427 ui.status(
2420 2428 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2421 2429 )
2422 2430
2423 2431 if not dryrun:
2424 2432 rejected = wctx.forget(forget, prefix)
2425 2433 bad.extend(f for f in rejected if f in match.files())
2426 2434 forgot.extend(f for f in forget if f not in rejected)
2427 2435 return bad, forgot
2428 2436
2429 2437
2430 2438 def files(ui, ctx, m, uipathfn, fm, fmt, subrepos):
2431 2439 ret = 1
2432 2440
2433 2441 needsfctx = ui.verbose or {b'size', b'flags'} & fm.datahint()
2434 2442 if fm.isplain() and not needsfctx:
2435 2443 # Fast path. The speed-up comes from skipping the formatter, and batching
2436 2444 # calls to ui.write.
2437 2445 buf = []
2438 2446 for f in ctx.matches(m):
2439 2447 buf.append(fmt % uipathfn(f))
2440 2448 if len(buf) > 100:
2441 2449 ui.write(b''.join(buf))
2442 2450 del buf[:]
2443 2451 ret = 0
2444 2452 if buf:
2445 2453 ui.write(b''.join(buf))
2446 2454 else:
2447 2455 for f in ctx.matches(m):
2448 2456 fm.startitem()
2449 2457 fm.context(ctx=ctx)
2450 2458 if needsfctx:
2451 2459 fc = ctx[f]
2452 2460 fm.write(b'size flags', b'% 10d % 1s ', fc.size(), fc.flags())
2453 2461 fm.data(path=f)
2454 2462 fm.plain(fmt % uipathfn(f))
2455 2463 ret = 0
2456 2464
2457 2465 for subpath in sorted(ctx.substate):
2458 2466 submatch = matchmod.subdirmatcher(subpath, m)
2459 2467 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2460 2468 if subrepos or m.exact(subpath) or any(submatch.files()):
2461 2469 sub = ctx.sub(subpath)
2462 2470 try:
2463 2471 recurse = m.exact(subpath) or subrepos
2464 2472 if (
2465 2473 sub.printfiles(ui, submatch, subuipathfn, fm, fmt, recurse)
2466 2474 == 0
2467 2475 ):
2468 2476 ret = 0
2469 2477 except error.LookupError:
2470 2478 ui.status(
2471 2479 _(b"skipping missing subrepository: %s\n")
2472 2480 % uipathfn(subpath)
2473 2481 )
2474 2482
2475 2483 return ret
2476 2484
2477 2485
2478 2486 def remove(
2479 2487 ui, repo, m, prefix, uipathfn, after, force, subrepos, dryrun, warnings=None
2480 2488 ):
2481 2489 ret = 0
2482 2490 s = repo.status(match=m, clean=True)
2483 2491 modified, added, deleted, clean = s.modified, s.added, s.deleted, s.clean
2484 2492
2485 2493 wctx = repo[None]
2486 2494
2487 2495 if warnings is None:
2488 2496 warnings = []
2489 2497 warn = True
2490 2498 else:
2491 2499 warn = False
2492 2500
2493 2501 subs = sorted(wctx.substate)
2494 2502 progress = ui.makeprogress(
2495 2503 _(b'searching'), total=len(subs), unit=_(b'subrepos')
2496 2504 )
2497 2505 for subpath in subs:
2498 2506 submatch = matchmod.subdirmatcher(subpath, m)
2499 2507 subprefix = repo.wvfs.reljoin(prefix, subpath)
2500 2508 subuipathfn = scmutil.subdiruipathfn(subpath, uipathfn)
2501 2509 if subrepos or m.exact(subpath) or any(submatch.files()):
2502 2510 progress.increment()
2503 2511 sub = wctx.sub(subpath)
2504 2512 try:
2505 2513 if sub.removefiles(
2506 2514 submatch,
2507 2515 subprefix,
2508 2516 subuipathfn,
2509 2517 after,
2510 2518 force,
2511 2519 subrepos,
2512 2520 dryrun,
2513 2521 warnings,
2514 2522 ):
2515 2523 ret = 1
2516 2524 except error.LookupError:
2517 2525 warnings.append(
2518 2526 _(b"skipping missing subrepository: %s\n")
2519 2527 % uipathfn(subpath)
2520 2528 )
2521 2529 progress.complete()
2522 2530
2523 2531 # warn about failure to delete explicit files/dirs
2524 2532 deleteddirs = pathutil.dirs(deleted)
2525 2533 files = m.files()
2526 2534 progress = ui.makeprogress(
2527 2535 _(b'deleting'), total=len(files), unit=_(b'files')
2528 2536 )
2529 2537 for f in files:
2530 2538
2531 2539 def insubrepo():
2532 2540 for subpath in wctx.substate:
2533 2541 if f.startswith(subpath + b'/'):
2534 2542 return True
2535 2543 return False
2536 2544
2537 2545 progress.increment()
2538 2546 isdir = f in deleteddirs or wctx.hasdir(f)
2539 2547 if f in repo.dirstate or isdir or f == b'.' or insubrepo() or f in subs:
2540 2548 continue
2541 2549
2542 2550 if repo.wvfs.exists(f):
2543 2551 if repo.wvfs.isdir(f):
2544 2552 warnings.append(
2545 2553 _(b'not removing %s: no tracked files\n') % uipathfn(f)
2546 2554 )
2547 2555 else:
2548 2556 warnings.append(
2549 2557 _(b'not removing %s: file is untracked\n') % uipathfn(f)
2550 2558 )
2551 2559 # missing files will generate a warning elsewhere
2552 2560 ret = 1
2553 2561 progress.complete()
2554 2562
2555 2563 if force:
2556 2564 list = modified + deleted + clean + added
2557 2565 elif after:
2558 2566 list = deleted
2559 2567 remaining = modified + added + clean
2560 2568 progress = ui.makeprogress(
2561 2569 _(b'skipping'), total=len(remaining), unit=_(b'files')
2562 2570 )
2563 2571 for f in remaining:
2564 2572 progress.increment()
2565 2573 if ui.verbose or (f in files):
2566 2574 warnings.append(
2567 2575 _(b'not removing %s: file still exists\n') % uipathfn(f)
2568 2576 )
2569 2577 ret = 1
2570 2578 progress.complete()
2571 2579 else:
2572 2580 list = deleted + clean
2573 2581 progress = ui.makeprogress(
2574 2582 _(b'skipping'), total=(len(modified) + len(added)), unit=_(b'files')
2575 2583 )
2576 2584 for f in modified:
2577 2585 progress.increment()
2578 2586 warnings.append(
2579 2587 _(
2580 2588 b'not removing %s: file is modified (use -f'
2581 2589 b' to force removal)\n'
2582 2590 )
2583 2591 % uipathfn(f)
2584 2592 )
2585 2593 ret = 1
2586 2594 for f in added:
2587 2595 progress.increment()
2588 2596 warnings.append(
2589 2597 _(
2590 2598 b"not removing %s: file has been marked for add"
2591 2599 b" (use 'hg forget' to undo add)\n"
2592 2600 )
2593 2601 % uipathfn(f)
2594 2602 )
2595 2603 ret = 1
2596 2604 progress.complete()
2597 2605
2598 2606 list = sorted(list)
2599 2607 progress = ui.makeprogress(
2600 2608 _(b'deleting'), total=len(list), unit=_(b'files')
2601 2609 )
2602 2610 for f in list:
2603 2611 if ui.verbose or not m.exact(f):
2604 2612 progress.increment()
2605 2613 ui.status(
2606 2614 _(b'removing %s\n') % uipathfn(f), label=b'ui.addremove.removed'
2607 2615 )
2608 2616 progress.complete()
2609 2617
2610 2618 if not dryrun:
2611 2619 with repo.wlock():
2612 2620 if not after:
2613 2621 for f in list:
2614 2622 if f in added:
2615 2623 continue # we never unlink added files on remove
2616 2624 rmdir = repo.ui.configbool(
2617 2625 b'experimental', b'removeemptydirs'
2618 2626 )
2619 2627 repo.wvfs.unlinkpath(f, ignoremissing=True, rmdir=rmdir)
2620 2628 repo[None].forget(list)
2621 2629
2622 2630 if warn:
2623 2631 for warning in warnings:
2624 2632 ui.warn(warning)
2625 2633
2626 2634 return ret
2627 2635
2628 2636
2629 2637 def _catfmtneedsdata(fm):
2630 2638 return not fm.datahint() or b'data' in fm.datahint()
2631 2639
2632 2640
2633 2641 def _updatecatformatter(fm, ctx, matcher, path, decode):
2634 2642 """Hook for adding data to the formatter used by ``hg cat``.
2635 2643
2636 2644 Extensions (e.g., lfs) can wrap this to inject keywords/data, but must call
2637 2645 this method first."""
2638 2646
2639 2647 # data() can be expensive to fetch (e.g. lfs), so don't fetch it if it
2640 2648 # wasn't requested.
2641 2649 data = b''
2642 2650 if _catfmtneedsdata(fm):
2643 2651 data = ctx[path].data()
2644 2652 if decode:
2645 2653 data = ctx.repo().wwritedata(path, data)
2646 2654 fm.startitem()
2647 2655 fm.context(ctx=ctx)
2648 2656 fm.write(b'data', b'%s', data)
2649 2657 fm.data(path=path)
2650 2658
2651 2659
2652 2660 def cat(ui, repo, ctx, matcher, basefm, fntemplate, prefix, **opts):
2653 2661 err = 1
2654 2662 opts = pycompat.byteskwargs(opts)
2655 2663
2656 2664 def write(path):
2657 2665 filename = None
2658 2666 if fntemplate:
2659 2667 filename = makefilename(
2660 2668 ctx, fntemplate, pathname=os.path.join(prefix, path)
2661 2669 )
2662 2670 # attempt to create the directory if it does not already exist
2663 2671 try:
2664 2672 os.makedirs(os.path.dirname(filename))
2665 2673 except OSError:
2666 2674 pass
2667 2675 with formatter.maybereopen(basefm, filename) as fm:
2668 2676 _updatecatformatter(fm, ctx, matcher, path, opts.get(b'decode'))
2669 2677
2670 2678 # Automation often uses hg cat on single files, so special case it
2671 2679 # for performance to avoid the cost of parsing the manifest.
2672 2680 if len(matcher.files()) == 1 and not matcher.anypats():
2673 2681 file = matcher.files()[0]
2674 2682 mfl = repo.manifestlog
2675 2683 mfnode = ctx.manifestnode()
2676 2684 try:
2677 2685 if mfnode and mfl[mfnode].find(file)[0]:
2678 2686 if _catfmtneedsdata(basefm):
2679 2687 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2680 2688 write(file)
2681 2689 return 0
2682 2690 except KeyError:
2683 2691 pass
2684 2692
2685 2693 if _catfmtneedsdata(basefm):
2686 2694 scmutil.prefetchfiles(repo, [(ctx.rev(), matcher)])
2687 2695
2688 2696 for abs in ctx.walk(matcher):
2689 2697 write(abs)
2690 2698 err = 0
2691 2699
2692 2700 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
2693 2701 for subpath in sorted(ctx.substate):
2694 2702 sub = ctx.sub(subpath)
2695 2703 try:
2696 2704 submatch = matchmod.subdirmatcher(subpath, matcher)
2697 2705 subprefix = os.path.join(prefix, subpath)
2698 2706 if not sub.cat(
2699 2707 submatch,
2700 2708 basefm,
2701 2709 fntemplate,
2702 2710 subprefix,
2703 2711 **pycompat.strkwargs(opts)
2704 2712 ):
2705 2713 err = 0
2706 2714 except error.RepoLookupError:
2707 2715 ui.status(
2708 2716 _(b"skipping missing subrepository: %s\n") % uipathfn(subpath)
2709 2717 )
2710 2718
2711 2719 return err
2712 2720
2713 2721
2714 2722 def commit(ui, repo, commitfunc, pats, opts):
2715 2723 '''commit the specified files or all outstanding changes'''
2716 2724 date = opts.get(b'date')
2717 2725 if date:
2718 2726 opts[b'date'] = dateutil.parsedate(date)
2719 2727 message = logmessage(ui, opts)
2720 2728 matcher = scmutil.match(repo[None], pats, opts)
2721 2729
2722 2730 dsguard = None
2723 2731 # extract addremove carefully -- this function can be called from a command
2724 2732 # that doesn't support addremove
2725 2733 if opts.get(b'addremove'):
2726 2734 dsguard = dirstateguard.dirstateguard(repo, b'commit')
2727 2735 with dsguard or util.nullcontextmanager():
2728 2736 if dsguard:
2729 2737 relative = scmutil.anypats(pats, opts)
2730 2738 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2731 2739 if scmutil.addremove(repo, matcher, b"", uipathfn, opts) != 0:
2732 2740 raise error.Abort(
2733 2741 _(b"failed to mark all new/missing files as added/removed")
2734 2742 )
2735 2743
2736 2744 return commitfunc(ui, repo, message, matcher, opts)
2737 2745
2738 2746
2739 2747 def samefile(f, ctx1, ctx2):
2740 2748 if f in ctx1.manifest():
2741 2749 a = ctx1.filectx(f)
2742 2750 if f in ctx2.manifest():
2743 2751 b = ctx2.filectx(f)
2744 2752 return not a.cmp(b) and a.flags() == b.flags()
2745 2753 else:
2746 2754 return False
2747 2755 else:
2748 2756 return f not in ctx2.manifest()
2749 2757
2750 2758
2751 2759 def amend(ui, repo, old, extra, pats, opts):
2752 2760 # avoid cycle context -> subrepo -> cmdutil
2753 2761 from . import context
2754 2762
2755 2763 # amend will reuse the existing user if not specified, but the obsolete
2756 2764 # marker creation requires that the current user's name is specified.
2757 2765 if obsolete.isenabled(repo, obsolete.createmarkersopt):
2758 2766 ui.username() # raise exception if username not set
2759 2767
2760 2768 ui.note(_(b'amending changeset %s\n') % old)
2761 2769 base = old.p1()
2762 2770
2763 2771 with repo.wlock(), repo.lock(), repo.transaction(b'amend'):
2764 2772 # Participating changesets:
2765 2773 #
2766 2774 # wctx o - workingctx that contains changes from working copy
2767 2775 # | to go into amending commit
2768 2776 # |
2769 2777 # old o - changeset to amend
2770 2778 # |
2771 2779 # base o - first parent of the changeset to amend
2772 2780 wctx = repo[None]
2773 2781
2774 2782 # Copy to avoid mutating input
2775 2783 extra = extra.copy()
2776 2784 # Update extra dict from amended commit (e.g. to preserve graft
2777 2785 # source)
2778 2786 extra.update(old.extra())
2779 2787
2780 2788 # Also update it from the from the wctx
2781 2789 extra.update(wctx.extra())
2782 2790
2783 2791 # date-only change should be ignored?
2784 2792 datemaydiffer = resolvecommitoptions(ui, opts)
2785 2793
2786 2794 date = old.date()
2787 2795 if opts.get(b'date'):
2788 2796 date = dateutil.parsedate(opts.get(b'date'))
2789 2797 user = opts.get(b'user') or old.user()
2790 2798
2791 2799 if len(old.parents()) > 1:
2792 2800 # ctx.files() isn't reliable for merges, so fall back to the
2793 2801 # slower repo.status() method
2794 2802 st = base.status(old)
2795 2803 files = set(st.modified) | set(st.added) | set(st.removed)
2796 2804 else:
2797 2805 files = set(old.files())
2798 2806
2799 2807 # add/remove the files to the working copy if the "addremove" option
2800 2808 # was specified.
2801 2809 matcher = scmutil.match(wctx, pats, opts)
2802 2810 relative = scmutil.anypats(pats, opts)
2803 2811 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=relative)
2804 2812 if opts.get(b'addremove') and scmutil.addremove(
2805 2813 repo, matcher, b"", uipathfn, opts
2806 2814 ):
2807 2815 raise error.Abort(
2808 2816 _(b"failed to mark all new/missing files as added/removed")
2809 2817 )
2810 2818
2811 2819 # Check subrepos. This depends on in-place wctx._status update in
2812 2820 # subrepo.precommit(). To minimize the risk of this hack, we do
2813 2821 # nothing if .hgsub does not exist.
2814 2822 if b'.hgsub' in wctx or b'.hgsub' in old:
2815 2823 subs, commitsubs, newsubstate = subrepoutil.precommit(
2816 2824 ui, wctx, wctx._status, matcher
2817 2825 )
2818 2826 # amend should abort if commitsubrepos is enabled
2819 2827 assert not commitsubs
2820 2828 if subs:
2821 2829 subrepoutil.writestate(repo, newsubstate)
2822 2830
2823 2831 ms = mergestatemod.mergestate.read(repo)
2824 2832 mergeutil.checkunresolved(ms)
2825 2833
2826 2834 filestoamend = {f for f in wctx.files() if matcher(f)}
2827 2835
2828 2836 changes = len(filestoamend) > 0
2829 2837 if changes:
2830 2838 # Recompute copies (avoid recording a -> b -> a)
2831 2839 copied = copies.pathcopies(base, wctx, matcher)
2832 2840 if old.p2:
2833 2841 copied.update(copies.pathcopies(old.p2(), wctx, matcher))
2834 2842
2835 2843 # Prune files which were reverted by the updates: if old
2836 2844 # introduced file X and the file was renamed in the working
2837 2845 # copy, then those two files are the same and
2838 2846 # we can discard X from our list of files. Likewise if X
2839 2847 # was removed, it's no longer relevant. If X is missing (aka
2840 2848 # deleted), old X must be preserved.
2841 2849 files.update(filestoamend)
2842 2850 files = [
2843 2851 f
2844 2852 for f in files
2845 2853 if (f not in filestoamend or not samefile(f, wctx, base))
2846 2854 ]
2847 2855
2848 2856 def filectxfn(repo, ctx_, path):
2849 2857 try:
2850 2858 # If the file being considered is not amongst the files
2851 2859 # to be amended, we should return the file context from the
2852 2860 # old changeset. This avoids issues when only some files in
2853 2861 # the working copy are being amended but there are also
2854 2862 # changes to other files from the old changeset.
2855 2863 if path not in filestoamend:
2856 2864 return old.filectx(path)
2857 2865
2858 2866 # Return None for removed files.
2859 2867 if path in wctx.removed():
2860 2868 return None
2861 2869
2862 2870 fctx = wctx[path]
2863 2871 flags = fctx.flags()
2864 2872 mctx = context.memfilectx(
2865 2873 repo,
2866 2874 ctx_,
2867 2875 fctx.path(),
2868 2876 fctx.data(),
2869 2877 islink=b'l' in flags,
2870 2878 isexec=b'x' in flags,
2871 2879 copysource=copied.get(path),
2872 2880 )
2873 2881 return mctx
2874 2882 except KeyError:
2875 2883 return None
2876 2884
2877 2885 else:
2878 2886 ui.note(_(b'copying changeset %s to %s\n') % (old, base))
2879 2887
2880 2888 # Use version of files as in the old cset
2881 2889 def filectxfn(repo, ctx_, path):
2882 2890 try:
2883 2891 return old.filectx(path)
2884 2892 except KeyError:
2885 2893 return None
2886 2894
2887 2895 # See if we got a message from -m or -l, if not, open the editor with
2888 2896 # the message of the changeset to amend.
2889 2897 message = logmessage(ui, opts)
2890 2898
2891 2899 editform = mergeeditform(old, b'commit.amend')
2892 2900
2893 2901 if not message:
2894 2902 message = old.description()
2895 2903 # Default if message isn't provided and --edit is not passed is to
2896 2904 # invoke editor, but allow --no-edit. If somehow we don't have any
2897 2905 # description, let's always start the editor.
2898 2906 doedit = not message or opts.get(b'edit') in [True, None]
2899 2907 else:
2900 2908 # Default if message is provided is to not invoke editor, but allow
2901 2909 # --edit.
2902 2910 doedit = opts.get(b'edit') is True
2903 2911 editor = getcommiteditor(edit=doedit, editform=editform)
2904 2912
2905 2913 pureextra = extra.copy()
2906 2914 extra[b'amend_source'] = old.hex()
2907 2915
2908 2916 new = context.memctx(
2909 2917 repo,
2910 2918 parents=[base.node(), old.p2().node()],
2911 2919 text=message,
2912 2920 files=files,
2913 2921 filectxfn=filectxfn,
2914 2922 user=user,
2915 2923 date=date,
2916 2924 extra=extra,
2917 2925 editor=editor,
2918 2926 )
2919 2927
2920 2928 newdesc = changelog.stripdesc(new.description())
2921 2929 if (
2922 2930 (not changes)
2923 2931 and newdesc == old.description()
2924 2932 and user == old.user()
2925 2933 and (date == old.date() or datemaydiffer)
2926 2934 and pureextra == old.extra()
2927 2935 ):
2928 2936 # nothing changed. continuing here would create a new node
2929 2937 # anyway because of the amend_source noise.
2930 2938 #
2931 2939 # This not what we expect from amend.
2932 2940 return old.node()
2933 2941
2934 2942 commitphase = None
2935 2943 if opts.get(b'secret'):
2936 2944 commitphase = phases.secret
2937 2945 newid = repo.commitctx(new)
2938 2946 ms.reset()
2939 2947
2940 2948 # Reroute the working copy parent to the new changeset
2941 2949 repo.setparents(newid, nullid)
2942 2950 mapping = {old.node(): (newid,)}
2943 2951 obsmetadata = None
2944 2952 if opts.get(b'note'):
2945 2953 obsmetadata = {b'note': encoding.fromlocal(opts[b'note'])}
2946 2954 backup = ui.configbool(b'rewrite', b'backup-bundle')
2947 2955 scmutil.cleanupnodes(
2948 2956 repo,
2949 2957 mapping,
2950 2958 b'amend',
2951 2959 metadata=obsmetadata,
2952 2960 fixphase=True,
2953 2961 targetphase=commitphase,
2954 2962 backup=backup,
2955 2963 )
2956 2964
2957 2965 # Fixing the dirstate because localrepo.commitctx does not update
2958 2966 # it. This is rather convenient because we did not need to update
2959 2967 # the dirstate for all the files in the new commit which commitctx
2960 2968 # could have done if it updated the dirstate. Now, we can
2961 2969 # selectively update the dirstate only for the amended files.
2962 2970 dirstate = repo.dirstate
2963 2971
2964 2972 # Update the state of the files which were added and modified in the
2965 2973 # amend to "normal" in the dirstate. We need to use "normallookup" since
2966 2974 # the files may have changed since the command started; using "normal"
2967 2975 # would mark them as clean but with uncommitted contents.
2968 2976 normalfiles = set(wctx.modified() + wctx.added()) & filestoamend
2969 2977 for f in normalfiles:
2970 2978 dirstate.normallookup(f)
2971 2979
2972 2980 # Update the state of files which were removed in the amend
2973 2981 # to "removed" in the dirstate.
2974 2982 removedfiles = set(wctx.removed()) & filestoamend
2975 2983 for f in removedfiles:
2976 2984 dirstate.drop(f)
2977 2985
2978 2986 return newid
2979 2987
2980 2988
2981 2989 def commiteditor(repo, ctx, subs, editform=b''):
2982 2990 if ctx.description():
2983 2991 return ctx.description()
2984 2992 return commitforceeditor(
2985 2993 repo, ctx, subs, editform=editform, unchangedmessagedetection=True
2986 2994 )
2987 2995
2988 2996
2989 2997 def commitforceeditor(
2990 2998 repo,
2991 2999 ctx,
2992 3000 subs,
2993 3001 finishdesc=None,
2994 3002 extramsg=None,
2995 3003 editform=b'',
2996 3004 unchangedmessagedetection=False,
2997 3005 ):
2998 3006 if not extramsg:
2999 3007 extramsg = _(b"Leave message empty to abort commit.")
3000 3008
3001 3009 forms = [e for e in editform.split(b'.') if e]
3002 3010 forms.insert(0, b'changeset')
3003 3011 templatetext = None
3004 3012 while forms:
3005 3013 ref = b'.'.join(forms)
3006 3014 if repo.ui.config(b'committemplate', ref):
3007 3015 templatetext = committext = buildcommittemplate(
3008 3016 repo, ctx, subs, extramsg, ref
3009 3017 )
3010 3018 break
3011 3019 forms.pop()
3012 3020 else:
3013 3021 committext = buildcommittext(repo, ctx, subs, extramsg)
3014 3022
3015 3023 # run editor in the repository root
3016 3024 olddir = encoding.getcwd()
3017 3025 os.chdir(repo.root)
3018 3026
3019 3027 # make in-memory changes visible to external process
3020 3028 tr = repo.currenttransaction()
3021 3029 repo.dirstate.write(tr)
3022 3030 pending = tr and tr.writepending() and repo.root
3023 3031
3024 3032 editortext = repo.ui.edit(
3025 3033 committext,
3026 3034 ctx.user(),
3027 3035 ctx.extra(),
3028 3036 editform=editform,
3029 3037 pending=pending,
3030 3038 repopath=repo.path,
3031 3039 action=b'commit',
3032 3040 )
3033 3041 text = editortext
3034 3042
3035 3043 # strip away anything below this special string (used for editors that want
3036 3044 # to display the diff)
3037 3045 stripbelow = re.search(_linebelow, text, flags=re.MULTILINE)
3038 3046 if stripbelow:
3039 3047 text = text[: stripbelow.start()]
3040 3048
3041 3049 text = re.sub(b"(?m)^HG:.*(\n|$)", b"", text)
3042 3050 os.chdir(olddir)
3043 3051
3044 3052 if finishdesc:
3045 3053 text = finishdesc(text)
3046 3054 if not text.strip():
3047 3055 raise error.Abort(_(b"empty commit message"))
3048 3056 if unchangedmessagedetection and editortext == templatetext:
3049 3057 raise error.Abort(_(b"commit message unchanged"))
3050 3058
3051 3059 return text
3052 3060
3053 3061
3054 3062 def buildcommittemplate(repo, ctx, subs, extramsg, ref):
3055 3063 ui = repo.ui
3056 3064 spec = formatter.reference_templatespec(ref)
3057 3065 t = logcmdutil.changesettemplater(ui, repo, spec)
3058 3066 t.t.cache.update(
3059 3067 (k, templater.unquotestring(v))
3060 3068 for k, v in repo.ui.configitems(b'committemplate')
3061 3069 )
3062 3070
3063 3071 if not extramsg:
3064 3072 extramsg = b'' # ensure that extramsg is string
3065 3073
3066 3074 ui.pushbuffer()
3067 3075 t.show(ctx, extramsg=extramsg)
3068 3076 return ui.popbuffer()
3069 3077
3070 3078
3071 3079 def hgprefix(msg):
3072 3080 return b"\n".join([b"HG: %s" % a for a in msg.split(b"\n") if a])
3073 3081
3074 3082
3075 3083 def buildcommittext(repo, ctx, subs, extramsg):
3076 3084 edittext = []
3077 3085 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
3078 3086 if ctx.description():
3079 3087 edittext.append(ctx.description())
3080 3088 edittext.append(b"")
3081 3089 edittext.append(b"") # Empty line between message and comments.
3082 3090 edittext.append(
3083 3091 hgprefix(
3084 3092 _(
3085 3093 b"Enter commit message."
3086 3094 b" Lines beginning with 'HG:' are removed."
3087 3095 )
3088 3096 )
3089 3097 )
3090 3098 edittext.append(hgprefix(extramsg))
3091 3099 edittext.append(b"HG: --")
3092 3100 edittext.append(hgprefix(_(b"user: %s") % ctx.user()))
3093 3101 if ctx.p2():
3094 3102 edittext.append(hgprefix(_(b"branch merge")))
3095 3103 if ctx.branch():
3096 3104 edittext.append(hgprefix(_(b"branch '%s'") % ctx.branch()))
3097 3105 if bookmarks.isactivewdirparent(repo):
3098 3106 edittext.append(hgprefix(_(b"bookmark '%s'") % repo._activebookmark))
3099 3107 edittext.extend([hgprefix(_(b"subrepo %s") % s) for s in subs])
3100 3108 edittext.extend([hgprefix(_(b"added %s") % f) for f in added])
3101 3109 edittext.extend([hgprefix(_(b"changed %s") % f) for f in modified])
3102 3110 edittext.extend([hgprefix(_(b"removed %s") % f) for f in removed])
3103 3111 if not added and not modified and not removed:
3104 3112 edittext.append(hgprefix(_(b"no files changed")))
3105 3113 edittext.append(b"")
3106 3114
3107 3115 return b"\n".join(edittext)
3108 3116
3109 3117
3110 3118 def commitstatus(repo, node, branch, bheads=None, opts=None):
3111 3119 if opts is None:
3112 3120 opts = {}
3113 3121 ctx = repo[node]
3114 3122 parents = ctx.parents()
3115 3123
3116 3124 if (
3117 3125 not opts.get(b'amend')
3118 3126 and bheads
3119 3127 and node not in bheads
3120 3128 and not any(
3121 3129 p.node() in bheads and p.branch() == branch for p in parents
3122 3130 )
3123 3131 ):
3124 3132 repo.ui.status(_(b'created new head\n'))
3125 3133 # The message is not printed for initial roots. For the other
3126 3134 # changesets, it is printed in the following situations:
3127 3135 #
3128 3136 # Par column: for the 2 parents with ...
3129 3137 # N: null or no parent
3130 3138 # B: parent is on another named branch
3131 3139 # C: parent is a regular non head changeset
3132 3140 # H: parent was a branch head of the current branch
3133 3141 # Msg column: whether we print "created new head" message
3134 3142 # In the following, it is assumed that there already exists some
3135 3143 # initial branch heads of the current branch, otherwise nothing is
3136 3144 # printed anyway.
3137 3145 #
3138 3146 # Par Msg Comment
3139 3147 # N N y additional topo root
3140 3148 #
3141 3149 # B N y additional branch root
3142 3150 # C N y additional topo head
3143 3151 # H N n usual case
3144 3152 #
3145 3153 # B B y weird additional branch root
3146 3154 # C B y branch merge
3147 3155 # H B n merge with named branch
3148 3156 #
3149 3157 # C C y additional head from merge
3150 3158 # C H n merge with a head
3151 3159 #
3152 3160 # H H n head merge: head count decreases
3153 3161
3154 3162 if not opts.get(b'close_branch'):
3155 3163 for r in parents:
3156 3164 if r.closesbranch() and r.branch() == branch:
3157 3165 repo.ui.status(
3158 3166 _(b'reopening closed branch head %d\n') % r.rev()
3159 3167 )
3160 3168
3161 3169 if repo.ui.debugflag:
3162 3170 repo.ui.write(
3163 3171 _(b'committed changeset %d:%s\n') % (ctx.rev(), ctx.hex())
3164 3172 )
3165 3173 elif repo.ui.verbose:
3166 3174 repo.ui.write(_(b'committed changeset %d:%s\n') % (ctx.rev(), ctx))
3167 3175
3168 3176
3169 3177 def postcommitstatus(repo, pats, opts):
3170 3178 return repo.status(match=scmutil.match(repo[None], pats, opts))
3171 3179
3172 3180
3173 3181 def revert(ui, repo, ctx, *pats, **opts):
3174 3182 opts = pycompat.byteskwargs(opts)
3175 3183 parent, p2 = repo.dirstate.parents()
3176 3184 node = ctx.node()
3177 3185
3178 3186 mf = ctx.manifest()
3179 3187 if node == p2:
3180 3188 parent = p2
3181 3189
3182 3190 # need all matching names in dirstate and manifest of target rev,
3183 3191 # so have to walk both. do not print errors if files exist in one
3184 3192 # but not other. in both cases, filesets should be evaluated against
3185 3193 # workingctx to get consistent result (issue4497). this means 'set:**'
3186 3194 # cannot be used to select missing files from target rev.
3187 3195
3188 3196 # `names` is a mapping for all elements in working copy and target revision
3189 3197 # The mapping is in the form:
3190 3198 # <abs path in repo> -> (<path from CWD>, <exactly specified by matcher?>)
3191 3199 names = {}
3192 3200 uipathfn = scmutil.getuipathfn(repo, legacyrelativevalue=True)
3193 3201
3194 3202 with repo.wlock():
3195 3203 ## filling of the `names` mapping
3196 3204 # walk dirstate to fill `names`
3197 3205
3198 3206 interactive = opts.get(b'interactive', False)
3199 3207 wctx = repo[None]
3200 3208 m = scmutil.match(wctx, pats, opts)
3201 3209
3202 3210 # we'll need this later
3203 3211 targetsubs = sorted(s for s in wctx.substate if m(s))
3204 3212
3205 3213 if not m.always():
3206 3214 matcher = matchmod.badmatch(m, lambda x, y: False)
3207 3215 for abs in wctx.walk(matcher):
3208 3216 names[abs] = m.exact(abs)
3209 3217
3210 3218 # walk target manifest to fill `names`
3211 3219
3212 3220 def badfn(path, msg):
3213 3221 if path in names:
3214 3222 return
3215 3223 if path in ctx.substate:
3216 3224 return
3217 3225 path_ = path + b'/'
3218 3226 for f in names:
3219 3227 if f.startswith(path_):
3220 3228 return
3221 3229 ui.warn(b"%s: %s\n" % (uipathfn(path), msg))
3222 3230
3223 3231 for abs in ctx.walk(matchmod.badmatch(m, badfn)):
3224 3232 if abs not in names:
3225 3233 names[abs] = m.exact(abs)
3226 3234
3227 3235 # Find status of all file in `names`.
3228 3236 m = scmutil.matchfiles(repo, names)
3229 3237
3230 3238 changes = repo.status(
3231 3239 node1=node, match=m, unknown=True, ignored=True, clean=True
3232 3240 )
3233 3241 else:
3234 3242 changes = repo.status(node1=node, match=m)
3235 3243 for kind in changes:
3236 3244 for abs in kind:
3237 3245 names[abs] = m.exact(abs)
3238 3246
3239 3247 m = scmutil.matchfiles(repo, names)
3240 3248
3241 3249 modified = set(changes.modified)
3242 3250 added = set(changes.added)
3243 3251 removed = set(changes.removed)
3244 3252 _deleted = set(changes.deleted)
3245 3253 unknown = set(changes.unknown)
3246 3254 unknown.update(changes.ignored)
3247 3255 clean = set(changes.clean)
3248 3256 modadded = set()
3249 3257
3250 3258 # We need to account for the state of the file in the dirstate,
3251 3259 # even when we revert against something else than parent. This will
3252 3260 # slightly alter the behavior of revert (doing back up or not, delete
3253 3261 # or just forget etc).
3254 3262 if parent == node:
3255 3263 dsmodified = modified
3256 3264 dsadded = added
3257 3265 dsremoved = removed
3258 3266 # store all local modifications, useful later for rename detection
3259 3267 localchanges = dsmodified | dsadded
3260 3268 modified, added, removed = set(), set(), set()
3261 3269 else:
3262 3270 changes = repo.status(node1=parent, match=m)
3263 3271 dsmodified = set(changes.modified)
3264 3272 dsadded = set(changes.added)
3265 3273 dsremoved = set(changes.removed)
3266 3274 # store all local modifications, useful later for rename detection
3267 3275 localchanges = dsmodified | dsadded
3268 3276
3269 3277 # only take into account for removes between wc and target
3270 3278 clean |= dsremoved - removed
3271 3279 dsremoved &= removed
3272 3280 # distinct between dirstate remove and other
3273 3281 removed -= dsremoved
3274 3282
3275 3283 modadded = added & dsmodified
3276 3284 added -= modadded
3277 3285
3278 3286 # tell newly modified apart.
3279 3287 dsmodified &= modified
3280 3288 dsmodified |= modified & dsadded # dirstate added may need backup
3281 3289 modified -= dsmodified
3282 3290
3283 3291 # We need to wait for some post-processing to update this set
3284 3292 # before making the distinction. The dirstate will be used for
3285 3293 # that purpose.
3286 3294 dsadded = added
3287 3295
3288 3296 # in case of merge, files that are actually added can be reported as
3289 3297 # modified, we need to post process the result
3290 3298 if p2 != nullid:
3291 3299 mergeadd = set(dsmodified)
3292 3300 for path in dsmodified:
3293 3301 if path in mf:
3294 3302 mergeadd.remove(path)
3295 3303 dsadded |= mergeadd
3296 3304 dsmodified -= mergeadd
3297 3305
3298 3306 # if f is a rename, update `names` to also revert the source
3299 3307 for f in localchanges:
3300 3308 src = repo.dirstate.copied(f)
3301 3309 # XXX should we check for rename down to target node?
3302 3310 if src and src not in names and repo.dirstate[src] == b'r':
3303 3311 dsremoved.add(src)
3304 3312 names[src] = True
3305 3313
3306 3314 # determine the exact nature of the deleted changesets
3307 3315 deladded = set(_deleted)
3308 3316 for path in _deleted:
3309 3317 if path in mf:
3310 3318 deladded.remove(path)
3311 3319 deleted = _deleted - deladded
3312 3320
3313 3321 # distinguish between file to forget and the other
3314 3322 added = set()
3315 3323 for abs in dsadded:
3316 3324 if repo.dirstate[abs] != b'a':
3317 3325 added.add(abs)
3318 3326 dsadded -= added
3319 3327
3320 3328 for abs in deladded:
3321 3329 if repo.dirstate[abs] == b'a':
3322 3330 dsadded.add(abs)
3323 3331 deladded -= dsadded
3324 3332
3325 3333 # For files marked as removed, we check if an unknown file is present at
3326 3334 # the same path. If a such file exists it may need to be backed up.
3327 3335 # Making the distinction at this stage helps have simpler backup
3328 3336 # logic.
3329 3337 removunk = set()
3330 3338 for abs in removed:
3331 3339 target = repo.wjoin(abs)
3332 3340 if os.path.lexists(target):
3333 3341 removunk.add(abs)
3334 3342 removed -= removunk
3335 3343
3336 3344 dsremovunk = set()
3337 3345 for abs in dsremoved:
3338 3346 target = repo.wjoin(abs)
3339 3347 if os.path.lexists(target):
3340 3348 dsremovunk.add(abs)
3341 3349 dsremoved -= dsremovunk
3342 3350
3343 3351 # action to be actually performed by revert
3344 3352 # (<list of file>, message>) tuple
3345 3353 actions = {
3346 3354 b'revert': ([], _(b'reverting %s\n')),
3347 3355 b'add': ([], _(b'adding %s\n')),
3348 3356 b'remove': ([], _(b'removing %s\n')),
3349 3357 b'drop': ([], _(b'removing %s\n')),
3350 3358 b'forget': ([], _(b'forgetting %s\n')),
3351 3359 b'undelete': ([], _(b'undeleting %s\n')),
3352 3360 b'noop': (None, _(b'no changes needed to %s\n')),
3353 3361 b'unknown': (None, _(b'file not managed: %s\n')),
3354 3362 }
3355 3363
3356 3364 # "constant" that convey the backup strategy.
3357 3365 # All set to `discard` if `no-backup` is set do avoid checking
3358 3366 # no_backup lower in the code.
3359 3367 # These values are ordered for comparison purposes
3360 3368 backupinteractive = 3 # do backup if interactively modified
3361 3369 backup = 2 # unconditionally do backup
3362 3370 check = 1 # check if the existing file differs from target
3363 3371 discard = 0 # never do backup
3364 3372 if opts.get(b'no_backup'):
3365 3373 backupinteractive = backup = check = discard
3366 3374 if interactive:
3367 3375 dsmodifiedbackup = backupinteractive
3368 3376 else:
3369 3377 dsmodifiedbackup = backup
3370 3378 tobackup = set()
3371 3379
3372 3380 backupanddel = actions[b'remove']
3373 3381 if not opts.get(b'no_backup'):
3374 3382 backupanddel = actions[b'drop']
3375 3383
3376 3384 disptable = (
3377 3385 # dispatch table:
3378 3386 # file state
3379 3387 # action
3380 3388 # make backup
3381 3389 ## Sets that results that will change file on disk
3382 3390 # Modified compared to target, no local change
3383 3391 (modified, actions[b'revert'], discard),
3384 3392 # Modified compared to target, but local file is deleted
3385 3393 (deleted, actions[b'revert'], discard),
3386 3394 # Modified compared to target, local change
3387 3395 (dsmodified, actions[b'revert'], dsmodifiedbackup),
3388 3396 # Added since target
3389 3397 (added, actions[b'remove'], discard),
3390 3398 # Added in working directory
3391 3399 (dsadded, actions[b'forget'], discard),
3392 3400 # Added since target, have local modification
3393 3401 (modadded, backupanddel, backup),
3394 3402 # Added since target but file is missing in working directory
3395 3403 (deladded, actions[b'drop'], discard),
3396 3404 # Removed since target, before working copy parent
3397 3405 (removed, actions[b'add'], discard),
3398 3406 # Same as `removed` but an unknown file exists at the same path
3399 3407 (removunk, actions[b'add'], check),
3400 3408 # Removed since targe, marked as such in working copy parent
3401 3409 (dsremoved, actions[b'undelete'], discard),
3402 3410 # Same as `dsremoved` but an unknown file exists at the same path
3403 3411 (dsremovunk, actions[b'undelete'], check),
3404 3412 ## the following sets does not result in any file changes
3405 3413 # File with no modification
3406 3414 (clean, actions[b'noop'], discard),
3407 3415 # Existing file, not tracked anywhere
3408 3416 (unknown, actions[b'unknown'], discard),
3409 3417 )
3410 3418
3411 3419 for abs, exact in sorted(names.items()):
3412 3420 # target file to be touch on disk (relative to cwd)
3413 3421 target = repo.wjoin(abs)
3414 3422 # search the entry in the dispatch table.
3415 3423 # if the file is in any of these sets, it was touched in the working
3416 3424 # directory parent and we are sure it needs to be reverted.
3417 3425 for table, (xlist, msg), dobackup in disptable:
3418 3426 if abs not in table:
3419 3427 continue
3420 3428 if xlist is not None:
3421 3429 xlist.append(abs)
3422 3430 if dobackup:
3423 3431 # If in interactive mode, don't automatically create
3424 3432 # .orig files (issue4793)
3425 3433 if dobackup == backupinteractive:
3426 3434 tobackup.add(abs)
3427 3435 elif backup <= dobackup or wctx[abs].cmp(ctx[abs]):
3428 3436 absbakname = scmutil.backuppath(ui, repo, abs)
3429 3437 bakname = os.path.relpath(
3430 3438 absbakname, start=repo.root
3431 3439 )
3432 3440 ui.note(
3433 3441 _(b'saving current version of %s as %s\n')
3434 3442 % (uipathfn(abs), uipathfn(bakname))
3435 3443 )
3436 3444 if not opts.get(b'dry_run'):
3437 3445 if interactive:
3438 3446 util.copyfile(target, absbakname)
3439 3447 else:
3440 3448 util.rename(target, absbakname)
3441 3449 if opts.get(b'dry_run'):
3442 3450 if ui.verbose or not exact:
3443 3451 ui.status(msg % uipathfn(abs))
3444 3452 elif exact:
3445 3453 ui.warn(msg % uipathfn(abs))
3446 3454 break
3447 3455
3448 3456 if not opts.get(b'dry_run'):
3449 3457 needdata = (b'revert', b'add', b'undelete')
3450 3458 oplist = [actions[name][0] for name in needdata]
3451 3459 prefetch = scmutil.prefetchfiles
3452 3460 matchfiles = scmutil.matchfiles(
3453 3461 repo, [f for sublist in oplist for f in sublist]
3454 3462 )
3455 3463 prefetch(
3456 3464 repo, [(ctx.rev(), matchfiles)],
3457 3465 )
3458 3466 match = scmutil.match(repo[None], pats)
3459 3467 _performrevert(
3460 3468 repo,
3461 3469 ctx,
3462 3470 names,
3463 3471 uipathfn,
3464 3472 actions,
3465 3473 match,
3466 3474 interactive,
3467 3475 tobackup,
3468 3476 )
3469 3477
3470 3478 if targetsubs:
3471 3479 # Revert the subrepos on the revert list
3472 3480 for sub in targetsubs:
3473 3481 try:
3474 3482 wctx.sub(sub).revert(
3475 3483 ctx.substate[sub], *pats, **pycompat.strkwargs(opts)
3476 3484 )
3477 3485 except KeyError:
3478 3486 raise error.Abort(
3479 3487 b"subrepository '%s' does not exist in %s!"
3480 3488 % (sub, short(ctx.node()))
3481 3489 )
3482 3490
3483 3491
3484 3492 def _performrevert(
3485 3493 repo,
3486 3494 ctx,
3487 3495 names,
3488 3496 uipathfn,
3489 3497 actions,
3490 3498 match,
3491 3499 interactive=False,
3492 3500 tobackup=None,
3493 3501 ):
3494 3502 """function that actually perform all the actions computed for revert
3495 3503
3496 3504 This is an independent function to let extension to plug in and react to
3497 3505 the imminent revert.
3498 3506
3499 3507 Make sure you have the working directory locked when calling this function.
3500 3508 """
3501 3509 parent, p2 = repo.dirstate.parents()
3502 3510 node = ctx.node()
3503 3511 excluded_files = []
3504 3512
3505 3513 def checkout(f):
3506 3514 fc = ctx[f]
3507 3515 repo.wwrite(f, fc.data(), fc.flags())
3508 3516
3509 3517 def doremove(f):
3510 3518 try:
3511 3519 rmdir = repo.ui.configbool(b'experimental', b'removeemptydirs')
3512 3520 repo.wvfs.unlinkpath(f, rmdir=rmdir)
3513 3521 except OSError:
3514 3522 pass
3515 3523 repo.dirstate.remove(f)
3516 3524
3517 3525 def prntstatusmsg(action, f):
3518 3526 exact = names[f]
3519 3527 if repo.ui.verbose or not exact:
3520 3528 repo.ui.status(actions[action][1] % uipathfn(f))
3521 3529
3522 3530 audit_path = pathutil.pathauditor(repo.root, cached=True)
3523 3531 for f in actions[b'forget'][0]:
3524 3532 if interactive:
3525 3533 choice = repo.ui.promptchoice(
3526 3534 _(b"forget added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3527 3535 )
3528 3536 if choice == 0:
3529 3537 prntstatusmsg(b'forget', f)
3530 3538 repo.dirstate.drop(f)
3531 3539 else:
3532 3540 excluded_files.append(f)
3533 3541 else:
3534 3542 prntstatusmsg(b'forget', f)
3535 3543 repo.dirstate.drop(f)
3536 3544 for f in actions[b'remove'][0]:
3537 3545 audit_path(f)
3538 3546 if interactive:
3539 3547 choice = repo.ui.promptchoice(
3540 3548 _(b"remove added file %s (Yn)?$$ &Yes $$ &No") % uipathfn(f)
3541 3549 )
3542 3550 if choice == 0:
3543 3551 prntstatusmsg(b'remove', f)
3544 3552 doremove(f)
3545 3553 else:
3546 3554 excluded_files.append(f)
3547 3555 else:
3548 3556 prntstatusmsg(b'remove', f)
3549 3557 doremove(f)
3550 3558 for f in actions[b'drop'][0]:
3551 3559 audit_path(f)
3552 3560 prntstatusmsg(b'drop', f)
3553 3561 repo.dirstate.remove(f)
3554 3562
3555 3563 normal = None
3556 3564 if node == parent:
3557 3565 # We're reverting to our parent. If possible, we'd like status
3558 3566 # to report the file as clean. We have to use normallookup for
3559 3567 # merges to avoid losing information about merged/dirty files.
3560 3568 if p2 != nullid:
3561 3569 normal = repo.dirstate.normallookup
3562 3570 else:
3563 3571 normal = repo.dirstate.normal
3564 3572
3565 3573 newlyaddedandmodifiedfiles = set()
3566 3574 if interactive:
3567 3575 # Prompt the user for changes to revert
3568 3576 torevert = [f for f in actions[b'revert'][0] if f not in excluded_files]
3569 3577 m = scmutil.matchfiles(repo, torevert)
3570 3578 diffopts = patch.difffeatureopts(
3571 3579 repo.ui,
3572 3580 whitespace=True,
3573 3581 section=b'commands',
3574 3582 configprefix=b'revert.interactive.',
3575 3583 )
3576 3584 diffopts.nodates = True
3577 3585 diffopts.git = True
3578 3586 operation = b'apply'
3579 3587 if node == parent:
3580 3588 if repo.ui.configbool(
3581 3589 b'experimental', b'revert.interactive.select-to-keep'
3582 3590 ):
3583 3591 operation = b'keep'
3584 3592 else:
3585 3593 operation = b'discard'
3586 3594
3587 3595 if operation == b'apply':
3588 3596 diff = patch.diff(repo, None, ctx.node(), m, opts=diffopts)
3589 3597 else:
3590 3598 diff = patch.diff(repo, ctx.node(), None, m, opts=diffopts)
3591 3599 originalchunks = patch.parsepatch(diff)
3592 3600
3593 3601 try:
3594 3602
3595 3603 chunks, opts = recordfilter(
3596 3604 repo.ui, originalchunks, match, operation=operation
3597 3605 )
3598 3606 if operation == b'discard':
3599 3607 chunks = patch.reversehunks(chunks)
3600 3608
3601 3609 except error.PatchError as err:
3602 3610 raise error.Abort(_(b'error parsing patch: %s') % err)
3603 3611
3604 3612 # FIXME: when doing an interactive revert of a copy, there's no way of
3605 3613 # performing a partial revert of the added file, the only option is
3606 3614 # "remove added file <name> (Yn)?", so we don't need to worry about the
3607 3615 # alsorestore value. Ideally we'd be able to partially revert
3608 3616 # copied/renamed files.
3609 3617 newlyaddedandmodifiedfiles, unusedalsorestore = newandmodified(
3610 3618 chunks, originalchunks
3611 3619 )
3612 3620 if tobackup is None:
3613 3621 tobackup = set()
3614 3622 # Apply changes
3615 3623 fp = stringio()
3616 3624 # chunks are serialized per file, but files aren't sorted
3617 3625 for f in sorted({c.header.filename() for c in chunks if ishunk(c)}):
3618 3626 prntstatusmsg(b'revert', f)
3619 3627 files = set()
3620 3628 for c in chunks:
3621 3629 if ishunk(c):
3622 3630 abs = c.header.filename()
3623 3631 # Create a backup file only if this hunk should be backed up
3624 3632 if c.header.filename() in tobackup:
3625 3633 target = repo.wjoin(abs)
3626 3634 bakname = scmutil.backuppath(repo.ui, repo, abs)
3627 3635 util.copyfile(target, bakname)
3628 3636 tobackup.remove(abs)
3629 3637 if abs not in files:
3630 3638 files.add(abs)
3631 3639 if operation == b'keep':
3632 3640 checkout(abs)
3633 3641 c.write(fp)
3634 3642 dopatch = fp.tell()
3635 3643 fp.seek(0)
3636 3644 if dopatch:
3637 3645 try:
3638 3646 patch.internalpatch(repo.ui, repo, fp, 1, eolmode=None)
3639 3647 except error.PatchError as err:
3640 3648 raise error.Abort(pycompat.bytestr(err))
3641 3649 del fp
3642 3650 else:
3643 3651 for f in actions[b'revert'][0]:
3644 3652 prntstatusmsg(b'revert', f)
3645 3653 checkout(f)
3646 3654 if normal:
3647 3655 normal(f)
3648 3656
3649 3657 for f in actions[b'add'][0]:
3650 3658 # Don't checkout modified files, they are already created by the diff
3651 3659 if f not in newlyaddedandmodifiedfiles:
3652 3660 prntstatusmsg(b'add', f)
3653 3661 checkout(f)
3654 3662 repo.dirstate.add(f)
3655 3663
3656 3664 normal = repo.dirstate.normallookup
3657 3665 if node == parent and p2 == nullid:
3658 3666 normal = repo.dirstate.normal
3659 3667 for f in actions[b'undelete'][0]:
3660 3668 if interactive:
3661 3669 choice = repo.ui.promptchoice(
3662 3670 _(b"add back removed file %s (Yn)?$$ &Yes $$ &No") % f
3663 3671 )
3664 3672 if choice == 0:
3665 3673 prntstatusmsg(b'undelete', f)
3666 3674 checkout(f)
3667 3675 normal(f)
3668 3676 else:
3669 3677 excluded_files.append(f)
3670 3678 else:
3671 3679 prntstatusmsg(b'undelete', f)
3672 3680 checkout(f)
3673 3681 normal(f)
3674 3682
3675 3683 copied = copies.pathcopies(repo[parent], ctx)
3676 3684
3677 3685 for f in (
3678 3686 actions[b'add'][0] + actions[b'undelete'][0] + actions[b'revert'][0]
3679 3687 ):
3680 3688 if f in copied:
3681 3689 repo.dirstate.copy(copied[f], f)
3682 3690
3683 3691
3684 3692 # a list of (ui, repo, otherpeer, opts, missing) functions called by
3685 3693 # commands.outgoing. "missing" is "missing" of the result of
3686 3694 # "findcommonoutgoing()"
3687 3695 outgoinghooks = util.hooks()
3688 3696
3689 3697 # a list of (ui, repo) functions called by commands.summary
3690 3698 summaryhooks = util.hooks()
3691 3699
3692 3700 # a list of (ui, repo, opts, changes) functions called by commands.summary.
3693 3701 #
3694 3702 # functions should return tuple of booleans below, if 'changes' is None:
3695 3703 # (whether-incomings-are-needed, whether-outgoings-are-needed)
3696 3704 #
3697 3705 # otherwise, 'changes' is a tuple of tuples below:
3698 3706 # - (sourceurl, sourcebranch, sourcepeer, incoming)
3699 3707 # - (desturl, destbranch, destpeer, outgoing)
3700 3708 summaryremotehooks = util.hooks()
3701 3709
3702 3710
3703 3711 def checkunfinished(repo, commit=False, skipmerge=False):
3704 3712 '''Look for an unfinished multistep operation, like graft, and abort
3705 3713 if found. It's probably good to check this right before
3706 3714 bailifchanged().
3707 3715 '''
3708 3716 # Check for non-clearable states first, so things like rebase will take
3709 3717 # precedence over update.
3710 3718 for state in statemod._unfinishedstates:
3711 3719 if (
3712 3720 state._clearable
3713 3721 or (commit and state._allowcommit)
3714 3722 or state._reportonly
3715 3723 ):
3716 3724 continue
3717 3725 if state.isunfinished(repo):
3718 3726 raise error.Abort(state.msg(), hint=state.hint())
3719 3727
3720 3728 for s in statemod._unfinishedstates:
3721 3729 if (
3722 3730 not s._clearable
3723 3731 or (commit and s._allowcommit)
3724 3732 or (s._opname == b'merge' and skipmerge)
3725 3733 or s._reportonly
3726 3734 ):
3727 3735 continue
3728 3736 if s.isunfinished(repo):
3729 3737 raise error.Abort(s.msg(), hint=s.hint())
3730 3738
3731 3739
3732 3740 def clearunfinished(repo):
3733 3741 '''Check for unfinished operations (as above), and clear the ones
3734 3742 that are clearable.
3735 3743 '''
3736 3744 for state in statemod._unfinishedstates:
3737 3745 if state._reportonly:
3738 3746 continue
3739 3747 if not state._clearable and state.isunfinished(repo):
3740 3748 raise error.Abort(state.msg(), hint=state.hint())
3741 3749
3742 3750 for s in statemod._unfinishedstates:
3743 3751 if s._opname == b'merge' or state._reportonly:
3744 3752 continue
3745 3753 if s._clearable and s.isunfinished(repo):
3746 3754 util.unlink(repo.vfs.join(s._fname))
3747 3755
3748 3756
3749 3757 def getunfinishedstate(repo):
3750 3758 ''' Checks for unfinished operations and returns statecheck object
3751 3759 for it'''
3752 3760 for state in statemod._unfinishedstates:
3753 3761 if state.isunfinished(repo):
3754 3762 return state
3755 3763 return None
3756 3764
3757 3765
3758 3766 def howtocontinue(repo):
3759 3767 '''Check for an unfinished operation and return the command to finish
3760 3768 it.
3761 3769
3762 3770 statemod._unfinishedstates list is checked for an unfinished operation
3763 3771 and the corresponding message to finish it is generated if a method to
3764 3772 continue is supported by the operation.
3765 3773
3766 3774 Returns a (msg, warning) tuple. 'msg' is a string and 'warning' is
3767 3775 a boolean.
3768 3776 '''
3769 3777 contmsg = _(b"continue: %s")
3770 3778 for state in statemod._unfinishedstates:
3771 3779 if not state._continueflag:
3772 3780 continue
3773 3781 if state.isunfinished(repo):
3774 3782 return contmsg % state.continuemsg(), True
3775 3783 if repo[None].dirty(missing=True, merge=False, branch=False):
3776 3784 return contmsg % _(b"hg commit"), False
3777 3785 return None, None
3778 3786
3779 3787
3780 3788 def checkafterresolved(repo):
3781 3789 '''Inform the user about the next action after completing hg resolve
3782 3790
3783 3791 If there's a an unfinished operation that supports continue flag,
3784 3792 howtocontinue will yield repo.ui.warn as the reporter.
3785 3793
3786 3794 Otherwise, it will yield repo.ui.note.
3787 3795 '''
3788 3796 msg, warning = howtocontinue(repo)
3789 3797 if msg is not None:
3790 3798 if warning:
3791 3799 repo.ui.warn(b"%s\n" % msg)
3792 3800 else:
3793 3801 repo.ui.note(b"%s\n" % msg)
3794 3802
3795 3803
3796 3804 def wrongtooltocontinue(repo, task):
3797 3805 '''Raise an abort suggesting how to properly continue if there is an
3798 3806 active task.
3799 3807
3800 3808 Uses howtocontinue() to find the active task.
3801 3809
3802 3810 If there's no task (repo.ui.note for 'hg commit'), it does not offer
3803 3811 a hint.
3804 3812 '''
3805 3813 after = howtocontinue(repo)
3806 3814 hint = None
3807 3815 if after[1]:
3808 3816 hint = after[0]
3809 3817 raise error.Abort(_(b'no %s in progress') % task, hint=hint)
3810 3818
3811 3819
3812 3820 def abortgraft(ui, repo, graftstate):
3813 3821 """abort the interrupted graft and rollbacks to the state before interrupted
3814 3822 graft"""
3815 3823 if not graftstate.exists():
3816 3824 raise error.Abort(_(b"no interrupted graft to abort"))
3817 3825 statedata = readgraftstate(repo, graftstate)
3818 3826 newnodes = statedata.get(b'newnodes')
3819 3827 if newnodes is None:
3820 3828 # and old graft state which does not have all the data required to abort
3821 3829 # the graft
3822 3830 raise error.Abort(_(b"cannot abort using an old graftstate"))
3823 3831
3824 3832 # changeset from which graft operation was started
3825 3833 if len(newnodes) > 0:
3826 3834 startctx = repo[newnodes[0]].p1()
3827 3835 else:
3828 3836 startctx = repo[b'.']
3829 3837 # whether to strip or not
3830 3838 cleanup = False
3831 3839
3832 3840 if newnodes:
3833 3841 newnodes = [repo[r].rev() for r in newnodes]
3834 3842 cleanup = True
3835 3843 # checking that none of the newnodes turned public or is public
3836 3844 immutable = [c for c in newnodes if not repo[c].mutable()]
3837 3845 if immutable:
3838 3846 repo.ui.warn(
3839 3847 _(b"cannot clean up public changesets %s\n")
3840 3848 % b', '.join(bytes(repo[r]) for r in immutable),
3841 3849 hint=_(b"see 'hg help phases' for details"),
3842 3850 )
3843 3851 cleanup = False
3844 3852
3845 3853 # checking that no new nodes are created on top of grafted revs
3846 3854 desc = set(repo.changelog.descendants(newnodes))
3847 3855 if desc - set(newnodes):
3848 3856 repo.ui.warn(
3849 3857 _(
3850 3858 b"new changesets detected on destination "
3851 3859 b"branch, can't strip\n"
3852 3860 )
3853 3861 )
3854 3862 cleanup = False
3855 3863
3856 3864 if cleanup:
3857 3865 with repo.wlock(), repo.lock():
3858 3866 mergemod.clean_update(startctx)
3859 3867 # stripping the new nodes created
3860 3868 strippoints = [
3861 3869 c.node() for c in repo.set(b"roots(%ld)", newnodes)
3862 3870 ]
3863 3871 repair.strip(repo.ui, repo, strippoints, backup=False)
3864 3872
3865 3873 if not cleanup:
3866 3874 # we don't update to the startnode if we can't strip
3867 3875 startctx = repo[b'.']
3868 3876 mergemod.clean_update(startctx)
3869 3877
3870 3878 ui.status(_(b"graft aborted\n"))
3871 3879 ui.status(_(b"working directory is now at %s\n") % startctx.hex()[:12])
3872 3880 graftstate.delete()
3873 3881 return 0
3874 3882
3875 3883
3876 3884 def readgraftstate(repo, graftstate):
3877 3885 # type: (Any, statemod.cmdstate) -> Dict[bytes, Any]
3878 3886 """read the graft state file and return a dict of the data stored in it"""
3879 3887 try:
3880 3888 return graftstate.read()
3881 3889 except error.CorruptedState:
3882 3890 nodes = repo.vfs.read(b'graftstate').splitlines()
3883 3891 return {b'nodes': nodes}
3884 3892
3885 3893
3886 3894 def hgabortgraft(ui, repo):
3887 3895 """ abort logic for aborting graft using 'hg abort'"""
3888 3896 with repo.wlock():
3889 3897 graftstate = statemod.cmdstate(repo, b'graftstate')
3890 3898 return abortgraft(ui, repo, graftstate)
@@ -1,30 +1,30 b''
1 1 $ cat >> $HGRCPATH << EOF
2 2 > [extensions]
3 3 > absorb=
4 4 > EOF
5 5
6 6 Abort absorb if there is an unfinished operation.
7 7
8 8 $ hg init abortunresolved
9 9 $ cd abortunresolved
10 10
11 11 $ echo "foo1" > foo.whole
12 12 $ hg commit -Aqm "foo 1"
13 13
14 14 $ hg update null
15 15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 16 $ echo "foo2" > foo.whole
17 17 $ hg commit -Aqm "foo 2"
18 18
19 19 $ hg --config extensions.rebase= rebase -r 1 -d 0
20 rebasing 1:c3b6dc0e177a "foo 2" (tip)
20 rebasing 1:c3b6dc0e177a tip "foo 2"
21 21 merging foo.whole
22 22 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
23 23 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
24 24 [1]
25 25
26 26 $ hg --config extensions.rebase= absorb
27 27 abort: rebase in progress
28 28 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
29 29 [255]
30 30
@@ -1,240 +1,240 b''
1 1 The simple store doesn't escape paths robustly and can't store paths
2 2 with periods, etc. So much of this test fails with it.
3 3 #require no-reposimplestore
4 4
5 5 $ hg init
6 6
7 7 audit of .hg
8 8
9 9 $ hg add .hg/00changelog.i
10 10 abort: path contains illegal component: .hg/00changelog.i
11 11 [255]
12 12
13 13 #if symlink
14 14
15 15 Symlinks
16 16
17 17 $ mkdir a
18 18 $ echo a > a/a
19 19 $ hg ci -Ama
20 20 adding a/a
21 21 $ ln -s a b
22 22 $ echo b > a/b
23 23 $ hg add b/b
24 24 abort: path 'b/b' traverses symbolic link 'b'
25 25 [255]
26 26 $ hg add b
27 27
28 28 should still fail - maybe
29 29
30 30 $ hg add b/b
31 31 abort: path 'b/b' traverses symbolic link 'b'
32 32 [255]
33 33
34 34 $ hg commit -m 'add symlink b'
35 35
36 36
37 37 Test symlink traversing when accessing history:
38 38 -----------------------------------------------
39 39
40 40 (build a changeset where the path exists as a directory)
41 41
42 42 $ hg up 0
43 43 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
44 44 $ mkdir b
45 45 $ echo c > b/a
46 46 $ hg add b/a
47 47 $ hg ci -m 'add directory b'
48 48 created new head
49 49
50 50 Test that hg cat does not do anything wrong the working copy has 'b' as directory
51 51
52 52 $ hg cat b/a
53 53 c
54 54 $ hg cat -r "desc(directory)" b/a
55 55 c
56 56 $ hg cat -r "desc(symlink)" b/a
57 57 b/a: no such file in rev bc151a1f53bd
58 58 [1]
59 59
60 60 Test that hg cat does not do anything wrong the working copy has 'b' as a symlink (issue4749)
61 61
62 62 $ hg up 'desc(symlink)'
63 63 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
64 64 $ hg cat b/a
65 65 b/a: no such file in rev bc151a1f53bd
66 66 [1]
67 67 $ hg cat -r "desc(directory)" b/a
68 68 c
69 69 $ hg cat -r "desc(symlink)" b/a
70 70 b/a: no such file in rev bc151a1f53bd
71 71 [1]
72 72
73 73 #endif
74 74
75 75
76 76 unbundle tampered bundle
77 77
78 78 $ hg init target
79 79 $ cd target
80 80 $ hg unbundle "$TESTDIR/bundles/tampered.hg"
81 81 adding changesets
82 82 adding manifests
83 83 adding file changes
84 84 added 5 changesets with 6 changes to 6 files (+4 heads)
85 85 new changesets b7da9bf6b037:fc1393d727bc (5 drafts)
86 86 (run 'hg heads' to see heads, 'hg merge' to merge)
87 87
88 88 attack .hg/test
89 89
90 90 $ hg manifest -r0
91 91 .hg/test
92 92 $ hg update -Cr0
93 93 abort: path contains illegal component: .hg/test
94 94 [255]
95 95
96 96 attack foo/.hg/test
97 97
98 98 $ hg manifest -r1
99 99 foo/.hg/test
100 100 $ hg update -Cr1
101 101 abort: path 'foo/.hg/test' is inside nested repo 'foo'
102 102 [255]
103 103
104 104 attack back/test where back symlinks to ..
105 105
106 106 $ hg manifest -r2
107 107 back
108 108 back/test
109 109 #if symlink
110 110 $ hg update -Cr2
111 111 abort: path 'back/test' traverses symbolic link 'back'
112 112 [255]
113 113 #else
114 114 ('back' will be a file and cause some other system specific error)
115 115 $ hg update -Cr2
116 116 abort: $TESTTMP/target/back/test: $ENOTDIR$
117 117 [255]
118 118 #endif
119 119
120 120 attack ../test
121 121
122 122 $ hg manifest -r3
123 123 ../test
124 124 $ mkdir ../test
125 125 $ echo data > ../test/file
126 126 $ hg update -Cr3
127 127 abort: path contains illegal component: ../test
128 128 [255]
129 129 $ cat ../test/file
130 130 data
131 131
132 132 attack /tmp/test
133 133
134 134 $ hg manifest -r4
135 135 /tmp/test
136 136 $ hg update -Cr4
137 137 abort: path contains illegal component: /tmp/test
138 138 [255]
139 139
140 140 $ cd ..
141 141
142 142 Test symlink traversal on merge:
143 143 --------------------------------
144 144
145 145 #if symlink
146 146
147 147 set up symlink hell
148 148
149 149 $ mkdir merge-symlink-out
150 150 $ hg init merge-symlink
151 151 $ cd merge-symlink
152 152 $ touch base
153 153 $ hg commit -qAm base
154 154 $ ln -s ../merge-symlink-out a
155 155 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
156 156 $ hg up -q 0
157 157 $ mkdir a
158 158 $ touch a/poisoned
159 159 $ hg commit -qAm 'file a/poisoned'
160 160 $ hg log -G -T '{rev}: {desc}\n'
161 161 @ 2: file a/poisoned
162 162 |
163 163 | o 1: symlink a -> ../merge-symlink-out
164 164 |/
165 165 o 0: base
166 166
167 167
168 168 try trivial merge
169 169
170 170 $ hg up -qC 1
171 171 $ hg merge 2
172 172 abort: path 'a/poisoned' traverses symbolic link 'a'
173 173 [255]
174 174
175 175 try rebase onto other revision: cache of audited paths should be discarded,
176 176 and the rebase should fail (issue5628)
177 177
178 178 $ hg up -qC 2
179 179 $ hg rebase -s 2 -d 1 --config extensions.rebase=
180 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
180 rebasing 2:e73c21d6b244 tip "file a/poisoned"
181 181 abort: path 'a/poisoned' traverses symbolic link 'a'
182 182 [255]
183 183 $ ls ../merge-symlink-out
184 184
185 185 $ cd ..
186 186
187 187 Test symlink traversal on update:
188 188 ---------------------------------
189 189
190 190 $ mkdir update-symlink-out
191 191 $ hg init update-symlink
192 192 $ cd update-symlink
193 193 $ ln -s ../update-symlink-out a
194 194 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
195 195 $ hg rm a
196 196 $ mkdir a && touch a/b
197 197 $ hg ci -qAm 'file a/b' a/b
198 198 $ hg up -qC 0
199 199 $ hg rm a
200 200 $ mkdir a && touch a/c
201 201 $ hg ci -qAm 'rm a, file a/c'
202 202 $ hg log -G -T '{rev}: {desc}\n'
203 203 @ 2: rm a, file a/c
204 204 |
205 205 | o 1: file a/b
206 206 |/
207 207 o 0: symlink a -> ../update-symlink-out
208 208
209 209
210 210 try linear update where symlink already exists:
211 211
212 212 $ hg up -qC 0
213 213 $ hg up 1
214 214 abort: path 'a/b' traverses symbolic link 'a'
215 215 [255]
216 216
217 217 try linear update including symlinked directory and its content: paths are
218 218 audited first by calculateupdates(), where no symlink is created so both
219 219 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
220 220
221 221 $ hg up -qC null
222 222 $ hg up 1
223 223 abort: path 'a/b' traverses symbolic link 'a'
224 224 [255]
225 225 $ ls ../update-symlink-out
226 226
227 227 try branch update replacing directory with symlink, and its content: the
228 228 path 'a' is audited as a directory first, which should be audited again as
229 229 a symlink.
230 230
231 231 $ rm -f a
232 232 $ hg up -qC 2
233 233 $ hg up 1
234 234 abort: path 'a/b' traverses symbolic link 'a'
235 235 [255]
236 236 $ ls ../update-symlink-out
237 237
238 238 $ cd ..
239 239
240 240 #endif
@@ -1,105 +1,105 b''
1 1 $ echo "[extensions]" >> $HGRCPATH
2 2 $ echo "rebase=" >> $HGRCPATH
3 3
4 4 initialize repository
5 5
6 6 $ hg init
7 7
8 8 $ echo 'a' > a
9 9 $ hg ci -A -m "0"
10 10 adding a
11 11
12 12 $ echo 'b' > b
13 13 $ hg ci -A -m "1"
14 14 adding b
15 15
16 16 $ hg up 0
17 17 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
18 18 $ echo 'c' > c
19 19 $ hg ci -A -m "2"
20 20 adding c
21 21 created new head
22 22
23 23 $ echo 'd' > d
24 24 $ hg ci -A -m "3"
25 25 adding d
26 26
27 27 $ hg bookmark -r 1 one
28 28 $ hg bookmark -r 3 two
29 29 $ hg up -q two
30 30
31 31 bookmark list
32 32
33 33 $ hg bookmark
34 34 one 1:925d80f479bb
35 35 * two 3:2ae46b1d99a7
36 36
37 37 rebase
38 38
39 39 $ hg rebase -s two -d one
40 rebasing 3:2ae46b1d99a7 "3" (two tip)
40 rebasing 3:2ae46b1d99a7 tip two "3"
41 41 saved backup bundle to $TESTTMP/.hg/strip-backup/2ae46b1d99a7-e6b057bc-rebase.hg
42 42
43 43 $ hg log
44 44 changeset: 3:42e5ed2cdcf4
45 45 bookmark: two
46 46 tag: tip
47 47 parent: 1:925d80f479bb
48 48 user: test
49 49 date: Thu Jan 01 00:00:00 1970 +0000
50 50 summary: 3
51 51
52 52 changeset: 2:db815d6d32e6
53 53 parent: 0:f7b1eb17ad24
54 54 user: test
55 55 date: Thu Jan 01 00:00:00 1970 +0000
56 56 summary: 2
57 57
58 58 changeset: 1:925d80f479bb
59 59 bookmark: one
60 60 user: test
61 61 date: Thu Jan 01 00:00:00 1970 +0000
62 62 summary: 1
63 63
64 64 changeset: 0:f7b1eb17ad24
65 65 user: test
66 66 date: Thu Jan 01 00:00:00 1970 +0000
67 67 summary: 0
68 68
69 69 aborted rebase should restore active bookmark.
70 70
71 71 $ hg up 1
72 72 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
73 73 (leaving bookmark two)
74 74 $ echo 'e' > d
75 75 $ hg ci -A -m "4"
76 76 adding d
77 77 created new head
78 78 $ hg bookmark three
79 79 $ hg rebase -s three -d two
80 rebasing 4:dd7c838e8362 "4" (three tip)
80 rebasing 4:dd7c838e8362 tip three "4"
81 81 merging d
82 82 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
83 83 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
84 84 [1]
85 85 $ hg rebase --abort
86 86 rebase aborted
87 87 $ hg bookmark
88 88 one 1:925d80f479bb
89 89 * three 4:dd7c838e8362
90 90 two 3:42e5ed2cdcf4
91 91
92 92 after aborted rebase, restoring a bookmark that has been removed should not fail
93 93
94 94 $ hg rebase -s three -d two
95 rebasing 4:dd7c838e8362 "4" (three tip)
95 rebasing 4:dd7c838e8362 tip three "4"
96 96 merging d
97 97 warning: conflicts while merging d! (edit, then use 'hg resolve --mark')
98 98 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
99 99 [1]
100 100 $ hg bookmark -d three
101 101 $ hg rebase --abort
102 102 rebase aborted
103 103 $ hg bookmark
104 104 one 1:925d80f479bb
105 105 two 3:42e5ed2cdcf4
@@ -1,489 +1,489 b''
1 1 #testcases extra sidedata
2 2
3 3 #if extra
4 4 $ cat >> $HGRCPATH << EOF
5 5 > [experimental]
6 6 > copies.write-to=changeset-only
7 7 > copies.read-from=changeset-only
8 8 > [alias]
9 9 > changesetcopies = log -r . -T 'files: {files}
10 10 > {extras % "{ifcontains("files", key, "{key}: {value}\n")}"}
11 11 > {extras % "{ifcontains("copies", key, "{key}: {value}\n")}"}'
12 12 > EOF
13 13 #endif
14 14
15 15 #if sidedata
16 16 $ cat >> $HGRCPATH << EOF
17 17 > [format]
18 18 > exp-use-copies-side-data-changeset = yes
19 19 > EOF
20 20 #endif
21 21
22 22 $ cat >> $HGRCPATH << EOF
23 23 > [alias]
24 24 > showcopies = log -r . -T '{file_copies % "{source} -> {name}\n"}'
25 25 > [extensions]
26 26 > rebase =
27 27 > split =
28 28 > EOF
29 29
30 30 Check that copies are recorded correctly
31 31
32 32 $ hg init repo
33 33 $ cd repo
34 34 #if sidedata
35 35 $ hg debugformat -v
36 36 format-variant repo config default
37 37 fncache: yes yes yes
38 38 dotencode: yes yes yes
39 39 generaldelta: yes yes yes
40 40 sparserevlog: yes yes yes
41 41 sidedata: yes yes no
42 42 persistent-nodemap: no no no
43 43 copies-sdc: yes yes no
44 44 plain-cl-delta: yes yes yes
45 45 compression: zlib zlib zlib
46 46 compression-level: default default default
47 47 #else
48 48 $ hg debugformat -v
49 49 format-variant repo config default
50 50 fncache: yes yes yes
51 51 dotencode: yes yes yes
52 52 generaldelta: yes yes yes
53 53 sparserevlog: yes yes yes
54 54 sidedata: no no no
55 55 persistent-nodemap: no no no
56 56 copies-sdc: no no no
57 57 plain-cl-delta: yes yes yes
58 58 compression: zlib zlib zlib
59 59 compression-level: default default default
60 60 #endif
61 61 $ echo a > a
62 62 $ hg add a
63 63 $ hg ci -m initial
64 64 $ hg cp a b
65 65 $ hg cp a c
66 66 $ hg cp a d
67 67 $ hg ci -m 'copy a to b, c, and d'
68 68
69 69 #if extra
70 70
71 71 $ hg changesetcopies
72 72 files: b c d
73 73 filesadded: 0
74 74 1
75 75 2
76 76
77 77 p1copies: 0\x00a (esc)
78 78 1\x00a (esc)
79 79 2\x00a (esc)
80 80 #else
81 81 $ hg debugsidedata -c -v -- -1
82 82 1 sidedata entries
83 83 entry-0014 size 44
84 84 '\x00\x00\x00\x04\x00\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00\x06\x00\x00\x00\x03\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x00abcd'
85 85 #endif
86 86
87 87 $ hg showcopies
88 88 a -> b
89 89 a -> c
90 90 a -> d
91 91
92 92 #if extra
93 93
94 94 $ hg showcopies --config experimental.copies.read-from=compatibility
95 95 a -> b
96 96 a -> c
97 97 a -> d
98 98 $ hg showcopies --config experimental.copies.read-from=filelog-only
99 99
100 100 #endif
101 101
102 102 Check that renames are recorded correctly
103 103
104 104 $ hg mv b b2
105 105 $ hg ci -m 'rename b to b2'
106 106
107 107 #if extra
108 108
109 109 $ hg changesetcopies
110 110 files: b b2
111 111 filesadded: 1
112 112 filesremoved: 0
113 113
114 114 p1copies: 1\x00b (esc)
115 115
116 116 #else
117 117 $ hg debugsidedata -c -v -- -1
118 118 1 sidedata entries
119 119 entry-0014 size 25
120 120 '\x00\x00\x00\x02\x0c\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x03\x00\x00\x00\x00bb2'
121 121 #endif
122 122
123 123 $ hg showcopies
124 124 b -> b2
125 125
126 126
127 127 Rename onto existing file. This should get recorded in the changeset files list and in the extras,
128 128 even though there is no filelog entry.
129 129
130 130 $ hg cp b2 c --force
131 131 $ hg st --copies
132 132 M c
133 133 b2
134 134
135 135 #if extra
136 136
137 137 $ hg debugindex c
138 138 rev linkrev nodeid p1 p2
139 139 0 1 b789fdd96dc2 000000000000 000000000000
140 140
141 141 #else
142 142
143 143 $ hg debugindex c
144 144 rev linkrev nodeid p1 p2
145 145 0 1 37d9b5d994ea 000000000000 000000000000
146 146
147 147 #endif
148 148
149 149
150 150 $ hg ci -m 'move b onto d'
151 151
152 152 #if extra
153 153
154 154 $ hg changesetcopies
155 155 files: c
156 156
157 157 p1copies: 0\x00b2 (esc)
158 158
159 159 #else
160 160 $ hg debugsidedata -c -v -- -1
161 161 1 sidedata entries
162 162 entry-0014 size 25
163 163 '\x00\x00\x00\x02\x00\x00\x00\x00\x02\x00\x00\x00\x00\x16\x00\x00\x00\x03\x00\x00\x00\x00b2c'
164 164 #endif
165 165
166 166 $ hg showcopies
167 167 b2 -> c
168 168
169 169 #if extra
170 170
171 171 $ hg debugindex c
172 172 rev linkrev nodeid p1 p2
173 173 0 1 b789fdd96dc2 000000000000 000000000000
174 174
175 175 #else
176 176
177 177 $ hg debugindex c
178 178 rev linkrev nodeid p1 p2
179 179 0 1 37d9b5d994ea 000000000000 000000000000
180 180 1 3 029625640347 000000000000 000000000000
181 181
182 182 #endif
183 183
184 184 Create a merge commit with copying done during merge.
185 185
186 186 $ hg co 0
187 187 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
188 188 $ hg cp a e
189 189 $ hg cp a f
190 190 $ hg ci -m 'copy a to e and f'
191 191 created new head
192 192 $ hg merge 3
193 193 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 194 (branch merge, don't forget to commit)
195 195 File 'a' exists on both sides, so 'g' could be recorded as being from p1 or p2, but we currently
196 196 always record it as being from p1
197 197 $ hg cp a g
198 198 File 'd' exists only in p2, so 'h' should be from p2
199 199 $ hg cp d h
200 200 File 'f' exists only in p1, so 'i' should be from p1
201 201 $ hg cp f i
202 202 $ hg ci -m 'merge'
203 203
204 204 #if extra
205 205
206 206 $ hg changesetcopies
207 207 files: g h i
208 208 filesadded: 0
209 209 1
210 210 2
211 211
212 212 p1copies: 0\x00a (esc)
213 213 2\x00f (esc)
214 214 p2copies: 1\x00d (esc)
215 215
216 216 #else
217 217 $ hg debugsidedata -c -v -- -1
218 218 1 sidedata entries
219 219 entry-0014 size 64
220 220 '\x00\x00\x00\x06\x00\x00\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x00\x06\x00\x00\x00\x04\x00\x00\x00\x00\x07\x00\x00\x00\x05\x00\x00\x00\x01\x06\x00\x00\x00\x06\x00\x00\x00\x02adfghi'
221 221 #endif
222 222
223 223 $ hg showcopies
224 224 a -> g
225 225 d -> h
226 226 f -> i
227 227
228 228 Test writing to both changeset and filelog
229 229
230 230 $ hg cp a j
231 231 #if extra
232 232 $ hg ci -m 'copy a to j' --config experimental.copies.write-to=compatibility
233 233 $ hg changesetcopies
234 234 files: j
235 235 filesadded: 0
236 236 filesremoved:
237 237
238 238 p1copies: 0\x00a (esc)
239 239 p2copies:
240 240 #else
241 241 $ hg ci -m 'copy a to j'
242 242 $ hg debugsidedata -c -v -- -1
243 243 1 sidedata entries
244 244 entry-0014 size 24
245 245 '\x00\x00\x00\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00aj'
246 246 #endif
247 247 $ hg debugdata j 0
248 248 \x01 (esc)
249 249 copy: a
250 250 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
251 251 \x01 (esc)
252 252 a
253 253 $ hg showcopies
254 254 a -> j
255 255 $ hg showcopies --config experimental.copies.read-from=compatibility
256 256 a -> j
257 257 $ hg showcopies --config experimental.copies.read-from=filelog-only
258 258 a -> j
259 259 Existing copy information in the changeset gets removed on amend and writing
260 260 copy information on to the filelog
261 261 #if extra
262 262 $ hg ci --amend -m 'copy a to j, v2' \
263 263 > --config experimental.copies.write-to=filelog-only
264 264 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
265 265 $ hg changesetcopies
266 266 files: j
267 267
268 268 #else
269 269 $ hg ci --amend -m 'copy a to j, v2'
270 270 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-*-amend.hg (glob)
271 271 $ hg debugsidedata -c -v -- -1
272 272 1 sidedata entries
273 273 entry-0014 size 24
274 274 '\x00\x00\x00\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00aj'
275 275 #endif
276 276 $ hg showcopies --config experimental.copies.read-from=filelog-only
277 277 a -> j
278 278 The entries should be written to extras even if they're empty (so the client
279 279 won't have to fall back to reading from filelogs)
280 280 $ echo x >> j
281 281 #if extra
282 282 $ hg ci -m 'modify j' --config experimental.copies.write-to=compatibility
283 283 $ hg changesetcopies
284 284 files: j
285 285 filesadded:
286 286 filesremoved:
287 287
288 288 p1copies:
289 289 p2copies:
290 290 #else
291 291 $ hg ci -m 'modify j'
292 292 $ hg debugsidedata -c -v -- -1
293 293 1 sidedata entries
294 294 entry-0014 size 14
295 295 '\x00\x00\x00\x01\x14\x00\x00\x00\x01\x00\x00\x00\x00j'
296 296 #endif
297 297
298 298 Test writing only to filelog
299 299
300 300 $ hg cp a k
301 301 #if extra
302 302 $ hg ci -m 'copy a to k' --config experimental.copies.write-to=filelog-only
303 303
304 304 $ hg changesetcopies
305 305 files: k
306 306
307 307 #else
308 308 $ hg ci -m 'copy a to k'
309 309 $ hg debugsidedata -c -v -- -1
310 310 1 sidedata entries
311 311 entry-0014 size 24
312 312 '\x00\x00\x00\x02\x00\x00\x00\x00\x01\x00\x00\x00\x00\x06\x00\x00\x00\x02\x00\x00\x00\x00ak'
313 313 #endif
314 314
315 315 $ hg debugdata k 0
316 316 \x01 (esc)
317 317 copy: a
318 318 copyrev: b789fdd96dc2f3bd229c1dd8eedf0fc60e2b68e3
319 319 \x01 (esc)
320 320 a
321 321 #if extra
322 322 $ hg showcopies
323 323
324 324 $ hg showcopies --config experimental.copies.read-from=compatibility
325 325 a -> k
326 326 $ hg showcopies --config experimental.copies.read-from=filelog-only
327 327 a -> k
328 328 #else
329 329 $ hg showcopies
330 330 a -> k
331 331 #endif
332 332
333 333 $ cd ..
334 334
335 335 Test rebasing a commit with copy information
336 336
337 337 $ hg init rebase-rename
338 338 $ cd rebase-rename
339 339 $ echo a > a
340 340 $ hg ci -Aqm 'add a'
341 341 $ echo a2 > a
342 342 $ hg ci -m 'modify a'
343 343 $ hg co -q 0
344 344 $ hg mv a b
345 345 $ hg ci -qm 'rename a to b'
346 346 $ hg rebase -d 1 --config rebase.experimental.inmemory=yes
347 rebasing 2:* "rename a to b" (tip) (glob)
347 rebasing 2:* tip "rename a to b" (glob)
348 348 merging a and b to b
349 349 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/*-*-rebase.hg (glob)
350 350 $ hg st --change . --copies
351 351 A b
352 352 a
353 353 R a
354 354 $ cd ..
355 355
356 356 Test splitting a commit
357 357
358 358 $ hg init split
359 359 $ cd split
360 360 $ echo a > a
361 361 $ echo b > b
362 362 $ hg ci -Aqm 'add a and b'
363 363 $ echo a2 > a
364 364 $ hg mv b c
365 365 $ hg ci -m 'modify a, move b to c'
366 366 $ hg --config ui.interactive=yes split <<EOF
367 367 > y
368 368 > y
369 369 > n
370 370 > y
371 371 > EOF
372 372 diff --git a/a b/a
373 373 1 hunks, 1 lines changed
374 374 examine changes to 'a'?
375 375 (enter ? for help) [Ynesfdaq?] y
376 376
377 377 @@ -1,1 +1,1 @@
378 378 -a
379 379 +a2
380 380 record this change to 'a'?
381 381 (enter ? for help) [Ynesfdaq?] y
382 382
383 383 diff --git a/b b/c
384 384 rename from b
385 385 rename to c
386 386 examine changes to 'b' and 'c'?
387 387 (enter ? for help) [Ynesfdaq?] n
388 388
389 389 created new head
390 390 diff --git a/b b/c
391 391 rename from b
392 392 rename to c
393 393 examine changes to 'b' and 'c'?
394 394 (enter ? for help) [Ynesfdaq?] y
395 395
396 396 saved backup bundle to $TESTTMP/split/.hg/strip-backup/*-*-split.hg (glob)
397 397 $ cd ..
398 398
399 399 Test committing half a rename
400 400
401 401 $ hg init partial
402 402 $ cd partial
403 403 $ echo a > a
404 404 $ hg ci -Aqm 'add a'
405 405 $ hg mv a b
406 406 $ hg ci -m 'remove a' a
407 407
408 408 #if sidedata
409 409
410 410 Test upgrading/downgrading to sidedata storage
411 411 ==============================================
412 412
413 413 downgrading (keeping some sidedata)
414 414
415 415 $ hg debugformat -v
416 416 format-variant repo config default
417 417 fncache: yes yes yes
418 418 dotencode: yes yes yes
419 419 generaldelta: yes yes yes
420 420 sparserevlog: yes yes yes
421 421 sidedata: yes yes no
422 422 persistent-nodemap: no no no
423 423 copies-sdc: yes yes no
424 424 plain-cl-delta: yes yes yes
425 425 compression: zlib zlib zlib
426 426 compression-level: default default default
427 427 $ hg debugsidedata -c -- 0
428 428 1 sidedata entries
429 429 entry-0014 size 14
430 430 $ hg debugsidedata -c -- 1
431 431 1 sidedata entries
432 432 entry-0014 size 14
433 433 $ hg debugsidedata -m -- 0
434 434 $ cat << EOF > .hg/hgrc
435 435 > [format]
436 436 > exp-use-side-data = yes
437 437 > exp-use-copies-side-data-changeset = no
438 438 > EOF
439 439 $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
440 440 $ hg debugformat -v
441 441 format-variant repo config default
442 442 fncache: yes yes yes
443 443 dotencode: yes yes yes
444 444 generaldelta: yes yes yes
445 445 sparserevlog: yes yes yes
446 446 sidedata: yes yes no
447 447 persistent-nodemap: no no no
448 448 copies-sdc: no no no
449 449 plain-cl-delta: yes yes yes
450 450 compression: zlib zlib zlib
451 451 compression-level: default default default
452 452 $ hg debugsidedata -c -- 0
453 453 1 sidedata entries
454 454 entry-0014 size 14
455 455 $ hg debugsidedata -c -- 1
456 456 1 sidedata entries
457 457 entry-0014 size 14
458 458 $ hg debugsidedata -m -- 0
459 459
460 460 upgrading
461 461
462 462 $ cat << EOF > .hg/hgrc
463 463 > [format]
464 464 > exp-use-copies-side-data-changeset = yes
465 465 > EOF
466 466 $ hg debugupgraderepo --run --quiet --no-backup > /dev/null
467 467 $ hg debugformat -v
468 468 format-variant repo config default
469 469 fncache: yes yes yes
470 470 dotencode: yes yes yes
471 471 generaldelta: yes yes yes
472 472 sparserevlog: yes yes yes
473 473 sidedata: yes yes no
474 474 persistent-nodemap: no no no
475 475 copies-sdc: yes yes no
476 476 plain-cl-delta: yes yes yes
477 477 compression: zlib zlib zlib
478 478 compression-level: default default default
479 479 $ hg debugsidedata -c -- 0
480 480 1 sidedata entries
481 481 entry-0014 size 14
482 482 $ hg debugsidedata -c -- 1
483 483 1 sidedata entries
484 484 entry-0014 size 14
485 485 $ hg debugsidedata -m -- 0
486 486
487 487 #endif
488 488
489 489 $ cd ..
@@ -1,653 +1,653 b''
1 1 #testcases filelog compatibility changeset sidedata
2 2
3 3 $ cat >> $HGRCPATH << EOF
4 4 > [extensions]
5 5 > rebase=
6 6 > [alias]
7 7 > l = log -G -T '{rev} {desc}\n{files}\n'
8 8 > EOF
9 9
10 10 #if compatibility
11 11 $ cat >> $HGRCPATH << EOF
12 12 > [experimental]
13 13 > copies.read-from = compatibility
14 14 > EOF
15 15 #endif
16 16
17 17 #if changeset
18 18 $ cat >> $HGRCPATH << EOF
19 19 > [experimental]
20 20 > copies.read-from = changeset-only
21 21 > copies.write-to = changeset-only
22 22 > EOF
23 23 #endif
24 24
25 25 #if sidedata
26 26 $ cat >> $HGRCPATH << EOF
27 27 > [format]
28 28 > exp-use-copies-side-data-changeset = yes
29 29 > EOF
30 30 #endif
31 31
32 32 $ REPONUM=0
33 33 $ newrepo() {
34 34 > cd $TESTTMP
35 35 > REPONUM=`expr $REPONUM + 1`
36 36 > hg init repo-$REPONUM
37 37 > cd repo-$REPONUM
38 38 > }
39 39
40 40 Simple rename case
41 41 $ newrepo
42 42 $ echo x > x
43 43 $ hg ci -Aqm 'add x'
44 44 $ hg mv x y
45 45 $ hg debugp1copies
46 46 x -> y
47 47 $ hg debugp2copies
48 48 $ hg ci -m 'rename x to y'
49 49 $ hg l
50 50 @ 1 rename x to y
51 51 | x y
52 52 o 0 add x
53 53 x
54 54 $ hg debugp1copies -r 1
55 55 x -> y
56 56 $ hg debugpathcopies 0 1
57 57 x -> y
58 58 $ hg debugpathcopies 1 0
59 59 y -> x
60 60 Test filtering copies by path. We do filtering by destination.
61 61 $ hg debugpathcopies 0 1 x
62 62 $ hg debugpathcopies 1 0 x
63 63 y -> x
64 64 $ hg debugpathcopies 0 1 y
65 65 x -> y
66 66 $ hg debugpathcopies 1 0 y
67 67
68 68 Copies not including commit changes
69 69 $ newrepo
70 70 $ echo x > x
71 71 $ hg ci -Aqm 'add x'
72 72 $ hg mv x y
73 73 $ hg debugpathcopies . .
74 74 $ hg debugpathcopies . 'wdir()'
75 75 x -> y
76 76 $ hg debugpathcopies 'wdir()' .
77 77 y -> x
78 78
79 79 Copy a file onto another file
80 80 $ newrepo
81 81 $ echo x > x
82 82 $ echo y > y
83 83 $ hg ci -Aqm 'add x and y'
84 84 $ hg cp -f x y
85 85 $ hg debugp1copies
86 86 x -> y
87 87 $ hg debugp2copies
88 88 $ hg ci -m 'copy x onto y'
89 89 $ hg l
90 90 @ 1 copy x onto y
91 91 | y
92 92 o 0 add x and y
93 93 x y
94 94 $ hg debugp1copies -r 1
95 95 x -> y
96 96 Incorrectly doesn't show the rename
97 97 $ hg debugpathcopies 0 1
98 98
99 99 Copy a file onto another file with same content. If metadata is stored in changeset, this does not
100 100 produce a new filelog entry. The changeset's "files" entry should still list the file.
101 101 $ newrepo
102 102 $ echo x > x
103 103 $ echo x > x2
104 104 $ hg ci -Aqm 'add x and x2 with same content'
105 105 $ hg cp -f x x2
106 106 $ hg ci -m 'copy x onto x2'
107 107 $ hg l
108 108 @ 1 copy x onto x2
109 109 | x2
110 110 o 0 add x and x2 with same content
111 111 x x2
112 112 $ hg debugp1copies -r 1
113 113 x -> x2
114 114 Incorrectly doesn't show the rename
115 115 $ hg debugpathcopies 0 1
116 116
117 117 Rename file in a loop: x->y->z->x
118 118 $ newrepo
119 119 $ echo x > x
120 120 $ hg ci -Aqm 'add x'
121 121 $ hg mv x y
122 122 $ hg debugp1copies
123 123 x -> y
124 124 $ hg debugp2copies
125 125 $ hg ci -m 'rename x to y'
126 126 $ hg mv y z
127 127 $ hg ci -m 'rename y to z'
128 128 $ hg mv z x
129 129 $ hg ci -m 'rename z to x'
130 130 $ hg l
131 131 @ 3 rename z to x
132 132 | x z
133 133 o 2 rename y to z
134 134 | y z
135 135 o 1 rename x to y
136 136 | x y
137 137 o 0 add x
138 138 x
139 139 $ hg debugpathcopies 0 3
140 140
141 141 Copy x to z, then remove z, then copy x2 (same content as x) to z. With copy metadata in the
142 142 changeset, the two copies here will have the same filelog entry, so ctx['z'].introrev() might point
143 143 to the first commit that added the file. We should still report the copy as being from x2.
144 144 $ newrepo
145 145 $ echo x > x
146 146 $ echo x > x2
147 147 $ hg ci -Aqm 'add x and x2 with same content'
148 148 $ hg cp x z
149 149 $ hg ci -qm 'copy x to z'
150 150 $ hg rm z
151 151 $ hg ci -m 'remove z'
152 152 $ hg cp x2 z
153 153 $ hg ci -m 'copy x2 to z'
154 154 $ hg l
155 155 @ 3 copy x2 to z
156 156 | z
157 157 o 2 remove z
158 158 | z
159 159 o 1 copy x to z
160 160 | z
161 161 o 0 add x and x2 with same content
162 162 x x2
163 163 $ hg debugp1copies -r 3
164 164 x2 -> z
165 165 $ hg debugpathcopies 0 3
166 166 x2 -> z
167 167
168 168 Create x and y, then rename them both to the same name, but on different sides of a fork
169 169 $ newrepo
170 170 $ echo x > x
171 171 $ echo y > y
172 172 $ hg ci -Aqm 'add x and y'
173 173 $ hg mv x z
174 174 $ hg ci -qm 'rename x to z'
175 175 $ hg co -q 0
176 176 $ hg mv y z
177 177 $ hg ci -qm 'rename y to z'
178 178 $ hg l
179 179 @ 2 rename y to z
180 180 | y z
181 181 | o 1 rename x to z
182 182 |/ x z
183 183 o 0 add x and y
184 184 x y
185 185 $ hg debugpathcopies 1 2
186 186 z -> x
187 187 y -> z
188 188
189 189 Fork renames x to y on one side and removes x on the other
190 190 $ newrepo
191 191 $ echo x > x
192 192 $ hg ci -Aqm 'add x'
193 193 $ hg mv x y
194 194 $ hg ci -m 'rename x to y'
195 195 $ hg co -q 0
196 196 $ hg rm x
197 197 $ hg ci -m 'remove x'
198 198 created new head
199 199 $ hg l
200 200 @ 2 remove x
201 201 | x
202 202 | o 1 rename x to y
203 203 |/ x y
204 204 o 0 add x
205 205 x
206 206 $ hg debugpathcopies 1 2
207 207
208 208 Merge rename from other branch
209 209 $ newrepo
210 210 $ echo x > x
211 211 $ hg ci -Aqm 'add x'
212 212 $ hg mv x y
213 213 $ hg ci -m 'rename x to y'
214 214 $ hg co -q 0
215 215 $ echo z > z
216 216 $ hg ci -Aqm 'add z'
217 217 $ hg merge -q 1
218 218 $ hg debugp1copies
219 219 $ hg debugp2copies
220 220 $ hg ci -m 'merge rename from p2'
221 221 $ hg l
222 222 @ 3 merge rename from p2
223 223 |\
224 224 | o 2 add z
225 225 | | z
226 226 o | 1 rename x to y
227 227 |/ x y
228 228 o 0 add x
229 229 x
230 230 Perhaps we should indicate the rename here, but `hg status` is documented to be weird during
231 231 merges, so...
232 232 $ hg debugp1copies -r 3
233 233 $ hg debugp2copies -r 3
234 234 $ hg debugpathcopies 0 3
235 235 x -> y
236 236 $ hg debugpathcopies 1 2
237 237 y -> x
238 238 $ hg debugpathcopies 1 3
239 239 $ hg debugpathcopies 2 3
240 240 x -> y
241 241
242 242 Copy file from either side in a merge
243 243 $ newrepo
244 244 $ echo x > x
245 245 $ hg ci -Aqm 'add x'
246 246 $ hg co -q null
247 247 $ echo y > y
248 248 $ hg ci -Aqm 'add y'
249 249 $ hg merge -q 0
250 250 $ hg cp y z
251 251 $ hg debugp1copies
252 252 y -> z
253 253 $ hg debugp2copies
254 254 $ hg ci -m 'copy file from p1 in merge'
255 255 $ hg co -q 1
256 256 $ hg merge -q 0
257 257 $ hg cp x z
258 258 $ hg debugp1copies
259 259 $ hg debugp2copies
260 260 x -> z
261 261 $ hg ci -qm 'copy file from p2 in merge'
262 262 $ hg l
263 263 @ 3 copy file from p2 in merge
264 264 |\ z
265 265 +---o 2 copy file from p1 in merge
266 266 | |/ z
267 267 | o 1 add y
268 268 | y
269 269 o 0 add x
270 270 x
271 271 $ hg debugp1copies -r 2
272 272 y -> z
273 273 $ hg debugp2copies -r 2
274 274 $ hg debugpathcopies 1 2
275 275 y -> z
276 276 $ hg debugpathcopies 0 2
277 277 $ hg debugp1copies -r 3
278 278 $ hg debugp2copies -r 3
279 279 x -> z
280 280 $ hg debugpathcopies 1 3
281 281 $ hg debugpathcopies 0 3
282 282 x -> z
283 283
284 284 Copy file that exists on both sides of the merge, same content on both sides
285 285 $ newrepo
286 286 $ echo x > x
287 287 $ hg ci -Aqm 'add x on branch 1'
288 288 $ hg co -q null
289 289 $ echo x > x
290 290 $ hg ci -Aqm 'add x on branch 2'
291 291 $ hg merge -q 0
292 292 $ hg cp x z
293 293 $ hg debugp1copies
294 294 x -> z
295 295 $ hg debugp2copies
296 296 $ hg ci -qm 'merge'
297 297 $ hg l
298 298 @ 2 merge
299 299 |\ z
300 300 | o 1 add x on branch 2
301 301 | x
302 302 o 0 add x on branch 1
303 303 x
304 304 $ hg debugp1copies -r 2
305 305 x -> z
306 306 $ hg debugp2copies -r 2
307 307 It's a little weird that it shows up on both sides
308 308 $ hg debugpathcopies 1 2
309 309 x -> z
310 310 $ hg debugpathcopies 0 2
311 311 x -> z (filelog !)
312 312
313 313 Copy file that exists on both sides of the merge, different content
314 314 $ newrepo
315 315 $ echo branch1 > x
316 316 $ hg ci -Aqm 'add x on branch 1'
317 317 $ hg co -q null
318 318 $ echo branch2 > x
319 319 $ hg ci -Aqm 'add x on branch 2'
320 320 $ hg merge -q 0
321 321 warning: conflicts while merging x! (edit, then use 'hg resolve --mark')
322 322 [1]
323 323 $ echo resolved > x
324 324 $ hg resolve -m x
325 325 (no more unresolved files)
326 326 $ hg cp x z
327 327 $ hg debugp1copies
328 328 x -> z
329 329 $ hg debugp2copies
330 330 $ hg ci -qm 'merge'
331 331 $ hg l
332 332 @ 2 merge
333 333 |\ x z
334 334 | o 1 add x on branch 2
335 335 | x
336 336 o 0 add x on branch 1
337 337 x
338 338 $ hg debugp1copies -r 2
339 339 x -> z (changeset !)
340 340 x -> z (sidedata !)
341 341 $ hg debugp2copies -r 2
342 342 x -> z (no-changeset no-sidedata !)
343 343 $ hg debugpathcopies 1 2
344 344 x -> z (changeset !)
345 345 x -> z (sidedata !)
346 346 $ hg debugpathcopies 0 2
347 347 x -> z (no-changeset no-sidedata !)
348 348
349 349 Copy x->y on one side of merge and copy x->z on the other side. Pathcopies from one parent
350 350 of the merge to the merge should include the copy from the other side.
351 351 $ newrepo
352 352 $ echo x > x
353 353 $ hg ci -Aqm 'add x'
354 354 $ hg cp x y
355 355 $ hg ci -qm 'copy x to y'
356 356 $ hg co -q 0
357 357 $ hg cp x z
358 358 $ hg ci -qm 'copy x to z'
359 359 $ hg merge -q 1
360 360 $ hg ci -m 'merge copy x->y and copy x->z'
361 361 $ hg l
362 362 @ 3 merge copy x->y and copy x->z
363 363 |\
364 364 | o 2 copy x to z
365 365 | | z
366 366 o | 1 copy x to y
367 367 |/ y
368 368 o 0 add x
369 369 x
370 370 $ hg debugp1copies -r 3
371 371 $ hg debugp2copies -r 3
372 372 $ hg debugpathcopies 2 3
373 373 x -> y
374 374 $ hg debugpathcopies 1 3
375 375 x -> z
376 376
377 377 Copy x to y on one side of merge, create y and rename to z on the other side.
378 378 $ newrepo
379 379 $ echo x > x
380 380 $ hg ci -Aqm 'add x'
381 381 $ hg cp x y
382 382 $ hg ci -qm 'copy x to y'
383 383 $ hg co -q 0
384 384 $ echo y > y
385 385 $ hg ci -Aqm 'add y'
386 386 $ hg mv y z
387 387 $ hg ci -m 'rename y to z'
388 388 $ hg merge -q 1
389 389 $ hg ci -m 'merge'
390 390 $ hg l
391 391 @ 4 merge
392 392 |\
393 393 | o 3 rename y to z
394 394 | | y z
395 395 | o 2 add y
396 396 | | y
397 397 o | 1 copy x to y
398 398 |/ y
399 399 o 0 add x
400 400 x
401 401 $ hg debugp1copies -r 3
402 402 y -> z
403 403 $ hg debugp2copies -r 3
404 404 $ hg debugpathcopies 2 3
405 405 y -> z
406 406 $ hg debugpathcopies 1 3
407 407 y -> z (no-filelog !)
408 408
409 409 Create x and y, then rename x to z on one side of merge, and rename y to z and
410 410 modify z on the other side. When storing copies in the changeset, we don't
411 411 filter out copies whose target was created on the other side of the merge.
412 412 $ newrepo
413 413 $ echo x > x
414 414 $ echo y > y
415 415 $ hg ci -Aqm 'add x and y'
416 416 $ hg mv x z
417 417 $ hg ci -qm 'rename x to z'
418 418 $ hg co -q 0
419 419 $ hg mv y z
420 420 $ hg ci -qm 'rename y to z'
421 421 $ echo z >> z
422 422 $ hg ci -m 'modify z'
423 423 $ hg merge -q 1
424 424 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
425 425 [1]
426 426 $ echo z > z
427 427 $ hg resolve -qm z
428 428 $ hg ci -m 'merge 1 into 3'
429 429 Try merging the other direction too
430 430 $ hg co -q 1
431 431 $ hg merge -q 3
432 432 warning: conflicts while merging z! (edit, then use 'hg resolve --mark')
433 433 [1]
434 434 $ echo z > z
435 435 $ hg resolve -qm z
436 436 $ hg ci -m 'merge 3 into 1'
437 437 created new head
438 438 $ hg l
439 439 @ 5 merge 3 into 1
440 440 |\ z
441 441 +---o 4 merge 1 into 3
442 442 | |/ z
443 443 | o 3 modify z
444 444 | | z
445 445 | o 2 rename y to z
446 446 | | y z
447 447 o | 1 rename x to z
448 448 |/ x z
449 449 o 0 add x and y
450 450 x y
451 451 $ hg debugpathcopies 1 4
452 452 y -> z (no-filelog !)
453 453 $ hg debugpathcopies 2 4
454 454 x -> z (no-filelog !)
455 455 $ hg debugpathcopies 0 4
456 456 x -> z (filelog !)
457 457 y -> z (no-filelog !)
458 458 $ hg debugpathcopies 1 5
459 459 y -> z (no-filelog !)
460 460 $ hg debugpathcopies 2 5
461 461 x -> z (no-filelog !)
462 462 $ hg debugpathcopies 0 5
463 463 x -> z
464 464
465 465
466 466 Create x and y, then rename x to z on one side of merge, and rename y to z and
467 467 then delete z on the other side.
468 468 $ newrepo
469 469 $ echo x > x
470 470 $ echo y > y
471 471 $ hg ci -Aqm 'add x and y'
472 472 $ hg mv x z
473 473 $ hg ci -qm 'rename x to z'
474 474 $ hg co -q 0
475 475 $ hg mv y z
476 476 $ hg ci -qm 'rename y to z'
477 477 $ hg rm z
478 478 $ hg ci -m 'delete z'
479 479 $ hg merge -q 1
480 480 $ echo z > z
481 481 $ hg ci -m 'merge 1 into 3'
482 482 Try merging the other direction too
483 483 $ hg co -q 1
484 484 $ hg merge -q 3
485 485 $ echo z > z
486 486 $ hg ci -m 'merge 3 into 1'
487 487 created new head
488 488 $ hg l
489 489 @ 5 merge 3 into 1
490 490 |\ z
491 491 +---o 4 merge 1 into 3
492 492 | |/ z
493 493 | o 3 delete z
494 494 | | z
495 495 | o 2 rename y to z
496 496 | | y z
497 497 o | 1 rename x to z
498 498 |/ x z
499 499 o 0 add x and y
500 500 x y
501 501 $ hg debugpathcopies 1 4
502 502 $ hg debugpathcopies 2 4
503 503 x -> z (no-filelog !)
504 504 $ hg debugpathcopies 0 4
505 505 x -> z (filelog !)
506 506 $ hg debugpathcopies 1 5
507 507 $ hg debugpathcopies 2 5
508 508 x -> z (no-filelog !)
509 509 $ hg debugpathcopies 0 5
510 510 x -> z
511 511
512 512
513 513 Test for a case in fullcopytracing algorithm where neither of the merging csets
514 514 is a descendant of the merge base. This test reflects that the algorithm
515 515 correctly finds the copies:
516 516
517 517 $ cat >> $HGRCPATH << EOF
518 518 > [experimental]
519 519 > evolution.createmarkers=True
520 520 > evolution.allowunstable=True
521 521 > EOF
522 522
523 523 $ newrepo
524 524 $ echo a > a
525 525 $ hg add a
526 526 $ hg ci -m "added a"
527 527 $ echo b > b
528 528 $ hg add b
529 529 $ hg ci -m "added b"
530 530
531 531 $ hg mv b b1
532 532 $ hg ci -m "rename b to b1"
533 533
534 534 $ hg up ".^"
535 535 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
536 536 $ echo d > d
537 537 $ hg add d
538 538 $ hg ci -m "added d"
539 539 created new head
540 540
541 541 $ echo baba >> b
542 542 $ hg ci --amend -m "added d, modified b"
543 543
544 544 $ hg l --hidden
545 545 @ 4 added d, modified b
546 546 | b d
547 547 | x 3 added d
548 548 |/ d
549 549 | o 2 rename b to b1
550 550 |/ b b1
551 551 o 1 added b
552 552 | b
553 553 o 0 added a
554 554 a
555 555
556 556 Grafting revision 4 on top of revision 2, showing that it respect the rename:
557 557
558 558 $ hg up 2 -q
559 559 $ hg graft -r 4 --base 3 --hidden
560 560 grafting 4:af28412ec03c "added d, modified b" (tip) (no-changeset !)
561 561 grafting 4:6325ca0b7a1c "added d, modified b" (tip) (changeset !)
562 562 merging b1 and b to b1
563 563
564 564 $ hg l -l1 -p
565 565 @ 5 added d, modified b
566 566 | b1
567 567 ~ diff -r 5a4825cc2926 -r 94a2f1a0e8e2 b1 (no-changeset !)
568 568 ~ diff -r 0a0ed3b3251c -r d544fb655520 b1 (changeset !)
569 569 --- a/b1 Thu Jan 01 00:00:00 1970 +0000
570 570 +++ b/b1 Thu Jan 01 00:00:00 1970 +0000
571 571 @@ -1,1 +1,2 @@
572 572 b
573 573 +baba
574 574
575 575 Test to make sure that fullcopytracing algorithm doesn't fail when neither of the
576 576 merging csets is a descendant of the base.
577 577 -------------------------------------------------------------------------------------------------
578 578
579 579 $ newrepo
580 580 $ echo a > a
581 581 $ hg add a
582 582 $ hg ci -m "added a"
583 583 $ echo b > b
584 584 $ hg add b
585 585 $ hg ci -m "added b"
586 586
587 587 $ echo foobar > willconflict
588 588 $ hg add willconflict
589 589 $ hg ci -m "added willconflict"
590 590 $ echo c > c
591 591 $ hg add c
592 592 $ hg ci -m "added c"
593 593
594 594 $ hg l
595 595 @ 3 added c
596 596 | c
597 597 o 2 added willconflict
598 598 | willconflict
599 599 o 1 added b
600 600 | b
601 601 o 0 added a
602 602 a
603 603
604 604 $ hg up ".^^"
605 605 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
606 606 $ echo d > d
607 607 $ hg add d
608 608 $ hg ci -m "added d"
609 609 created new head
610 610
611 611 $ echo barfoo > willconflict
612 612 $ hg add willconflict
613 613 $ hg ci --amend -m "added willconflict and d"
614 614
615 615 $ hg l
616 616 @ 5 added willconflict and d
617 617 | d willconflict
618 618 | o 3 added c
619 619 | | c
620 620 | o 2 added willconflict
621 621 |/ willconflict
622 622 o 1 added b
623 623 | b
624 624 o 0 added a
625 625 a
626 626
627 627 $ hg rebase -r . -d 2 -t :other
628 rebasing 5:5018b1509e94 "added willconflict and d" (tip) (no-changeset !)
629 rebasing 5:af8d273bf580 "added willconflict and d" (tip) (changeset !)
628 rebasing 5:5018b1509e94 tip "added willconflict and d" (no-changeset !)
629 rebasing 5:af8d273bf580 tip "added willconflict and d" (changeset !)
630 630
631 631 $ hg up 3 -q
632 632 $ hg l --hidden
633 633 o 6 added willconflict and d
634 634 | d willconflict
635 635 | x 5 added willconflict and d
636 636 | | d willconflict
637 637 | | x 4 added d
638 638 | |/ d
639 639 +---@ 3 added c
640 640 | | c
641 641 o | 2 added willconflict
642 642 |/ willconflict
643 643 o 1 added b
644 644 | b
645 645 o 0 added a
646 646 a
647 647
648 648 Now if we trigger a merge between revision 3 and 6 using base revision 4,
649 649 neither of the merging csets will be a descendant of the base revision:
650 650
651 651 $ hg graft -r 6 --base 4 --hidden -t :other
652 652 grafting 6:99802e4f1e46 "added willconflict and d" (tip) (no-changeset !)
653 653 grafting 6:b19f0df72728 "added willconflict and d" (tip) (changeset !)
@@ -1,255 +1,255 b''
1 1 Test for the full copytracing algorithm
2 2 =======================================
3 3
4 4
5 5 Initial Setup
6 6 =============
7 7
8 8 use git diff to see rename
9 9
10 10 $ cat << EOF >> $HGRCPATH
11 11 > [diff]
12 12 > git=yes
13 13 > EOF
14 14
15 15 Setup an history where one side copy and rename a file (and update it) while the other side update it.
16 16
17 17 $ hg init t
18 18 $ cd t
19 19
20 20 $ echo 1 > a
21 21 $ hg ci -qAm "first"
22 22
23 23 $ hg cp a b
24 24 $ hg mv a c
25 25 $ echo 2 >> b
26 26 $ echo 2 >> c
27 27
28 28 $ hg ci -qAm "second"
29 29
30 30 $ hg co -C 0
31 31 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
32 32
33 33 $ echo 0 > a
34 34 $ echo 1 >> a
35 35
36 36 $ hg ci -qAm "other"
37 37
38 38 $ hg log -G --patch
39 39 @ changeset: 2:add3f11052fa
40 40 | tag: tip
41 41 | parent: 0:b8bf91eeebbc
42 42 | user: test
43 43 | date: Thu Jan 01 00:00:00 1970 +0000
44 44 | summary: other
45 45 |
46 46 | diff --git a/a b/a
47 47 | --- a/a
48 48 | +++ b/a
49 49 | @@ -1,1 +1,2 @@
50 50 | +0
51 51 | 1
52 52 |
53 53 | o changeset: 1:17c05bb7fcb6
54 54 |/ user: test
55 55 | date: Thu Jan 01 00:00:00 1970 +0000
56 56 | summary: second
57 57 |
58 58 | diff --git a/a b/b
59 59 | rename from a
60 60 | rename to b
61 61 | --- a/a
62 62 | +++ b/b
63 63 | @@ -1,1 +1,2 @@
64 64 | 1
65 65 | +2
66 66 | diff --git a/a b/c
67 67 | copy from a
68 68 | copy to c
69 69 | --- a/a
70 70 | +++ b/c
71 71 | @@ -1,1 +1,2 @@
72 72 | 1
73 73 | +2
74 74 |
75 75 o changeset: 0:b8bf91eeebbc
76 76 user: test
77 77 date: Thu Jan 01 00:00:00 1970 +0000
78 78 summary: first
79 79
80 80 diff --git a/a b/a
81 81 new file mode 100644
82 82 --- /dev/null
83 83 +++ b/a
84 84 @@ -0,0 +1,1 @@
85 85 +1
86 86
87 87
88 88 Test Simple Merge
89 89 =================
90 90
91 91 $ hg merge --debug
92 92 unmatched files in other:
93 93 b
94 94 c
95 95 all copies found (* = to merge, ! = divergent, % = renamed and deleted):
96 96 on remote side:
97 97 src: 'a' -> dst: 'b' *
98 98 src: 'a' -> dst: 'c' *
99 99 checking for directory renames
100 100 resolving manifests
101 101 branchmerge: True, force: False, partial: False
102 102 ancestor: b8bf91eeebbc, local: add3f11052fa+, remote: 17c05bb7fcb6
103 103 preserving a for resolve of b
104 104 preserving a for resolve of c
105 105 removing a
106 106 starting 4 threads for background file closing (?)
107 107 b: remote moved from a -> m (premerge)
108 108 picked tool ':merge' for b (binary False symlink False changedelete False)
109 109 merging a and b to b
110 110 my b@add3f11052fa+ other b@17c05bb7fcb6 ancestor a@b8bf91eeebbc
111 111 premerge successful
112 112 c: remote moved from a -> m (premerge)
113 113 picked tool ':merge' for c (binary False symlink False changedelete False)
114 114 merging a and c to c
115 115 my c@add3f11052fa+ other c@17c05bb7fcb6 ancestor a@b8bf91eeebbc
116 116 premerge successful
117 117 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
118 118 (branch merge, don't forget to commit)
119 119
120 120 file b
121 121 $ cat b
122 122 0
123 123 1
124 124 2
125 125
126 126 file c
127 127 $ cat c
128 128 0
129 129 1
130 130 2
131 131
132 132 Test disabling copy tracing
133 133 ===========================
134 134
135 135 first verify copy metadata was kept
136 136 -----------------------------------
137 137
138 138 $ hg up -qC 2
139 139 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase=
140 rebasing 2:add3f11052fa "other" (tip)
140 rebasing 2:add3f11052fa tip "other"
141 141 merging b and a to b
142 142 merging c and a to c
143 143
144 144 $ cat b
145 145 0
146 146 1
147 147 2
148 148
149 149 next verify copy metadata is lost when disabled
150 150 ------------------------------------------------
151 151
152 152 $ hg strip -r . --config extensions.strip=
153 153 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
154 154 saved backup bundle to $TESTTMP/t/.hg/strip-backup/550bd84c0cd3-fc575957-backup.hg
155 155 $ hg up -qC 2
156 156 $ hg rebase --keep -d 1 -b 2 --config extensions.rebase= --config experimental.copytrace=off --config ui.interactive=True << EOF
157 157 > c
158 158 > EOF
159 rebasing 2:add3f11052fa "other" (tip)
159 rebasing 2:add3f11052fa tip "other"
160 160 file 'a' was deleted in local [dest] but was modified in other [source].
161 161 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
162 162 What do you want to do? c
163 163
164 164 $ cat b
165 165 1
166 166 2
167 167
168 168 $ cd ..
169 169
170 170 Verify disabling copy tracing still keeps copies from rebase source
171 171 -------------------------------------------------------------------
172 172
173 173 $ hg init copydisable
174 174 $ cd copydisable
175 175 $ touch a
176 176 $ hg ci -Aqm 'add a'
177 177 $ touch b
178 178 $ hg ci -Aqm 'add b, c'
179 179 $ hg cp b x
180 180 $ echo x >> x
181 181 $ hg ci -qm 'copy b->x'
182 182 $ hg up -q 1
183 183 $ touch z
184 184 $ hg ci -Aqm 'add z'
185 185 $ hg log -G -T '{rev} {desc}\n'
186 186 @ 3 add z
187 187 |
188 188 | o 2 copy b->x
189 189 |/
190 190 o 1 add b, c
191 191 |
192 192 o 0 add a
193 193
194 194 $ hg rebase -d . -b 2 --config extensions.rebase= --config experimental.copytrace=off
195 195 rebasing 2:6adcf8c12e7d "copy b->x"
196 196 saved backup bundle to $TESTTMP/copydisable/.hg/strip-backup/6adcf8c12e7d-ce4b3e75-rebase.hg
197 197 $ hg up -q 3
198 198 $ hg log -f x -T '{rev} {desc}\n'
199 199 3 copy b->x
200 200 1 add b, c
201 201
202 202 $ cd ../
203 203
204 204
205 205 test storage preservation
206 206 -------------------------
207 207
208 208 Verify rebase do not discard recorded copies data when copy tracing usage is
209 209 disabled.
210 210
211 211 Setup
212 212
213 213 $ hg init copydisable3
214 214 $ cd copydisable3
215 215 $ touch a
216 216 $ hg ci -Aqm 'add a'
217 217 $ hg cp a b
218 218 $ hg ci -Aqm 'copy a->b'
219 219 $ hg mv b c
220 220 $ hg ci -Aqm 'move b->c'
221 221 $ hg up -q 0
222 222 $ hg cp a b
223 223 $ echo b >> b
224 224 $ hg ci -Aqm 'copy a->b (2)'
225 225 $ hg log -G -T '{rev} {desc}\n'
226 226 @ 3 copy a->b (2)
227 227 |
228 228 | o 2 move b->c
229 229 | |
230 230 | o 1 copy a->b
231 231 |/
232 232 o 0 add a
233 233
234 234
235 235 Actual Test
236 236
237 237 A file is copied on one side and has been moved twice on the other side. the
238 238 file is copied from `0:a`, so the file history of the `3:b` should trace directly to `0:a`.
239 239
240 240 $ hg rebase -d 2 -s 3 --config extensions.rebase= --config experimental.copytrace=off
241 rebasing 3:47e1a9e6273b "copy a->b (2)" (tip)
241 rebasing 3:47e1a9e6273b tip "copy a->b (2)"
242 242 saved backup bundle to $TESTTMP/copydisable3/.hg/strip-backup/47e1a9e6273b-2d099c59-rebase.hg
243 243
244 244 $ hg log -G -f b
245 245 @ changeset: 3:76024fb4b05b
246 246 : tag: tip
247 247 : user: test
248 248 : date: Thu Jan 01 00:00:00 1970 +0000
249 249 : summary: copy a->b (2)
250 250 :
251 251 o changeset: 0:ac82d8b1f7c4
252 252 user: test
253 253 date: Thu Jan 01 00:00:00 1970 +0000
254 254 summary: add a
255 255
@@ -1,726 +1,726 b''
1 1 Test for the heuristic copytracing algorithm
2 2 ============================================
3 3
4 4 $ cat >> $TESTTMP/copytrace.sh << '__EOF__'
5 5 > initclient() {
6 6 > cat >> $1/.hg/hgrc <<EOF
7 7 > [experimental]
8 8 > copytrace = heuristics
9 9 > copytrace.sourcecommitlimit = -1
10 10 > EOF
11 11 > }
12 12 > __EOF__
13 13 $ . "$TESTTMP/copytrace.sh"
14 14
15 15 $ cat >> $HGRCPATH << EOF
16 16 > [extensions]
17 17 > rebase=
18 18 > [alias]
19 19 > l = log -G -T 'rev: {rev}\ndesc: {desc}\n'
20 20 > pl = log -G -T 'rev: {rev}, phase: {phase}\ndesc: {desc}\n'
21 21 > EOF
22 22
23 23 NOTE: calling initclient() set copytrace.sourcecommitlimit=-1 as we want to
24 24 prevent the full copytrace algorithm to run and test the heuristic algorithm
25 25 without complexing the test cases with public and draft commits.
26 26
27 27 Check filename heuristics (same dirname and same basename)
28 28 ----------------------------------------------------------
29 29
30 30 $ hg init repo
31 31 $ initclient repo
32 32 $ cd repo
33 33 $ echo a > a
34 34 $ mkdir dir
35 35 $ echo a > dir/file.txt
36 36 $ hg addremove
37 37 adding a
38 38 adding dir/file.txt
39 39 $ hg ci -m initial
40 40 $ hg mv a b
41 41 $ hg mv -q dir dir2
42 42 $ hg ci -m 'mv a b, mv dir/ dir2/'
43 43 $ hg up -q 0
44 44 $ echo b > a
45 45 $ echo b > dir/file.txt
46 46 $ hg ci -qm 'mod a, mod dir/file.txt'
47 47
48 48 $ hg l
49 49 @ rev: 2
50 50 | desc: mod a, mod dir/file.txt
51 51 | o rev: 1
52 52 |/ desc: mv a b, mv dir/ dir2/
53 53 o rev: 0
54 54 desc: initial
55 55
56 56 $ hg rebase -s . -d 1
57 rebasing 2:557f403c0afd "mod a, mod dir/file.txt" (tip)
57 rebasing 2:557f403c0afd tip "mod a, mod dir/file.txt"
58 58 merging b and a to b
59 59 merging dir2/file.txt and dir/file.txt to dir2/file.txt
60 60 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/557f403c0afd-9926eeff-rebase.hg
61 61 $ cd ..
62 62 $ rm -rf repo
63 63
64 64 Make sure filename heuristics do not when they are not related
65 65 --------------------------------------------------------------
66 66
67 67 $ hg init repo
68 68 $ initclient repo
69 69 $ cd repo
70 70 $ echo 'somecontent' > a
71 71 $ hg add a
72 72 $ hg ci -m initial
73 73 $ hg rm a
74 74 $ echo 'completelydifferentcontext' > b
75 75 $ hg add b
76 76 $ hg ci -m 'rm a, add b'
77 77 $ hg up -q 0
78 78 $ printf 'somecontent\nmoarcontent' > a
79 79 $ hg ci -qm 'mode a'
80 80
81 81 $ hg l
82 82 @ rev: 2
83 83 | desc: mode a
84 84 | o rev: 1
85 85 |/ desc: rm a, add b
86 86 o rev: 0
87 87 desc: initial
88 88
89 89 $ hg rebase -s . -d 1
90 rebasing 2:d526312210b9 "mode a" (tip)
90 rebasing 2:d526312210b9 tip "mode a"
91 91 file 'a' was deleted in local [dest] but was modified in other [source].
92 92 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
93 93 What do you want to do? u
94 94 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
95 95 [1]
96 96
97 97 $ cd ..
98 98 $ rm -rf repo
99 99
100 100 Test when lca didn't modified the file that was moved
101 101 -----------------------------------------------------
102 102
103 103 $ hg init repo
104 104 $ initclient repo
105 105 $ cd repo
106 106 $ echo 'somecontent' > a
107 107 $ hg add a
108 108 $ hg ci -m initial
109 109 $ echo c > c
110 110 $ hg add c
111 111 $ hg ci -m randomcommit
112 112 $ hg mv a b
113 113 $ hg ci -m 'mv a b'
114 114 $ hg up -q 1
115 115 $ echo b > a
116 116 $ hg ci -qm 'mod a'
117 117
118 118 $ hg pl
119 119 @ rev: 3, phase: draft
120 120 | desc: mod a
121 121 | o rev: 2, phase: draft
122 122 |/ desc: mv a b
123 123 o rev: 1, phase: draft
124 124 | desc: randomcommit
125 125 o rev: 0, phase: draft
126 126 desc: initial
127 127
128 128 $ hg rebase -s . -d 2
129 rebasing 3:9d5cf99c3d9f "mod a" (tip)
129 rebasing 3:9d5cf99c3d9f tip "mod a"
130 130 merging b and a to b
131 131 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9d5cf99c3d9f-f02358cc-rebase.hg
132 132 $ cd ..
133 133 $ rm -rf repo
134 134
135 135 Rebase "backwards"
136 136 ------------------
137 137
138 138 $ hg init repo
139 139 $ initclient repo
140 140 $ cd repo
141 141 $ echo 'somecontent' > a
142 142 $ hg add a
143 143 $ hg ci -m initial
144 144 $ echo c > c
145 145 $ hg add c
146 146 $ hg ci -m randomcommit
147 147 $ hg mv a b
148 148 $ hg ci -m 'mv a b'
149 149 $ hg up -q 2
150 150 $ echo b > b
151 151 $ hg ci -qm 'mod b'
152 152
153 153 $ hg l
154 154 @ rev: 3
155 155 | desc: mod b
156 156 o rev: 2
157 157 | desc: mv a b
158 158 o rev: 1
159 159 | desc: randomcommit
160 160 o rev: 0
161 161 desc: initial
162 162
163 163 $ hg rebase -s . -d 0
164 rebasing 3:fbe97126b396 "mod b" (tip)
164 rebasing 3:fbe97126b396 tip "mod b"
165 165 merging a and b to a
166 166 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/fbe97126b396-cf5452a1-rebase.hg
167 167 $ cd ..
168 168 $ rm -rf repo
169 169
170 170 Check a few potential move candidates
171 171 -------------------------------------
172 172
173 173 $ hg init repo
174 174 $ initclient repo
175 175 $ cd repo
176 176 $ mkdir dir
177 177 $ echo a > dir/a
178 178 $ hg add dir/a
179 179 $ hg ci -qm initial
180 180 $ hg mv dir/a dir/b
181 181 $ hg ci -qm 'mv dir/a dir/b'
182 182 $ mkdir dir2
183 183 $ echo b > dir2/a
184 184 $ hg add dir2/a
185 185 $ hg ci -qm 'create dir2/a'
186 186 $ hg up -q 0
187 187 $ echo b > dir/a
188 188 $ hg ci -qm 'mod dir/a'
189 189
190 190 $ hg l
191 191 @ rev: 3
192 192 | desc: mod dir/a
193 193 | o rev: 2
194 194 | | desc: create dir2/a
195 195 | o rev: 1
196 196 |/ desc: mv dir/a dir/b
197 197 o rev: 0
198 198 desc: initial
199 199
200 200 $ hg rebase -s . -d 2
201 rebasing 3:6b2f4cece40f "mod dir/a" (tip)
201 rebasing 3:6b2f4cece40f tip "mod dir/a"
202 202 merging dir/b and dir/a to dir/b
203 203 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/6b2f4cece40f-503efe60-rebase.hg
204 204 $ cd ..
205 205 $ rm -rf repo
206 206
207 207 Test the copytrace.movecandidateslimit with many move candidates
208 208 ----------------------------------------------------------------
209 209
210 210 $ hg init repo
211 211 $ initclient repo
212 212 $ cd repo
213 213 $ echo a > a
214 214 $ hg add a
215 215 $ hg ci -m initial
216 216 $ hg mv a foo
217 217 $ echo a > b
218 218 $ echo a > c
219 219 $ echo a > d
220 220 $ echo a > e
221 221 $ echo a > f
222 222 $ echo a > g
223 223 $ hg add b
224 224 $ hg add c
225 225 $ hg add d
226 226 $ hg add e
227 227 $ hg add f
228 228 $ hg add g
229 229 $ hg ci -m 'mv a foo, add many files'
230 230 $ hg up -q ".^"
231 231 $ echo b > a
232 232 $ hg ci -m 'mod a'
233 233 created new head
234 234
235 235 $ hg l
236 236 @ rev: 2
237 237 | desc: mod a
238 238 | o rev: 1
239 239 |/ desc: mv a foo, add many files
240 240 o rev: 0
241 241 desc: initial
242 242
243 243 With small limit
244 244
245 245 $ hg rebase -s 2 -d 1 --config experimental.copytrace.movecandidateslimit=0
246 rebasing 2:ef716627c70b "mod a" (tip)
246 rebasing 2:ef716627c70b tip "mod a"
247 247 skipping copytracing for 'a', more candidates than the limit: 7
248 248 file 'a' was deleted in local [dest] but was modified in other [source].
249 249 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
250 250 What do you want to do? u
251 251 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
252 252 [1]
253 253
254 254 $ hg rebase --abort
255 255 rebase aborted
256 256
257 257 With default limit which is 100
258 258
259 259 $ hg rebase -s 2 -d 1
260 rebasing 2:ef716627c70b "mod a" (tip)
260 rebasing 2:ef716627c70b tip "mod a"
261 261 merging foo and a to foo
262 262 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg
263 263
264 264 $ cd ..
265 265 $ rm -rf repo
266 266
267 267 Move file in one branch and delete it in another
268 268 -----------------------------------------------
269 269
270 270 $ hg init repo
271 271 $ initclient repo
272 272 $ cd repo
273 273 $ echo a > a
274 274 $ hg add a
275 275 $ hg ci -m initial
276 276 $ hg mv a b
277 277 $ hg ci -m 'mv a b'
278 278 $ hg up -q ".^"
279 279 $ hg rm a
280 280 $ hg ci -m 'del a'
281 281 created new head
282 282
283 283 $ hg pl
284 284 @ rev: 2, phase: draft
285 285 | desc: del a
286 286 | o rev: 1, phase: draft
287 287 |/ desc: mv a b
288 288 o rev: 0, phase: draft
289 289 desc: initial
290 290
291 291 $ hg rebase -s 1 -d 2
292 292 rebasing 1:472e38d57782 "mv a b"
293 293 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-17d50e29-rebase.hg
294 294 $ hg up -q c492ed3c7e35dcd1dc938053b8adf56e2cfbd062
295 295 $ ls -A
296 296 .hg
297 297 b
298 298 $ cd ..
299 299 $ rm -rf repo
300 300
301 301 Move a directory in draft branch
302 302 --------------------------------
303 303
304 304 $ hg init repo
305 305 $ initclient repo
306 306 $ cd repo
307 307 $ mkdir dir
308 308 $ echo a > dir/a
309 309 $ hg add dir/a
310 310 $ hg ci -qm initial
311 311 $ echo b > dir/a
312 312 $ hg ci -qm 'mod dir/a'
313 313 $ hg up -q ".^"
314 314 $ hg mv -q dir/ dir2
315 315 $ hg ci -qm 'mv dir/ dir2/'
316 316
317 317 $ hg l
318 318 @ rev: 2
319 319 | desc: mv dir/ dir2/
320 320 | o rev: 1
321 321 |/ desc: mod dir/a
322 322 o rev: 0
323 323 desc: initial
324 324
325 325 $ hg rebase -s . -d 1
326 rebasing 2:a33d80b6e352 "mv dir/ dir2/" (tip)
326 rebasing 2:a33d80b6e352 tip "mv dir/ dir2/"
327 327 merging dir/a and dir2/a to dir2/a
328 328 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/a33d80b6e352-fecb9ada-rebase.hg
329 329 $ cd ..
330 330 $ rm -rf server
331 331 $ rm -rf repo
332 332
333 333 Move file twice and rebase mod on top of moves
334 334 ----------------------------------------------
335 335
336 336 $ hg init repo
337 337 $ initclient repo
338 338 $ cd repo
339 339 $ echo a > a
340 340 $ hg add a
341 341 $ hg ci -m initial
342 342 $ hg mv a b
343 343 $ hg ci -m 'mv a b'
344 344 $ hg mv b c
345 345 $ hg ci -m 'mv b c'
346 346 $ hg up -q 0
347 347 $ echo c > a
348 348 $ hg ci -m 'mod a'
349 349 created new head
350 350
351 351 $ hg l
352 352 @ rev: 3
353 353 | desc: mod a
354 354 | o rev: 2
355 355 | | desc: mv b c
356 356 | o rev: 1
357 357 |/ desc: mv a b
358 358 o rev: 0
359 359 desc: initial
360 360 $ hg rebase -s . -d 2
361 rebasing 3:d41316942216 "mod a" (tip)
361 rebasing 3:d41316942216 tip "mod a"
362 362 merging c and a to c
363 363 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/d41316942216-2b5949bc-rebase.hg
364 364
365 365 $ cd ..
366 366 $ rm -rf repo
367 367
368 368 Move file twice and rebase moves on top of mods
369 369 -----------------------------------------------
370 370
371 371 $ hg init repo
372 372 $ initclient repo
373 373 $ cd repo
374 374 $ echo a > a
375 375 $ hg add a
376 376 $ hg ci -m initial
377 377 $ hg mv a b
378 378 $ hg ci -m 'mv a b'
379 379 $ hg mv b c
380 380 $ hg ci -m 'mv b c'
381 381 $ hg up -q 0
382 382 $ echo c > a
383 383 $ hg ci -m 'mod a'
384 384 created new head
385 385 $ hg l
386 386 @ rev: 3
387 387 | desc: mod a
388 388 | o rev: 2
389 389 | | desc: mv b c
390 390 | o rev: 1
391 391 |/ desc: mv a b
392 392 o rev: 0
393 393 desc: initial
394 394 $ hg rebase -s 1 -d .
395 395 rebasing 1:472e38d57782 "mv a b"
396 396 merging a and b to b
397 397 rebasing 2:d3efd280421d "mv b c"
398 398 merging b and c to c
399 399 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/472e38d57782-ab8d3c58-rebase.hg
400 400
401 401 $ cd ..
402 402 $ rm -rf repo
403 403
404 404 Move one file and add another file in the same folder in one branch, modify file in another branch
405 405 --------------------------------------------------------------------------------------------------
406 406
407 407 $ hg init repo
408 408 $ initclient repo
409 409 $ cd repo
410 410 $ echo a > a
411 411 $ hg add a
412 412 $ hg ci -m initial
413 413 $ hg mv a b
414 414 $ hg ci -m 'mv a b'
415 415 $ echo c > c
416 416 $ hg add c
417 417 $ hg ci -m 'add c'
418 418 $ hg up -q 0
419 419 $ echo b > a
420 420 $ hg ci -m 'mod a'
421 421 created new head
422 422
423 423 $ hg l
424 424 @ rev: 3
425 425 | desc: mod a
426 426 | o rev: 2
427 427 | | desc: add c
428 428 | o rev: 1
429 429 |/ desc: mv a b
430 430 o rev: 0
431 431 desc: initial
432 432
433 433 $ hg rebase -s . -d 2
434 rebasing 3:ef716627c70b "mod a" (tip)
434 rebasing 3:ef716627c70b tip "mod a"
435 435 merging b and a to b
436 436 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg
437 437 $ ls -A
438 438 .hg
439 439 b
440 440 c
441 441 $ cat b
442 442 b
443 443 $ rm -rf repo
444 444
445 445 Merge test
446 446 ----------
447 447
448 448 $ hg init repo
449 449 $ initclient repo
450 450 $ cd repo
451 451 $ echo a > a
452 452 $ hg add a
453 453 $ hg ci -m initial
454 454 $ echo b > a
455 455 $ hg ci -m 'modify a'
456 456 $ hg up -q 0
457 457 $ hg mv a b
458 458 $ hg ci -m 'mv a b'
459 459 created new head
460 460 $ hg up -q 2
461 461
462 462 $ hg l
463 463 @ rev: 2
464 464 | desc: mv a b
465 465 | o rev: 1
466 466 |/ desc: modify a
467 467 o rev: 0
468 468 desc: initial
469 469
470 470 $ hg merge 1
471 471 merging b and a to b
472 472 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
473 473 (branch merge, don't forget to commit)
474 474 $ hg ci -m merge
475 475 $ ls -A
476 476 .hg
477 477 b
478 478 $ cd ..
479 479 $ rm -rf repo
480 480
481 481 Copy and move file
482 482 ------------------
483 483
484 484 $ hg init repo
485 485 $ initclient repo
486 486 $ cd repo
487 487 $ echo a > a
488 488 $ hg add a
489 489 $ hg ci -m initial
490 490 $ hg cp a c
491 491 $ hg mv a b
492 492 $ hg ci -m 'cp a c, mv a b'
493 493 $ hg up -q 0
494 494 $ echo b > a
495 495 $ hg ci -m 'mod a'
496 496 created new head
497 497
498 498 $ hg l
499 499 @ rev: 2
500 500 | desc: mod a
501 501 | o rev: 1
502 502 |/ desc: cp a c, mv a b
503 503 o rev: 0
504 504 desc: initial
505 505
506 506 $ hg rebase -s . -d 1
507 rebasing 2:ef716627c70b "mod a" (tip)
507 rebasing 2:ef716627c70b tip "mod a"
508 508 merging b and a to b
509 509 merging c and a to c
510 510 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/ef716627c70b-24681561-rebase.hg
511 511 $ ls -A
512 512 .hg
513 513 b
514 514 c
515 515 $ cat b
516 516 b
517 517 $ cat c
518 518 b
519 519 $ cd ..
520 520 $ rm -rf repo
521 521
522 522 Do a merge commit with many consequent moves in one branch
523 523 ----------------------------------------------------------
524 524
525 525 $ hg init repo
526 526 $ initclient repo
527 527 $ cd repo
528 528 $ echo a > a
529 529 $ hg add a
530 530 $ hg ci -m initial
531 531 $ echo b > a
532 532 $ hg ci -qm 'mod a'
533 533 $ hg up -q ".^"
534 534 $ hg mv a b
535 535 $ hg ci -qm 'mv a b'
536 536 $ hg mv b c
537 537 $ hg ci -qm 'mv b c'
538 538 $ hg up -q 1
539 539 $ hg l
540 540 o rev: 3
541 541 | desc: mv b c
542 542 o rev: 2
543 543 | desc: mv a b
544 544 | @ rev: 1
545 545 |/ desc: mod a
546 546 o rev: 0
547 547 desc: initial
548 548
549 549 $ hg merge 3
550 550 merging a and c to c
551 551 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
552 552 (branch merge, don't forget to commit)
553 553 $ hg ci -qm 'merge'
554 554 $ hg pl
555 555 @ rev: 4, phase: draft
556 556 |\ desc: merge
557 557 | o rev: 3, phase: draft
558 558 | | desc: mv b c
559 559 | o rev: 2, phase: draft
560 560 | | desc: mv a b
561 561 o | rev: 1, phase: draft
562 562 |/ desc: mod a
563 563 o rev: 0, phase: draft
564 564 desc: initial
565 565 $ ls -A
566 566 .hg
567 567 c
568 568 $ cd ..
569 569 $ rm -rf repo
570 570
571 571 Test shelve/unshelve
572 572 -------------------
573 573
574 574 $ hg init repo
575 575 $ initclient repo
576 576 $ cd repo
577 577 $ echo a > a
578 578 $ hg add a
579 579 $ hg ci -m initial
580 580 $ echo b > a
581 581 $ hg shelve
582 582 shelved as default
583 583 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
584 584 $ hg mv a b
585 585 $ hg ci -m 'mv a b'
586 586
587 587 $ hg l
588 588 @ rev: 1
589 589 | desc: mv a b
590 590 o rev: 0
591 591 desc: initial
592 592 $ hg unshelve
593 593 unshelving change 'default'
594 594 rebasing shelved changes
595 595 merging b and a to b
596 596 $ ls -A
597 597 .hg
598 598 b
599 599 $ cat b
600 600 b
601 601 $ cd ..
602 602 $ rm -rf repo
603 603
604 604 Test full copytrace ability on draft branch
605 605 -------------------------------------------
606 606
607 607 File directory and base name changed in same move
608 608 $ hg init repo
609 609 $ initclient repo
610 610 $ mkdir repo/dir1
611 611 $ cd repo/dir1
612 612 $ echo a > a
613 613 $ hg add a
614 614 $ hg ci -qm initial
615 615 $ cd ..
616 616 $ hg mv -q dir1 dir2
617 617 $ hg mv dir2/a dir2/b
618 618 $ hg ci -qm 'mv a b; mv dir1 dir2'
619 619 $ hg up -q '.^'
620 620 $ cd dir1
621 621 $ echo b >> a
622 622 $ cd ..
623 623 $ hg ci -qm 'mod a'
624 624
625 625 $ hg pl
626 626 @ rev: 2, phase: draft
627 627 | desc: mod a
628 628 | o rev: 1, phase: draft
629 629 |/ desc: mv a b; mv dir1 dir2
630 630 o rev: 0, phase: draft
631 631 desc: initial
632 632
633 633 $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
634 rebasing 2:6207d2d318e7 "mod a" (tip)
634 rebasing 2:6207d2d318e7 tip "mod a"
635 635 merging dir2/b and dir1/a to dir2/b
636 636 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/6207d2d318e7-1c9779ad-rebase.hg
637 637 $ cat dir2/b
638 638 a
639 639 b
640 640 $ cd ..
641 641 $ rm -rf repo
642 642
643 643 Move directory in one merge parent, while adding file to original directory
644 644 in other merge parent. File moved on rebase.
645 645
646 646 $ hg init repo
647 647 $ initclient repo
648 648 $ mkdir repo/dir1
649 649 $ cd repo/dir1
650 650 $ echo dummy > dummy
651 651 $ hg add dummy
652 652 $ cd ..
653 653 $ hg ci -qm initial
654 654 $ cd dir1
655 655 $ echo a > a
656 656 $ hg add a
657 657 $ cd ..
658 658 $ hg ci -qm 'hg add dir1/a'
659 659 $ hg up -q '.^'
660 660 $ hg mv -q dir1 dir2
661 661 $ hg ci -qm 'mv dir1 dir2'
662 662
663 663 $ hg pl
664 664 @ rev: 2, phase: draft
665 665 | desc: mv dir1 dir2
666 666 | o rev: 1, phase: draft
667 667 |/ desc: hg add dir1/a
668 668 o rev: 0, phase: draft
669 669 desc: initial
670 670
671 671 $ hg rebase -s . -d 1 --config experimental.copytrace.sourcecommitlimit=100
672 rebasing 2:e8919e7df8d0 "mv dir1 dir2" (tip)
672 rebasing 2:e8919e7df8d0 tip "mv dir1 dir2"
673 673 saved backup bundle to $TESTTMP/repo/repo/.hg/strip-backup/e8919e7df8d0-f62fab62-rebase.hg
674 674 $ ls dir2
675 675 a
676 676 dummy
677 677 $ rm -rf repo
678 678
679 679 Testing the sourcecommitlimit config
680 680 -----------------------------------
681 681
682 682 $ hg init repo
683 683 $ initclient repo
684 684 $ cd repo
685 685 $ echo a > a
686 686 $ hg ci -Aqm "added a"
687 687 $ echo "more things" >> a
688 688 $ hg ci -qm "added more things to a"
689 689 $ hg up 0
690 690 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
691 691 $ echo b > b
692 692 $ hg ci -Aqm "added b"
693 693 $ mkdir foo
694 694 $ hg mv a foo/bar
695 695 $ hg ci -m "Moved a to foo/bar"
696 696 $ hg pl
697 697 @ rev: 3, phase: draft
698 698 | desc: Moved a to foo/bar
699 699 o rev: 2, phase: draft
700 700 | desc: added b
701 701 | o rev: 1, phase: draft
702 702 |/ desc: added more things to a
703 703 o rev: 0, phase: draft
704 704 desc: added a
705 705
706 706 When the sourcecommitlimit is small and we have more drafts, we use heuristics only
707 707
708 708 $ hg rebase -s 1 -d .
709 709 rebasing 1:8b6e13696c38 "added more things to a"
710 710 file 'a' was deleted in local [dest] but was modified in other [source].
711 711 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
712 712 What do you want to do? u
713 713 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
714 714 [1]
715 715
716 716 But when we have "sourcecommitlimit > (no. of drafts from base to c1)", we do
717 717 fullcopytracing
718 718
719 719 $ hg rebase --abort
720 720 rebase aborted
721 721 $ hg rebase -s 1 -d . --config experimental.copytrace.sourcecommitlimit=100
722 722 rebasing 1:8b6e13696c38 "added more things to a"
723 723 merging foo/bar and a to foo/bar
724 724 saved backup bundle to $TESTTMP/repo/repo/repo/.hg/strip-backup/8b6e13696c38-fc14ac83-rebase.hg
725 725 $ cd ..
726 726 $ rm -rf repo
@@ -1,236 +1,236 b''
1 1 $ hg init repo
2 2 $ cd repo
3 3 $ echo a > a
4 4 $ hg add a
5 5 $ hg commit -m test
6 6
7 7 Do we ever miss a sub-second change?:
8 8
9 9 $ for i in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20; do
10 10 > hg co -qC 0
11 11 > echo b > a
12 12 > hg st
13 13 > done
14 14 M a
15 15 M a
16 16 M a
17 17 M a
18 18 M a
19 19 M a
20 20 M a
21 21 M a
22 22 M a
23 23 M a
24 24 M a
25 25 M a
26 26 M a
27 27 M a
28 28 M a
29 29 M a
30 30 M a
31 31 M a
32 32 M a
33 33 M a
34 34
35 35 $ echo test > b
36 36 $ mkdir dir1
37 37 $ echo test > dir1/c
38 38 $ echo test > d
39 39
40 40 $ echo test > e
41 41 #if execbit
42 42 A directory will typically have the execute bit -- make sure it doesn't get
43 43 confused with a file with the exec bit set
44 44 $ chmod +x e
45 45 #endif
46 46
47 47 $ hg add b dir1 d e
48 48 adding dir1/c
49 49 $ hg commit -m test2
50 50
51 51 $ cat >> $TESTTMP/dirstaterace.py << EOF
52 52 > from mercurial import (
53 53 > context,
54 54 > extensions,
55 55 > )
56 56 > def extsetup(ui):
57 57 > extensions.wrapfunction(context.workingctx, '_checklookup', overridechecklookup)
58 58 > def overridechecklookup(orig, self, files):
59 59 > # make an update that changes the dirstate from underneath
60 60 > self._repo.ui.system(br"sh '$TESTTMP/dirstaterace.sh'",
61 61 > cwd=self._repo.root)
62 62 > return orig(self, files)
63 63 > EOF
64 64
65 65 $ hg debugrebuilddirstate
66 66 $ hg debugdirstate
67 67 n 0 -1 unset a
68 68 n 0 -1 unset b
69 69 n 0 -1 unset d
70 70 n 0 -1 unset dir1/c
71 71 n 0 -1 unset e
72 72
73 73 XXX Note that this returns M for files that got replaced by directories. This is
74 74 definitely a bug, but the fix for that is hard and the next status run is fine
75 75 anyway.
76 76
77 77 $ cat > $TESTTMP/dirstaterace.sh <<EOF
78 78 > rm b && rm -r dir1 && rm d && mkdir d && rm e && mkdir e
79 79 > EOF
80 80
81 81 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py
82 82 M d
83 83 M e
84 84 ! b
85 85 ! dir1/c
86 86 $ hg debugdirstate
87 87 n 644 2 * a (glob)
88 88 n 0 -1 unset b
89 89 n 0 -1 unset d
90 90 n 0 -1 unset dir1/c
91 91 n 0 -1 unset e
92 92
93 93 $ hg status
94 94 ! b
95 95 ! d
96 96 ! dir1/c
97 97 ! e
98 98
99 99 $ rmdir d e
100 100 $ hg update -C -q .
101 101
102 102 Test that dirstate changes aren't written out at the end of "hg
103 103 status", if .hg/dirstate is already changed simultaneously before
104 104 acquisition of wlock in workingctx._poststatusfixup().
105 105
106 106 This avoidance is important to keep consistency of dirstate in race
107 107 condition (see issue5584 for detail).
108 108
109 109 $ hg parents -q
110 110 1:* (glob)
111 111
112 112 $ hg debugrebuilddirstate
113 113 $ hg debugdirstate
114 114 n 0 -1 unset a
115 115 n 0 -1 unset b
116 116 n 0 -1 unset d
117 117 n 0 -1 unset dir1/c
118 118 n 0 -1 unset e
119 119
120 120 $ cat > $TESTTMP/dirstaterace.sh <<EOF
121 121 > # This script assumes timetable of typical issue5584 case below:
122 122 > #
123 123 > # 1. "hg status" loads .hg/dirstate
124 124 > # 2. "hg status" confirms clean-ness of FILE
125 125 > # 3. "hg update -C 0" updates the working directory simultaneously
126 126 > # (FILE is removed, and FILE is dropped from .hg/dirstate)
127 127 > # 4. "hg status" acquires wlock
128 128 > # (.hg/dirstate is re-loaded = no FILE entry in dirstate)
129 129 > # 5. "hg status" marks FILE in dirstate as clean
130 130 > # (FILE entry is added to in-memory dirstate)
131 131 > # 6. "hg status" writes dirstate changes into .hg/dirstate
132 132 > # (FILE entry is written into .hg/dirstate)
133 133 > #
134 134 > # To reproduce similar situation easily and certainly, #2 and #3
135 135 > # are swapped. "hg cat" below ensures #2 on "hg status" side.
136 136 >
137 137 > hg update -q -C 0
138 138 > hg cat -r 1 b > b
139 139 > EOF
140 140
141 141 "hg status" below should excludes "e", of which exec flag is set, for
142 142 portability of test scenario, because unsure but missing "e" is
143 143 treated differently in _checklookup() according to runtime platform.
144 144
145 145 - "missing(!)" on POSIX, "pctx[f].cmp(self[f])" raises ENOENT
146 146 - "modified(M)" on Windows, "self.flags(f) != pctx.flags(f)" is True
147 147
148 148 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug -X path:e
149 149 skip updating dirstate: identity mismatch
150 150 M a
151 151 ! d
152 152 ! dir1/c
153 153
154 154 $ hg parents -q
155 155 0:* (glob)
156 156 $ hg files
157 157 a
158 158 $ hg debugdirstate
159 159 n * * * a (glob)
160 160
161 161 $ rm b
162 162
163 163 #if fsmonitor
164 164
165 165 Create fsmonitor state.
166 166
167 167 $ hg status
168 168 $ f --type .hg/fsmonitor.state
169 169 .hg/fsmonitor.state: file
170 170
171 171 Test that invalidating fsmonitor state in the middle (which doesn't require the
172 172 wlock) causes the fsmonitor update to be skipped.
173 173 hg debugrebuilddirstate ensures that the dirstaterace hook will be called, but
174 174 it also invalidates the fsmonitor state. So back it up and restore it.
175 175
176 176 $ mv .hg/fsmonitor.state .hg/fsmonitor.state.tmp
177 177 $ hg debugrebuilddirstate
178 178 $ mv .hg/fsmonitor.state.tmp .hg/fsmonitor.state
179 179
180 180 $ cat > $TESTTMP/dirstaterace.sh <<EOF
181 181 > rm .hg/fsmonitor.state
182 182 > EOF
183 183
184 184 $ hg status --config extensions.dirstaterace=$TESTTMP/dirstaterace.py --debug
185 185 skip updating fsmonitor.state: identity mismatch
186 186 $ f .hg/fsmonitor.state
187 187 .hg/fsmonitor.state: file not found
188 188
189 189 #endif
190 190
191 191 Set up a rebase situation for issue5581.
192 192
193 193 $ echo c2 > a
194 194 $ echo c2 > b
195 195 $ hg add b
196 196 $ hg commit -m c2
197 197 created new head
198 198 $ echo c3 >> a
199 199 $ hg commit -m c3
200 200 $ hg update 2
201 201 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
202 202 $ echo c4 >> a
203 203 $ echo c4 >> b
204 204 $ hg commit -m c4
205 205 created new head
206 206
207 207 Configure a merge tool that runs status in the middle of the rebase. The goal of
208 208 the status call is to trigger a potential bug if fsmonitor's state is written
209 209 even though the wlock is held by another process. The output of 'hg status' in
210 210 the merge tool goes to /dev/null because we're more interested in the results of
211 211 'hg status' run after the rebase.
212 212
213 213 $ cat >> $TESTTMP/mergetool-race.sh << EOF
214 214 > echo "custom merge tool"
215 215 > printf "c2\nc3\nc4\n" > \$1
216 216 > hg --cwd "$TESTTMP/repo" status > /dev/null
217 217 > echo "custom merge tool end"
218 218 > EOF
219 219 $ cat >> $HGRCPATH << EOF
220 220 > [extensions]
221 221 > rebase =
222 222 > [merge-tools]
223 223 > test.executable=sh
224 224 > test.args=$TESTTMP/mergetool-race.sh \$output
225 225 > EOF
226 226
227 227 $ hg rebase -s . -d 3 --tool test
228 rebasing 4:b08445fd6b2a "c4" (tip)
228 rebasing 4:b08445fd6b2a tip "c4"
229 229 merging a
230 230 custom merge tool
231 231 custom merge tool end
232 232 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/* (glob)
233 233
234 234 This hg status should be empty, whether or not fsmonitor is enabled (issue5581).
235 235
236 236 $ hg status
@@ -1,1693 +1,1693 b''
1 1 A script that implements uppercasing of specific lines in a file. This
2 2 approximates the behavior of code formatters well enough for our tests.
3 3
4 4 $ UPPERCASEPY="$TESTTMP/uppercase.py"
5 5 $ cat > $UPPERCASEPY <<EOF
6 6 > import sys
7 7 > from mercurial.utils.procutil import setbinary
8 8 > setbinary(sys.stdin)
9 9 > setbinary(sys.stdout)
10 10 > lines = set()
11 11 > for arg in sys.argv[1:]:
12 12 > if arg == 'all':
13 13 > sys.stdout.write(sys.stdin.read().upper())
14 14 > sys.exit(0)
15 15 > else:
16 16 > first, last = arg.split('-')
17 17 > lines.update(range(int(first), int(last) + 1))
18 18 > for i, line in enumerate(sys.stdin.readlines()):
19 19 > if i + 1 in lines:
20 20 > sys.stdout.write(line.upper())
21 21 > else:
22 22 > sys.stdout.write(line)
23 23 > EOF
24 24 $ TESTLINES="foo\nbar\nbaz\nqux\n"
25 25 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY
26 26 foo
27 27 bar
28 28 baz
29 29 qux
30 30 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY all
31 31 FOO
32 32 BAR
33 33 BAZ
34 34 QUX
35 35 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-1
36 36 FOO
37 37 bar
38 38 baz
39 39 qux
40 40 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 1-2
41 41 FOO
42 42 BAR
43 43 baz
44 44 qux
45 45 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-3
46 46 foo
47 47 BAR
48 48 BAZ
49 49 qux
50 50 $ printf $TESTLINES | "$PYTHON" $UPPERCASEPY 2-2 4-4
51 51 foo
52 52 BAR
53 53 baz
54 54 QUX
55 55
56 56 Set up the config with two simple fixers: one that fixes specific line ranges,
57 57 and one that always fixes the whole file. They both "fix" files by converting
58 58 letters to uppercase. They use different file extensions, so each test case can
59 59 choose which behavior to use by naming files.
60 60
61 61 $ cat >> $HGRCPATH <<EOF
62 62 > [extensions]
63 63 > fix =
64 64 > [experimental]
65 65 > evolution.createmarkers=True
66 66 > evolution.allowunstable=True
67 67 > [fix]
68 68 > uppercase-whole-file:command="$PYTHON" $UPPERCASEPY all
69 69 > uppercase-whole-file:pattern=set:**.whole
70 70 > uppercase-changed-lines:command="$PYTHON" $UPPERCASEPY
71 71 > uppercase-changed-lines:linerange={first}-{last}
72 72 > uppercase-changed-lines:pattern=set:**.changed
73 73 > EOF
74 74
75 75 Help text for fix.
76 76
77 77 $ hg help fix
78 78 hg fix [OPTION]... [FILE]...
79 79
80 80 rewrite file content in changesets or working directory
81 81
82 82 Runs any configured tools to fix the content of files. Only affects files
83 83 with changes, unless file arguments are provided. Only affects changed
84 84 lines of files, unless the --whole flag is used. Some tools may always
85 85 affect the whole file regardless of --whole.
86 86
87 87 If --working-dir is used, files with uncommitted changes in the working
88 88 copy will be fixed. Note that no backup are made.
89 89
90 90 If revisions are specified with --source, those revisions and their
91 91 descendants will be checked, and they may be replaced with new revisions
92 92 that have fixed file content. By automatically including the descendants,
93 93 no merging, rebasing, or evolution will be required. If an ancestor of the
94 94 working copy is included, then the working copy itself will also be fixed,
95 95 and the working copy will be updated to the fixed parent.
96 96
97 97 When determining what lines of each file to fix at each revision, the
98 98 whole set of revisions being fixed is considered, so that fixes to earlier
99 99 revisions are not forgotten in later ones. The --base flag can be used to
100 100 override this default behavior, though it is not usually desirable to do
101 101 so.
102 102
103 103 (use 'hg help -e fix' to show help for the fix extension)
104 104
105 105 options ([+] can be repeated):
106 106
107 107 --all fix all non-public non-obsolete revisions
108 108 --base REV [+] revisions to diff against (overrides automatic selection,
109 109 and applies to every revision being fixed)
110 110 -s --source REV [+] fix the specified revisions and their descendants
111 111 -w --working-dir fix the working directory
112 112 --whole always fix every line of a file
113 113
114 114 (some details hidden, use --verbose to show complete help)
115 115
116 116 $ hg help -e fix
117 117 fix extension - rewrite file content in changesets or working copy
118 118 (EXPERIMENTAL)
119 119
120 120 Provides a command that runs configured tools on the contents of modified
121 121 files, writing back any fixes to the working copy or replacing changesets.
122 122
123 123 Here is an example configuration that causes 'hg fix' to apply automatic
124 124 formatting fixes to modified lines in C++ code:
125 125
126 126 [fix]
127 127 clang-format:command=clang-format --assume-filename={rootpath}
128 128 clang-format:linerange=--lines={first}:{last}
129 129 clang-format:pattern=set:**.cpp or **.hpp
130 130
131 131 The :command suboption forms the first part of the shell command that will be
132 132 used to fix a file. The content of the file is passed on standard input, and
133 133 the fixed file content is expected on standard output. Any output on standard
134 134 error will be displayed as a warning. If the exit status is not zero, the file
135 135 will not be affected. A placeholder warning is displayed if there is a non-
136 136 zero exit status but no standard error output. Some values may be substituted
137 137 into the command:
138 138
139 139 {rootpath} The path of the file being fixed, relative to the repo root
140 140 {basename} The name of the file being fixed, without the directory path
141 141
142 142 If the :linerange suboption is set, the tool will only be run if there are
143 143 changed lines in a file. The value of this suboption is appended to the shell
144 144 command once for every range of changed lines in the file. Some values may be
145 145 substituted into the command:
146 146
147 147 {first} The 1-based line number of the first line in the modified range
148 148 {last} The 1-based line number of the last line in the modified range
149 149
150 150 Deleted sections of a file will be ignored by :linerange, because there is no
151 151 corresponding line range in the version being fixed.
152 152
153 153 By default, tools that set :linerange will only be executed if there is at
154 154 least one changed line range. This is meant to prevent accidents like running
155 155 a code formatter in such a way that it unexpectedly reformats the whole file.
156 156 If such a tool needs to operate on unchanged files, it should set the
157 157 :skipclean suboption to false.
158 158
159 159 The :pattern suboption determines which files will be passed through each
160 160 configured tool. See 'hg help patterns' for possible values. However, all
161 161 patterns are relative to the repo root, even if that text says they are
162 162 relative to the current working directory. If there are file arguments to 'hg
163 163 fix', the intersection of these patterns is used.
164 164
165 165 There is also a configurable limit for the maximum size of file that will be
166 166 processed by 'hg fix':
167 167
168 168 [fix]
169 169 maxfilesize = 2MB
170 170
171 171 Normally, execution of configured tools will continue after a failure
172 172 (indicated by a non-zero exit status). It can also be configured to abort
173 173 after the first such failure, so that no files will be affected if any tool
174 174 fails. This abort will also cause 'hg fix' to exit with a non-zero status:
175 175
176 176 [fix]
177 177 failure = abort
178 178
179 179 When multiple tools are configured to affect a file, they execute in an order
180 180 defined by the :priority suboption. The priority suboption has a default value
181 181 of zero for each tool. Tools are executed in order of descending priority. The
182 182 execution order of tools with equal priority is unspecified. For example, you
183 183 could use the 'sort' and 'head' utilities to keep only the 10 smallest numbers
184 184 in a text file by ensuring that 'sort' runs before 'head':
185 185
186 186 [fix]
187 187 sort:command = sort -n
188 188 head:command = head -n 10
189 189 sort:pattern = numbers.txt
190 190 head:pattern = numbers.txt
191 191 sort:priority = 2
192 192 head:priority = 1
193 193
194 194 To account for changes made by each tool, the line numbers used for
195 195 incremental formatting are recomputed before executing the next tool. So, each
196 196 tool may see different values for the arguments added by the :linerange
197 197 suboption.
198 198
199 199 Each fixer tool is allowed to return some metadata in addition to the fixed
200 200 file content. The metadata must be placed before the file content on stdout,
201 201 separated from the file content by a zero byte. The metadata is parsed as a
202 202 JSON value (so, it should be UTF-8 encoded and contain no zero bytes). A fixer
203 203 tool is expected to produce this metadata encoding if and only if the
204 204 :metadata suboption is true:
205 205
206 206 [fix]
207 207 tool:command = tool --prepend-json-metadata
208 208 tool:metadata = true
209 209
210 210 The metadata values are passed to hooks, which can be used to print summaries
211 211 or perform other post-fixing work. The supported hooks are:
212 212
213 213 "postfixfile"
214 214 Run once for each file in each revision where any fixer tools made changes
215 215 to the file content. Provides "$HG_REV" and "$HG_PATH" to identify the file,
216 216 and "$HG_METADATA" with a map of fixer names to metadata values from fixer
217 217 tools that affected the file. Fixer tools that didn't affect the file have a
218 218 value of None. Only fixer tools that executed are present in the metadata.
219 219
220 220 "postfix"
221 221 Run once after all files and revisions have been handled. Provides
222 222 "$HG_REPLACEMENTS" with information about what revisions were created and
223 223 made obsolete. Provides a boolean "$HG_WDIRWRITTEN" to indicate whether any
224 224 files in the working copy were updated. Provides a list "$HG_METADATA"
225 225 mapping fixer tool names to lists of metadata values returned from
226 226 executions that modified a file. This aggregates the same metadata
227 227 previously passed to the "postfixfile" hook.
228 228
229 229 Fixer tools are run in the repository's root directory. This allows them to
230 230 read configuration files from the working copy, or even write to the working
231 231 copy. The working copy is not updated to match the revision being fixed. In
232 232 fact, several revisions may be fixed in parallel. Writes to the working copy
233 233 are not amended into the revision being fixed; fixer tools should always write
234 234 fixed file content back to stdout as documented above.
235 235
236 236 list of commands:
237 237
238 238 fix rewrite file content in changesets or working directory
239 239
240 240 (use 'hg help -v -e fix' to show built-in aliases and global options)
241 241
242 242 There is no default behavior in the absence of --rev and --working-dir.
243 243
244 244 $ hg init badusage
245 245 $ cd badusage
246 246
247 247 $ hg fix
248 248 abort: no changesets specified
249 249 (use --rev or --working-dir)
250 250 [255]
251 251 $ hg fix --whole
252 252 abort: no changesets specified
253 253 (use --rev or --working-dir)
254 254 [255]
255 255 $ hg fix --base 0
256 256 abort: no changesets specified
257 257 (use --rev or --working-dir)
258 258 [255]
259 259
260 260 Fixing a public revision isn't allowed. It should abort early enough that
261 261 nothing happens, even to the working directory.
262 262
263 263 $ printf "hello\n" > hello.whole
264 264 $ hg commit -Aqm "hello"
265 265 $ hg phase -r 0 --public
266 266 $ hg fix -r 0
267 267 abort: cannot fix public changesets
268 268 (see 'hg help phases' for details)
269 269 [255]
270 270 $ hg fix -r 0 --working-dir
271 271 abort: cannot fix public changesets
272 272 (see 'hg help phases' for details)
273 273 [255]
274 274 $ hg cat -r tip hello.whole
275 275 hello
276 276 $ cat hello.whole
277 277 hello
278 278
279 279 $ cd ..
280 280
281 281 Fixing a clean working directory should do nothing. Even the --whole flag
282 282 shouldn't cause any clean files to be fixed. Specifying a clean file explicitly
283 283 should only fix it if the fixer always fixes the whole file. The combination of
284 284 an explicit filename and --whole should format the entire file regardless.
285 285
286 286 $ hg init fixcleanwdir
287 287 $ cd fixcleanwdir
288 288
289 289 $ printf "hello\n" > hello.changed
290 290 $ printf "world\n" > hello.whole
291 291 $ hg commit -Aqm "foo"
292 292 $ hg fix --working-dir
293 293 $ hg diff
294 294 $ hg fix --working-dir --whole
295 295 $ hg diff
296 296 $ hg fix --working-dir *
297 297 $ cat *
298 298 hello
299 299 WORLD
300 300 $ hg revert --all --no-backup
301 301 reverting hello.whole
302 302 $ hg fix --working-dir * --whole
303 303 $ cat *
304 304 HELLO
305 305 WORLD
306 306
307 307 The same ideas apply to fixing a revision, so we create a revision that doesn't
308 308 modify either of the files in question and try fixing it. This also tests that
309 309 we ignore a file that doesn't match any configured fixer.
310 310
311 311 $ hg revert --all --no-backup
312 312 reverting hello.changed
313 313 reverting hello.whole
314 314 $ printf "unimportant\n" > some.file
315 315 $ hg commit -Aqm "some other file"
316 316
317 317 $ hg fix -r .
318 318 $ hg cat -r tip *
319 319 hello
320 320 world
321 321 unimportant
322 322 $ hg fix -r . --whole
323 323 $ hg cat -r tip *
324 324 hello
325 325 world
326 326 unimportant
327 327 $ hg fix -r . *
328 328 $ hg cat -r tip *
329 329 hello
330 330 WORLD
331 331 unimportant
332 332 $ hg fix -r . * --whole --config experimental.evolution.allowdivergence=true
333 333 2 new content-divergent changesets
334 334 $ hg cat -r tip *
335 335 HELLO
336 336 WORLD
337 337 unimportant
338 338
339 339 $ cd ..
340 340
341 341 Fixing the working directory should still work if there are no revisions.
342 342
343 343 $ hg init norevisions
344 344 $ cd norevisions
345 345
346 346 $ printf "something\n" > something.whole
347 347 $ hg add
348 348 adding something.whole
349 349 $ hg fix --working-dir
350 350 $ cat something.whole
351 351 SOMETHING
352 352
353 353 $ cd ..
354 354
355 355 Test the effect of fixing the working directory for each possible status, with
356 356 and without providing explicit file arguments.
357 357
358 358 $ hg init implicitlyfixstatus
359 359 $ cd implicitlyfixstatus
360 360
361 361 $ printf "modified\n" > modified.whole
362 362 $ printf "removed\n" > removed.whole
363 363 $ printf "deleted\n" > deleted.whole
364 364 $ printf "clean\n" > clean.whole
365 365 $ printf "ignored.whole" > .hgignore
366 366 $ hg commit -Aqm "stuff"
367 367
368 368 $ printf "modified!!!\n" > modified.whole
369 369 $ printf "unknown\n" > unknown.whole
370 370 $ printf "ignored\n" > ignored.whole
371 371 $ printf "added\n" > added.whole
372 372 $ hg add added.whole
373 373 $ hg remove removed.whole
374 374 $ rm deleted.whole
375 375
376 376 $ hg status --all
377 377 M modified.whole
378 378 A added.whole
379 379 R removed.whole
380 380 ! deleted.whole
381 381 ? unknown.whole
382 382 I ignored.whole
383 383 C .hgignore
384 384 C clean.whole
385 385
386 386 $ hg fix --working-dir
387 387
388 388 $ hg status --all
389 389 M modified.whole
390 390 A added.whole
391 391 R removed.whole
392 392 ! deleted.whole
393 393 ? unknown.whole
394 394 I ignored.whole
395 395 C .hgignore
396 396 C clean.whole
397 397
398 398 $ cat *.whole
399 399 ADDED
400 400 clean
401 401 ignored
402 402 MODIFIED!!!
403 403 unknown
404 404
405 405 $ printf "modified!!!\n" > modified.whole
406 406 $ printf "added\n" > added.whole
407 407
408 408 Listing the files explicitly causes untracked files to also be fixed, but
409 409 ignored files are still unaffected.
410 410
411 411 $ hg fix --working-dir *.whole
412 412
413 413 $ hg status --all
414 414 M clean.whole
415 415 M modified.whole
416 416 A added.whole
417 417 R removed.whole
418 418 ! deleted.whole
419 419 ? unknown.whole
420 420 I ignored.whole
421 421 C .hgignore
422 422
423 423 $ cat *.whole
424 424 ADDED
425 425 CLEAN
426 426 ignored
427 427 MODIFIED!!!
428 428 UNKNOWN
429 429
430 430 $ cd ..
431 431
432 432 Test that incremental fixing works on files with additions, deletions, and
433 433 changes in multiple line ranges. Note that deletions do not generally cause
434 434 neighboring lines to be fixed, so we don't return a line range for purely
435 435 deleted sections. In the future we should support a :deletion config that
436 436 allows fixers to know where deletions are located.
437 437
438 438 $ hg init incrementalfixedlines
439 439 $ cd incrementalfixedlines
440 440
441 441 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.txt
442 442 $ hg commit -Aqm "foo"
443 443 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.txt
444 444
445 445 $ hg --config "fix.fail:command=echo" \
446 446 > --config "fix.fail:linerange={first}:{last}" \
447 447 > --config "fix.fail:pattern=foo.txt" \
448 448 > fix --working-dir
449 449 $ cat foo.txt
450 450 1:1 4:6 8:8
451 451
452 452 $ cd ..
453 453
454 454 Test that --whole fixes all lines regardless of the diffs present.
455 455
456 456 $ hg init wholeignoresdiffs
457 457 $ cd wholeignoresdiffs
458 458
459 459 $ printf "a\nb\nc\nd\ne\nf\ng\n" > foo.changed
460 460 $ hg commit -Aqm "foo"
461 461 $ printf "zz\na\nc\ndd\nee\nff\nf\ngg\n" > foo.changed
462 462
463 463 $ hg fix --working-dir
464 464 $ cat foo.changed
465 465 ZZ
466 466 a
467 467 c
468 468 DD
469 469 EE
470 470 FF
471 471 f
472 472 GG
473 473
474 474 $ hg fix --working-dir --whole
475 475 $ cat foo.changed
476 476 ZZ
477 477 A
478 478 C
479 479 DD
480 480 EE
481 481 FF
482 482 F
483 483 GG
484 484
485 485 $ cd ..
486 486
487 487 We should do nothing with symlinks, and their targets should be unaffected. Any
488 488 other behavior would be more complicated to implement and harder to document.
489 489
490 490 #if symlink
491 491 $ hg init dontmesswithsymlinks
492 492 $ cd dontmesswithsymlinks
493 493
494 494 $ printf "hello\n" > hello.whole
495 495 $ ln -s hello.whole hellolink
496 496 $ hg add
497 497 adding hello.whole
498 498 adding hellolink
499 499 $ hg fix --working-dir hellolink
500 500 $ hg status
501 501 A hello.whole
502 502 A hellolink
503 503
504 504 $ cd ..
505 505 #endif
506 506
507 507 We should allow fixers to run on binary files, even though this doesn't sound
508 508 like a common use case. There's not much benefit to disallowing it, and users
509 509 can add "and not binary()" to their filesets if needed. The Mercurial
510 510 philosophy is generally to not handle binary files specially anyway.
511 511
512 512 $ hg init cantouchbinaryfiles
513 513 $ cd cantouchbinaryfiles
514 514
515 515 $ printf "hello\0\n" > hello.whole
516 516 $ hg add
517 517 adding hello.whole
518 518 $ hg fix --working-dir 'set:binary()'
519 519 $ cat hello.whole
520 520 HELLO\x00 (esc)
521 521
522 522 $ cd ..
523 523
524 524 We have a config for the maximum size of file we will attempt to fix. This can
525 525 be helpful to avoid running unsuspecting fixer tools on huge inputs, which
526 526 could happen by accident without a well considered configuration. A more
527 527 precise configuration could use the size() fileset function if one global limit
528 528 is undesired.
529 529
530 530 $ hg init maxfilesize
531 531 $ cd maxfilesize
532 532
533 533 $ printf "this file is huge\n" > hello.whole
534 534 $ hg add
535 535 adding hello.whole
536 536 $ hg --config fix.maxfilesize=10 fix --working-dir
537 537 ignoring file larger than 10 bytes: hello.whole
538 538 $ cat hello.whole
539 539 this file is huge
540 540
541 541 $ cd ..
542 542
543 543 If we specify a file to fix, other files should be left alone, even if they
544 544 have changes.
545 545
546 546 $ hg init fixonlywhatitellyouto
547 547 $ cd fixonlywhatitellyouto
548 548
549 549 $ printf "fix me!\n" > fixme.whole
550 550 $ printf "not me.\n" > notme.whole
551 551 $ hg add
552 552 adding fixme.whole
553 553 adding notme.whole
554 554 $ hg fix --working-dir fixme.whole
555 555 $ cat *.whole
556 556 FIX ME!
557 557 not me.
558 558
559 559 $ cd ..
560 560
561 561 If we try to fix a missing file, we still fix other files.
562 562
563 563 $ hg init fixmissingfile
564 564 $ cd fixmissingfile
565 565
566 566 $ printf "fix me!\n" > foo.whole
567 567 $ hg add
568 568 adding foo.whole
569 569 $ hg fix --working-dir foo.whole bar.whole
570 570 bar.whole: $ENOENT$
571 571 $ cat *.whole
572 572 FIX ME!
573 573
574 574 $ cd ..
575 575
576 576 Specifying a directory name should fix all its files and subdirectories.
577 577
578 578 $ hg init fixdirectory
579 579 $ cd fixdirectory
580 580
581 581 $ mkdir -p dir1/dir2
582 582 $ printf "foo\n" > foo.whole
583 583 $ printf "bar\n" > dir1/bar.whole
584 584 $ printf "baz\n" > dir1/dir2/baz.whole
585 585 $ hg add
586 586 adding dir1/bar.whole
587 587 adding dir1/dir2/baz.whole
588 588 adding foo.whole
589 589 $ hg fix --working-dir dir1
590 590 $ cat foo.whole dir1/bar.whole dir1/dir2/baz.whole
591 591 foo
592 592 BAR
593 593 BAZ
594 594
595 595 $ cd ..
596 596
597 597 Fixing a file in the working directory that needs no fixes should not actually
598 598 write back to the file, so for example the mtime shouldn't change.
599 599
600 600 $ hg init donttouchunfixedfiles
601 601 $ cd donttouchunfixedfiles
602 602
603 603 $ printf "NO FIX NEEDED\n" > foo.whole
604 604 $ hg add
605 605 adding foo.whole
606 606 $ cp -p foo.whole foo.whole.orig
607 607 $ cp -p foo.whole.orig foo.whole
608 608 $ sleep 2 # mtime has a resolution of one or two seconds.
609 609 $ hg fix --working-dir
610 610 $ f foo.whole.orig --newer foo.whole
611 611 foo.whole.orig: newer than foo.whole
612 612
613 613 $ cd ..
614 614
615 615 When a fixer prints to stderr, we don't assume that it has failed. We show the
616 616 error messages to the user, and we still let the fixer affect the file it was
617 617 fixing if its exit code is zero. Some code formatters might emit error messages
618 618 on stderr and nothing on stdout, which would cause us the clear the file,
619 619 except that they also exit with a non-zero code. We show the user which fixer
620 620 emitted the stderr, and which revision, but we assume that the fixer will print
621 621 the filename if it is relevant (since the issue may be non-specific). There is
622 622 also a config to abort (without affecting any files whatsoever) if we see any
623 623 tool with a non-zero exit status.
624 624
625 625 $ hg init showstderr
626 626 $ cd showstderr
627 627
628 628 $ printf "hello\n" > hello.txt
629 629 $ hg add
630 630 adding hello.txt
631 631 $ cat > $TESTTMP/work.sh <<'EOF'
632 632 > printf 'HELLO\n'
633 633 > printf "$@: some\nerror that didn't stop the tool" >&2
634 634 > exit 0 # success despite the stderr output
635 635 > EOF
636 636 $ hg --config "fix.work:command=sh $TESTTMP/work.sh {rootpath}" \
637 637 > --config "fix.work:pattern=hello.txt" \
638 638 > fix --working-dir
639 639 [wdir] work: hello.txt: some
640 640 [wdir] work: error that didn't stop the tool
641 641 $ cat hello.txt
642 642 HELLO
643 643
644 644 $ printf "goodbye\n" > hello.txt
645 645 $ printf "foo\n" > foo.whole
646 646 $ hg add
647 647 adding foo.whole
648 648 $ cat > $TESTTMP/fail.sh <<'EOF'
649 649 > printf 'GOODBYE\n'
650 650 > printf "$@: some\nerror that did stop the tool\n" >&2
651 651 > exit 42 # success despite the stdout output
652 652 > EOF
653 653 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
654 654 > --config "fix.fail:pattern=hello.txt" \
655 655 > --config "fix.failure=abort" \
656 656 > fix --working-dir
657 657 [wdir] fail: hello.txt: some
658 658 [wdir] fail: error that did stop the tool
659 659 abort: no fixes will be applied
660 660 (use --config fix.failure=continue to apply any successful fixes anyway)
661 661 [255]
662 662 $ cat hello.txt
663 663 goodbye
664 664 $ cat foo.whole
665 665 foo
666 666
667 667 $ hg --config "fix.fail:command=sh $TESTTMP/fail.sh {rootpath}" \
668 668 > --config "fix.fail:pattern=hello.txt" \
669 669 > fix --working-dir
670 670 [wdir] fail: hello.txt: some
671 671 [wdir] fail: error that did stop the tool
672 672 $ cat hello.txt
673 673 goodbye
674 674 $ cat foo.whole
675 675 FOO
676 676
677 677 $ hg --config "fix.fail:command=exit 42" \
678 678 > --config "fix.fail:pattern=hello.txt" \
679 679 > fix --working-dir
680 680 [wdir] fail: exited with status 42
681 681
682 682 $ cd ..
683 683
684 684 Fixing the working directory and its parent revision at the same time should
685 685 check out the replacement revision for the parent. This prevents any new
686 686 uncommitted changes from appearing. We test this for a clean working directory
687 687 and a dirty one. In both cases, all lines/files changed since the grandparent
688 688 will be fixed. The grandparent is the "baserev" for both the parent and the
689 689 working copy.
690 690
691 691 $ hg init fixdotandcleanwdir
692 692 $ cd fixdotandcleanwdir
693 693
694 694 $ printf "hello\n" > hello.whole
695 695 $ printf "world\n" > world.whole
696 696 $ hg commit -Aqm "the parent commit"
697 697
698 698 $ hg parents --template '{rev} {desc}\n'
699 699 0 the parent commit
700 700 $ hg fix --working-dir -r .
701 701 $ hg parents --template '{rev} {desc}\n'
702 702 1 the parent commit
703 703 $ hg cat -r . *.whole
704 704 HELLO
705 705 WORLD
706 706 $ cat *.whole
707 707 HELLO
708 708 WORLD
709 709 $ hg status
710 710
711 711 $ cd ..
712 712
713 713 Same test with a dirty working copy.
714 714
715 715 $ hg init fixdotanddirtywdir
716 716 $ cd fixdotanddirtywdir
717 717
718 718 $ printf "hello\n" > hello.whole
719 719 $ printf "world\n" > world.whole
720 720 $ hg commit -Aqm "the parent commit"
721 721
722 722 $ printf "hello,\n" > hello.whole
723 723 $ printf "world!\n" > world.whole
724 724
725 725 $ hg parents --template '{rev} {desc}\n'
726 726 0 the parent commit
727 727 $ hg fix --working-dir -r .
728 728 $ hg parents --template '{rev} {desc}\n'
729 729 1 the parent commit
730 730 $ hg cat -r . *.whole
731 731 HELLO
732 732 WORLD
733 733 $ cat *.whole
734 734 HELLO,
735 735 WORLD!
736 736 $ hg status
737 737 M hello.whole
738 738 M world.whole
739 739
740 740 $ cd ..
741 741
742 742 When we have a chain of commits that change mutually exclusive lines of code,
743 743 we should be able to do incremental fixing that causes each commit in the chain
744 744 to include fixes made to the previous commits. This prevents children from
745 745 backing out the fixes made in their parents. A dirty working directory is
746 746 conceptually similar to another commit in the chain.
747 747
748 748 $ hg init incrementallyfixchain
749 749 $ cd incrementallyfixchain
750 750
751 751 $ cat > file.changed <<EOF
752 752 > first
753 753 > second
754 754 > third
755 755 > fourth
756 756 > fifth
757 757 > EOF
758 758 $ hg commit -Aqm "the common ancestor (the baserev)"
759 759 $ cat > file.changed <<EOF
760 760 > first (changed)
761 761 > second
762 762 > third
763 763 > fourth
764 764 > fifth
765 765 > EOF
766 766 $ hg commit -Aqm "the first commit to fix"
767 767 $ cat > file.changed <<EOF
768 768 > first (changed)
769 769 > second
770 770 > third (changed)
771 771 > fourth
772 772 > fifth
773 773 > EOF
774 774 $ hg commit -Aqm "the second commit to fix"
775 775 $ cat > file.changed <<EOF
776 776 > first (changed)
777 777 > second
778 778 > third (changed)
779 779 > fourth
780 780 > fifth (changed)
781 781 > EOF
782 782
783 783 $ hg fix -r . -r '.^' --working-dir
784 784
785 785 $ hg parents --template '{rev}\n'
786 786 4
787 787 $ hg cat -r '.^^' file.changed
788 788 first
789 789 second
790 790 third
791 791 fourth
792 792 fifth
793 793 $ hg cat -r '.^' file.changed
794 794 FIRST (CHANGED)
795 795 second
796 796 third
797 797 fourth
798 798 fifth
799 799 $ hg cat -r . file.changed
800 800 FIRST (CHANGED)
801 801 second
802 802 THIRD (CHANGED)
803 803 fourth
804 804 fifth
805 805 $ cat file.changed
806 806 FIRST (CHANGED)
807 807 second
808 808 THIRD (CHANGED)
809 809 fourth
810 810 FIFTH (CHANGED)
811 811
812 812 $ cd ..
813 813
814 814 If we incrementally fix a merge commit, we should fix any lines that changed
815 815 versus either parent. You could imagine only fixing the intersection or some
816 816 other subset, but this is necessary if either parent is being fixed. It
817 817 prevents us from forgetting fixes made in either parent.
818 818
819 819 $ hg init incrementallyfixmergecommit
820 820 $ cd incrementallyfixmergecommit
821 821
822 822 $ printf "a\nb\nc\n" > file.changed
823 823 $ hg commit -Aqm "ancestor"
824 824
825 825 $ printf "aa\nb\nc\n" > file.changed
826 826 $ hg commit -m "change a"
827 827
828 828 $ hg checkout '.^'
829 829 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
830 830 $ printf "a\nb\ncc\n" > file.changed
831 831 $ hg commit -m "change c"
832 832 created new head
833 833
834 834 $ hg merge
835 835 merging file.changed
836 836 0 files updated, 1 files merged, 0 files removed, 0 files unresolved
837 837 (branch merge, don't forget to commit)
838 838 $ hg commit -m "merge"
839 839 $ hg cat -r . file.changed
840 840 aa
841 841 b
842 842 cc
843 843
844 844 $ hg fix -r . --working-dir
845 845 $ hg cat -r . file.changed
846 846 AA
847 847 b
848 848 CC
849 849
850 850 $ cd ..
851 851
852 852 Abort fixing revisions if there is an unfinished operation. We don't want to
853 853 make things worse by editing files or stripping/obsoleting things. Also abort
854 854 fixing the working directory if there are unresolved merge conflicts.
855 855
856 856 $ hg init abortunresolved
857 857 $ cd abortunresolved
858 858
859 859 $ echo "foo1" > foo.whole
860 860 $ hg commit -Aqm "foo 1"
861 861
862 862 $ hg update null
863 863 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
864 864 $ echo "foo2" > foo.whole
865 865 $ hg commit -Aqm "foo 2"
866 866
867 867 $ hg --config extensions.rebase= rebase -r 1 -d 0
868 rebasing 1:c3b6dc0e177a "foo 2" (tip)
868 rebasing 1:c3b6dc0e177a tip "foo 2"
869 869 merging foo.whole
870 870 warning: conflicts while merging foo.whole! (edit, then use 'hg resolve --mark')
871 871 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
872 872 [1]
873 873
874 874 $ hg --config extensions.rebase= fix --working-dir
875 875 abort: unresolved conflicts
876 876 (use 'hg resolve')
877 877 [255]
878 878
879 879 $ hg --config extensions.rebase= fix -r .
880 880 abort: rebase in progress
881 881 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
882 882 [255]
883 883
884 884 $ cd ..
885 885
886 886 When fixing a file that was renamed, we should diff against the source of the
887 887 rename for incremental fixing and we should correctly reproduce the rename in
888 888 the replacement revision.
889 889
890 890 $ hg init fixrenamecommit
891 891 $ cd fixrenamecommit
892 892
893 893 $ printf "a\nb\nc\n" > source.changed
894 894 $ hg commit -Aqm "source revision"
895 895 $ hg move source.changed dest.changed
896 896 $ printf "a\nb\ncc\n" > dest.changed
897 897 $ hg commit -m "dest revision"
898 898
899 899 $ hg fix -r .
900 900 $ hg log -r tip --copies --template "{file_copies}\n"
901 901 dest.changed (source.changed)
902 902 $ hg cat -r tip dest.changed
903 903 a
904 904 b
905 905 CC
906 906
907 907 $ cd ..
908 908
909 909 When fixing revisions that remove files we must ensure that the replacement
910 910 actually removes the file, whereas it could accidentally leave it unchanged or
911 911 write an empty string to it.
912 912
913 913 $ hg init fixremovedfile
914 914 $ cd fixremovedfile
915 915
916 916 $ printf "foo\n" > foo.whole
917 917 $ printf "bar\n" > bar.whole
918 918 $ hg commit -Aqm "add files"
919 919 $ hg remove bar.whole
920 920 $ hg commit -m "remove file"
921 921 $ hg status --change .
922 922 R bar.whole
923 923 $ hg fix -r . foo.whole
924 924 $ hg status --change tip
925 925 M foo.whole
926 926 R bar.whole
927 927
928 928 $ cd ..
929 929
930 930 If fixing a revision finds no fixes to make, no replacement revision should be
931 931 created.
932 932
933 933 $ hg init nofixesneeded
934 934 $ cd nofixesneeded
935 935
936 936 $ printf "FOO\n" > foo.whole
937 937 $ hg commit -Aqm "add file"
938 938 $ hg log --template '{rev}\n'
939 939 0
940 940 $ hg fix -r .
941 941 $ hg log --template '{rev}\n'
942 942 0
943 943
944 944 $ cd ..
945 945
946 946 If fixing a commit reverts all the changes in the commit, we replace it with a
947 947 commit that changes no files.
948 948
949 949 $ hg init nochangesleft
950 950 $ cd nochangesleft
951 951
952 952 $ printf "FOO\n" > foo.whole
953 953 $ hg commit -Aqm "add file"
954 954 $ printf "foo\n" > foo.whole
955 955 $ hg commit -m "edit file"
956 956 $ hg status --change .
957 957 M foo.whole
958 958 $ hg fix -r .
959 959 $ hg status --change tip
960 960
961 961 $ cd ..
962 962
963 963 If we fix a parent and child revision together, the child revision must be
964 964 replaced if the parent is replaced, even if the diffs of the child needed no
965 965 fixes. However, we're free to not replace revisions that need no fixes and have
966 966 no ancestors that are replaced.
967 967
968 968 $ hg init mustreplacechild
969 969 $ cd mustreplacechild
970 970
971 971 $ printf "FOO\n" > foo.whole
972 972 $ hg commit -Aqm "add foo"
973 973 $ printf "foo\n" > foo.whole
974 974 $ hg commit -m "edit foo"
975 975 $ printf "BAR\n" > bar.whole
976 976 $ hg commit -Aqm "add bar"
977 977
978 978 $ hg log --graph --template '{rev} {files}'
979 979 @ 2 bar.whole
980 980 |
981 981 o 1 foo.whole
982 982 |
983 983 o 0 foo.whole
984 984
985 985 $ hg fix -r 0:2
986 986 $ hg log --graph --template '{rev} {files}'
987 987 o 4 bar.whole
988 988 |
989 989 o 3
990 990 |
991 991 | @ 2 bar.whole
992 992 | |
993 993 | x 1 foo.whole
994 994 |/
995 995 o 0 foo.whole
996 996
997 997
998 998 $ cd ..
999 999
1000 1000 It's also possible that the child needs absolutely no changes, but we still
1001 1001 need to replace it to update its parent. If we skipped replacing the child
1002 1002 because it had no file content changes, it would become an orphan for no good
1003 1003 reason.
1004 1004
1005 1005 $ hg init mustreplacechildevenifnop
1006 1006 $ cd mustreplacechildevenifnop
1007 1007
1008 1008 $ printf "Foo\n" > foo.whole
1009 1009 $ hg commit -Aqm "add a bad foo"
1010 1010 $ printf "FOO\n" > foo.whole
1011 1011 $ hg commit -m "add a good foo"
1012 1012 $ hg fix -r . -r '.^'
1013 1013 $ hg log --graph --template '{rev} {desc}'
1014 1014 o 3 add a good foo
1015 1015 |
1016 1016 o 2 add a bad foo
1017 1017
1018 1018 @ 1 add a good foo
1019 1019 |
1020 1020 x 0 add a bad foo
1021 1021
1022 1022
1023 1023 $ cd ..
1024 1024
1025 1025 Similar to the case above, the child revision may become empty as a result of
1026 1026 fixing its parent. We should still create an empty replacement child.
1027 1027 TODO: determine how this should interact with ui.allowemptycommit given that
1028 1028 the empty replacement could have children.
1029 1029
1030 1030 $ hg init mustreplacechildevenifempty
1031 1031 $ cd mustreplacechildevenifempty
1032 1032
1033 1033 $ printf "foo\n" > foo.whole
1034 1034 $ hg commit -Aqm "add foo"
1035 1035 $ printf "Foo\n" > foo.whole
1036 1036 $ hg commit -m "edit foo"
1037 1037 $ hg fix -r . -r '.^'
1038 1038 $ hg log --graph --template '{rev} {desc}\n' --stat
1039 1039 o 3 edit foo
1040 1040 |
1041 1041 o 2 add foo
1042 1042 foo.whole | 1 +
1043 1043 1 files changed, 1 insertions(+), 0 deletions(-)
1044 1044
1045 1045 @ 1 edit foo
1046 1046 | foo.whole | 2 +-
1047 1047 | 1 files changed, 1 insertions(+), 1 deletions(-)
1048 1048 |
1049 1049 x 0 add foo
1050 1050 foo.whole | 1 +
1051 1051 1 files changed, 1 insertions(+), 0 deletions(-)
1052 1052
1053 1053
1054 1054 $ cd ..
1055 1055
1056 1056 Fixing a secret commit should replace it with another secret commit.
1057 1057
1058 1058 $ hg init fixsecretcommit
1059 1059 $ cd fixsecretcommit
1060 1060
1061 1061 $ printf "foo\n" > foo.whole
1062 1062 $ hg commit -Aqm "add foo" --secret
1063 1063 $ hg fix -r .
1064 1064 $ hg log --template '{rev} {phase}\n'
1065 1065 1 secret
1066 1066 0 secret
1067 1067
1068 1068 $ cd ..
1069 1069
1070 1070 We should also preserve phase when fixing a draft commit while the user has
1071 1071 their default set to secret.
1072 1072
1073 1073 $ hg init respectphasesnewcommit
1074 1074 $ cd respectphasesnewcommit
1075 1075
1076 1076 $ printf "foo\n" > foo.whole
1077 1077 $ hg commit -Aqm "add foo"
1078 1078 $ hg --config phases.newcommit=secret fix -r .
1079 1079 $ hg log --template '{rev} {phase}\n'
1080 1080 1 draft
1081 1081 0 draft
1082 1082
1083 1083 $ cd ..
1084 1084
1085 1085 Debug output should show what fixer commands are being subprocessed, which is
1086 1086 useful for anyone trying to set up a new config.
1087 1087
1088 1088 $ hg init debugoutput
1089 1089 $ cd debugoutput
1090 1090
1091 1091 $ printf "foo\nbar\nbaz\n" > foo.changed
1092 1092 $ hg commit -Aqm "foo"
1093 1093 $ printf "Foo\nbar\nBaz\n" > foo.changed
1094 1094 $ hg --debug fix --working-dir
1095 1095 subprocess: * $TESTTMP/uppercase.py 1-1 3-3 (glob)
1096 1096
1097 1097 $ cd ..
1098 1098
1099 1099 Fixing an obsolete revision can cause divergence, so we abort unless the user
1100 1100 configures to allow it. This is not yet smart enough to know whether there is a
1101 1101 successor, but even then it is not likely intentional or idiomatic to fix an
1102 1102 obsolete revision.
1103 1103
1104 1104 $ hg init abortobsoleterev
1105 1105 $ cd abortobsoleterev
1106 1106
1107 1107 $ printf "foo\n" > foo.changed
1108 1108 $ hg commit -Aqm "foo"
1109 1109 $ hg debugobsolete `hg parents --template '{node}'`
1110 1110 1 new obsolescence markers
1111 1111 obsoleted 1 changesets
1112 1112 $ hg --hidden fix -r 0
1113 1113 abort: fixing obsolete revision could cause divergence
1114 1114 [255]
1115 1115
1116 1116 $ hg --hidden fix -r 0 --config experimental.evolution.allowdivergence=true
1117 1117 $ hg cat -r tip foo.changed
1118 1118 FOO
1119 1119
1120 1120 $ cd ..
1121 1121
1122 1122 Test all of the available substitution values for fixer commands.
1123 1123
1124 1124 $ hg init substitution
1125 1125 $ cd substitution
1126 1126
1127 1127 $ mkdir foo
1128 1128 $ printf "hello\ngoodbye\n" > foo/bar
1129 1129 $ hg add
1130 1130 adding foo/bar
1131 1131 $ hg --config "fix.fail:command=printf '%s\n' '{rootpath}' '{basename}'" \
1132 1132 > --config "fix.fail:linerange='{first}' '{last}'" \
1133 1133 > --config "fix.fail:pattern=foo/bar" \
1134 1134 > fix --working-dir
1135 1135 $ cat foo/bar
1136 1136 foo/bar
1137 1137 bar
1138 1138 1
1139 1139 2
1140 1140
1141 1141 $ cd ..
1142 1142
1143 1143 The --base flag should allow picking the revisions to diff against for changed
1144 1144 files and incremental line formatting.
1145 1145
1146 1146 $ hg init baseflag
1147 1147 $ cd baseflag
1148 1148
1149 1149 $ printf "one\ntwo\n" > foo.changed
1150 1150 $ printf "bar\n" > bar.changed
1151 1151 $ hg commit -Aqm "first"
1152 1152 $ printf "one\nTwo\n" > foo.changed
1153 1153 $ hg commit -m "second"
1154 1154 $ hg fix -w --base .
1155 1155 $ hg status
1156 1156 $ hg fix -w --base null
1157 1157 $ cat foo.changed
1158 1158 ONE
1159 1159 TWO
1160 1160 $ cat bar.changed
1161 1161 BAR
1162 1162
1163 1163 $ cd ..
1164 1164
1165 1165 If the user asks to fix the parent of another commit, they are asking to create
1166 1166 an orphan. We must respect experimental.evolution.allowunstable.
1167 1167
1168 1168 $ hg init allowunstable
1169 1169 $ cd allowunstable
1170 1170
1171 1171 $ printf "one\n" > foo.whole
1172 1172 $ hg commit -Aqm "first"
1173 1173 $ printf "two\n" > foo.whole
1174 1174 $ hg commit -m "second"
1175 1175 $ hg --config experimental.evolution.allowunstable=False fix -r '.^'
1176 1176 abort: cannot fix changeset with children
1177 1177 [255]
1178 1178 $ hg fix -r '.^'
1179 1179 1 new orphan changesets
1180 1180 $ hg cat -r 2 foo.whole
1181 1181 ONE
1182 1182
1183 1183 $ cd ..
1184 1184
1185 1185 The --base flag affects the set of files being fixed. So while the --whole flag
1186 1186 makes the base irrelevant for changed line ranges, it still changes the
1187 1187 meaning and effect of the command. In this example, no files or lines are fixed
1188 1188 until we specify the base, but then we do fix unchanged lines.
1189 1189
1190 1190 $ hg init basewhole
1191 1191 $ cd basewhole
1192 1192 $ printf "foo1\n" > foo.changed
1193 1193 $ hg commit -Aqm "first"
1194 1194 $ printf "foo2\n" >> foo.changed
1195 1195 $ printf "bar\n" > bar.changed
1196 1196 $ hg commit -Aqm "second"
1197 1197
1198 1198 $ hg fix --working-dir --whole
1199 1199 $ cat *.changed
1200 1200 bar
1201 1201 foo1
1202 1202 foo2
1203 1203
1204 1204 $ hg fix --working-dir --base 0 --whole
1205 1205 $ cat *.changed
1206 1206 BAR
1207 1207 FOO1
1208 1208 FOO2
1209 1209
1210 1210 $ cd ..
1211 1211
1212 1212 The execution order of tools can be controlled. This example doesn't work if
1213 1213 you sort after truncating, but the config defines the correct order while the
1214 1214 definitions are out of order (which might imply the incorrect order given the
1215 1215 implementation of fix). The goal is to use multiple tools to select the lowest
1216 1216 5 numbers in the file.
1217 1217
1218 1218 $ hg init priorityexample
1219 1219 $ cd priorityexample
1220 1220
1221 1221 $ cat >> .hg/hgrc <<EOF
1222 1222 > [fix]
1223 1223 > head:command = head -n 5
1224 1224 > head:pattern = numbers.txt
1225 1225 > head:priority = 1
1226 1226 > sort:command = sort -n
1227 1227 > sort:pattern = numbers.txt
1228 1228 > sort:priority = 2
1229 1229 > EOF
1230 1230
1231 1231 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1232 1232 $ hg add -q
1233 1233 $ hg fix -w
1234 1234 $ cat numbers.txt
1235 1235 0
1236 1236 1
1237 1237 2
1238 1238 3
1239 1239 4
1240 1240
1241 1241 And of course we should be able to break this by reversing the execution order.
1242 1242 Test negative priorities while we're at it.
1243 1243
1244 1244 $ cat >> .hg/hgrc <<EOF
1245 1245 > [fix]
1246 1246 > head:priority = -1
1247 1247 > sort:priority = -2
1248 1248 > EOF
1249 1249 $ printf "8\n2\n3\n6\n7\n4\n9\n5\n1\n0\n" > numbers.txt
1250 1250 $ hg fix -w
1251 1251 $ cat numbers.txt
1252 1252 2
1253 1253 3
1254 1254 6
1255 1255 7
1256 1256 8
1257 1257
1258 1258 $ cd ..
1259 1259
1260 1260 It's possible for repeated applications of a fixer tool to create cycles in the
1261 1261 generated content of a file. For example, two users with different versions of
1262 1262 a code formatter might fight over the formatting when they run hg fix. In the
1263 1263 absence of other changes, this means we could produce commits with the same
1264 1264 hash in subsequent runs of hg fix. This is a problem unless we support
1265 1265 obsolescence cycles well. We avoid this by adding an extra field to the
1266 1266 successor which forces it to have a new hash. That's why this test creates
1267 1267 three revisions instead of two.
1268 1268
1269 1269 $ hg init cyclictool
1270 1270 $ cd cyclictool
1271 1271
1272 1272 $ cat >> .hg/hgrc <<EOF
1273 1273 > [fix]
1274 1274 > swapletters:command = tr ab ba
1275 1275 > swapletters:pattern = foo
1276 1276 > EOF
1277 1277
1278 1278 $ echo ab > foo
1279 1279 $ hg commit -Aqm foo
1280 1280
1281 1281 $ hg fix -r 0
1282 1282 $ hg fix -r 1
1283 1283
1284 1284 $ hg cat -r 0 foo --hidden
1285 1285 ab
1286 1286 $ hg cat -r 1 foo --hidden
1287 1287 ba
1288 1288 $ hg cat -r 2 foo
1289 1289 ab
1290 1290
1291 1291 $ cd ..
1292 1292
1293 1293 We run fixer tools in the repo root so they can look for config files or other
1294 1294 important things in the working directory. This does NOT mean we are
1295 1295 reconstructing a working copy of every revision being fixed; we're just giving
1296 1296 the tool knowledge of the repo's location in case it can do something
1297 1297 reasonable with that.
1298 1298
1299 1299 $ hg init subprocesscwd
1300 1300 $ cd subprocesscwd
1301 1301
1302 1302 $ cat >> .hg/hgrc <<EOF
1303 1303 > [fix]
1304 1304 > printcwd:command = "$PYTHON" -c "import os; print(os.getcwd())"
1305 1305 > printcwd:pattern = relpath:foo/bar
1306 1306 > filesetpwd:command = "$PYTHON" -c "import os; print('fs: ' + os.getcwd())"
1307 1307 > filesetpwd:pattern = set:**quux
1308 1308 > EOF
1309 1309
1310 1310 $ mkdir foo
1311 1311 $ printf "bar\n" > foo/bar
1312 1312 $ printf "quux\n" > quux
1313 1313 $ hg commit -Aqm blah
1314 1314
1315 1315 $ hg fix -w -r . foo/bar
1316 1316 $ hg cat -r tip foo/bar
1317 1317 $TESTTMP/subprocesscwd
1318 1318 $ cat foo/bar
1319 1319 $TESTTMP/subprocesscwd
1320 1320
1321 1321 $ cd foo
1322 1322
1323 1323 $ hg fix -w -r . bar
1324 1324 $ hg cat -r tip bar ../quux
1325 1325 $TESTTMP/subprocesscwd
1326 1326 quux
1327 1327 $ cat bar ../quux
1328 1328 $TESTTMP/subprocesscwd
1329 1329 quux
1330 1330 $ echo modified > bar
1331 1331 $ hg fix -w bar
1332 1332 $ cat bar
1333 1333 $TESTTMP/subprocesscwd
1334 1334
1335 1335 Apparently fixing p1() and its descendants doesn't include wdir() unless
1336 1336 explicitly stated.
1337 1337
1338 1338 $ hg fix -r '.::'
1339 1339 $ hg cat -r . ../quux
1340 1340 quux
1341 1341 $ hg cat -r tip ../quux
1342 1342 fs: $TESTTMP/subprocesscwd
1343 1343 $ cat ../quux
1344 1344 quux
1345 1345
1346 1346 Clean files are not fixed unless explicitly named
1347 1347 $ echo 'dirty' > ../quux
1348 1348
1349 1349 $ hg fix --working-dir
1350 1350 $ cat ../quux
1351 1351 fs: $TESTTMP/subprocesscwd
1352 1352
1353 1353 $ cd ../..
1354 1354
1355 1355 Tools configured without a pattern are ignored. It would be too dangerous to
1356 1356 run them on all files, because this might happen while testing a configuration
1357 1357 that also deletes all of the file content. There is no reasonable subset of the
1358 1358 files to use as a default. Users should be explicit about what files are
1359 1359 affected by a tool. This test also confirms that we don't crash when the
1360 1360 pattern config is missing, and that we only warn about it once.
1361 1361
1362 1362 $ hg init nopatternconfigured
1363 1363 $ cd nopatternconfigured
1364 1364
1365 1365 $ printf "foo" > foo
1366 1366 $ printf "bar" > bar
1367 1367 $ hg add -q
1368 1368 $ hg fix --debug --working-dir --config "fix.nopattern:command=echo fixed"
1369 1369 fixer tool has no pattern configuration: nopattern
1370 1370 $ cat foo bar
1371 1371 foobar (no-eol)
1372 1372 $ hg fix --debug --working-dir --config "fix.nocommand:pattern=foo.bar"
1373 1373 fixer tool has no command configuration: nocommand
1374 1374
1375 1375 $ cd ..
1376 1376
1377 1377 Tools can be disabled. Disabled tools do nothing but print a debug message.
1378 1378
1379 1379 $ hg init disabled
1380 1380 $ cd disabled
1381 1381
1382 1382 $ printf "foo\n" > foo
1383 1383 $ hg add -q
1384 1384 $ hg fix --debug --working-dir --config "fix.disabled:command=echo fixed" \
1385 1385 > --config "fix.disabled:pattern=foo" \
1386 1386 > --config "fix.disabled:enabled=false"
1387 1387 ignoring disabled fixer tool: disabled
1388 1388 $ cat foo
1389 1389 foo
1390 1390
1391 1391 $ cd ..
1392 1392
1393 1393 Test that we can configure a fixer to affect all files regardless of the cwd.
1394 1394 The way we invoke matching must not prohibit this.
1395 1395
1396 1396 $ hg init affectallfiles
1397 1397 $ cd affectallfiles
1398 1398
1399 1399 $ mkdir foo bar
1400 1400 $ printf "foo" > foo/file
1401 1401 $ printf "bar" > bar/file
1402 1402 $ printf "baz" > baz_file
1403 1403 $ hg add -q
1404 1404
1405 1405 $ cd bar
1406 1406 $ hg fix --working-dir --config "fix.cooltool:command=echo fixed" \
1407 1407 > --config "fix.cooltool:pattern=glob:**"
1408 1408 $ cd ..
1409 1409
1410 1410 $ cat foo/file
1411 1411 fixed
1412 1412 $ cat bar/file
1413 1413 fixed
1414 1414 $ cat baz_file
1415 1415 fixed
1416 1416
1417 1417 $ cd ..
1418 1418
1419 1419 Tools should be able to run on unchanged files, even if they set :linerange.
1420 1420 This includes a corner case where deleted chunks of a file are not considered
1421 1421 changes.
1422 1422
1423 1423 $ hg init skipclean
1424 1424 $ cd skipclean
1425 1425
1426 1426 $ printf "a\nb\nc\n" > foo
1427 1427 $ printf "a\nb\nc\n" > bar
1428 1428 $ printf "a\nb\nc\n" > baz
1429 1429 $ hg commit -Aqm "base"
1430 1430
1431 1431 $ printf "a\nc\n" > foo
1432 1432 $ printf "a\nx\nc\n" > baz
1433 1433
1434 1434 $ cat >> print.py <<EOF
1435 1435 > import sys
1436 1436 > for a in sys.argv[1:]:
1437 1437 > print(a)
1438 1438 > EOF
1439 1439
1440 1440 $ hg fix --working-dir foo bar baz \
1441 1441 > --config "fix.changedlines:command=\"$PYTHON\" print.py \"Line ranges:\"" \
1442 1442 > --config 'fix.changedlines:linerange="{first} through {last}"' \
1443 1443 > --config 'fix.changedlines:pattern=glob:**' \
1444 1444 > --config 'fix.changedlines:skipclean=false'
1445 1445
1446 1446 $ cat foo
1447 1447 Line ranges:
1448 1448 $ cat bar
1449 1449 Line ranges:
1450 1450 $ cat baz
1451 1451 Line ranges:
1452 1452 2 through 2
1453 1453
1454 1454 $ cd ..
1455 1455
1456 1456 Test various cases around merges. We were previously dropping files if they were
1457 1457 created on only the p2 side of the merge, so let's test permutations of:
1458 1458 * added, was fixed
1459 1459 * added, considered for fixing but was already good
1460 1460 * added, not considered for fixing
1461 1461 * modified, was fixed
1462 1462 * modified, considered for fixing but was already good
1463 1463 * modified, not considered for fixing
1464 1464
1465 1465 Before the bug was fixed where we would drop files, this test demonstrated the
1466 1466 following issues:
1467 1467 * new_in_r1.ignored, new_in_r1_already_good.changed, and
1468 1468 > mod_in_r1_already_good.changed were NOT in the manifest for the merge commit
1469 1469 * mod_in_r1.ignored had its contents from r0, NOT r1.
1470 1470
1471 1471 We're also setting a named branch for every commit to demonstrate that the
1472 1472 branch is kept intact and there aren't issues updating to another branch in the
1473 1473 middle of fix.
1474 1474
1475 1475 $ hg init merge_keeps_files
1476 1476 $ cd merge_keeps_files
1477 1477 $ for f in r0 mod_in_r1 mod_in_r2 mod_in_merge mod_in_child; do
1478 1478 > for c in changed whole ignored; do
1479 1479 > printf "hello\n" > $f.$c
1480 1480 > done
1481 1481 > printf "HELLO\n" > "mod_in_${f}_already_good.changed"
1482 1482 > done
1483 1483 $ hg branch -q r0
1484 1484 $ hg ci -Aqm 'r0'
1485 1485 $ hg phase -p
1486 1486 $ make_test_files() {
1487 1487 > printf "world\n" >> "mod_in_$1.changed"
1488 1488 > printf "world\n" >> "mod_in_$1.whole"
1489 1489 > printf "world\n" >> "mod_in_$1.ignored"
1490 1490 > printf "WORLD\n" >> "mod_in_$1_already_good.changed"
1491 1491 > printf "new in $1\n" > "new_in_$1.changed"
1492 1492 > printf "new in $1\n" > "new_in_$1.whole"
1493 1493 > printf "new in $1\n" > "new_in_$1.ignored"
1494 1494 > printf "ALREADY GOOD, NEW IN THIS REV\n" > "new_in_$1_already_good.changed"
1495 1495 > }
1496 1496 $ make_test_commit() {
1497 1497 > make_test_files "$1"
1498 1498 > hg branch -q "$1"
1499 1499 > hg ci -Aqm "$2"
1500 1500 > }
1501 1501 $ make_test_commit r1 "merge me, pt1"
1502 1502 $ hg co -q ".^"
1503 1503 $ make_test_commit r2 "merge me, pt2"
1504 1504 $ hg merge -qr 1
1505 1505 $ make_test_commit merge "evil merge"
1506 1506 $ make_test_commit child "child of merge"
1507 1507 $ make_test_files wdir
1508 1508 $ hg fix -r 'not public()' -w
1509 1509 $ hg log -G -T'{rev}:{shortest(node,8)}: branch:{branch} desc:{desc}'
1510 1510 @ 8:c22ce900: branch:child desc:child of merge
1511 1511 |
1512 1512 o 7:5a30615a: branch:merge desc:evil merge
1513 1513 |\
1514 1514 | o 6:4e5acdc4: branch:r2 desc:merge me, pt2
1515 1515 | |
1516 1516 o | 5:eea01878: branch:r1 desc:merge me, pt1
1517 1517 |/
1518 1518 o 0:0c548d87: branch:r0 desc:r0
1519 1519
1520 1520 $ hg files -r tip
1521 1521 mod_in_child.changed
1522 1522 mod_in_child.ignored
1523 1523 mod_in_child.whole
1524 1524 mod_in_child_already_good.changed
1525 1525 mod_in_merge.changed
1526 1526 mod_in_merge.ignored
1527 1527 mod_in_merge.whole
1528 1528 mod_in_merge_already_good.changed
1529 1529 mod_in_mod_in_child_already_good.changed
1530 1530 mod_in_mod_in_merge_already_good.changed
1531 1531 mod_in_mod_in_r1_already_good.changed
1532 1532 mod_in_mod_in_r2_already_good.changed
1533 1533 mod_in_r0_already_good.changed
1534 1534 mod_in_r1.changed
1535 1535 mod_in_r1.ignored
1536 1536 mod_in_r1.whole
1537 1537 mod_in_r1_already_good.changed
1538 1538 mod_in_r2.changed
1539 1539 mod_in_r2.ignored
1540 1540 mod_in_r2.whole
1541 1541 mod_in_r2_already_good.changed
1542 1542 new_in_child.changed
1543 1543 new_in_child.ignored
1544 1544 new_in_child.whole
1545 1545 new_in_child_already_good.changed
1546 1546 new_in_merge.changed
1547 1547 new_in_merge.ignored
1548 1548 new_in_merge.whole
1549 1549 new_in_merge_already_good.changed
1550 1550 new_in_r1.changed
1551 1551 new_in_r1.ignored
1552 1552 new_in_r1.whole
1553 1553 new_in_r1_already_good.changed
1554 1554 new_in_r2.changed
1555 1555 new_in_r2.ignored
1556 1556 new_in_r2.whole
1557 1557 new_in_r2_already_good.changed
1558 1558 r0.changed
1559 1559 r0.ignored
1560 1560 r0.whole
1561 1561 $ for f in "$(hg files -r tip)"; do hg cat -r tip $f -T'{path}:\n{data}\n'; done
1562 1562 mod_in_child.changed:
1563 1563 hello
1564 1564 WORLD
1565 1565
1566 1566 mod_in_child.ignored:
1567 1567 hello
1568 1568 world
1569 1569
1570 1570 mod_in_child.whole:
1571 1571 HELLO
1572 1572 WORLD
1573 1573
1574 1574 mod_in_child_already_good.changed:
1575 1575 WORLD
1576 1576
1577 1577 mod_in_merge.changed:
1578 1578 hello
1579 1579 WORLD
1580 1580
1581 1581 mod_in_merge.ignored:
1582 1582 hello
1583 1583 world
1584 1584
1585 1585 mod_in_merge.whole:
1586 1586 HELLO
1587 1587 WORLD
1588 1588
1589 1589 mod_in_merge_already_good.changed:
1590 1590 WORLD
1591 1591
1592 1592 mod_in_mod_in_child_already_good.changed:
1593 1593 HELLO
1594 1594
1595 1595 mod_in_mod_in_merge_already_good.changed:
1596 1596 HELLO
1597 1597
1598 1598 mod_in_mod_in_r1_already_good.changed:
1599 1599 HELLO
1600 1600
1601 1601 mod_in_mod_in_r2_already_good.changed:
1602 1602 HELLO
1603 1603
1604 1604 mod_in_r0_already_good.changed:
1605 1605 HELLO
1606 1606
1607 1607 mod_in_r1.changed:
1608 1608 hello
1609 1609 WORLD
1610 1610
1611 1611 mod_in_r1.ignored:
1612 1612 hello
1613 1613 world
1614 1614
1615 1615 mod_in_r1.whole:
1616 1616 HELLO
1617 1617 WORLD
1618 1618
1619 1619 mod_in_r1_already_good.changed:
1620 1620 WORLD
1621 1621
1622 1622 mod_in_r2.changed:
1623 1623 hello
1624 1624 WORLD
1625 1625
1626 1626 mod_in_r2.ignored:
1627 1627 hello
1628 1628 world
1629 1629
1630 1630 mod_in_r2.whole:
1631 1631 HELLO
1632 1632 WORLD
1633 1633
1634 1634 mod_in_r2_already_good.changed:
1635 1635 WORLD
1636 1636
1637 1637 new_in_child.changed:
1638 1638 NEW IN CHILD
1639 1639
1640 1640 new_in_child.ignored:
1641 1641 new in child
1642 1642
1643 1643 new_in_child.whole:
1644 1644 NEW IN CHILD
1645 1645
1646 1646 new_in_child_already_good.changed:
1647 1647 ALREADY GOOD, NEW IN THIS REV
1648 1648
1649 1649 new_in_merge.changed:
1650 1650 NEW IN MERGE
1651 1651
1652 1652 new_in_merge.ignored:
1653 1653 new in merge
1654 1654
1655 1655 new_in_merge.whole:
1656 1656 NEW IN MERGE
1657 1657
1658 1658 new_in_merge_already_good.changed:
1659 1659 ALREADY GOOD, NEW IN THIS REV
1660 1660
1661 1661 new_in_r1.changed:
1662 1662 NEW IN R1
1663 1663
1664 1664 new_in_r1.ignored:
1665 1665 new in r1
1666 1666
1667 1667 new_in_r1.whole:
1668 1668 NEW IN R1
1669 1669
1670 1670 new_in_r1_already_good.changed:
1671 1671 ALREADY GOOD, NEW IN THIS REV
1672 1672
1673 1673 new_in_r2.changed:
1674 1674 NEW IN R2
1675 1675
1676 1676 new_in_r2.ignored:
1677 1677 new in r2
1678 1678
1679 1679 new_in_r2.whole:
1680 1680 NEW IN R2
1681 1681
1682 1682 new_in_r2_already_good.changed:
1683 1683 ALREADY GOOD, NEW IN THIS REV
1684 1684
1685 1685 r0.changed:
1686 1686 hello
1687 1687
1688 1688 r0.ignored:
1689 1689 hello
1690 1690
1691 1691 r0.whole:
1692 1692 hello
1693 1693
@@ -1,443 +1,443 b''
1 1 #require no-reposimplestore
2 2
3 3 Testing the case when there is no infinitepush extension present on the client
4 4 side and the server routes each push to bundlestore. This case is very much
5 5 similar to CI use case.
6 6
7 7 Setup
8 8 -----
9 9
10 10 $ . "$TESTDIR/library-infinitepush.sh"
11 11 $ cat >> $HGRCPATH <<EOF
12 12 > [ui]
13 13 > ssh = python "$TESTDIR/dummyssh"
14 14 > [alias]
15 15 > glog = log -GT "{rev}:{node|short} {desc}\n{phase}"
16 16 > EOF
17 17 $ cp $HGRCPATH $TESTTMP/defaulthgrc
18 18 $ hg init repo
19 19 $ cd repo
20 20 $ setupserver
21 21 $ echo "pushtobundlestore = True" >> .hg/hgrc
22 22 $ echo "[extensions]" >> .hg/hgrc
23 23 $ echo "infinitepush=" >> .hg/hgrc
24 24 $ echo initialcommit > initialcommit
25 25 $ hg ci -Aqm "initialcommit"
26 26 $ hg phase --public .
27 27
28 28 $ cd ..
29 29 $ hg clone repo client -q
30 30 $ hg clone repo client2 -q
31 31 $ hg clone ssh://user@dummy/repo client3 -q
32 32 $ cd client
33 33
34 34 Pushing a new commit from the client to the server
35 35 -----------------------------------------------------
36 36
37 37 $ echo foobar > a
38 38 $ hg ci -Aqm "added a"
39 39 $ hg glog
40 40 @ 1:6cb0989601f1 added a
41 41 | draft
42 42 o 0:67145f466344 initialcommit
43 43 public
44 44
45 45 $ hg push
46 46 pushing to $TESTTMP/repo
47 47 searching for changes
48 48 storing changesets on the bundlestore
49 49 pushing 1 commit:
50 50 6cb0989601f1 added a
51 51
52 52 $ scratchnodes
53 53 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
54 54
55 55 Understanding how data is stored on the bundlestore in server
56 56 -------------------------------------------------------------
57 57
58 58 There are two things, filebundlestore and index
59 59 $ ls ../repo/.hg/scratchbranches
60 60 filebundlestore
61 61 index
62 62
63 63 filebundlestore stores the bundles
64 64 $ ls ../repo/.hg/scratchbranches/filebundlestore/3b/41/
65 65 3b414252ff8acab801318445d88ff48faf4a28c3
66 66
67 67 index/nodemap stores a map of node id and file in which bundle is stored in filebundlestore
68 68 $ ls ../repo/.hg/scratchbranches/index/
69 69 nodemap
70 70 $ ls ../repo/.hg/scratchbranches/index/nodemap/
71 71 6cb0989601f1fb5805238edfb16f3606713d9a0b
72 72
73 73 $ cd ../repo
74 74
75 75 Checking that the commit was not applied to revlog on the server
76 76 ------------------------------------------------------------------
77 77
78 78 $ hg glog
79 79 @ 0:67145f466344 initialcommit
80 80 public
81 81
82 82 Applying the changeset from the bundlestore
83 83 --------------------------------------------
84 84
85 85 $ hg unbundle .hg/scratchbranches/filebundlestore/3b/41/3b414252ff8acab801318445d88ff48faf4a28c3
86 86 adding changesets
87 87 adding manifests
88 88 adding file changes
89 89 added 1 changesets with 1 changes to 1 files
90 90 new changesets 6cb0989601f1
91 91 (run 'hg update' to get a working copy)
92 92
93 93 $ hg glog
94 94 o 1:6cb0989601f1 added a
95 95 | public
96 96 @ 0:67145f466344 initialcommit
97 97 public
98 98
99 99 Pushing more changesets from the local repo
100 100 --------------------------------------------
101 101
102 102 $ cd ../client
103 103 $ echo b > b
104 104 $ hg ci -Aqm "added b"
105 105 $ echo c > c
106 106 $ hg ci -Aqm "added c"
107 107 $ hg glog
108 108 @ 3:bf8a6e3011b3 added c
109 109 | draft
110 110 o 2:eaba929e866c added b
111 111 | draft
112 112 o 1:6cb0989601f1 added a
113 113 | public
114 114 o 0:67145f466344 initialcommit
115 115 public
116 116
117 117 $ hg push
118 118 pushing to $TESTTMP/repo
119 119 searching for changes
120 120 storing changesets on the bundlestore
121 121 pushing 2 commits:
122 122 eaba929e866c added b
123 123 bf8a6e3011b3 added c
124 124
125 125 Checking that changesets are not applied on the server
126 126 ------------------------------------------------------
127 127
128 128 $ hg glog -R ../repo
129 129 o 1:6cb0989601f1 added a
130 130 | public
131 131 @ 0:67145f466344 initialcommit
132 132 public
133 133
134 134 Both of the new changesets are stored in a single bundle-file
135 135 $ scratchnodes
136 136 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
137 137 bf8a6e3011b345146bbbedbcb1ebd4837571492a 239585f5e61f0c09ce7106bdc1097bff731738f4
138 138 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 239585f5e61f0c09ce7106bdc1097bff731738f4
139 139
140 140 Pushing more changesets to the server
141 141 -------------------------------------
142 142
143 143 $ echo d > d
144 144 $ hg ci -Aqm "added d"
145 145 $ echo e > e
146 146 $ hg ci -Aqm "added e"
147 147
148 148 XXX: we should have pushed only the parts which are not in bundlestore
149 149 $ hg push
150 150 pushing to $TESTTMP/repo
151 151 searching for changes
152 152 storing changesets on the bundlestore
153 153 pushing 4 commits:
154 154 eaba929e866c added b
155 155 bf8a6e3011b3 added c
156 156 1bb96358eda2 added d
157 157 b4e4bce66051 added e
158 158
159 159 Sneak peek into the bundlestore at the server
160 160 $ scratchnodes
161 161 1bb96358eda285b536c6d1c66846a7cdb2336cea 98fbae0016662521b0007da1b7bc349cd3caacd1
162 162 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
163 163 b4e4bce660512ad3e71189e14588a70ac8e31fef 98fbae0016662521b0007da1b7bc349cd3caacd1
164 164 bf8a6e3011b345146bbbedbcb1ebd4837571492a 98fbae0016662521b0007da1b7bc349cd3caacd1
165 165 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 98fbae0016662521b0007da1b7bc349cd3caacd1
166 166
167 167 Checking if `hg pull` pulls something or `hg incoming` shows something
168 168 -----------------------------------------------------------------------
169 169
170 170 $ hg incoming
171 171 comparing with $TESTTMP/repo
172 172 searching for changes
173 173 no changes found
174 174 [1]
175 175
176 176 $ hg pull
177 177 pulling from $TESTTMP/repo
178 178 searching for changes
179 179 no changes found
180 180
181 181 Pulling from second client which is a localpeer to test `hg pull -r <rev>`
182 182 --------------------------------------------------------------------------
183 183
184 184 Pulling the revision which is applied
185 185
186 186 $ cd ../client2
187 187 $ hg pull -r 6cb0989601f1
188 188 pulling from $TESTTMP/repo
189 189 searching for changes
190 190 adding changesets
191 191 adding manifests
192 192 adding file changes
193 193 added 1 changesets with 1 changes to 1 files
194 194 new changesets 6cb0989601f1
195 195 (run 'hg update' to get a working copy)
196 196 $ hg glog
197 197 o 1:6cb0989601f1 added a
198 198 | public
199 199 @ 0:67145f466344 initialcommit
200 200 public
201 201
202 202 Pulling the revision which is in bundlestore
203 203 XXX: we should support pulling revisions from a local peers bundlestore without
204 204 client side wrapping
205 205
206 206 $ hg pull -r b4e4bce660512ad3e71189e14588a70ac8e31fef
207 207 pulling from $TESTTMP/repo
208 208 abort: unknown revision 'b4e4bce660512ad3e71189e14588a70ac8e31fef'!
209 209 [255]
210 210 $ hg glog
211 211 o 1:6cb0989601f1 added a
212 212 | public
213 213 @ 0:67145f466344 initialcommit
214 214 public
215 215
216 216 $ cd ../client
217 217
218 218 Pulling from third client which is not a localpeer
219 219 ---------------------------------------------------
220 220
221 221 Pulling the revision which is applied
222 222
223 223 $ cd ../client3
224 224 $ hg pull -r 6cb0989601f1
225 225 pulling from ssh://user@dummy/repo
226 226 searching for changes
227 227 adding changesets
228 228 adding manifests
229 229 adding file changes
230 230 added 1 changesets with 1 changes to 1 files
231 231 new changesets 6cb0989601f1
232 232 (run 'hg update' to get a working copy)
233 233 $ hg glog
234 234 o 1:6cb0989601f1 added a
235 235 | public
236 236 @ 0:67145f466344 initialcommit
237 237 public
238 238
239 239 Pulling the revision which is in bundlestore
240 240
241 241 Trying to specify short hash
242 242 XXX: we should support this
243 243 $ hg pull -r b4e4bce660512
244 244 pulling from ssh://user@dummy/repo
245 245 abort: unknown revision 'b4e4bce660512'!
246 246 [255]
247 247
248 248 XXX: we should show better message when the pull is happening from bundlestore
249 249 $ hg pull -r b4e4bce660512ad3e71189e14588a70ac8e31fef
250 250 pulling from ssh://user@dummy/repo
251 251 searching for changes
252 252 adding changesets
253 253 adding manifests
254 254 adding file changes
255 255 added 4 changesets with 4 changes to 4 files
256 256 new changesets eaba929e866c:b4e4bce66051
257 257 (run 'hg update' to get a working copy)
258 258 $ hg glog
259 259 o 5:b4e4bce66051 added e
260 260 | public
261 261 o 4:1bb96358eda2 added d
262 262 | public
263 263 o 3:bf8a6e3011b3 added c
264 264 | public
265 265 o 2:eaba929e866c added b
266 266 | public
267 267 o 1:6cb0989601f1 added a
268 268 | public
269 269 @ 0:67145f466344 initialcommit
270 270 public
271 271
272 272 $ cd ../client
273 273
274 274 Checking storage of phase information with the bundle on bundlestore
275 275 ---------------------------------------------------------------------
276 276
277 277 creating a draft commit
278 278 $ cat >> $HGRCPATH <<EOF
279 279 > [phases]
280 280 > publish = False
281 281 > EOF
282 282 $ echo f > f
283 283 $ hg ci -Aqm "added f"
284 284 $ hg glog -r '.^::'
285 285 @ 6:9b42578d4447 added f
286 286 | draft
287 287 o 5:b4e4bce66051 added e
288 288 | public
289 289 ~
290 290
291 291 $ hg push
292 292 pushing to $TESTTMP/repo
293 293 searching for changes
294 294 storing changesets on the bundlestore
295 295 pushing 5 commits:
296 296 eaba929e866c added b
297 297 bf8a6e3011b3 added c
298 298 1bb96358eda2 added d
299 299 b4e4bce66051 added e
300 300 9b42578d4447 added f
301 301
302 302 XXX: the phase of 9b42578d4447 should not be changed here
303 303 $ hg glog -r .
304 304 @ 6:9b42578d4447 added f
305 305 | public
306 306 ~
307 307
308 308 applying the bundle on the server to check preservation of phase-information
309 309
310 310 $ cd ../repo
311 311 $ scratchnodes
312 312 1bb96358eda285b536c6d1c66846a7cdb2336cea 280a46a259a268f0e740c81c5a7751bdbfaec85f
313 313 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
314 314 9b42578d44473575994109161430d65dd147d16d 280a46a259a268f0e740c81c5a7751bdbfaec85f
315 315 b4e4bce660512ad3e71189e14588a70ac8e31fef 280a46a259a268f0e740c81c5a7751bdbfaec85f
316 316 bf8a6e3011b345146bbbedbcb1ebd4837571492a 280a46a259a268f0e740c81c5a7751bdbfaec85f
317 317 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 280a46a259a268f0e740c81c5a7751bdbfaec85f
318 318
319 319 $ hg unbundle .hg/scratchbranches/filebundlestore/28/0a/280a46a259a268f0e740c81c5a7751bdbfaec85f
320 320 adding changesets
321 321 adding manifests
322 322 adding file changes
323 323 added 5 changesets with 5 changes to 5 files
324 324 new changesets eaba929e866c:9b42578d4447 (1 drafts)
325 325 (run 'hg update' to get a working copy)
326 326
327 327 $ hg glog
328 328 o 6:9b42578d4447 added f
329 329 | draft
330 330 o 5:b4e4bce66051 added e
331 331 | public
332 332 o 4:1bb96358eda2 added d
333 333 | public
334 334 o 3:bf8a6e3011b3 added c
335 335 | public
336 336 o 2:eaba929e866c added b
337 337 | public
338 338 o 1:6cb0989601f1 added a
339 339 | public
340 340 @ 0:67145f466344 initialcommit
341 341 public
342 342
343 343 Checking storage of obsmarkers in the bundlestore
344 344 --------------------------------------------------
345 345
346 346 enabling obsmarkers and rebase extension
347 347
348 348 $ cat >> $HGRCPATH << EOF
349 349 > [experimental]
350 350 > evolution = all
351 351 > [extensions]
352 352 > rebase =
353 353 > EOF
354 354
355 355 $ cd ../client
356 356
357 357 $ hg phase -r . --draft --force
358 358 $ hg rebase -r 6 -d 3
359 rebasing 6:9b42578d4447 "added f" (tip)
359 rebasing 6:9b42578d4447 tip "added f"
360 360
361 361 $ hg glog
362 362 @ 7:99949238d9ac added f
363 363 | draft
364 364 | o 5:b4e4bce66051 added e
365 365 | | public
366 366 | o 4:1bb96358eda2 added d
367 367 |/ public
368 368 o 3:bf8a6e3011b3 added c
369 369 | public
370 370 o 2:eaba929e866c added b
371 371 | public
372 372 o 1:6cb0989601f1 added a
373 373 | public
374 374 o 0:67145f466344 initialcommit
375 375 public
376 376
377 377 $ hg push -f
378 378 pushing to $TESTTMP/repo
379 379 searching for changes
380 380 storing changesets on the bundlestore
381 381 pushing 1 commit:
382 382 99949238d9ac added f
383 383
384 384 XXX: the phase should not have changed here
385 385 $ hg glog -r .
386 386 @ 7:99949238d9ac added f
387 387 | public
388 388 ~
389 389
390 390 Unbundling on server to see obsmarkers being applied
391 391
392 392 $ cd ../repo
393 393
394 394 $ scratchnodes
395 395 1bb96358eda285b536c6d1c66846a7cdb2336cea 280a46a259a268f0e740c81c5a7751bdbfaec85f
396 396 6cb0989601f1fb5805238edfb16f3606713d9a0b 3b414252ff8acab801318445d88ff48faf4a28c3
397 397 99949238d9ac7f2424a33a46dface6f866afd059 090a24fe63f31d3b4bee714447f835c8c362ff57
398 398 9b42578d44473575994109161430d65dd147d16d 280a46a259a268f0e740c81c5a7751bdbfaec85f
399 399 b4e4bce660512ad3e71189e14588a70ac8e31fef 280a46a259a268f0e740c81c5a7751bdbfaec85f
400 400 bf8a6e3011b345146bbbedbcb1ebd4837571492a 280a46a259a268f0e740c81c5a7751bdbfaec85f
401 401 eaba929e866c59bc9a6aada5a9dd2f6990db83c0 280a46a259a268f0e740c81c5a7751bdbfaec85f
402 402
403 403 $ hg glog
404 404 o 6:9b42578d4447 added f
405 405 | draft
406 406 o 5:b4e4bce66051 added e
407 407 | public
408 408 o 4:1bb96358eda2 added d
409 409 | public
410 410 o 3:bf8a6e3011b3 added c
411 411 | public
412 412 o 2:eaba929e866c added b
413 413 | public
414 414 o 1:6cb0989601f1 added a
415 415 | public
416 416 @ 0:67145f466344 initialcommit
417 417 public
418 418
419 419 $ hg unbundle .hg/scratchbranches/filebundlestore/09/0a/090a24fe63f31d3b4bee714447f835c8c362ff57
420 420 adding changesets
421 421 adding manifests
422 422 adding file changes
423 423 added 1 changesets with 0 changes to 1 files (+1 heads)
424 424 1 new obsolescence markers
425 425 obsoleted 1 changesets
426 426 new changesets 99949238d9ac (1 drafts)
427 427 (run 'hg heads' to see heads, 'hg merge' to merge)
428 428
429 429 $ hg glog
430 430 o 7:99949238d9ac added f
431 431 | draft
432 432 | o 5:b4e4bce66051 added e
433 433 | | public
434 434 | o 4:1bb96358eda2 added d
435 435 |/ public
436 436 o 3:bf8a6e3011b3 added c
437 437 | public
438 438 o 2:eaba929e866c added b
439 439 | public
440 440 o 1:6cb0989601f1 added a
441 441 | public
442 442 @ 0:67145f466344 initialcommit
443 443 public
@@ -1,797 +1,797 b''
1 1 #require no-reposimplestore
2 2
3 3 This file focuses mainly on updating largefiles in the working
4 4 directory (and ".hg/largefiles/dirstate")
5 5
6 6 $ cat >> $HGRCPATH <<EOF
7 7 > [ui]
8 8 > merge = internal:merge
9 9 > [extensions]
10 10 > largefiles =
11 11 > [extdiff]
12 12 > # for portability:
13 13 > pdiff = sh "$RUNTESTDIR/pdiff"
14 14 > EOF
15 15
16 16 $ hg init repo
17 17 $ cd repo
18 18
19 19 $ echo large1 > large1
20 20 $ echo large2 > large2
21 21 $ hg add --large large1 large2
22 22 $ echo normal1 > normal1
23 23 $ hg add normal1
24 24 $ hg commit -m '#0'
25 25 $ echo 'large1 in #1' > large1
26 26 $ echo 'normal1 in #1' > normal1
27 27 $ hg commit -m '#1'
28 28 $ hg pdiff -r '.^' --config extensions.extdiff=
29 29 diff -Nru repo.0d9d9b8dc9a3/.hglf/large1 repo/.hglf/large1
30 30 --- repo.0d9d9b8dc9a3/.hglf/large1 * (glob)
31 31 +++ repo/.hglf/large1 * (glob)
32 32 @@ -1* +1* @@ (glob)
33 33 -4669e532d5b2c093a78eca010077e708a071bb64
34 34 +58e24f733a964da346e2407a2bee99d9001184f5
35 35 diff -Nru repo.0d9d9b8dc9a3/normal1 repo/normal1
36 36 --- repo.0d9d9b8dc9a3/normal1 * (glob)
37 37 +++ repo/normal1 * (glob)
38 38 @@ -1* +1* @@ (glob)
39 39 -normal1
40 40 +normal1 in #1
41 41 [1]
42 42 $ hg update -q -C 0
43 43 $ echo 'large2 in #2' > large2
44 44 $ hg commit -m '#2'
45 45 created new head
46 46
47 47 Test that update also updates the lfdirstate of 'unsure' largefiles after
48 48 hashing them:
49 49
50 50 The previous operations will usually have left us with largefiles with a mtime
51 51 within the same second as the dirstate was written.
52 52 The lfdirstate entries will thus have been written with an invalidated/unset
53 53 mtime to make sure further changes within the same second is detected.
54 54 We will however occasionally be "lucky" and get a tick between writing
55 55 largefiles and writing dirstate so we get valid lfdirstate timestamps. The
56 56 following verification is thus disabled but can be verified manually.
57 57
58 58 #if false
59 59 $ hg debugdirstate --large --nodate
60 60 n 644 7 unset large1
61 61 n 644 13 unset large2
62 62 #endif
63 63
64 64 Wait to make sure we get a tick so the mtime of the largefiles become valid.
65 65
66 66 $ sleep 1
67 67
68 68 A linear merge will update standins before performing the actual merge. It will
69 69 do a lfdirstate status walk and find 'unset'/'unsure' files, hash them, and
70 70 update the corresponding standins.
71 71 Verify that it actually marks the clean files as clean in lfdirstate so
72 72 we don't have to hash them again next time we update.
73 73
74 74 $ hg up
75 75 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
76 76 updated to "f74e50bd9e55: #2"
77 77 1 other heads for branch "default"
78 78 $ hg debugdirstate --large --nodate
79 79 n 644 7 set large1
80 80 n 644 13 set large2
81 81
82 82 Test that lfdirstate keeps track of last modification of largefiles and
83 83 prevents unnecessary hashing of content - also after linear/noop update
84 84
85 85 $ sleep 1
86 86 $ hg st
87 87 $ hg debugdirstate --large --nodate
88 88 n 644 7 set large1
89 89 n 644 13 set large2
90 90 $ hg up
91 91 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
92 92 updated to "f74e50bd9e55: #2"
93 93 1 other heads for branch "default"
94 94 $ hg debugdirstate --large --nodate
95 95 n 644 7 set large1
96 96 n 644 13 set large2
97 97
98 98 Test that "hg merge" updates largefiles from "other" correctly
99 99
100 100 (getting largefiles from "other" normally)
101 101
102 102 $ hg status -A large1
103 103 C large1
104 104 $ cat large1
105 105 large1
106 106 $ cat .hglf/large1
107 107 4669e532d5b2c093a78eca010077e708a071bb64
108 108 $ hg merge --config debug.dirstate.delaywrite=2
109 109 getting changed largefiles
110 110 1 largefiles updated, 0 removed
111 111 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
112 112 (branch merge, don't forget to commit)
113 113 $ hg status -A large1
114 114 M large1
115 115 $ cat large1
116 116 large1 in #1
117 117 $ cat .hglf/large1
118 118 58e24f733a964da346e2407a2bee99d9001184f5
119 119 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
120 120 -4669e532d5b2c093a78eca010077e708a071bb64
121 121 +58e24f733a964da346e2407a2bee99d9001184f5
122 122
123 123 (getting largefiles from "other" via conflict prompt)
124 124
125 125 $ hg update -q -C 2
126 126 $ echo 'large1 in #3' > large1
127 127 $ echo 'normal1 in #3' > normal1
128 128 $ hg commit -m '#3'
129 129 $ cat .hglf/large1
130 130 e5bb990443d6a92aaf7223813720f7566c9dd05b
131 131 $ hg merge --config debug.dirstate.delaywrite=2 --config ui.interactive=True <<EOF
132 132 > o
133 133 > EOF
134 134 largefile large1 has a merge conflict
135 135 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
136 136 you can keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5.
137 137 what do you want to do? o
138 138 merging normal1
139 139 warning: conflicts while merging normal1! (edit, then use 'hg resolve --mark')
140 140 getting changed largefiles
141 141 1 largefiles updated, 0 removed
142 142 0 files updated, 1 files merged, 0 files removed, 1 files unresolved
143 143 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
144 144 [1]
145 145 $ hg status -A large1
146 146 M large1
147 147 $ cat large1
148 148 large1 in #1
149 149 $ cat .hglf/large1
150 150 58e24f733a964da346e2407a2bee99d9001184f5
151 151 $ rm normal1.orig
152 152
153 153 (merge non-existing largefiles from "other" via conflict prompt -
154 154 make sure the following commit doesn't abort in a confusing way when trying to
155 155 mark the non-existing file as normal in lfdirstate)
156 156
157 157 $ mv .hg/largefiles/58e24f733a964da346e2407a2bee99d9001184f5 .
158 158 $ hg update -q -C 3
159 159 $ hg merge --config largefiles.usercache=not --config debug.dirstate.delaywrite=2 --tool :local --config ui.interactive=True <<EOF
160 160 > o
161 161 > EOF
162 162 largefile large1 has a merge conflict
163 163 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
164 164 you can keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5.
165 165 what do you want to do? o
166 166 getting changed largefiles
167 167 large1: largefile 58e24f733a964da346e2407a2bee99d9001184f5 not available from file:/*/$TESTTMP/repo (glob)
168 168 0 largefiles updated, 0 removed
169 169 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
170 170 (branch merge, don't forget to commit)
171 171 $ hg commit -m '1-2-3 testing' --config largefiles.usercache=not
172 172 large1: largefile 58e24f733a964da346e2407a2bee99d9001184f5 not available from local store
173 173 $ hg up -C . --config largefiles.usercache=not
174 174 getting changed largefiles
175 175 large1: largefile 58e24f733a964da346e2407a2bee99d9001184f5 not available from file:/*/$TESTTMP/repo (glob)
176 176 0 largefiles updated, 0 removed
177 177 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
178 178 $ hg st large1
179 179 ! large1
180 180 $ hg rollback -q
181 181 $ mv 58e24f733a964da346e2407a2bee99d9001184f5 .hg/largefiles/
182 182
183 183 Test that "hg revert -r REV" updates largefiles from "REV" correctly
184 184
185 185 $ hg update -q -C 3
186 186 $ hg status -A large1
187 187 C large1
188 188 $ cat large1
189 189 large1 in #3
190 190 $ cat .hglf/large1
191 191 e5bb990443d6a92aaf7223813720f7566c9dd05b
192 192 $ hg diff -c 1 --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
193 193 -4669e532d5b2c093a78eca010077e708a071bb64
194 194 +58e24f733a964da346e2407a2bee99d9001184f5
195 195 $ hg revert --no-backup -r 1 --config debug.dirstate.delaywrite=2 large1
196 196 $ hg status -A large1
197 197 M large1
198 198 $ cat large1
199 199 large1 in #1
200 200 $ cat .hglf/large1
201 201 58e24f733a964da346e2407a2bee99d9001184f5
202 202
203 203 Test that "hg rollback" restores status of largefiles correctly
204 204
205 205 $ hg update -C -q
206 206 $ hg remove large1
207 207 $ test -f .hglf/large1
208 208 [1]
209 209 $ hg forget large2
210 210 $ test -f .hglf/large2
211 211 [1]
212 212 $ echo largeX > largeX
213 213 $ hg add --large largeX
214 214 $ cat .hglf/largeX
215 215
216 216 $ hg commit -m 'will be rollback-ed soon'
217 217 $ echo largeY > largeY
218 218 $ hg add --large largeY
219 219
220 220 $ hg status -A large1
221 221 large1: $ENOENT$
222 222
223 223 $ hg status -A large2
224 224 ? large2
225 225 $ hg status -A largeX
226 226 C largeX
227 227 $ hg status -A largeY
228 228 A largeY
229 229 $ hg rollback
230 230 repository tip rolled back to revision 3 (undo commit)
231 231 working directory now based on revision 3
232 232 $ hg status -A large1
233 233 R large1
234 234 $ test -f .hglf/large1
235 235 [1]
236 236 $ hg status -A large2
237 237 R large2
238 238 $ test -f .hglf/large2
239 239 [1]
240 240 $ hg status -A largeX
241 241 A largeX
242 242 $ cat .hglf/largeX
243 243
244 244 $ hg status -A largeY
245 245 ? largeY
246 246 $ test -f .hglf/largeY
247 247 [1]
248 248 $ rm largeY
249 249
250 250 Test that "hg rollback" restores standins correctly
251 251
252 252 $ hg commit -m 'will be rollback-ed soon'
253 253 $ hg update -q -C 2
254 254 $ cat large1
255 255 large1
256 256 $ cat .hglf/large1
257 257 4669e532d5b2c093a78eca010077e708a071bb64
258 258 $ cat large2
259 259 large2 in #2
260 260 $ cat .hglf/large2
261 261 3cfce6277e7668985707b6887ce56f9f62f6ccd9
262 262
263 263 $ hg rollback -q -f
264 264 $ cat large1
265 265 large1
266 266 $ cat .hglf/large1
267 267 4669e532d5b2c093a78eca010077e708a071bb64
268 268 $ cat large2
269 269 large2 in #2
270 270 $ cat .hglf/large2
271 271 3cfce6277e7668985707b6887ce56f9f62f6ccd9
272 272
273 273 (rollback the parent of the working directory, when the parent of it
274 274 is not branch-tip)
275 275
276 276 $ hg update -q -C 1
277 277 $ cat .hglf/large1
278 278 58e24f733a964da346e2407a2bee99d9001184f5
279 279 $ cat .hglf/large2
280 280 1deebade43c8c498a3c8daddac0244dc55d1331d
281 281
282 282 $ echo normalX > normalX
283 283 $ hg add normalX
284 284 $ hg commit -m 'will be rollback-ed soon'
285 285 $ hg rollback -q
286 286
287 287 $ cat .hglf/large1
288 288 58e24f733a964da346e2407a2bee99d9001184f5
289 289 $ cat .hglf/large2
290 290 1deebade43c8c498a3c8daddac0244dc55d1331d
291 291 $ rm normalX
292 292
293 293 Test that "hg status" shows status of largefiles correctly just after
294 294 automated commit like rebase/transplant
295 295
296 296 $ cat >> .hg/hgrc <<EOF
297 297 > [extensions]
298 298 > rebase =
299 299 > strip =
300 300 > transplant =
301 301 > EOF
302 302 $ hg update -q -C 1
303 303 $ hg remove large1
304 304 $ echo largeX > largeX
305 305 $ hg add --large largeX
306 306 $ hg commit -m '#4'
307 307
308 308 $ hg rebase -s 1 -d 2 --keep
309 309 rebasing 1:72518492caa6 "#1"
310 rebasing 4:07d6153b5c04 "#4" (tip)
310 rebasing 4:07d6153b5c04 tip "#4"
311 311
312 312 $ hg status -A large1
313 313 large1: $ENOENT$
314 314
315 315 $ hg status -A largeX
316 316 C largeX
317 317 $ hg strip -q 5
318 318
319 319 $ hg update -q -C 2
320 320 $ hg transplant -q 1 4
321 321
322 322 $ hg status -A large1
323 323 large1: $ENOENT$
324 324
325 325 $ hg status -A largeX
326 326 C largeX
327 327 $ hg strip -q 5
328 328
329 329 $ hg update -q -C 2
330 330 $ hg transplant -q --merge 1 --merge 4
331 331
332 332 $ hg status -A large1
333 333 large1: $ENOENT$
334 334
335 335 $ hg status -A largeX
336 336 C largeX
337 337 $ hg strip -q 5
338 338
339 339 Test that linear merge can detect modification (and conflict) correctly
340 340
341 341 (linear merge without conflict)
342 342
343 343 $ echo 'large2 for linear merge (no conflict)' > large2
344 344 $ hg update 3 --config debug.dirstate.delaywrite=2
345 345 getting changed largefiles
346 346 1 largefiles updated, 0 removed
347 347 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
348 348 $ hg status -A large2
349 349 M large2
350 350 $ cat large2
351 351 large2 for linear merge (no conflict)
352 352 $ cat .hglf/large2
353 353 9c4bf8f1b33536d6e5f89447e10620cfe52ea710
354 354
355 355 (linear merge with conflict, choosing "other")
356 356
357 357 $ hg update -q -C 2
358 358 $ echo 'large1 for linear merge (conflict)' > large1
359 359 $ hg update 3 --config ui.interactive=True <<EOF
360 360 > o
361 361 > EOF
362 362 largefile large1 has a merge conflict
363 363 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
364 364 you can keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b.
365 365 what do you want to do? o
366 366 getting changed largefiles
367 367 1 largefiles updated, 0 removed
368 368 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
369 369 $ hg status -A large1
370 370 C large1
371 371 $ cat large1
372 372 large1 in #3
373 373 $ cat .hglf/large1
374 374 e5bb990443d6a92aaf7223813720f7566c9dd05b
375 375
376 376 (linear merge with conflict, choosing "local")
377 377
378 378 $ hg update -q -C 2
379 379 $ echo 'large1 for linear merge (conflict)' > large1
380 380 $ hg update 3 --config debug.dirstate.delaywrite=2
381 381 largefile large1 has a merge conflict
382 382 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
383 383 you can keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b.
384 384 what do you want to do? l
385 385 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
386 386 $ hg status -A large1
387 387 M large1
388 388 $ cat large1
389 389 large1 for linear merge (conflict)
390 390 $ cat .hglf/large1
391 391 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
392 392
393 393 Test a linear merge to a revision containing same-name normal file
394 394
395 395 $ hg update -q -C 3
396 396 $ hg remove large2
397 397 $ echo 'large2 as normal file' > large2
398 398 $ hg add large2
399 399 $ echo 'large3 as normal file' > large3
400 400 $ hg add large3
401 401 $ hg commit -m '#5'
402 402 $ hg manifest
403 403 .hglf/large1
404 404 large2
405 405 large3
406 406 normal1
407 407
408 408 (modified largefile is already switched to normal)
409 409
410 410 $ hg update -q -C 2
411 411 $ echo 'modified large2 for linear merge' > large2
412 412 $ hg update -q 5
413 413 remote turned local largefile large2 into a normal file
414 414 keep (l)argefile or use (n)ormal file? l
415 415 $ hg debugdirstate --no-dates | grep large2
416 416 a 0 -1 unset .hglf/large2
417 417 r 0 0 set large2
418 418 $ hg status -A large2
419 419 A large2
420 420 $ cat large2
421 421 modified large2 for linear merge
422 422
423 423 (added largefile is already committed as normal)
424 424
425 425 $ hg update -q -C 2
426 426 $ echo 'large3 as large file for linear merge' > large3
427 427 $ hg add --large large3
428 428 $ hg update -q 5
429 429 remote turned local largefile large3 into a normal file
430 430 keep (l)argefile or use (n)ormal file? l
431 431 $ hg debugdirstate --no-dates | grep large3
432 432 a 0 -1 unset .hglf/large3
433 433 r 0 0 set large3
434 434 $ hg status -A large3
435 435 A large3
436 436 $ cat large3
437 437 large3 as large file for linear merge
438 438 $ rm -f large3 .hglf/large3
439 439
440 440 Test that the internal linear merging works correctly
441 441 (both heads are stripped to keep pairing of revision number and commit log)
442 442
443 443 $ hg update -q -C 2
444 444 $ hg strip 3 4
445 445 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/9530e27857f7-2e7b195d-backup.hg
446 446 $ mv .hg/strip-backup/9530e27857f7-2e7b195d-backup.hg $TESTTMP
447 447
448 448 (internal linear merging at "hg pull --update")
449 449
450 450 $ echo 'large1 for linear merge (conflict)' > large1
451 451 $ echo 'large2 for linear merge (conflict with normal file)' > large2
452 452 $ hg pull --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-2e7b195d-backup.hg
453 453 pulling from $TESTTMP/9530e27857f7-2e7b195d-backup.hg
454 454 searching for changes
455 455 adding changesets
456 456 adding manifests
457 457 adding file changes
458 458 added 3 changesets with 5 changes to 5 files
459 459 new changesets 9530e27857f7:d65e59e952a9 (3 drafts)
460 460 remote turned local largefile large2 into a normal file
461 461 keep (l)argefile or use (n)ormal file? l
462 462 largefile large1 has a merge conflict
463 463 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
464 464 you can keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b.
465 465 what do you want to do? l
466 466 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
467 467 updated to "d65e59e952a9: #5"
468 468 1 other heads for branch "default"
469 469
470 470 $ hg status -A large1
471 471 M large1
472 472 $ cat large1
473 473 large1 for linear merge (conflict)
474 474 $ cat .hglf/large1
475 475 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
476 476 $ hg status -A large2
477 477 A large2
478 478 $ cat large2
479 479 large2 for linear merge (conflict with normal file)
480 480 $ cat .hglf/large2
481 481 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
482 482
483 483 (internal linear merging at "hg unbundle --update")
484 484
485 485 $ hg update -q -C 2
486 486 $ hg rollback -q
487 487
488 488 $ echo 'large1 for linear merge (conflict)' > large1
489 489 $ echo 'large2 for linear merge (conflict with normal file)' > large2
490 490 $ hg unbundle --update --config debug.dirstate.delaywrite=2 $TESTTMP/9530e27857f7-2e7b195d-backup.hg
491 491 adding changesets
492 492 adding manifests
493 493 adding file changes
494 494 added 3 changesets with 5 changes to 5 files
495 495 new changesets 9530e27857f7:d65e59e952a9 (3 drafts)
496 496 remote turned local largefile large2 into a normal file
497 497 keep (l)argefile or use (n)ormal file? l
498 498 largefile large1 has a merge conflict
499 499 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
500 500 you can keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b.
501 501 what do you want to do? l
502 502 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
503 503 updated to "d65e59e952a9: #5"
504 504 1 other heads for branch "default"
505 505
506 506 $ hg status -A large1
507 507 M large1
508 508 $ cat large1
509 509 large1 for linear merge (conflict)
510 510 $ cat .hglf/large1
511 511 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
512 512 $ hg status -A large2
513 513 A large2
514 514 $ cat large2
515 515 large2 for linear merge (conflict with normal file)
516 516 $ cat .hglf/large2
517 517 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
518 518
519 519 (internal linear merging in subrepo at "hg update")
520 520
521 521 $ cd ..
522 522 $ hg init subparent
523 523 $ cd subparent
524 524
525 525 $ hg clone -q -u 2 ../repo sub
526 526 $ cat > .hgsub <<EOF
527 527 > sub = sub
528 528 > EOF
529 529 $ hg add .hgsub
530 530 $ hg commit -m '#0@parent'
531 531 $ cat .hgsubstate
532 532 f74e50bd9e5594b7cf1e6c5cbab86ddd25f3ca2f sub
533 533 $ hg -R sub update -q
534 534 $ hg commit -m '#1@parent'
535 535 $ cat .hgsubstate
536 536 d65e59e952a9638e2ce863b41a420ca723dd3e8d sub
537 537 $ hg update -q 0
538 538
539 539 $ echo 'large1 for linear merge (conflict)' > sub/large1
540 540 $ echo 'large2 for linear merge (conflict with normal file)' > sub/large2
541 541 $ hg update --config ui.interactive=True --config debug.dirstate.delaywrite=2 <<EOF
542 542 > m
543 543 > r
544 544 > l
545 545 > l
546 546 > EOF
547 547 subrepository sub diverged (local revision: f74e50bd9e55, remote revision: d65e59e952a9)
548 548 you can (m)erge, keep (l)ocal [working copy] or keep (r)emote [destination].
549 549 what do you want to do? m
550 550 subrepository sources for sub differ (in checked out version)
551 551 you can use (l)ocal source (f74e50bd9e55) or (r)emote source (d65e59e952a9).
552 552 what do you want to do? r
553 553 remote turned local largefile large2 into a normal file
554 554 keep (l)argefile or use (n)ormal file? l
555 555 largefile large1 has a merge conflict
556 556 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
557 557 you can keep (l)ocal ba94c2efe5b7c5e0af8d189295ce00553b0612b7 or take (o)ther e5bb990443d6a92aaf7223813720f7566c9dd05b.
558 558 what do you want to do? l
559 559 2 files updated, 1 files merged, 0 files removed, 0 files unresolved
560 560 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
561 561
562 562 $ hg -R sub status -A sub/large1
563 563 M sub/large1
564 564 $ cat sub/large1
565 565 large1 for linear merge (conflict)
566 566 $ cat sub/.hglf/large1
567 567 ba94c2efe5b7c5e0af8d189295ce00553b0612b7
568 568 $ hg -R sub status -A sub/large2
569 569 A sub/large2
570 570 $ cat sub/large2
571 571 large2 for linear merge (conflict with normal file)
572 572 $ cat sub/.hglf/large2
573 573 d7591fe9be0f6227d90bddf3e4f52ff41fc1f544
574 574
575 575 $ cd ..
576 576 $ cd repo
577 577
578 578 Test that rebase updates largefiles in the working directory even if
579 579 it is aborted by conflict.
580 580
581 581 $ hg update -q -C 3
582 582 $ cat .hglf/large1
583 583 e5bb990443d6a92aaf7223813720f7566c9dd05b
584 584 $ cat large1
585 585 large1 in #3
586 586 $ hg rebase -s 1 -d 3 --keep --config ui.interactive=True <<EOF
587 587 > o
588 588 > EOF
589 589 rebasing 1:72518492caa6 "#1"
590 590 largefile large1 has a merge conflict
591 591 ancestor was 4669e532d5b2c093a78eca010077e708a071bb64
592 592 you can keep (l)ocal e5bb990443d6a92aaf7223813720f7566c9dd05b or take (o)ther 58e24f733a964da346e2407a2bee99d9001184f5.
593 593 what do you want to do? o
594 594 merging normal1
595 595 warning: conflicts while merging normal1! (edit, then use 'hg resolve --mark')
596 596 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
597 597 [1]
598 598 $ cat .hglf/large1
599 599 58e24f733a964da346e2407a2bee99d9001184f5
600 600 $ cat large1
601 601 large1 in #1
602 602 $ rm normal1.orig
603 603
604 604 Test that rebase updates standins for manually modified largefiles at
605 605 the 1st commit of resuming.
606 606
607 607 $ echo "manually modified before 'hg rebase --continue'" > large1
608 608 $ hg resolve -m normal1
609 609 (no more unresolved files)
610 610 continue: hg rebase --continue
611 611 $ hg rebase --continue --config ui.interactive=True <<EOF
612 612 > c
613 613 > EOF
614 614 rebasing 1:72518492caa6 "#1"
615 615 rebasing 4:07d6153b5c04 "#4"
616 616 file '.hglf/large1' was deleted in other [source] but was modified in local [dest].
617 617 You can use (c)hanged version, (d)elete, or leave (u)nresolved.
618 618 What do you want to do? c
619 619
620 620 $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
621 621 -e5bb990443d6a92aaf7223813720f7566c9dd05b
622 622 +8a4f783556e7dea21139ca0466eafce954c75c13
623 623 $ rm -f large1
624 624 $ hg update -q -C tip
625 625 $ cat large1
626 626 manually modified before 'hg rebase --continue'
627 627
628 628 Test that transplant updates largefiles, of which standins are safely
629 629 changed, even if it is aborted by conflict of other.
630 630
631 631 $ hg update -q -C 5
632 632 $ cat .hglf/large1
633 633 e5bb990443d6a92aaf7223813720f7566c9dd05b
634 634 $ cat large1
635 635 large1 in #3
636 636 $ hg diff -c 4 .hglf/largeX | grep '^[+-][0-9a-z]'
637 637 +fa44618ea25181aff4f48b70428294790cec9f61
638 638 $ hg transplant 4
639 639 applying 07d6153b5c04
640 640 patching file .hglf/large1
641 641 Hunk #1 FAILED at 0
642 642 1 out of 1 hunks FAILED -- saving rejects to file .hglf/large1.rej
643 643 patch failed to apply
644 644 abort: fix up the working directory and run hg transplant --continue
645 645 [255]
646 646 $ hg status -A large1
647 647 C large1
648 648 $ cat .hglf/large1
649 649 e5bb990443d6a92aaf7223813720f7566c9dd05b
650 650 $ cat large1
651 651 large1 in #3
652 652 $ hg status -A largeX
653 653 A largeX
654 654 $ cat .hglf/largeX
655 655 fa44618ea25181aff4f48b70428294790cec9f61
656 656 $ cat largeX
657 657 largeX
658 658
659 659 Test that transplant updates standins for manually modified largefiles
660 660 at the 1st commit of resuming.
661 661
662 662 $ echo "manually modified before 'hg transplant --continue'" > large1
663 663 $ hg transplant --continue
664 664 07d6153b5c04 transplanted as f1bf30eb88cc
665 665 $ hg diff -c tip .hglf/large1 | grep '^[+-][0-9a-z]'
666 666 -e5bb990443d6a92aaf7223813720f7566c9dd05b
667 667 +6a4f36d4075fbe0f30ec1d26ca44e63c05903671
668 668 $ rm -f large1
669 669 $ hg update -q -C tip
670 670 $ cat large1
671 671 manually modified before 'hg transplant --continue'
672 672
673 673 Test that "hg status" doesn't show removal of largefiles not managed
674 674 in the target context.
675 675
676 676 $ hg update -q -C 4
677 677 $ hg remove largeX
678 678 $ hg status -A largeX
679 679 R largeX
680 680 $ hg status -A --rev '.^1' largeX
681 681
682 682 #if execbit
683 683
684 684 Test that "hg status" against revisions other than parent notices exec
685 685 bit changes of largefiles.
686 686
687 687 $ hg update -q -C 4
688 688
689 689 (the case that large2 doesn't have exec bit in the target context but
690 690 in the working context)
691 691
692 692 $ chmod +x large2
693 693 $ hg status -A --rev 0 large2
694 694 M large2
695 695 $ hg commit -m 'chmod +x large2'
696 696
697 697 (the case that large2 has exec bit in the target context but not in
698 698 the working context)
699 699
700 700 $ echo dummy > dummy
701 701 $ hg add dummy
702 702 $ hg commit -m 'revision for separation'
703 703 $ chmod -x large2
704 704 $ hg status -A --rev '.^1' large2
705 705 M large2
706 706
707 707 #else
708 708
709 709 Test that "hg status" against revisions other than parent ignores exec
710 710 bit correctly on the platform being unaware of it.
711 711
712 712 $ hg update -q -C 4
713 713
714 714 $ cat > ../exec-bit.patch <<EOF
715 715 > # HG changeset patch
716 716 > # User test
717 717 > # Date 0 0
718 718 > # Thu Jan 01 00:00:00 1970 +0000
719 719 > # Node ID be1b433a65b12b27b5519d92213e14f7e1769b90
720 720 > # Parent 07d6153b5c04313efb75deec9ba577de7faeb727
721 721 > chmod +x large2
722 722 >
723 723 > diff --git a/.hglf/large2 b/.hglf/large2
724 724 > old mode 100644
725 725 > new mode 100755
726 726 > EOF
727 727 $ hg import --exact --bypass ../exec-bit.patch
728 728 applying ../exec-bit.patch
729 729 $ hg status -A --rev tip large2
730 730 C large2
731 731
732 732 #endif
733 733
734 734 The fileset revset is evaluated for each revision, instead of once on wdir(),
735 735 and then patterns matched on each revision. Here, no exec bits are set in
736 736 wdir(), but a matching revision is detected.
737 737
738 738 (Teach large2 is not an executable. Maybe this is a bug of largefiles.)
739 739 #if execbit
740 740 $ chmod -x .hglf/large2
741 741 #endif
742 742
743 743 $ hg files 'set:exec()'
744 744 [1]
745 745 $ hg log -qr 'file("set:exec()")'
746 746 9:be1b433a65b1
747 747
748 748 Test a fatal error interrupting an update. Verify that status report dirty
749 749 files correctly after an interrupted update. Also verify that checking all
750 750 hashes reveals it isn't clean.
751 751
752 752 Start with clean dirstates:
753 753 $ hg up --quiet --clean --rev "8^"
754 754 $ sleep 1
755 755 $ hg st
756 756 Update standins without updating largefiles - large1 is modified and largeX is
757 757 added:
758 758 $ cat << EOF > ../crashupdatelfiles.py
759 759 > import hgext.largefiles.lfutil
760 760 > def getlfilestoupdate(oldstandins, newstandins):
761 761 > raise SystemExit(7)
762 762 > hgext.largefiles.lfutil.getlfilestoupdate = getlfilestoupdate
763 763 > EOF
764 764 $ hg up -Cr "8" --config extensions.crashupdatelfiles=../crashupdatelfiles.py
765 765 [7]
766 766 Check large1 content and status ... and that update will undo modifications:
767 767 $ cat large1
768 768 large1 in #3
769 769 $ hg st
770 770 M large1
771 771 ! largeX
772 772 $ hg up -Cr .
773 773 getting changed largefiles
774 774 2 largefiles updated, 0 removed
775 775 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
776 776 $ cat large1
777 777 manually modified before 'hg transplant --continue'
778 778 $ hg st
779 779 Force largefiles rehashing and check that all changes have been caught by
780 780 status and update:
781 781 $ rm .hg/largefiles/dirstate
782 782 $ hg st
783 783
784 784 $ cd ..
785 785
786 786 Test that "hg convert" avoids copying largefiles from the working
787 787 directory into store, because "hg convert" doesn't update largefiles
788 788 in the working directory (removing files under ".cache/largefiles"
789 789 forces "hg convert" to copy corresponding largefiles)
790 790
791 791 $ cat >> $HGRCPATH <<EOF
792 792 > [extensions]
793 793 > convert =
794 794 > EOF
795 795
796 796 $ rm $TESTTMP/.cache/largefiles/6a4f36d4075fbe0f30ec1d26ca44e63c05903671
797 797 $ hg convert -q repo repo.converted
@@ -1,74 +1,74 b''
1 1
2 2 $ . "$TESTDIR/narrow-library.sh"
3 3
4 4 create full repo
5 5
6 6 $ hg init master
7 7 $ cd master
8 8
9 9 $ mkdir inside
10 10 $ echo inside > inside/f1
11 11 $ mkdir outside
12 12 $ echo outside > outside/f2
13 13 $ hg ci -Aqm 'initial'
14 14
15 15 $ hg mv outside/f2 inside/f2
16 16 $ hg ci -qm 'move f2 from outside'
17 17
18 18 $ echo modified > inside/f2
19 19 $ hg ci -qm 'modify inside/f2'
20 20
21 21 $ mkdir outside
22 22 $ echo new > outside/f3
23 23 $ hg ci -Aqm 'add outside/f3'
24 24 $ cd ..
25 25
26 26 $ hg clone --narrow ssh://user@dummy/master narrow --include inside -r 2
27 27 adding changesets
28 28 adding manifests
29 29 adding file changes
30 30 added 3 changesets with 3 changes to 2 files
31 31 new changesets *:* (glob)
32 32 updating to branch default
33 33 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
34 34 $ cd narrow
35 35
36 36 $ hg co 'desc("move f2")'
37 37 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
38 38 $ hg status
39 39 $ hg diff
40 40 $ hg diff --change . --git
41 41 diff --git a/inside/f2 b/inside/f2
42 42 new file mode 100644
43 43 --- /dev/null
44 44 +++ b/inside/f2
45 45 @@ -0,0 +1,1 @@
46 46 +outside
47 47
48 48 $ hg log --follow inside/f2 -r tip
49 49 changeset: 2:bcfb756e0ca9
50 50 tag: tip
51 51 user: test
52 52 date: Thu Jan 01 00:00:00 1970 +0000
53 53 summary: modify inside/f2
54 54
55 55 changeset: 1:5a016133b2bb
56 56 user: test
57 57 date: Thu Jan 01 00:00:00 1970 +0000
58 58 summary: move f2 from outside
59 59
60 60 $ echo new > inside/f4
61 61 $ hg ci -Aqm 'add inside/f4'
62 62 $ hg pull -q
63 63 $ hg --config extensions.rebase= rebase -d tip
64 64 rebasing 3:4f84b666728c "add inside/f4"
65 65 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/4f84b666728c-4269b76e-rebase.hg
66 66
67 67 $ hg co -q 0
68 68 $ echo modified > inside/f1
69 69 $ hg ci -qm 'modify inside/f1'
70 70 $ echo new > inside/f5
71 71 $ hg ci -Aqm 'add inside/f5'
72 72 $ hg --config extensions.rebase= rebase -d 'public()' -r .
73 rebasing 6:610b60178c28 "add inside/f5" (tip)
73 rebasing 6:610b60178c28 tip "add inside/f5"
74 74 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/610b60178c28-65716a78-rebase.hg
@@ -1,99 +1,99 b''
1 1 #testcases continuecommand continueflag
2 2 #if continueflag
3 3 $ cat >> $HGRCPATH <<EOF
4 4 > [alias]
5 5 > continue = rebase --continue
6 6 > EOF
7 7 #endif
8 8
9 9 $ . "$TESTDIR/narrow-library.sh"
10 10
11 11 create full repo
12 12
13 13 $ hg init master
14 14 $ cd master
15 15
16 16 $ mkdir inside
17 17 $ echo inside1 > inside/f1
18 18 $ echo inside2 > inside/f2
19 19 $ mkdir outside
20 20 $ echo outside1 > outside/f1
21 21 $ echo outside2 > outside/f2
22 22 $ hg ci -Aqm 'initial'
23 23
24 24 $ echo modified > inside/f1
25 25 $ hg ci -qm 'modify inside/f1'
26 26
27 27 $ hg update -q 0
28 28 $ echo modified2 > inside/f2
29 29 $ hg ci -qm 'modify inside/f2'
30 30
31 31 $ hg update -q 0
32 32 $ echo modified > outside/f1
33 33 $ hg ci -qm 'modify outside/f1'
34 34
35 35 $ hg update -q 0
36 36 $ echo modified2 > outside/f1
37 37 $ hg ci -qm 'conflicting outside/f1'
38 38
39 39 $ cd ..
40 40
41 41 $ hg clone --narrow ssh://user@dummy/master narrow --include inside
42 42 requesting all changes
43 43 adding changesets
44 44 adding manifests
45 45 adding file changes
46 46 added 5 changesets with 4 changes to 2 files (+3 heads)
47 47 new changesets *:* (glob)
48 48 updating to branch default
49 49 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
50 50 $ cd narrow
51 51 $ cat >> $HGRCPATH <<EOF
52 52 > [extensions]
53 53 > rebase=
54 54 > EOF
55 55
56 56 $ hg update -q 0
57 57
58 58 Can rebase onto commit where no files outside narrow spec are involved
59 59
60 60 $ hg update -q 0
61 61 $ echo modified > inside/f2
62 62 $ hg ci -qm 'modify inside/f2'
63 63 $ hg rebase -d 'desc("modify inside/f1")'
64 rebasing 5:c2f36d04e05d "modify inside/f2" (tip)
64 rebasing 5:c2f36d04e05d tip "modify inside/f2"
65 65 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
66 66
67 67 Can rebase onto conflicting changes inside narrow spec
68 68
69 69 $ hg update -q 0
70 70 $ echo conflicting > inside/f1
71 71 $ hg ci -qm 'conflicting inside/f1'
72 72 $ hg rebase -d 'desc("modify inside/f1")' 2>&1 | egrep -v '(warning:|incomplete!)'
73 rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip)
73 rebasing 6:cdce97fbf653 tip "conflicting inside/f1"
74 74 merging inside/f1
75 75 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
76 76 $ echo modified3 > inside/f1
77 77 $ hg resolve -m 2>&1 | grep -v continue:
78 78 (no more unresolved files)
79 79 $ hg continue
80 rebasing 6:cdce97fbf653 "conflicting inside/f1" (tip)
80 rebasing 6:cdce97fbf653 tip "conflicting inside/f1"
81 81 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
82 82
83 83 Can rebase onto non-conflicting changes outside narrow spec
84 84
85 85 $ hg update -q 0
86 86 $ echo modified > inside/f2
87 87 $ hg ci -qm 'modify inside/f2'
88 88 $ hg rebase -d 'desc("modify outside/f1")'
89 rebasing 7:c2f36d04e05d "modify inside/f2" (tip)
89 rebasing 7:c2f36d04e05d tip "modify inside/f2"
90 90 saved backup bundle to $TESTTMP/narrow/.hg/strip-backup/*-rebase.hg (glob)
91 91
92 92 Rebase interrupts on conflicting changes outside narrow spec
93 93
94 94 $ hg update -q 'desc("conflicting outside/f1")'
95 95 $ hg phase -f -d .
96 96 $ hg rebase -d 'desc("modify outside/f1")'
97 97 rebasing 4:707c035aadb6 "conflicting outside/f1"
98 98 abort: conflict in file 'outside/f1' is outside narrow clone
99 99 [255]
@@ -1,167 +1,167 b''
1 1 Test the 'effect-flags' feature
2 2
3 3 Global setup
4 4 ============
5 5
6 6 $ . $TESTDIR/testlib/obsmarker-common.sh
7 7 $ cat >> $HGRCPATH <<EOF
8 8 > [ui]
9 9 > interactive = true
10 10 > [phases]
11 11 > publish=False
12 12 > [extensions]
13 13 > rebase =
14 14 > [experimental]
15 15 > evolution = all
16 16 > evolution.effect-flags = 1
17 17 > EOF
18 18
19 19 $ hg init $TESTTMP/effect-flags
20 20 $ cd $TESTTMP/effect-flags
21 21 $ mkcommit ROOT
22 22
23 23 amend touching the description only
24 24 -----------------------------------
25 25
26 26 $ mkcommit A0
27 27 $ hg commit --amend -m "A1"
28 28
29 29 check result
30 30
31 31 $ hg debugobsolete --rev .
32 32 471f378eab4c5e25f6c77f785b27c936efb22874 fdf9bde5129a28d4548fadd3f62b265cdd3b7a2e 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '1', 'operation': 'amend', 'user': 'test'}
33 33
34 34 amend touching the user only
35 35 ----------------------------
36 36
37 37 $ mkcommit B0
38 38 $ hg commit --amend -u "bob <bob@bob.com>"
39 39
40 40 check result
41 41
42 42 $ hg debugobsolete --rev .
43 43 ef4a313b1e0ade55718395d80e6b88c5ccd875eb 5485c92d34330dac9d7a63dc07e1e3373835b964 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '16', 'operation': 'amend', 'user': 'test'}
44 44
45 45 amend touching the date only
46 46 ----------------------------
47 47
48 48 $ mkcommit B1
49 49 $ hg commit --amend -d "42 0"
50 50
51 51 check result
52 52
53 53 $ hg debugobsolete --rev .
54 54 2ef0680ff45038ac28c9f1ff3644341f54487280 4dd84345082e9e5291c2e6b3f335bbf8bf389378 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '32', 'operation': 'amend', 'user': 'test'}
55 55
56 56 amend touching the branch only
57 57 ----------------------------
58 58
59 59 $ mkcommit B2
60 60 $ hg branch my-branch
61 61 marked working directory as branch my-branch
62 62 (branches are permanent and global, did you want a bookmark?)
63 63 $ hg commit --amend
64 64
65 65 check result
66 66
67 67 $ hg debugobsolete --rev .
68 68 bd3db8264ceebf1966319f5df3be7aac6acd1a8e 14a01456e0574f0e0a0b15b2345486a6364a8d79 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '64', 'operation': 'amend', 'user': 'test'}
69 69
70 70 $ hg up default
71 71 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
72 72
73 73 rebase (parents change)
74 74 -----------------------
75 75
76 76 $ mkcommit C0
77 77 $ mkcommit D0
78 78 $ hg rebase -r . -d 'desc(B0)'
79 rebasing 10:c85eff83a034 "D0" (tip)
79 rebasing 10:c85eff83a034 tip "D0"
80 80
81 81 check result
82 82
83 83 $ hg debugobsolete --rev .
84 84 c85eff83a0340efd9da52b806a94c350222f3371 da86aa2f19a30d6686b15cae15c7b6c908ec9699 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
85 85
86 86 amend touching the diff
87 87 -----------------------
88 88
89 89 $ mkcommit E0
90 90 $ echo 42 >> E0
91 91 $ hg commit --amend
92 92
93 93 check result
94 94
95 95 $ hg debugobsolete --rev .
96 96 ebfe0333e0d96f68a917afd97c0a0af87f1c3b5f 75781fdbdbf58a987516b00c980bccda1e9ae588 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '8', 'operation': 'amend', 'user': 'test'}
97 97
98 98 amend with multiple effect (desc and meta)
99 99 -------------------------------------------
100 100
101 101 $ mkcommit F0
102 102 $ hg branch my-other-branch
103 103 marked working directory as branch my-other-branch
104 104 $ hg commit --amend -m F1 -u "bob <bob@bob.com>" -d "42 0"
105 105
106 106 check result
107 107
108 108 $ hg debugobsolete --rev .
109 109 fad47e5bd78e6aa4db1b5a0a1751bc12563655ff a94e0fd5f1c81d969381a76eb0d37ce499a44fae 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '113', 'operation': 'amend', 'user': 'test'}
110 110
111 111 rebase not touching the diff
112 112 ----------------------------
113 113
114 114 $ cat << EOF > H0
115 115 > 0
116 116 > 1
117 117 > 2
118 118 > 3
119 119 > 4
120 120 > 5
121 121 > 6
122 122 > 7
123 123 > 8
124 124 > 9
125 125 > 10
126 126 > EOF
127 127 $ hg add H0
128 128 $ hg commit -m 'H0'
129 129 $ echo "H1" >> H0
130 130 $ hg commit -m "H1"
131 131 $ hg up -r "desc(H0)"
132 132 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
133 133 $ cat << EOF > H0
134 134 > H2
135 135 > 0
136 136 > 1
137 137 > 2
138 138 > 3
139 139 > 4
140 140 > 5
141 141 > 6
142 142 > 7
143 143 > 8
144 144 > 9
145 145 > 10
146 146 > EOF
147 147 $ hg commit -m "H2"
148 148 created new head
149 149 $ hg rebase -s "desc(H1)" -d "desc(H2)" -t :merge3
150 150 rebasing 17:b57fed8d8322 "H1"
151 151 merging H0
152 152 $ hg debugobsolete -r tip
153 153 b57fed8d83228a8ae3748d8c3760a77638dd4f8c e509e2eb3df5d131ff7c02350bf2a9edd0c09478 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '4', 'operation': 'rebase', 'user': 'test'}
154 154
155 155 amend closing the branch should be detected as meta change
156 156 ----------------------------------------------------------
157 157
158 158 $ hg branch closedbranch
159 159 marked working directory as branch closedbranch
160 160 $ mkcommit G0
161 161 $ mkcommit I0
162 162 $ hg commit --amend --close-branch
163 163
164 164 check result
165 165
166 166 $ hg debugobsolete -r .
167 167 2f599e54c1c6974299065cdf54e1ad640bfb7b5d 12c6238b5e371eea00fd2013b12edce3f070928b 0 (Thu Jan 01 00:00:00 1970 +0000) {'ef1': '2', 'operation': 'amend', 'user': 'test'}
@@ -1,511 +1,511 b''
1 1 #testcases abortcommand abortflag
2 2 #testcases continuecommand continueflag
3 3
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > rebase=
7 7 >
8 8 > [phases]
9 9 > publish=False
10 10 >
11 11 > [alias]
12 12 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches}\n"
13 13 > EOF
14 14
15 15 #if abortflag
16 16 $ cat >> $HGRCPATH <<EOF
17 17 > [alias]
18 18 > abort = rebase --abort
19 19 > EOF
20 20 #endif
21 21
22 22 #if continueflag
23 23 $ cat >> $HGRCPATH <<EOF
24 24 > [alias]
25 25 > continue = rebase --continue
26 26 > EOF
27 27 #endif
28 28
29 29 $ hg init a
30 30 $ cd a
31 31
32 32 $ touch .hg/rebasestate
33 33 $ hg sum
34 34 parent: -1:000000000000 tip (empty repository)
35 35 branch: default
36 36 commit: (clean)
37 37 update: (current)
38 38 abort: .hg/rebasestate is incomplete
39 39 [255]
40 40 $ rm .hg/rebasestate
41 41
42 42 $ echo c1 > common
43 43 $ hg add common
44 44 $ hg ci -m C1
45 45
46 46 $ echo c2 >> common
47 47 $ hg ci -m C2
48 48
49 49 $ echo c3 >> common
50 50 $ hg ci -m C3
51 51
52 52 $ hg up -q -C 1
53 53
54 54 $ echo l1 >> extra
55 55 $ hg add extra
56 56 $ hg ci -m L1
57 57 created new head
58 58
59 59 $ sed -e 's/c2/l2/' common > common.new
60 60 $ mv common.new common
61 61 $ hg ci -m L2
62 62
63 63 $ hg phase --force --secret 2
64 64
65 65 $ hg tglog
66 66 @ 4:draft 'L2'
67 67 |
68 68 o 3:draft 'L1'
69 69 |
70 70 | o 2:secret 'C3'
71 71 |/
72 72 o 1:draft 'C2'
73 73 |
74 74 o 0:draft 'C1'
75 75
76 76
77 77 Conflicting rebase:
78 78
79 79 $ hg rebase -s 3 -d 2
80 80 rebasing 3:3163e20567cc "L1"
81 rebasing 4:46f0b057b5c0 "L2" (tip)
81 rebasing 4:46f0b057b5c0 tip "L2"
82 82 merging common
83 83 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
84 84 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
85 85 [1]
86 86
87 87 Insert unsupported advisory merge record:
88 88
89 89 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -x
90 90 $ hg debugmergestate
91 91 local (dest): 3e046f2ecedb793b97ed32108086edd1a162f8bc
92 92 other (source): 46f0b057b5c061d276b91491c22151f78698abd2
93 93 file: common (state "u")
94 94 local path: common (hash 94c8c21d08740f5da9eaa38d1f175c592692f0d1, flags "")
95 95 ancestor path: common (node de0a666fdd9c1a0b0698b90d85064d8bd34f74b6)
96 96 other path: common (node 2f6411de53677f6f1048fef5bf888d67a342e0a5)
97 97 extra: ancestorlinknode = 3163e20567cc93074fbb7a53c8b93312e59dbf2c
98 98 $ hg resolve -l
99 99 U common
100 100
101 101 Insert unsupported mandatory merge record:
102 102
103 103 $ hg --config extensions.fakemergerecord=$TESTDIR/fakemergerecord.py fakemergerecord -X
104 104 $ hg debugmergestate
105 105 abort: unsupported merge state records: X
106 106 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
107 107 [255]
108 108 $ hg resolve -l
109 109 abort: unsupported merge state records: X
110 110 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
111 111 [255]
112 112 $ hg resolve -ma
113 113 abort: unsupported merge state records: X
114 114 (see https://mercurial-scm.org/wiki/MergeStateRecords for more information)
115 115 [255]
116 116
117 117 Abort (should clear out unsupported merge state):
118 118
119 119 #if abortcommand
120 120 when in dry-run mode
121 121 $ hg abort --dry-run
122 122 rebase in progress, will be aborted
123 123 #endif
124 124
125 125 $ hg abort
126 126 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3e046f2ecedb-6beef7d5-backup.hg
127 127 rebase aborted
128 128 $ hg debugmergestate
129 129 no merge state found
130 130
131 131 $ hg tglog
132 132 @ 4:draft 'L2'
133 133 |
134 134 o 3:draft 'L1'
135 135 |
136 136 | o 2:secret 'C3'
137 137 |/
138 138 o 1:draft 'C2'
139 139 |
140 140 o 0:draft 'C1'
141 141
142 142 Test safety for inconsistent rebase state, which may be created (and
143 143 forgotten) by Mercurial earlier than 2.7. This emulates Mercurial
144 144 earlier than 2.7 by renaming ".hg/rebasestate" temporarily.
145 145
146 146 $ hg rebase -s 3 -d 2
147 147 rebasing 3:3163e20567cc "L1"
148 rebasing 4:46f0b057b5c0 "L2" (tip)
148 rebasing 4:46f0b057b5c0 tip "L2"
149 149 merging common
150 150 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
151 151 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
152 152 [1]
153 153
154 154 $ mv .hg/rebasestate .hg/rebasestate.back
155 155 $ hg update --quiet --clean 2
156 156 $ hg --config extensions.mq= strip --quiet "destination()"
157 157 $ mv .hg/rebasestate.back .hg/rebasestate
158 158
159 159 $ hg continue
160 160 abort: cannot continue inconsistent rebase
161 161 (use "hg rebase --abort" to clear broken state)
162 162 [255]
163 163 $ hg summary | grep '^rebase: '
164 164 rebase: (use "hg rebase --abort" to clear broken state)
165 165 $ hg abort
166 166 rebase aborted (no revision is removed, only broken state is cleared)
167 167
168 168 $ cd ..
169 169
170 170
171 171 Construct new repo:
172 172
173 173 $ hg init b
174 174 $ cd b
175 175
176 176 $ echo a > a
177 177 $ hg ci -Am A
178 178 adding a
179 179
180 180 $ echo b > b
181 181 $ hg ci -Am B
182 182 adding b
183 183
184 184 $ echo c > c
185 185 $ hg ci -Am C
186 186 adding c
187 187
188 188 $ hg up -q 0
189 189
190 190 $ echo b > b
191 191 $ hg ci -Am 'B bis'
192 192 adding b
193 193 created new head
194 194
195 195 $ echo c1 > c
196 196 $ hg ci -Am C1
197 197 adding c
198 198
199 199 $ hg phase --force --secret 1
200 200 $ hg phase --public 1
201 201
202 202 Rebase and abort without generating new changesets:
203 203
204 204 $ hg tglog
205 205 @ 4:draft 'C1'
206 206 |
207 207 o 3:draft 'B bis'
208 208 |
209 209 | o 2:secret 'C'
210 210 | |
211 211 | o 1:public 'B'
212 212 |/
213 213 o 0:public 'A'
214 214
215 215 $ hg rebase -b 4 -d 2
216 216 rebasing 3:a6484957d6b9 "B bis"
217 217 note: not rebasing 3:a6484957d6b9 "B bis", its destination already has all its changes
218 rebasing 4:145842775fec "C1" (tip)
218 rebasing 4:145842775fec tip "C1"
219 219 merging c
220 220 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
221 221 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
222 222 [1]
223 223
224 224 $ hg tglog
225 225 % 4:draft 'C1'
226 226 |
227 227 o 3:draft 'B bis'
228 228 |
229 229 | @ 2:secret 'C'
230 230 | |
231 231 | o 1:public 'B'
232 232 |/
233 233 o 0:public 'A'
234 234
235 235 $ hg rebase -a
236 236 rebase aborted
237 237
238 238 $ hg tglog
239 239 @ 4:draft 'C1'
240 240 |
241 241 o 3:draft 'B bis'
242 242 |
243 243 | o 2:secret 'C'
244 244 | |
245 245 | o 1:public 'B'
246 246 |/
247 247 o 0:public 'A'
248 248
249 249
250 250 $ cd ..
251 251
252 252 rebase abort should not leave working copy in a merge state if tip-1 is public
253 253 (issue4082)
254 254
255 255 $ hg init abortpublic
256 256 $ cd abortpublic
257 257 $ echo a > a && hg ci -Aqm a
258 258 $ hg book master
259 259 $ hg book foo
260 260 $ echo b > b && hg ci -Aqm b
261 261 $ hg up -q master
262 262 $ echo c > c && hg ci -Aqm c
263 263 $ hg phase -p -r .
264 264 $ hg up -q foo
265 265 $ echo C > c && hg ci -Aqm C
266 266 $ hg log -G --template "{rev} {desc} {bookmarks}"
267 267 @ 3 C foo
268 268 |
269 269 | o 2 c master
270 270 | |
271 271 o | 1 b
272 272 |/
273 273 o 0 a
274 274
275 275
276 276 $ hg rebase -d master -r foo
277 rebasing 3:6c0f977a22d8 "C" (foo tip)
277 rebasing 3:6c0f977a22d8 tip foo "C"
278 278 merging c
279 279 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
280 280 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
281 281 [1]
282 282 $ hg abort
283 283 rebase aborted
284 284 $ hg log -G --template "{rev} {desc} {bookmarks}"
285 285 @ 3 C foo
286 286 |
287 287 | o 2 c master
288 288 | |
289 289 o | 1 b
290 290 |/
291 291 o 0 a
292 292
293 293 $ cd ..
294 294
295 295 Make sure we don't clobber changes in the working directory when the
296 296 user has somehow managed to update to a different revision (issue4009)
297 297
298 298 $ hg init noupdate
299 299 $ cd noupdate
300 300 $ hg book @
301 301 $ echo original > a
302 302 $ hg add a
303 303 $ hg commit -m a
304 304 $ echo x > b
305 305 $ hg add b
306 306 $ hg commit -m b1
307 307 $ hg up 0
308 308 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
309 309 (leaving bookmark @)
310 310 $ hg book foo
311 311 $ echo y > b
312 312 $ hg add b
313 313 $ hg commit -m b2
314 314 created new head
315 315
316 316 $ hg rebase -d @ -b foo --tool=internal:fail
317 rebasing 2:070cf4580bb5 "b2" (foo tip)
317 rebasing 2:070cf4580bb5 tip foo "b2"
318 318 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
319 319 [1]
320 320
321 321 $ mv .hg/rebasestate ./ # so we're allowed to hg up like in mercurial <2.6.3
322 322 $ hg up -C 0 # user does other stuff in the repo
323 323 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
324 324
325 325 $ mv rebasestate .hg/ # user upgrades to 2.7
326 326
327 327 $ echo new > a
328 328 $ hg up 1 # user gets an error saying to run hg rebase --abort
329 329 abort: rebase in progress
330 330 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
331 331 [255]
332 332
333 333 $ cat a
334 334 new
335 335 $ hg abort
336 336 rebase aborted
337 337 $ cat a
338 338 new
339 339
340 340 $ cd ..
341 341
342 342 test aborting an interrupted series (issue5084)
343 343 $ hg init interrupted
344 344 $ cd interrupted
345 345 $ touch base
346 346 $ hg add base
347 347 $ hg commit -m base
348 348 $ touch a
349 349 $ hg add a
350 350 $ hg commit -m a
351 351 $ echo 1 > a
352 352 $ hg commit -m 1
353 353 $ touch b
354 354 $ hg add b
355 355 $ hg commit -m b
356 356 $ echo 2 >> a
357 357 $ hg commit -m c
358 358 $ touch d
359 359 $ hg add d
360 360 $ hg commit -m d
361 361 $ hg co -q 1
362 362 $ hg rm a
363 363 $ hg commit -m no-a
364 364 created new head
365 365 $ hg co 0
366 366 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
367 367 $ hg log -G --template "{rev} {desc} {bookmarks}"
368 368 o 6 no-a
369 369 |
370 370 | o 5 d
371 371 | |
372 372 | o 4 c
373 373 | |
374 374 | o 3 b
375 375 | |
376 376 | o 2 1
377 377 |/
378 378 o 1 a
379 379 |
380 380 @ 0 base
381 381
382 382 $ hg --config extensions.n=$TESTDIR/failfilemerge.py rebase -s 3 -d tip
383 383 rebasing 3:3a71550954f1 "b"
384 384 rebasing 4:e80b69427d80 "c"
385 385 abort: ^C
386 386 [255]
387 387
388 388 New operations are blocked with the correct state message
389 389
390 390 $ find .hg -name '*state' -prune | sort
391 391 .hg/dirstate
392 392 .hg/merge/state
393 393 .hg/rebasestate
394 394 .hg/undo.backup.dirstate
395 395 .hg/undo.dirstate
396 396 .hg/updatestate
397 397
398 398 $ hg rebase -s 3 -d tip
399 399 abort: rebase in progress
400 400 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
401 401 [255]
402 402 $ hg up .
403 403 abort: rebase in progress
404 404 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
405 405 [255]
406 406 $ hg up -C .
407 407 abort: rebase in progress
408 408 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
409 409 [255]
410 410
411 411 $ hg graft 3
412 412 abort: rebase in progress
413 413 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
414 414 [255]
415 415
416 416 $ hg abort
417 417 saved backup bundle to $TESTTMP/interrupted/.hg/strip-backup/3d8812cf300d-93041a90-backup.hg
418 418 rebase aborted
419 419 $ hg log -G --template "{rev} {desc} {bookmarks}"
420 420 o 6 no-a
421 421 |
422 422 | o 5 d
423 423 | |
424 424 | o 4 c
425 425 | |
426 426 | o 3 b
427 427 | |
428 428 | o 2 1
429 429 |/
430 430 o 1 a
431 431 |
432 432 @ 0 base
433 433
434 434 $ hg summary
435 435 parent: 0:df4f53cec30a
436 436 base
437 437 branch: default
438 438 commit: (clean)
439 439 update: 6 new changesets (update)
440 440 phases: 7 draft
441 441
442 442 $ cd ..
443 443 On the other hand, make sure we *do* clobber changes whenever we
444 444 haven't somehow managed to update the repo to a different revision
445 445 during a rebase (issue4661)
446 446
447 447 $ hg ini yesupdate
448 448 $ cd yesupdate
449 449 $ echo "initial data" > foo.txt
450 450 $ hg add
451 451 adding foo.txt
452 452 $ hg ci -m "initial checkin"
453 453 $ echo "change 1" > foo.txt
454 454 $ hg ci -m "change 1"
455 455 $ hg up 0
456 456 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
457 457 $ echo "conflicting change 1" > foo.txt
458 458 $ hg ci -m "conflicting 1"
459 459 created new head
460 460 $ echo "conflicting change 2" > foo.txt
461 461 $ hg ci -m "conflicting 2"
462 462
463 463 $ hg rebase -d 1 --tool 'internal:fail'
464 464 rebasing 2:e4ea5cdc9789 "conflicting 1"
465 465 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
466 466 [1]
467 467 $ hg abort
468 468 rebase aborted
469 469 $ hg summary
470 470 parent: 3:b16646383533 tip
471 471 conflicting 2
472 472 branch: default
473 473 commit: (clean)
474 474 update: 1 new changesets, 2 branch heads (merge)
475 475 phases: 4 draft
476 476 $ cd ..
477 477
478 478 test aborting a rebase succeeds after rebasing with skipped commits onto a
479 479 public changeset (issue4896)
480 480
481 481 $ hg init succeedonpublic
482 482 $ cd succeedonpublic
483 483 $ echo 'content' > root
484 484 $ hg commit -A -m 'root' -q
485 485
486 486 set up public branch
487 487 $ echo 'content' > disappear
488 488 $ hg commit -A -m 'disappear public' -q
489 489 commit will cause merge conflict on rebase
490 490 $ echo '' > root
491 491 $ hg commit -m 'remove content public' -q
492 492 $ hg phase --public
493 493
494 494 setup the draft branch that will be rebased onto public commit
495 495 $ hg up -r 0 -q
496 496 $ echo 'content' > disappear
497 497 commit will disappear
498 498 $ hg commit -A -m 'disappear draft' -q
499 499 $ echo 'addedcontADDEDentadded' > root
500 500 commit will cause merge conflict on rebase
501 501 $ hg commit -m 'add content draft' -q
502 502
503 503 $ hg rebase -d 'public()' --tool :merge -q
504 504 note: not rebasing 3:0682fd3dabf5 "disappear draft", its destination already has all its changes
505 505 warning: conflicts while merging root! (edit, then use 'hg resolve --mark')
506 506 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
507 507 [1]
508 508 $ hg abort
509 509 rebase aborted
510 510 $ cd ..
511 511
@@ -1,384 +1,384 b''
1 1 Test the "--base" flag of the rebase command. (Tests unrelated to the "--base"
2 2 flag should probably live in somewhere else)
3 3
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > rebase=
7 7 > drawdag=$TESTDIR/drawdag.py
8 8 >
9 9 > [phases]
10 10 > publish=False
11 11 >
12 12 > [alias]
13 13 > tglog = log -G --template "{rev}: {node|short} {desc}"
14 14 > EOF
15 15
16 16 $ rebasewithdag() {
17 17 > N=`"$PYTHON" -c "print($N+1)"`
18 18 > hg init repo$N && cd repo$N
19 19 > hg debugdrawdag
20 20 > hg rebase "$@" > _rebasetmp
21 21 > r=$?
22 22 > grep -v 'saved backup bundle' _rebasetmp
23 23 > [ $r -eq 0 ] && hg tglog
24 24 > cd ..
25 25 > return $r
26 26 > }
27 27
28 28 Single branching point, without merge:
29 29
30 30 $ rebasewithdag -b D -d Z <<'EOS'
31 31 > D E
32 32 > |/
33 33 > Z B C # C: branching point, E should be picked
34 34 > \|/ # B should not be picked
35 35 > A
36 36 > |
37 37 > R
38 38 > EOS
39 rebasing 3:d6003a550c2c "C" (C)
40 rebasing 5:4526cf523425 "D" (D)
41 rebasing 6:b296604d9846 "E" (E tip)
39 rebasing 3:d6003a550c2c C "C"
40 rebasing 5:4526cf523425 D "D"
41 rebasing 6:b296604d9846 E tip "E"
42 42 o 6: 4870f5e7df37 E
43 43 |
44 44 | o 5: dc999528138a D
45 45 |/
46 46 o 4: 6b3e11729672 C
47 47 |
48 48 o 3: 57e70bad1ea3 Z
49 49 |
50 50 | o 2: c1e6b162678d B
51 51 |/
52 52 o 1: 21a6c4502885 A
53 53 |
54 54 o 0: b41ce7760717 R
55 55
56 56 Multiple branching points caused by selecting a single merge changeset:
57 57
58 58 $ rebasewithdag -b E -d Z <<'EOS'
59 59 > E
60 60 > /|
61 61 > B C D # B, C: multiple branching points
62 62 > | |/ # D should not be picked
63 63 > Z | /
64 64 > \|/
65 65 > A
66 66 > |
67 67 > R
68 68 > EOS
69 rebasing 2:c1e6b162678d "B" (B)
70 rebasing 3:d6003a550c2c "C" (C)
71 rebasing 6:54c8f00cb91c "E" (E tip)
69 rebasing 2:c1e6b162678d B "B"
70 rebasing 3:d6003a550c2c C "C"
71 rebasing 6:54c8f00cb91c E tip "E"
72 72 o 6: 00598421b616 E
73 73 |\
74 74 | o 5: 6b3e11729672 C
75 75 | |
76 76 o | 4: 85260910e847 B
77 77 |/
78 78 o 3: 57e70bad1ea3 Z
79 79 |
80 80 | o 2: 8924700906fe D
81 81 |/
82 82 o 1: 21a6c4502885 A
83 83 |
84 84 o 0: b41ce7760717 R
85 85
86 86 Rebase should not extend the "--base" revset using "descendants":
87 87
88 88 $ rebasewithdag -b B -d Z <<'EOS'
89 89 > E
90 90 > /|
91 91 > Z B C # descendants(B) = B+E. With E, C will be included incorrectly
92 92 > \|/
93 93 > A
94 94 > |
95 95 > R
96 96 > EOS
97 rebasing 2:c1e6b162678d "B" (B)
98 rebasing 5:54c8f00cb91c "E" (E tip)
97 rebasing 2:c1e6b162678d B "B"
98 rebasing 5:54c8f00cb91c E tip "E"
99 99 o 5: e583bf3ff54c E
100 100 |\
101 101 | o 4: 85260910e847 B
102 102 | |
103 103 | o 3: 57e70bad1ea3 Z
104 104 | |
105 105 o | 2: d6003a550c2c C
106 106 |/
107 107 o 1: 21a6c4502885 A
108 108 |
109 109 o 0: b41ce7760717 R
110 110
111 111 Rebase should not simplify the "--base" revset using "roots":
112 112
113 113 $ rebasewithdag -b B+E -d Z <<'EOS'
114 114 > E
115 115 > /|
116 116 > Z B C # roots(B+E) = B. Without E, C will be missed incorrectly
117 117 > \|/
118 118 > A
119 119 > |
120 120 > R
121 121 > EOS
122 rebasing 2:c1e6b162678d "B" (B)
123 rebasing 3:d6003a550c2c "C" (C)
124 rebasing 5:54c8f00cb91c "E" (E tip)
122 rebasing 2:c1e6b162678d B "B"
123 rebasing 3:d6003a550c2c C "C"
124 rebasing 5:54c8f00cb91c E tip "E"
125 125 o 5: 00598421b616 E
126 126 |\
127 127 | o 4: 6b3e11729672 C
128 128 | |
129 129 o | 3: 85260910e847 B
130 130 |/
131 131 o 2: 57e70bad1ea3 Z
132 132 |
133 133 o 1: 21a6c4502885 A
134 134 |
135 135 o 0: b41ce7760717 R
136 136
137 137 The destination is one of the two branching points of a merge:
138 138
139 139 $ rebasewithdag -b F -d Z <<'EOS'
140 140 > F
141 141 > / \
142 142 > E D
143 143 > / /
144 144 > Z C
145 145 > \ /
146 146 > B
147 147 > |
148 148 > A
149 149 > EOS
150 150 nothing to rebase
151 151 [1]
152 152
153 153 Multiple branching points caused by multiple bases (issue5420):
154 154
155 155 $ rebasewithdag -b E1+E2+C2+B1 -d Z <<'EOS'
156 156 > Z E2
157 157 > | /
158 158 > F E1 C2
159 159 > |/ /
160 160 > E C1 B2
161 161 > |/ /
162 162 > C B1
163 163 > |/
164 164 > B
165 165 > |
166 166 > A
167 167 > |
168 168 > R
169 169 > EOS
170 rebasing 3:a113dbaa660a "B1" (B1)
171 rebasing 5:06ce7b1cc8c2 "B2" (B2)
172 rebasing 6:0ac98cce32d3 "C1" (C1)
173 rebasing 8:781512f5e33d "C2" (C2)
174 rebasing 9:428d8c18f641 "E1" (E1)
175 rebasing 11:e1bf82f6b6df "E2" (E2)
170 rebasing 3:a113dbaa660a B1 "B1"
171 rebasing 5:06ce7b1cc8c2 B2 "B2"
172 rebasing 6:0ac98cce32d3 C1 "C1"
173 rebasing 8:781512f5e33d C2 "C2"
174 rebasing 9:428d8c18f641 E1 "E1"
175 rebasing 11:e1bf82f6b6df E2 "E2"
176 176 o 12: e4a37b6fdbd2 E2
177 177 |
178 178 o 11: 9675bea983df E1
179 179 |
180 180 | o 10: 4faf5d4c80dc C2
181 181 | |
182 182 | o 9: d4799b1ad57d C1
183 183 |/
184 184 | o 8: 772732dc64d6 B2
185 185 | |
186 186 | o 7: ad3ac528a49f B1
187 187 |/
188 188 o 6: 2cbdfca6b9d5 Z
189 189 |
190 190 o 5: fcdb3293ec13 F
191 191 |
192 192 o 4: a4652bb8ac54 E
193 193 |
194 194 o 3: bd5548558fcf C
195 195 |
196 196 o 2: c1e6b162678d B
197 197 |
198 198 o 1: 21a6c4502885 A
199 199 |
200 200 o 0: b41ce7760717 R
201 201
202 202 Multiple branching points with multiple merges:
203 203
204 204 $ rebasewithdag -b G+P -d Z <<'EOS'
205 205 > G H P
206 206 > |\ /| |\
207 207 > F E D M N
208 208 > \|/| /| |\
209 209 > Z C B I J K L
210 210 > \|/ |/ |/
211 211 > A A A
212 212 > EOS
213 rebasing 2:dc0947a82db8 "C" (C)
214 rebasing 8:4e4f9194f9f1 "D" (D)
215 rebasing 9:03ca77807e91 "E" (E)
216 rebasing 10:afc707c82df0 "F" (F)
217 rebasing 13:690dfff91e9e "G" (G)
218 rebasing 14:2893b886bb10 "H" (H)
219 rebasing 3:08ebfeb61bac "I" (I)
220 rebasing 4:a0a5005cec67 "J" (J)
221 rebasing 5:83780307a7e8 "K" (K)
222 rebasing 6:e131637a1cb6 "L" (L)
223 rebasing 11:d1f6d0c3c7e4 "M" (M)
224 rebasing 12:7aaec6f81888 "N" (N)
225 rebasing 15:325bc8f1760d "P" (P tip)
213 rebasing 2:dc0947a82db8 C "C"
214 rebasing 8:4e4f9194f9f1 D "D"
215 rebasing 9:03ca77807e91 E "E"
216 rebasing 10:afc707c82df0 F "F"
217 rebasing 13:690dfff91e9e G "G"
218 rebasing 14:2893b886bb10 H "H"
219 rebasing 3:08ebfeb61bac I "I"
220 rebasing 4:a0a5005cec67 J "J"
221 rebasing 5:83780307a7e8 K "K"
222 rebasing 6:e131637a1cb6 L "L"
223 rebasing 11:d1f6d0c3c7e4 M "M"
224 rebasing 12:7aaec6f81888 N "N"
225 rebasing 15:325bc8f1760d P tip "P"
226 226 o 15: 6ef6a0ea3b18 P
227 227 |\
228 228 | o 14: 20ba3610a7e5 N
229 229 | |\
230 230 o \ \ 13: cd4f6c06d2ab M
231 231 |\ \ \
232 232 | | | o 12: bca872041455 L
233 233 | | | |
234 234 | | o | 11: 7bbb6c8a6ad7 K
235 235 | | |/
236 236 | o / 10: de0cbffe893e J
237 237 | |/
238 238 o / 9: 0e710f176a88 I
239 239 |/
240 240 | o 8: 52507bab39ca H
241 241 | |\
242 242 | | | o 7: bb5fe4652f0d G
243 243 | | |/|
244 244 | | | o 6: f4ad4b31daf4 F
245 245 | | | |
246 246 | | o | 5: b168f85f2e78 E
247 247 | | |/
248 248 | o | 4: 8d09fcdb5594 D
249 249 | |\|
250 250 +---o 3: ab70b4c5a9c9 C
251 251 | |
252 252 o | 2: 262e37e34f63 Z
253 253 | |
254 254 | o 1: 112478962961 B
255 255 |/
256 256 o 0: 426bada5c675 A
257 257
258 258 Slightly more complex merge case (mentioned in https://www.mercurial-scm.org/pipermail/mercurial-devel/2016-November/091074.html):
259 259
260 260 $ rebasewithdag -b A3+B3 -d Z <<'EOF'
261 261 > Z C1 A3 B3
262 262 > | / / \ / \
263 263 > M3 C0 A1 A2 B1 B2
264 264 > | / | | | |
265 265 > M2 M1 C1 C1 M3
266 266 > |
267 267 > M1
268 268 > |
269 269 > M0
270 270 > EOF
271 rebasing 4:8817fae53c94 "C0" (C0)
272 rebasing 6:06ca5dfe3b5b "B2" (B2)
273 rebasing 7:73508237b032 "C1" (C1)
274 rebasing 9:fdb955e2faed "A2" (A2)
275 rebasing 11:4e449bd1a643 "A3" (A3)
276 rebasing 10:0a33b0519128 "B1" (B1)
277 rebasing 12:209327807c3a "B3" (B3 tip)
271 rebasing 4:8817fae53c94 C0 "C0"
272 rebasing 6:06ca5dfe3b5b B2 "B2"
273 rebasing 7:73508237b032 C1 "C1"
274 rebasing 9:fdb955e2faed A2 "A2"
275 rebasing 11:4e449bd1a643 A3 "A3"
276 rebasing 10:0a33b0519128 B1 "B1"
277 rebasing 12:209327807c3a B3 tip "B3"
278 278 o 12: ceb984566332 B3
279 279 |\
280 280 | o 11: 19d93caac497 B1
281 281 | |
282 282 | | o 10: 058e73d3916b A3
283 283 | | |\
284 284 | +---o 9: 0ba13ad72234 A2
285 285 | | |
286 286 | o | 8: c122c2af10c6 C1
287 287 | | |
288 288 o | | 7: 74275896650e B2
289 289 | | |
290 290 | o | 6: 455ba9bd3ea2 C0
291 291 |/ /
292 292 o | 5: b3d7d2fda53b Z
293 293 | |
294 294 o | 4: 182ab6383dd7 M3
295 295 | |
296 296 o | 3: 6c3f73563d5f M2
297 297 | |
298 298 | o 2: 88c860fffcc2 A1
299 299 |/
300 300 o 1: bc852baa85dd M1
301 301 |
302 302 o 0: dbdfc5c9bcd5 M0
303 303
304 304 Disconnected graph:
305 305
306 306 $ rebasewithdag -b B -d Z <<'EOS'
307 307 > B
308 308 > |
309 309 > Z A
310 310 > EOS
311 311 nothing to rebase from 112478962961 to 48b9aae0607f
312 312 [1]
313 313
314 314 Multiple roots. Roots are ancestors of dest:
315 315
316 316 $ rebasewithdag -b B+D -d Z <<'EOF'
317 317 > D Z B
318 318 > \|\|
319 319 > C A
320 320 > EOF
321 rebasing 2:112478962961 "B" (B)
322 rebasing 3:b70f76719894 "D" (D)
321 rebasing 2:112478962961 B "B"
322 rebasing 3:b70f76719894 D "D"
323 323 o 4: 511efad7bf13 D
324 324 |
325 325 | o 3: 25c4e279af62 B
326 326 |/
327 327 o 2: 3a49f54d7bb1 Z
328 328 |\
329 329 | o 1: 96cc3511f894 C
330 330 |
331 331 o 0: 426bada5c675 A
332 332
333 333 Multiple roots. One root is not an ancestor of dest:
334 334
335 335 $ rebasewithdag -b B+D -d Z <<'EOF'
336 336 > Z B D
337 337 > \|\|
338 338 > A C
339 339 > EOF
340 340 nothing to rebase from f675d5a1c6a4+b70f76719894 to 262e37e34f63
341 341 [1]
342 342
343 343 Multiple roots. One root is not an ancestor of dest. Select using a merge:
344 344
345 345 $ rebasewithdag -b E -d Z <<'EOF'
346 346 > E
347 347 > |\
348 348 > Z B D
349 349 > \|\|
350 350 > A C
351 351 > EOF
352 rebasing 2:f675d5a1c6a4 "B" (B)
353 rebasing 5:f68696fe6af8 "E" (E tip)
352 rebasing 2:f675d5a1c6a4 B "B"
353 rebasing 5:f68696fe6af8 E tip "E"
354 354 o 5: f6e6f5081554 E
355 355 |\
356 356 | o 4: 30cabcba27be B
357 357 | |\
358 358 | | o 3: 262e37e34f63 Z
359 359 | | |
360 360 o | | 2: b70f76719894 D
361 361 |/ /
362 362 o / 1: 96cc3511f894 C
363 363 /
364 364 o 0: 426bada5c675 A
365 365
366 366 Multiple roots. Two children share two parents while dest has only one parent:
367 367
368 368 $ rebasewithdag -b B+D -d Z <<'EOF'
369 369 > Z B D
370 370 > \|\|\
371 371 > A C A
372 372 > EOF
373 rebasing 2:f675d5a1c6a4 "B" (B)
374 rebasing 3:c2a779e13b56 "D" (D)
373 rebasing 2:f675d5a1c6a4 B "B"
374 rebasing 3:c2a779e13b56 D "D"
375 375 o 4: 5eecd056b5f8 D
376 376 |\
377 377 +---o 3: 30cabcba27be B
378 378 | |/
379 379 | o 2: 262e37e34f63 Z
380 380 | |
381 381 o | 1: 96cc3511f894 C
382 382 /
383 383 o 0: 426bada5c675 A
384 384
@@ -1,245 +1,245 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: {node|short} '{desc}' bookmarks: {bookmarks}\n"
11 11 > EOF
12 12
13 13 Create a repo with several bookmarks
14 14 $ hg init a
15 15 $ cd a
16 16
17 17 $ echo a > a
18 18 $ hg ci -Am A
19 19 adding a
20 20
21 21 $ echo b > b
22 22 $ hg ci -Am B
23 23 adding b
24 24 $ hg book 'X'
25 25 $ hg book 'Y'
26 26
27 27 $ echo c > c
28 28 $ hg ci -Am C
29 29 adding c
30 30 $ hg book 'Z'
31 31
32 32 $ hg up -q 0
33 33
34 34 $ echo d > d
35 35 $ hg ci -Am D
36 36 adding d
37 37 created new head
38 38
39 39 $ hg book W
40 40
41 41 $ hg tglog
42 42 @ 3: 41acb9dca9eb 'D' bookmarks: W
43 43 |
44 44 | o 2: 49cb3485fa0c 'C' bookmarks: Y Z
45 45 | |
46 46 | o 1: 6c81ed0049f8 'B' bookmarks: X
47 47 |/
48 48 o 0: 1994f17a630e 'A' bookmarks:
49 49
50 50
51 51 Move only rebased bookmarks
52 52
53 53 $ cd ..
54 54 $ hg clone -q a a1
55 55
56 56 $ cd a1
57 57 $ hg up -q Z
58 58
59 59 Test deleting divergent bookmarks from dest (issue3685)
60 60
61 61 $ hg book -r 3 Z@diverge
62 62
63 63 ... and also test that bookmarks not on dest or not being moved aren't deleted
64 64
65 65 $ hg book -r 3 X@diverge
66 66 $ hg book -r 0 Y@diverge
67 67
68 68 $ hg tglog
69 69 o 3: 41acb9dca9eb 'D' bookmarks: W X@diverge Z@diverge
70 70 |
71 71 | @ 2: 49cb3485fa0c 'C' bookmarks: Y Z
72 72 | |
73 73 | o 1: 6c81ed0049f8 'B' bookmarks: X
74 74 |/
75 75 o 0: 1994f17a630e 'A' bookmarks: Y@diverge
76 76
77 77 $ hg rebase -s Y -d 3
78 rebasing 2:49cb3485fa0c "C" (Y Z)
78 rebasing 2:49cb3485fa0c Y Z "C"
79 79 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/49cb3485fa0c-126f3e97-rebase.hg
80 80
81 81 $ hg tglog
82 82 @ 3: 17fb3faba63c 'C' bookmarks: Y Z
83 83 |
84 84 o 2: 41acb9dca9eb 'D' bookmarks: W X@diverge
85 85 |
86 86 | o 1: 6c81ed0049f8 'B' bookmarks: X
87 87 |/
88 88 o 0: 1994f17a630e 'A' bookmarks: Y@diverge
89 89
90 90 Do not try to keep active but deleted divergent bookmark
91 91
92 92 $ cd ..
93 93 $ hg clone -q a a4
94 94
95 95 $ cd a4
96 96 $ hg up -q 2
97 97 $ hg book W@diverge
98 98
99 99 $ hg rebase -s W -d .
100 rebasing 3:41acb9dca9eb "D" (W tip)
100 rebasing 3:41acb9dca9eb tip W "D"
101 101 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/41acb9dca9eb-b35a6a63-rebase.hg
102 102
103 103 $ hg bookmarks
104 104 W 3:0d3554f74897
105 105 X 1:6c81ed0049f8
106 106 Y 2:49cb3485fa0c
107 107 Z 2:49cb3485fa0c
108 108
109 109 Keep bookmarks to the correct rebased changeset
110 110
111 111 $ cd ..
112 112 $ hg clone -q a a2
113 113
114 114 $ cd a2
115 115 $ hg up -q Z
116 116
117 117 $ hg rebase -s 1 -d 3
118 rebasing 1:6c81ed0049f8 "B" (X)
119 rebasing 2:49cb3485fa0c "C" (Y Z)
118 rebasing 1:6c81ed0049f8 X "B"
119 rebasing 2:49cb3485fa0c Y Z "C"
120 120 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/6c81ed0049f8-a687065f-rebase.hg
121 121
122 122 $ hg tglog
123 123 @ 3: 3d5fa227f4b5 'C' bookmarks: Y Z
124 124 |
125 125 o 2: e926fccfa8ec 'B' bookmarks: X
126 126 |
127 127 o 1: 41acb9dca9eb 'D' bookmarks: W
128 128 |
129 129 o 0: 1994f17a630e 'A' bookmarks:
130 130
131 131
132 132 Keep active bookmark on the correct changeset
133 133
134 134 $ cd ..
135 135 $ hg clone -q a a3
136 136
137 137 $ cd a3
138 138 $ hg up -q X
139 139
140 140 $ hg rebase -d W
141 rebasing 1:6c81ed0049f8 "B" (X)
142 rebasing 2:49cb3485fa0c "C" (Y Z)
141 rebasing 1:6c81ed0049f8 X "B"
142 rebasing 2:49cb3485fa0c Y Z "C"
143 143 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/6c81ed0049f8-a687065f-rebase.hg
144 144
145 145 $ hg tglog
146 146 o 3: 3d5fa227f4b5 'C' bookmarks: Y Z
147 147 |
148 148 @ 2: e926fccfa8ec 'B' bookmarks: X
149 149 |
150 150 o 1: 41acb9dca9eb 'D' bookmarks: W
151 151 |
152 152 o 0: 1994f17a630e 'A' bookmarks:
153 153
154 154 $ hg bookmarks
155 155 W 1:41acb9dca9eb
156 156 * X 2:e926fccfa8ec
157 157 Y 3:3d5fa227f4b5
158 158 Z 3:3d5fa227f4b5
159 159
160 160 rebase --continue with bookmarks present (issue3802)
161 161
162 162 $ hg up 2
163 163 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
164 164 (leaving bookmark X)
165 165 $ echo 'C' > c
166 166 $ hg add c
167 167 $ hg ci -m 'other C'
168 168 created new head
169 169 $ hg up 3
170 170 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
171 171 $ hg rebase --dest 4
172 rebasing 3:3d5fa227f4b5 "C" (Y Z)
172 rebasing 3:3d5fa227f4b5 Y Z "C"
173 173 merging c
174 174 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
175 175 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
176 176 [1]
177 177 $ echo 'c' > c
178 178 $ hg resolve --mark c
179 179 (no more unresolved files)
180 180 continue: hg rebase --continue
181 181 $ hg rebase --continue
182 rebasing 3:3d5fa227f4b5 "C" (Y Z)
182 rebasing 3:3d5fa227f4b5 Y Z "C"
183 183 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/3d5fa227f4b5-c6ea2371-rebase.hg
184 184 $ hg tglog
185 185 @ 4: 45c0f0ec1203 'C' bookmarks: Y Z
186 186 |
187 187 o 3: b0e10b7175fd 'other C' bookmarks:
188 188 |
189 189 o 2: e926fccfa8ec 'B' bookmarks: X
190 190 |
191 191 o 1: 41acb9dca9eb 'D' bookmarks: W
192 192 |
193 193 o 0: 1994f17a630e 'A' bookmarks:
194 194
195 195
196 196 ensure that bookmarks given the names of revset functions can be used
197 197 as --rev arguments (issue3950)
198 198
199 199 $ hg update -q 3
200 200 $ echo bimble > bimble
201 201 $ hg add bimble
202 202 $ hg commit -q -m 'bisect'
203 203 $ echo e >> bimble
204 204 $ hg ci -m bisect2
205 205 $ echo e >> bimble
206 206 $ hg ci -m bisect3
207 207 $ hg book bisect
208 208 $ hg update -q Y
209 209 $ hg rebase -r '"bisect"^^::"bisect"^' -r bisect -d Z
210 210 rebasing 5:345c90f326a4 "bisect"
211 211 rebasing 6:f677a2907404 "bisect2"
212 rebasing 7:325c16001345 "bisect3" (bisect tip)
212 rebasing 7:325c16001345 tip bisect "bisect3"
213 213 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/345c90f326a4-b4840586-rebase.hg
214 214
215 215 Bookmark and working parent get moved even if --keep is set (issue5682)
216 216
217 217 $ hg init $TESTTMP/book-keep
218 218 $ cd $TESTTMP/book-keep
219 219 $ hg debugdrawdag <<'EOS'
220 220 > B C
221 221 > |/
222 222 > A
223 223 > EOS
224 224 $ eval `hg tags -T 'hg bookmark -ir {node} {tag};\n' | grep -v tip`
225 225 $ rm .hg/localtags
226 226 $ hg up -q B
227 227 $ hg tglog
228 228 o 2: dc0947a82db8 'C' bookmarks: C
229 229 |
230 230 | @ 1: 112478962961 'B' bookmarks: B
231 231 |/
232 232 o 0: 426bada5c675 'A' bookmarks: A
233 233
234 234 $ hg rebase -r B -d C --keep
235 rebasing 1:112478962961 "B" (B)
235 rebasing 1:112478962961 B "B"
236 236 $ hg tglog
237 237 @ 3: 9769fc65c4c5 'B' bookmarks: B
238 238 |
239 239 o 2: dc0947a82db8 'C' bookmarks: C
240 240 |
241 241 | o 1: 112478962961 'B' bookmarks:
242 242 |/
243 243 o 0: 426bada5c675 'A' bookmarks: A
244 244
245 245
@@ -1,483 +1,483 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > mq=
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}: '{desc}' {branches}\n"
11 11 > theads = heads --template "{rev}: '{desc}' {branches}\n"
12 12 > EOF
13 13
14 14 $ hg init a
15 15 $ cd a
16 16
17 17 $ echo a > a
18 18 $ hg ci -Am A
19 19 adding a
20 20
21 21 $ hg branch branch1
22 22 marked working directory as branch branch1
23 23 (branches are permanent and global, did you want a bookmark?)
24 24 $ hg ci -m 'branch1'
25 25
26 26 $ echo b > b
27 27 $ hg ci -Am B
28 28 adding b
29 29
30 30 $ hg up -q 0
31 31
32 32 $ hg branch branch2
33 33 marked working directory as branch branch2
34 34 $ hg ci -m 'branch2'
35 35
36 36 $ echo c > C
37 37 $ hg ci -Am C
38 38 adding C
39 39
40 40 $ hg up -q 2
41 41
42 42 $ hg branch -f branch2
43 43 marked working directory as branch branch2
44 44 $ echo d > d
45 45 $ hg ci -Am D
46 46 adding d
47 47 created new head
48 48
49 49 $ echo e > e
50 50 $ hg ci -Am E
51 51 adding e
52 52
53 53 $ hg update default
54 54 0 files updated, 0 files merged, 3 files removed, 0 files unresolved
55 55
56 56 $ hg branch branch3
57 57 marked working directory as branch branch3
58 58 $ hg ci -m 'branch3'
59 59
60 60 $ echo f > f
61 61 $ hg ci -Am F
62 62 adding f
63 63
64 64 $ cd ..
65 65
66 66
67 67 Rebase part of branch2 (5-6) onto branch3 (8):
68 68
69 69 $ hg clone -q -u . a a1
70 70 $ cd a1
71 71
72 72 $ hg tglog
73 73 @ 8: 'F' branch3
74 74 |
75 75 o 7: 'branch3' branch3
76 76 |
77 77 | o 6: 'E' branch2
78 78 | |
79 79 | o 5: 'D' branch2
80 80 | |
81 81 | | o 4: 'C' branch2
82 82 | | |
83 83 +---o 3: 'branch2' branch2
84 84 | |
85 85 | o 2: 'B' branch1
86 86 | |
87 87 | o 1: 'branch1' branch1
88 88 |/
89 89 o 0: 'A'
90 90
91 91 $ hg branches
92 92 branch3 8:4666b71e8e32
93 93 branch2 6:5097051d331d
94 94 branch1 2:0a03079c47fd (inactive)
95 95 default 0:1994f17a630e (inactive)
96 96
97 97 $ hg theads
98 98 8: 'F' branch3
99 99 6: 'E' branch2
100 100 4: 'C' branch2
101 101 2: 'B' branch1
102 102 0: 'A'
103 103
104 104 $ hg rebase -s 5 -d 8
105 105 rebasing 5:635859577d0b "D"
106 106 rebasing 6:5097051d331d "E"
107 107 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/635859577d0b-89160bff-rebase.hg
108 108
109 109 $ hg branches
110 110 branch3 8:466cdfb14b62
111 111 branch2 4:e4fdb121d036
112 112 branch1 2:0a03079c47fd
113 113 default 0:1994f17a630e (inactive)
114 114
115 115 $ hg theads
116 116 8: 'E' branch3
117 117 4: 'C' branch2
118 118 2: 'B' branch1
119 119 0: 'A'
120 120
121 121 $ hg tglog
122 122 o 8: 'E' branch3
123 123 |
124 124 o 7: 'D' branch3
125 125 |
126 126 @ 6: 'F' branch3
127 127 |
128 128 o 5: 'branch3' branch3
129 129 |
130 130 | o 4: 'C' branch2
131 131 | |
132 132 | o 3: 'branch2' branch2
133 133 |/
134 134 | o 2: 'B' branch1
135 135 | |
136 136 | o 1: 'branch1' branch1
137 137 |/
138 138 o 0: 'A'
139 139
140 140 $ cd ..
141 141
142 142
143 143 Rebase head of branch3 (8) onto branch2 (6):
144 144
145 145 $ hg clone -q -u . a a2
146 146 $ cd a2
147 147
148 148 $ hg tglog
149 149 @ 8: 'F' branch3
150 150 |
151 151 o 7: 'branch3' branch3
152 152 |
153 153 | o 6: 'E' branch2
154 154 | |
155 155 | o 5: 'D' branch2
156 156 | |
157 157 | | o 4: 'C' branch2
158 158 | | |
159 159 +---o 3: 'branch2' branch2
160 160 | |
161 161 | o 2: 'B' branch1
162 162 | |
163 163 | o 1: 'branch1' branch1
164 164 |/
165 165 o 0: 'A'
166 166
167 167 $ hg rebase -s 8 -d 6
168 rebasing 8:4666b71e8e32 "F" (tip)
168 rebasing 8:4666b71e8e32 tip "F"
169 169 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/4666b71e8e32-fc1c4e96-rebase.hg
170 170
171 171 $ hg branches
172 172 branch2 8:6b4bdc1b5ac0
173 173 branch3 7:653b9feb4616
174 174 branch1 2:0a03079c47fd (inactive)
175 175 default 0:1994f17a630e (inactive)
176 176
177 177 $ hg theads
178 178 8: 'F' branch2
179 179 7: 'branch3' branch3
180 180 4: 'C' branch2
181 181 2: 'B' branch1
182 182 0: 'A'
183 183
184 184 $ hg tglog
185 185 @ 8: 'F' branch2
186 186 |
187 187 | o 7: 'branch3' branch3
188 188 | |
189 189 o | 6: 'E' branch2
190 190 | |
191 191 o | 5: 'D' branch2
192 192 | |
193 193 | | o 4: 'C' branch2
194 194 | | |
195 195 | | o 3: 'branch2' branch2
196 196 | |/
197 197 o | 2: 'B' branch1
198 198 | |
199 199 o | 1: 'branch1' branch1
200 200 |/
201 201 o 0: 'A'
202 202
203 203 $ hg verify -q
204 204
205 205 $ cd ..
206 206
207 207
208 208 Rebase entire branch3 (7-8) onto branch2 (6):
209 209
210 210 $ hg clone -q -u . a a3
211 211 $ cd a3
212 212
213 213 $ hg tglog
214 214 @ 8: 'F' branch3
215 215 |
216 216 o 7: 'branch3' branch3
217 217 |
218 218 | o 6: 'E' branch2
219 219 | |
220 220 | o 5: 'D' branch2
221 221 | |
222 222 | | o 4: 'C' branch2
223 223 | | |
224 224 +---o 3: 'branch2' branch2
225 225 | |
226 226 | o 2: 'B' branch1
227 227 | |
228 228 | o 1: 'branch1' branch1
229 229 |/
230 230 o 0: 'A'
231 231
232 232 $ hg rebase -s 7 -d 6
233 233 rebasing 7:653b9feb4616 "branch3"
234 234 note: not rebasing 7:653b9feb4616 "branch3", its destination already has all its changes
235 rebasing 8:4666b71e8e32 "F" (tip)
235 rebasing 8:4666b71e8e32 tip "F"
236 236 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/653b9feb4616-3c88de16-rebase.hg
237 237
238 238 $ hg branches
239 239 branch2 7:6b4bdc1b5ac0
240 240 branch1 2:0a03079c47fd (inactive)
241 241 default 0:1994f17a630e (inactive)
242 242
243 243 $ hg theads
244 244 7: 'F' branch2
245 245 4: 'C' branch2
246 246 2: 'B' branch1
247 247 0: 'A'
248 248
249 249 $ hg tglog
250 250 @ 7: 'F' branch2
251 251 |
252 252 o 6: 'E' branch2
253 253 |
254 254 o 5: 'D' branch2
255 255 |
256 256 | o 4: 'C' branch2
257 257 | |
258 258 | o 3: 'branch2' branch2
259 259 | |
260 260 o | 2: 'B' branch1
261 261 | |
262 262 o | 1: 'branch1' branch1
263 263 |/
264 264 o 0: 'A'
265 265
266 266 $ hg verify -q
267 267
268 268 Stripping multiple branches in one go bypasses the fast-case code to
269 269 update the branch cache.
270 270
271 271 $ hg strip 2
272 272 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
273 273 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/0a03079c47fd-11b7c407-backup.hg
274 274
275 275 $ hg tglog
276 276 o 3: 'C' branch2
277 277 |
278 278 o 2: 'branch2' branch2
279 279 |
280 280 | @ 1: 'branch1' branch1
281 281 |/
282 282 o 0: 'A'
283 283
284 284
285 285 $ hg branches
286 286 branch2 3:e4fdb121d036
287 287 branch1 1:63379ac49655
288 288 default 0:1994f17a630e (inactive)
289 289
290 290 $ hg theads
291 291 3: 'C' branch2
292 292 1: 'branch1' branch1
293 293 0: 'A'
294 294
295 295 Fast path branchcache code should not be invoked if branches stripped is not
296 296 the same as branches remaining.
297 297
298 298 $ hg init b
299 299 $ cd b
300 300
301 301 $ hg branch branch1
302 302 marked working directory as branch branch1
303 303 (branches are permanent and global, did you want a bookmark?)
304 304 $ hg ci -m 'branch1'
305 305
306 306 $ hg branch branch2
307 307 marked working directory as branch branch2
308 308 $ hg ci -m 'branch2'
309 309
310 310 $ hg branch -f branch1
311 311 marked working directory as branch branch1
312 312
313 313 $ echo a > A
314 314 $ hg ci -Am A
315 315 adding A
316 316 created new head
317 317
318 318 $ hg tglog
319 319 @ 2: 'A' branch1
320 320 |
321 321 o 1: 'branch2' branch2
322 322 |
323 323 o 0: 'branch1' branch1
324 324
325 325
326 326 $ hg theads
327 327 2: 'A' branch1
328 328 1: 'branch2' branch2
329 329
330 330 $ hg strip 2
331 331 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
332 332 saved backup bundle to $TESTTMP/a3/b/.hg/strip-backup/a5b4b27ed7b4-a3b6984e-backup.hg
333 333
334 334 $ hg theads
335 335 1: 'branch2' branch2
336 336 0: 'branch1' branch1
337 337
338 338
339 339 Make sure requesting to strip a revision already stripped does not confuse things.
340 340 Try both orders.
341 341
342 342 $ cd ..
343 343
344 344 $ hg init c
345 345 $ cd c
346 346
347 347 $ echo a > a
348 348 $ hg ci -Am A
349 349 adding a
350 350 $ echo b > b
351 351 $ hg ci -Am B
352 352 adding b
353 353 $ echo c > c
354 354 $ hg ci -Am C
355 355 adding c
356 356 $ echo d > d
357 357 $ hg ci -Am D
358 358 adding d
359 359 $ echo e > e
360 360 $ hg ci -Am E
361 361 adding e
362 362
363 363 $ hg tglog
364 364 @ 4: 'E'
365 365 |
366 366 o 3: 'D'
367 367 |
368 368 o 2: 'C'
369 369 |
370 370 o 1: 'B'
371 371 |
372 372 o 0: 'A'
373 373
374 374
375 375 $ hg strip 3 4
376 376 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
377 377 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/67a385d4e6f2-b9243789-backup.hg
378 378
379 379 $ hg theads
380 380 2: 'C'
381 381
382 382 $ hg strip 2 1
383 383 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
384 384 saved backup bundle to $TESTTMP/a3/c/.hg/strip-backup/6c81ed0049f8-a687065f-backup.hg
385 385
386 386 $ hg theads
387 387 0: 'A'
388 388
389 389 Make sure rebase does not break for phase/filter related reason
390 390 ----------------------------------------------------------------
391 391 (issue3858)
392 392
393 393 $ cd ..
394 394
395 395 $ cat >> $HGRCPATH << EOF
396 396 > [ui]
397 397 > logtemplate={rev} {desc} {phase}\n
398 398 > EOF
399 399
400 400
401 401 $ hg init c4
402 402 $ cd c4
403 403
404 404 $ echo a > a
405 405 $ hg ci -Am A
406 406 adding a
407 407 $ echo b > b
408 408 $ hg ci -Am B
409 409 adding b
410 410 $ hg up 0
411 411 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
412 412 $ echo c > c
413 413 $ hg ci -Am C
414 414 adding c
415 415 created new head
416 416 $ hg up 1
417 417 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
418 418 $ hg merge
419 419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
420 420 (branch merge, don't forget to commit)
421 421 $ hg ci -m d
422 422 $ hg up 2
423 423 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
424 424 $ echo e > e
425 425 $ hg ci -Am E
426 426 adding e
427 427 created new head
428 428 $ hg merge 3
429 429 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
430 430 (branch merge, don't forget to commit)
431 431 $ hg ci -m F
432 432 $ hg up 3
433 433 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
434 434 $ echo g > g
435 435 $ hg ci -Am G
436 436 adding g
437 437 created new head
438 438 $ echo h > h
439 439 $ hg ci -Am H
440 440 adding h
441 441 $ hg up 5
442 442 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
443 443 $ echo i > i
444 444 $ hg ci -Am I
445 445 adding i
446 446
447 447 Turn most changeset public
448 448
449 449 $ hg ph -p 7
450 450
451 451 $ hg heads
452 452 8 I draft
453 453 7 H public
454 454 $ hg log -G
455 455 @ 8 I draft
456 456 |
457 457 | o 7 H public
458 458 | |
459 459 | o 6 G public
460 460 | |
461 461 o | 5 F draft
462 462 |\|
463 463 o | 4 E draft
464 464 | |
465 465 | o 3 d public
466 466 |/|
467 467 o | 2 C public
468 468 | |
469 469 | o 1 B public
470 470 |/
471 471 o 0 A public
472 472
473 473
474 474 $ cat > $TESTTMP/checkeditform.sh <<EOF
475 475 > env | grep HGEDITFORM
476 476 > true
477 477 > EOF
478 478 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --dest 7 --source 5 -e
479 479 rebasing 5:361a99976cc9 "F"
480 480 HGEDITFORM=rebase.merge
481 rebasing 8:326cfedc031c "I" (tip)
481 rebasing 8:326cfedc031c tip "I"
482 482 HGEDITFORM=rebase.normal
483 483 saved backup bundle to $TESTTMP/a3/c4/.hg/strip-backup/361a99976cc9-35e980d0-rebase.hg
@@ -1,181 +1,181 b''
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 $ echo A > A
17 17 $ hg add A
18 18 $ hg ci -m A
19 19
20 20 $ echo 'B' > B
21 21 $ hg add B
22 22 $ hg ci -m B
23 23
24 24 $ echo C >> A
25 25 $ hg ci -m C
26 26
27 27 $ hg up -q -C 0
28 28
29 29 $ echo D >> A
30 30 $ hg ci -m D
31 31 created new head
32 32
33 33 $ echo E > E
34 34 $ hg add E
35 35 $ hg ci -m E
36 36
37 37 $ hg up -q -C 0
38 38
39 39 $ hg branch 'notdefault'
40 40 marked working directory as branch notdefault
41 41 (branches are permanent and global, did you want a bookmark?)
42 42 $ echo F >> A
43 43 $ hg ci -m F
44 44
45 45 $ cd ..
46 46
47 47
48 48 Rebasing B onto E - check keep: and phases
49 49
50 50 $ hg clone -q -u . a a1
51 51 $ cd a1
52 52 $ hg phase --force --secret 2
53 53
54 54 $ hg tglog
55 55 @ 5:draft 'F' notdefault
56 56 |
57 57 | o 4:draft 'E'
58 58 | |
59 59 | o 3:draft 'D'
60 60 |/
61 61 | o 2:secret 'C'
62 62 | |
63 63 | o 1:draft 'B'
64 64 |/
65 65 o 0:draft 'A'
66 66
67 67 $ hg rebase -s 1 -d 4 --keep
68 68 rebasing 1:27547f69f254 "B"
69 69 rebasing 2:965c486023db "C"
70 70 merging A
71 71 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
72 72 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
73 73 [1]
74 74
75 75 Solve the conflict and go on:
76 76
77 77 $ echo 'conflict solved' > A
78 78 $ rm A.orig
79 79 $ hg resolve -m A
80 80 (no more unresolved files)
81 81 continue: hg rebase --continue
82 82 $ hg rebase --continue
83 83 already rebased 1:27547f69f254 "B" as 45396c49d53b
84 84 rebasing 2:965c486023db "C"
85 85
86 86 $ hg tglog
87 87 o 7:secret 'C'
88 88 |
89 89 o 6:draft 'B'
90 90 |
91 91 | @ 5:draft 'F' notdefault
92 92 | |
93 93 o | 4:draft 'E'
94 94 | |
95 95 o | 3:draft 'D'
96 96 |/
97 97 | o 2:secret 'C'
98 98 | |
99 99 | o 1:draft 'B'
100 100 |/
101 101 o 0:draft 'A'
102 102
103 103 $ cd ..
104 104
105 105
106 106 Rebase F onto E - check keepbranches:
107 107
108 108 $ hg clone -q -u . a a2
109 109 $ cd a2
110 110 $ hg phase --force --secret 2
111 111
112 112 $ hg tglog
113 113 @ 5:draft 'F' notdefault
114 114 |
115 115 | o 4:draft 'E'
116 116 | |
117 117 | o 3:draft 'D'
118 118 |/
119 119 | o 2:secret 'C'
120 120 | |
121 121 | o 1:draft 'B'
122 122 |/
123 123 o 0:draft 'A'
124 124
125 125 $ hg rebase -s 5 -d 4 --keepbranches
126 rebasing 5:01e6ebbd8272 "F" (tip)
126 rebasing 5:01e6ebbd8272 tip "F"
127 127 merging A
128 128 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
129 129 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
130 130 [1]
131 131
132 132 Solve the conflict and go on:
133 133
134 134 $ echo 'conflict solved' > A
135 135 $ rm A.orig
136 136 $ hg resolve -m A
137 137 (no more unresolved files)
138 138 continue: hg rebase --continue
139 139 $ hg rebase --continue
140 rebasing 5:01e6ebbd8272 "F" (tip)
140 rebasing 5:01e6ebbd8272 tip "F"
141 141 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/01e6ebbd8272-6fd3a015-rebase.hg
142 142
143 143 $ hg tglog
144 144 @ 5:draft 'F' notdefault
145 145 |
146 146 o 4:draft 'E'
147 147 |
148 148 o 3:draft 'D'
149 149 |
150 150 | o 2:secret 'C'
151 151 | |
152 152 | o 1:draft 'B'
153 153 |/
154 154 o 0:draft 'A'
155 155
156 156 $ cat >> .hg/hgrc << EOF
157 157 > [experimental]
158 158 > evolution.createmarkers=True
159 159 > EOF
160 160
161 161 When updating away from a dirty, obsolete wdir, don't complain that the old p1
162 162 is filtered and requires --hidden.
163 163
164 164 $ echo conflict > A
165 165 $ hg debugobsolete 071d07019675449d53b7e312c65bcf28adbbdb64 965c486023dbfdc9c32c52dc249a231882fd5c17
166 166 1 new obsolescence markers
167 167 obsoleted 1 changesets
168 168 $ hg update -r 2 --config ui.merge=internal:merge --merge
169 169 merging A
170 170 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
171 171 1 files updated, 0 files merged, 1 files removed, 1 files unresolved
172 172 use 'hg resolve' to retry unresolved file merges
173 173 [1]
174 174 $ hg resolve A
175 175 merging A
176 176 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
177 177 [1]
178 178
179 179 $ hg up -C -q .
180 180
181 181 $ cd ..
@@ -1,776 +1,776 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > mq=
5 5 > drawdag=$TESTDIR/drawdag.py
6 6 >
7 7 > [phases]
8 8 > publish=False
9 9 >
10 10 > [alias]
11 11 > tglog = log -G --template "{rev}: {node|short} '{desc}' {branches}\n"
12 12 > tglogp = log -G --template "{rev}: {node|short} {phase} '{desc}' {branches}\n"
13 13 > EOF
14 14
15 15 Highest phase of source commits is used:
16 16
17 17 $ hg init phase
18 18 $ cd phase
19 19 $ hg debugdrawdag << 'EOF'
20 20 > D
21 21 > |
22 22 > F C
23 23 > | |
24 24 > E B
25 25 > |/
26 26 > A
27 27 > EOF
28 28
29 29 $ hg phase --force --secret D
30 30
31 31 $ cat > $TESTTMP/editor.sh <<EOF
32 32 > echo "==== before editing"
33 33 > cat \$1
34 34 > echo "===="
35 35 > echo "edited manually" >> \$1
36 36 > EOF
37 37 $ HGEDITOR="sh $TESTTMP/editor.sh" hg rebase --collapse --keepbranches -e --source B --dest F
38 rebasing 1:112478962961 "B" (B)
39 rebasing 3:26805aba1e60 "C" (C)
40 rebasing 5:f585351a92f8 "D" (D tip)
38 rebasing 1:112478962961 B "B"
39 rebasing 3:26805aba1e60 C "C"
40 rebasing 5:f585351a92f8 D tip "D"
41 41 ==== before editing
42 42 Collapsed revision
43 43 * B
44 44 * C
45 45 * D
46 46
47 47
48 48 HG: Enter commit message. Lines beginning with 'HG:' are removed.
49 49 HG: Leave message empty to abort commit.
50 50 HG: --
51 51 HG: user: test
52 52 HG: branch 'default'
53 53 HG: added B
54 54 HG: added C
55 55 HG: added D
56 56 ====
57 57 saved backup bundle to $TESTTMP/phase/.hg/strip-backup/112478962961-cb2a9b47-rebase.hg
58 58
59 59 $ hg tglogp
60 60 o 3: 92fa5f5fe108 secret 'Collapsed revision
61 61 | * B
62 62 | * C
63 63 | * D
64 64 |
65 65 |
66 66 | edited manually'
67 67 o 2: 64a8289d2492 draft 'F'
68 68 |
69 69 o 1: 7fb047a69f22 draft 'E'
70 70 |
71 71 o 0: 426bada5c675 draft 'A'
72 72
73 73 $ hg manifest --rev tip
74 74 A
75 75 B
76 76 C
77 77 D
78 78 E
79 79 F
80 80
81 81 $ cd ..
82 82
83 83
84 84 Merge gets linearized:
85 85
86 86 $ hg init linearized-merge
87 87 $ cd linearized-merge
88 88
89 89 $ hg debugdrawdag << 'EOF'
90 90 > F D
91 91 > |/|
92 92 > C B
93 93 > |/
94 94 > A
95 95 > EOF
96 96
97 97 $ hg phase --force --secret D
98 98 $ hg rebase --source B --collapse --dest F
99 rebasing 1:112478962961 "B" (B)
100 rebasing 3:4e4f9194f9f1 "D" (D)
99 rebasing 1:112478962961 B "B"
100 rebasing 3:4e4f9194f9f1 D "D"
101 101 saved backup bundle to $TESTTMP/linearized-merge/.hg/strip-backup/112478962961-e389075b-rebase.hg
102 102
103 103 $ hg tglog
104 104 o 3: 5bdc08b7da2b 'Collapsed revision
105 105 | * B
106 106 | * D'
107 107 o 2: afc707c82df0 'F'
108 108 |
109 109 o 1: dc0947a82db8 'C'
110 110 |
111 111 o 0: 426bada5c675 'A'
112 112
113 113 $ hg manifest --rev tip
114 114 A
115 115 B
116 116 C
117 117 F
118 118
119 119 $ cd ..
120 120
121 121 Custom message:
122 122
123 123 $ hg init message
124 124 $ cd message
125 125
126 126 $ hg debugdrawdag << 'EOF'
127 127 > C
128 128 > |
129 129 > D B
130 130 > |/
131 131 > A
132 132 > EOF
133 133
134 134
135 135 $ hg rebase --base B -m 'custom message'
136 136 abort: message can only be specified with collapse
137 137 [255]
138 138
139 139 $ cat > $TESTTMP/checkeditform.sh <<EOF
140 140 > env | grep HGEDITFORM
141 141 > true
142 142 > EOF
143 143 $ HGEDITOR="sh $TESTTMP/checkeditform.sh" hg rebase --source B --collapse -m 'custom message' -e --dest D
144 rebasing 1:112478962961 "B" (B)
145 rebasing 3:26805aba1e60 "C" (C tip)
144 rebasing 1:112478962961 B "B"
145 rebasing 3:26805aba1e60 C tip "C"
146 146 HGEDITFORM=rebase.collapse
147 147 saved backup bundle to $TESTTMP/message/.hg/strip-backup/112478962961-f4131707-rebase.hg
148 148
149 149 $ hg tglog
150 150 o 2: 2f197b9a08f3 'custom message'
151 151 |
152 152 o 1: b18e25de2cf5 'D'
153 153 |
154 154 o 0: 426bada5c675 'A'
155 155
156 156 $ hg manifest --rev tip
157 157 A
158 158 B
159 159 C
160 160 D
161 161
162 162 $ cd ..
163 163
164 164 Rebase and collapse - more than one external (fail):
165 165
166 166 $ hg init multiple-external-parents
167 167 $ cd multiple-external-parents
168 168
169 169 $ hg debugdrawdag << 'EOF'
170 170 > G
171 171 > |\
172 172 > | F
173 173 > | |
174 174 > D E
175 175 > |\|
176 176 > H C B
177 177 > \|/
178 178 > A
179 179 > EOF
180 180
181 181 $ hg rebase -s C --dest H --collapse
182 182 abort: unable to collapse on top of 3, there is more than one external parent: 1, 6
183 183 [255]
184 184
185 185 Rebase and collapse - E onto H:
186 186
187 187 $ hg rebase -s E --dest H --collapse # root (E) is not a merge
188 rebasing 5:49cb92066bfd "E" (E)
189 rebasing 6:11abe3fb10b8 "F" (F)
190 rebasing 7:64e264db77f0 "G" (G tip)
188 rebasing 5:49cb92066bfd E "E"
189 rebasing 6:11abe3fb10b8 F "F"
190 rebasing 7:64e264db77f0 G tip "G"
191 191 saved backup bundle to $TESTTMP/multiple-external-parents/.hg/strip-backup/49cb92066bfd-ee8a8a79-rebase.hg
192 192
193 193 $ hg tglog
194 194 o 5: 8b2315790719 'Collapsed revision
195 195 |\ * E
196 196 | | * F
197 197 | | * G'
198 198 | o 4: 4e4f9194f9f1 'D'
199 199 | |\
200 200 o | | 3: 575c4b5ec114 'H'
201 201 | | |
202 202 +---o 2: dc0947a82db8 'C'
203 203 | |
204 204 | o 1: 112478962961 'B'
205 205 |/
206 206 o 0: 426bada5c675 'A'
207 207
208 208 $ hg manifest --rev tip
209 209 A
210 210 C
211 211 E
212 212 F
213 213 H
214 214
215 215 $ cd ..
216 216
217 217
218 218
219 219
220 220 Test that branchheads cache is updated correctly when doing a strip in which
221 221 the parent of the ancestor node to be stripped does not become a head and also,
222 222 the parent of a node that is a child of the node stripped becomes a head (node
223 223 3). The code is now much simpler and we could just test a simpler scenario
224 224 We keep it the test this way in case new complexity is injected.
225 225
226 226 Create repo b:
227 227
228 228 $ hg init branch-heads
229 229 $ cd branch-heads
230 230
231 231 $ hg debugdrawdag << 'EOF'
232 232 > G
233 233 > |\
234 234 > | F
235 235 > | |
236 236 > D E
237 237 > |\|
238 238 > H C B
239 239 > \|/
240 240 > A
241 241 > EOF
242 242
243 243 $ hg heads --template="{rev}:{node} {branch}\n"
244 244 7:64e264db77f061f16d9132b70c5a58e2461fb630 default
245 245 3:575c4b5ec114d64b681d33f8792853568bfb2b2c default
246 246
247 247 $ cat $TESTTMP/branch-heads/.hg/cache/branch2-served
248 248 64e264db77f061f16d9132b70c5a58e2461fb630 7
249 249 575c4b5ec114d64b681d33f8792853568bfb2b2c o default
250 250 64e264db77f061f16d9132b70c5a58e2461fb630 o default
251 251
252 252 $ hg strip 4
253 253 saved backup bundle to $TESTTMP/branch-heads/.hg/strip-backup/4e4f9194f9f1-5ec4b5e6-backup.hg
254 254
255 255 $ cat $TESTTMP/branch-heads/.hg/cache/branch2-served
256 256 11abe3fb10b8689b560681094b17fe161871d043 5
257 257 dc0947a82db884575bb76ea10ac97b08536bfa03 o default
258 258 575c4b5ec114d64b681d33f8792853568bfb2b2c o default
259 259 11abe3fb10b8689b560681094b17fe161871d043 o default
260 260
261 261 $ hg heads --template="{rev}:{node} {branch}\n"
262 262 5:11abe3fb10b8689b560681094b17fe161871d043 default
263 263 3:575c4b5ec114d64b681d33f8792853568bfb2b2c default
264 264 2:dc0947a82db884575bb76ea10ac97b08536bfa03 default
265 265
266 266 $ cd ..
267 267
268 268
269 269
270 270 Preserves external parent
271 271
272 272 $ hg init external-parent
273 273 $ cd external-parent
274 274
275 275 $ hg debugdrawdag << 'EOF'
276 276 > H
277 277 > |\
278 278 > | G
279 279 > | |
280 280 > | F # F/E = F\n
281 281 > | |
282 282 > D E # D/D = D\n
283 283 > |\|
284 284 > I C B
285 285 > \|/
286 286 > A
287 287 > EOF
288 288
289 289 $ hg rebase -s F --dest I --collapse # root (F) is not a merge
290 rebasing 6:c82b08f646f1 "F" (F)
290 rebasing 6:c82b08f646f1 F "F"
291 291 file 'E' was deleted in local [dest] but was modified in other [source].
292 292 You can use (c)hanged version, leave (d)eleted, or leave (u)nresolved.
293 293 What do you want to do? u
294 294 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
295 295 [1]
296 296
297 297 $ echo F > E
298 298 $ hg resolve -m
299 299 (no more unresolved files)
300 300 continue: hg rebase --continue
301 301 $ hg rebase -c
302 rebasing 6:c82b08f646f1 "F" (F)
303 rebasing 7:a6db7fa104e1 "G" (G)
304 rebasing 8:e1d201b72d91 "H" (H tip)
302 rebasing 6:c82b08f646f1 F "F"
303 rebasing 7:a6db7fa104e1 G "G"
304 rebasing 8:e1d201b72d91 H tip "H"
305 305 saved backup bundle to $TESTTMP/external-parent/.hg/strip-backup/c82b08f646f1-f2721fbf-rebase.hg
306 306
307 307 $ hg tglog
308 308 o 6: 681daa3e686d 'Collapsed revision
309 309 |\ * F
310 310 | | * G
311 311 | | * H'
312 312 | | o 5: 49cb92066bfd 'E'
313 313 | | |
314 314 | o | 4: 09143c0bf13e 'D'
315 315 | |\|
316 316 o | | 3: 08ebfeb61bac 'I'
317 317 | | |
318 318 | o | 2: dc0947a82db8 'C'
319 319 |/ /
320 320 | o 1: 112478962961 'B'
321 321 |/
322 322 o 0: 426bada5c675 'A'
323 323
324 324 $ hg manifest --rev tip
325 325 A
326 326 C
327 327 D
328 328 E
329 329 F
330 330 G
331 331 I
332 332
333 333 $ hg up tip -q
334 334 $ cat E
335 335 F
336 336
337 337 $ cd ..
338 338
339 339 Rebasing from multiple bases:
340 340
341 341 $ hg init multiple-bases
342 342 $ cd multiple-bases
343 343 $ hg debugdrawdag << 'EOF'
344 344 > C B
345 345 > D |/
346 346 > |/
347 347 > A
348 348 > EOF
349 349 $ hg rebase --collapse -r 'B+C' -d D
350 rebasing 1:fc2b737bb2e5 "B" (B)
351 rebasing 2:dc0947a82db8 "C" (C)
350 rebasing 1:fc2b737bb2e5 B "B"
351 rebasing 2:dc0947a82db8 C "C"
352 352 saved backup bundle to $TESTTMP/multiple-bases/.hg/strip-backup/dc0947a82db8-b0c1a7ea-rebase.hg
353 353 $ hg tglog
354 354 o 2: 2127ae44d291 'Collapsed revision
355 355 | * B
356 356 | * C'
357 357 o 1: b18e25de2cf5 'D'
358 358 |
359 359 o 0: 426bada5c675 'A'
360 360
361 361 $ cd ..
362 362
363 363 With non-contiguous commits:
364 364
365 365 $ hg init non-contiguous
366 366 $ cd non-contiguous
367 367 $ cat >> .hg/hgrc <<EOF
368 368 > [experimental]
369 369 > evolution=all
370 370 > EOF
371 371
372 372 $ hg debugdrawdag << 'EOF'
373 373 > F
374 374 > |
375 375 > E
376 376 > |
377 377 > D
378 378 > |
379 379 > C
380 380 > |
381 381 > B G
382 382 > |/
383 383 > A
384 384 > EOF
385 385
386 386 BROKEN: should be allowed
387 387 $ hg rebase --collapse -r 'B+D+F' -d G
388 388 abort: unable to collapse on top of 2, there is more than one external parent: 3, 5
389 389 [255]
390 390 $ cd ..
391 391
392 392
393 393 $ hg init multiple-external-parents-2
394 394 $ cd multiple-external-parents-2
395 395 $ hg debugdrawdag << 'EOF'
396 396 > D G
397 397 > |\ /|
398 398 > B C E F
399 399 > \| |/
400 400 > \ H /
401 401 > \|/
402 402 > A
403 403 > EOF
404 404
405 405 $ hg rebase --collapse -d H -s 'B+F'
406 406 abort: unable to collapse on top of 5, there is more than one external parent: 1, 3
407 407 [255]
408 408 $ cd ..
409 409
410 410 With internal merge:
411 411
412 412 $ hg init internal-merge
413 413 $ cd internal-merge
414 414
415 415 $ hg debugdrawdag << 'EOF'
416 416 > E
417 417 > |\
418 418 > C D
419 419 > |/
420 420 > F B
421 421 > |/
422 422 > A
423 423 > EOF
424 424
425 425
426 426 $ hg rebase -s B --collapse --dest F
427 rebasing 1:112478962961 "B" (B)
428 rebasing 3:26805aba1e60 "C" (C)
429 rebasing 4:be0ef73c17ad "D" (D)
430 rebasing 5:02c4367d6973 "E" (E tip)
427 rebasing 1:112478962961 B "B"
428 rebasing 3:26805aba1e60 C "C"
429 rebasing 4:be0ef73c17ad D "D"
430 rebasing 5:02c4367d6973 E tip "E"
431 431 saved backup bundle to $TESTTMP/internal-merge/.hg/strip-backup/112478962961-1dfb057b-rebase.hg
432 432
433 433 $ hg tglog
434 434 o 2: c0512a1797b0 'Collapsed revision
435 435 | * B
436 436 | * C
437 437 | * D
438 438 | * E'
439 439 o 1: 8908a377a434 'F'
440 440 |
441 441 o 0: 426bada5c675 'A'
442 442
443 443 $ hg manifest --rev tip
444 444 A
445 445 B
446 446 C
447 447 D
448 448 F
449 449 $ cd ..
450 450
451 451 Interactions between collapse and keepbranches
452 452 $ hg init e
453 453 $ cd e
454 454 $ echo 'a' > a
455 455 $ hg ci -Am 'A'
456 456 adding a
457 457
458 458 $ hg branch 'one'
459 459 marked working directory as branch one
460 460 (branches are permanent and global, did you want a bookmark?)
461 461 $ echo 'b' > b
462 462 $ hg ci -Am 'B'
463 463 adding b
464 464
465 465 $ hg branch 'two'
466 466 marked working directory as branch two
467 467 $ echo 'c' > c
468 468 $ hg ci -Am 'C'
469 469 adding c
470 470
471 471 $ hg up -q 0
472 472 $ echo 'd' > d
473 473 $ hg ci -Am 'D'
474 474 adding d
475 475
476 476 $ hg tglog
477 477 @ 3: 41acb9dca9eb 'D'
478 478 |
479 479 | o 2: 8ac4a08debf1 'C' two
480 480 | |
481 481 | o 1: 1ba175478953 'B' one
482 482 |/
483 483 o 0: 1994f17a630e 'A'
484 484
485 485 $ hg rebase --keepbranches --collapse -s 1 -d 3
486 486 abort: cannot collapse multiple named branches
487 487 [255]
488 488
489 489 $ cd ..
490 490
491 491 Rebase, collapse and copies
492 492
493 493 $ hg init copies
494 494 $ cd copies
495 495 $ hg unbundle "$TESTDIR/bundles/renames.hg"
496 496 adding changesets
497 497 adding manifests
498 498 adding file changes
499 499 added 4 changesets with 11 changes to 7 files (+1 heads)
500 500 new changesets f447d5abf5ea:338e84e2e558 (4 drafts)
501 501 (run 'hg heads' to see heads, 'hg merge' to merge)
502 502 $ hg up -q tip
503 503 $ hg tglog
504 504 @ 3: 338e84e2e558 'move2'
505 505 |
506 506 o 2: 6e7340ee38c0 'move1'
507 507 |
508 508 | o 1: 1352765a01d4 'change'
509 509 |/
510 510 o 0: f447d5abf5ea 'add'
511 511
512 512 $ hg rebase --collapse -d 1
513 513 rebasing 2:6e7340ee38c0 "move1"
514 514 merging a and d to d
515 515 merging b and e to e
516 516 merging c and f to f
517 rebasing 3:338e84e2e558 "move2" (tip)
517 rebasing 3:338e84e2e558 tip "move2"
518 518 merging f and c to c
519 519 merging e and g to g
520 520 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/6e7340ee38c0-ef8ef003-rebase.hg
521 521 $ hg st
522 522 $ hg st --copies --change tip
523 523 A d
524 524 a
525 525 A g
526 526 b
527 527 R b
528 528 $ hg up tip -q
529 529 $ cat c
530 530 c
531 531 c
532 532 $ cat d
533 533 a
534 534 a
535 535 $ cat g
536 536 b
537 537 b
538 538 $ hg log -r . --template "{file_copies}\n"
539 539 d (a)g (b)
540 540
541 541 Test collapsing a middle revision in-place
542 542
543 543 $ hg tglog
544 544 @ 2: 64b456429f67 'Collapsed revision
545 545 | * move1
546 546 | * move2'
547 547 o 1: 1352765a01d4 'change'
548 548 |
549 549 o 0: f447d5abf5ea 'add'
550 550
551 551 $ hg rebase --collapse -r 1 -d 0
552 552 abort: cannot rebase changeset with children
553 553 (use --keep to keep original changesets)
554 554 [255]
555 555
556 556 Test collapsing in place
557 557
558 558 $ hg rebase --collapse -b . -d 0
559 559 rebasing 1:1352765a01d4 "change"
560 rebasing 2:64b456429f67 "Collapsed revision" (tip)
560 rebasing 2:64b456429f67 tip "Collapsed revision"
561 561 saved backup bundle to $TESTTMP/copies/.hg/strip-backup/1352765a01d4-45a352ea-rebase.hg
562 562 $ hg st --change tip --copies
563 563 M a
564 564 M c
565 565 A d
566 566 a
567 567 A g
568 568 b
569 569 R b
570 570 $ hg up tip -q
571 571 $ cat a
572 572 a
573 573 a
574 574 $ cat c
575 575 c
576 576 c
577 577 $ cat d
578 578 a
579 579 a
580 580 $ cat g
581 581 b
582 582 b
583 583 $ cd ..
584 584
585 585
586 586 Test stripping a revision with another child
587 587
588 588 $ hg init f
589 589 $ cd f
590 590
591 591 $ hg debugdrawdag << 'EOF'
592 592 > C B
593 593 > |/
594 594 > A
595 595 > EOF
596 596
597 597 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
598 598 2:dc0947a82db884575bb76ea10ac97b08536bfa03 default: C
599 599 1:112478962961147124edd43549aedd1a335e44bf default: B
600 600
601 601 $ hg strip C
602 602 saved backup bundle to $TESTTMP/f/.hg/strip-backup/dc0947a82db8-d21b92a4-backup.hg
603 603
604 604 $ hg tglog
605 605 o 1: 112478962961 'B'
606 606 |
607 607 o 0: 426bada5c675 'A'
608 608
609 609
610 610
611 611 $ hg heads --template="{rev}:{node} {branch}: {desc}\n"
612 612 1:112478962961147124edd43549aedd1a335e44bf default: B
613 613
614 614 $ cd ..
615 615
616 616 Test collapsing changes that add then remove a file
617 617
618 618 $ hg init collapseaddremove
619 619 $ cd collapseaddremove
620 620
621 621 $ touch base
622 622 $ hg commit -Am base
623 623 adding base
624 624 $ touch a
625 625 $ hg commit -Am a
626 626 adding a
627 627 $ hg rm a
628 628 $ touch b
629 629 $ hg commit -Am b
630 630 adding b
631 631 $ hg book foo
632 632 $ hg rebase -d 0 -r "1::2" --collapse -m collapsed
633 633 rebasing 1:6d8d9f24eec3 "a"
634 rebasing 2:1cc73eca5ecc "b" (foo tip)
634 rebasing 2:1cc73eca5ecc tip foo "b"
635 635 saved backup bundle to $TESTTMP/collapseaddremove/.hg/strip-backup/6d8d9f24eec3-77d3b6e2-rebase.hg
636 636 $ hg log -G --template "{rev}: '{desc}' {bookmarks}"
637 637 @ 1: 'collapsed' foo
638 638 |
639 639 o 0: 'base'
640 640
641 641 $ hg manifest --rev tip
642 642 b
643 643 base
644 644
645 645 $ cd ..
646 646
647 647 Test that rebase --collapse will remember message after
648 648 running into merge conflict and invoking rebase --continue.
649 649
650 650 $ hg init collapse_remember_message
651 651 $ cd collapse_remember_message
652 652 $ hg debugdrawdag << 'EOF'
653 653 > C B # B/A = B\n
654 654 > |/ # C/A = C\n
655 655 > A
656 656 > EOF
657 657 $ hg rebase --collapse -m "new message" -b B -d C
658 rebasing 1:81e5401e4d37 "B" (B)
658 rebasing 1:81e5401e4d37 B "B"
659 659 merging A
660 660 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
661 661 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
662 662 [1]
663 663 $ rm A.orig
664 664 $ hg resolve --mark A
665 665 (no more unresolved files)
666 666 continue: hg rebase --continue
667 667 $ hg rebase --continue
668 rebasing 1:81e5401e4d37 "B" (B)
668 rebasing 1:81e5401e4d37 B "B"
669 669 saved backup bundle to $TESTTMP/collapse_remember_message/.hg/strip-backup/81e5401e4d37-96c3dd30-rebase.hg
670 670 $ hg log
671 671 changeset: 2:17186933e123
672 672 tag: tip
673 673 user: test
674 674 date: Thu Jan 01 00:00:00 1970 +0000
675 675 summary: new message
676 676
677 677 changeset: 1:043039e9df84
678 678 tag: C
679 679 user: test
680 680 date: Thu Jan 01 00:00:00 1970 +0000
681 681 summary: C
682 682
683 683 changeset: 0:426bada5c675
684 684 tag: A
685 685 user: test
686 686 date: Thu Jan 01 00:00:00 1970 +0000
687 687 summary: A
688 688
689 689 $ cd ..
690 690
691 691 Test aborted editor on final message
692 692
693 693 $ HGMERGE=:merge3
694 694 $ export HGMERGE
695 695 $ hg init aborted-editor
696 696 $ cd aborted-editor
697 697 $ hg debugdrawdag << 'EOF'
698 698 > C # D/A = D\n
699 699 > | # C/A = C\n
700 700 > B D # B/A = B\n
701 701 > |/ # A/A = A\n
702 702 > A
703 703 > EOF
704 704 $ hg rebase --collapse -t internal:merge3 -s B -d D
705 rebasing 1:f899f3910ce7 "B" (B)
705 rebasing 1:f899f3910ce7 B "B"
706 706 merging A
707 707 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
708 708 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
709 709 [1]
710 710 $ hg tglog
711 711 o 3: 63668d570d21 'C'
712 712 |
713 713 | @ 2: 82b8abf9c185 'D'
714 714 | |
715 715 % | 1: f899f3910ce7 'B'
716 716 |/
717 717 o 0: 4a2df7238c3b 'A'
718 718
719 719 $ cat A
720 720 <<<<<<< dest: 82b8abf9c185 D - test: D
721 721 D
722 722 ||||||| base
723 723 A
724 724 =======
725 725 B
726 726 >>>>>>> source: f899f3910ce7 B - test: B
727 727 $ echo BC > A
728 728 $ hg resolve -m
729 729 (no more unresolved files)
730 730 continue: hg rebase --continue
731 731 $ hg rebase --continue
732 rebasing 1:f899f3910ce7 "B" (B)
733 rebasing 3:63668d570d21 "C" (C tip)
732 rebasing 1:f899f3910ce7 B "B"
733 rebasing 3:63668d570d21 C tip "C"
734 734 merging A
735 735 warning: conflicts while merging A! (edit, then use 'hg resolve --mark')
736 736 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
737 737 [1]
738 738 $ hg tglog
739 739 % 3: 63668d570d21 'C'
740 740 |
741 741 | @ 2: 82b8abf9c185 'D'
742 742 | |
743 743 o | 1: f899f3910ce7 'B'
744 744 |/
745 745 o 0: 4a2df7238c3b 'A'
746 746
747 747 $ cat A
748 748 <<<<<<< dest: 82b8abf9c185 D - test: D
749 749 BC
750 750 ||||||| base
751 751 B
752 752 =======
753 753 C
754 754 >>>>>>> source: 63668d570d21 C tip - test: C
755 755 $ echo BD > A
756 756 $ hg resolve -m
757 757 (no more unresolved files)
758 758 continue: hg rebase --continue
759 759 $ HGEDITOR=false hg rebase --continue --config ui.interactive=1
760 already rebased 1:f899f3910ce7 "B" (B) as 82b8abf9c185
761 rebasing 3:63668d570d21 "C" (C tip)
760 already rebased 1:f899f3910ce7 B "B" as 82b8abf9c185
761 rebasing 3:63668d570d21 C tip "C"
762 762 abort: edit failed: false exited with status 1
763 763 [255]
764 764 $ hg tglog
765 765 o 3: 63668d570d21 'C'
766 766 |
767 767 | @ 2: 82b8abf9c185 'D'
768 768 | |
769 769 o | 1: f899f3910ce7 'B'
770 770 |/
771 771 o 0: 4a2df7238c3b 'A'
772 772
773 773 $ hg rebase --continue
774 already rebased 1:f899f3910ce7 "B" (B) as 82b8abf9c185
775 already rebased 3:63668d570d21 "C" (C tip) as 82b8abf9c185
774 already rebased 1:f899f3910ce7 B "B" as 82b8abf9c185
775 already rebased 3:63668d570d21 C tip "C" as 82b8abf9c185
776 776 saved backup bundle to $TESTTMP/aborted-editor/.hg/strip-backup/f899f3910ce7-7cab5e15-rebase.hg
@@ -1,500 +1,500 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 >
6 6 > [phases]
7 7 > publish=False
8 8 >
9 9 > [alias]
10 10 > tglog = log -G --template "{rev}:{phase} '{desc}' {branches} {bookmarks}\n"
11 11 > EOF
12 12
13 13 $ hg init a
14 14 $ cd a
15 15 $ echo c1 >common
16 16 $ hg add common
17 17 $ hg ci -m C1
18 18
19 19 $ echo c2 >>common
20 20 $ hg ci -m C2
21 21
22 22 $ echo c3 >>common
23 23 $ hg ci -m C3
24 24
25 25 $ hg up -q -C 1
26 26
27 27 $ echo l1 >>extra
28 28 $ hg add extra
29 29 $ hg ci -m L1
30 30 created new head
31 31
32 32 $ sed -e 's/c2/l2/' common > common.new
33 33 $ mv common.new common
34 34 $ hg ci -m L2
35 35
36 36 $ echo l3 >> extra2
37 37 $ hg add extra2
38 38 $ hg ci -m L3
39 39 $ hg bookmark mybook
40 40
41 41 $ hg phase --force --secret 4
42 42
43 43 $ hg tglog
44 44 @ 5:secret 'L3' mybook
45 45 |
46 46 o 4:secret 'L2'
47 47 |
48 48 o 3:draft 'L1'
49 49 |
50 50 | o 2:draft 'C3'
51 51 |/
52 52 o 1:draft 'C2'
53 53 |
54 54 o 0:draft 'C1'
55 55
56 56 Try to call --continue:
57 57
58 58 $ hg rebase --continue
59 59 abort: no rebase in progress
60 60 [255]
61 61
62 62 Conflicting rebase:
63 63
64 64 $ hg rebase -s 3 -d 2
65 65 rebasing 3:3163e20567cc "L1"
66 66 rebasing 4:46f0b057b5c0 "L2"
67 67 merging common
68 68 warning: conflicts while merging common! (edit, then use 'hg resolve --mark')
69 69 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
70 70 [1]
71 71
72 72 $ hg status --config commands.status.verbose=1
73 73 M common
74 74 ? common.orig
75 75 # The repository is in an unfinished *rebase* state.
76 76
77 77 # Unresolved merge conflicts:
78 78 #
79 79 # common
80 80 #
81 81 # To mark files as resolved: hg resolve --mark FILE
82 82
83 83 # To continue: hg rebase --continue
84 84 # To abort: hg rebase --abort
85 85 # To stop: hg rebase --stop
86 86
87 87
88 88 Try to continue without solving the conflict:
89 89
90 90 $ hg rebase --continue
91 91 abort: unresolved merge conflicts (see 'hg help resolve')
92 92 [255]
93 93
94 94 Conclude rebase:
95 95
96 96 $ echo 'resolved merge' >common
97 97 $ hg resolve -m common
98 98 (no more unresolved files)
99 99 continue: hg rebase --continue
100 100 $ hg rebase --continue
101 101 already rebased 3:3163e20567cc "L1" as 3e046f2ecedb
102 102 rebasing 4:46f0b057b5c0 "L2"
103 rebasing 5:8029388f38dc "L3" (mybook)
103 rebasing 5:8029388f38dc mybook "L3"
104 104 saved backup bundle to $TESTTMP/a/.hg/strip-backup/3163e20567cc-5ca4656e-rebase.hg
105 105
106 106 $ hg tglog
107 107 @ 5:secret 'L3' mybook
108 108 |
109 109 o 4:secret 'L2'
110 110 |
111 111 o 3:draft 'L1'
112 112 |
113 113 o 2:draft 'C3'
114 114 |
115 115 o 1:draft 'C2'
116 116 |
117 117 o 0:draft 'C1'
118 118
119 119 Check correctness:
120 120
121 121 $ hg cat -r 0 common
122 122 c1
123 123
124 124 $ hg cat -r 1 common
125 125 c1
126 126 c2
127 127
128 128 $ hg cat -r 2 common
129 129 c1
130 130 c2
131 131 c3
132 132
133 133 $ hg cat -r 3 common
134 134 c1
135 135 c2
136 136 c3
137 137
138 138 $ hg cat -r 4 common
139 139 resolved merge
140 140
141 141 $ hg cat -r 5 common
142 142 resolved merge
143 143
144 144 Bookmark stays active after --continue
145 145 $ hg bookmarks
146 146 * mybook 5:d67b21408fc0
147 147
148 148 $ cd ..
149 149
150 150 Check that the right ancestors is used while rebasing a merge (issue4041)
151 151
152 152 $ hg init issue4041
153 153 $ cd issue4041
154 154 $ hg unbundle "$TESTDIR/bundles/issue4041.hg"
155 155 adding changesets
156 156 adding manifests
157 157 adding file changes
158 158 added 11 changesets with 8 changes to 3 files (+1 heads)
159 159 new changesets 24797d4f68de:2f2496ddf49d (11 drafts)
160 160 (run 'hg heads' to see heads)
161 161 $ hg up default
162 162 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
163 163 $ hg log -G
164 164 o changeset: 10:2f2496ddf49d
165 165 |\ branch: f1
166 166 | | tag: tip
167 167 | | parent: 7:4c9fbe56a16f
168 168 | | parent: 9:e31216eec445
169 169 | | user: szhang
170 170 | | date: Thu Sep 05 12:59:39 2013 -0400
171 171 | | summary: merge
172 172 | |
173 173 | o changeset: 9:e31216eec445
174 174 | | branch: f1
175 175 | | user: szhang
176 176 | | date: Thu Sep 05 12:59:10 2013 -0400
177 177 | | summary: more changes to f1
178 178 | |
179 179 | o changeset: 8:8e4e2c1a07ae
180 180 | |\ branch: f1
181 181 | | | parent: 2:4bc80088dc6b
182 182 | | | parent: 6:400110238667
183 183 | | | user: szhang
184 184 | | | date: Thu Sep 05 12:57:59 2013 -0400
185 185 | | | summary: bad merge
186 186 | | |
187 187 o | | changeset: 7:4c9fbe56a16f
188 188 |/ / branch: f1
189 189 | | parent: 2:4bc80088dc6b
190 190 | | user: szhang
191 191 | | date: Thu Sep 05 12:54:00 2013 -0400
192 192 | | summary: changed f1
193 193 | |
194 194 | o changeset: 6:400110238667
195 195 | | branch: f2
196 196 | | parent: 4:12e8ec6bb010
197 197 | | user: szhang
198 198 | | date: Tue Sep 03 13:58:02 2013 -0400
199 199 | | summary: changed f2 on f2
200 200 | |
201 201 | | @ changeset: 5:d79e2059b5c0
202 202 | | | parent: 3:8a951942e016
203 203 | | | user: szhang
204 204 | | | date: Tue Sep 03 13:57:39 2013 -0400
205 205 | | | summary: changed f2 on default
206 206 | | |
207 207 | o | changeset: 4:12e8ec6bb010
208 208 | |/ branch: f2
209 209 | | user: szhang
210 210 | | date: Tue Sep 03 13:57:18 2013 -0400
211 211 | | summary: created f2 branch
212 212 | |
213 213 | o changeset: 3:8a951942e016
214 214 | | parent: 0:24797d4f68de
215 215 | | user: szhang
216 216 | | date: Tue Sep 03 13:57:11 2013 -0400
217 217 | | summary: added f2.txt
218 218 | |
219 219 o | changeset: 2:4bc80088dc6b
220 220 | | branch: f1
221 221 | | user: szhang
222 222 | | date: Tue Sep 03 13:56:20 2013 -0400
223 223 | | summary: added f1.txt
224 224 | |
225 225 o | changeset: 1:ef53c9e6b608
226 226 |/ branch: f1
227 227 | user: szhang
228 228 | date: Tue Sep 03 13:55:26 2013 -0400
229 229 | summary: created f1 branch
230 230 |
231 231 o changeset: 0:24797d4f68de
232 232 user: szhang
233 233 date: Tue Sep 03 13:55:08 2013 -0400
234 234 summary: added default.txt
235 235
236 236 $ hg rebase -s9 -d2 --debug # use debug to really check merge base used
237 237 rebase onto 4bc80088dc6b starting from e31216eec445
238 238 rebasing on disk
239 239 rebase status stored
240 240 rebasing 9:e31216eec445 "more changes to f1"
241 241 future parents are 2 and -1
242 242 update to 2:4bc80088dc6b
243 243 resolving manifests
244 244 branchmerge: False, force: True, partial: False
245 245 ancestor: d79e2059b5c0+, local: d79e2059b5c0+, remote: 4bc80088dc6b
246 246 f2.txt: other deleted -> r
247 247 removing f2.txt
248 248 f1.txt: remote created -> g
249 249 getting f1.txt
250 250 merge against 9:e31216eec445
251 251 detach base 8:8e4e2c1a07ae
252 252 resolving manifests
253 253 branchmerge: True, force: True, partial: False
254 254 ancestor: 8e4e2c1a07ae, local: 4bc80088dc6b+, remote: e31216eec445
255 255 f1.txt: remote is newer -> g
256 256 getting f1.txt
257 257 committing files:
258 258 f1.txt
259 259 committing manifest
260 260 committing changelog
261 261 updating the branch cache
262 262 rebased as 19c888675e13
263 263 rebase status stored
264 rebasing 10:2f2496ddf49d "merge" (tip)
264 rebasing 10:2f2496ddf49d tip "merge"
265 265 future parents are 11 and 7
266 266 already in destination
267 267 merge against 10:2f2496ddf49d
268 268 detach base 9:e31216eec445
269 269 resolving manifests
270 270 branchmerge: True, force: True, partial: False
271 271 ancestor: e31216eec445, local: 19c888675e13+, remote: 2f2496ddf49d
272 272 f1.txt: remote is newer -> g
273 273 getting f1.txt
274 274 committing files:
275 275 f1.txt
276 276 committing manifest
277 277 committing changelog
278 278 updating the branch cache
279 279 rebased as 2a7f09cac94c
280 280 rebase status stored
281 281 rebase merging completed
282 282 update back to initial working directory parent
283 283 resolving manifests
284 284 branchmerge: False, force: False, partial: False
285 285 ancestor: 2a7f09cac94c, local: 2a7f09cac94c+, remote: d79e2059b5c0
286 286 f1.txt: other deleted -> r
287 287 removing f1.txt
288 288 f2.txt: remote created -> g
289 289 getting f2.txt
290 290 2 changesets found
291 291 list of changesets:
292 292 e31216eec445e44352c5f01588856059466a24c9
293 293 2f2496ddf49d69b5ef23ad8cf9fb2e0e4faf0ac2
294 294 bundle2-output-bundle: "HG20", (1 params) 3 parts total
295 295 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
296 296 bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
297 297 bundle2-output-part: "phase-heads" 24 bytes payload
298 298 saved backup bundle to $TESTTMP/issue4041/.hg/strip-backup/e31216eec445-15f7a814-rebase.hg
299 299 3 changesets found
300 300 list of changesets:
301 301 4c9fbe56a16f30c0d5dcc40ec1a97bbe3325209c
302 302 19c888675e133ab5dff84516926a65672eaf04d9
303 303 2a7f09cac94c7f4b73ebd5cd1a62d3b2e8e336bf
304 304 bundle2-output-bundle: "HG20", 3 parts total
305 305 bundle2-output-part: "changegroup" (params: 1 mandatory 1 advisory) streamed payload
306 306 bundle2-output-part: "cache:rev-branch-cache" (advisory) streamed payload
307 307 bundle2-output-part: "phase-heads" 24 bytes payload
308 308 adding branch
309 309 bundle2-input-bundle: with-transaction
310 310 bundle2-input-part: "changegroup" (params: 1 mandatory 1 advisory) supported
311 311 adding changesets
312 312 add changeset 4c9fbe56a16f
313 313 add changeset 19c888675e13
314 314 add changeset 2a7f09cac94c
315 315 adding manifests
316 316 adding file changes
317 317 adding f1.txt revisions
318 318 bundle2-input-part: total payload size 1686
319 319 bundle2-input-part: "cache:rev-branch-cache" (advisory) supported
320 320 bundle2-input-part: total payload size 74
321 321 truncating cache/rbc-revs-v1 to 56
322 322 bundle2-input-part: "phase-heads" supported
323 323 bundle2-input-part: total payload size 24
324 324 bundle2-input-bundle: 3 parts total
325 325 added 2 changesets with 2 changes to 1 files
326 326 updating the branch cache
327 327 invalid branch cache (served): tip differs
328 328 invalid branch cache (served.hidden): tip differs
329 329 rebase completed
330 330
331 331 Test minimization of merge conflicts
332 332 $ hg up -q null
333 333 $ echo a > a
334 334 $ hg add a
335 335 $ hg commit -q -m 'a'
336 336 $ echo b >> a
337 337 $ hg commit -q -m 'ab'
338 338 $ hg bookmark ab
339 339 $ hg up -q '.^'
340 340 $ echo b >> a
341 341 $ echo c >> a
342 342 $ hg commit -q -m 'abc'
343 343 $ hg rebase -s 7bc217434fc1 -d ab --keep
344 rebasing 13:7bc217434fc1 "abc" (tip)
344 rebasing 13:7bc217434fc1 tip "abc"
345 345 merging a
346 346 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
347 347 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
348 348 [1]
349 349 $ hg diff
350 350 diff -r 328e4ab1f7cc a
351 351 --- a/a Thu Jan 01 00:00:00 1970 +0000
352 352 +++ b/a * (glob)
353 353 @@ -1,2 +1,6 @@
354 354 a
355 355 b
356 356 +<<<<<<< dest: 328e4ab1f7cc ab - test: ab
357 357 +=======
358 358 +c
359 359 +>>>>>>> source: 7bc217434fc1 - test: abc
360 360 $ hg rebase --abort
361 361 rebase aborted
362 362 $ hg up -q -C 7bc217434fc1
363 363 $ hg rebase -s . -d ab --keep -t internal:merge3
364 rebasing 13:7bc217434fc1 "abc" (tip)
364 rebasing 13:7bc217434fc1 tip "abc"
365 365 merging a
366 366 warning: conflicts while merging a! (edit, then use 'hg resolve --mark')
367 367 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
368 368 [1]
369 369 $ hg diff
370 370 diff -r 328e4ab1f7cc a
371 371 --- a/a Thu Jan 01 00:00:00 1970 +0000
372 372 +++ b/a * (glob)
373 373 @@ -1,2 +1,8 @@
374 374 a
375 375 +<<<<<<< dest: 328e4ab1f7cc ab - test: ab
376 376 b
377 377 +||||||| base
378 378 +=======
379 379 +b
380 380 +c
381 381 +>>>>>>> source: 7bc217434fc1 - test: abc
382 382
383 383 Test rebase with obsstore turned on and off (issue5606)
384 384
385 385 $ cd $TESTTMP
386 386 $ hg init b
387 387 $ cd b
388 388 $ hg debugdrawdag <<'EOS'
389 389 > D
390 390 > |
391 391 > C
392 392 > |
393 393 > B E
394 394 > |/
395 395 > A
396 396 > EOS
397 397
398 398 $ hg update E -q
399 399 $ echo 3 > B
400 400 $ hg commit --amend -m E -A B -q
401 401 $ hg rebase -r B+D -d . --config experimental.evolution=true
402 rebasing 1:112478962961 "B" (B)
402 rebasing 1:112478962961 B "B"
403 403 merging B
404 404 warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
405 405 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
406 406 [1]
407 407
408 408 $ echo 4 > B
409 409 $ hg resolve -m
410 410 (no more unresolved files)
411 411 continue: hg rebase --continue
412 412 $ hg rebase --continue --config experimental.evolution=none
413 rebasing 1:112478962961 "B" (B)
414 rebasing 3:f585351a92f8 "D" (D)
413 rebasing 1:112478962961 B "B"
414 rebasing 3:f585351a92f8 D "D"
415 415 warning: orphaned descendants detected, not stripping 112478962961
416 416 saved backup bundle to $TESTTMP/b/.hg/strip-backup/f585351a92f8-e536a9e4-rebase.hg
417 417
418 418 $ rm .hg/localtags
419 419 $ hg tglog
420 420 o 5:draft 'D'
421 421 |
422 422 o 4:draft 'B'
423 423 |
424 424 @ 3:draft 'E'
425 425 |
426 426 | o 2:draft 'C'
427 427 | |
428 428 | o 1:draft 'B'
429 429 |/
430 430 o 0:draft 'A'
431 431
432 432
433 433 Test where the conflict happens when rebasing a merge commit
434 434
435 435 $ cd $TESTTMP
436 436 $ hg init conflict-in-merge
437 437 $ cd conflict-in-merge
438 438 $ hg debugdrawdag <<'EOS'
439 439 > F # F/conflict = foo\n
440 440 > |\
441 441 > D E
442 442 > |/
443 443 > C B # B/conflict = bar\n
444 444 > |/
445 445 > A
446 446 > EOS
447 447
448 448 $ hg co F
449 449 5 files updated, 0 files merged, 0 files removed, 0 files unresolved
450 450 $ hg rebase -d B
451 rebasing 2:dc0947a82db8 "C" (C)
452 rebasing 3:e7b3f00ed42e "D" (D)
453 rebasing 4:03ca77807e91 "E" (E)
454 rebasing 5:9a6b91dc2044 "F" (F tip)
451 rebasing 2:dc0947a82db8 C "C"
452 rebasing 3:e7b3f00ed42e D "D"
453 rebasing 4:03ca77807e91 E "E"
454 rebasing 5:9a6b91dc2044 F tip "F"
455 455 merging conflict
456 456 warning: conflicts while merging conflict! (edit, then use 'hg resolve --mark')
457 457 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
458 458 [1]
459 459 $ hg tglog
460 460 @ 8:draft 'E'
461 461 |
462 462 | @ 7:draft 'D'
463 463 |/
464 464 o 6:draft 'C'
465 465 |
466 466 | % 5:draft 'F'
467 467 | |\
468 468 | | o 4:draft 'E'
469 469 | | |
470 470 | o | 3:draft 'D'
471 471 | |/
472 472 | o 2:draft 'C'
473 473 | |
474 474 o | 1:draft 'B'
475 475 |/
476 476 o 0:draft 'A'
477 477
478 478 $ echo baz > conflict
479 479 $ hg resolve -m
480 480 (no more unresolved files)
481 481 continue: hg rebase --continue
482 482 $ hg rebase -c
483 already rebased 2:dc0947a82db8 "C" (C) as 0199610c343e
484 already rebased 3:e7b3f00ed42e "D" (D) as f0dd538aaa63
485 already rebased 4:03ca77807e91 "E" (E) as cbf25af8347d
486 rebasing 5:9a6b91dc2044 "F" (F)
483 already rebased 2:dc0947a82db8 C "C" as 0199610c343e
484 already rebased 3:e7b3f00ed42e D "D" as f0dd538aaa63
485 already rebased 4:03ca77807e91 E "E" as cbf25af8347d
486 rebasing 5:9a6b91dc2044 F "F"
487 487 saved backup bundle to $TESTTMP/conflict-in-merge/.hg/strip-backup/dc0947a82db8-ca7e7d5b-rebase.hg
488 488 $ hg tglog
489 489 @ 5:draft 'F'
490 490 |\
491 491 | o 4:draft 'E'
492 492 | |
493 493 o | 3:draft 'D'
494 494 |/
495 495 o 2:draft 'C'
496 496 |
497 497 o 1:draft 'B'
498 498 |
499 499 o 0:draft 'A'
500 500
@@ -1,465 +1,465 b''
1 1 Require a destination
2 2 $ cat >> $HGRCPATH <<EOF
3 3 > [extensions]
4 4 > rebase =
5 5 > [commands]
6 6 > rebase.requiredest = True
7 7 > EOF
8 8 $ hg init repo
9 9 $ cd repo
10 10 $ echo a >> a
11 11 $ hg commit -qAm aa
12 12 $ echo b >> b
13 13 $ hg commit -qAm bb
14 14 $ hg up ".^"
15 15 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
16 16 $ echo c >> c
17 17 $ hg commit -qAm cc
18 18 $ hg rebase
19 19 abort: you must specify a destination
20 20 (use: hg rebase -d REV)
21 21 [255]
22 22 $ hg rebase -d 1
23 rebasing 2:5db65b93a12b "cc" (tip)
23 rebasing 2:5db65b93a12b tip "cc"
24 24 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/5db65b93a12b-4fb789ec-rebase.hg
25 25 $ hg rebase -d 0 -r . -q
26 26 $ HGPLAIN=1 hg rebase
27 rebasing 2:889b0bc6a730 "cc" (tip)
27 rebasing 2:889b0bc6a730 tip "cc"
28 28 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/889b0bc6a730-41ec4f81-rebase.hg
29 29 $ hg rebase -d 0 -r . -q
30 30 $ hg --config commands.rebase.requiredest=False rebase
31 rebasing 2:279de9495438 "cc" (tip)
31 rebasing 2:279de9495438 tip "cc"
32 32 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/279de9495438-ab0a5128-rebase.hg
33 33
34 34 Requiring dest should not break continue or other rebase options
35 35 $ hg up 1 -q
36 36 $ echo d >> c
37 37 $ hg commit -qAm dc
38 38 $ hg log -G -T '{rev} {desc}'
39 39 @ 3 dc
40 40 |
41 41 | o 2 cc
42 42 |/
43 43 o 1 bb
44 44 |
45 45 o 0 aa
46 46
47 47 $ hg rebase -d 2
48 rebasing 3:0537f6b50def "dc" (tip)
48 rebasing 3:0537f6b50def tip "dc"
49 49 merging c
50 50 warning: conflicts while merging c! (edit, then use 'hg resolve --mark')
51 51 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
52 52 [1]
53 53 $ echo d > c
54 54 $ hg resolve --mark --all
55 55 (no more unresolved files)
56 56 continue: hg rebase --continue
57 57 $ hg rebase --continue
58 rebasing 3:0537f6b50def "dc" (tip)
58 rebasing 3:0537f6b50def tip "dc"
59 59 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/0537f6b50def-be4c7386-rebase.hg
60 60
61 61 $ cd ..
62 62
63 63 Check rebase.requiredest interaction with pull --rebase
64 64 $ hg clone repo clone
65 65 updating to branch default
66 66 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
67 67 $ cd repo
68 68 $ echo e > e
69 69 $ hg commit -qAm ee
70 70 $ cd ..
71 71 $ cd clone
72 72 $ echo f > f
73 73 $ hg commit -qAm ff
74 74 $ hg pull --rebase
75 75 abort: rebase destination required by configuration
76 76 (use hg pull followed by hg rebase -d DEST)
77 77 [255]
78 78
79 79 Setup rebase with multiple destinations
80 80
81 81 $ cd $TESTTMP
82 82
83 83 $ cat >> $TESTTMP/maprevset.py <<EOF
84 84 > from __future__ import absolute_import
85 85 > from mercurial import registrar, revset, revsetlang, smartset
86 86 > revsetpredicate = registrar.revsetpredicate()
87 87 > cache = {}
88 88 > @revsetpredicate(b'map')
89 89 > def map(repo, subset, x):
90 90 > """(set, mapping)"""
91 91 > setarg, maparg = revsetlang.getargs(x, 2, 2, b'')
92 92 > rset = revset.getset(repo, smartset.fullreposet(repo), setarg)
93 93 > mapstr = revsetlang.getstring(maparg, b'')
94 94 > map = dict(a.split(b':') for a in mapstr.split(b','))
95 95 > rev = rset.first()
96 96 > desc = repo[rev].description()
97 97 > newdesc = map.get(desc)
98 98 > if newdesc == b'null':
99 99 > revs = [-1]
100 100 > else:
101 101 > query = revsetlang.formatspec(b'desc(%s)', newdesc)
102 102 > revs = repo.revs(query)
103 103 > return smartset.baseset(revs)
104 104 > EOF
105 105
106 106 $ cat >> $HGRCPATH <<EOF
107 107 > [ui]
108 108 > allowemptycommit=1
109 109 > [extensions]
110 110 > drawdag=$TESTDIR/drawdag.py
111 111 > [phases]
112 112 > publish=False
113 113 > [alias]
114 114 > tglog = log -G --template "{rev}: {node|short} {desc} {instabilities}" -r 'sort(all(), topo)'
115 115 > [extensions]
116 116 > maprevset=$TESTTMP/maprevset.py
117 117 > [experimental]
118 118 > evolution=true
119 119 > EOF
120 120
121 121 $ rebasewithdag() {
122 122 > N=`"$PYTHON" -c "print($N+1)"`
123 123 > hg init repo$N && cd repo$N
124 124 > hg debugdrawdag
125 125 > hg rebase "$@" > _rebasetmp
126 126 > r=$?
127 127 > grep -v 'saved backup bundle' _rebasetmp
128 128 > [ $r -eq 0 ] && rm -f .hg/localtags && hg tglog
129 129 > cd ..
130 130 > return $r
131 131 > }
132 132
133 133 Destination resolves to an empty set:
134 134
135 135 $ rebasewithdag -s B -d 'SRC - SRC' <<'EOS'
136 136 > C
137 137 > |
138 138 > B
139 139 > |
140 140 > A
141 141 > EOS
142 142 nothing to rebase - empty destination
143 143 [1]
144 144
145 145 Multiple destinations and --collapse are not compatible:
146 146
147 147 $ rebasewithdag -s C+E -d 'SRC^^' --collapse <<'EOS'
148 148 > C F
149 149 > | |
150 150 > B E
151 151 > | |
152 152 > A D
153 153 > EOS
154 154 abort: --collapse does not work with multiple destinations
155 155 [255]
156 156
157 157 Multiple destinations cannot be used with --base:
158 158
159 159 $ rebasewithdag -b B -b E -d 'SRC^^' --collapse <<'EOS'
160 160 > B E
161 161 > | |
162 162 > A D
163 163 > EOS
164 164 abort: unknown revision 'SRC'!
165 165 [255]
166 166
167 167 Rebase to null should work:
168 168
169 169 $ rebasewithdag -r A+C+D -d 'null' <<'EOS'
170 170 > C D
171 171 > | |
172 172 > A B
173 173 > EOS
174 already rebased 0:426bada5c675 "A" (A)
175 already rebased 2:dc0947a82db8 "C" (C)
176 rebasing 3:004dc1679908 "D" (D tip)
174 already rebased 0:426bada5c675 A "A"
175 already rebased 2:dc0947a82db8 C "C"
176 rebasing 3:004dc1679908 D tip "D"
177 177 o 4: d8d8601abd5e D
178 178
179 179 o 2: dc0947a82db8 C
180 180 |
181 181 | o 1: fc2b737bb2e5 B
182 182 |
183 183 o 0: 426bada5c675 A
184 184
185 185 Destination resolves to multiple changesets:
186 186
187 187 $ rebasewithdag -s B -d 'ALLSRC+SRC' <<'EOS'
188 188 > C
189 189 > |
190 190 > B
191 191 > |
192 192 > Z
193 193 > EOS
194 194 abort: rebase destination for f0a671a46792 is not unique
195 195 [255]
196 196
197 197 Destination is an ancestor of source:
198 198
199 199 $ rebasewithdag -s B -d 'SRC' <<'EOS'
200 200 > C
201 201 > |
202 202 > B
203 203 > |
204 204 > Z
205 205 > EOS
206 206 abort: source and destination form a cycle
207 207 [255]
208 208
209 209 BUG: cycles aren't flagged correctly when --dry-run is set:
210 210 $ rebasewithdag -s B -d 'SRC' --dry-run <<'EOS'
211 211 > C
212 212 > |
213 213 > B
214 214 > |
215 215 > Z
216 216 > EOS
217 217 abort: source and destination form a cycle
218 218 starting dry-run rebase; repository will not be changed
219 219 [255]
220 220
221 221 Switch roots:
222 222
223 223 $ rebasewithdag -s 'all() - roots(all())' -d 'roots(all()) - ::SRC' <<'EOS'
224 224 > C F
225 225 > | |
226 226 > B E
227 227 > | |
228 228 > A D
229 229 > EOS
230 rebasing 2:112478962961 "B" (B)
231 rebasing 4:26805aba1e60 "C" (C)
232 rebasing 3:cd488e83d208 "E" (E)
233 rebasing 5:0069ba24938a "F" (F tip)
230 rebasing 2:112478962961 B "B"
231 rebasing 4:26805aba1e60 C "C"
232 rebasing 3:cd488e83d208 E "E"
233 rebasing 5:0069ba24938a F tip "F"
234 234 o 9: d150ff263fc8 F
235 235 |
236 236 o 8: 66f30a1a2eab E
237 237 |
238 238 | o 7: 93db94ffae0e C
239 239 | |
240 240 | o 6: d0071c3b0c88 B
241 241 | |
242 242 | o 1: 058c1e1fb10a D
243 243 |
244 244 o 0: 426bada5c675 A
245 245
246 246 Different destinations for merge changesets with a same root:
247 247
248 248 $ rebasewithdag -s B -d '((parents(SRC)-B-A)::) - (::ALLSRC)' <<'EOS'
249 249 > C G
250 250 > |\|
251 251 > | F
252 252 > |
253 253 > B E
254 254 > |\|
255 255 > A D
256 256 > EOS
257 rebasing 3:a4256619d830 "B" (B)
258 rebasing 6:8e139e245220 "C" (C tip)
257 rebasing 3:a4256619d830 B "B"
258 rebasing 6:8e139e245220 C tip "C"
259 259 o 8: d7d1169e9b1c C
260 260 |\
261 261 | o 7: 2ed0c8546285 B
262 262 | |\
263 263 o | | 5: 8fdb2c1feb20 G
264 264 | | |
265 265 | | o 4: cd488e83d208 E
266 266 | | |
267 267 o | | 2: a6661b868de9 F
268 268 / /
269 269 | o 1: 058c1e1fb10a D
270 270 |
271 271 o 0: 426bada5c675 A
272 272
273 273 Move to a previous parent:
274 274
275 275 $ rebasewithdag -s E -s F -s G -d 'SRC^^' <<'EOS'
276 276 > H
277 277 > |
278 278 > D G
279 279 > |/
280 280 > C F
281 281 > |/
282 282 > B E # E will be ignored, since E^^ is empty
283 283 > |/
284 284 > A
285 285 > EOS
286 rebasing 4:33441538d4aa "F" (F)
287 rebasing 6:cf43ad9da869 "G" (G)
288 rebasing 7:eef94f3b5f03 "H" (H tip)
286 rebasing 4:33441538d4aa F "F"
287 rebasing 6:cf43ad9da869 G "G"
288 rebasing 7:eef94f3b5f03 H tip "H"
289 289 o 10: b3d84c6666cf H
290 290 |
291 291 | o 5: f585351a92f8 D
292 292 |/
293 293 o 3: 26805aba1e60 C
294 294 |
295 295 | o 9: f7c28a1a15e2 G
296 296 |/
297 297 o 1: 112478962961 B
298 298 |
299 299 | o 8: 02aa697facf7 F
300 300 |/
301 301 | o 2: 7fb047a69f22 E
302 302 |/
303 303 o 0: 426bada5c675 A
304 304
305 305 Source overlaps with destination:
306 306
307 307 $ rebasewithdag -s 'B+C+D' -d 'map(SRC, "B:C,C:D")' <<'EOS'
308 308 > B C D
309 309 > \|/
310 310 > A
311 311 > EOS
312 rebasing 2:dc0947a82db8 "C" (C)
313 rebasing 1:112478962961 "B" (B)
312 rebasing 2:dc0947a82db8 C "C"
313 rebasing 1:112478962961 B "B"
314 314 o 5: 5fe9935d5222 B
315 315 |
316 316 o 4: 12d20731b9e0 C
317 317 |
318 318 o 3: b18e25de2cf5 D
319 319 |
320 320 o 0: 426bada5c675 A
321 321
322 322 Detect cycles early:
323 323
324 324 $ rebasewithdag -r 'all()-Z' -d 'map(SRC, "A:B,B:C,C:D,D:B")' <<'EOS'
325 325 > A B C
326 326 > \|/
327 327 > | D
328 328 > |/
329 329 > Z
330 330 > EOS
331 331 abort: source and destination form a cycle
332 332 [255]
333 333
334 334 Detect source is ancestor of dest in runtime:
335 335
336 336 $ rebasewithdag -r 'C+B' -d 'map(SRC, "C:B,B:D")' -q <<'EOS'
337 337 > D
338 338 > |
339 339 > B C
340 340 > \|
341 341 > A
342 342 > EOS
343 343 abort: source is ancestor of destination
344 344 [255]
345 345
346 346 "Already rebased" fast path still works:
347 347
348 348 $ rebasewithdag -r 'all()' -d 'SRC^' <<'EOS'
349 349 > E F
350 350 > /| |
351 351 > B C D
352 352 > \|/
353 353 > A
354 354 > EOS
355 already rebased 1:112478962961 "B" (B)
356 already rebased 2:dc0947a82db8 "C" (C)
357 already rebased 3:b18e25de2cf5 "D" (D)
358 already rebased 4:312782b8f06e "E" (E)
359 already rebased 5:ad6717a6a58e "F" (F tip)
355 already rebased 1:112478962961 B "B"
356 already rebased 2:dc0947a82db8 C "C"
357 already rebased 3:b18e25de2cf5 D "D"
358 already rebased 4:312782b8f06e E "E"
359 already rebased 5:ad6717a6a58e F tip "F"
360 360 o 5: ad6717a6a58e F
361 361 |
362 362 o 3: b18e25de2cf5 D
363 363 |
364 364 | o 4: 312782b8f06e E
365 365 | |\
366 366 +---o 2: dc0947a82db8 C
367 367 | |
368 368 | o 1: 112478962961 B
369 369 |/
370 370 o 0: 426bada5c675 A
371 371
372 372 Massively rewrite the DAG:
373 373
374 374 $ rebasewithdag -r 'all()' -d 'map(SRC, "A:I,I:null,H:A,B:J,J:C,C:H,D:E,F:G,G:K,K:D,E:B")' <<'EOS'
375 375 > D G K
376 376 > | | |
377 377 > C F J
378 378 > | | |
379 379 > B E I
380 380 > \| |
381 381 > A H
382 382 > EOS
383 rebasing 4:701514e1408d "I" (I)
384 rebasing 0:426bada5c675 "A" (A)
385 rebasing 1:e7050b6e5048 "H" (H)
386 rebasing 5:26805aba1e60 "C" (C)
387 rebasing 7:cf89f86b485b "J" (J)
388 rebasing 2:112478962961 "B" (B)
389 rebasing 3:7fb047a69f22 "E" (E)
390 rebasing 8:f585351a92f8 "D" (D)
391 rebasing 10:ae41898d7875 "K" (K tip)
392 rebasing 9:711f53bbef0b "G" (G)
393 rebasing 6:64a8289d2492 "F" (F)
383 rebasing 4:701514e1408d I "I"
384 rebasing 0:426bada5c675 A "A"
385 rebasing 1:e7050b6e5048 H "H"
386 rebasing 5:26805aba1e60 C "C"
387 rebasing 7:cf89f86b485b J "J"
388 rebasing 2:112478962961 B "B"
389 rebasing 3:7fb047a69f22 E "E"
390 rebasing 8:f585351a92f8 D "D"
391 rebasing 10:ae41898d7875 K tip "K"
392 rebasing 9:711f53bbef0b G "G"
393 rebasing 6:64a8289d2492 F "F"
394 394 o 21: 3735afb3713a F
395 395 |
396 396 o 20: 07698142d7a7 G
397 397 |
398 398 o 19: 33aba52e7e72 K
399 399 |
400 400 o 18: 9fdae89dc5a1 D
401 401 |
402 402 o 17: 277dda9a65ee E
403 403 |
404 404 o 16: 9c74fd8657ad B
405 405 |
406 406 o 15: 6527eb0688bb J
407 407 |
408 408 o 14: e94d655b928d C
409 409 |
410 410 o 13: 620d6d349459 H
411 411 |
412 412 o 12: a569a116758f A
413 413 |
414 414 o 11: 2bf1302f5c18 I
415 415
416 416 Resolve instability:
417 417
418 418 $ rebasewithdag <<'EOF' -r 'orphan()-obsolete()' -d 'max((successors(max(roots(ALLSRC) & ::SRC)^)-obsolete())::)'
419 419 > F2
420 420 > |
421 421 > J E E2
422 422 > | |/
423 423 > I2 I | E3
424 424 > \| |/
425 425 > H | G
426 426 > | | |
427 427 > B2 D F
428 428 > | |/ # rebase: B -> B2
429 429 > N C # amend: E -> E2
430 430 > | | # amend: E2 -> E3
431 431 > M B # rebase: F -> F2
432 432 > \| # amend: I -> I2
433 433 > A
434 434 > EOF
435 435 6 new orphan changesets
436 rebasing 16:5c432343bf59 "J" (J tip)
437 rebasing 3:26805aba1e60 "C" (C)
438 rebasing 6:f585351a92f8 "D" (D)
439 rebasing 10:ffebc37c5d0b "E3" (E3)
440 rebasing 13:fb184bcfeee8 "F2" (F2)
441 rebasing 11:dc838ab4c0da "G" (G)
436 rebasing 16:5c432343bf59 J tip "J"
437 rebasing 3:26805aba1e60 C "C"
438 rebasing 6:f585351a92f8 D "D"
439 rebasing 10:ffebc37c5d0b E3 "E3"
440 rebasing 13:fb184bcfeee8 F2 "F2"
441 rebasing 11:dc838ab4c0da G "G"
442 442 o 22: 174f63d574a8 G
443 443 |
444 444 o 21: c9d9fbe76705 F2
445 445 |
446 446 o 20: 0a03c2ede755 E3
447 447 |
448 448 o 19: 228d9d2541b1 D
449 449 |
450 450 o 18: cd856b400c95 C
451 451 |
452 452 o 17: 9148200c858c J
453 453 |
454 454 o 15: eb74780f5094 I2
455 455 |
456 456 o 12: 78309edd643f H
457 457 |
458 458 o 5: 4b4531bd8e1d B2
459 459 |
460 460 o 4: 337c285c272b N
461 461 |
462 462 o 2: 699bc4b6fa22 M
463 463 |
464 464 o 0: 426bada5c675 A
465 465
@@ -1,317 +1,317 b''
1 1 $ cat >> $HGRCPATH <<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 >
5 5 > [alias]
6 6 > tglog = log -G --template "{rev}: {node|short} '{desc}'\n"
7 7 >
8 8 > [extensions]
9 9 > drawdag=$TESTDIR/drawdag.py
10 10 > EOF
11 11
12 12 Rebasing D onto B detaching from C (one commit):
13 13
14 14 $ hg init a1
15 15 $ cd a1
16 16
17 17 $ hg debugdrawdag <<EOF
18 18 > D
19 19 > |
20 20 > C B
21 21 > |/
22 22 > A
23 23 > EOF
24 24 $ hg phase --force --secret D
25 25
26 26 $ hg rebase -s D -d B
27 rebasing 3:e7b3f00ed42e "D" (D tip)
27 rebasing 3:e7b3f00ed42e D tip "D"
28 28 saved backup bundle to $TESTTMP/a1/.hg/strip-backup/e7b3f00ed42e-6f368371-rebase.hg
29 29
30 30 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
31 31 o 3:secret 'D'
32 32 |
33 33 | o 2:draft 'C'
34 34 | |
35 35 o | 1:draft 'B'
36 36 |/
37 37 o 0:draft 'A'
38 38
39 39 $ hg manifest --rev tip
40 40 A
41 41 B
42 42 D
43 43
44 44 $ cd ..
45 45
46 46
47 47 Rebasing D onto B detaching from C (two commits):
48 48
49 49 $ hg init a2
50 50 $ cd a2
51 51
52 52 $ hg debugdrawdag <<EOF
53 53 > E
54 54 > |
55 55 > D
56 56 > |
57 57 > C B
58 58 > |/
59 59 > A
60 60 > EOF
61 61
62 62 $ hg rebase -s D -d B
63 rebasing 3:e7b3f00ed42e "D" (D)
64 rebasing 4:69a34c08022a "E" (E tip)
63 rebasing 3:e7b3f00ed42e D "D"
64 rebasing 4:69a34c08022a E tip "E"
65 65 saved backup bundle to $TESTTMP/a2/.hg/strip-backup/e7b3f00ed42e-a2ec7cea-rebase.hg
66 66
67 67 $ hg tglog
68 68 o 4: ee79e0744528 'E'
69 69 |
70 70 o 3: 10530e1d72d9 'D'
71 71 |
72 72 | o 2: dc0947a82db8 'C'
73 73 | |
74 74 o | 1: 112478962961 'B'
75 75 |/
76 76 o 0: 426bada5c675 'A'
77 77
78 78 $ hg manifest --rev tip
79 79 A
80 80 B
81 81 D
82 82 E
83 83
84 84 $ cd ..
85 85
86 86 Rebasing C onto B using detach (same as not using it):
87 87
88 88 $ hg init a3
89 89 $ cd a3
90 90
91 91 $ hg debugdrawdag <<EOF
92 92 > D
93 93 > |
94 94 > C B
95 95 > |/
96 96 > A
97 97 > EOF
98 98
99 99 $ hg rebase -s C -d B
100 rebasing 2:dc0947a82db8 "C" (C)
101 rebasing 3:e7b3f00ed42e "D" (D tip)
100 rebasing 2:dc0947a82db8 C "C"
101 rebasing 3:e7b3f00ed42e D tip "D"
102 102 saved backup bundle to $TESTTMP/a3/.hg/strip-backup/dc0947a82db8-b8481714-rebase.hg
103 103
104 104 $ hg tglog
105 105 o 3: 7375f3dbfb0f 'D'
106 106 |
107 107 o 2: bbfdd6cb49aa 'C'
108 108 |
109 109 o 1: 112478962961 'B'
110 110 |
111 111 o 0: 426bada5c675 'A'
112 112
113 113 $ hg manifest --rev tip
114 114 A
115 115 B
116 116 C
117 117 D
118 118
119 119 $ cd ..
120 120
121 121
122 122 Rebasing D onto B detaching from C and collapsing:
123 123
124 124 $ hg init a4
125 125 $ cd a4
126 126
127 127 $ hg debugdrawdag <<EOF
128 128 > E
129 129 > |
130 130 > D
131 131 > |
132 132 > C B
133 133 > |/
134 134 > A
135 135 > EOF
136 136 $ hg phase --force --secret E
137 137
138 138 $ hg rebase --collapse -s D -d B
139 rebasing 3:e7b3f00ed42e "D" (D)
140 rebasing 4:69a34c08022a "E" (E tip)
139 rebasing 3:e7b3f00ed42e D "D"
140 rebasing 4:69a34c08022a E tip "E"
141 141 saved backup bundle to $TESTTMP/a4/.hg/strip-backup/e7b3f00ed42e-a2ec7cea-rebase.hg
142 142
143 143 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
144 144 o 3:secret 'Collapsed revision
145 145 | * D
146 146 | * E'
147 147 | o 2:draft 'C'
148 148 | |
149 149 o | 1:draft 'B'
150 150 |/
151 151 o 0:draft 'A'
152 152
153 153 $ hg manifest --rev tip
154 154 A
155 155 B
156 156 D
157 157 E
158 158
159 159 $ cd ..
160 160
161 161 Rebasing across null as ancestor
162 162 $ hg init a5
163 163 $ cd a5
164 164
165 165 $ hg debugdrawdag <<EOF
166 166 > E
167 167 > |
168 168 > D
169 169 > |
170 170 > C
171 171 > |
172 172 > A B
173 173 > EOF
174 174
175 175 $ hg rebase -s C -d B
176 rebasing 2:dc0947a82db8 "C" (C)
177 rebasing 3:e7b3f00ed42e "D" (D)
178 rebasing 4:69a34c08022a "E" (E tip)
176 rebasing 2:dc0947a82db8 C "C"
177 rebasing 3:e7b3f00ed42e D "D"
178 rebasing 4:69a34c08022a E tip "E"
179 179 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/dc0947a82db8-3eefec98-rebase.hg
180 180
181 181 $ hg tglog
182 182 o 4: e3d0c70d606d 'E'
183 183 |
184 184 o 3: e9153d36a1af 'D'
185 185 |
186 186 o 2: a7ac28b870a8 'C'
187 187 |
188 188 o 1: fc2b737bb2e5 'B'
189 189
190 190 o 0: 426bada5c675 'A'
191 191
192 192 $ hg rebase -d 1 -s 3
193 193 rebasing 3:e9153d36a1af "D"
194 rebasing 4:e3d0c70d606d "E" (tip)
194 rebasing 4:e3d0c70d606d tip "E"
195 195 saved backup bundle to $TESTTMP/a5/.hg/strip-backup/e9153d36a1af-db7388ed-rebase.hg
196 196 $ hg tglog
197 197 o 4: 2c24e540eccd 'E'
198 198 |
199 199 o 3: 73f786ed52ff 'D'
200 200 |
201 201 | o 2: a7ac28b870a8 'C'
202 202 |/
203 203 o 1: fc2b737bb2e5 'B'
204 204
205 205 o 0: 426bada5c675 'A'
206 206
207 207 $ cd ..
208 208
209 209 Verify that target is not selected as external rev (issue3085)
210 210
211 211 $ hg init a6
212 212 $ cd a6
213 213
214 214 $ hg debugdrawdag <<EOF
215 215 > H
216 216 > | G
217 217 > |/|
218 218 > F E
219 219 > |/
220 220 > A
221 221 > EOF
222 222 $ hg up -q G
223 223
224 224 $ echo "I" >> E
225 225 $ hg ci -m "I"
226 226 $ hg tag --local I
227 227 $ hg merge H
228 228 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
229 229 (branch merge, don't forget to commit)
230 230 $ hg ci -m "Merge"
231 231 $ echo "J" >> F
232 232 $ hg ci -m "J"
233 233 $ hg tglog
234 234 @ 7: c6aaf0d259c0 'J'
235 235 |
236 236 o 6: 0cfbc7e8faaf 'Merge'
237 237 |\
238 238 | o 5: b92d164ad3cb 'I'
239 239 | |
240 240 o | 4: 4ea5b230dea3 'H'
241 241 | |
242 242 | o 3: c6001eacfde5 'G'
243 243 |/|
244 244 o | 2: 8908a377a434 'F'
245 245 | |
246 246 | o 1: 7fb047a69f22 'E'
247 247 |/
248 248 o 0: 426bada5c675 'A'
249 249
250 250 $ hg rebase -s I -d H --collapse --config ui.merge=internal:other
251 rebasing 5:b92d164ad3cb "I" (I)
251 rebasing 5:b92d164ad3cb I "I"
252 252 rebasing 6:0cfbc7e8faaf "Merge"
253 rebasing 7:c6aaf0d259c0 "J" (tip)
253 rebasing 7:c6aaf0d259c0 tip "J"
254 254 saved backup bundle to $TESTTMP/a6/.hg/strip-backup/b92d164ad3cb-88fd7ab7-rebase.hg
255 255
256 256 $ hg tglog
257 257 @ 5: 65079693dac4 'Collapsed revision
258 258 | * I
259 259 | * Merge
260 260 | * J'
261 261 o 4: 4ea5b230dea3 'H'
262 262 |
263 263 | o 3: c6001eacfde5 'G'
264 264 |/|
265 265 o | 2: 8908a377a434 'F'
266 266 | |
267 267 | o 1: 7fb047a69f22 'E'
268 268 |/
269 269 o 0: 426bada5c675 'A'
270 270
271 271
272 272 $ hg log --rev tip
273 273 changeset: 5:65079693dac4
274 274 tag: tip
275 275 user: test
276 276 date: Thu Jan 01 00:00:00 1970 +0000
277 277 summary: Collapsed revision
278 278
279 279
280 280 $ cd ..
281 281
282 282 Ensure --continue restores a correct state (issue3046) and phase:
283 283 $ hg init a7
284 284 $ cd a7
285 285
286 286 $ hg debugdrawdag <<EOF
287 287 > C B
288 288 > |/
289 289 > A
290 290 > EOF
291 291 $ hg up -q C
292 292 $ echo 'B2' > B
293 293 $ hg ci -A -m 'B2'
294 294 adding B
295 295 $ hg phase --force --secret .
296 296 $ hg rebase -s . -d B --config ui.merge=internal:merge
297 rebasing 3:17b4880d2402 "B2" (tip)
297 rebasing 3:17b4880d2402 tip "B2"
298 298 merging B
299 299 warning: conflicts while merging B! (edit, then use 'hg resolve --mark')
300 300 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
301 301 [1]
302 302 $ hg resolve --all -t internal:local
303 303 (no more unresolved files)
304 304 continue: hg rebase --continue
305 305 $ hg rebase -c
306 rebasing 3:17b4880d2402 "B2" (tip)
307 note: not rebasing 3:17b4880d2402 "B2" (tip), its destination already has all its changes
306 rebasing 3:17b4880d2402 tip "B2"
307 note: not rebasing 3:17b4880d2402 tip "B2", its destination already has all its changes
308 308 saved backup bundle to $TESTTMP/a7/.hg/strip-backup/17b4880d2402-1ae1f6cc-rebase.hg
309 309 $ hg log -G --template "{rev}:{phase} '{desc}' {branches}\n"
310 310 o 2:draft 'C'
311 311 |
312 312 | @ 1:draft 'B'
313 313 |/
314 314 o 0:draft 'A'
315 315
316 316
317 317 $ cd ..
@@ -1,44 +1,44 b''
1 1 $ cat << EOF >> $HGRCPATH
2 2 > [extensions]
3 3 > rebase=
4 4 > [alias]
5 5 > tglog = log -G -T "{rev} '{desc}'\n"
6 6 > EOF
7 7
8 8 $ hg init
9 9
10 10 $ echo a > a; hg add a; hg ci -m a
11 11 $ echo b > b; hg add b; hg ci -m b1
12 12 $ hg up 0 -q
13 13 $ echo b > b; hg add b; hg ci -m b2 -q
14 14
15 15 $ hg tglog
16 16 @ 2 'b2'
17 17 |
18 18 | o 1 'b1'
19 19 |/
20 20 o 0 'a'
21 21
22 22
23 23 With rewrite.empty-successor=skip, b2 is skipped because it would become empty.
24 24
25 25 $ hg rebase -s 2 -d 1 --config rewrite.empty-successor=skip --dry-run
26 26 starting dry-run rebase; repository will not be changed
27 rebasing 2:6e2aad5e0f3c "b2" (tip)
28 note: not rebasing 2:6e2aad5e0f3c "b2" (tip), its destination already has all its changes
27 rebasing 2:6e2aad5e0f3c tip "b2"
28 note: not rebasing 2:6e2aad5e0f3c tip "b2", its destination already has all its changes
29 29 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
30 30
31 31 With rewrite.empty-successor=keep, b2 will be recreated although it became empty.
32 32
33 33 $ hg rebase -s 2 -d 1 --config rewrite.empty-successor=keep
34 rebasing 2:6e2aad5e0f3c "b2" (tip)
35 note: created empty successor for 2:6e2aad5e0f3c "b2" (tip), its destination already has all its changes
34 rebasing 2:6e2aad5e0f3c tip "b2"
35 note: created empty successor for 2:6e2aad5e0f3c tip "b2", its destination already has all its changes
36 36 saved backup bundle to $TESTTMP/.hg/strip-backup/6e2aad5e0f3c-7d7c8801-rebase.hg
37 37
38 38 $ hg tglog
39 39 @ 2 'b2'
40 40 |
41 41 o 1 'b1'
42 42 |
43 43 o 0 'a'
44 44
@@ -1,217 +1,217 b''
1 1 $ cat >> $HGRCPATH<<EOF
2 2 > [extensions]
3 3 > rebase=
4 4 > drawdag=$TESTDIR/drawdag.py
5 5 > EOF
6 6
7 7 $ hg init non-merge
8 8 $ cd non-merge
9 9 $ hg debugdrawdag<<'EOS'
10 10 > F
11 11 > |
12 12 > E
13 13 > |
14 14 > D
15 15 > |
16 16 > B C
17 17 > |/
18 18 > A
19 19 > EOS
20 20
21 21 $ for i in C D E F; do
22 22 > hg bookmark -r $i -i BOOK-$i
23 23 > done
24 24
25 25 $ hg debugdrawdag<<'EOS'
26 26 > E
27 27 > |
28 28 > D
29 29 > |
30 30 > B
31 31 > EOS
32 32
33 33 $ hg log -G -T '{rev} {desc} {bookmarks}'
34 34 o 7 E
35 35 |
36 36 o 6 D
37 37 |
38 38 | o 5 F BOOK-F
39 39 | |
40 40 | o 4 E BOOK-E
41 41 | |
42 42 | o 3 D BOOK-D
43 43 | |
44 44 | o 2 C BOOK-C
45 45 | |
46 46 o | 1 B
47 47 |/
48 48 o 0 A
49 49
50 50 With --keep, bookmark should move
51 51
52 52 $ hg rebase -r 3+4 -d E --keep
53 rebasing 3:e7b3f00ed42e "D" (BOOK-D)
54 note: not rebasing 3:e7b3f00ed42e "D" (BOOK-D), its destination already has all its changes
55 rebasing 4:69a34c08022a "E" (BOOK-E)
56 note: not rebasing 4:69a34c08022a "E" (BOOK-E), its destination already has all its changes
53 rebasing 3:e7b3f00ed42e BOOK-D "D"
54 note: not rebasing 3:e7b3f00ed42e BOOK-D "D", its destination already has all its changes
55 rebasing 4:69a34c08022a BOOK-E "E"
56 note: not rebasing 4:69a34c08022a BOOK-E "E", its destination already has all its changes
57 57 $ hg log -G -T '{rev} {desc} {bookmarks}'
58 58 o 7 E BOOK-D BOOK-E
59 59 |
60 60 o 6 D
61 61 |
62 62 | o 5 F BOOK-F
63 63 | |
64 64 | o 4 E
65 65 | |
66 66 | o 3 D
67 67 | |
68 68 | o 2 C BOOK-C
69 69 | |
70 70 o | 1 B
71 71 |/
72 72 o 0 A
73 73
74 74 Move D and E back for the next test
75 75
76 76 $ hg bookmark BOOK-D -fqir 3
77 77 $ hg bookmark BOOK-E -fqir 4
78 78
79 79 Bookmark is usually an indication of a head. For changes that are introduced by
80 80 an ancestor of bookmark B, after moving B to B-NEW, the changes are ideally
81 81 still introduced by an ancestor of changeset on B-NEW. In the below case,
82 82 "BOOK-D", and "BOOK-E" include changes introduced by "C".
83 83
84 84 $ hg rebase -s 2 -d E
85 rebasing 2:dc0947a82db8 "C" (BOOK-C C)
86 rebasing 3:e7b3f00ed42e "D" (BOOK-D)
87 note: not rebasing 3:e7b3f00ed42e "D" (BOOK-D), its destination already has all its changes
88 rebasing 4:69a34c08022a "E" (BOOK-E)
89 note: not rebasing 4:69a34c08022a "E" (BOOK-E), its destination already has all its changes
90 rebasing 5:6b2aeab91270 "F" (BOOK-F F)
85 rebasing 2:dc0947a82db8 C BOOK-C "C"
86 rebasing 3:e7b3f00ed42e BOOK-D "D"
87 note: not rebasing 3:e7b3f00ed42e BOOK-D "D", its destination already has all its changes
88 rebasing 4:69a34c08022a BOOK-E "E"
89 note: not rebasing 4:69a34c08022a BOOK-E "E", its destination already has all its changes
90 rebasing 5:6b2aeab91270 F BOOK-F "F"
91 91 saved backup bundle to $TESTTMP/non-merge/.hg/strip-backup/dc0947a82db8-52bb4973-rebase.hg
92 92 $ hg log -G -T '{rev} {desc} {bookmarks}'
93 93 o 5 F BOOK-F
94 94 |
95 95 o 4 C BOOK-C BOOK-D BOOK-E
96 96 |
97 97 o 3 E
98 98 |
99 99 o 2 D
100 100 |
101 101 o 1 B
102 102 |
103 103 o 0 A
104 104
105 105 Merge and its ancestors all become empty
106 106
107 107 $ hg init $TESTTMP/merge1
108 108 $ cd $TESTTMP/merge1
109 109
110 110 $ hg debugdrawdag<<'EOS'
111 111 > E
112 112 > /|
113 113 > B C D
114 114 > \|/
115 115 > A
116 116 > EOS
117 117
118 118 $ for i in C D E; do
119 119 > hg bookmark -r $i -i BOOK-$i
120 120 > done
121 121
122 122 $ hg debugdrawdag<<'EOS'
123 123 > H
124 124 > |
125 125 > D
126 126 > |
127 127 > C
128 128 > |
129 129 > B
130 130 > EOS
131 131
132 132 Previously, there was a bug where the empty commit check compared the parent
133 133 branch name with the wdir branch name instead of the actual branch name (which
134 134 should stay unchanged if --keepbranches is passed), and erroneously assumed
135 135 that an otherwise empty changeset should be created because of the incorrectly
136 136 assumed branch name change.
137 137
138 138 $ hg update H -q
139 139 $ hg branch foo -q
140 140
141 141 $ hg rebase -r '(A::)-(B::)-A' -d H --keepbranches
142 rebasing 2:dc0947a82db8 "C" (BOOK-C)
143 note: not rebasing 2:dc0947a82db8 "C" (BOOK-C), its destination already has all its changes
144 rebasing 3:b18e25de2cf5 "D" (BOOK-D)
145 note: not rebasing 3:b18e25de2cf5 "D" (BOOK-D), its destination already has all its changes
146 rebasing 4:86a1f6686812 "E" (BOOK-E E)
147 note: not rebasing 4:86a1f6686812 "E" (BOOK-E E), its destination already has all its changes
142 rebasing 2:dc0947a82db8 BOOK-C "C"
143 note: not rebasing 2:dc0947a82db8 BOOK-C "C", its destination already has all its changes
144 rebasing 3:b18e25de2cf5 BOOK-D "D"
145 note: not rebasing 3:b18e25de2cf5 BOOK-D "D", its destination already has all its changes
146 rebasing 4:86a1f6686812 E BOOK-E "E"
147 note: not rebasing 4:86a1f6686812 E BOOK-E "E", its destination already has all its changes
148 148 saved backup bundle to $TESTTMP/merge1/.hg/strip-backup/b18e25de2cf5-1fd0a4ba-rebase.hg
149 149 $ hg update null -q
150 150
151 151 $ hg log -G -T '{rev} {desc} {bookmarks}'
152 152 o 4 H BOOK-C BOOK-D BOOK-E
153 153 |
154 154 o 3 D
155 155 |
156 156 o 2 C
157 157 |
158 158 o 1 B
159 159 |
160 160 o 0 A
161 161
162 162 Part of ancestors of a merge become empty
163 163
164 164 $ hg init $TESTTMP/merge2
165 165 $ cd $TESTTMP/merge2
166 166
167 167 $ hg debugdrawdag<<'EOS'
168 168 > G
169 169 > /|
170 170 > E F
171 171 > | |
172 172 > B C D
173 173 > \|/
174 174 > A
175 175 > EOS
176 176
177 177 $ for i in C D E F G; do
178 178 > hg bookmark -r $i -i BOOK-$i
179 179 > done
180 180
181 181 $ hg debugdrawdag<<'EOS'
182 182 > H
183 183 > |
184 184 > F
185 185 > |
186 186 > C
187 187 > |
188 188 > B
189 189 > EOS
190 190
191 191 $ hg rebase -r '(A::)-(B::)-A' -d H
192 rebasing 2:dc0947a82db8 "C" (BOOK-C)
193 note: not rebasing 2:dc0947a82db8 "C" (BOOK-C), its destination already has all its changes
194 rebasing 3:b18e25de2cf5 "D" (BOOK-D D)
195 rebasing 4:03ca77807e91 "E" (BOOK-E E)
196 rebasing 5:ad6717a6a58e "F" (BOOK-F)
197 note: not rebasing 5:ad6717a6a58e "F" (BOOK-F), its destination already has all its changes
198 rebasing 6:c58e8bdac1f4 "G" (BOOK-G G)
192 rebasing 2:dc0947a82db8 BOOK-C "C"
193 note: not rebasing 2:dc0947a82db8 BOOK-C "C", its destination already has all its changes
194 rebasing 3:b18e25de2cf5 D BOOK-D "D"
195 rebasing 4:03ca77807e91 E BOOK-E "E"
196 rebasing 5:ad6717a6a58e BOOK-F "F"
197 note: not rebasing 5:ad6717a6a58e BOOK-F "F", its destination already has all its changes
198 rebasing 6:c58e8bdac1f4 G BOOK-G "G"
199 199 saved backup bundle to $TESTTMP/merge2/.hg/strip-backup/b18e25de2cf5-2d487005-rebase.hg
200 200
201 201 $ hg log -G -T '{rev} {desc} {bookmarks}'
202 202 o 7 G BOOK-G
203 203 |\
204 204 | o 6 E BOOK-E
205 205 | |
206 206 o | 5 D BOOK-D BOOK-F
207 207 |/
208 208 o 4 H BOOK-C
209 209 |
210 210 o 3 F
211 211 |
212 212 o 2 C
213 213 |
214 214 o 1 B
215 215 |
216 216 o 0 A
217 217
@@ -1,974 +1,974 b''
1 1 #require symlink execbit
2 2 $ cat << EOF >> $HGRCPATH
3 3 > [phases]
4 4 > publish=False
5 5 > [extensions]
6 6 > amend=
7 7 > rebase=
8 8 > debugdrawdag=$TESTDIR/drawdag.py
9 9 > strip=
10 10 > [rebase]
11 11 > experimental.inmemory=1
12 12 > [diff]
13 13 > git=1
14 14 > [alias]
15 15 > tglog = log -G --template "{rev}: {node|short} '{desc}'\n"
16 16 > EOF
17 17
18 18 Rebase a simple DAG:
19 19 $ hg init repo1
20 20 $ cd repo1
21 21 $ hg debugdrawdag <<'EOS'
22 22 > c b
23 23 > |/
24 24 > d
25 25 > |
26 26 > a
27 27 > EOS
28 28 $ hg up -C a
29 29 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
30 30 $ hg tglog
31 31 o 3: 814f6bd05178 'c'
32 32 |
33 33 | o 2: db0e82a16a62 'b'
34 34 |/
35 35 o 1: 02952614a83d 'd'
36 36 |
37 37 @ 0: b173517d0057 'a'
38 38
39 39 $ hg cat -r 3 c
40 40 c (no-eol)
41 41 $ hg cat -r 2 b
42 42 b (no-eol)
43 43 $ hg rebase --debug -r b -d c | grep rebasing
44 44 rebasing in memory
45 rebasing 2:db0e82a16a62 "b" (b)
45 rebasing 2:db0e82a16a62 b "b"
46 46 $ hg tglog
47 47 o 3: ca58782ad1e4 'b'
48 48 |
49 49 o 2: 814f6bd05178 'c'
50 50 |
51 51 o 1: 02952614a83d 'd'
52 52 |
53 53 @ 0: b173517d0057 'a'
54 54
55 55 $ hg cat -r 3 b
56 56 b (no-eol)
57 57 $ hg cat -r 2 c
58 58 c (no-eol)
59 59 $ cd ..
60 60
61 61 Case 2:
62 62 $ hg init repo2
63 63 $ cd repo2
64 64 $ hg debugdrawdag <<'EOS'
65 65 > c b
66 66 > |/
67 67 > d
68 68 > |
69 69 > a
70 70 > EOS
71 71
72 72 Add a symlink and executable file:
73 73 $ hg up -C c
74 74 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
75 75 $ ln -s somefile e
76 76 $ echo f > f
77 77 $ chmod +x f
78 78 $ hg add e f
79 79 $ hg amend -q
80 80 $ hg up -Cq a
81 81
82 82 Write files to the working copy, and ensure they're still there after the rebase
83 83 $ echo "abc" > a
84 84 $ ln -s def b
85 85 $ echo "ghi" > c
86 86 $ echo "jkl" > d
87 87 $ echo "mno" > e
88 88 $ hg tglog
89 89 o 3: f56b71190a8f 'c'
90 90 |
91 91 | o 2: db0e82a16a62 'b'
92 92 |/
93 93 o 1: 02952614a83d 'd'
94 94 |
95 95 @ 0: b173517d0057 'a'
96 96
97 97 $ hg cat -r 3 c
98 98 c (no-eol)
99 99 $ hg cat -r 2 b
100 100 b (no-eol)
101 101 $ hg cat -r 3 e
102 102 somefile (no-eol)
103 103 $ hg rebase --debug -s b -d a | grep rebasing
104 104 rebasing in memory
105 rebasing 2:db0e82a16a62 "b" (b)
105 rebasing 2:db0e82a16a62 b "b"
106 106 $ hg tglog
107 107 o 3: fc055c3b4d33 'b'
108 108 |
109 109 | o 2: f56b71190a8f 'c'
110 110 | |
111 111 | o 1: 02952614a83d 'd'
112 112 |/
113 113 @ 0: b173517d0057 'a'
114 114
115 115 $ hg cat -r 2 c
116 116 c (no-eol)
117 117 $ hg cat -r 3 b
118 118 b (no-eol)
119 119 $ hg rebase --debug -s 1 -d 3 | grep rebasing
120 120 rebasing in memory
121 rebasing 1:02952614a83d "d" (d)
121 rebasing 1:02952614a83d d "d"
122 122 rebasing 2:f56b71190a8f "c"
123 123 $ hg tglog
124 124 o 3: 753feb6fd12a 'c'
125 125 |
126 126 o 2: 09c044d2cb43 'd'
127 127 |
128 128 o 1: fc055c3b4d33 'b'
129 129 |
130 130 @ 0: b173517d0057 'a'
131 131
132 132 Ensure working copy files are still there:
133 133 $ cat a
134 134 abc
135 135 $ readlink.py b
136 136 b -> def
137 137 $ cat e
138 138 mno
139 139
140 140 Ensure symlink and executable files were rebased properly:
141 141 $ hg up -Cq 3
142 142 $ readlink.py e
143 143 e -> somefile
144 144 $ ls -l f | cut -c -10
145 145 -rwxr-xr-x
146 146
147 147 Rebase the working copy parent
148 148 $ hg up -C 3
149 149 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
150 150 $ hg rebase -r 3 -d 0 --debug | grep rebasing
151 151 rebasing in memory
152 rebasing 3:753feb6fd12a "c" (tip)
152 rebasing 3:753feb6fd12a tip "c"
153 153 $ hg tglog
154 154 @ 3: 844a7de3e617 'c'
155 155 |
156 156 | o 2: 09c044d2cb43 'd'
157 157 | |
158 158 | o 1: fc055c3b4d33 'b'
159 159 |/
160 160 o 0: b173517d0057 'a'
161 161
162 162
163 163 Test reporting of path conflicts
164 164
165 165 $ hg rm a
166 166 $ mkdir a
167 167 $ touch a/a
168 168 $ hg ci -Am "a/a"
169 169 adding a/a
170 170 $ hg tglog
171 171 @ 4: daf7dfc139cb 'a/a'
172 172 |
173 173 o 3: 844a7de3e617 'c'
174 174 |
175 175 | o 2: 09c044d2cb43 'd'
176 176 | |
177 177 | o 1: fc055c3b4d33 'b'
178 178 |/
179 179 o 0: b173517d0057 'a'
180 180
181 181 $ hg rebase -r . -d 2
182 rebasing 4:daf7dfc139cb "a/a" (tip)
182 rebasing 4:daf7dfc139cb tip "a/a"
183 183 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/daf7dfc139cb-fdbfcf4f-rebase.hg
184 184
185 185 $ hg tglog
186 186 @ 4: c6ad37a4f250 'a/a'
187 187 |
188 188 | o 3: 844a7de3e617 'c'
189 189 | |
190 190 o | 2: 09c044d2cb43 'd'
191 191 | |
192 192 o | 1: fc055c3b4d33 'b'
193 193 |/
194 194 o 0: b173517d0057 'a'
195 195
196 196 $ echo foo > foo
197 197 $ hg ci -Aqm "added foo"
198 198 $ hg up '.^'
199 199 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
200 200 $ echo bar > bar
201 201 $ hg ci -Aqm "added bar"
202 202 $ hg rm a/a
203 203 $ echo a > a
204 204 $ hg ci -Aqm "added a back!"
205 205 $ hg tglog
206 206 @ 7: 855e9797387e 'added a back!'
207 207 |
208 208 o 6: d14530e5e3e6 'added bar'
209 209 |
210 210 | o 5: 9b94b9373deb 'added foo'
211 211 |/
212 212 o 4: c6ad37a4f250 'a/a'
213 213 |
214 214 | o 3: 844a7de3e617 'c'
215 215 | |
216 216 o | 2: 09c044d2cb43 'd'
217 217 | |
218 218 o | 1: fc055c3b4d33 'b'
219 219 |/
220 220 o 0: b173517d0057 'a'
221 221
222 222 $ hg rebase -r . -d 5
223 rebasing 7:855e9797387e "added a back!" (tip)
223 rebasing 7:855e9797387e tip "added a back!"
224 224 saved backup bundle to $TESTTMP/repo2/.hg/strip-backup/855e9797387e-81ee4c5d-rebase.hg
225 225
226 226 $ hg tglog
227 227 @ 7: bb3f02be2688 'added a back!'
228 228 |
229 229 | o 6: d14530e5e3e6 'added bar'
230 230 | |
231 231 o | 5: 9b94b9373deb 'added foo'
232 232 |/
233 233 o 4: c6ad37a4f250 'a/a'
234 234 |
235 235 | o 3: 844a7de3e617 'c'
236 236 | |
237 237 o | 2: 09c044d2cb43 'd'
238 238 | |
239 239 o | 1: fc055c3b4d33 'b'
240 240 |/
241 241 o 0: b173517d0057 'a'
242 242
243 243 $ mkdir -p c/subdir
244 244 $ echo c > c/subdir/file.txt
245 245 $ hg add c/subdir/file.txt
246 246 $ hg ci -m 'c/subdir/file.txt'
247 247 $ hg rebase -r . -d 3 -n
248 248 starting dry-run rebase; repository will not be changed
249 rebasing 8:e147e6e3c490 "c/subdir/file.txt" (tip)
249 rebasing 8:e147e6e3c490 tip "c/subdir/file.txt"
250 250 abort: error: 'c/subdir/file.txt' conflicts with file 'c' in 3.
251 251 [255]
252 252 FIXME: shouldn't need this, but when we hit path conflicts in dryrun mode, we
253 253 don't clean up rebasestate.
254 254 $ hg rebase --abort
255 255 rebase aborted
256 256 $ hg rebase -r 3 -d . -n
257 257 starting dry-run rebase; repository will not be changed
258 258 rebasing 3:844a7de3e617 "c"
259 259 abort: error: file 'c' cannot be written because 'c/' is a directory in e147e6e3c490 (containing 1 entries: c/subdir/file.txt)
260 260 [255]
261 261
262 262 $ cd ..
263 263
264 264 Test path auditing (issue5818)
265 265
266 266 $ mkdir lib_
267 267 $ ln -s lib_ lib
268 268 $ hg init repo
269 269 $ cd repo
270 270 $ mkdir -p ".$TESTTMP/lib"
271 271 $ touch ".$TESTTMP/lib/a"
272 272 $ hg add ".$TESTTMP/lib/a"
273 273 $ hg ci -m 'a'
274 274
275 275 $ touch ".$TESTTMP/lib/b"
276 276 $ hg add ".$TESTTMP/lib/b"
277 277 $ hg ci -m 'b'
278 278
279 279 $ hg up -q '.^'
280 280 $ touch ".$TESTTMP/lib/c"
281 281 $ hg add ".$TESTTMP/lib/c"
282 282 $ hg ci -m 'c'
283 283 created new head
284 284 $ hg rebase -s 1 -d .
285 285 rebasing 1:* "b" (glob)
286 286 saved backup bundle to $TESTTMP/repo/.hg/strip-backup/*-rebase.hg (glob)
287 287 $ cd ..
288 288
289 289 Test dry-run rebasing
290 290
291 291 $ hg init repo3
292 292 $ cd repo3
293 293 $ echo a>a
294 294 $ hg ci -Aqma
295 295 $ echo b>b
296 296 $ hg ci -Aqmb
297 297 $ echo c>c
298 298 $ hg ci -Aqmc
299 299 $ echo d>d
300 300 $ hg ci -Aqmd
301 301 $ echo e>e
302 302 $ hg ci -Aqme
303 303
304 304 $ hg up 1 -q
305 305 $ echo f>f
306 306 $ hg ci -Amf
307 307 adding f
308 308 created new head
309 309 $ echo g>g
310 310 $ hg ci -Aqmg
311 311 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
312 312 @ 6:baf10c5166d4 test
313 313 | g
314 314 |
315 315 o 5:6343ca3eff20 test
316 316 | f
317 317 |
318 318 | o 4:e860deea161a test
319 319 | | e
320 320 | |
321 321 | o 3:055a42cdd887 test
322 322 | | d
323 323 | |
324 324 | o 2:177f92b77385 test
325 325 |/ c
326 326 |
327 327 o 1:d2ae7f538514 test
328 328 | b
329 329 |
330 330 o 0:cb9a9f314b8b test
331 331 a
332 332
333 333 Make sure it throws error while passing --continue or --abort with --dry-run
334 334 $ hg rebase -s 2 -d 6 -n --continue
335 335 abort: cannot specify both --continue and --dry-run
336 336 [255]
337 337 $ hg rebase -s 2 -d 6 -n --abort
338 338 abort: cannot specify both --abort and --dry-run
339 339 [255]
340 340
341 341 Check dryrun gives correct results when there is no conflict in rebasing
342 342 $ hg rebase -s 2 -d 6 -n
343 343 starting dry-run rebase; repository will not be changed
344 344 rebasing 2:177f92b77385 "c"
345 345 rebasing 3:055a42cdd887 "d"
346 346 rebasing 4:e860deea161a "e"
347 347 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
348 348
349 349 $ hg diff
350 350 $ hg status
351 351
352 352 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
353 353 @ 6:baf10c5166d4 test
354 354 | g
355 355 |
356 356 o 5:6343ca3eff20 test
357 357 | f
358 358 |
359 359 | o 4:e860deea161a test
360 360 | | e
361 361 | |
362 362 | o 3:055a42cdd887 test
363 363 | | d
364 364 | |
365 365 | o 2:177f92b77385 test
366 366 |/ c
367 367 |
368 368 o 1:d2ae7f538514 test
369 369 | b
370 370 |
371 371 o 0:cb9a9f314b8b test
372 372 a
373 373
374 374 Check dryrun working with --collapse when there is no conflict
375 375 $ hg rebase -s 2 -d 6 -n --collapse
376 376 starting dry-run rebase; repository will not be changed
377 377 rebasing 2:177f92b77385 "c"
378 378 rebasing 3:055a42cdd887 "d"
379 379 rebasing 4:e860deea161a "e"
380 380 dry-run rebase completed successfully; run without -n/--dry-run to perform this rebase
381 381
382 382 Check dryrun gives correct results when there is conflict in rebasing
383 383 Make a conflict:
384 384 $ hg up 6 -q
385 385 $ echo conflict>e
386 386 $ hg ci -Aqm "conflict with e"
387 387 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
388 388 @ 7:d2c195b28050 test
389 389 | conflict with e
390 390 |
391 391 o 6:baf10c5166d4 test
392 392 | g
393 393 |
394 394 o 5:6343ca3eff20 test
395 395 | f
396 396 |
397 397 | o 4:e860deea161a test
398 398 | | e
399 399 | |
400 400 | o 3:055a42cdd887 test
401 401 | | d
402 402 | |
403 403 | o 2:177f92b77385 test
404 404 |/ c
405 405 |
406 406 o 1:d2ae7f538514 test
407 407 | b
408 408 |
409 409 o 0:cb9a9f314b8b test
410 410 a
411 411
412 412 $ hg rebase -s 2 -d 7 -n
413 413 starting dry-run rebase; repository will not be changed
414 414 rebasing 2:177f92b77385 "c"
415 415 rebasing 3:055a42cdd887 "d"
416 416 rebasing 4:e860deea161a "e"
417 417 merging e
418 418 hit a merge conflict
419 419 [1]
420 420 $ hg diff
421 421 $ hg status
422 422 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
423 423 @ 7:d2c195b28050 test
424 424 | conflict with e
425 425 |
426 426 o 6:baf10c5166d4 test
427 427 | g
428 428 |
429 429 o 5:6343ca3eff20 test
430 430 | f
431 431 |
432 432 | o 4:e860deea161a test
433 433 | | e
434 434 | |
435 435 | o 3:055a42cdd887 test
436 436 | | d
437 437 | |
438 438 | o 2:177f92b77385 test
439 439 |/ c
440 440 |
441 441 o 1:d2ae7f538514 test
442 442 | b
443 443 |
444 444 o 0:cb9a9f314b8b test
445 445 a
446 446
447 447 Check dryrun working with --collapse when there is conflicts
448 448 $ hg rebase -s 2 -d 7 -n --collapse
449 449 starting dry-run rebase; repository will not be changed
450 450 rebasing 2:177f92b77385 "c"
451 451 rebasing 3:055a42cdd887 "d"
452 452 rebasing 4:e860deea161a "e"
453 453 merging e
454 454 hit a merge conflict
455 455 [1]
456 456
457 457 In-memory rebase that fails due to merge conflicts
458 458
459 459 $ hg rebase -s 2 -d 7
460 460 rebasing 2:177f92b77385 "c"
461 461 rebasing 3:055a42cdd887 "d"
462 462 rebasing 4:e860deea161a "e"
463 463 merging e
464 464 hit merge conflicts; rebasing that commit again in the working copy
465 465 merging e
466 466 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
467 467 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
468 468 [1]
469 469 $ hg rebase --abort
470 470 saved backup bundle to $TESTTMP/repo3/.hg/strip-backup/c1e524d4287c-f91f82e1-backup.hg
471 471 rebase aborted
472 472
473 473 Retrying without in-memory merge won't lose working copy changes
474 474 $ cd ..
475 475 $ hg clone repo3 repo3-dirty -q
476 476 $ cd repo3-dirty
477 477 $ echo dirty > a
478 478 $ hg rebase -s 2 -d 7
479 479 rebasing 2:177f92b77385 "c"
480 480 rebasing 3:055a42cdd887 "d"
481 481 rebasing 4:e860deea161a "e"
482 482 merging e
483 483 hit merge conflicts; rebasing that commit again in the working copy
484 484 transaction abort!
485 485 rollback completed
486 486 abort: uncommitted changes
487 487 [255]
488 488 $ cat a
489 489 dirty
490 490
491 491 Retrying without in-memory merge won't lose merge state
492 492 $ cd ..
493 493 $ hg clone repo3 repo3-merge-state -q
494 494 $ cd repo3-merge-state
495 495 $ hg merge 4
496 496 merging e
497 497 warning: conflicts while merging e! (edit, then use 'hg resolve --mark')
498 498 2 files updated, 0 files merged, 0 files removed, 1 files unresolved
499 499 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
500 500 [1]
501 501 $ hg resolve -l
502 502 U e
503 503 $ hg rebase -s 2 -d 7
504 504 abort: outstanding uncommitted merge
505 505 (use 'hg commit' or 'hg merge --abort')
506 506 [255]
507 507 $ hg resolve -l
508 508 U e
509 509
510 510 ==========================
511 511 Test for --confirm option|
512 512 ==========================
513 513 $ cd ..
514 514 $ hg clone repo3 repo4 -q
515 515 $ cd repo4
516 516 $ hg strip 7 -q
517 517 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
518 518 @ 6:baf10c5166d4 test
519 519 | g
520 520 |
521 521 o 5:6343ca3eff20 test
522 522 | f
523 523 |
524 524 | o 4:e860deea161a test
525 525 | | e
526 526 | |
527 527 | o 3:055a42cdd887 test
528 528 | | d
529 529 | |
530 530 | o 2:177f92b77385 test
531 531 |/ c
532 532 |
533 533 o 1:d2ae7f538514 test
534 534 | b
535 535 |
536 536 o 0:cb9a9f314b8b test
537 537 a
538 538
539 539 Check it gives error when both --dryrun and --confirm is used:
540 540 $ hg rebase -s 2 -d . --confirm --dry-run
541 541 abort: cannot specify both --confirm and --dry-run
542 542 [255]
543 543 $ hg rebase -s 2 -d . --confirm --abort
544 544 abort: cannot specify both --abort and --confirm
545 545 [255]
546 546 $ hg rebase -s 2 -d . --confirm --continue
547 547 abort: cannot specify both --continue and --confirm
548 548 [255]
549 549
550 550 Test --confirm option when there are no conflicts:
551 551 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
552 552 > n
553 553 > EOF
554 554 starting in-memory rebase
555 555 rebasing 2:177f92b77385 "c"
556 556 rebasing 3:055a42cdd887 "d"
557 557 rebasing 4:e860deea161a "e"
558 558 rebase completed successfully
559 559 apply changes (yn)? n
560 560 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
561 561 @ 6:baf10c5166d4 test
562 562 | g
563 563 |
564 564 o 5:6343ca3eff20 test
565 565 | f
566 566 |
567 567 | o 4:e860deea161a test
568 568 | | e
569 569 | |
570 570 | o 3:055a42cdd887 test
571 571 | | d
572 572 | |
573 573 | o 2:177f92b77385 test
574 574 |/ c
575 575 |
576 576 o 1:d2ae7f538514 test
577 577 | b
578 578 |
579 579 o 0:cb9a9f314b8b test
580 580 a
581 581
582 582 $ hg rebase -s 2 -d . --keep --config ui.interactive=True --confirm << EOF
583 583 > y
584 584 > EOF
585 585 starting in-memory rebase
586 586 rebasing 2:177f92b77385 "c"
587 587 rebasing 3:055a42cdd887 "d"
588 588 rebasing 4:e860deea161a "e"
589 589 rebase completed successfully
590 590 apply changes (yn)? y
591 591 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
592 592 o 9:9fd28f55f6dc test
593 593 | e
594 594 |
595 595 o 8:12cbf031f469 test
596 596 | d
597 597 |
598 598 o 7:c83b1da5b1ae test
599 599 | c
600 600 |
601 601 @ 6:baf10c5166d4 test
602 602 | g
603 603 |
604 604 o 5:6343ca3eff20 test
605 605 | f
606 606 |
607 607 | o 4:e860deea161a test
608 608 | | e
609 609 | |
610 610 | o 3:055a42cdd887 test
611 611 | | d
612 612 | |
613 613 | o 2:177f92b77385 test
614 614 |/ c
615 615 |
616 616 o 1:d2ae7f538514 test
617 617 | b
618 618 |
619 619 o 0:cb9a9f314b8b test
620 620 a
621 621
622 622 Test --confirm option when there is a conflict
623 623 $ hg up tip -q
624 624 $ echo ee>e
625 625 $ hg ci --amend -m "conflict with e" -q
626 626 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
627 627 @ 9:906d72f66a59 test
628 628 | conflict with e
629 629 |
630 630 o 8:12cbf031f469 test
631 631 | d
632 632 |
633 633 o 7:c83b1da5b1ae test
634 634 | c
635 635 |
636 636 o 6:baf10c5166d4 test
637 637 | g
638 638 |
639 639 o 5:6343ca3eff20 test
640 640 | f
641 641 |
642 642 | o 4:e860deea161a test
643 643 | | e
644 644 | |
645 645 | o 3:055a42cdd887 test
646 646 | | d
647 647 | |
648 648 | o 2:177f92b77385 test
649 649 |/ c
650 650 |
651 651 o 1:d2ae7f538514 test
652 652 | b
653 653 |
654 654 o 0:cb9a9f314b8b test
655 655 a
656 656
657 657 $ hg rebase -s 4 -d . --keep --confirm
658 658 starting in-memory rebase
659 659 rebasing 4:e860deea161a "e"
660 660 merging e
661 661 hit a merge conflict
662 662 [1]
663 663 $ hg log -G --template "{rev}:{short(node)} {person(author)}\n{firstline(desc)} {topic}\n\n"
664 664 @ 9:906d72f66a59 test
665 665 | conflict with e
666 666 |
667 667 o 8:12cbf031f469 test
668 668 | d
669 669 |
670 670 o 7:c83b1da5b1ae test
671 671 | c
672 672 |
673 673 o 6:baf10c5166d4 test
674 674 | g
675 675 |
676 676 o 5:6343ca3eff20 test
677 677 | f
678 678 |
679 679 | o 4:e860deea161a test
680 680 | | e
681 681 | |
682 682 | o 3:055a42cdd887 test
683 683 | | d
684 684 | |
685 685 | o 2:177f92b77385 test
686 686 |/ c
687 687 |
688 688 o 1:d2ae7f538514 test
689 689 | b
690 690 |
691 691 o 0:cb9a9f314b8b test
692 692 a
693 693
694 694 Test a metadata-only in-memory merge
695 695 $ cd $TESTTMP
696 696 $ hg init no_exception
697 697 $ cd no_exception
698 698 # Produce the following graph:
699 699 # o 'add +x to foo.txt'
700 700 # | o r1 (adds bar.txt, just for something to rebase to)
701 701 # |/
702 702 # o r0 (adds foo.txt, no +x)
703 703 $ echo hi > foo.txt
704 704 $ hg ci -qAm r0
705 705 $ echo hi > bar.txt
706 706 $ hg ci -qAm r1
707 707 $ hg co -qr ".^"
708 708 $ chmod +x foo.txt
709 709 $ hg ci -qAm 'add +x to foo.txt'
710 710 issue5960: this was raising an AttributeError exception
711 711 $ hg rebase -r . -d 1
712 rebasing 2:539b93e77479 "add +x to foo.txt" (tip)
712 rebasing 2:539b93e77479 tip "add +x to foo.txt"
713 713 saved backup bundle to $TESTTMP/no_exception/.hg/strip-backup/*.hg (glob)
714 714 $ hg diff -c tip
715 715 diff --git a/foo.txt b/foo.txt
716 716 old mode 100644
717 717 new mode 100755
718 718
719 719 Test rebasing a commit with copy information, but no content changes
720 720
721 721 $ cd ..
722 722 $ hg clone -q repo1 merge-and-rename
723 723 $ cd merge-and-rename
724 724 $ cat << EOF >> .hg/hgrc
725 725 > [experimental]
726 726 > evolution.createmarkers=True
727 727 > evolution.allowunstable=True
728 728 > EOF
729 729 $ hg co -q 1
730 730 $ hg mv d e
731 731 $ hg ci -qm 'rename d to e'
732 732 $ hg co -q 3
733 733 $ hg merge -q 4
734 734 $ hg ci -m 'merge'
735 735 $ hg co -q 2
736 736 $ mv d e
737 737 $ hg addremove -qs 0
738 738 $ hg ci -qm 'untracked rename of d to e'
739 739 $ hg debugobsolete -q `hg log -T '{node}' -r 4` `hg log -T '{node}' -r .`
740 740 1 new orphan changesets
741 741 $ hg tglog
742 742 @ 6: 676538af172d 'untracked rename of d to e'
743 743 |
744 744 | * 5: 574d92ad16fc 'merge'
745 745 | |\
746 746 | | x 4: 2c8b5dad7956 'rename d to e'
747 747 | | |
748 748 | o | 3: ca58782ad1e4 'b'
749 749 |/ /
750 750 o / 2: 814f6bd05178 'c'
751 751 |/
752 752 o 1: 02952614a83d 'd'
753 753 |
754 754 o 0: b173517d0057 'a'
755 755
756 756 $ hg rebase -b 5 -d tip
757 757 rebasing 3:ca58782ad1e4 "b"
758 758 rebasing 5:574d92ad16fc "merge"
759 759 note: not rebasing 5:574d92ad16fc "merge", its destination already has all its changes
760 760
761 761 $ cd ..
762 762
763 763 Test rebasing a commit with copy information
764 764
765 765 $ hg init rebase-rename
766 766 $ cd rebase-rename
767 767 $ echo a > a
768 768 $ hg ci -Aqm 'add a'
769 769 $ echo a2 > a
770 770 $ hg ci -m 'modify a'
771 771 $ hg co -q 0
772 772 $ hg mv a b
773 773 $ hg ci -qm 'rename a to b'
774 774 $ hg rebase -d 1
775 rebasing 2:b977edf6f839 "rename a to b" (tip)
775 rebasing 2:b977edf6f839 tip "rename a to b"
776 776 merging a and b to b
777 777 saved backup bundle to $TESTTMP/rebase-rename/.hg/strip-backup/b977edf6f839-0864f570-rebase.hg
778 778 $ hg st --copies --change .
779 779 A b
780 780 a
781 781 R a
782 782 $ cd ..
783 783
784 784 Test rebasing a commit with copy information, where the target is empty
785 785
786 786 $ hg init rebase-rename-empty
787 787 $ cd rebase-rename-empty
788 788 $ echo a > a
789 789 $ hg ci -Aqm 'add a'
790 790 $ cat > a
791 791 $ hg ci -m 'make a empty'
792 792 $ hg co -q 0
793 793 $ hg mv a b
794 794 $ hg ci -qm 'rename a to b'
795 795 $ hg rebase -d 1
796 rebasing 2:b977edf6f839 "rename a to b" (tip)
796 rebasing 2:b977edf6f839 tip "rename a to b"
797 797 merging a and b to b
798 798 saved backup bundle to $TESTTMP/rebase-rename-empty/.hg/strip-backup/b977edf6f839-0864f570-rebase.hg
799 799 $ hg st --copies --change .
800 800 A b
801 801 a
802 802 R a
803 803 $ cd ..
804 804 Rebase across a copy with --collapse
805 805
806 806 $ hg init rebase-rename-collapse
807 807 $ cd rebase-rename-collapse
808 808 $ echo a > a
809 809 $ hg ci -Aqm 'add a'
810 810 $ hg mv a b
811 811 $ hg ci -m 'rename a to b'
812 812 $ hg co -q 0
813 813 $ echo a2 > a
814 814 $ hg ci -qm 'modify a'
815 815 $ hg rebase -r . -d 1 --collapse
816 rebasing 2:41c4ea50d4cf "modify a" (tip)
816 rebasing 2:41c4ea50d4cf tip "modify a"
817 817 merging b and a to b
818 818 saved backup bundle to $TESTTMP/rebase-rename-collapse/.hg/strip-backup/41c4ea50d4cf-b90b7994-rebase.hg
819 819 $ cd ..
820 820
821 821 Test rebasing when the file we are merging in destination is empty
822 822
823 823 $ hg init test
824 824 $ cd test
825 825 $ echo a > foo
826 826 $ hg ci -Aqm 'added a to foo'
827 827
828 828 $ rm foo
829 829 $ touch foo
830 830 $ hg di
831 831 diff --git a/foo b/foo
832 832 --- a/foo
833 833 +++ b/foo
834 834 @@ -1,1 +0,0 @@
835 835 -a
836 836
837 837 $ hg ci -m "make foo an empty file"
838 838
839 839 $ hg up '.^'
840 840 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
841 841 $ echo b > foo
842 842 $ hg di
843 843 diff --git a/foo b/foo
844 844 --- a/foo
845 845 +++ b/foo
846 846 @@ -1,1 +1,1 @@
847 847 -a
848 848 +b
849 849 $ hg ci -m "add b to foo"
850 850 created new head
851 851
852 852 $ hg rebase -r . -d 1 --config ui.merge=internal:merge3
853 rebasing 2:fb62b706688e "add b to foo" (tip)
853 rebasing 2:fb62b706688e tip "add b to foo"
854 854 merging foo
855 855 hit merge conflicts; rebasing that commit again in the working copy
856 856 merging foo
857 857 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
858 858 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
859 859 [1]
860 860
861 861 $ cd $TESTTMP
862 862
863 863 Test rebasing when we're in the middle of a rebase already
864 864 $ hg init test_issue6214
865 865 $ cd test_issue6214
866 866 $ echo r0 > r0
867 867 $ hg ci -qAm 'r0'
868 868 $ echo hi > foo
869 869 $ hg ci -qAm 'hi from foo'
870 870 $ hg co -q '.^'
871 871 $ echo bye > foo
872 872 $ hg ci -qAm 'bye from foo'
873 873 $ hg co -q '.^'
874 874 $ echo unrelated > some_other_file
875 875 $ hg ci -qAm 'some unrelated changes'
876 876 $ hg log -G -T'{rev}: {desc}\n{files%"{file}\n"}'
877 877 @ 3: some unrelated changes
878 878 | some_other_file
879 879 | o 2: bye from foo
880 880 |/ foo
881 881 | o 1: hi from foo
882 882 |/ foo
883 883 o 0: r0
884 884 r0
885 885 $ hg rebase -r 2 -d 1 -t:merge3
886 886 rebasing 2:b4d249fbf8dd "bye from foo"
887 887 merging foo
888 888 hit merge conflicts; rebasing that commit again in the working copy
889 889 merging foo
890 890 warning: conflicts while merging foo! (edit, then use 'hg resolve --mark')
891 891 unresolved conflicts (see 'hg resolve', then 'hg rebase --continue')
892 892 [1]
893 893 $ hg rebase -r 3 -d 1 -t:merge3
894 894 abort: rebase in progress
895 895 (use 'hg rebase --continue', 'hg rebase --abort', or 'hg rebase --stop')
896 896 [255]
897 897 $ hg resolve --list
898 898 U foo
899 899 $ hg resolve --all --re-merge -t:other
900 900 (no more unresolved files)
901 901 continue: hg rebase --continue
902 902 $ hg rebase --continue
903 903 rebasing 2:b4d249fbf8dd "bye from foo"
904 904 saved backup bundle to $TESTTMP/test_issue6214/.hg/strip-backup/b4d249fbf8dd-299ec25c-rebase.hg
905 905 $ hg log -G -T'{rev}: {desc}\n{files%"{file}\n"}'
906 906 o 3: bye from foo
907 907 | foo
908 908 | @ 2: some unrelated changes
909 909 | | some_other_file
910 910 o | 1: hi from foo
911 911 |/ foo
912 912 o 0: r0
913 913 r0
914 914
915 915 $ cd ..
916 916
917 917 Changesets that become empty should not be committed. Merges are not empty by
918 918 definition.
919 919
920 920 $ hg init keep_merge
921 921 $ cd keep_merge
922 922 $ echo base > base; hg add base; hg ci -m base
923 923 $ echo test > test; hg add test; hg ci -m a
924 924 $ hg up 0 -q
925 925 $ echo test > test; hg add test; hg ci -m b -q
926 926 $ hg up 0 -q
927 927 $ echo test > test; hg add test; hg ci -m c -q
928 928 $ hg up 1 -q
929 929 $ hg merge 2 -q
930 930 $ hg ci -m merge
931 931 $ hg up null -q
932 932 $ hg tglog
933 933 o 4: 59c8292117b1 'merge'
934 934 |\
935 935 | | o 3: 531f80391e4a 'c'
936 936 | | |
937 937 | o | 2: 0194f1db184a 'b'
938 938 | |/
939 939 o / 1: 6f252845ea45 'a'
940 940 |/
941 941 o 0: d20a80d4def3 'base'
942 942
943 943 $ hg rebase -s 2 -d 3
944 944 rebasing 2:0194f1db184a "b"
945 945 note: not rebasing 2:0194f1db184a "b", its destination already has all its changes
946 rebasing 4:59c8292117b1 "merge" (tip)
946 rebasing 4:59c8292117b1 tip "merge"
947 947 saved backup bundle to $TESTTMP/keep_merge/.hg/strip-backup/0194f1db184a-aee31d03-rebase.hg
948 948 $ hg tglog
949 949 o 3: 506e2454484b 'merge'
950 950 |\
951 951 | o 2: 531f80391e4a 'c'
952 952 | |
953 953 o | 1: 6f252845ea45 'a'
954 954 |/
955 955 o 0: d20a80d4def3 'base'
956 956
957 957
958 958 $ cd ..
959 959
960 960 Test (virtual) working directory without changes, created by merge conflict
961 961 resolution. There was a regression where the file was incorrectly detected as
962 962 changed although the file contents were the same as in the parent.
963 963
964 964 $ hg init nofilechanges
965 965 $ cd nofilechanges
966 966 $ echo a > a; hg add a; hg ci -m a
967 967 $ echo foo > test; hg add test; hg ci -m b
968 968 $ hg up 0 -q
969 969 $ echo bar > test; hg add test; hg ci -m c
970 970 created new head
971 971 $ hg rebase -d 2 -d 1 --tool :local
972 rebasing 2:ca2749322ee5 "c" (tip)
973 note: not rebasing 2:ca2749322ee5 "c" (tip), its destination already has all its changes
972 rebasing 2:ca2749322ee5 tip "c"
973 note: not rebasing 2:ca2749322ee5 tip "c", its destination already has all its changes
974 974 saved backup bundle to $TESTTMP/nofilechanges/.hg/strip-backup/ca2749322ee5-6dc7e94b-rebase.hg
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
1 NO CONTENT: modified file
The requested commit or file is too big and content was truncated. Show full diff
General Comments 0
You need to be logged in to leave comments. Login now