def git_get_commit_id_from_repo_ref(repo, ref): # We only handle git and http/s URLs if not (repo.find('git://') == 0 or repo.find('http://') == 0 or repo.find('https://') == 0): logger.debug('%s uses unsupported protocol', repo) return None logger.debug('getting commit-id from: %s %s', repo, ref) # Drop the leading "refs/", if any ref = re.sub(r'^refs/', '', ref) # Is it a full ref name or a shortname? if ref.find('heads/') < 0 and ref.find('tags/') < 0: # Try grabbing it as a head first lines = b4.git_get_command_lines(None, ['ls-remote', repo, 'refs/heads/%s' % ref]) if not lines: # try it as a tag, then lines = b4.git_get_command_lines(None, ['ls-remote', repo, 'refs/tags/%s^{}' % ref]) elif ref.find('tags/') == 0: lines = b4.git_get_command_lines(None, ['ls-remote', repo, 'refs/%s^{}' % ref]) else: # Grab it as a head and hope for the best lines = b4.git_get_command_lines(None, ['ls-remote', repo, 'refs/%s' % ref]) if not lines: # Oh well, we tried logger.debug('did not find commit-id, ignoring pull request') return None commit_id = lines[0].split()[0] logger.debug('success, commit-id: %s', commit_id) return commit_id
def get_all_commits(gitdir, branch, since='1.week', committer=None): global MY_COMMITS if MY_COMMITS is not None: return MY_COMMITS MY_COMMITS = dict() if committer is None: usercfg = b4.get_user_config() committer = usercfg['email'] gitargs = ['log', '--committer', committer, '--no-abbrev', '--oneline', '--since', since, branch] lines = b4.git_get_command_lines(gitdir, gitargs) if not len(lines): logger.debug('No new commits from the current user --since=%s', since) return MY_COMMITS logger.info('Found %s of your commits since %s', len(lines), since) logger.info('Calculating patch hashes, may take a moment...') # Get patch hash of each commit for line in lines: commit_id, subject = line.split(maxsplit=1) ecode, out = git_get_rev_diff(gitdir, commit_id) pwhash = b4.LoreMessage.get_patchwork_hash(out) logger.debug('phash=%s', pwhash) # get all message-id or link trailers ecode, out = git_get_commit_message(gitdir, commit_id) matches = re.findall(r'^\s*(?:message-id|link):[ \t]+(\S+)\s*$', out, flags=re.I | re.M) trackers = list() if matches: for tvalue in matches: trackers.append(tvalue) MY_COMMITS[pwhash] = (commit_id, subject, trackers) return MY_COMMITS
def auto_locate_pr(gitdir, jsondata, branch): pr_commit_id = jsondata['pr_commit_id'] logger.debug('Checking %s', jsondata['pr_commit_id']) if not b4.git_commit_exists(gitdir, pr_commit_id): return None onbranches = b4.git_branch_contains(gitdir, pr_commit_id) if not len(onbranches): logger.debug('%s is not on any branches', pr_commit_id) return None if branch not in onbranches: logger.debug('%s is not on branch %s', pr_commit_id, branch) return None # Get the merge commit merge_commit_id = git_get_merge_id(gitdir, pr_commit_id, branch) if not merge_commit_id: logger.debug('Could not get a merge commit-id for %s', pr_commit_id) return None # Check that we are the author of the merge commit gitargs = ['show', '--format=%ae', merge_commit_id] out = b4.git_get_command_lines(gitdir, gitargs) if not out: logger.debug('Could not get merge commit author for %s', pr_commit_id) return None usercfg = b4.get_user_config() if usercfg['email'] not in out: logger.debug('Merged by a different author, ignoring %s', pr_commit_id) logger.debug('Author: %s', out[0]) return None return merge_commit_id
def git_get_merge_id(gitdir, commit_id, branch=None): # get merge commit id args = ['rev-list', '%s..' % commit_id, '--ancestry-path'] if branch is not None: args += [branch] lines = b4.git_get_command_lines(gitdir, args) if not len(lines): return None return lines[-1]
def attest_fetch_head(gitdir, lmsg): config = b4.get_main_config() attpolicy = config['attestation-policy'] if config['attestation-checkmarks'] == 'fancy': attpass = b4.PASS_FANCY attfail = b4.FAIL_FANCY else: attpass = b4.PASS_SIMPLE attfail = b4.FAIL_SIMPLE # Is FETCH_HEAD a tag or a commit? htype = b4.git_get_command_lines(gitdir, ['cat-file', '-t', 'FETCH_HEAD']) passing = False out = '' otype = 'unknown' if len(htype): otype = htype[0] if otype == 'tag': ecode, out = b4.git_run_command(gitdir, ['verify-tag', '--raw', 'FETCH_HEAD'], logstderr=True) elif otype == 'commit': ecode, out = b4.git_run_command(gitdir, ['verify-commit', '--raw', 'FETCH_HEAD'], logstderr=True) lsig = b4.LoreAttestationSignature(out, 'git') if lsig.good and lsig.valid and lsig.trusted: passing = True out = out.strip() if not len(out) and attpolicy != 'check': lsig.errors.add('Remote %s is not signed!' % otype) if passing: trailer = lsig.attestor.get_trailer(lmsg.fromemail) logger.info(' ---') logger.info(' %s %s', attpass, trailer) return if lsig.errors: logger.critical(' ---') if len(out): logger.critical(' Pull request is signed, but verification did not succeed:') else: logger.critical(' Pull request verification did not succeed:') for error in lsig.errors: logger.critical(' %s %s', attfail, error) if attpolicy == 'hardfail': import sys sys.exit(128)
def get_branch_info(gitdir, branch): global BRANCH_INFO if BRANCH_INFO is not None: return BRANCH_INFO BRANCH_INFO = dict() remotecfg = b4.get_config_from_git('branch\\.%s\\.*' % branch) if remotecfg is None or 'remote' not in remotecfg: # Did not find a matching branch entry, so look at remotes gitargs = ['remote', 'show'] lines = b4.git_get_command_lines(gitdir, gitargs) if not len(lines): # No remotes? Hmm... return BRANCH_INFO remote = None for entry in lines: if branch.find(f'{entry}/') == 0: remote = entry break if remote is None: # Not found any matching remotes return BRANCH_INFO BRANCH_INFO['remote'] = remote BRANCH_INFO['branch'] = branch.replace(f'{remote}/', '') else: BRANCH_INFO['remote'] = remotecfg['remote'] if 'merge' in remotecfg: BRANCH_INFO['branch'] = re.sub(r'^refs/heads/', '', remotecfg['merge']) # Grab template overrides remotecfg = b4.get_config_from_git('remote\\.%s\\..*' % BRANCH_INFO['remote']) BRANCH_INFO.update(remotecfg) return BRANCH_INFO
def get_wanted_branch(cmdargs): global BRANCH_INFO gitdir = cmdargs.gitdir if not cmdargs.branch: # Find out our current branch gitargs = ['symbolic-ref', '-q', 'HEAD'] ecode, out = b4.git_run_command(gitdir, gitargs) if ecode > 0: logger.critical('Not able to get current branch (git symbolic-ref HEAD)') sys.exit(1) wantbranch = re.sub(r'^refs/heads/', '', out.strip()) logger.debug('will check branch=%s', wantbranch) else: # Make sure it's a real branch gitargs = ['branch', '--format=%(refname)', '--list', '--all', cmdargs.branch] lines = b4.git_get_command_lines(gitdir, gitargs) if not len(lines): logger.critical('Requested branch not found in git branch --list --all %s', cmdargs.branch) sys.exit(1) wantbranch = cmdargs.branch return wantbranch
def main(cmdargs): gitdir = cmdargs.gitdir msgid = b4.get_msgid(cmdargs) savefile = mkstemp()[1] mboxfile = b4.get_pi_thread_by_msgid(msgid, savefile) if mboxfile is None: os.unlink(savefile) return # Find the message with the msgid we were asked about mbx = mailbox.mbox(mboxfile) lmsg = None for msg in mbx: mmsgid = b4.LoreMessage.get_clean_msgid(msg) if mmsgid == msgid: lmsg = parse_pr_data(msg) # Got all we need from it mbx.close() os.unlink(savefile) if lmsg is None or lmsg.pr_remote_tip_commit is None: logger.critical('ERROR: Could not find pull request info in %s', msgid) sys.exit(1) if not lmsg.pr_tip_commit: lmsg.pr_tip_commit = lmsg.pr_remote_tip_commit if cmdargs.explode: savefile = cmdargs.outmbox if savefile is None: savefile = '%s.mbx' % lmsg.msgid if os.path.exists(savefile): logger.info('File exists: %s', savefile) sys.exit(1) explode(gitdir, lmsg, savefile) exists = b4.git_commit_exists(gitdir, lmsg.pr_tip_commit) if exists: # Is it in any branch, or just flapping in the wind? branches = b4.git_branch_contains(gitdir, lmsg.pr_tip_commit) if len(branches): logger.info('Pull request tip commit exists in the following branches:') for branch in branches: logger.info(' %s', branch) if cmdargs.check: sys.exit(0) sys.exit(1) # Is it at the tip of FETCH_HEAD? loglines = b4.git_get_command_lines(gitdir, ['log', '-1', '--pretty=oneline', 'FETCH_HEAD']) if len(loglines) and loglines[0].find(lmsg.pr_tip_commit) == 0: logger.info('Pull request is at the tip of FETCH_HEAD') if cmdargs.check: attest_fetch_head(gitdir, lmsg) sys.exit(0) elif cmdargs.check: logger.info('Pull request does not appear to be in this tree.') sys.exit(0) fetch_remote(gitdir, lmsg, branch=cmdargs.branch)
def mbox_to_am(mboxfile, cmdargs): config = b4.get_main_config() outdir = cmdargs.outdir if outdir == '-': cmdargs.nocover = True wantver = cmdargs.wantver wantname = cmdargs.wantname covertrailers = cmdargs.covertrailers if os.path.isdir(mboxfile): mbx = mailbox.Maildir(mboxfile) else: mbx = mailbox.mbox(mboxfile) count = len(mbx) logger.info('Analyzing %s messages in the thread', count) lmbx = b4.LoreMailbox() # Go through the mbox once to populate base series for key, msg in mbx.items(): lmbx.add_message(msg) lser = lmbx.get_series(revision=wantver, sloppytrailers=cmdargs.sloppytrailers) if lser is None and wantver is None: logger.critical('No patches found.') return if lser is None: logger.critical('Unable to find revision %s', wantver) return if len(lmbx.series) > 1 and not wantver: logger.info('Will use the latest revision: v%s', lser.revision) logger.info('You can pick other revisions using the -vN flag') if wantname: slug = wantname if wantname.find('.') > -1: slug = '.'.join(wantname.split('.')[:-1]) gitbranch = slug else: slug = lser.get_slug(extended=True) gitbranch = lser.get_slug(extended=False) if outdir != '-': am_filename = os.path.join(outdir, '%s.mbx' % slug) am_cover = os.path.join(outdir, '%s.cover' % slug) if os.path.exists(am_filename): os.unlink(am_filename) else: # Create a temporary file that we will remove later am_filename = mkstemp('b4-am-stdout')[1] am_cover = None logger.info('---') if cmdargs.cherrypick: cherrypick = list() if cmdargs.cherrypick == '_': msgid = b4.get_msgid(cmdargs) # Only grab the exact msgid provided at = 0 for lmsg in lser.patches[1:]: at += 1 if lmsg and lmsg.msgid == msgid: cherrypick = [at] cmdargs.cherrypick = f'<{msgid}>' break if not len(cherrypick): logger.critical( 'Specified msgid is not present in the series, cannot cherrypick' ) sys.exit(1) elif cmdargs.cherrypick.find('*') >= 0: # Globbing on subject at = 0 for lmsg in lser.patches[1:]: at += 1 if fnmatch.fnmatch(lmsg.subject, cmdargs.cherrypick): cherrypick.append(at) if not len(cherrypick): logger.critical( 'Could not match "%s" to any subjects in the series', cmdargs.cherrypick) sys.exit(1) else: cherrypick = list( b4.parse_int_range(cmdargs.cherrypick, upper=len(lser.patches) - 1)) else: cherrypick = None logger.critical('Writing %s', am_filename) mbx = mailbox.mbox(am_filename) try: am_mbx = lser.save_am_mbox(mbx, noaddtrailers=cmdargs.noaddtrailers, covertrailers=covertrailers, trailer_order=config['trailer-order'], addmysob=cmdargs.addmysob, addlink=cmdargs.addlink, linkmask=config['linkmask'], cherrypick=cherrypick) except KeyError: sys.exit(1) logger.info('---') if cherrypick is None: logger.critical('Total patches: %s', len(am_mbx)) else: logger.info('Total patches: %s (cherrypicked: %s)', len(am_mbx), cmdargs.cherrypick) if lser.has_cover and lser.patches[ 0].followup_trailers and not covertrailers: # Warn that some trailers were sent to the cover letter logger.critical('---') logger.critical('NOTE: Some trailers were sent to the cover letter:') for trailer in lser.patches[0].followup_trailers: logger.critical(' %s: %s', trailer[0], trailer[1]) logger.critical('NOTE: Rerun with -t to apply them to all patches') if len(lser.trailer_mismatches): logger.critical('---') logger.critical( 'NOTE: some trailers ignored due to from/email mismatches:') for tname, tvalue, fname, femail in lser.trailer_mismatches: logger.critical(' ! Trailer: %s: %s', tname, tvalue) logger.critical(' Msg From: %s <%s>', fname, femail) logger.critical('NOTE: Rerun with -S to apply them anyway') topdir = None # Are we in a git tree and if so, what is our toplevel? gitargs = ['rev-parse', '--show-toplevel'] lines = b4.git_get_command_lines(None, gitargs) if len(lines) == 1: topdir = lines[0] if cmdargs.threeway: if not topdir: logger.critical('WARNING: cannot prepare 3-way (not in a git dir)') elif not lser.complete: logger.critical( 'WARNING: cannot prepare 3-way (series incomplete)') else: rstart, rend = lser.make_fake_am_range(gitdir=None) if rstart and rend: logger.info( 'Prepared a fake commit range for 3-way merge (%.12s..%.12s)', rstart, rend) logger.critical('---') if not lser.complete and not cmdargs.cherrypick: logger.critical('WARNING: Thread incomplete!') if lser.has_cover and not cmdargs.nocover: lser.save_cover(am_cover) top_msgid = None first_body = None for lmsg in lser.patches: if lmsg is not None: first_body = lmsg.body top_msgid = lmsg.msgid break if top_msgid is None: logger.critical('Could not find any patches in the series.') return linkurl = config['linkmask'] % top_msgid if cmdargs.quiltready: q_dirname = os.path.join(outdir, '%s.patches' % slug) am_mbox_to_quilt(am_mbx, q_dirname) logger.critical('Quilt: %s', q_dirname) logger.critical(' Link: %s', linkurl) base_commit = None matches = re.search(r'base-commit: .*?([0-9a-f]+)', first_body, re.MULTILINE) if matches: base_commit = matches.groups()[0] else: # Try a more relaxed search matches = re.search(r'based on .*?([0-9a-f]{40})', first_body, re.MULTILINE) if matches: base_commit = matches.groups()[0] if base_commit: logger.critical(' Base: %s', base_commit) logger.critical(' git checkout -b %s %s', gitbranch, base_commit) if cmdargs.outdir != '-': logger.critical(' git am %s', am_filename) else: cleanmsg = '' if topdir is not None: checked, mismatches = lser.check_applies_clean(topdir) if mismatches == 0 and checked != mismatches: cleanmsg = ' (applies clean to current tree)' elif cmdargs.guessbase: # Look at the last 10 tags and see if it applies cleanly to # any of them. I'm not sure how useful this is, but I'm going # to put it in for now and maybe remove later if it causes # problems or slowness if checked != mismatches: best_matches = mismatches cleanmsg = ' (best guess: current tree)' else: best_matches = None # sort the tags by authordate gitargs = ['tag', '-l', '--sort=-taggerdate'] lines = b4.git_get_command_lines(None, gitargs) if lines: # Check last 10 tags for tag in lines[:10]: logger.debug('Checking base-commit possibility for %s', tag) checked, mismatches = lser.check_applies_clean( topdir, tag) if mismatches == 0 and checked != mismatches: cleanmsg = ' (applies clean to: %s)' % tag break # did they all mismatch? if checked == mismatches: continue if best_matches is None or mismatches < best_matches: best_matches = mismatches cleanmsg = ' (best guess: %s)' % tag logger.critical(' Base: not found%s', cleanmsg) if cmdargs.outdir != '-': logger.critical(' git am %s', am_filename) am_mbx.close() if cmdargs.outdir == '-': logger.info('---') with open(am_filename, 'rb') as fh: shutil.copyfileobj(fh, sys.stdout.buffer) os.unlink(am_filename) thanks_record_am(lser, cherrypick=cherrypick)