Example #1
0
    def _run_task(self):
        repo = self.get_repo()

        version = self.options["version"]
        self.tag_name = self.project_config.get_tag_for_version(version)

        for release in repo.iter_releases():
            if release.tag_name == self.tag_name:
                message = "Release {} already exists at {}".format(
                    release.name, release.html_url)
                self.logger.error(message)
                raise GithubException(message)

        commit = self.options.get("commit", self.project_config.repo_commit)
        if not commit:
            message = "Could not detect the current commit from the local repo"
            self.logger.error(message)
            raise GithubException(message)

        ref = repo.ref("tags/{}".format(self.tag_name))

        if not ref:
            # Create the annotated tag
            tag = repo.create_tag(
                tag=self.tag_name,
                message="Release of version {}".format(version),
                sha=commit,
                obj_type="commit",
                tagger={
                    "name": self.github_config.username,
                    "email": self.github_config.email,
                    "date": "{}Z".format(datetime.utcnow().isoformat()),
                },
                lightweight=False,
            )

            # Get the ref created from the previous call that for some reason creates
            # a ref to the commit sha rather than the tag sha.  Delete the ref so we
            # can create the right one.  FIXME: Is this a bug in github3.py?
            ref = repo.ref("tags/{}".format(self.tag_name))
            if ref:
                ref.delete()

            # Create the ref linking to the tag
            ref = repo.create_ref(ref="refs/tags/{}".format(self.tag_name),
                                  sha=tag.sha)

            # Sleep for Github to catch up with the fact that the tag actually exists!
            time.sleep(3)

        prerelease = "Beta" in version

        # Create the Github Release
        release = repo.create_release(tag_name=self.tag_name,
                                      name=version,
                                      prerelease=prerelease)
        self.return_values = {"tag_name": self.tag_name, "name": version}
        self.logger.info("Created release {} at {}".format(
            release.name, release.html_url))
Example #2
0
    def _create_release(self, path, commit):
        # Get current release info
        repo = self.get_repo()
        # Get the ref
        try:
            ref = repo.ref(f"tags/{self.tag_name}")
        except github3.exceptions.NotFoundError:
            message = f"Ref not found for tag {self.tag_name}"
            raise GithubException(message)
        # Get the tag
        try:
            tag = repo.tag(ref.object.sha)
        except github3.exceptions.NotFoundError:
            message = f"Tag {self.tag_name} not found"
            raise GithubException(message)
        # Get the release
        try:
            release = repo.release_from_tag(self.tag_name)
            release_body = release.body if self.options["release_body"] else ""
        except github3.exceptions.NotFoundError:
            message = f"Release for {self.tag_name} not found"
            raise GithubException(message)

        # Check for tag in target repo
        try:
            self.target_repo.ref(f"tags/{self.tag_name}")
        except github3.exceptions.NotFoundError:
            pass
        else:
            message = f"Ref for tag {self.tag_name} already exists in target repo"
            raise GithubException(message)

        # Create the tag
        self.target_repo.create_tag(
            tag=self.tag_name,
            message=tag.message,
            sha=commit,
            obj_type="commit",
            tagger={
                "name": self.github_config.username,
                "email": self.github_config.email,
                "date": "{}Z".format(datetime.utcnow().isoformat()),
            },
            lightweight=False,
        )

        # Create the release
        self.target_repo.create_release(
            tag_name=self.tag_name,
            name=self.options["version"],
            prerelease=release.prerelease,
            body=release_body,
        )
