def test_github_example_matches( path: str, expected: List[Tuple[Literal["USERNAME", "EMAIL", "TEAM"], str]]) -> None: owners = CodeOwners(EXAMPLE) actual = owners.of(path) assert (actual == expected ), f"mismatch for {path}, expected: {expected}, got: {actual}"
def test_specific_pattern_path_matching(name: str, pattern: str, paths: Dict[str, bool]) -> None: assert paths for path, expected in paths.items(): owners = CodeOwners(f"{pattern} @js-user") matches = owners.of(path) == [("USERNAME", "@js-user")] regex, *_ = owners.paths[0] assert ( matches == expected ), f"""{pattern} {regex} {"matches" if expected else "shouldn't match"} {path}"""
def test_codeowners(pattern, expected): # for some reason the codeowners library does not publish all the wheels # for Mac and Windows. Eventually we could write our own codeowners parser, # but for now it is good enough. If codeowners is not installed, this test # will be skipped try: from codeowners import CodeOwners except: pytest.skip("Skipping as codeowners not installed.") with open(".github/CODEOWNERS") as f: owners = CodeOwners(f.read()) assert set(owners.of(pattern)) == expected
def junit_upload_from_tgz(junit_tgz, codeowners_path=".github/CODEOWNERS"): """ Upload all JUnit XML files contained in given tgz archive. """ from codeowners import CodeOwners with open(codeowners_path) as f: codeowners = CodeOwners(f.read()) with tempfile.TemporaryDirectory() as unpack_dir: # unpack all files from archive with tarfile.open(junit_tgz) as tgz: tgz.extractall(path=unpack_dir) # read additional tags with open(os.path.join(unpack_dir, TAGS_FILE_NAME)) as tf: tags = tf.read().split() # read job url (see comment in produce_junit_tar) with open(os.path.join(unpack_dir, JOB_URL_FILE_NAME)) as jf: job_url = jf.read() # for each unpacked xml file, split it and submit all parts for xmlfile in glob.glob(f"{unpack_dir}/*.xml"): with tempfile.TemporaryDirectory() as output_dir: written_owners = split_junitxml(xmlfile, codeowners, output_dir) upload_junitxmls(output_dir, written_owners, tags, job_url)
async def conclude_reviewer_list(owner: str = None, repo: str = None) -> typing.List[str]: """Conclude on a set of Reviewers (their GitHub user id) that could be assigned to a Pull Request.""" reviewers = [] github_api = None if owner is None or repo is None: return None try: github_api = RUNTIME_CONTEXT.app_installation_client except Exception: access_token = GitHubOAuthToken(os.environ["GITHUB_ACCESS_TOKEN"]) github_api = RawGitHubAPI(access_token, user_agent="sesheta-actions") try: codeowners = await github_api.getitem( f"/repos/{owner}/{repo}/contents/.github/CODEOWNERS") codeowners_content = base64.b64decode( codeowners["content"]).decode("utf-8") code_owner = CodeOwners(codeowners_content) for owner in code_owner.of("."): reviewers.append(owner[1][1:]) # remove the @ except gidgethub.HTTPException as http_exception: # if there is no CODEOWNERS, lets have some sane defaults if http_exception.status_code == 404: if owner.lower() == "thoth-station": reviewers.append("fridex") reviewers.append("pacospace") if "prometheus" in repo.lower(): reviewers.append("4n4nd") reviewers.append("MichaelClifford") if "log-" in repo.lower(): reviewers.append("zmhassan") reviewers.append("4n4nd") else: _LOGGER.error(http_exception) return None except Exception as err: # on any other Error, we can not generate a reviewers list _LOGGER.error(str(err)) return None _LOGGER.debug(f"final reviewers: '{reviewers}'") return reviewers
def test_github_example_matches_with_lines( path: str, expected_path: str, expected_owners: List[Tuple[Literal["USERNAME", "EMAIL", "TEAM"], str]], expected_line_num: int, ) -> None: owners = CodeOwners(EXAMPLE) actual_owners, actual_line_num, actual_path = owners.matching_line(path) assert ( actual_owners == expected_owners ), f"mismatch for {path}, expected: {expected_owners}, got: {actual_owners}" assert ( actual_line_num == expected_line_num ), f"mismatch for {path}, expected linenum: {expected_line_num}, got: {actual_line_num}" assert ( actual_line_num == expected_line_num ), f"mismatch for {path}, expected linenum: {expected_path}, got: {actual_path}"
def main() -> None: args = setup_args() if args.verbose: logger.setLevel(logging.DEBUG) with open(codeowners_filename) as f: codeowners = CodeOwners("\n".join(f.readlines())) covered_files = flatten_directories(get_source_files()) all_files = flatten_directories(ROOT) cache = load_cache(args.cache) teams = get_all_teams(team=args.team) covered = analyze_files(covered_files, codeowners, cache, teams=teams, status="mypy.ini") # If the team has no coverage, then don't bother getting the denominator. teams_with_covered_lines = {t for t in teams if covered.get(t, 0) > 0} not_covered = analyze_files( all_files - covered_files, codeowners, cache, teams=teams_with_covered_lines, status="root" ) store_cache(cache, args.cache) print_results(covered, not_covered, teams)
def read_owners(owners_file): from codeowners import CodeOwners with open(owners_file, 'r') as f: return CodeOwners(f.read())
def main(): app_id = os.getenv('GITHUB_APP_ID') private_key = os.getenv('GITHUB_PRIVATE_KEY').encode('utf-8') organization_name, repository_name = os.getenv('PROJECT').split('/') pull_request_id = os.getenv('PULL_REQUEST_ID') github_app = github.GitHub() github_app.login_as_app(private_key, app_id) app_slug = github_app.authenticated_app().slug github_installation = github.GitHub() github_installation.login_as_app_installation( private_key, app_id, github_app.app_installation_for_repository(organization_name, repository_name).id) organization = github_installation.organization(organization_name) repository = github_installation.repository(organization.login, repository_name) pull_request = github_installation.pull_request(organization.login, repository.name, pull_request_id) # Get reviewers reviewers = set() try: owner_file_contents = repository.file_contents('CODEOWNERS') data = owner_file_contents.decoded.decode('utf-8') code_owners = CodeOwners(data) for f in pull_request.files(): file_reviewers = code_owners.of(f.filename) if file_reviewers is None: continue for reviewer_type, reviewer in file_reviewers: if reviewer_type == 'USERNAME': reviewers.add(reviewer.replace('@', '')) continue raise SystemError("Unknown reviewer_type: %s" % reviewer_type) except: return 0 # Get last commit date commit_dates = [] for c in pull_request.commits(): date = dateutil_parser.parse(c.commit.committer['date']) commit_dates.append(date) last_commit_date = max(commit_dates) if commit_dates else None # Parse comments approvals = set() for comment in pull_request.issue_comments(): # Find the signoffs if comment.created_at > last_commit_date: for line in comment.body.split("\n"): if line.strip() in APPROVAL_ALIASES: approvals.add(comment.user.login) if any([(r in approvals) for r in reviewers]): pull_request.create_review("Pull Request Approved.", list(pull_request.commits())[-1].sha, 'APPROVE') return 0 return 1
def test_rule_missing_owner() -> None: assert CodeOwners("*.js").of("bar.js") == []
def test_unterminated_char_class() -> None: """ Ensure we warn about unterminated character classes """ with pytest.raises(ValueError): CodeOwners("foo[bar.js @js-user")