Mercurial > hg > mercurial-crew-with-dirclash
annotate contrib/patchbomb @ 907:652507dc9fce
Modify init command to take an optional directory to set up.
If the directory does not exist, it is created.
If no directory is given, the current directory is used.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Sun, 14 Aug 2005 21:33:09 -0800 |
parents | 01215ad04283 |
children | 458b84a96e1c |
rev | line source |
---|---|
875 | 1 #!/usr/bin/python |
2 # | |
3 # Interactive script for sending a collection of Mercurial changesets | |
4 # as a series of patch emails. | |
5 # | |
6 # The series is started off with a "[PATCH 0 of N]" introduction, | |
7 # which describes the series as a whole. | |
8 # | |
9 # Each patch email has a Subject line of "[PATCH M of N] ...", using | |
10 # the first line of the changeset description as the subject text. | |
11 # The message contains two or three body parts: | |
12 # | |
13 # The remainder of the changeset description. | |
14 # | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
15 # [Optional] If the diffstat program is installed, the result of |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
16 # running diffstat on the patch. |
875 | 17 # |
18 # The patch itself, as generated by "hg export". | |
19 # | |
20 # Each message refers to all of its predecessors using the In-Reply-To | |
21 # and References headers, so they will show up as a sequence in | |
22 # threaded mail and news readers, and in mail archives. | |
23 # | |
24 # For each changeset, you will be prompted with a diffstat summary and | |
25 # the changeset summary, so you can be sure you are sending the right | |
26 # changes. | |
27 # | |
28 # It is best to run this script with the "-n" (test only) flag before | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
29 # firing it up "for real", in which case it will use your pager to |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
30 # display each of the messages that it would send. |
875 | 31 # |
32 # To configure a default mail host, add a section like this to your | |
33 # hgrc file: | |
34 # | |
35 # [smtp] | |
36 # host = my_mail_host | |
37 # port = 1025 | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
38 # |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
39 # To configure other defaults, add a section like this to your hgrc |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
40 # file: |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
41 # |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
42 # [patchbomb] |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
43 # from = My Name <my@email> |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
44 # to = recipient1, recipient2, ... |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
45 # cc = cc1, cc2, ... |
875 | 46 |
47 from email.MIMEMultipart import MIMEMultipart | |
48 from email.MIMEText import MIMEText | |
49 from mercurial import commands | |
50 from mercurial import fancyopts | |
51 from mercurial import hg | |
52 from mercurial import ui | |
53 import os | |
54 import popen2 | |
55 import readline | |
56 import smtplib | |
57 import socket | |
58 import sys | |
59 import tempfile | |
60 import time | |
61 | |
62 def diffstat(patch): | |
63 fd, name = tempfile.mkstemp() | |
64 try: | |
65 p = popen2.Popen3('diffstat -p1 -w79 2>/dev/null > ' + name) | |
66 try: | |
67 for line in patch: print >> p.tochild, line | |
68 p.tochild.close() | |
69 if p.wait(): return | |
70 fp = os.fdopen(fd, 'r') | |
71 stat = [] | |
72 for line in fp: stat.append(line.lstrip()) | |
73 last = stat.pop() | |
74 stat.insert(0, last) | |
75 stat = ''.join(stat) | |
76 if stat.startswith('0 files'): raise ValueError | |
77 return stat | |
78 except: raise | |
79 finally: | |
80 try: os.unlink(name) | |
81 except: pass | |
82 | |
83 def patchbomb(ui, repo, *revs, **opts): | |
84 def prompt(prompt, default = None, rest = ': ', empty_ok = False): | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
85 if default: prompt += ' [%s]' % default |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
86 prompt += rest |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
87 while True: |
875 | 88 r = raw_input(prompt) |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
89 if r: return r |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
90 if default is not None: return default |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
91 if empty_ok: return r |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
92 ui.warn('Please enter a valid value.\n') |
875 | 93 |
94 def confirm(s): | |
95 if not prompt(s, default = 'y', rest = '? ').lower().startswith('y'): | |
96 raise ValueError | |
97 | |
98 def cdiffstat(summary, patch): | |
99 s = diffstat(patch) | |
100 if s: | |
101 if summary: | |
102 ui.write(summary, '\n') | |
103 ui.write(s, '\n') | |
104 confirm('Does the diffstat above look okay') | |
105 return s | |
106 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
107 def makepatch(patch, idx, total): |
875 | 108 desc = [] |
109 node = None | |
110 for line in patch: | |
111 if line.startswith('#'): | |
112 if line.startswith('# Node ID'): node = line.split()[-1] | |
113 continue | |
114 if line.startswith('diff -r'): break | |
115 desc.append(line) | |
116 if not node: raise ValueError | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
117 body = ('\n'.join(desc[1:]).strip() or |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
118 'Patch subject is complete summary.') |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
119 body += '\n\n\n' |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
120 if opts['diffstat']: |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
121 body += cdiffstat('\n'.join(desc), patch) + '\n\n' |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
122 body += '\n'.join(patch) |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
123 msg = MIMEText(body) |
875 | 124 subj = '[PATCH %d of %d] %s' % (idx, total, desc[0].strip()) |
125 if subj.endswith('.'): subj = subj[:-1] | |
126 msg['Subject'] = subj | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
127 msg['X-Mercurial-Node'] = node |
875 | 128 return msg |
129 | |
130 start_time = int(time.time()) | |
131 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
132 def genmsgid(id): |
875 | 133 return '<%s.%s@%s>' % (id[:20], start_time, socket.getfqdn()) |
134 | |
135 patches = [] | |
136 | |
137 class exportee: | |
138 def __init__(self, container): | |
139 self.lines = [] | |
140 self.container = container | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
141 self.name = 'email' |
875 | 142 |
143 def write(self, data): | |
144 self.lines.append(data) | |
145 | |
146 def close(self): | |
147 self.container.append(''.join(self.lines).split('\n')) | |
148 self.lines = [] | |
149 | |
150 commands.export(ui, repo, *args, **{'output': exportee(patches)}) | |
151 | |
152 jumbo = [] | |
153 msgs = [] | |
154 | |
155 ui.write('This patch series consists of %d patches.\n\n' % len(patches)) | |
156 | |
157 for p, i in zip(patches, range(len(patches))): | |
158 jumbo.extend(p) | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
159 msgs.append(makepatch(p, i + 1, len(patches))) |
875 | 160 |
161 ui.write('\nWrite the introductory message for the patch series.\n\n') | |
162 | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
163 sender = (opts['from'] or ui.config('patchbomb', 'from') or |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
164 prompt('From', ui.username())) |
875 | 165 |
166 msg = MIMEMultipart() | |
167 msg['Subject'] = '[PATCH 0 of %d] %s' % ( | |
168 len(patches), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
169 opts['subject'] or |
875 | 170 prompt('Subject:', rest = ' [PATCH 0 of %d] ' % len(patches))) |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
171 to = (opts['to'] or ui.config('patchbomb', 'to') or |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
172 [s.strip() for s in prompt('To').split(',')]) |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
173 cc = (opts['cc'] or ui.config('patchbomb', 'cc') or |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
174 [s.strip() for s in prompt('Cc', default = '').split(',')]) |
875 | 175 |
176 ui.write('Finish with ^D or a dot on a line by itself.\n\n') | |
177 | |
178 body = [] | |
179 | |
180 while True: | |
181 try: l = raw_input() | |
182 except EOFError: break | |
183 if l == '.': break | |
184 body.append(l) | |
185 | |
186 msg.attach(MIMEText('\n'.join(body) + '\n')) | |
187 | |
188 ui.write('\n') | |
189 | |
190 d = cdiffstat('Final summary:\n', jumbo) | |
191 if d: msg.attach(MIMEText(d)) | |
192 | |
193 msgs.insert(0, msg) | |
194 | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
195 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
196 s = smtplib.SMTP() |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
197 s.connect(host = ui.config('smtp', 'host', 'mail'), |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
198 port = int(ui.config('smtp', 'port', 25))) |
875 | 199 |
200 parent = None | |
201 tz = time.strftime('%z') | |
202 for m in msgs: | |
203 try: | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
204 m['Message-Id'] = genmsgid(m['X-Mercurial-Node']) |
875 | 205 except TypeError: |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
206 m['Message-Id'] = genmsgid('patchbomb') |
875 | 207 if parent: |
208 m['In-Reply-To'] = parent | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
209 else: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
210 parent = m['Message-Id'] |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
211 m['Date'] = time.strftime('%a, %e %b %Y %T ', time.localtime(start_time)) + tz |
875 | 212 start_time += 1 |
213 m['From'] = sender | |
214 m['To'] = ', '.join(to) | |
215 if cc: m['Cc'] = ', '.join(cc) | |
216 ui.status('Sending ', m['Subject'], ' ...\n') | |
217 if opts['test']: | |
218 fp = os.popen(os.getenv('PAGER', 'more'), 'w') | |
219 fp.write(m.as_string(0)) | |
220 fp.write('\n') | |
221 fp.close() | |
222 else: | |
223 s.sendmail(sender, to + cc, m.as_string(0)) | |
876
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
224 if not opts['test']: |
14cfaaec2e8e
Get patchbomb script to not use MIME attachments.
Bryan O'Sullivan <bos@serpentine.com>
parents:
875
diff
changeset
|
225 s.close() |
875 | 226 |
227 if __name__ == '__main__': | |
228 optspec = [('c', 'cc', [], 'email addresses of copy recipients'), | |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
229 ('d', 'diffstat', None, 'add diffstat output to messages'), |
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
230 ('f', 'from', '', 'email address of sender'), |
875 | 231 ('n', 'test', None, 'print messages that would be sent'), |
877
25430c523677
Polish patchbomb script.
Bryan O'Sullivan <bos@serpentine.com>
parents:
876
diff
changeset
|
232 ('s', 'subject', '', 'subject of introductory message'), |
875 | 233 ('t', 'to', [], 'email addresses of recipients')] |
234 options = {} | |
235 try: | |
236 args = fancyopts.fancyopts(sys.argv[1:], commands.globalopts + optspec, | |
237 options) | |
238 except fancyopts.getopt.GetoptError, inst: | |
239 u = ui.ui() | |
240 u.warn('error: %s' % inst) | |
241 sys.exit(1) | |
242 | |
243 u = ui.ui(options["verbose"], options["debug"], options["quiet"], | |
244 not options["noninteractive"]) | |
245 repo = hg.repository(ui = u) | |
246 | |
247 patchbomb(u, repo, *args, **options) |