4 # |
4 # |
5 # This software may be used and distributed according to the terms |
5 # This software may be used and distributed according to the terms |
6 # of the GNU General Public License, incorporated herein by reference. |
6 # of the GNU General Public License, incorporated herein by reference. |
7 |
7 |
8 from demandload import demandload |
8 from demandload import demandload |
|
9 from i18n import gettext as _ |
9 demandload(globals(), "util") |
10 demandload(globals(), "util") |
10 demandload(globals(), "os re shutil tempfile") |
11 demandload(globals(), "cStringIO email.Parser os re shutil tempfile") |
|
12 |
|
13 def extract(ui, fileobj): |
|
14 '''extract patch from data read from fileobj. |
|
15 |
|
16 patch can be normal patch or contained in email message. |
|
17 |
|
18 return tuple (filename, message, user, date). any item in returned |
|
19 tuple can be None. if filename is None, fileobj did not contain |
|
20 patch. caller must unlink filename when done.''' |
|
21 |
|
22 # attempt to detect the start of a patch |
|
23 # (this heuristic is borrowed from quilt) |
|
24 diffre = re.compile(r'^(?:Index:[ \t]|diff[ \t]|RCS file: |' + |
|
25 'retrieving revision [0-9]+(\.[0-9]+)*$|' + |
|
26 '(---|\*\*\*)[ \t])', re.MULTILINE) |
|
27 |
|
28 fd, tmpname = tempfile.mkstemp(prefix='hg-patch-') |
|
29 tmpfp = os.fdopen(fd, 'w') |
|
30 try: |
|
31 hgpatch = False |
|
32 |
|
33 msg = email.Parser.Parser().parse(fileobj) |
|
34 |
|
35 message = msg['Subject'] |
|
36 user = msg['From'] |
|
37 # should try to parse msg['Date'] |
|
38 date = None |
|
39 |
|
40 if message: |
|
41 message = message.replace('\n\t', ' ') |
|
42 ui.debug('Subject: %s\n' % message) |
|
43 if user: |
|
44 ui.debug('From: %s\n' % user) |
|
45 diffs_seen = 0 |
|
46 ok_types = ('text/plain', 'text/x-diff', 'text/x-patch') |
|
47 |
|
48 for part in msg.walk(): |
|
49 content_type = part.get_content_type() |
|
50 ui.debug('Content-Type: %s\n' % content_type) |
|
51 if content_type not in ok_types: |
|
52 continue |
|
53 payload = part.get_payload(decode=True) |
|
54 m = diffre.search(payload) |
|
55 if m: |
|
56 ui.debug(_('found patch at byte %d\n') % m.start(0)) |
|
57 diffs_seen += 1 |
|
58 cfp = cStringIO.StringIO() |
|
59 if message: |
|
60 cfp.write(message) |
|
61 cfp.write('\n') |
|
62 for line in payload[:m.start(0)].splitlines(): |
|
63 if line.startswith('# HG changeset patch'): |
|
64 ui.debug(_('patch generated by hg export\n')) |
|
65 hgpatch = True |
|
66 # drop earlier commit message content |
|
67 cfp.seek(0) |
|
68 cfp.truncate() |
|
69 elif hgpatch: |
|
70 if line.startswith('# User '): |
|
71 user = line[7:] |
|
72 ui.debug('From: %s\n' % user) |
|
73 elif line.startswith("# Date "): |
|
74 date = line[7:] |
|
75 if not line.startswith('# '): |
|
76 cfp.write(line) |
|
77 cfp.write('\n') |
|
78 message = cfp.getvalue() |
|
79 if tmpfp: |
|
80 tmpfp.write(payload) |
|
81 if not payload.endswith('\n'): |
|
82 tmpfp.write('\n') |
|
83 elif not diffs_seen and message and content_type == 'text/plain': |
|
84 message += '\n' + payload |
|
85 except: |
|
86 tmpfp.close() |
|
87 os.unlink(tmpname) |
|
88 raise |
|
89 |
|
90 tmpfp.close() |
|
91 if not diffs_seen: |
|
92 os.unlink(tmpname) |
|
93 return None, message, user, date |
|
94 return tmpname, message, user, date |
11 |
95 |
12 def readgitpatch(patchname): |
96 def readgitpatch(patchname): |
13 """extract git-style metadata about patches from <patchname>""" |
97 """extract git-style metadata about patches from <patchname>""" |
14 class gitpatch: |
98 class gitpatch: |
15 "op is one of ADD, DELETE, RENAME, MODIFY or COPY" |
99 "op is one of ADD, DELETE, RENAME, MODIFY or COPY" |