Example #3
0
    def _run_task(self):

        # args
        branch = self.options.get(
            'branch',
            self.project_config.project__apexdoc__branch,
        )
        if not branch:
            raise GithubException('Unable to determine branch name')
        if 'dir_local' in self.options:
            local_dir = self.options['dir_local']
        else:
            local_base_dir = (
                self.project_config.project__apexdoc__dir
                if self.project_config.project__apexdoc__dir
                else self.project_config.repo_root
            )
            local_dir = os.path.join(local_base_dir, 'ApexDocumentation')
        repo_dir = self.options.get(
            'dir_repo',
            self.project_config.project__apexdoc__repo_dir,
        )
        dry_run = process_bool_arg(self.options.get('dry_run', False))
        commit_message = self.options.get('commit_message', 'Update Apex docs')

        # get API
        repo = self.get_repo()

        # commit
        author = {
            'name': self.github.user().name,
            'email': self.github_config.email,
        }
        commit_dir = CommitDir(repo, self.logger, author)
        commit_dir(local_dir, branch, repo_dir, commit_message, dry_run)
Example #4
0
def get_github_api_for_repo(keychain, owner, repo):
    gh = GitHub()
    # Apply retry policy
    gh.session.mount("http://", adapter)
    gh.session.mount("https://", adapter)

    APP_KEY = os.environ.get("GITHUB_APP_KEY", "").encode("utf-8")
    APP_ID = os.environ.get("GITHUB_APP_ID")
    if APP_ID and APP_KEY:
        installation = INSTALLATIONS.get((owner, repo))
        if installation is None:
            gh.login_as_app(APP_KEY, APP_ID, expire_in=120)
            try:
                installation = gh.app_installation_for_repository(owner, repo)
            except github3.exceptions.NotFoundError:
                raise GithubException(
                    "Could not access {}/{} using GitHub app. "
                    "Does the app need to be installed for this repository?".
                    format(owner, repo))
            INSTALLATIONS[(owner, repo)] = installation
        gh.login_as_app_installation(APP_KEY, APP_ID, installation.id)
    else:
        github_config = keychain.get_service("github")
        gh.login(github_config.username, github_config.password)
    return gh
Example #5
0
 def _get_latest_tag_for_prefix(self, repo, prefix):
     for release in repo.releases():
         if not release.tag_name.startswith(prefix):
             continue
         return release.tag_name
     raise GithubException(
         f"No release found for {self.repo_url} with tag prefix {prefix}")
Example #6
0
    def _run_task(self):
        repo = self.get_repo()
        ref = repo.ref("tags/{}".format(self.options["src_tag"]))
        try:
            src_tag = repo.tag(ref.object.sha)
        except github3.exceptions.NotFoundError:
            message = "Tag {} not found".format(self.options["src_tag"])
            self.logger.error(message)
            raise GithubException(message)

        tag = repo.create_tag(
            tag=self.options["tag"],
            message="Cloned from {}".format(self.options["src_tag"]),
            sha=src_tag.sha,
            obj_type="commit",
            tagger={
                "name": self.github_config.username,
                "email": self.github_config.email,
                "date": "{}Z".format(datetime.utcnow().isoformat()),
            },
        )
        self.logger.info("Tag {} created by cloning {}".format(
            self.options["tag"], self.options["src_tag"]))

        return tag
Example #7
0
    def _run_task(self):

        # args
        branch = self.options.get(
            "branch", self.project_config.project__apexdoc__branch
        )
        if not branch:
            raise GithubException("Unable to determine branch name")
        local_dir = self.options.get("dir_local")
        if not local_dir:
            local_base_dir = (
                self.project_config.project__apexdoc__dir
                if self.project_config.project__apexdoc__dir
                else self.project_config.repo_root
            )
            local_dir = os.path.join(local_base_dir, "ApexDocumentation")
        repo_dir = self.options.get(
            "dir_repo", self.project_config.project__apexdoc__repo_dir
        )
        dry_run = process_bool_arg(self.options.get("dry_run", False))
        commit_message = self.options.get("commit_message", "Update Apex docs")

        # get API
        repo = self.get_repo()

        # commit
        author = {"name": self.github.user().name, "email": self.github_config.email}
        commit_dir = CommitDir(repo, self.logger, author)
        commit_dir(local_dir, branch, repo_dir, commit_message, dry_run)
