Esempio n. 1
0
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')
Esempio n. 2
0
    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()
Esempio n. 3
0
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))
Esempio n. 4
0
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}")