Mercurial > public > mercurial-scm > hg-stable
comparison mercurial/commands.py @ 40499:3c0d5016b2be
graft: introduce --base option for using custom base revision while merging
The graft command usually performs an internal merge of the current parent
revision with the graft revision, using p1 of the grafted revision as base for
the merge.
As a trivial extension of this, we introduce the --base option to allow for
using another base revision.
This can be used as a building block for grafting and collapsing multiple
changesets at once, or for grafting the resulting change from a merge as a
single simple change. (This is kind of similar to backout --parent ... only
different: this graft base must be an ancestor, but is usually *not* a parent.)
This is probably an advanced use case, and we do thus not show it in the
non-verbose help.
author | Mads Kiilerich <mads@kiilerich.com> |
---|---|
date | Sun, 14 Oct 2018 17:08:18 +0200 |
parents | 1feb4b2c8e40 |
children | db61a18148a4 |
comparison
equal
deleted
inserted
replaced
40498:808b762679cd | 40499:3c0d5016b2be |
---|---|
2247 return rejected and 1 or 0 | 2247 return rejected and 1 or 0 |
2248 | 2248 |
2249 @command( | 2249 @command( |
2250 'graft', | 2250 'graft', |
2251 [('r', 'rev', [], _('revisions to graft'), _('REV')), | 2251 [('r', 'rev', [], _('revisions to graft'), _('REV')), |
2252 ('', 'base', '', | |
2253 _('base revision when doing the graft merge (ADVANCED)'), _('REV')), | |
2252 ('c', 'continue', False, _('resume interrupted graft')), | 2254 ('c', 'continue', False, _('resume interrupted graft')), |
2253 ('', 'stop', False, _('stop interrupted graft')), | 2255 ('', 'stop', False, _('stop interrupted graft')), |
2254 ('', 'abort', False, _('abort interrupted graft')), | 2256 ('', 'abort', False, _('abort interrupted graft')), |
2255 ('e', 'edit', False, _('invoke editor on commit messages')), | 2257 ('e', 'edit', False, _('invoke editor on commit messages')), |
2256 ('', 'log', None, _('append graft info to log message')), | 2258 ('', 'log', None, _('append graft info to log message')), |
2292 | 2294 |
2293 The -c/--continue option reapplies all the earlier options. | 2295 The -c/--continue option reapplies all the earlier options. |
2294 | 2296 |
2295 .. container:: verbose | 2297 .. container:: verbose |
2296 | 2298 |
2299 The --base option exposes more of how graft internally uses merge with a | |
2300 custom base revision. --base can be used to specify another ancestor than | |
2301 the first and only parent. | |
2302 | |
2303 The command:: | |
2304 | |
2305 hg graft -r 345 --base 234 | |
2306 | |
2307 is thus pretty much the same as:: | |
2308 | |
2309 hg diff -r 234 -r 345 | hg import | |
2310 | |
2311 but using merge to resolve conflicts and track moved files. | |
2312 | |
2313 The result of a merge can thus be backported as a single commit by | |
2314 specifying one of the merge parents as base, and thus effectively | |
2315 grafting the changes from the other side. | |
2316 | |
2317 It is also possible to collapse multiple changesets and clean up history | |
2318 by specifying another ancestor as base, much like rebase --collapse | |
2319 --keep. | |
2320 | |
2321 The commit message can be tweaked after the fact using commit --amend . | |
2322 | |
2323 For using non-ancestors as the base to backout changes, see the backout | |
2324 command and the hidden --parent option. | |
2325 | |
2326 .. container:: verbose | |
2327 | |
2297 Examples: | 2328 Examples: |
2298 | 2329 |
2299 - copy a single change to the stable branch and edit its description:: | 2330 - copy a single change to the stable branch and edit its description:: |
2300 | 2331 |
2301 hg update stable | 2332 hg update stable |
2314 hg log --debug -r . | 2345 hg log --debug -r . |
2315 | 2346 |
2316 - show revisions sorted by date:: | 2347 - show revisions sorted by date:: |
2317 | 2348 |
2318 hg log -r "sort(all(), date)" | 2349 hg log -r "sort(all(), date)" |
2350 | |
2351 - backport the result of a merge as a single commit:: | |
2352 | |
2353 hg graft -r 123 --base 123^ | |
2354 | |
2355 - land a feature branch as one changeset:: | |
2356 | |
2357 hg up -cr default | |
2358 hg graft -r featureX --base "ancestor('featureX', 'default')" | |
2319 | 2359 |
2320 See :hg:`help revisions` for more about specifying revisions. | 2360 See :hg:`help revisions` for more about specifying revisions. |
2321 | 2361 |
2322 Returns 0 on successful completion. | 2362 Returns 0 on successful completion. |
2323 ''' | 2363 ''' |
2330 ui.warn(_('warning: inconsistent use of --rev might give unexpected ' | 2370 ui.warn(_('warning: inconsistent use of --rev might give unexpected ' |
2331 'revision ordering!\n')) | 2371 'revision ordering!\n')) |
2332 | 2372 |
2333 revs = list(revs) | 2373 revs = list(revs) |
2334 revs.extend(opts.get('rev')) | 2374 revs.extend(opts.get('rev')) |
2375 basectx = None | |
2376 if opts.get('base'): | |
2377 basectx = scmutil.revsingle(repo, opts['base'], None) | |
2335 # a dict of data to be stored in state file | 2378 # a dict of data to be stored in state file |
2336 statedata = {} | 2379 statedata = {} |
2337 # list of new nodes created by ongoing graft | 2380 # list of new nodes created by ongoing graft |
2338 statedata['newnodes'] = [] | 2381 statedata['newnodes'] = [] |
2339 | 2382 |
2409 cmdutil.checkunfinished(repo) | 2452 cmdutil.checkunfinished(repo) |
2410 cmdutil.bailifchanged(repo) | 2453 cmdutil.bailifchanged(repo) |
2411 revs = scmutil.revrange(repo, revs) | 2454 revs = scmutil.revrange(repo, revs) |
2412 | 2455 |
2413 skipped = set() | 2456 skipped = set() |
2414 # check for merges | 2457 if basectx is None: |
2415 for rev in repo.revs('%ld and merge()', revs): | 2458 # check for merges |
2416 ui.warn(_('skipping ungraftable merge revision %d\n') % rev) | 2459 for rev in repo.revs('%ld and merge()', revs): |
2417 skipped.add(rev) | 2460 ui.warn(_('skipping ungraftable merge revision %d\n') % rev) |
2461 skipped.add(rev) | |
2418 revs = [r for r in revs if r not in skipped] | 2462 revs = [r for r in revs if r not in skipped] |
2419 if not revs: | 2463 if not revs: |
2420 return -1 | 2464 return -1 |
2465 if basectx is not None and len(revs) != 1: | |
2466 raise error.Abort(_('only one revision allowed with --base ')) | |
2421 | 2467 |
2422 # Don't check in the --continue case, in effect retaining --force across | 2468 # Don't check in the --continue case, in effect retaining --force across |
2423 # --continues. That's because without --force, any revisions we decided to | 2469 # --continues. That's because without --force, any revisions we decided to |
2424 # skip would have been filtered out here, so they wouldn't have made their | 2470 # skip would have been filtered out here, so they wouldn't have made their |
2425 # way to the graftstate. With --force, any revisions we would have otherwise | 2471 # way to the graftstate. With --force, any revisions we would have otherwise |
2426 # skipped would not have been filtered out, and if they hadn't been applied | 2472 # skipped would not have been filtered out, and if they hadn't been applied |
2427 # already, they'd have been in the graftstate. | 2473 # already, they'd have been in the graftstate. |
2428 if not (cont or opts.get('force')): | 2474 if not (cont or opts.get('force')) and basectx is None: |
2429 # check for ancestors of dest branch | 2475 # check for ancestors of dest branch |
2430 crev = repo['.'].rev() | 2476 crev = repo['.'].rev() |
2431 ancestors = repo.changelog.ancestors([crev], inclusive=True) | 2477 ancestors = repo.changelog.ancestors([crev], inclusive=True) |
2432 # XXX make this lazy in the future | 2478 # XXX make this lazy in the future |
2433 # don't mutate while iterating, create a copy | 2479 # don't mutate while iterating, create a copy |
2519 | 2565 |
2520 # we don't merge the first commit when continuing | 2566 # we don't merge the first commit when continuing |
2521 if not cont: | 2567 if not cont: |
2522 # perform the graft merge with p1(rev) as 'ancestor' | 2568 # perform the graft merge with p1(rev) as 'ancestor' |
2523 overrides = {('ui', 'forcemerge'): opts.get('tool', '')} | 2569 overrides = {('ui', 'forcemerge'): opts.get('tool', '')} |
2570 base = ctx.p1() if basectx is None else basectx | |
2524 with ui.configoverride(overrides, 'graft'): | 2571 with ui.configoverride(overrides, 'graft'): |
2525 stats = mergemod.graft(repo, ctx, ctx.p1(), ['local', 'graft']) | 2572 stats = mergemod.graft(repo, ctx, base, ['local', 'graft']) |
2526 # report any conflicts | 2573 # report any conflicts |
2527 if stats.unresolvedcount > 0: | 2574 if stats.unresolvedcount > 0: |
2528 # write out state for --continue | 2575 # write out state for --continue |
2529 nodes = [repo[rev].hex() for rev in revs[pos:]] | 2576 nodes = [repo[rev].hex() for rev in revs[pos:]] |
2530 statedata['nodes'] = nodes | 2577 statedata['nodes'] = nodes |