Example #8
0
def get_github_api_for_repo(keychain, owner, repo, session=None):
    gh = GitHub(
        session=session
        or GitHubSession(default_read_timeout=30, default_connect_timeout=30)
    )
    # Apply retry policy
    gh.session.mount("http://", adapter)
    gh.session.mount("https://", adapter)

    GITHUB_TOKEN = os.environ.get("GITHUB_TOKEN")
    APP_KEY = os.environ.get("GITHUB_APP_KEY", "").encode("utf-8")
    APP_ID = os.environ.get("GITHUB_APP_ID")
    if APP_ID and APP_KEY:
        installation = INSTALLATIONS.get((owner, repo))
        if installation is None:
            gh.login_as_app(APP_KEY, APP_ID, expire_in=120)
            try:
                installation = gh.app_installation_for_repository(owner, repo)
            except github3.exceptions.NotFoundError:
                raise GithubException(
                    f"Could not access {owner}/{repo} using GitHub app. "
                    "Does the app need to be installed for this repository?"
                )
            INSTALLATIONS[(owner, repo)] = installation
        gh.login_as_app_installation(APP_KEY, APP_ID, installation.id)
    elif GITHUB_TOKEN:
        gh.login(token=GITHUB_TOKEN)
    else:
        github_config = keychain.get_service("github")
        gh.login(github_config.username, github_config.password)
    return gh
Example #9
0
 def _get_repo(self):
     gh = self.get_github_api()
     repo = gh.repository(self.repo_owner, self.repo_name)
     if repo is None:
         raise GithubException(
             f"Github repository not found or not authorized. ({self.repo_url})"
         )
     return repo
Example #10
0
 def _update_head(self, new_commit):
     if self.dry_run:
         self.logger.info("[dry_run] Skipping call to update HEAD")
     else:
         self.logger.info("Updating HEAD")
         success = self.head.update(new_commit.sha)
         if not success:
             raise GithubException("Failed to update HEAD")
Example #11
0
def validate_service(options):
    username = options["username"]
    password = options["password"]
    gh = get_github_api(username, password)
    try:
        gh.rate_limit()
    except Exception as e:
        raise GithubException(f"Could not confirm access to the GitHub API: {str(e)}")
Example #12
0
 def _create_tree(self, new_tree_list):
     new_tree = None
     if self.dry_run:
         self.logger.info("[dry_run] Skipping creation of new tree")
     else:
         self.logger.info("Creating new tree")
         new_tree = self.repo.create_tree(new_tree_list, None)
         if not new_tree:
             raise GithubException("Failed to create tree")
     return new_tree
Example #13
0
 def _create_blob(self, content):
     try:
         content = content.decode('utf-8')
         blob_sha = self.repo.create_blob(content, 'utf-8')
     except UnicodeDecodeError:
         content = base64.b64encode(content)
         blob_sha = self.repo.create_blob(content, 'base64')
     if not blob_sha:
         raise GithubException('Failed to create blob')
     return blob_sha
Example #14
0
 def _create_blob(self, content):
     try:
         content = content.decode("utf-8")
         blob_sha = self.repo.create_blob(content, "utf-8")
     except UnicodeDecodeError:
         content = base64.b64encode(content)
         blob_sha = self.repo.create_blob(content, "base64")
     if not blob_sha:
         raise GithubException("Failed to create blob")
     return blob_sha
Example #15
0
    def _init_options(self, kwargs):
        super()._init_options(kwargs)

        self.commit = self.options.get("commit", self.project_config.repo_commit)
        if not self.commit:
            message = "Could not detect the current commit from the local repo"
            self.logger.error(message)
            raise GithubException(message)
        if len(self.commit) != 40:
            raise TaskOptionsError("The commit option must be exactly 40 characters.")
