Commit | Line | Data |
---|---|---|
0d2cd1e4 CM |
1 | __copyright__ = """ |
2 | Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com> | |
3 | ||
4 | This program is free software; you can redistribute it and/or modify | |
5 | it under the terms of the GNU General Public License version 2 as | |
6 | published by the Free Software Foundation. | |
7 | ||
8 | This program is distributed in the hope that it will be useful, | |
9 | but WITHOUT ANY WARRANTY; without even the implied warranty of | |
10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
11 | GNU General Public License for more details. | |
12 | ||
13 | You should have received a copy of the GNU General Public License | |
14 | along with this program; if not, write to the Free Software | |
15 | Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA | |
16 | """ | |
17 | ||
6ef533bc | 18 | import sys, os, re, email |
99c52915 | 19 | from mailbox import UnixMailbox |
457c3093 | 20 | from StringIO import StringIO |
0d2cd1e4 CM |
21 | from optparse import OptionParser, make_option |
22 | ||
23 | from stgit.commands.common import * | |
24 | from stgit.utils import * | |
5e888f30 | 25 | from stgit.out import * |
0d2cd1e4 CM |
26 | from stgit import stack, git |
27 | ||
28 | ||
29 | help = 'import a GNU diff file as a new patch' | |
575c575e | 30 | usage = """%prog [options] [<file>|<url>] |
0d2cd1e4 | 31 | |
b8a0986f CM |
32 | Create a new patch and apply the given GNU diff file (or the standard |
33 | input). By default, the file name is used as the patch name but this | |
388f63b6 | 34 | can be overridden with the '--name' option. The patch can either be a |
b8a0986f CM |
35 | normal file with the description at the top or it can have standard |
36 | mail format, the Subject, From and Date headers being used for | |
99c52915 CM |
37 | generating the patch information. The command can also read series and |
38 | mbox files. | |
39 | ||
40 | If a patch does not apply cleanly, the failed diff is written to the | |
41 | .stgit-failed.patch file and an empty StGIT patch is added to the | |
42 | stack. | |
0d2cd1e4 | 43 | |
b8a0986f | 44 | The patch description has to be separated from the data with a '---' |
99e73103 | 45 | line.""" |
0d2cd1e4 | 46 | |
6dd8fafa | 47 | directory = DirectoryHasRepository() |
0d2cd1e4 CM |
48 | options = [make_option('-m', '--mail', |
49 | help = 'import the patch from a standard e-mail file', | |
50 | action = 'store_true'), | |
99c52915 CM |
51 | make_option('-M', '--mbox', |
52 | help = 'import a series of patches from an mbox file', | |
53 | action = 'store_true'), | |
54 | make_option('-s', '--series', | |
55 | help = 'import a series of patches', | |
56 | action = 'store_true'), | |
575c575e CW |
57 | make_option('-u', '--url', |
58 | help = 'import a patch from a URL', | |
59 | action = 'store_true'), | |
0d2cd1e4 CM |
60 | make_option('-n', '--name', |
61 | help = 'use NAME as the patch name'), | |
b0cdad5e CM |
62 | make_option('-t', '--strip', |
63 | help = 'strip numbering and extension from patch name', | |
64 | action = 'store_true'), | |
9417ece4 CM |
65 | make_option('-i', '--ignore', |
66 | help = 'ignore the applied patches in the series', | |
67 | action = 'store_true'), | |
034db15c CM |
68 | make_option('--replace', |
69 | help = 'replace the unapplied patches in the series', | |
70 | action = 'store_true'), | |
b21bc8d1 | 71 | make_option('-b', '--base', |
35344f86 | 72 | help = 'use BASE instead of HEAD for file importing'), |
33e580e0 CM |
73 | make_option('-e', '--edit', |
74 | help = 'invoke an editor for the patch description', | |
75 | action = 'store_true'), | |
9417ece4 | 76 | make_option('-p', '--showpatch', |
6ad48e48 PBG |
77 | help = 'show the patch content in the editor buffer', |
78 | action = 'store_true'), | |
0d2cd1e4 CM |
79 | make_option('-a', '--author', metavar = '"NAME <EMAIL>"', |
80 | help = 'use "NAME <EMAIL>" as the author details'), | |
81 | make_option('--authname', | |
82 | help = 'use AUTHNAME as the author name'), | |
83 | make_option('--authemail', | |
84 | help = 'use AUTHEMAIL as the author e-mail'), | |
85 | make_option('--authdate', | |
86 | help = 'use AUTHDATE as the author date'), | |
87 | make_option('--commname', | |
88 | help = 'use COMMNAME as the committer name'), | |
89 | make_option('--commemail', | |
130df01a KH |
90 | help = 'use COMMEMAIL as the committer e-mail') |
91 | ] + make_sign_options() | |
0d2cd1e4 CM |
92 | |
93 | ||
b0cdad5e | 94 | def __strip_patch_name(name): |
bcb6d890 CM |
95 | stripped = re.sub('^[0-9]+-(.*)$', '\g<1>', name) |
96 | stripped = re.sub('^(.*)\.(diff|patch)$', '\g<1>', stripped) | |
97 | ||
98 | return stripped | |
b0cdad5e | 99 | |
613a2f16 PBG |
100 | def __replace_slashes_with_dashes(name): |
101 | stripped = name.replace('/', '-') | |
102 | ||
103 | return stripped | |
104 | ||
fd1c0cfc | 105 | def __create_patch(filename, message, author_name, author_email, |
99c52915 CM |
106 | author_date, diff, options): |
107 | """Create a new patch on the stack | |
0d2cd1e4 | 108 | """ |
fd1c0cfc CM |
109 | if options.name: |
110 | patch = options.name | |
111 | elif filename: | |
112 | patch = os.path.basename(filename) | |
113 | else: | |
114 | patch = '' | |
115 | if options.strip: | |
116 | patch = __strip_patch_name(patch) | |
6ef533bc | 117 | |
fff9bce5 | 118 | if not patch: |
c4f99b6c KH |
119 | if options.ignore or options.replace: |
120 | unacceptable_name = lambda name: False | |
121 | else: | |
122 | unacceptable_name = crt_series.patch_exists | |
123 | patch = make_patch_name(message, unacceptable_name) | |
fd1c0cfc CM |
124 | else: |
125 | # fix possible invalid characters in the patch name | |
126 | patch = re.sub('[^\w.]+', '-', patch).strip('-') | |
127 | ||
99c52915 | 128 | if options.ignore and patch in crt_series.get_applied(): |
27ac2b7e | 129 | out.info('Ignoring already applied patch "%s"' % patch) |
99c52915 CM |
130 | return |
131 | if options.replace and patch in crt_series.get_unapplied(): | |
c26ca1b2 | 132 | crt_series.delete_patch(patch, keep_log = True) |
fff9bce5 | 133 | |
95742cfc PBG |
134 | # refresh_patch() will invoke the editor in this case, with correct |
135 | # patch content | |
9d15ccd8 | 136 | if not message: |
95742cfc | 137 | can_edit = False |
9d15ccd8 | 138 | |
99c52915 CM |
139 | committer_name = committer_email = None |
140 | ||
141 | if options.author: | |
142 | options.authname, options.authemail = name_email(options.author) | |
143 | ||
0d2cd1e4 CM |
144 | # override the automatically parsed settings |
145 | if options.authname: | |
146 | author_name = options.authname | |
147 | if options.authemail: | |
148 | author_email = options.authemail | |
149 | if options.authdate: | |
150 | author_date = options.authdate | |
151 | if options.commname: | |
152 | committer_name = options.commname | |
153 | if options.commemail: | |
154 | committer_email = options.commemail | |
155 | ||
95742cfc | 156 | crt_series.new_patch(patch, message = message, can_edit = False, |
0d2cd1e4 CM |
157 | author_name = author_name, |
158 | author_email = author_email, | |
159 | author_date = author_date, | |
160 | committer_name = committer_name, | |
161 | committer_email = committer_email) | |
162 | ||
5f1629be CM |
163 | if not diff: |
164 | out.warn('No diff found, creating empty patch') | |
35344f86 | 165 | else: |
5f1629be CM |
166 | out.start('Importing patch "%s"' % patch) |
167 | if options.base: | |
6972fd6b KH |
168 | git.apply_patch(diff = diff, |
169 | base = git_id(crt_series, options.base)) | |
5f1629be CM |
170 | else: |
171 | git.apply_patch(diff = diff) | |
172 | crt_series.refresh_patch(edit = options.edit, | |
130df01a KH |
173 | show_patch = options.showpatch, |
174 | sign_str = options.sign_str) | |
5f1629be | 175 | out.done() |
99c52915 | 176 | |
fd1c0cfc | 177 | def __import_file(filename, options, patch = None): |
99c52915 CM |
178 | """Import a patch from a file or standard input |
179 | """ | |
180 | if filename: | |
181 | f = file(filename) | |
182 | else: | |
183 | f = sys.stdin | |
184 | ||
185 | if options.mail: | |
186 | try: | |
187 | msg = email.message_from_file(f) | |
188 | except Exception, ex: | |
189 | raise CmdException, 'error parsing the e-mail file: %s' % str(ex) | |
190 | message, author_name, author_email, author_date, diff = \ | |
ed60fdae | 191 | parse_mail(msg) |
99c52915 CM |
192 | else: |
193 | message, author_name, author_email, author_date, diff = \ | |
ed60fdae | 194 | parse_patch(f) |
99c52915 CM |
195 | |
196 | if filename: | |
197 | f.close() | |
198 | ||
fd1c0cfc CM |
199 | if patch: |
200 | pname = patch | |
201 | else: | |
202 | pname = filename | |
203 | ||
204 | __create_patch(pname, message, author_name, author_email, | |
99c52915 | 205 | author_date, diff, options) |
9417ece4 CM |
206 | |
207 | def __import_series(filename, options): | |
208 | """Import a series of patches | |
209 | """ | |
210 | applied = crt_series.get_applied() | |
211 | ||
212 | if filename: | |
213 | f = file(filename) | |
214 | patchdir = os.path.dirname(filename) | |
215 | else: | |
216 | f = sys.stdin | |
217 | patchdir = '' | |
218 | ||
219 | for line in f: | |
220 | patch = re.sub('#.*$', '', line).strip() | |
221 | if not patch: | |
222 | continue | |
bcb6d890 | 223 | patchfile = os.path.join(patchdir, patch) |
613a2f16 | 224 | patch = __replace_slashes_with_dashes(patch); |
9417ece4 | 225 | |
fd1c0cfc | 226 | __import_file(patchfile, options, patch) |
99c52915 CM |
227 | |
228 | if filename: | |
229 | f.close() | |
230 | ||
231 | def __import_mbox(filename, options): | |
232 | """Import a series from an mbox file | |
233 | """ | |
234 | if filename: | |
235 | f = file(filename, 'rb') | |
236 | else: | |
457c3093 | 237 | f = StringIO(sys.stdin.read()) |
99c52915 CM |
238 | |
239 | try: | |
240 | mbox = UnixMailbox(f, email.message_from_file) | |
241 | except Exception, ex: | |
242 | raise CmdException, 'error parsing the mbox file: %s' % str(ex) | |
243 | ||
244 | for msg in mbox: | |
245 | message, author_name, author_email, author_date, diff = \ | |
ed60fdae | 246 | parse_mail(msg) |
99c52915 CM |
247 | __create_patch(None, message, author_name, author_email, |
248 | author_date, diff, options) | |
249 | ||
457c3093 | 250 | f.close() |
9417ece4 | 251 | |
575c575e CW |
252 | def __import_url(url, options): |
253 | """Import a patch from a URL | |
254 | """ | |
255 | import urllib | |
256 | import tempfile | |
257 | ||
258 | if not url: | |
259 | parser.error('URL argument required') | |
260 | ||
fd1c0cfc CM |
261 | patch = os.path.basename(urllib.unquote(url)) |
262 | filename = os.path.join(tempfile.gettempdir(), patch) | |
263 | urllib.urlretrieve(url, filename) | |
264 | __import_file(filename, options) | |
575c575e | 265 | |
9417ece4 CM |
266 | def func(parser, options, args): |
267 | """Import a GNU diff file as a new patch | |
268 | """ | |
269 | if len(args) > 1: | |
270 | parser.error('incorrect number of arguments') | |
271 | ||
272 | check_local_changes() | |
273 | check_conflicts() | |
6972fd6b | 274 | check_head_top_equal(crt_series) |
9417ece4 CM |
275 | |
276 | if len(args) == 1: | |
277 | filename = args[0] | |
278 | else: | |
279 | filename = None | |
280 | ||
281 | if options.series: | |
282 | __import_series(filename, options) | |
99c52915 CM |
283 | elif options.mbox: |
284 | __import_mbox(filename, options) | |
575c575e CW |
285 | elif options.url: |
286 | __import_url(filename, options) | |
9417ece4 | 287 | else: |
fd1c0cfc | 288 | __import_file(filename, options) |
9417ece4 | 289 | |
6972fd6b | 290 | print_crt_patch(crt_series) |