hgext/transplant.py
changeset 46113 59fa3890d40a
parent 45942 89a2afe31e82
child 46944 0428e555acb7
equal deleted inserted replaced
46112:d6afa9c149c3 46113:59fa3890d40a
    17 
    17 
    18 import os
    18 import os
    19 
    19 
    20 from mercurial.i18n import _
    20 from mercurial.i18n import _
    21 from mercurial.pycompat import open
    21 from mercurial.pycompat import open
       
    22 from mercurial.node import (
       
    23     bin,
       
    24     hex,
       
    25     nullid,
       
    26     short,
       
    27 )
    22 from mercurial import (
    28 from mercurial import (
    23     bundlerepo,
    29     bundlerepo,
    24     cmdutil,
    30     cmdutil,
    25     error,
    31     error,
    26     exchange,
    32     exchange,
    27     hg,
    33     hg,
    28     logcmdutil,
    34     logcmdutil,
    29     match,
    35     match,
    30     merge,
    36     merge,
    31     node as nodemod,
       
    32     patch,
    37     patch,
    33     pycompat,
    38     pycompat,
    34     registrar,
    39     registrar,
    35     revlog,
       
    36     revset,
    40     revset,
    37     scmutil,
    41     scmutil,
    38     smartset,
    42     smartset,
    39     state as statemod,
    43     state as statemod,
    40     util,
    44     util,
    93 
    97 
    94     def read(self):
    98     def read(self):
    95         abspath = os.path.join(self.path, self.transplantfile)
    99         abspath = os.path.join(self.path, self.transplantfile)
    96         if self.transplantfile and os.path.exists(abspath):
   100         if self.transplantfile and os.path.exists(abspath):
    97             for line in self.opener.read(self.transplantfile).splitlines():
   101             for line in self.opener.read(self.transplantfile).splitlines():
    98                 lnode, rnode = map(revlog.bin, line.split(b':'))
   102                 lnode, rnode = map(bin, line.split(b':'))
    99                 list = self.transplants.setdefault(rnode, [])
   103                 list = self.transplants.setdefault(rnode, [])
   100                 list.append(transplantentry(lnode, rnode))
   104                 list.append(transplantentry(lnode, rnode))
   101 
   105 
   102     def write(self):
   106     def write(self):
   103         if self.dirty and self.transplantfile:
   107         if self.dirty and self.transplantfile:
   104             if not os.path.isdir(self.path):
   108             if not os.path.isdir(self.path):
   105                 os.mkdir(self.path)
   109                 os.mkdir(self.path)
   106             fp = self.opener(self.transplantfile, b'w')
   110             fp = self.opener(self.transplantfile, b'w')
   107             for list in pycompat.itervalues(self.transplants):
   111             for list in pycompat.itervalues(self.transplants):
   108                 for t in list:
   112                 for t in list:
   109                     l, r = map(nodemod.hex, (t.lnode, t.rnode))
   113                     l, r = map(hex, (t.lnode, t.rnode))
   110                     fp.write(l + b':' + r + b'\n')
   114                     fp.write(l + b':' + r + b'\n')
   111             fp.close()
   115             fp.close()
   112         self.dirty = False
   116         self.dirty = False
   113 
   117 
   114     def get(self, rnode):
   118     def get(self, rnode):
   181         try:
   185         try:
   182             lock = repo.lock()
   186             lock = repo.lock()
   183             tr = repo.transaction(b'transplant')
   187             tr = repo.transaction(b'transplant')
   184             for rev in revs:
   188             for rev in revs:
   185                 node = revmap[rev]
   189                 node = revmap[rev]
   186                 revstr = b'%d:%s' % (rev, nodemod.short(node))
   190                 revstr = b'%d:%s' % (rev, short(node))
   187 
   191 
   188                 if self.applied(repo, node, p1):
   192                 if self.applied(repo, node, p1):
   189                     self.ui.warn(
   193                     self.ui.warn(
   190                         _(b'skipping already applied revision %s\n') % revstr
   194                         _(b'skipping already applied revision %s\n') % revstr
   191                     )
   195                     )
   214                     domerge = True
   218                     domerge = True
   215                     if not hasnode(repo, node):
   219                     if not hasnode(repo, node):
   216                         exchange.pull(repo, source.peer(), heads=[node])
   220                         exchange.pull(repo, source.peer(), heads=[node])
   217 
   221 
   218                 skipmerge = False
   222                 skipmerge = False
   219                 if parents[1] != revlog.nullid:
   223                 if parents[1] != nullid:
   220                     if not opts.get(b'parent'):
   224                     if not opts.get(b'parent'):
   221                         self.ui.note(
   225                         self.ui.note(
   222                             _(b'skipping merge changeset %d:%s\n')
   226                             _(b'skipping merge changeset %d:%s\n')
   223                             % (rev, nodemod.short(node))
   227                             % (rev, short(node))
   224                         )
   228                         )
   225                         skipmerge = True
   229                         skipmerge = True
   226                     else:
   230                     else:
   227                         parent = source.lookup(opts[b'parent'])
   231                         parent = source.lookup(opts[b'parent'])
   228                         if parent not in parents:
   232                         if parent not in parents:
   229                             raise error.Abort(
   233                             raise error.Abort(
   230                                 _(b'%s is not a parent of %s')
   234                                 _(b'%s is not a parent of %s')
   231                                 % (nodemod.short(parent), nodemod.short(node))
   235                                 % (short(parent), short(node))
   232                             )
   236                             )
   233                 else:
   237                 else:
   234                     parent = parents[0]
   238                     parent = parents[0]
   235 
   239 
   236                 if skipmerge:
   240                 if skipmerge:
   261                             # fix the merge or cancel everything
   265                             # fix the merge or cancel everything
   262                             tr.close()
   266                             tr.close()
   263                             raise
   267                             raise
   264                         if n and domerge:
   268                         if n and domerge:
   265                             self.ui.status(
   269                             self.ui.status(
   266                                 _(b'%s merged at %s\n')
   270                                 _(b'%s merged at %s\n') % (revstr, short(n))
   267                                 % (revstr, nodemod.short(n))
       
   268                             )
   271                             )
   269                         elif n:
   272                         elif n:
   270                             self.ui.status(
   273                             self.ui.status(
   271                                 _(b'%s transplanted to %s\n')
   274                                 _(b'%s transplanted to %s\n')
   272                                 % (nodemod.short(node), nodemod.short(n))
   275                                 % (short(node), short(n))
   273                             )
   276                             )
   274                     finally:
   277                     finally:
   275                         if patchfile:
   278                         if patchfile:
   276                             os.unlink(patchfile)
   279                             os.unlink(patchfile)
   277             tr.close()
   280             tr.close()
   307                     procutil.shellquote(headerfile),
   310                     procutil.shellquote(headerfile),
   308                     procutil.shellquote(patchfile),
   311                     procutil.shellquote(patchfile),
   309                 ),
   312                 ),
   310                 environ={
   313                 environ={
   311                     b'HGUSER': changelog[1],
   314                     b'HGUSER': changelog[1],
   312                     b'HGREVISION': nodemod.hex(node),
   315                     b'HGREVISION': hex(node),
   313                 },
   316                 },
   314                 onerr=error.Abort,
   317                 onerr=error.Abort,
   315                 errprefix=_(b'filter failed'),
   318                 errprefix=_(b'filter failed'),
   316                 blockedtag=b'transplant_filter',
   319                 blockedtag=b'transplant_filter',
   317             )
   320             )
   331         if filter:
   334         if filter:
   332             (user, date, message) = self.filter(filter, node, cl, patchfile)
   335             (user, date, message) = self.filter(filter, node, cl, patchfile)
   333 
   336 
   334         if log:
   337         if log:
   335             # we don't translate messages inserted into commits
   338             # we don't translate messages inserted into commits
   336             message += b'\n(transplanted from %s)' % nodemod.hex(node)
   339             message += b'\n(transplanted from %s)' % hex(node)
   337 
   340 
   338         self.ui.status(_(b'applying %s\n') % nodemod.short(node))
   341         self.ui.status(_(b'applying %s\n') % short(node))
   339         self.ui.note(b'%s %s\n%s\n' % (user, date, message))
   342         self.ui.note(b'%s %s\n%s\n' % (user, date, message))
   340 
   343 
   341         if not patchfile and not merge:
   344         if not patchfile and not merge:
   342             raise error.Abort(_(b'can only omit patchfile if merging'))
   345             raise error.Abort(_(b'can only omit patchfile if merging'))
   343         if patchfile:
   346         if patchfile:
   375             extra=extra,
   378             extra=extra,
   376             match=m,
   379             match=m,
   377             editor=self.getcommiteditor(),
   380             editor=self.getcommiteditor(),
   378         )
   381         )
   379         if not n:
   382         if not n:
   380             self.ui.warn(
   383             self.ui.warn(_(b'skipping emptied changeset %s\n') % short(node))
   381                 _(b'skipping emptied changeset %s\n') % nodemod.short(node)
       
   382             )
       
   383             return None
   384             return None
   384         if not merge:
   385         if not merge:
   385             self.transplants.set(n, node)
   386             self.transplants.set(n, node)
   386 
   387 
   387         return n
   388         return n
   393         '''recover last transaction and apply remaining changesets'''
   394         '''recover last transaction and apply remaining changesets'''
   394         if os.path.exists(os.path.join(self.path, b'journal')):
   395         if os.path.exists(os.path.join(self.path, b'journal')):
   395             n, node = self.recover(repo, source, opts)
   396             n, node = self.recover(repo, source, opts)
   396             if n:
   397             if n:
   397                 self.ui.status(
   398                 self.ui.status(
   398                     _(b'%s transplanted as %s\n')
   399                     _(b'%s transplanted as %s\n') % (short(node), short(n))
   399                     % (nodemod.short(node), nodemod.short(n))
       
   400                 )
   400                 )
   401             else:
   401             else:
   402                 self.ui.status(
   402                 self.ui.status(
   403                     _(b'%s skipped due to empty diff\n')
   403                     _(b'%s skipped due to empty diff\n') % (short(node),)
   404                     % (nodemod.short(node),)
       
   405                 )
   404                 )
   406         seriespath = os.path.join(self.path, b'series')
   405         seriespath = os.path.join(self.path, b'series')
   407         if not os.path.exists(seriespath):
   406         if not os.path.exists(seriespath):
   408             self.transplants.write()
   407             self.transplants.write()
   409             return
   408             return
   428             if opts.get(b'parent'):
   427             if opts.get(b'parent'):
   429                 parent = source.lookup(opts[b'parent'])
   428                 parent = source.lookup(opts[b'parent'])
   430                 if parent not in parents:
   429                 if parent not in parents:
   431                     raise error.Abort(
   430                     raise error.Abort(
   432                         _(b'%s is not a parent of %s')
   431                         _(b'%s is not a parent of %s')
   433                         % (nodemod.short(parent), nodemod.short(node))
   432                         % (short(parent), short(node))
   434                     )
   433                     )
   435             else:
   434             else:
   436                 merge = True
   435                 merge = True
   437 
   436 
   438         extra = {b'transplant_source': node}
   437         extra = {b'transplant_source': node}
   439         try:
   438         try:
   440             p1 = repo.dirstate.p1()
   439             p1 = repo.dirstate.p1()
   441             if p1 != parent:
   440             if p1 != parent:
   442                 raise error.Abort(
   441                 raise error.Abort(
   443                     _(b'working directory not at transplant parent %s')
   442                     _(b'working directory not at transplant parent %s')
   444                     % nodemod.hex(parent)
   443                     % hex(parent)
   445                 )
   444                 )
   446             if merge:
   445             if merge:
   447                 repo.setparents(p1, parents[1])
   446                 repo.setparents(p1, parents[1])
   448             st = repo.status()
   447             st = repo.status()
   449             modified, added, removed, deleted = (
   448             modified, added, removed, deleted = (
   492         cur = nodes
   491         cur = nodes
   493         for line in self.opener.read(b'series').splitlines():
   492         for line in self.opener.read(b'series').splitlines():
   494             if line.startswith(b'# Merges'):
   493             if line.startswith(b'# Merges'):
   495                 cur = merges
   494                 cur = merges
   496                 continue
   495                 continue
   497             cur.append(revlog.bin(line))
   496             cur.append(bin(line))
   498 
   497 
   499         return (nodes, merges)
   498         return (nodes, merges)
   500 
   499 
   501     def saveseries(self, revmap, merges):
   500     def saveseries(self, revmap, merges):
   502         if not revmap:
   501         if not revmap:
   504 
   503 
   505         if not os.path.isdir(self.path):
   504         if not os.path.isdir(self.path):
   506             os.mkdir(self.path)
   505             os.mkdir(self.path)
   507         series = self.opener(b'series', b'w')
   506         series = self.opener(b'series', b'w')
   508         for rev in sorted(revmap):
   507         for rev in sorted(revmap):
   509             series.write(nodemod.hex(revmap[rev]) + b'\n')
   508             series.write(hex(revmap[rev]) + b'\n')
   510         if merges:
   509         if merges:
   511             series.write(b'# Merges\n')
   510             series.write(b'# Merges\n')
   512             for m in merges:
   511             for m in merges:
   513                 series.write(nodemod.hex(m) + b'\n')
   512                 series.write(hex(m) + b'\n')
   514         series.close()
   513         series.close()
   515 
   514 
   516     def parselog(self, fp):
   515     def parselog(self, fp):
   517         parents = []
   516         parents = []
   518         message = []
   517         message = []
   519         node = revlog.nullid
   518         node = nullid
   520         inmsg = False
   519         inmsg = False
   521         user = None
   520         user = None
   522         date = None
   521         date = None
   523         for line in fp.read().splitlines():
   522         for line in fp.read().splitlines():
   524             if inmsg:
   523             if inmsg:
   526             elif line.startswith(b'# User '):
   525             elif line.startswith(b'# User '):
   527                 user = line[7:]
   526                 user = line[7:]
   528             elif line.startswith(b'# Date '):
   527             elif line.startswith(b'# Date '):
   529                 date = line[7:]
   528                 date = line[7:]
   530             elif line.startswith(b'# Node ID '):
   529             elif line.startswith(b'# Node ID '):
   531                 node = revlog.bin(line[10:])
   530                 node = bin(line[10:])
   532             elif line.startswith(b'# Parent '):
   531             elif line.startswith(b'# Parent '):
   533                 parents.append(revlog.bin(line[9:]))
   532                 parents.append(bin(line[9:]))
   534             elif not line.startswith(b'# '):
   533             elif not line.startswith(b'# '):
   535                 inmsg = True
   534                 inmsg = True
   536                 message.append(line)
   535                 message.append(line)
   537         if None in (user, date):
   536         if None in (user, date):
   538             raise error.Abort(
   537             raise error.Abort(
   546         if not os.path.isdir(self.path):
   545         if not os.path.isdir(self.path):
   547             os.mkdir(self.path)
   546             os.mkdir(self.path)
   548         fp = self.opener(b'journal', b'w')
   547         fp = self.opener(b'journal', b'w')
   549         fp.write(b'# User %s\n' % user)
   548         fp.write(b'# User %s\n' % user)
   550         fp.write(b'# Date %s\n' % date)
   549         fp.write(b'# Date %s\n' % date)
   551         fp.write(b'# Node ID %s\n' % nodemod.hex(p2))
   550         fp.write(b'# Node ID %s\n' % hex(p2))
   552         fp.write(b'# Parent ' + nodemod.hex(p1) + b'\n')
   551         fp.write(b'# Parent ' + hex(p1) + b'\n')
   553         if merge:
   552         if merge:
   554             fp.write(b'# Parent ' + nodemod.hex(p2) + b'\n')
   553             fp.write(b'# Parent ' + hex(p2) + b'\n')
   555         fp.write(message.rstrip() + b'\n')
   554         fp.write(message.rstrip() + b'\n')
   556         fp.close()
   555         fp.close()
   557 
   556 
   558     def readlog(self):
   557     def readlog(self):
   559         return self.parselog(self.opener(b'journal'))
   558         return self.parselog(self.opener(b'journal'))
   566 
   565 
   567     def transplantfilter(self, repo, source, root):
   566     def transplantfilter(self, repo, source, root):
   568         def matchfn(node):
   567         def matchfn(node):
   569             if self.applied(repo, node, root):
   568             if self.applied(repo, node, root):
   570                 return False
   569                 return False
   571             if source.changelog.parents(node)[1] != revlog.nullid:
   570             if source.changelog.parents(node)[1] != nullid:
   572                 return False
   571                 return False
   573             extra = source.changelog.read(node)[5]
   572             extra = source.changelog.read(node)[5]
   574             cnode = extra.get(b'transplant_source')
   573             cnode = extra.get(b'transplant_source')
   575             if cnode and self.applied(repo, cnode, root):
   574             if cnode and self.applied(repo, cnode, root):
   576                 return False
   575                 return False
   802         opts[b'filter'] = ui.config(b'transplant', b'filter')
   801         opts[b'filter'] = ui.config(b'transplant', b'filter')
   803 
   802 
   804     tp = transplanter(ui, repo, opts)
   803     tp = transplanter(ui, repo, opts)
   805 
   804 
   806     p1 = repo.dirstate.p1()
   805     p1 = repo.dirstate.p1()
   807     if len(repo) > 0 and p1 == revlog.nullid:
   806     if len(repo) > 0 and p1 == nullid:
   808         raise error.Abort(_(b'no revision checked out'))
   807         raise error.Abort(_(b'no revision checked out'))
   809     if opts.get(b'continue'):
   808     if opts.get(b'continue'):
   810         if not tp.canresume():
   809         if not tp.canresume():
   811             raise error.StateError(_(b'no transplant to continue'))
   810             raise error.StateError(_(b'no transplant to continue'))
   812     elif opts.get(b'stop'):
   811     elif opts.get(b'stop'):
   907 def kwtransplanted(context, mapping):
   906 def kwtransplanted(context, mapping):
   908     """String. The node identifier of the transplanted
   907     """String. The node identifier of the transplanted
   909     changeset if any."""
   908     changeset if any."""
   910     ctx = context.resource(mapping, b'ctx')
   909     ctx = context.resource(mapping, b'ctx')
   911     n = ctx.extra().get(b'transplant_source')
   910     n = ctx.extra().get(b'transplant_source')
   912     return n and nodemod.hex(n) or b''
   911     return n and hex(n) or b''
   913 
   912 
   914 
   913 
   915 def extsetup(ui):
   914 def extsetup(ui):
   916     statemod.addunfinished(
   915     statemod.addunfinished(
   917         b'transplant',
   916         b'transplant',