Example #16
0
 def _validate_dirs(self, local_dir, repo_dir):
     local_dir = os.path.abspath(local_dir)
     if not os.path.isdir(local_dir):
         raise GithubException("Not a dir: {}".format(local_dir))
     # do not use os.path because repo_dir is not local
     if repo_dir is None:
         repo_dir = ""
     if repo_dir.startswith("."):
         repo_dir = repo_dir[1:]
     if repo_dir.startswith("/"):
         repo_dir = repo_dir[1:]
     if repo_dir.endswith("/"):
         repo_dir = repo_dir[:-1]
     return local_dir, repo_dir
Example #17
0
 def get_latest_tag(self, beta=False):
     """ Query Github Releases to find the latest production or beta tag """
     repo = self._get_repo()
     if not beta:
         try:
             release = repo.latest_release()
         except github3.exceptions.NotFoundError:
             raise GithubException(f"No release found for repo {self.repo_url}")
         prefix = self.project__git__prefix_release
         if not release.tag_name.startswith(prefix):
             return self._get_latest_tag_for_prefix(repo, prefix)
         return release.tag_name
     else:
         return self._get_latest_tag_for_prefix(repo, self.project__git__prefix_beta)
Example #18
0
 def _create_commit(self, commit_message, new_tree):
     if commit_message is None:
         commit_message = "Commit dir {} to {}/{}/{} via CumulusCI".format(
             self.local_dir, self.repo.owner, self.repo.name, self.repo_dir)
     new_commit = None
     if self.dry_run:
         self.logger.info("[dry_run] Skipping creation of new commit")
     else:
         self.logger.info("Creating new commit")
         new_commit = self.repo.create_commit(
             message=commit_message,
             tree=new_tree.sha,
             parents=[self.parent_commit.sha],
             author=self.author,
             committer=self.author,
         )
         if not new_commit:
             raise GithubException("Failed to create commit")
     return new_commit
Example #19
0
 def _create_blob(self, content, local_file):
     if self.dry_run:
         self.logger.info("[dry_run] Skipping creation of " +
                          "blob for new file: {}".format(local_file))
         blob_sha = None
     else:
         self.logger.info(
             "Creating blob for new file: {}".format(local_file))
         try:
             content = content.decode("utf-8")
             blob_sha = self.repo.create_blob(content, "utf-8")
         except UnicodeDecodeError:
             content = base64.b64encode(content)
             blob_sha = self.repo.create_blob(content.decode("utf-8"),
                                              "base64")
         if not blob_sha:
             raise GithubException("Failed to create blob")
     self.logger.debug("Blob created: {}".format(blob_sha))
     return blob_sha
Example #20
0
 def _create_commit(self, commit_message, new_tree):
     if commit_message is None:
         commit_message = "Commit dir {} to {}/{}/{} via CumulusCI".format(
             self.local_dir, self.repo.owner, self.repo.name, self.repo_dir)
     new_commit = None
     if self.dry_run:
         self.logger.info("[dry_run] Skipping creation of new commit")
     else:
         self.logger.info("Creating new commit")
         commit_info = {
             "message": commit_message,
             "tree": new_tree.sha,
             "parents": [self.parent_commit.sha],
         }
         if self.author:
             commit_info["author"] = self.author
             commit_info["committer"] = self.author
         new_commit = self.repo.create_commit(**commit_info)
         if not new_commit:
             raise GithubException("Failed to create commit")
     return new_commit
Example #21
0
    def _run_task(self):
        repo = self.get_repo()
        ref = repo.ref('tags/{}'.format(self.options['src_tag']))
        src_tag = repo.tag(ref.object.sha)
        if not src_tag:
            message = 'Tag {} not found'.format(self.options['src_tag'])
            logger.error(message)
            raise GithubException(message)

        tag = repo.create_tag(
            tag = self.options['tag'],
            message = 'Cloned from {}'.format(self.options['src_tag']),
            sha = src_tag.sha,
            obj_type = 'commit',
            tagger = {
                'name': self.github_config.username,
                'email': self.github_config.email,
                'date': '{}Z'.format(datetime.utcnow().isoformat()),
            },
        )
        self.logger.info('Tag {} created by cloning {}'.format(self.options['tag'], self.options['src_tag']))

        return tag
