14 _msg_id_regex
= re
.compile(r
'<([^<>]+)>')
15 def _parse_msg_id(str):
16 return _msg_id_regex
.search(str).group(1)
18 parser
= email
.Parser
.Parser()
20 def _detect_reply_id(msg
):
21 if msg
['In-Reply-To']:
22 return _parse_msg_id(msg
['In-Reply-To'])
24 refs
= ' '.join(msg
.get_all('References'))
25 ref_ids
= [m
.group(1) for m
in _msg_id_regex
.finditer(refs
)]
28 def _get_text_payload(msg
):
29 if not msg
.is_multipart():
30 return msg
.get_payload()
31 textpart
= max(email
.Iterators
.typed_subpart_iterator(msg
), key
=len)
32 if textpart
.is_multipart():
33 return textpart
.get_payload(0)
35 return textpart
.get_payload()
37 _format_patch_regex
= re
.compile('.*^---$.*^diff --git', re
.MULTILINE|re
.DOTALL
)
38 _snip_patch_regex
= re
.compile('.*^-+ ?(?:8<|>8) ?-+\n(.*^diff --git.*)',
39 re
.MULTILINE|re
.DOTALL
)
40 def _guess_patch_contents(msg
):
41 p
= _get_text_payload(msg
)
42 if _format_patch_regex
.match(p
):
44 return msg
.as_string()
45 m
= _snip_patch_regex
.match(p
)
47 msg
.set_payload(m
.group(1))
48 return msg
.as_string()
53 def later_unapplied_patches(session
, msg
):
54 return (session
.query(Mail
)
55 .filter(Mail
.has_patch
==True)
56 .filter(sqlalchemy
.in_(msg
.message_id
, Mail
.references
))
60 def try_patch(session
, m
, pp
, commit
):
61 git('checkout', commit
.sha1
)
64 except patch
.PatchError
:
66 pipe
= git('show', ret_pipe
=True)
67 output
= git('patch-id', input_pipe
=pipe
)[0]
68 patch_id
, commit_id
= output
.split()
69 c
= session
.query(db
.Commit
).filter(db
.Commit
.sha1
== commit_id
).first()
71 output
= git('log', '-1', '--pretty=format:%ct %at')[0]
72 adate
, cdate
= [int(s
) for s
in output
.split()]
73 c
= db
.Commit(commit_id
, cdate
, adate
, patch_id
, False)
75 p
= db
.Patch(c
, m
.id, pp
.notes
)
79 def try_patch_anywhere(session
, msg
, m
):
80 pdata
= _guess_patch_contents(msg
)
83 pp
= patch
.Patch(pdata
)
84 # first try on the commit given by the blobs
86 for prefix
in pp
.blobs_pre
:
87 ret
= (session
.query(db
.Blob
, db
.Commit
)
88 .filter(db
.Blob
.newest_commit_sha1
== db
.Commit
.sha1
)
89 .filter(db
.Blob
.sha1
.like(prefix
+'%'))
90 .filter(db
.Commit
.upstream
== True)
91 .order_by(db
.Commit
.cdate
.desc()).first())
93 print 'blob %s not found?!' % prefix
95 commits
.append(ret
[1])
100 print 'trying canonical commit %s' % cmt
.sha1
101 applied
= try_patch(session
, m
, pp
, cmt
)
104 # this is just hopeless: it doesn't apply to the commit it should!
107 print "no canonical commit found"
108 # if we have a parent, try on the parent
109 parent
= session
.query(db
.Mail
).filter(db
.Mail
.message_id
==m
.in_reply_to
).first()
110 if parent
and parent
.has_patch
and parent
.patch_id
:
111 cmt
= (session
.query(db
.Commit
)
112 .filter(db
.Commit
.patch_id
==parent
.patch_id
)
113 .order_by(db
.Commit
.cdate
.desc()).first())
114 print 'trying to apply on parent %s' % cmt
.sha1
115 applied
= try_patch(session
, m
, pp
, cmt
)
119 print "no parent commit found"
120 # try on origin/master
121 print 'trying on origin/master'
122 master
= git('rev-parse', 'origin/master')[0].strip()
123 cmt
= session
.query(db
.Commit
).filter(db
.Commit
.sha1
==master
).one()
124 applied
= try_patch(session
, m
, pp
, cmt
)
127 # same for origin/next
128 print 'trying on origin/next'
129 next
= git('rev-parse', 'origin/next')[0].strip()
130 cmt
= session
.query(db
.Commit
).filter(db
.Commit
.sha1
==next
).one()
131 applied
= try_patch(session
, m
, pp
, cmt
)
136 def parse_mail(session
, msg
):
137 if (session
.query(db
.Mail
.message_id
)
138 .filter(db
.Mail
.message_id
== _parse_msg_id(msg
['Message-Id']))
140 return [] # already exists
142 m
.message_id
= _parse_msg_id(msg
['Message-Id'])
143 m
.author
= msg
['From']
144 m
.in_reply_to
= _detect_reply_id(msg
)
145 m
.post_date
= time
.mktime(email
.utils
.parsedate(msg
['Date']))
146 m
.payload
= msg
.as_string()
147 m
.has_patch
= bool(_guess_patch_contents(msg
))
150 if msg
['References']:
151 for im
in _msg_id_regex
.finditer(' '.join(msg
.get_all('References'))):
152 references
.append((m
, im
.group(1)))
153 patch
= try_patch_anywhere(session
, msg
, m
)
155 m
.patch_id
= patch
.commit
.patch_id
159 def parse_mbox(fname
):
160 session
= db
.Session()
161 mbox
= mailbox
.mbox(fname
, parser
.parse
)
162 mbox_parsed
= list(mbox
)
164 for msg
in mbox_parsed
:
165 references
.extend(parse_mail(session
, msg
))
167 for m
, r
in references
:
168 session
.add(db
.Reference(m
.id, r
))
171 if __name__
== '__main__':
172 for mbox
in sys
.argv
[1:]: