def test_github(): g = GitHub() status, data = g.users.octocat.get() assert data.get('name') == 'The Octocat' assert status == 200 # Workaround to https://github.com/mozilla/agithub/issues/67 response_headers = dict([(x.lower(), y) for x, y in g.getheaders()]) assert (response_headers.get( 'Content-Type'.lower()) == 'application/json; charset=utf-8')
def DoTask(self): buildSetup = self.buildSetup changelog_path = join(buildSetup.sourceDir, "CHANGELOG.TXT") appVer = buildSetup.appVersion bldDate = time.strftime("%Y-%m-%d", time.gmtime(buildSetup.buildTime)) token = buildSetup.gitConfig["token"] user = buildSetup.gitConfig["user"] repo = buildSetup.gitConfig["repo"] branch = buildSetup.gitConfig["branch"] gh = GitHub(token=token) rc, data = gh.repos[user][repo].releases.latest.get() if rc != 200: # no latest release to_commit = self.get_alternative_release(gh, user, repo) else: to_commit = data['target_commitish'] new_logs = ['**{0} ({1})**\n'.format(appVer, bldDate), '\n'] # get commits since last release page = 1 while page > 0: rc, data = gh.repos[user][repo].commits.get( sha=branch, per_page=100, page=page ) if rc != 200: print "INFO: couldn't get commits." return for item in data: if item['sha'] == to_commit: break author = item['commit']['author']['name'] try: msg = item['commit']['message'].splitlines()[0] if msg.startswith("Merge pull request #"): continue newline = "- {0} ({1})\n".format(msg, author) new_logs.append(newline) except IndexError: pass if item['sha'] == to_commit: break hdr = gh.getheaders() header = {item[0].strip(): item[1].strip() for item in hdr} # NOQA page = NextPage(gh) # read the existing changelog... try: infile = open(changelog_path, "r") except IOError: old_changelog = '' else: old_changelog = infile.read() infile.close() # ... and put the new changelog on top try: outfile = open(changelog_path, "w+") except IOError: import sys import wx parent = wx.GetApp().GetTopWindow() msg = "CHANGELOG.TXT couldn't be written.\n({0})".format( sys.exc_value ) dlg = wx.MessageDialog(parent, msg, caption="Error", style=wx.OK | wx.ICON_ERROR) dlg.ShowModal() else: outfile.writelines(new_logs) if old_changelog: outfile.write('\n\n') outfile.write(old_changelog) outfile.close()
class ApiClient(object): def __init__(self, username=None, password=None, token=None): self._api = GitHub(username=username, password=password, token=token) def list_repos(self, username): repos = list() next_page = 1 while next_page: status, response = self._api.users[username].repos.get( page=next_page) if self._is_successful(status): repos.extend(response) next_page = self._get_page_number('next') else: break return repos def get_readme_html(self, owner, repository): headers = {'accept': 'application/vnd.github.v3.html'} status, response = self._api.repos[owner][repository].readme.get( headers=headers) if self._is_successful(status): return response def get_readme_raw(self, owner, repository): status, response = self._api.repos[owner][repository].readme.get() if self._is_successful(status): return base64.b64decode(response.get('content')) def get_commit_stats(self, owner, repository): status, first_page = self._api.repos[owner][repository].commits.get() if self._is_successful(status) and first_page: last_commit = first_page[0].get('commit', dict()) message = last_commit.get('message') author = last_commit.get('author', dict()) author_name = author.get('name') commit_date = author.get('date') num_commits = len(first_page) last_page_num = self._get_page_number('last') if last_page_num > 1: last_status, last_page = self._api.repos[owner][ repository].commits.get(page=str(last_page_num)) if self._is_successful(last_status) and last_page: num_commits = num_commits * (last_page_num - 1) + len(last_page) return { 'total': num_commits, 'latest': { 'author': author_name, 'date': commit_date, 'message': message } } @staticmethod def _is_successful(status): return status / 100 == 2 def _get_links(self): headers = self._api.getheaders() for key, value in headers: if key.lower() == 'link': return value def _get_link(self, rel): links = self._get_links() if links: for match in re.finditer(r'<([^>]+)>; rel="%s"' % rel, links, flags=re.IGNORECASE): return match.group(1) def _get_page_number(self, link_rel): link = self._get_link(link_rel) if link: return int( re.sub(r'.*?page=([0-9]+)', r'\1', link, flags=re.IGNORECASE))
def main(): # pylint:disable=too-many-locals,too-many-branches,too-many-statements """Main function of this script.""" keyfile = os.path.join(os.environ['HOME'], GITHUBTOKEN_FILE) parser = argparse.ArgumentParser() parser.add_argument("-k", "--keyfile", type=argparse.FileType('r'), default=keyfile, help="File containing github token") parser.add_argument("-c", "--comment", action="store_true", help="Put a comment with a reference under" "the original PR") parser.add_argument("-n", "--noop", action="store_true", help="Limited noop mode, creates branch, but doesn't" "push and create the PR") parser.add_argument("-r", "--release-branch", type=str, help="Base the backport on this branch, " "default is the latest") parser.add_argument("--backport-branch-fmt", type=str, default=BACKPORT_BRANCH, help="Backport branch format. " "Fields '{release}' and '{origbranch} will be " "replaced by the release name and remote branch " "name.") parser.add_argument('-d', '--gitdir', type=str, default=os.getcwd(), help="Base git repo to work from") parser.add_argument("PR", type=int, help="Pull request number to backport") args = parser.parse_args() gittoken = args.keyfile.read().strip() github_api = GitHub(token=gittoken) # TODO: exception handling status, user = github_api.user.get() if status != 200: print(f'Could not retrieve user: {user["message"]}') sys.exit(1) # Token-scope-check: Is the token is powerful enough to complete # the Backport? response_headers = dict(github_api.getheaders()) # agithub documentation says it's lower case header field-names but # at this moment it's not if 'X-OAuth-Scopes' in response_headers: scopes = response_headers['X-OAuth-Scopes'] else: scopes = response_headers['x-oauth-scopes'] scopes_list = [x.strip() for x in scopes.split(',')] if not ('public_repo' in scopes_list or 'repo' in scopes_list): print("missing public_repo scope from token settings." " Please add it on the GitHub webinterface") sys.exit(1) username = user['login'] status, pulldata = github_api.repos[ORG][REPO].pulls[args.PR].get() if status != 200: print(f'Commit #{args.PR} not found: {pulldata["message"]}') sys.exit(2) if not pulldata['merged']: print("Original PR not yet merged") sys.exit(0) print(f'Fetching for commit: #{args.PR}: {pulldata["title"]}') orig_branch = pulldata['head']['ref'] status, commits = github_api.repos[ORG][REPO].pulls[args.PR].commits.get() if status != 200: print(f'No commits found for #{args.PR}: {commits["message"]}') sys.exit(3) for commit in commits: print(f'found {commit["sha"]} : {commit["commit"]["message"]}') # Find latest release branch if args.release_branch: release_fullname = args.release_branch release_shortname = _branch_name_strip(args.release_branch) else: status, branches = github_api.repos[ORG][REPO].branches.get() if status != 200: print(f'Could not retrieve branches for {ORG}/{REPO}: ' f'{branches["message"]}') sys.exit(4) release_shortname, release_fullname = _get_latest_release(branches) if not release_fullname: print("No release branch found, exiting") sys.exit(5) print(f"Backport based on branch {release_fullname}") repo = git.Repo(args.gitdir) # Fetch current upstream upstream_remote = _get_upstream(repo) if not upstream_remote: print("No upstream remote found, can't fetch") sys.exit(6) print(f"Fetching {upstream_remote} remote") upstream_remote.fetch() # Build topic branch in temp dir new_branch = args.backport_branch_fmt.format(release=release_shortname, origbranch=orig_branch) if new_branch in repo.branches: print(f"ERROR: Branch {new_branch} already exists") sys.exit(1) worktree_dir = os.path.join(args.gitdir, WORKTREE_SUBDIR) repo.git.worktree("add", "-b", new_branch, WORKTREE_SUBDIR, f"{upstream_remote}/{release_fullname}") # transform branch name into Head object for later configuring new_branch = repo.branches[new_branch] try: bp_repo = git.Repo(worktree_dir) # Apply commits for commit in commits: bp_repo.git.cherry_pick('-x', commit['sha']) # Push to github origin = _find_remote(repo, username, REPO) print(f"Pushing branch {new_branch} to {origin}") if not args.noop: push_info = origin.push(f"{new_branch}:{new_branch}") new_branch.set_tracking_branch(push_info[0].remote_ref) except Exception as exc: # Delete worktree print(f"Pruning temporary workdir at {worktree_dir}") _delete_worktree(repo, worktree_dir) # also delete branch created by worktree; this is only possible after # the worktree was deleted repo.delete_head(new_branch) raise exc else: # Delete worktree print(f"Pruning temporary workdir at {worktree_dir}") _delete_worktree(repo, worktree_dir) labels = _get_labels(pulldata) merger = pulldata['merged_by']['login'] if not args.noop: # Open new PR on github pull_request = { 'title': f'{pulldata["title"]} [backport {release_shortname}]', 'head': f'{username}:{new_branch}', 'base': release_fullname, 'body': f'# Backport of #{args.PR}\n\n{pulldata["body"]}', 'maintainer_can_modify': True, } status, new_pr = github_api.repos[ORG][REPO].pulls.post( body=pull_request) if status != 201: print(f'Error creating the new pr: "{new_pr["message"]}". ' 'Is "Public Repo" access enabled for the token?') pr_number = new_pr['number'] print(f"Create PR number #{pr_number} for backport") github_api.repos[ORG][REPO].issues[pr_number].labels.post(body=labels) review_request = {"reviewers": [merger]} github_api.repos[ORG][REPO].pulls[pr_number].\ requested_reviewers.post(body=review_request) # Put commit under old PR if args.comment and not args.noop: comment = {"body": f"Backport provided in #{pr_number}"} status, res = github_api.repos[ORG][REPO].\ issues[args.PR].comments.post(body=comment) if status != 201: print(f'Something went wrong adding the comment: {res["message"]}') print(f"Added comment to #{args.PR}")