Example #22
0
    def __call__(self,
            local_dir,
            branch,
            repo_dir=None,
            commit_message=None,
            dry_run=False,
        ):
        """
        local_dir: path to local directory to commit
        branch: target branch name
        repo_dir: target path within repo - use '' for repo root
        commit_message: message for git commit
        dry_run: skip creating GitHub data if True
        """

        # prepare dir args
        local_dir = os.path.abspath(local_dir)
        if not os.path.isdir(local_dir):
            raise GithubException('Not a dir: {}'.format(local_dir))
        # do not use os.path because repo_dir is not local
        if repo_dir is None:
            repo_dir = ''
        if repo_dir.startswith('.'):
            repo_dir = repo_dir[1:]
        if repo_dir.startswith('/'):
            repo_dir = repo_dir[1:]
        if repo_dir.endswith('/'):
            repo_dir = repo_dir[:-1]

        # get ref to branch HEAD
        head = self.repo.ref('heads/{}'.format(branch))

        # get commit pointed to by HEAD
        commit = self.repo.git_commit(head.object.sha)

        # get tree of commit
        tree = self.repo.tree(commit.tree.sha) # shallow tree
        tree = tree.recurse() # recurse/flatten
        tree = tree.to_json() # convert to native types

        # create new tree (delete, update)
        new_tree_list = []
        for item in tree['tree']:
            if item['type'] == 'tree':
                # remove sub-trees as they are implied by blob paths
                self.logger.debug('Removing tree: {}'.format(item['path']))
                continue
            if not item['path'].startswith(repo_dir):
                # outside target dir in repo - keep in tree
                self.logger.debug(
                    'Unchanged (outside target path): {}'.format(item['path'])
                )
                new_tree_list.append(item)
                continue
            len_path = (len(repo_dir) + 1) if repo_dir else 0
            item_subpath = item['path'][len_path:]
            local_file = os.path.join(local_dir, item_subpath)
            if not os.path.isfile(local_file):
                # delete blob from tree
                self.logger.debug('Delete: {}'.format(item['path']))
                continue
            with io.open(local_file, 'rb') as f:
                content = f.read()
            header = 'blob {}\0'.format(len(content))
            sha = hashlib.sha1(header + content).hexdigest()
            new_item = item.copy()
            if sha != item['sha']:
                self.logger.debug('Update: {}'.format(local_file))
                if dry_run:
                    self.logger.info('[dry_run] Skipping creation of blob ' +
                        'for file: {}'.format(local_file)
                    )
                    blob_sha = None
                else:
                    self.logger.info(
                        'Creating blob for updated file: {}'.format(local_file)
                    )
                    blob_sha = self._create_blob(content)
                    self.logger.debug('Blob created: {}'.format(blob_sha))
                new_item['sha'] = blob_sha
            else:
                self.logger.debug('Unchanged: {}'.format(item['path']))
            new_tree_list.append(new_item)

        # add new files to tree
        new_tree_target_subpaths = []
        for item in new_tree_list:
            if not item['path'].startswith(repo_dir):
                # skip items not in target dir
                continue
            len_path = (len(repo_dir) + 1) if repo_dir else 0
            new_tree_target_subpaths.append(item['path'][len_path:])
        for root, dirs, files in os.walk(local_dir):
            for filename in files:
                if filename.startswith('.'):
                    # skip hidden files
                    continue
                local_file = os.path.join(root, filename)
                local_file_subpath = local_file[(len(local_dir) + 1):]
                if local_file_subpath not in new_tree_target_subpaths:
                    with io.open(local_file, 'rb') as f:
                        content = f.read()
                    if dry_run:
                        self.logger.info('[dry_run] Skipping creation of ' +
                            'blob for new file: {}'.format(local_file)
                        )
                        blob_sha = None
                    else:
                        self.logger.info(
                            'Creating blob for new file: {}'.format(local_file)
                        )
                        blob_sha = self._create_blob(content)
                        self.logger.debug('Blob created: {}'.format(blob_sha))
                    repo_path = (repo_dir + '/') if repo_dir else ''
                    new_item = {
                        'path': '{}{}'.format(repo_path, local_file_subpath),
                        'mode': '100644',
                        'type': 'blob',
                        'sha': blob_sha,
                    }
                    new_tree_list.append(new_item)

        # generate summary of changes
        self.logger.info('Summary of changes:')
        new_shas = [item['sha'] for item in new_tree_list]
        new_paths = [item['path'] for item in new_tree_list]
        old_paths = [item['path'] for item in tree['tree']]
        old_tree_list = []
        for item in tree['tree']:
            if item['type'] == 'tree':
                continue
            old_tree_list.append(item)
            if item['path'] not in new_paths:
                self.logger.warning('Delete:\t{}'.format(item['path']))
            elif item['sha'] not in new_shas:
                self.logger.info('Update:\t{}'.format(item['path']))
        for item in new_tree_list:
            if item['path'] not in old_paths:
                self.logger.info('Add:\t{}'.format(item['path']))
        if new_tree_list == old_tree_list:
            self.logger.warning('No changes found, aborting commit')
            return


        # create new tree
        if dry_run:
            self.logger.info('[dry_run] Skipping creation of new tree')
        else:
            self.logger.info('Creating new tree')
            new_tree = self.repo.create_tree(new_tree_list, None)
            if not new_tree:
                raise GithubException('Failed to create tree')

        # create new commit
        if commit_message is None:
            commit_message = 'Commit dir {} to {}/{}/{} via CumulusCI'.format(
                local_dir,
                self.repo.owner,
                self.repo.name,
                repo_dir,
            )
        if dry_run:
            self.logger.info('[dry_run] Skipping creation of new commit')
        else:
            self.logger.info('Creating new commit')
            new_commit = self.repo.create_commit(
                message=commit_message,
                tree=new_tree.sha,
                parents=[commit.sha],
                author=self.author,
                committer=self.author,
            )
            if not new_commit:
                raise GithubException('Failed to create commit')

        # update HEAD
        if dry_run:
            self.logger.info('[dry_run] Skipping call to update HEAD')
        else:
            self.logger.info('Updating HEAD')
            success = head.update(new_commit.sha)
            if not success:
                raise GithubException('Failed to update HEAD')
