def build_patch(commits, merged_heads, msg, trees, leader=False): patch = {} sub = message.decode_subject(msg) stripped_subject = sub['subject'] if sub.has_key('pull-request') and sub['pull-request']: parts = msg.get_message_parts() patch['pull-request'] = {} extract_repo = False for line in parts[0].get_payload().split('\n'): stripped_line = line.strip() if stripped_line == 'are available in the git repository at:': extract_repo = True elif extract_repo and stripped_line: extract_repo = False try: uri, refspec = stripped_line.split(' ', 1) except ValueError: continue # not a pull refspec patch['pull-request']['uri'] = uri patch['pull-request']['refspec'] = refspec elif line.startswith('for you to fetch changes up to '): patch['pull-request']['head'] = line.rsplit(' ', 1)[1][:-1] elif line.startswith('---'): break if 'head' in patch['pull-request'] and 'uri' in patch['pull-request']: if patch['pull-request']['head'] in merged_heads: patch['pull-request']['commit'] = merged_heads[patch['pull-request']['head']] if sub['n'] == 0: # Patch 0/M is the cover letter patch['cover'] = True if leader and is_leader_obsolete(stripped_subject, sub['version'], msg.get_date()): # If this is older than a version we've seen, the whole series is # obsolete. We only look at the thread leader which is either the # cover letter or the very first patch. patch['obsolete'] = True elif commits.has_key(stripped_subject): # If there are multiple commits that have this subject, just pick # the first one. c = commits[stripped_subject] if type(c) == list: c = c[0] patch['commit'] = c['hexsha'] patch['tree'] = c['branch'] patch['url'] = trees[c['branch']] % patch['commit'] patch['committer'] = c['committer'] patch['tags'], patch['to'], patch['cc'] = message.find_extra_tags(msg, leader) patch['subject'] = message.get_subject(msg) patch['message-id'] = msg.get_message_id() if sub['rfc']: patch['rfc'] = sub['rfc'] if sub.has_key('for-release'): patch['for-release'] = sub['for-release'] if sub.has_key('tags'): patch['subject-tags'] = sub['tags'] patch['from'] = message.parse_email_address(message.get_header(msg, 'From')) d = datetime.date.fromtimestamp(msg.get_date()) patch['date'] = d.strftime('%Y-%m-%d') patch['full_date'] = msg.get_date() return patch
def build_patch(commits, merged_heads, msg, trees, leader=False): patch = {} sub = message.decode_subject(msg) stripped_subject = sub['subject'] if sub.has_key('pull-request') and sub['pull-request']: parts = msg.get_message_parts() patch['pull-request'] = {} extract_repo = False for line in parts[0].get_payload().split('\n'): stripped_line = line.strip() if stripped_line == 'are available in the git repository at:': extract_repo = True elif extract_repo and stripped_line: extract_repo = False try: uri, refspec = stripped_line.split(' ', 1) except ValueError: continue # not a pull refspec patch['pull-request']['uri'] = uri patch['pull-request']['refspec'] = refspec elif line.startswith('for you to fetch changes up to '): patch['pull-request']['head'] = line.rsplit(' ', 1)[1][:-1] elif line.startswith('---'): break if 'head' in patch['pull-request'] and 'uri' in patch['pull-request']: if patch['pull-request']['head'] in merged_heads: patch['pull-request']['commit'] = merged_heads[ patch['pull-request']['head']] if sub['n'] == 0: # Patch 0/M is the cover letter patch['cover'] = True if leader and is_leader_obsolete(stripped_subject, sub['version'], msg.get_date()): # If this is older than a version we've seen, the whole series is # obsolete. We only look at the thread leader which is either the # cover letter or the very first patch. patch['obsolete'] = True elif commits.has_key(stripped_subject): # If there are multiple commits that have this subject, just pick # the first one. c = commits[stripped_subject] if type(c) == list: c = c[0] patch['commit'] = c['hexsha'] patch['tree'] = c['branch'] patch['url'] = trees[c['branch']] % patch['commit'] patch['committer'] = c['committer'] patch['tags'], patch['to'], patch['cc'] = message.find_extra_tags( msg, leader) patch['subject'] = message.get_subject(msg) patch['message-id'] = msg.get_message_id() if sub['rfc']: patch['rfc'] = sub['rfc'] if sub.has_key('for-release'): patch['for-release'] = sub['for-release'] if sub.has_key('tags'): patch['subject-tags'] = sub['tags'] patch['from'] = message.parse_email_address(message.get_header( msg, 'From')) d = datetime.date.fromtimestamp(msg.get_date()) patch['date'] = d.strftime('%Y-%m-%d') patch['full_date'] = msg.get_date() return patch
def build_patches(notmuch_dir, search_days, mail_query, trees): db = notmuch.Database(notmuch_dir) now = long(time()) then = now - days_to_seconds(search_days) query = '%s (subject:PATCH or subject:PULL) %s..%s' % (mail_query, then, now) q = notmuch.Query(db, query) oldest = build_thread_leaders(q, then) # A pull request may contain patches older than the posted commits. That's # because a commit doesn't happen *after* the post like what normally # happens with a patch but rather the post happens after the commit. # There's no obvious way to handle this other than the hack below. # Give some extra time for pull request commits oldest -= (30 * 24 * 60 * 60) commits = gitcmd.get_commits(oldest, trees) merged_heads = gitcmd.get_merges(oldest) mbox.setup_mboxes() patches = [] for thread in q.search_threads(): try: top = list(thread.get_toplevel_messages())[0] except notmuch.errors.NullPointerError: continue if not message.is_patch(top): continue # The parser chokes on emails too often, simply report the error and # skip the thread so that scan can complete. try: patch = build_patch(commits, merged_heads, top, trees, leader=True) except: import traceback import sys sys.stderr.write('Message-Id: %s\n' % top.get_message_id()) traceback.print_exc() continue patch_list = [ patch ] message_list = [] for reply in top.get_replies(): # notmuch won't let us call get_replies twice so we have to do # everything in a single loop. # any first level replies are replies to the top level post. if not message.is_patch(reply): new_tags, to, cc = message.find_extra_tags(reply, False) patch_list[0]['tags'] = message.merge_tags(patch_list[0]['tags'], new_tags) patch_list[0]['to'] = message.dedup(patch_list[0]['to'] + to) patch_list[0]['cc'] = message.dedup(patch_list[0]['cc'] + cc) if message.is_thanks_applied(reply): patch_list[0]['applied-by'] = message.parse_email_address(message.get_header(reply, 'From')) else: patch = build_patch(commits, merged_heads, reply, trees) patch_list.append(patch) message_list.append((reply, patch['tags'])) # now we're done with replies so tags for the top patch are known if not message.is_cover(patch_list[0]): message_list.insert(0, (top, patch_list[0]['tags'])) series = { 'messages': patch_list, 'total_messages': thread.get_total_messages() } if series_.is_pull_request(series): series = fixup_pull_request(series, merged_heads) message_list.sort(message.cmp_patch) m = message.parse_subject(top)[1] if len(message_list) != m: series['broken'] = True if (not series_.is_broken(series) and not series_.is_obsolete(series) and not series_.any_committed(series) and not series_.is_pull_request(series) and not series_.is_applied(series)): if message.is_cover(series['messages'][0]): tags = series['messages'][0]['tags'] else: tags = {} series['mbox_path'] = mbox.generate_mbox(message_list, tags) series['mbox_hash'] = mbox.get_hash(series['mbox_path']) patches.append(series) return patches
def build_patches(notmuch_dir, search_days, mail_query, trees): db = notmuch.Database(notmuch_dir) now = long(time()) then = now - days_to_seconds(search_days) query = '%s (subject:PATCH or subject:PULL) %s..%s' % (mail_query, then, now) q = notmuch.Query(db, query) oldest = build_thread_leaders(q, then) # A pull request may contain patches older than the posted commits. That's # because a commit doesn't happen *after* the post like what normally # happens with a patch but rather the post happens after the commit. # There's no obvious way to handle this other than the hack below. # Give some extra time for pull request commits oldest -= (30 * 24 * 60 * 60) commits = gitcmd.get_commits(oldest, trees) merged_heads = gitcmd.get_merges(oldest) mbox.setup_mboxes() patches = [] for thread in q.search_threads(): try: top = list(thread.get_toplevel_messages())[0] except notmuch.errors.NullPointerError: continue if not message.is_patch(top): continue # The parser chokes on emails too often, simply report the error and # skip the thread so that scan can complete. try: patch = build_patch(commits, merged_heads, top, trees, leader=True) except: import traceback import sys sys.stderr.write('Message-Id: %s\n' % top.get_message_id()) traceback.print_exc() continue patch_list = [patch] message_list = [] for reply in top.get_replies(): # notmuch won't let us call get_replies twice so we have to do # everything in a single loop. # any first level replies are replies to the top level post. if not message.is_patch(reply): new_tags, to, cc = message.find_extra_tags(reply, False) patch_list[0]['tags'] = message.merge_tags( patch_list[0]['tags'], new_tags) patch_list[0]['to'] = message.dedup(patch_list[0]['to'] + to) patch_list[0]['cc'] = message.dedup(patch_list[0]['cc'] + cc) if message.is_thanks_applied(reply): patch_list[0]['applied-by'] = message.parse_email_address( message.get_header(reply, 'From')) else: patch = build_patch(commits, merged_heads, reply, trees) patch_list.append(patch) message_list.append((reply, patch['tags'])) # now we're done with replies so tags for the top patch are known if not message.is_cover(patch_list[0]): message_list.insert(0, (top, patch_list[0]['tags'])) series = { 'messages': patch_list, 'total_messages': thread.get_total_messages() } if series_.is_pull_request(series): series = fixup_pull_request(series, merged_heads) message_list.sort(message.cmp_patch) m = message.parse_subject(top)[1] if len(message_list) != m: series['broken'] = True if (not series_.is_broken(series) and not series_.is_obsolete(series) and not series_.any_committed(series) and not series_.is_pull_request(series) and not series_.is_applied(series)): if message.is_cover(series['messages'][0]): tags = series['messages'][0]['tags'] else: tags = {} series['mbox_path'] = mbox.generate_mbox(message_list, tags) series['mbox_hash'] = mbox.get_hash(series['mbox_path']) patches.append(series) return patches