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