Example #23
0
    def _run_task(self):
        repo = self.get_repo()

        version = self.options["version"]
        tag_name = self.project_config.get_tag_for_version(version)

        # Make sure release doesn't already exist
        try:
            release = repo.release_from_tag(tag_name)
        except github3.exceptions.NotFoundError:
            pass
        else:
            message = "Release {} already exists at {}".format(
                release.name, release.html_url
            )
            self.logger.error(message)
            raise GithubException(message)

        # Build tag message
        message = self.options.get("message", "Release of version {}".format(version))
        dependencies = self.project_config.get_static_dependencies(
            self.options.get("dependencies")
            or self.project_config.project__dependencies
        )
        if dependencies:
            message += "\n\ndependencies: {}".format(json.dumps(dependencies, indent=4))

        try:
            repo.ref("tags/{}".format(tag_name))
        except github3.exceptions.NotFoundError:
            # Create the annotated tag
            repo.create_tag(
                tag=tag_name,
                message=message,
                sha=self.commit,
                obj_type="commit",
                tagger={
                    "name": self.github_config.username,
                    "email": self.github_config.email,
                    "date": "{}Z".format(datetime.utcnow().isoformat()),
                },
                lightweight=False,
            )

            # Sleep for Github to catch up with the fact that the tag actually exists!
            time.sleep(3)

        prerelease = "Beta" in version

        # Create the Github Release
        release = repo.create_release(
            tag_name=tag_name, name=version, prerelease=prerelease
        )
        self.return_values = {
            "tag_name": tag_name,
            "name": version,
            "dependencies": dependencies,
        }
        self.logger.info(
            "Created release {} at {}".format(release.name, release.html_url)
        )
