eggs/mercurial-1.7.3-py2.6-linux-x86_64.egg/hgext/transplant.py
changeset 69 c6bca38c1cbf
equal deleted inserted replaced
68:5ff1fc726848 69:c6bca38c1cbf
       
     1 # Patch transplanting extension for Mercurial
       
     2 #
       
     3 # Copyright 2006, 2007 Brendan Cully <brendan@kublai.com>
       
     4 #
       
     5 # This software may be used and distributed according to the terms of the
       
     6 # GNU General Public License version 2 or any later version.
       
     7 
       
     8 '''command to transplant changesets from another branch
       
     9 
       
    10 This extension allows you to transplant patches from another branch.
       
    11 
       
    12 Transplanted patches are recorded in .hg/transplant/transplants, as a
       
    13 map from a changeset hash to its hash in the source repository.
       
    14 '''
       
    15 
       
    16 from mercurial.i18n import _
       
    17 import os, tempfile
       
    18 from mercurial import bundlerepo, cmdutil, hg, merge, match
       
    19 from mercurial import patch, revlog, util, error
       
    20 from mercurial import revset
       
    21 
       
    22 class transplantentry(object):
       
    23     def __init__(self, lnode, rnode):
       
    24         self.lnode = lnode
       
    25         self.rnode = rnode
       
    26 
       
    27 class transplants(object):
       
    28     def __init__(self, path=None, transplantfile=None, opener=None):
       
    29         self.path = path
       
    30         self.transplantfile = transplantfile
       
    31         self.opener = opener
       
    32 
       
    33         if not opener:
       
    34             self.opener = util.opener(self.path)
       
    35         self.transplants = {}
       
    36         self.dirty = False
       
    37         self.read()
       
    38 
       
    39     def read(self):
       
    40         abspath = os.path.join(self.path, self.transplantfile)
       
    41         if self.transplantfile and os.path.exists(abspath):
       
    42             for line in self.opener(self.transplantfile).read().splitlines():
       
    43                 lnode, rnode = map(revlog.bin, line.split(':'))
       
    44                 list = self.transplants.setdefault(rnode, [])
       
    45                 list.append(transplantentry(lnode, rnode))
       
    46 
       
    47     def write(self):
       
    48         if self.dirty and self.transplantfile:
       
    49             if not os.path.isdir(self.path):
       
    50                 os.mkdir(self.path)
       
    51             fp = self.opener(self.transplantfile, 'w')
       
    52             for list in self.transplants.itervalues():
       
    53                 for t in list:
       
    54                     l, r = map(revlog.hex, (t.lnode, t.rnode))
       
    55                     fp.write(l + ':' + r + '\n')
       
    56             fp.close()
       
    57         self.dirty = False
       
    58 
       
    59     def get(self, rnode):
       
    60         return self.transplants.get(rnode) or []
       
    61 
       
    62     def set(self, lnode, rnode):
       
    63         list = self.transplants.setdefault(rnode, [])
       
    64         list.append(transplantentry(lnode, rnode))
       
    65         self.dirty = True
       
    66 
       
    67     def remove(self, transplant):
       
    68         list = self.transplants.get(transplant.rnode)
       
    69         if list:
       
    70             del list[list.index(transplant)]
       
    71             self.dirty = True
       
    72 
       
    73 class transplanter(object):
       
    74     def __init__(self, ui, repo):
       
    75         self.ui = ui
       
    76         self.path = repo.join('transplant')
       
    77         self.opener = util.opener(self.path)
       
    78         self.transplants = transplants(self.path, 'transplants',
       
    79                                        opener=self.opener)
       
    80 
       
    81     def applied(self, repo, node, parent):
       
    82         '''returns True if a node is already an ancestor of parent
       
    83         or has already been transplanted'''
       
    84         if hasnode(repo, node):
       
    85             if node in repo.changelog.reachable(parent, stop=node):
       
    86                 return True
       
    87         for t in self.transplants.get(node):
       
    88             # it might have been stripped
       
    89             if not hasnode(repo, t.lnode):
       
    90                 self.transplants.remove(t)
       
    91                 return False
       
    92             if t.lnode in repo.changelog.reachable(parent, stop=t.lnode):
       
    93                 return True
       
    94         return False
       
    95 
       
    96     def apply(self, repo, source, revmap, merges, opts={}):
       
    97         '''apply the revisions in revmap one by one in revision order'''
       
    98         revs = sorted(revmap)
       
    99         p1, p2 = repo.dirstate.parents()
       
   100         pulls = []
       
   101         diffopts = patch.diffopts(self.ui, opts)
       
   102         diffopts.git = True
       
   103 
       
   104         lock = wlock = None
       
   105         try:
       
   106             wlock = repo.wlock()
       
   107             lock = repo.lock()
       
   108             for rev in revs:
       
   109                 node = revmap[rev]
       
   110                 revstr = '%s:%s' % (rev, revlog.short(node))
       
   111 
       
   112                 if self.applied(repo, node, p1):
       
   113                     self.ui.warn(_('skipping already applied revision %s\n') %
       
   114                                  revstr)
       
   115                     continue
       
   116 
       
   117                 parents = source.changelog.parents(node)
       
   118                 if not opts.get('filter'):
       
   119                     # If the changeset parent is the same as the
       
   120                     # wdir's parent, just pull it.
       
   121                     if parents[0] == p1:
       
   122                         pulls.append(node)
       
   123                         p1 = node
       
   124                         continue
       
   125                     if pulls:
       
   126                         if source != repo:
       
   127                             repo.pull(source, heads=pulls)
       
   128                         merge.update(repo, pulls[-1], False, False, None)
       
   129                         p1, p2 = repo.dirstate.parents()
       
   130                         pulls = []
       
   131 
       
   132                 domerge = False
       
   133                 if node in merges:
       
   134                     # pulling all the merge revs at once would mean we
       
   135                     # couldn't transplant after the latest even if
       
   136                     # transplants before them fail.
       
   137                     domerge = True
       
   138                     if not hasnode(repo, node):
       
   139                         repo.pull(source, heads=[node])
       
   140 
       
   141                 if parents[1] != revlog.nullid:
       
   142                     self.ui.note(_('skipping merge changeset %s:%s\n')
       
   143                                  % (rev, revlog.short(node)))
       
   144                     patchfile = None
       
   145                 else:
       
   146                     fd, patchfile = tempfile.mkstemp(prefix='hg-transplant-')
       
   147                     fp = os.fdopen(fd, 'w')
       
   148                     gen = patch.diff(source, parents[0], node, opts=diffopts)
       
   149                     for chunk in gen:
       
   150                         fp.write(chunk)
       
   151                     fp.close()
       
   152 
       
   153                 del revmap[rev]
       
   154                 if patchfile or domerge:
       
   155                     try:
       
   156                         n = self.applyone(repo, node,
       
   157                                           source.changelog.read(node),
       
   158                                           patchfile, merge=domerge,
       
   159                                           log=opts.get('log'),
       
   160                                           filter=opts.get('filter'))
       
   161                         if n and domerge:
       
   162                             self.ui.status(_('%s merged at %s\n') % (revstr,
       
   163                                       revlog.short(n)))
       
   164                         elif n:
       
   165                             self.ui.status(_('%s transplanted to %s\n')
       
   166                                            % (revlog.short(node),
       
   167                                               revlog.short(n)))
       
   168                     finally:
       
   169                         if patchfile:
       
   170                             os.unlink(patchfile)
       
   171             if pulls:
       
   172                 repo.pull(source, heads=pulls)
       
   173                 merge.update(repo, pulls[-1], False, False, None)
       
   174         finally:
       
   175             self.saveseries(revmap, merges)
       
   176             self.transplants.write()
       
   177             lock.release()
       
   178             wlock.release()
       
   179 
       
   180     def filter(self, filter, changelog, patchfile):
       
   181         '''arbitrarily rewrite changeset before applying it'''
       
   182 
       
   183         self.ui.status(_('filtering %s\n') % patchfile)
       
   184         user, date, msg = (changelog[1], changelog[2], changelog[4])
       
   185 
       
   186         fd, headerfile = tempfile.mkstemp(prefix='hg-transplant-')
       
   187         fp = os.fdopen(fd, 'w')
       
   188         fp.write("# HG changeset patch\n")
       
   189         fp.write("# User %s\n" % user)
       
   190         fp.write("# Date %d %d\n" % date)
       
   191         fp.write(msg + '\n')
       
   192         fp.close()
       
   193 
       
   194         try:
       
   195             util.system('%s %s %s' % (filter, util.shellquote(headerfile),
       
   196                                    util.shellquote(patchfile)),
       
   197                         environ={'HGUSER': changelog[1]},
       
   198                         onerr=util.Abort, errprefix=_('filter failed'))
       
   199             user, date, msg = self.parselog(file(headerfile))[1:4]
       
   200         finally:
       
   201             os.unlink(headerfile)
       
   202 
       
   203         return (user, date, msg)
       
   204 
       
   205     def applyone(self, repo, node, cl, patchfile, merge=False, log=False,
       
   206                  filter=None):
       
   207         '''apply the patch in patchfile to the repository as a transplant'''
       
   208         (manifest, user, (time, timezone), files, message) = cl[:5]
       
   209         date = "%d %d" % (time, timezone)
       
   210         extra = {'transplant_source': node}
       
   211         if filter:
       
   212             (user, date, message) = self.filter(filter, cl, patchfile)
       
   213 
       
   214         if log:
       
   215             # we don't translate messages inserted into commits
       
   216             message += '\n(transplanted from %s)' % revlog.hex(node)
       
   217 
       
   218         self.ui.status(_('applying %s\n') % revlog.short(node))
       
   219         self.ui.note('%s %s\n%s\n' % (user, date, message))
       
   220 
       
   221         if not patchfile and not merge:
       
   222             raise util.Abort(_('can only omit patchfile if merging'))
       
   223         if patchfile:
       
   224             try:
       
   225                 files = {}
       
   226                 try:
       
   227                     patch.patch(patchfile, self.ui, cwd=repo.root,
       
   228                                 files=files, eolmode=None)
       
   229                     if not files:
       
   230                         self.ui.warn(_('%s: empty changeset')
       
   231                                      % revlog.hex(node))
       
   232                         return None
       
   233                 finally:
       
   234                     files = cmdutil.updatedir(self.ui, repo, files)
       
   235             except Exception, inst:
       
   236                 seriespath = os.path.join(self.path, 'series')
       
   237                 if os.path.exists(seriespath):
       
   238                     os.unlink(seriespath)
       
   239                 p1 = repo.dirstate.parents()[0]
       
   240                 p2 = node
       
   241                 self.log(user, date, message, p1, p2, merge=merge)
       
   242                 self.ui.write(str(inst) + '\n')
       
   243                 raise util.Abort(_('fix up the merge and run '
       
   244                                    'hg transplant --continue'))
       
   245         else:
       
   246             files = None
       
   247         if merge:
       
   248             p1, p2 = repo.dirstate.parents()
       
   249             repo.dirstate.setparents(p1, node)
       
   250             m = match.always(repo.root, '')
       
   251         else:
       
   252             m = match.exact(repo.root, '', files)
       
   253 
       
   254         n = repo.commit(message, user, date, extra=extra, match=m)
       
   255         if not n:
       
   256             # Crash here to prevent an unclear crash later, in
       
   257             # transplants.write().  This can happen if patch.patch()
       
   258             # does nothing but claims success or if repo.status() fails
       
   259             # to report changes done by patch.patch().  These both
       
   260             # appear to be bugs in other parts of Mercurial, but dying
       
   261             # here, as soon as we can detect the problem, is preferable
       
   262             # to silently dropping changesets on the floor.
       
   263             raise RuntimeError('nothing committed after transplant')
       
   264         if not merge:
       
   265             self.transplants.set(n, node)
       
   266 
       
   267         return n
       
   268 
       
   269     def resume(self, repo, source, opts=None):
       
   270         '''recover last transaction and apply remaining changesets'''
       
   271         if os.path.exists(os.path.join(self.path, 'journal')):
       
   272             n, node = self.recover(repo)
       
   273             self.ui.status(_('%s transplanted as %s\n') % (revlog.short(node),
       
   274                                                            revlog.short(n)))
       
   275         seriespath = os.path.join(self.path, 'series')
       
   276         if not os.path.exists(seriespath):
       
   277             self.transplants.write()
       
   278             return
       
   279         nodes, merges = self.readseries()
       
   280         revmap = {}
       
   281         for n in nodes:
       
   282             revmap[source.changelog.rev(n)] = n
       
   283         os.unlink(seriespath)
       
   284 
       
   285         self.apply(repo, source, revmap, merges, opts)
       
   286 
       
   287     def recover(self, repo):
       
   288         '''commit working directory using journal metadata'''
       
   289         node, user, date, message, parents = self.readlog()
       
   290         merge = len(parents) == 2
       
   291 
       
   292         if not user or not date or not message or not parents[0]:
       
   293             raise util.Abort(_('transplant log file is corrupt'))
       
   294 
       
   295         extra = {'transplant_source': node}
       
   296         wlock = repo.wlock()
       
   297         try:
       
   298             p1, p2 = repo.dirstate.parents()
       
   299             if p1 != parents[0]:
       
   300                 raise util.Abort(
       
   301                     _('working dir not at transplant parent %s') %
       
   302                                  revlog.hex(parents[0]))
       
   303             if merge:
       
   304                 repo.dirstate.setparents(p1, parents[1])
       
   305             n = repo.commit(message, user, date, extra=extra)
       
   306             if not n:
       
   307                 raise util.Abort(_('commit failed'))
       
   308             if not merge:
       
   309                 self.transplants.set(n, node)
       
   310             self.unlog()
       
   311 
       
   312             return n, node
       
   313         finally:
       
   314             wlock.release()
       
   315 
       
   316     def readseries(self):
       
   317         nodes = []
       
   318         merges = []
       
   319         cur = nodes
       
   320         for line in self.opener('series').read().splitlines():
       
   321             if line.startswith('# Merges'):
       
   322                 cur = merges
       
   323                 continue
       
   324             cur.append(revlog.bin(line))
       
   325 
       
   326         return (nodes, merges)
       
   327 
       
   328     def saveseries(self, revmap, merges):
       
   329         if not revmap:
       
   330             return
       
   331 
       
   332         if not os.path.isdir(self.path):
       
   333             os.mkdir(self.path)
       
   334         series = self.opener('series', 'w')
       
   335         for rev in sorted(revmap):
       
   336             series.write(revlog.hex(revmap[rev]) + '\n')
       
   337         if merges:
       
   338             series.write('# Merges\n')
       
   339             for m in merges:
       
   340                 series.write(revlog.hex(m) + '\n')
       
   341         series.close()
       
   342 
       
   343     def parselog(self, fp):
       
   344         parents = []
       
   345         message = []
       
   346         node = revlog.nullid
       
   347         inmsg = False
       
   348         for line in fp.read().splitlines():
       
   349             if inmsg:
       
   350                 message.append(line)
       
   351             elif line.startswith('# User '):
       
   352                 user = line[7:]
       
   353             elif line.startswith('# Date '):
       
   354                 date = line[7:]
       
   355             elif line.startswith('# Node ID '):
       
   356                 node = revlog.bin(line[10:])
       
   357             elif line.startswith('# Parent '):
       
   358                 parents.append(revlog.bin(line[9:]))
       
   359             elif not line.startswith('# '):
       
   360                 inmsg = True
       
   361                 message.append(line)
       
   362         return (node, user, date, '\n'.join(message), parents)
       
   363 
       
   364     def log(self, user, date, message, p1, p2, merge=False):
       
   365         '''journal changelog metadata for later recover'''
       
   366 
       
   367         if not os.path.isdir(self.path):
       
   368             os.mkdir(self.path)
       
   369         fp = self.opener('journal', 'w')
       
   370         fp.write('# User %s\n' % user)
       
   371         fp.write('# Date %s\n' % date)
       
   372         fp.write('# Node ID %s\n' % revlog.hex(p2))
       
   373         fp.write('# Parent ' + revlog.hex(p1) + '\n')
       
   374         if merge:
       
   375             fp.write('# Parent ' + revlog.hex(p2) + '\n')
       
   376         fp.write(message.rstrip() + '\n')
       
   377         fp.close()
       
   378 
       
   379     def readlog(self):
       
   380         return self.parselog(self.opener('journal'))
       
   381 
       
   382     def unlog(self):
       
   383         '''remove changelog journal'''
       
   384         absdst = os.path.join(self.path, 'journal')
       
   385         if os.path.exists(absdst):
       
   386             os.unlink(absdst)
       
   387 
       
   388     def transplantfilter(self, repo, source, root):
       
   389         def matchfn(node):
       
   390             if self.applied(repo, node, root):
       
   391                 return False
       
   392             if source.changelog.parents(node)[1] != revlog.nullid:
       
   393                 return False
       
   394             extra = source.changelog.read(node)[5]
       
   395             cnode = extra.get('transplant_source')
       
   396             if cnode and self.applied(repo, cnode, root):
       
   397                 return False
       
   398             return True
       
   399 
       
   400         return matchfn
       
   401 
       
   402 def hasnode(repo, node):
       
   403     try:
       
   404         return repo.changelog.rev(node) != None
       
   405     except error.RevlogError:
       
   406         return False
       
   407 
       
   408 def browserevs(ui, repo, nodes, opts):
       
   409     '''interactively transplant changesets'''
       
   410     def browsehelp(ui):
       
   411         ui.write(_('y: transplant this changeset\n'
       
   412                    'n: skip this changeset\n'
       
   413                    'm: merge at this changeset\n'
       
   414                    'p: show patch\n'
       
   415                    'c: commit selected changesets\n'
       
   416                    'q: cancel transplant\n'
       
   417                    '?: show this help\n'))
       
   418 
       
   419     displayer = cmdutil.show_changeset(ui, repo, opts)
       
   420     transplants = []
       
   421     merges = []
       
   422     for node in nodes:
       
   423         displayer.show(repo[node])
       
   424         action = None
       
   425         while not action:
       
   426             action = ui.prompt(_('apply changeset? [ynmpcq?]:'))
       
   427             if action == '?':
       
   428                 browsehelp(ui)
       
   429                 action = None
       
   430             elif action == 'p':
       
   431                 parent = repo.changelog.parents(node)[0]
       
   432                 for chunk in patch.diff(repo, parent, node):
       
   433                     ui.write(chunk)
       
   434                 action = None
       
   435             elif action not in ('y', 'n', 'm', 'c', 'q'):
       
   436                 ui.write(_('no such option\n'))
       
   437                 action = None
       
   438         if action == 'y':
       
   439             transplants.append(node)
       
   440         elif action == 'm':
       
   441             merges.append(node)
       
   442         elif action == 'c':
       
   443             break
       
   444         elif action == 'q':
       
   445             transplants = ()
       
   446             merges = ()
       
   447             break
       
   448     displayer.close()
       
   449     return (transplants, merges)
       
   450 
       
   451 def transplant(ui, repo, *revs, **opts):
       
   452     '''transplant changesets from another branch
       
   453 
       
   454     Selected changesets will be applied on top of the current working
       
   455     directory with the log of the original changeset. If --log is
       
   456     specified, log messages will have a comment appended of the form::
       
   457 
       
   458       (transplanted from CHANGESETHASH)
       
   459 
       
   460     You can rewrite the changelog message with the --filter option.
       
   461     Its argument will be invoked with the current changelog message as
       
   462     $1 and the patch as $2.
       
   463 
       
   464     If --source/-s is specified, selects changesets from the named
       
   465     repository. If --branch/-b is specified, selects changesets from
       
   466     the branch holding the named revision, up to that revision. If
       
   467     --all/-a is specified, all changesets on the branch will be
       
   468     transplanted, otherwise you will be prompted to select the
       
   469     changesets you want.
       
   470 
       
   471     :hg:`transplant --branch REVISION --all` will rebase the selected
       
   472     branch (up to the named revision) onto your current working
       
   473     directory.
       
   474 
       
   475     You can optionally mark selected transplanted changesets as merge
       
   476     changesets. You will not be prompted to transplant any ancestors
       
   477     of a merged transplant, and you can merge descendants of them
       
   478     normally instead of transplanting them.
       
   479 
       
   480     If no merges or revisions are provided, :hg:`transplant` will
       
   481     start an interactive changeset browser.
       
   482 
       
   483     If a changeset application fails, you can fix the merge by hand
       
   484     and then resume where you left off by calling :hg:`transplant
       
   485     --continue/-c`.
       
   486     '''
       
   487     def incwalk(repo, incoming, branches, match=util.always):
       
   488         if not branches:
       
   489             branches = None
       
   490         for node in repo.changelog.nodesbetween(incoming, branches)[0]:
       
   491             if match(node):
       
   492                 yield node
       
   493 
       
   494     def transplantwalk(repo, root, branches, match=util.always):
       
   495         if not branches:
       
   496             branches = repo.heads()
       
   497         ancestors = []
       
   498         for branch in branches:
       
   499             ancestors.append(repo.changelog.ancestor(root, branch))
       
   500         for node in repo.changelog.nodesbetween(ancestors, branches)[0]:
       
   501             if match(node):
       
   502                 yield node
       
   503 
       
   504     def checkopts(opts, revs):
       
   505         if opts.get('continue'):
       
   506             if opts.get('branch') or opts.get('all') or opts.get('merge'):
       
   507                 raise util.Abort(_('--continue is incompatible with '
       
   508                                    'branch, all or merge'))
       
   509             return
       
   510         if not (opts.get('source') or revs or
       
   511                 opts.get('merge') or opts.get('branch')):
       
   512             raise util.Abort(_('no source URL, branch tag or revision '
       
   513                                'list provided'))
       
   514         if opts.get('all'):
       
   515             if not opts.get('branch'):
       
   516                 raise util.Abort(_('--all requires a branch revision'))
       
   517             if revs:
       
   518                 raise util.Abort(_('--all is incompatible with a '
       
   519                                    'revision list'))
       
   520 
       
   521     checkopts(opts, revs)
       
   522 
       
   523     if not opts.get('log'):
       
   524         opts['log'] = ui.config('transplant', 'log')
       
   525     if not opts.get('filter'):
       
   526         opts['filter'] = ui.config('transplant', 'filter')
       
   527 
       
   528     tp = transplanter(ui, repo)
       
   529 
       
   530     p1, p2 = repo.dirstate.parents()
       
   531     if len(repo) > 0 and p1 == revlog.nullid:
       
   532         raise util.Abort(_('no revision checked out'))
       
   533     if not opts.get('continue'):
       
   534         if p2 != revlog.nullid:
       
   535             raise util.Abort(_('outstanding uncommitted merges'))
       
   536         m, a, r, d = repo.status()[:4]
       
   537         if m or a or r or d:
       
   538             raise util.Abort(_('outstanding local changes'))
       
   539 
       
   540     bundle = None
       
   541     source = opts.get('source')
       
   542     if source:
       
   543         sourcerepo = ui.expandpath(source)
       
   544         source = hg.repository(ui, sourcerepo)
       
   545         source, incoming, bundle = bundlerepo.getremotechanges(ui, repo, source,
       
   546                                     force=True)
       
   547     else:
       
   548         source = repo
       
   549 
       
   550     try:
       
   551         if opts.get('continue'):
       
   552             tp.resume(repo, source, opts)
       
   553             return
       
   554 
       
   555         tf = tp.transplantfilter(repo, source, p1)
       
   556         if opts.get('prune'):
       
   557             prune = [source.lookup(r)
       
   558                      for r in cmdutil.revrange(source, opts.get('prune'))]
       
   559             matchfn = lambda x: tf(x) and x not in prune
       
   560         else:
       
   561             matchfn = tf
       
   562         branches = map(source.lookup, opts.get('branch', ()))
       
   563         merges = map(source.lookup, opts.get('merge', ()))
       
   564         revmap = {}
       
   565         if revs:
       
   566             for r in cmdutil.revrange(source, revs):
       
   567                 revmap[int(r)] = source.lookup(r)
       
   568         elif opts.get('all') or not merges:
       
   569             if source != repo:
       
   570                 alltransplants = incwalk(source, incoming, branches,
       
   571                                          match=matchfn)
       
   572             else:
       
   573                 alltransplants = transplantwalk(source, p1, branches,
       
   574                                                 match=matchfn)
       
   575             if opts.get('all'):
       
   576                 revs = alltransplants
       
   577             else:
       
   578                 revs, newmerges = browserevs(ui, source, alltransplants, opts)
       
   579                 merges.extend(newmerges)
       
   580             for r in revs:
       
   581                 revmap[source.changelog.rev(r)] = r
       
   582         for r in merges:
       
   583             revmap[source.changelog.rev(r)] = r
       
   584 
       
   585         tp.apply(repo, source, revmap, merges, opts)
       
   586     finally:
       
   587         if bundle:
       
   588             source.close()
       
   589             os.unlink(bundle)
       
   590 
       
   591 def revsettransplanted(repo, subset, x):
       
   592     """``transplanted(set)``
       
   593     Transplanted changesets in set.
       
   594     """
       
   595     if x:
       
   596       s = revset.getset(repo, subset, x)
       
   597     else:
       
   598       s = subset
       
   599     cs = set()
       
   600     for r in xrange(0, len(repo)):
       
   601       if repo[r].extra().get('transplant_source'):
       
   602         cs.add(r)
       
   603     return [r for r in s if r in cs]
       
   604 
       
   605 def extsetup(ui):
       
   606     revset.symbols['transplanted'] = revsettransplanted
       
   607 
       
   608 cmdtable = {
       
   609     "transplant":
       
   610         (transplant,
       
   611          [('s', 'source', '',
       
   612            _('pull patches from REPO'), _('REPO')),
       
   613           ('b', 'branch', [],
       
   614            _('pull patches from branch BRANCH'), _('BRANCH')),
       
   615           ('a', 'all', None, _('pull all changesets up to BRANCH')),
       
   616           ('p', 'prune', [],
       
   617            _('skip over REV'), _('REV')),
       
   618           ('m', 'merge', [],
       
   619            _('merge at REV'), _('REV')),
       
   620           ('', 'log', None, _('append transplant info to log message')),
       
   621           ('c', 'continue', None, _('continue last transplant session '
       
   622                                     'after repair')),
       
   623           ('', 'filter', '',
       
   624            _('filter changesets through command'), _('CMD'))],
       
   625          _('hg transplant [-s REPO] [-b BRANCH [-a]] [-p REV] '
       
   626            '[-m REV] [REV]...'))
       
   627 }
       
   628 
       
   629 # tell hggettext to extract docstrings from these functions:
       
   630 i18nfunctions = [revsettransplanted]