5 from stgit
import argparse
6 from stgit
.argparse
import opt
7 from stgit
.commands
.common
import (
9 DirectoryHasRepository
,
16 from stgit
.compat
import decode_utf8_with_latin1
17 from stgit
.lib
.git
import CommitData
, Date
, Person
18 from stgit
.lib
.transaction
import StackTransaction
, TransactionHalted
19 from stgit
.out
import out
20 from stgit
.run
import Run
23 Copyright (C) 2005, Catalin Marinas <catalin.marinas@gmail.com>
25 This program is free software; you can redistribute it and/or modify
26 it under the terms of the GNU General Public License version 2 as
27 published by the Free Software Foundation.
29 This program is distributed in the hope that it will be useful,
30 but WITHOUT ANY WARRANTY; without even the implied warranty of
31 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
32 GNU General Public License for more details.
34 You should have received a copy of the GNU General Public License
35 along with this program; if not, see http://www.gnu.org/licenses/.
39 help = 'Import a GNU diff file as a new patch'
41 usage
= ['[options] [--] [<file>|<url>]']
43 Create a new patch and apply the given GNU diff file (or the standard
44 input). By default, the file name is used as the patch name but this
45 can be overridden with the '--name' option. The patch can either be a
46 normal file with the description at the top or it can have standard
47 mail format, the Subject, From and Date headers being used for
48 generating the patch information. The command can also read series and
51 If a patch does not apply cleanly, the failed diff is written to the
52 .stgit-failed.patch file and an empty StGIT patch is added to the
55 The patch description has to be separated from the data with a '---'
65 short
='Import the patch from a standard e-mail file',
71 short
='Import a series of patches from an mbox file',
77 short
='Import a series of patches',
78 long="Import a series of patches from a series file or a tar archive.",
84 short
='Import a patch from a URL',
89 short
='Use NAME as the patch name',
96 short
='Remove N leading slashes from diff paths (default 1)',
102 short
='Strip numbering and extension from patch name',
108 short
='Ignore the applied patches in the series',
113 short
='Replace the unapplied patches in the series',
119 short
='Use BASE instead of HEAD for file importing',
124 short
='Leave the rejected hunks in corresponding *.rej files',
129 short
='Do not remove "\\r" from email lines ending with "\\r\\n"',
135 short
='Invoke an editor for the patch description',
141 short
='Show the patch content in the editor buffer',
144 + argparse
.author_options()
145 + argparse
.sign_options()
148 directory
= DirectoryHasRepository()
152 filename
, message
, author_name
, author_email
, author_date
, diff
, options
154 """Create a new patch on the stack"""
155 stack
= directory
.repository
.current_stack
160 name
= os
.path
.basename(filename
)
164 if options
.stripname
:
165 # Removing leading numbers and trailing extension
168 (?:[0-9]+-)? # Optional leading patch number
169 (.*?) # Patch name group (non-greedy)
170 (?:\.(?:diff|patch))? # Optional .diff or .patch extension
178 need_unique
= not (options
.ignore
or options
.replace
)
181 name
= stack
.patches
.make_name(name
, unique
=need_unique
, lower
=False)
183 name
= stack
.patches
.make_name(message
, unique
=need_unique
, lower
=True)
185 if options
.ignore
and name
in stack
.patchorder
.applied
:
186 out
.info('Ignoring already applied patch "%s"' % name
)
189 out
.start('Importing patch "%s"' % name
)
191 author
= options
.author(
195 Date
.maybe(author_date
),
201 out
.warn('No diff found, creating empty patch')
202 tree
= stack
.head
.data
.tree
204 iw
= stack
.repository
.default_iw
205 iw
.apply(diff
, quiet
=False, reject
=options
.reject
, strip
=options
.strip
)
206 tree
= iw
.index
.write_tree()
210 parents
=[stack
.head
],
214 cd
= update_commit_data(
218 sign_str
=options
.sign_str
,
221 commit
= stack
.repository
.commit(cd
)
223 trans
= StackTransaction(stack
, 'import: %s' % name
)
226 if options
.replace
and name
in stack
.patchorder
.unapplied
:
227 trans
.delete_patches(lambda pn
: pn
== name
, quiet
=True)
229 trans
.patches
[name
] = commit
230 trans
.applied
.append(name
)
231 except TransactionHalted
:
238 def __get_handle_and_name(filename
):
239 """Return a file object and a patch name derived from filename"""
243 # see if it's a gzip'ed or bzip2'ed patch
244 for copen
, ext
in [(gzip
.open, '.gz'), (bz2
.BZ2File
, '.bz2')]:
249 if filename
.lower().endswith(ext
):
250 filename
= filename
[: -len(ext
)]
256 return (open(filename
, 'rb'), filename
)
259 def __import_file(filename
, options
):
260 """Import a patch from a file or standard input"""
262 f
, filename
= __get_handle_and_name(filename
)
264 f
= os
.fdopen(sys
.__stdin
__.fileno(), 'rb')
266 patch_data
= f
.read()
271 message
, author_name
, author_email
, author_date
, diff
= parse_patch(
272 patch_data
, contains_diff
=True
286 def __import_series(filename
, options
):
287 """Import a series of patches"""
290 if filename
and tarfile
.is_tarfile(filename
):
291 __import_tarfile(filename
, options
)
295 patchdir
= os
.path
.dirname(filename
)
301 patch
= re
.sub('#.*$', '', line
).strip()
304 # Quilt can have "-p0", "-p1" or "-pab" patches stacked in the
305 # series but as strip level default to 1, only "-p0" can actually
306 # be found in the series file, the other ones are implicit
308 r
'(?P<patchfilename>.*)\s+-p\s*(?P<striplevel>(\d+|ab)?)\s*$', patch
311 patch
= m
.group('patchfilename')
312 if m
.group('striplevel') != '0':
314 "error importing quilt series, patch '%s'"
315 " has unsupported strip level: '-p%s'"
316 % (patch
, m
.group('striplevel'))
321 patchfile
= os
.path
.join(patchdir
, patch
)
323 __import_file(patchfile
, options
)
329 def __import_mail(filename
, options
):
330 """Import a patch from an email file or mbox"""
334 tmpdir
= tempfile
.mkdtemp('.stg')
336 mail_paths
= __mailsplit(tmpdir
, filename
, options
)
337 for mail_path
in mail_paths
:
338 __import_mail_path(mail_path
, filename
, options
)
340 shutil
.rmtree(tmpdir
)
343 def __mailsplit(tmpdir
, filename
, options
):
344 mailsplit_cmd
= ['git', 'mailsplit', '-d4', '-o' + tmpdir
]
346 mailsplit_cmd
.append('-b')
348 mailsplit_cmd
.append('--keep-cr')
351 mailsplit_cmd
.extend(['--', filename
])
352 r
= Run(*mailsplit_cmd
)
354 stdin
= os
.fdopen(sys
.__stdin
__.fileno(), 'rb')
355 r
= Run(*mailsplit_cmd
).encoding(None).raw_input(stdin
.read())
357 num_patches
= int(r
.output_one_line())
359 mail_paths
= [os
.path
.join(tmpdir
, '%04d' % n
) for n
in range(1, num_patches
+ 1)]
364 def __import_mail_path(mail_path
, filename
, options
):
365 with
open(mail_path
, 'rb') as f
:
368 msg_path
= mail_path
+ '-msg'
369 patch_path
= mail_path
+ '-patch'
372 Run('git', 'mailinfo', msg_path
, patch_path
)
379 mailinfo
= dict(line
.split(b
': ', 1) for line
in mailinfo_lines
if line
)
381 with
open(msg_path
, 'rb') as f
:
384 msg_bytes
= mailinfo
[b
'Subject'] + b
'\n\n' + msg_body
386 with
open(patch_path
, 'rb') as f
:
390 None if options
.mbox
else filename
,
391 decode_utf8_with_latin1(msg_bytes
),
392 mailinfo
[b
'Author'].decode('utf-8'),
393 mailinfo
[b
'Email'].decode('utf-8'),
394 mailinfo
[b
'Date'].decode('utf-8'),
400 def __import_url(url
, options
):
401 """Import a patch from a URL"""
403 from urllib
.parse
import unquote
404 from urllib
.request
import urlretrieve
406 from urllib
import unquote
, urlretrieve
410 raise CmdException('URL argument required')
412 patch
= os
.path
.basename(unquote(url
))
413 filename
= os
.path
.join(tempfile
.gettempdir(), patch
)
414 urlretrieve(url
, filename
)
415 __import_file(filename
, options
)
418 def __import_tarfile(tarpath
, options
):
419 """Import patch series from a tar archive"""
424 assert tarfile
.is_tarfile(tarpath
)
426 tar
= tarfile
.open(tarpath
, 'r')
427 names
= tar
.getnames()
429 # verify paths in the tarfile are safe
431 if n
.startswith('/'):
432 raise CmdException("Absolute path found in %s" % tarpath
)
433 if n
.find("..") > -1:
434 raise CmdException("Relative path found in %s" % tarpath
)
436 # find the series file
437 for seriesfile
in names
:
438 if seriesfile
.endswith('/series') or seriesfile
== 'series':
441 raise CmdException("no 'series' file found in %s" % tarpath
)
443 # unpack into a tmp dir
444 tmpdir
= tempfile
.mkdtemp('.stg')
446 tar
.extractall(tmpdir
)
447 __import_series(os
.path
.join(tmpdir
, seriesfile
), options
)
449 shutil
.rmtree(tmpdir
)
452 def func(parser
, options
, args
):
454 parser
.error('incorrect number of arguments')
460 if not options
.url
and filename
:
461 filename
= os
.path
.abspath(filename
)
463 directory
.cd_to_topdir()
465 repository
= directory
.repository
466 stack
= repository
.current_stack
468 check_local_changes(repository
)
469 check_conflicts(repository
.default_iw
)
470 check_head_top_equal(stack
)
473 __import_series(filename
, options
)
474 elif options
.mail
or options
.mbox
:
475 __import_mail(filename
, options
)
477 __import_url(filename
, options
)
479 __import_file(filename
, options
)