Example #24
0
    def __call__(self,
                 local_dir,
                 branch,
                 repo_dir=None,
                 commit_message=None,
                 dry_run=False):
        """
        local_dir: path to local directory to commit
        branch: target branch name
        repo_dir: target path within repo - use '' for repo root
        commit_message: message for git commit
        dry_run: skip creating GitHub data if True
        """

        # prepare dir args
        local_dir, repo_dir = self._validate_dirs(local_dir, repo_dir)

        # get ref to branch HEAD
        head = self.repo.ref("heads/{}".format(branch))

        # get commit pointed to by HEAD
        commit = self.repo.git_commit(head.object.sha)

        # get tree of commit
        tree = self.repo.tree(commit.tree.sha)  # shallow tree
        tree = tree.recurse()  # recurse/flatten
        tree = tree.to_json()  # convert to native types

        # create new tree (delete, update)
        new_tree_list = []
        for item in tree["tree"]:
            if item["type"] == "tree":
                # remove sub-trees as they are implied by blob paths
                self.logger.debug("Removing tree: {}".format(item["path"]))
                continue
            if not item["path"].startswith(repo_dir):
                # outside target dir in repo - keep in tree
                self.logger.debug("Unchanged (outside target path): {}".format(
                    item["path"]))
                new_tree_list.append(item)
                continue
            len_path = (len(repo_dir) + 1) if repo_dir else 0
            item_subpath = item["path"][len_path:]
            local_file = os.path.join(local_dir, item_subpath)
            if not os.path.isfile(local_file):
                # delete blob from tree
                self.logger.debug("Delete: {}".format(item["path"]))
                continue
            with io.open(local_file, "rb") as f:
                content = f.read()
            header = b"blob " + str(len(content)).encode() + b"\0"
            sha = hashlib.sha1(header + content).hexdigest()
            new_item = item.copy()
            if sha != item["sha"]:
                self.logger.debug("Update: {}".format(local_file))
                if dry_run:
                    self.logger.info("[dry_run] Skipping creation of blob " +
                                     "for file: {}".format(local_file))
                    blob_sha = None
                else:
                    self.logger.info(
                        "Creating blob for updated file: {}".format(
                            local_file))
                    blob_sha = self._create_blob(content)
                    self.logger.debug("Blob created: {}".format(blob_sha))
                new_item["sha"] = blob_sha
            else:
                self.logger.debug("Unchanged: {}".format(item["path"]))
            new_tree_list.append(new_item)

        # add new files to tree
        new_tree_target_subpaths = []
        for item in new_tree_list:
            if not item["path"].startswith(repo_dir):
                # skip items not in target dir
                continue
            len_path = (len(repo_dir) + 1) if repo_dir else 0
            new_tree_target_subpaths.append(item["path"][len_path:])
        for root, dirs, files in os.walk(local_dir):
            for filename in files:
                if filename.startswith("."):
                    # skip hidden files
                    continue
                local_file = os.path.join(root, filename)
                local_file_subpath = local_file[(len(local_dir) + 1):]
                if local_file_subpath not in new_tree_target_subpaths:
                    with io.open(local_file, "rb") as f:
                        content = f.read()
                    if dry_run:
                        self.logger.info(
                            "[dry_run] Skipping creation of " +
                            "blob for new file: {}".format(local_file))
                        blob_sha = None
                    else:
                        self.logger.info(
                            "Creating blob for new file: {}".format(
                                local_file))
                        blob_sha = self._create_blob(content)
                        self.logger.debug("Blob created: {}".format(blob_sha))
                    repo_path = (repo_dir + "/") if repo_dir else ""
                    new_item = {
                        "path": "{}{}".format(repo_path, local_file_subpath),
                        "mode": "100644",
                        "type": "blob",
                        "sha": blob_sha,
                    }
                    new_tree_list.append(new_item)

        # generate summary of changes
        self.logger.info("Summary of changes:")
        new_shas = [item["sha"] for item in new_tree_list]
        new_paths = [item["path"] for item in new_tree_list]
        old_paths = [item["path"] for item in tree["tree"]]
        old_tree_list = []
        for item in tree["tree"]:
            if item["type"] == "tree":
                continue
            old_tree_list.append(item)
            if item["path"] not in new_paths:
                self.logger.warning("Delete:\t{}".format(item["path"]))
            elif item["sha"] not in new_shas:
                self.logger.info("Update:\t{}".format(item["path"]))
        for item in new_tree_list:
            if item["path"] not in old_paths:
                self.logger.info("Add:\t{}".format(item["path"]))
        if new_tree_list == old_tree_list:
            self.logger.warning("No changes found, aborting commit")
            return

        # create new tree
        if dry_run:
            self.logger.info("[dry_run] Skipping creation of new tree")
        else:
            self.logger.info("Creating new tree")
            new_tree = self.repo.create_tree(new_tree_list, None)
            if not new_tree:
                raise GithubException("Failed to create tree")

        # create new commit
        if commit_message is None:
            commit_message = "Commit dir {} to {}/{}/{} via CumulusCI".format(
                local_dir, self.repo.owner, self.repo.name, repo_dir)
        if dry_run:
            self.logger.info("[dry_run] Skipping creation of new commit")
        else:
            self.logger.info("Creating new commit")
            new_commit = self.repo.create_commit(
                message=commit_message,
                tree=new_tree.sha,
                parents=[commit.sha],
                author=self.author,
                committer=self.author,
            )
            if not new_commit:
                raise GithubException("Failed to create commit")

        # update HEAD
        if dry_run:
            self.logger.info("[dry_run] Skipping call to update HEAD")
        else:
            self.logger.info("Updating HEAD")
            success = head.update(new_commit.sha)
            if not success:
                raise GithubException("Failed to update HEAD")
