# HG changeset patch # User Matt Mackall # Date 2011-10-12 23:45:36 # Node ID 2d710c12ffc0cef637fe19d53aed48803047c090 # Parent 7196ed7a150534d29c159b9cb68461325bd7ee67 graft: add initial implementation diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -2447,6 +2447,75 @@ def forget(ui, repo, *pats, **opts): repo[None].forget(forget) return errs +@command('graft', + [], + _('[OPTION]... REVISION...')) +def graft(ui, repo, rev, *revs, **opts): + '''copy changes from other branches onto the current branch + + This command uses Mercurial's merge logic to copy individual + changes from other branches without merging branches in the + history graph. This is sometimes known as 'backporting' or + 'cherry-picking'. + + Changesets that are ancestors of the current revision, that have + already been grafted, or that are merges will be skipped. + + Returns 0 on successful completion. + ''' + + cmdutil.bailifchanged(repo) + + revs = [rev] + list(revs) + revs = scmutil.revrange(repo, revs) + + # check for merges + for ctx in repo.set('%ld and merge()', revs): + ui.warn(_('skipping ungraftable merge revision %s\n') % ctx.rev()) + revs.remove(ctx.rev()) + if not revs: + return -1 + + # check for ancestors of dest branch + for ctx in repo.set('::. and %ld', revs): + ui.warn(_('skipping ancestor revision %s\n') % ctx.rev()) + revs.remove(ctx.rev()) + if not revs: + return -1 + + # check ancestors for earlier grafts + ui.debug('scanning for existing transplants') + for ctx in repo.set("::. - ::%ld", revs): + n = ctx.extra().get('source') + if n and n in repo: + r = repo[n].rev() + ui.warn(_('skipping already grafted revision %s\n') % r) + revs.remove(r) + if not revs: + return -1 + + for ctx in repo.set("%ld", revs): + current = repo['.'] + ui.debug('grafting revision %s', ctx.rev()) + # perform the graft merge with p1(rev) as 'ancestor' + stats = mergemod.update(repo, ctx.node(), True, True, False, + ctx.p1().node()) + # drop the second merge parent + repo.dirstate.setparents(current.node(), nullid) + repo.dirstate.write() + # fix up dirstate for copies and renames + cmdutil.duplicatecopies(repo, ctx.rev(), current.node(), nullid) + # report any conflicts + if stats and stats[3] > 0: + raise util.Abort(_("unresolved conflicts, can't continue"), + hint=_('use hg resolve and hg graft --continue')) + # commit + extra = {'source': ctx.hex()} + repo.commit(text=ctx.description(), user=ctx.user(), + date=ctx.date(), extra=extra) + + return 0 + @command('grep', [('0', 'print0', None, _('end fields with NUL')), ('', 'all', None, _('print all revisions that match')), diff --git a/tests/test-debugcomplete.t b/tests/test-debugcomplete.t --- a/tests/test-debugcomplete.t +++ b/tests/test-debugcomplete.t @@ -17,6 +17,7 @@ Show all commands except debug commands diff export forget + graft grep heads help @@ -242,6 +243,7 @@ Show all commands + options debugsub: rev debugwalk: include, exclude debugwireargs: three, four, five, ssh, remotecmd, insecure + graft: grep: print0, all, text, follow, ignore-case, files-with-matches, line-number, rev, user, date, include, exclude heads: rev, topo, active, closed, style, template help: extension, command diff --git a/tests/test-globalopts.t b/tests/test-globalopts.t --- a/tests/test-globalopts.t +++ b/tests/test-globalopts.t @@ -296,6 +296,7 @@ Testing -h/--help: diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -377,6 +378,7 @@ Testing -h/--help: diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview diff --git a/tests/test-help.t b/tests/test-help.t --- a/tests/test-help.t +++ b/tests/test-help.t @@ -66,6 +66,7 @@ Short help: diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -141,6 +142,7 @@ Short help: diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview @@ -626,6 +628,7 @@ Test that default list of commands omits diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget forget the specified files on the next commit + graft copy changes from other branches onto the current branch grep search for a pattern in specified files and revisions heads show current repository heads or show branch heads help show help for a given topic or a help overview