hgext/patchbomb.py
changeset 7354 cad454f295b0
parent 7353 982b55ec80be
child 7359 b0fa5dbd9cdd
equal deleted inserted replaced
7353:982b55ec80be 7354:cad454f295b0
    79 
    79 
    80     def close(self):
    80     def close(self):
    81         self.container.append(''.join(self.lines).split('\n'))
    81         self.container.append(''.join(self.lines).split('\n'))
    82         self.lines = []
    82         self.lines = []
    83 
    83 
       
    84 def prompt(ui, prompt, default=None, rest=': ', empty_ok=False):
       
    85     if not ui.interactive:
       
    86         return default
       
    87     if default:
       
    88         prompt += ' [%s]' % default
       
    89     prompt += rest
       
    90     while True:
       
    91         r = ui.prompt(prompt, default=default)
       
    92         if r:
       
    93             return r
       
    94         if default is not None:
       
    95             return default
       
    96         if empty_ok:
       
    97             return r
       
    98         ui.warn(_('Please enter a valid value.\n'))
       
    99 
       
   100 def cdiffstat(ui, summary, patchlines):
       
   101     s = patch.diffstat(patchlines)
       
   102     if s:
       
   103         if summary:
       
   104             ui.write(summary, '\n')
       
   105             ui.write(s, '\n')
       
   106         ans = prompt(ui, _('Does the diffstat above look okay? '), 'y')
       
   107         if not ans.lower().startswith('y'):
       
   108             raise util.Abort(_('diffstat rejected'))
       
   109     elif s is None:
       
   110         ui.warn(_('no diffstat information available\n'))
       
   111         s = ''
       
   112     return s
       
   113 
       
   114 def makepatch(ui, repo, patch, opts, _charsets, idx, total, patchname=None):
       
   115 
       
   116     desc = []
       
   117     node = None
       
   118     body = ''
       
   119 
       
   120     for line in patch:
       
   121         if line.startswith('#'):
       
   122             if line.startswith('# Node ID'):
       
   123                 node = line.split()[-1]
       
   124             continue
       
   125         if line.startswith('diff -r') or line.startswith('diff --git'):
       
   126             break
       
   127         desc.append(line)
       
   128 
       
   129     if not patchname and not node:
       
   130         raise ValueError
       
   131 
       
   132     if opts.get('attach'):
       
   133         body = ('\n'.join(desc[1:]).strip() or
       
   134                 'Patch subject is complete summary.')
       
   135         body += '\n\n\n'
       
   136 
       
   137     if opts.get('plain'):
       
   138         while patch and patch[0].startswith('# '):
       
   139             patch.pop(0)
       
   140         if patch:
       
   141             patch.pop(0)
       
   142         while patch and not patch[0].strip():
       
   143             patch.pop(0)
       
   144 
       
   145     if opts.get('diffstat'):
       
   146         body += cdiffstat(ui, '\n'.join(desc), patch) + '\n\n'
       
   147 
       
   148     if opts.get('attach') or opts.get('inline'):
       
   149         msg = email.MIMEMultipart.MIMEMultipart()
       
   150         if body:
       
   151             msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
       
   152         p = mail.mimetextpatch('\n'.join(patch), 'x-patch', opts.get('test'))
       
   153         binnode = bin(node)
       
   154         # if node is mq patch, it will have patch file name as tag
       
   155         if not patchname:
       
   156             patchtags = [t for t in repo.nodetags(binnode)
       
   157                          if t.endswith('.patch') or t.endswith('.diff')]
       
   158             if patchtags:
       
   159                 patchname = patchtags[0]
       
   160             elif total > 1:
       
   161                 patchname = cmdutil.make_filename(repo, '%b-%n.patch',
       
   162                                                   binnode, idx, total)
       
   163             else:
       
   164                 patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
       
   165         disposition = 'inline'
       
   166         if opts.get('attach'):
       
   167             disposition = 'attachment'
       
   168         p['Content-Disposition'] = disposition + '; filename=' + patchname
       
   169         msg.attach(p)
       
   170     else:
       
   171         body += '\n'.join(patch)
       
   172         msg = mail.mimetextpatch(body, display=opts.get('test'))
       
   173 
       
   174     subj = desc[0].strip().rstrip('. ')
       
   175     if total == 1:
       
   176         subj = '[PATCH] ' + (opts.get('subject') or subj)
       
   177     else:
       
   178         tlen = len(str(total))
       
   179         subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
       
   180     msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
       
   181     msg['X-Mercurial-Node'] = node
       
   182     return msg, subj
       
   183 
    84 def patchbomb(ui, repo, *revs, **opts):
   184 def patchbomb(ui, repo, *revs, **opts):
    85     '''send changesets by email
   185     '''send changesets by email
    86 
   186 
    87     By default, diffs are sent in the format generated by hg export,
   187     By default, diffs are sent in the format generated by hg export,
    88     one per message.  The series starts with a "[PATCH 0 of N]"
   188     one per message.  The series starts with a "[PATCH 0 of N]"
   123     Before using this command, you will need to enable email in your hgrc.
   223     Before using this command, you will need to enable email in your hgrc.
   124     See the [email] section in hgrc(5) for details.
   224     See the [email] section in hgrc(5) for details.
   125     '''
   225     '''
   126 
   226 
   127     _charsets = mail._charsets(ui)
   227     _charsets = mail._charsets(ui)
   128 
       
   129     def prompt(prompt, default = None, rest = ': ', empty_ok = False):
       
   130         if not ui.interactive:
       
   131             return default
       
   132         if default:
       
   133             prompt += ' [%s]' % default
       
   134         prompt += rest
       
   135         while True:
       
   136             r = ui.prompt(prompt, default=default)
       
   137             if r:
       
   138                 return r
       
   139             if default is not None:
       
   140                 return default
       
   141             if empty_ok:
       
   142                 return r
       
   143             ui.warn(_('Please enter a valid value.\n'))
       
   144 
       
   145     def confirm(s, denial):
       
   146         if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'):
       
   147             raise util.Abort(denial)
       
   148 
       
   149     def cdiffstat(summary, patchlines):
       
   150         s = patch.diffstat(patchlines)
       
   151         if s:
       
   152             if summary:
       
   153                 ui.write(summary, '\n')
       
   154                 ui.write(s, '\n')
       
   155             confirm(_('Does the diffstat above look okay'),
       
   156                     _('diffstat rejected'))
       
   157         elif s is None:
       
   158             ui.warn(_('No diffstat information available.\n'))
       
   159             s = ''
       
   160         return s
       
   161 
       
   162     def makepatch(patch, idx, total, patchname=None):
       
   163         desc = []
       
   164         node = None
       
   165         body = ''
       
   166         for line in patch:
       
   167             if line.startswith('#'):
       
   168                 if line.startswith('# Node ID'):
       
   169                     node = line.split()[-1]
       
   170                 continue
       
   171             if line.startswith('diff -r') or line.startswith('diff --git'):
       
   172                 break
       
   173             desc.append(line)
       
   174         if not patchname and not node:
       
   175             raise ValueError
       
   176 
       
   177         if opts.get('attach'):
       
   178             body = ('\n'.join(desc[1:]).strip() or
       
   179                     'Patch subject is complete summary.')
       
   180             body += '\n\n\n'
       
   181 
       
   182         if opts.get('plain'):
       
   183             while patch and patch[0].startswith('# '):
       
   184                 patch.pop(0)
       
   185             if patch:
       
   186                 patch.pop(0)
       
   187             while patch and not patch[0].strip():
       
   188                 patch.pop(0)
       
   189         if opts.get('diffstat'):
       
   190             body += cdiffstat('\n'.join(desc), patch) + '\n\n'
       
   191         if opts.get('attach') or opts.get('inline'):
       
   192             msg = email.MIMEMultipart.MIMEMultipart()
       
   193             if body:
       
   194                 msg.attach(mail.mimeencode(ui, body, _charsets,
       
   195                                            opts.get('test')))
       
   196             p = mail.mimetextpatch('\n'.join(patch), 'x-patch',
       
   197                                    opts.get('test'))
       
   198             binnode = bin(node)
       
   199             # if node is mq patch, it will have patch file name as tag
       
   200             if not patchname:
       
   201                 patchtags = [t for t in repo.nodetags(binnode)
       
   202                              if t.endswith('.patch') or t.endswith('.diff')]
       
   203                 if patchtags:
       
   204                     patchname = patchtags[0]
       
   205                 elif total > 1:
       
   206                     patchname = cmdutil.make_filename(repo, '%b-%n.patch',
       
   207                                                       binnode, idx, total)
       
   208                 else:
       
   209                     patchname = cmdutil.make_filename(repo, '%b.patch', binnode)
       
   210             disposition = 'inline'
       
   211             if opts.get('attach'):
       
   212                 disposition = 'attachment'
       
   213             p['Content-Disposition'] = disposition + '; filename=' + patchname
       
   214             msg.attach(p)
       
   215         else:
       
   216             body += '\n'.join(patch)
       
   217             msg = mail.mimetextpatch(body, display=opts.get('test'))
       
   218 
       
   219         subj = desc[0].strip().rstrip('. ')
       
   220         if total == 1:
       
   221             subj = '[PATCH] ' + (opts.get('subject') or subj)
       
   222         else:
       
   223             tlen = len(str(total))
       
   224             subj = '[PATCH %0*d of %d] %s' % (tlen, idx, total, subj)
       
   225         msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
       
   226         msg['X-Mercurial-Node'] = node
       
   227         return msg, subj
       
   228 
   228 
   229     def outgoing(dest, revs):
   229     def outgoing(dest, revs):
   230         '''Return the revisions present locally but not in dest'''
   230         '''Return the revisions present locally but not in dest'''
   231         dest = ui.expandpath(dest or 'default-push', dest or 'default')
   231         dest = ui.expandpath(dest or 'default-push', dest or 'default')
   232         revs = [repo.lookup(rev) for rev in revs]
   232         revs = [repo.lookup(rev) for rev in revs]
   310         name = None
   310         name = None
   311         for p, i in zip(patches, xrange(len(patches))):
   311         for p, i in zip(patches, xrange(len(patches))):
   312             jumbo.extend(p)
   312             jumbo.extend(p)
   313             if patchnames:
   313             if patchnames:
   314                 name = patchnames[i]
   314                 name = patchnames[i]
   315             msgs.append(makepatch(p, i + 1, len(patches), name))
   315             msg = makepatch(ui, repo, p, opts, _charsets, i + 1,
       
   316                             len(patches), name)
       
   317             msgs.append(msg)
   316 
   318 
   317         if len(patches) > 1:
   319         if len(patches) > 1:
   318             tlen = len(str(len(patches)))
   320             tlen = len(str(len(patches)))
   319 
   321 
   320             subj = '[PATCH %0*d of %d] %s' % (
   322             subj = '[PATCH %0*d of %d] %s' % (
   321                 tlen, 0, len(patches),
   323                 tlen, 0, len(patches),
   322                 opts.get('subject') or
   324                 opts.get('subject') or
   323                 prompt('Subject:',
   325                 prompt(ui, 'Subject:',
   324                        rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
   326                        rest=' [PATCH %0*d of %d] ' % (tlen, 0, len(patches))))
   325 
   327 
   326             body = ''
   328             body = ''
   327             if opts.get('diffstat'):
   329             if opts.get('diffstat'):
   328                 d = cdiffstat(_('Final summary:\n'), jumbo)
   330                 d = cdiffstat(ui, _('Final summary:\n'), jumbo)
   329                 if d:
   331                 if d:
   330                     body = '\n' + d
   332                     body = '\n' + d
   331 
   333 
   332             body = getdescription(body, sender)
   334             body = getdescription(body, sender)
   333             msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
   335             msg = mail.mimeencode(ui, body, _charsets, opts.get('test'))
   337             msgs.insert(0, (msg, subj))
   339             msgs.insert(0, (msg, subj))
   338         return msgs
   340         return msgs
   339 
   341 
   340     def getbundlemsgs(bundle):
   342     def getbundlemsgs(bundle):
   341         subj = (opts.get('subject')
   343         subj = (opts.get('subject')
   342                 or prompt('Subject:', default='A bundle for your repository'))
   344                 or prompt(ui, 'Subject:', 'A bundle for your repository'))
   343 
   345 
   344         body = getdescription('', sender)
   346         body = getdescription('', sender)
   345         msg = email.MIMEMultipart.MIMEMultipart()
   347         msg = email.MIMEMultipart.MIMEMultipart()
   346         if body:
   348         if body:
   347             msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
   349             msg.attach(mail.mimeencode(ui, body, _charsets, opts.get('test')))
   354         msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
   356         msg['Subject'] = mail.headencode(ui, subj, _charsets, opts.get('test'))
   355         return [(msg, subj)]
   357         return [(msg, subj)]
   356 
   358 
   357     sender = (opts.get('from') or ui.config('email', 'from') or
   359     sender = (opts.get('from') or ui.config('email', 'from') or
   358               ui.config('patchbomb', 'from') or
   360               ui.config('patchbomb', 'from') or
   359               prompt('From', ui.username()))
   361               prompt(ui, 'From', ui.username()))
   360 
   362 
   361     patches = opts.get('patches')
   363     patches = opts.get('patches')
   362     if patches:
   364     if patches:
   363         msgs = getpatchmsgs(patches, opts.get('patchnames'))
   365         msgs = getpatchmsgs(patches, opts.get('patchnames'))
   364     elif opts.get('bundle'):
   366     elif opts.get('bundle'):
   372         msgs = getpatchmsgs(patches)
   374         msgs = getpatchmsgs(patches)
   373 
   375 
   374     def getaddrs(opt, prpt, default = None):
   376     def getaddrs(opt, prpt, default = None):
   375         addrs = opts.get(opt) or (ui.config('email', opt) or
   377         addrs = opts.get(opt) or (ui.config('email', opt) or
   376                                   ui.config('patchbomb', opt) or
   378                                   ui.config('patchbomb', opt) or
   377                                   prompt(prpt, default = default)).split(',')
   379                                   prompt(ui, prpt, default)).split(',')
   378         return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test'))
   380         return [mail.addressencode(ui, a.strip(), _charsets, opts.get('test'))
   379                 for a in addrs if a.strip()]
   381                 for a in addrs if a.strip()]
   380 
   382 
   381     to = getaddrs('to', 'To')
   383     to = getaddrs('to', 'To')
   382     cc = getaddrs('cc', 'Cc', '')
   384     cc = getaddrs('cc', 'Cc', '')