def apply_admin_change(change: Change[Admin], org: Organization) -> Change[Admin]: if change.action not in [ChangeActions.ADD, ChangeActions.REMOVE]: print_warning("Unsupported change action for org admins: %s" % change.action) return change.skipped() from ghconf.github import gh if change.action == ChangeActions.ADD and change.after is not None: try: user = gh.get_user(change.after.username) org.add_to_members(user, role="admin") except GithubException as e: print_debug("Unable to add admin user %s: %s" % (change.after.username, str(e))) return change.failure() elif change.action == ChangeActions.REMOVE and change.before is not None: try: user = gh.get_user(change.before.username) org.remove_from_members(user) except GithubException as e: print_debug("Unable to remove admin user %s: %s" % (change.before.username, str(e))) return change.failure() return change.success()
def apply_config(self, config: Dict[Any, Any], org: Organization, repo: Repository, repo_branches: List[Branch]) -> List[ChangeSet]: ret = [] if "repo_procs" in config: branches = {branch.name: branch for branch in repo_branches} changes = [] # type: List[Change[str]] for proc in config["repo_procs"]: # type: repoproc_t changes += proc(org, repo, branches) if changes: cs = ChangeSet( "Repo {name}: Procedures".format(name=repo.name), changes=changes, ) ret.append(cs) if "access" in config: print_debug("Building *access* changes for repo %s" % repo.name) if org.name not in self._org_accessfactories: # poor man's caching for AccessChangeFactory.org_teams self._org_accessfactories[org.name] = AccessChangeFactory(org) cs = ChangeSet( "Repo {name}: Access".format(name=repo.name), self._org_accessfactories[org.name].diff_team_access( repo, config["access"]), description="Changes to team permissions") ret.append(cs) return ret
def execute_dismiss_reviews( change: Change[str], branch: Branch, required_reviews: Optional[RequiredPullRequestReviews], dismiss_approvals: bool) -> Change[str]: try: if branch.protected and required_reviews: print_debug( "Setting already protected branch %s to %s stale reviews" % (highlight(branch.name), highlight("dismiss" if dismiss_approvals else "allow"))) branch.edit_required_pull_request_reviews( dismiss_stale_reviews=dismiss_approvals) else: print_debug( "Changing branch %s to %s stale reviews" % (highlight(branch.name), highlight("dismiss" if dismiss_approvals else "allow"))) safe_branch_edit_protection( branch, dismiss_stale_reviews=dismiss_approvals) except GithubException as e: print_error( "Can't set review dismissal on branch %s to %s: %s" % (highlight(branch.name), str(dismiss_approvals), str(e))) return change.failure() return change.success()
def _set_dismiss_stale_approvals(branch: Branch, dismiss_approvals: bool = True ) -> List[Change[str]]: def execute_dismiss_reviews( change: Change[str], branch: Branch, required_reviews: Optional[RequiredPullRequestReviews], dismiss_approvals: bool) -> Change[str]: try: if branch.protected and required_reviews: print_debug( "Setting already protected branch %s to %s stale reviews" % (highlight(branch.name), highlight("dismiss" if dismiss_approvals else "allow"))) branch.edit_required_pull_request_reviews( dismiss_stale_reviews=dismiss_approvals) else: print_debug( "Changing branch %s to %s stale reviews" % (highlight(branch.name), highlight("dismiss" if dismiss_approvals else "allow"))) safe_branch_edit_protection( branch, dismiss_stale_reviews=dismiss_approvals) except GithubException as e: print_error( "Can't set review dismissal on branch %s to %s: %s" % (highlight(branch.name), str(dismiss_approvals), str(e))) return change.failure() return change.success() change_needed = False rpr = None # type: Optional[RequiredPullRequestReviews] if branch.protected: prot = branch.get_protection() rpr = prot.required_pull_request_reviews if rpr.dismiss_stale_reviews == dismiss_approvals: print_debug( "Branch %s already %s stale reviews" % (highlight(branch.name), highlight("dismisses" if dismiss_approvals else "allows"))) change_needed = False else: change_needed = True else: change_needed = True if change_needed: change = Change(meta=ChangeMetadata( executor=execute_dismiss_reviews, params=[branch, rpr, dismiss_approvals]), action=ChangeActions.REPLACE if branch.protected else ChangeActions.ADD, before="%s stale reviews" % ("Allow" if dismiss_approvals else "Dismiss"), after="%s stale reviews" % ("Dismiss" if dismiss_approvals else "Allow"), cosmetic_prefix="Protect branch<%s>" % branch.name) return [change] return []
def __execute_master_default(change: Change[str], repo: Repository) -> Change[str]: print_debug("[%s] Enforcing master as the default branch" % highlight(repo.name)) try: repo.edit(default_branch="master") except GithubException: return change.failure() return change.success()
def execute_develop_default(change: Change[str], repo: Repository) -> Change[str]: print_debug("[%s] Changing default branch to 'develop'" % highlight(repo.name)) try: repo.edit(default_branch="develop") except GithubException: return change.failure() return change.success()
def __eq__(self, other: object) -> bool: if other is None or not isinstance(other, self.__class__): print_debug("Comparison with different class (Team vs. {})".format( other.__class__)) return False # it's by design that this doesn't compare attributes like default_permission and privacy as these # attributes are compared separately and they don't distinguish two Team instances to the point # of being entirely different teams metadata_eq = self.name == other.name and self.slug == other.slug return metadata_eq
def execute_remove_all_status_checks( change: Change[str], branch: Branch, existing_checks: Set[str]) -> Change[str]: print_debug("Removing all status checks from branch %s" % highlight(branch.name)) try: if existing_checks: branch.remove_required_status_checks() except GithubException as e: print_error(str(e)) return change.failure() else: return change.success()
def init_github(github_token: str, dry_run: bool = False, *args: Any, **kwargs: Any) -> None: global gh if not gh: print_debug("Initializing Github instance") gh = Github(github_token, *args, **kwargs) checked_weave(gh, handle_rate_limits) if dry_run: checked_weave(gh, enforce_dryrun) checked_weave(gh, retry_on_server_failure) checked_weave(gh, create_recursive_weave_aspect(dry_run))
def build_repository_changesets( self, org: Organization, repo: Repository, repo_branches: List[Branch]) -> List[ChangeSet]: ret = [] # type: List[ChangeSet] matched = False for pattern in self.config.keys(): if pattern.match(repo.name) is not None: matched = True print_debug("Repository %s matched by config key %s" % (highlight(repo.name), highlight(pattern.pattern))) ret += self.apply_config(self.config[pattern], org, repo, repo_branches) if not matched and self.default_config: print_debug("Applying %s to repository %s" % (highlight("default config"), highlight(repo.name))) ret += self.apply_config(self.default_config, org, repo, repo_branches) return ret
def force_master_default_if_no_develop( org: Organization, repo: Repository, branches: Dict[str, Branch]) -> List[Change[str]]: if repo.default_branch != 'develop' and repo.default_branch != 'master' and not repo.archived: if "develop" not in branches and "master" in branches: change = Change(meta=ChangeMetadata( executor=__execute_master_default, params=[repo], ), action=ChangeActions.REPLACE, before=repo.default_branch, after="master", cosmetic_prefix="Default:") return [change] elif "develop" not in branches and "master" not in branches: print_debug("Repo %s has neither 'develop' or 'master'" % repo.name) return [] else: return [] return []
def apply_member_change(change: Change[BaseMember], org: Organization, team: Team) -> Change[BaseMember]: if change.action not in [ChangeActions.ADD, ChangeActions.REMOVE]: print_debug("Unsupported change action for team members: %s" % change.action) return change.skipped() from ghconf.github import gh if change.action == ChangeActions.ADD and change.after is not None: ghteam = org.get_team(team.id) ghmember = gh.get_user(change.after.username) membership = ghteam.add_membership(ghmember, role=change.after.role) if membership.state == "active": print_debug("Added member %s to team %s" % (change.after.username, team.name)) return change.success() else: print_debug( "Adding member %s to team %s failed with state %s" % (change.after.username, team.name, membership.state)) return change.failure() elif change.action == ChangeActions.REMOVE and change.before is not None: ghteam = org.get_team(team.id) ghmember = gh.get_user(change.before.username) if ghteam.remove_membership(ghmember): return change.success() else: return change.failure() return change.success()
def execute_review_protection( change: Change[str], branch: Branch, existing_protection: Optional[BranchProtection], review_count: int) -> Change[str]: try: if branch.protected and existing_protection and existing_protection.required_pull_request_reviews: if review_count > 0: print_debug( "Replacing review protection on branch %s (%s reviews)" % (highlight(branch.name), str(review_count))) branch.edit_required_pull_request_reviews( required_approving_review_count=review_count) else: print_debug("Removing review protection on branch: %s" % highlight(branch.name)) branch.remove_required_pull_request_reviews() elif review_count > 0: print_debug( "Adding review protection on branch: %s (%s reviews)" % (highlight(branch.name), str(review_count))) safe_branch_edit_protection( branch, required_approving_review_count=review_count) except GithubException as e: print_error("Can't set review protection on branch %s to %s: %s" % (highlight(branch.name), str(review_count), str(e))) return change.failure() return change.success()
def execute_test_protection(change: Change[str], branch: Branch, existing_checks: Set[str], known_checks: Set[str]) -> Change[str]: print_debug("[%s] Changing status checks on branch '%s' to [%s]" % (highlight(repo.name), highlight(branch.name), highlight(", ".join(list(known_checks))))) try: if existing_checks: branch.edit_required_status_checks(strict=True, contexts=list(known_checks)) else: safe_branch_edit_protection( branch, strict=True, contexts=list(known_checks), ) except GithubException as e: print_error( "Can't edit required status checks on repo %s branch %s: %s" % (repo.name, branch.name, str(e))) return change.failure() return change.success()
def execute_set_repo_features( change: Change[str], repo: Repository, enable_wiki: Optional[bool] = None, enable_issues: Optional[bool] = None, enable_projects: Optional[bool] = None) -> Change[str]: if change.action == ChangeActions.REPLACE: print_debug("[%s] Setting features" % highlight(repo.name)) kw = { 'has_wiki': NotSet if enable_wiki is None else enable_wiki, 'has_issues': NotSet if enable_issues is None else enable_issues, 'has_projects': NotSet if enable_projects is None else enable_projects } try: repo.edit(**kw) except GithubException: return change.failure() return change.success()
def apply_team_access(self, change: Change[str], repo: Repository, role: str) -> Change[str]: if change.action not in [ChangeActions.ADD, ChangeActions.REMOVE]: return change.skipped() if change.action == ChangeActions.REMOVE and change.before is not None: if change.before in self.org_teams: try: self.org_teams[change.before].remove_from_repos(repo) except GithubException as e: print_error("Can't remove team %s from repo %s (%s)" % (highlight(change.before), highlight( repo.name), str(e))) return change.failure() return change.success() else: # the team was probably removed by another change print_debug("Unknown team %s to remove from repo %s" % (highlight(change.before), highlight(repo.name))) return change.success() elif change.action == ChangeActions.ADD and change.after is not None: if change.after in self.org_teams: try: self.org_teams[change.after].set_repo_permission( repo, role) except GithubException as e: print_error( "Can't set permission %s for team %s on repo %s (%s)" % (highlight(role), highlight(change.after), highlight(repo.name), highlight(str(e)))) return change.failure() return change.success() else: print_error("Unknown team %s to add to repo %s" % (highlight(change.after), highlight(repo.name))) return change.failure() return change.success()
def apply_team_change(change: Change[Team], org: Organization) -> Change[Team]: if change.action not in [ChangeActions.ADD, ChangeActions.REMOVE]: print_warning("Unsupported change action for teams: %s" % change.action) return change.skipped() if change.action == ChangeActions.ADD and change.after is not None: to_create = change.after # type: Team if not isinstance(to_create, Team): raise ErrorMessage("Create action without team to create") created = org.create_team(to_create.name, permission=to_create.default_permission, privacy=to_create.privacy) created.edit(created.name, description=to_create.description) to_create.id = created.id return change.success() elif change.action == ChangeActions.REMOVE and change.before is not None: try: print_debug("Retrieving team id %s for deletion" % highlight(str(change.before.id))) to_delete = org.get_team(change.before.id) # type: GithubTeam except GithubException: # if the team is already gone... ok return change.success() try: print_debug("Deleting team id %s" % highlight(str(change.before.id))) to_delete.delete() except GithubException as e: print_error("Can't delete team id %s: %s" % (highlight(str(change.before.id)), str(e))) return change.failure() change.before.id = None return change.success()
def assemble_changedict(args: Namespace, org: Organization) -> Dict[str, ChangeSet]: changedict = {} if args.skip_org_changes: print_warning("Skipping org changes (as per --no-org-changes)") else: pbar = None if utils.enable_progressbar: pbar = progressbar(len(modules)) for modulename, moduledef in modules.items( ): # type: str, GHConfModuleDef if utils.enable_progressbar and pbar: pbar.update() try: print_info("Building org changeset for %s" % modulename) cslist = moduledef.build_organization_changesets(org) for cs in cslist: changedict.update(cs.todict()) except NotImplementedError: print_debug( "%s does not support creating an organization changeset. It might not modify the " "org at all or it might just not report it." % utils.highlight(modulename)) if pbar: pbar.close() capcache = {} # type: Dict[str, bool] repolist = assemble_repolist(args, org) pbar = None repocount = len(repolist) repofmt = "{{ix:>{len}}}/{count} Processing repo {{repo}}".format( len=len(str(repocount)), count=str(repocount)) if utils.enable_progressbar: pbar = progressbar(repocount) for ix, repo in enumerate(repolist): if utils.enable_progressbar: pbar.update() if utils.enable_verbose_output: print_info(repofmt.format(ix=ix, repo=repo.full_name)) branches = list(repo.get_branches()) for modulename, moduledef in modules.items(): if not capcache.get(modulename, True): print_debug( "Capability cache for module %s indicates no support for repos" % modulename) continue try: print_debug("Building repo changeset for %s => %s" % (modulename, repo.name)) cslist = moduledef.build_repository_changesets( org, repo, branches) for cs in cslist: changedict.update(cs.todict()) except NotImplementedError: print_debug( "%s does not support creating a repo changeset for repo %s. It might just not " "make any modifications at all or it might not report them." % (utils.highlight(modulename), utils.highlight(repo.name))) capcache[modulename] = False continue pbar.close() return changedict
def main() -> None: module_parser = ArgumentParser(add_help=False) module_parser.add_argument("-m", "--module", action="append", dest="modules", default=[], help=SUPPRESS) module_parser.add_argument("--debug", action="store_true", dest="debug", default=False) module_parser.add_argument("--no-color", action="store_true", dest="no_color", default=False) preargs, _ = module_parser.parse_known_args() utils.enable_debug_output = preargs.debug utils.init_color(preargs.no_color) if preargs.modules: import importlib for module in preargs.modules: # type: str if ":" in module: module, entrypoint_name = module.split(":", 1) else: entrypoint_name = "entry_point" try: print_debug("Loading module %s:%s" % (module, entrypoint_name)) mod = importlib.import_module(module) if hasattr(mod, entrypoint_name): entrypoint = getattr(mod, entrypoint_name) try: i = iter(entrypoint) mods = entrypoint except TypeError: mods = [entrypoint] for ep in mods: if isinstance(ep, type): try: modules["%s::%s" % (module, ep.__class__.__name__)] = ep() except Exception as e: raise utils.ErrorMessage( "Unable to instantiate `entry_point` for module %s" % module) from e elif isinstance(ep, GHConfModuleDef): modules["%s::%s" % (module, ep.__class__.__name__)] = ep else: raise utils.ErrorMessage( "Module entry point %s is neither an instance of GHConfModuleDef, " "a list of GHConfModuleDef or a subclass of GHConfModuleDef." % module) else: raise utils.ErrorMessage( "Module %s has no `entry_point` top-level variable" % module) except ImportError as e: raise utils.ErrorMessage( "Can't import module %s (use --debug for more information)" % module) from e parser = ArgumentParser( description= "ghconf is a tool that parses declarative configuration files in a Python DSL " "and then runs Python modules against a preconfigured PyGithub instance. This " "allows us to apply common GitHub configuration through GitHub's v3 REST API " "to all repositories that are part of our organization.") parser.add_argument( "-o", "--organization", dest="org", default="optile", help= "The GitHub organization to run against. The GitHub API token must have write access to " "this organization.") parser.add_argument( "-r", "--repo", dest="repos", action="append", default=[], help= "Specify one or more repositories to run the configuration against. (Optional. If not " "specified, changes will be made to all repos in the org as modules see fit.)" ) parser.add_argument( "-re", "--repo-regex", dest="reporegexes", action="append", default=[], help= "Specify one or more regular expressions to match repositories to run the configuration " "against. (Optional. If not specified, changes will be made to all repos in the org as " "modules see fit.)") parser.add_argument( "--no-repo-changes", dest="skip_repo_changes", action="store_true", default=False, help="When set, ghconf will only execute org level changes.") parser.add_argument( "--no-org-changes", dest="skip_org_changes", action="store_true", default=False, help="When set, ghconf will not execute org level changes.") parser.add_argument( "--github-token", dest="github_token", default=None, help= "A GitHub API token for the user specified through '--github-user' to use for accessing " "the GitHub API. (Envvar: GITHUB_TOKEN)") parser.add_argument( "--module", dest="modules", action="append", default=[], required=True, help= "Specify Python modules as configuration that will be imported by ghconf." ) parser.add_argument("--debug", dest="debug", action="store_true", default=False, help="Enable debug output about API interactions.") parser.add_argument( "--list-unconfigured-repos", dest="list_unconfigured", action="store_true", default=False, help= "List the names of all repositories that remain untouched with the current configuration" ) parser.add_argument( "-v", "--verbose", dest="verbose", action="store_true", default=False, help= "Verbose output. Include informational output, like objects that don't change." ) parser.add_argument("--no-color", dest="no_color", action="store_true", default=False, help="Don't output ANSI colors") parser.add_argument("--no-progressbar", dest="no_progressbar", action="store_true", default=False, help="Skip the progress bar") parser.add_argument( "--plan", dest="plan", action="store_true", default=False, help= "Evaluate all changes and show what the tool would change with the current configuration." ) parser.add_argument( "--execute", dest="execute", action="store_true", default=False, help= "Execute any detected changes without asking first. If this is not set, ghconf will ask " "for permission before executing any changes.") for modulename, moduledef in modules.items(): # type: str, GHConfModuleDef try: print_debug("Add args for %s" % modulename) moduledef.add_args(parser) except NotImplementedError: pass args = parser.parse_args(sys.argv[1:]) if args.verbose: utils.enable_verbose_output = True utils.enable_progressbar = not args.no_progressbar if args.github_token: ghcgithub.init_github(args.github_token, dry_run=args.plan) elif os.getenv("GITHUB_TOKEN"): ghcgithub.init_github(os.getenv("GITHUB_TOKEN"), dry_run=args.plan) else: raise utils.ErrorMessage( "'--github-token' or environment variable GITHUB_TOKEN must be set" ) for modulename, moduledef in modules.items(): try: print_debug("Validate args for %s" % modulename) moduledef.validate_args(args) except NotImplementedError: pass try: print_debug("Initialize GitHub API, load organization") org = ghcgithub.gh.get_organization(args.org) # type: Organization except GithubException: raise utils.ErrorMessage( "No such Github organization %s for the given API token" % args.org) if args.plan: # banner print_info("=" * (shutil.get_terminal_size()[0] - 15)) print_info("{{:^{width}}}".format(width=shutil.get_terminal_size()[0] - 15).format("Plan mode")) print_info("=" * (shutil.get_terminal_size()[0] - 15)) ### print_changedict(assemble_changedict(args, org)) elif args.list_unconfigured: print_info("=" * (shutil.get_terminal_size()[0] - 15)) print_info( "{{:^{width}}}".format(width=shutil.get_terminal_size()[0] - 15).format("Unconfigured repositories")) print_info("=" * (shutil.get_terminal_size()[0] - 15)) repolist = assemble_repolist(args, org) pbar = progressbar(len(repolist) * len(modules)) for repo in repolist: branches = list(repo.get_branches()) for modulename, moduledef in modules.items(): pbar.update() try: if moduledef.applies_to_repository(org, repo, branches): repo.ghconf_touched = True break except NotImplementedError: continue if not hasattr(repo, "ghconf_touched"): repo.ghconf_touched = False for repo in repolist: if not repo.ghconf_touched: print_wrapped(repo.full_name) pbar.close() else: # banner print_info("=" * (shutil.get_terminal_size()[0] - 15)) print_info("{{:^{width}}}".format(width=shutil.get_terminal_size()[0] - 15).format( utils.highlight("Execute mode"))) print_info("=" * (shutil.get_terminal_size()[0] - 15)) ### changedict = assemble_changedict(args, org) if args.execute: apply_changedict(changedict) else: print_changedict(changedict) choice = prompt("Proceed and execute? [y/N] ", choices=["y", "n"], default="n") if choice == "y": apply_changedict(changedict) else: print_info("Execution cancelled")
def _protect_branch(branch: Branch, required_review_count: int) -> List[Change[str]]: def execute_review_protection( change: Change[str], branch: Branch, existing_protection: Optional[BranchProtection], review_count: int) -> Change[str]: try: if branch.protected and existing_protection and existing_protection.required_pull_request_reviews: if review_count > 0: print_debug( "Replacing review protection on branch %s (%s reviews)" % (highlight(branch.name), str(review_count))) branch.edit_required_pull_request_reviews( required_approving_review_count=review_count) else: print_debug("Removing review protection on branch: %s" % highlight(branch.name)) branch.remove_required_pull_request_reviews() elif review_count > 0: print_debug( "Adding review protection on branch: %s (%s reviews)" % (highlight(branch.name), str(review_count))) safe_branch_edit_protection( branch, required_approving_review_count=review_count) except GithubException as e: print_error("Can't set review protection on branch %s to %s: %s" % (highlight(branch.name), str(review_count), str(e))) return change.failure() return change.success() change_needed = False prot = None current_reqcount = 0 # The Github API will gladly return a required review count > 0 for a branch that had a required review # count previously, but it has now been turned off. So we need to correlate a bunch of information to find # out whether the branch actually requires reviews or not. if branch.protected: prot = branch.get_protection() if prot and prot.required_pull_request_reviews: rpr = prot.required_pull_request_reviews # type: RequiredPullRequestReviews if rpr.required_approving_review_count == required_review_count: print_debug( "Branch %s already requires %s reviews" % (highlight( branch.name), highlight(str(required_review_count)))) change_needed = False else: current_reqcount = rpr.required_approving_review_count change_needed = True else: if required_review_count == 0 and ( prot is None or prot.required_pull_request_reviews is None): print_debug( "Branch %s required no review and requested count is %s" % (highlight(branch.name), highlight("zero"))) change_needed = False else: change_needed = True else: change_needed = True if change_needed: change = Change( meta=ChangeMetadata(executor=execute_review_protection, params=[branch, prot, required_review_count]), action=ChangeActions.REPLACE if branch.protected else ChangeActions.ADD, before="Require %s reviews" % current_reqcount if branch.protected else "No protection", after="Require %s reviews" % required_review_count, cosmetic_prefix="Protect branch<%s>:" % branch.name) return [change] return []
def protect_pr_branch_with_tests_if_any_exist( org: Organization, repo: Repository, branches: Dict[str, Branch]) -> List[Change[str]]: def execute_test_protection(change: Change[str], branch: Branch, existing_checks: Set[str], known_checks: Set[str]) -> Change[str]: print_debug("[%s] Changing status checks on branch '%s' to [%s]" % (highlight(repo.name), highlight(branch.name), highlight(", ".join(list(known_checks))))) try: if existing_checks: branch.edit_required_status_checks(strict=True, contexts=list(known_checks)) else: safe_branch_edit_protection( branch, strict=True, contexts=list(known_checks), ) except GithubException as e: print_error( "Can't edit required status checks on repo %s branch %s: %s" % (repo.name, branch.name, str(e))) return change.failure() return change.success() prb = get_pr_branch(repo, branches) if not prb: return [] existing_checks = set() # type: Set[str] try: rqs = prb.get_required_status_checks() except GithubException: # the repository has currently no status checks pass else: if len(rqs.contexts) > 0: # The repository already has some status checks, in that case we do nothing existing_checks = set(rqs.contexts) print_debug("Branch %s on repo %s already has status checks [%s]" % (highlight(prb.name), highlight( repo.name), highlight(", ".join(existing_checks)))) # the repository currently has no status checks, let's see if any came in within the last 7 days sevendaysago = datetime.now() - timedelta(days=7) known_checks = set() # type: Set[str] for commit in repo.get_commits(prb.name, since=sevendaysago): for status in commit.get_statuses(): # type: CommitStatus known_checks.add(status.context) if known_checks and known_checks != existing_checks: # add all known checks as required checks print_debug('Adding checks [%s] to branch %s on repo %s' % (highlight(", ".join(known_checks)), highlight( prb.name), highlight(repo.name))) return [ Change( meta=ChangeMetadata( executor=execute_test_protection, params=[prb, existing_checks, known_checks]), action=ChangeActions.REPLACE if existing_checks else ChangeActions.ADD, before="%s checks" % len(existing_checks) if existing_checks else "No checks", after="%s checks" % len(known_checks), ) ] return []
def _set_repo_features(org: Organization, repo: Repository, branches: Dict[str, Branch]) -> List[Change[str]]: def execute_set_repo_features( change: Change[str], repo: Repository, enable_wiki: Optional[bool] = None, enable_issues: Optional[bool] = None, enable_projects: Optional[bool] = None) -> Change[str]: if change.action == ChangeActions.REPLACE: print_debug("[%s] Setting features" % highlight(repo.name)) kw = { 'has_wiki': NotSet if enable_wiki is None else enable_wiki, 'has_issues': NotSet if enable_issues is None else enable_issues, 'has_projects': NotSet if enable_projects is None else enable_projects } try: repo.edit(**kw) except GithubException: return change.failure() return change.success() if repo.archived: return [] elif repo.has_issues or repo.has_wiki or repo.has_projects: psc = "X" if repo.has_projects else " " if not org.has_repository_projects: psc = "disabled" psp = "X" if enable_projects else " " if not org.has_repository_projects: psp = "disabled" before_state = "wiki[%s], issues[%s], projects[%s]" % ( "X" if repo.has_wiki else " ", "X" if repo.has_issues else " ", psc) after_state = "wiki[%s], issues[%s], projects[%s]" % ( "X" if enable_wiki else " ", "X" if enable_issues else " ", psp) print_debug( "Enabling/disabling issue tracker, wiki and projects on %s" % (repo.name, )) if ((enable_projects != repo.has_projects) and org.has_repository_projects) or \ enable_issues != repo.has_issues or \ enable_wiki != repo.has_wiki: return [ Change(meta=ChangeMetadata( executor=execute_set_repo_features, params=[ repo, enable_wiki, enable_issues, enable_projects if org.has_repository_projects else None ], ), action=ChangeActions.REPLACE, before=before_state, after=after_state, cosmetic_prefix="Repo features:") ] return []