Example #25
0
    def _run_task(self):
        repo = self.get_repo()

        for release in repo.iter_releases():
            if release.name == self.options['version']:
                message = 'Release {} already exists at {}'.format(
                    release.name, release.html_url)
                self.logger.error(message)
                return GithubException(message)

        commit = self.options.get('commit', self.project_config.repo_commit)
        if not commit:
            message = 'Could not detect the current commit from the local repo'
            self.logger.error(message)
            return GithubException(message)

        version = self.options['version']
        self.tag_name = self.project_config.get_tag_for_version(version)

        ref = repo.ref('tags/{}'.format(self.tag_name))

        if not ref:
            # Create the annotated tag
            tag = repo.create_tag(
                tag=self.tag_name,
                message='Release of version {}'.format(version),
                sha=commit,
                obj_type='commit',
                tagger={
                    'name': self.github_config.username,
                    'email': self.github_config.email,
                    'date': '{}Z'.format(datetime.now().isoformat()),
                },
                lightweight=False,
            )

            # Get the ref created from the previous call that for some reason creates
            # a ref to the commit sha rather than the tag sha.  Delete the ref so we
            # can create the right one.  FIXME: Is this a bug in github3.py?
            ref = repo.ref('tags/{}'.format(self.tag_name))
            if ref:
                ref.delete()

            # Create the ref linking to the tag
            ref = repo.create_ref(
                ref='refs/tags/{}'.format(self.tag_name),
                sha=tag.sha,
            )

            # Sleep for Github to catch up with the fact that the tag actually exists!
            time.sleep(3)

        prerelease = 'Beta' in version

        # Create the Github Release
        release = repo.create_release(
            tag_name=self.tag_name,
            name=version,
            prerelease=prerelease,
        )
        self.return_values = {'tag_name': self.tag_name}
        self.logger.info('Created release {} at {}'.format(
            release.name, release.html_url))