def check_manifest():
    """Check the project manifest"""
    if util.PYPROJECT.exists() or util.SETUP_PY.exists():
        util.run("check-manifest -v")
    else:
        util.log(
            "Skipping check-manifest since there are no python package files")
def build_python(dist_dir):
    """Build Python dist files"""
    if not util.PYPROJECT.exists() and not util.SETUP_PY.exists():
        util.log(
            "Skipping build-python since there are no python package files")
        return
    python.build_dist(dist_dir)
Exemple #3
0
def check_python(dist_dir, test_cmd):
    """Check Python dist files"""
    for dist_file in glob(f"{dist_dir}/*"):
        if Path(dist_file).suffix not in [".gz", ".whl"]:
            util.log(f"Skipping non-python dist file {dist_file}")
            continue
        python.check_dist(dist_file, test_cmd=test_cmd)
def draft_changelog(version_spec, branch, repo, auth, dry_run):
    """Create a changelog entry PR"""
    repo = repo or util.get_repo()
    branch = branch or util.get_branch()
    version = util.get_version()

    tags = util.run("git --no-pager tag")
    if f"v{version}" in tags.splitlines():
        raise ValueError(f"Tag v{version} already exists")

    # Check out any unstaged files from version bump
    util.run("git checkout -- .")

    title = f"{changelog.PR_PREFIX} for {version} on {branch}"
    commit_message = f'git commit -a -m "{title}"'
    body = title

    # Check for multiple versions
    if util.PACKAGE_JSON.exists():
        body += npm.get_package_versions(version)

    body += '\n\nAfter merging this PR run the "Draft Release" Workflow with the following inputs'
    body += f"""
| Input  | Value |
| ------------- | ------------- |
| Target | {repo}  |
| Branch  | {branch}  |
| Version Spec | {version_spec} |
"""
    util.log(body)

    make_changelog_pr(auth, branch, repo, title, commit_message, body, dry_run=dry_run)
def make_changelog_pr(auth, branch, repo, title, commit_message, body, dry_run=False):
    repo = repo or util.get_repo()

    # Make a new branch with a uuid suffix
    pr_branch = f"changelog-{uuid.uuid1().hex}"

    if not dry_run:
        util.run("git --no-pager diff")
        util.run("git stash")
        util.run(f"git fetch origin {branch}")
        util.run(f"git checkout -b {pr_branch} origin/{branch}")
        util.run("git stash apply")

    # Add a commit with the message
    util.run(commit_message)

    # Create the pull
    owner, repo_name = repo.split("/")
    gh = GhApi(owner=owner, repo=repo_name, token=auth)

    base = branch
    head = pr_branch
    maintainer_can_modify = True

    if dry_run:
        util.log("Skipping pull request due to dry run")
        return

    util.run(f"git push origin {pr_branch}")

    #  title, head, base, body, maintainer_can_modify, draft, issue
    pull = gh.pulls.create(title, head, base, body, maintainer_can_modify, False, None)

    util.actions_output("pr_url", pull.html_url)
Exemple #6
0
def extract_dist(dist_dir, target):
    """Extract dist files from a dist_dir into a target dir"""
    names = []
    paths = sorted(glob(f"{dist_dir}/*.tgz"))
    util.log(f"Extracting {len(paths)} packages...")

    for package in paths:
        path = Path(package)

        data = extract_package(path)
        name = data["name"]

        # Skip if it is a private package
        if data.get("private", False):  # pragma: no cover
            util.log(f"Skipping private package {name}")
            continue

        names.append(name)

        pkg_dir = target / name
        if not pkg_dir.parent.exists():
            os.makedirs(pkg_dir.parent)

        tar = tarfile.open(path)
        tar.extractall(target)
        tar.close()

        shutil.move(str(target / "package"), str(pkg_dir))

    return names
def prep_git(branch, repo, auth, username, url, install=True):
    """Set up git"""
    repo = repo or util.get_repo()

    user_name = ""
    try:
        user_name = util.run("git config --global user.email")
    except Exception as e:
        pass

    if not user_name:
        # Use email address for the GitHub Actions bot
        # https://github.community/t/github-actions-bot-email-address/17204/6
        util.run(
            'git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"'
        )
        util.run('git config --global user.name "GitHub Action"')

    # Set up the repository
    checkout_dir = os.environ.get("RH_CHECKOUT_DIR", util.CHECKOUT_NAME)
    checkout_exists = False
    if osp.exists(osp.join(checkout_dir, ".git")):
        util.log("Git checkout already exists")
        checkout_exists = True

    if not checkout_exists:
        util.run(f"git init {checkout_dir}")

    orig_dir = os.getcwd()
    os.chdir(checkout_dir)

    if not url:
        if auth:
            url = f"https://{username}:{auth}@github.com/{repo}.git"
        else:
            url = f"https://github.com/{repo}.git"

    if osp.exists(url):
        url = util.normalize_path(url)

    if not checkout_exists:
        util.run(f"git remote add origin {url}")

    branch = branch or util.get_default_branch()

    util.run(f"git fetch origin {branch}")

    # Make sure we have *all* tags
    util.run("git fetch origin --tags")

    util.run(f"git checkout {branch}")

    # Install the package with test deps
    if util.SETUP_PY.exists() and install:
        util.run('pip install ".[test]"')

    os.chdir(orig_dir)

    return branch
def check_entry(branch, repo, auth, changelog_path, since, resolve_backports,
                output):
    """Check changelog entry"""
    branch = branch or util.get_branch()

    # Get the new version
    version = util.get_version()

    # Finalize changelog
    changelog = Path(changelog_path).read_text(encoding="utf-8")

    start = changelog.find(START_MARKER)
    end = changelog.find(END_MARKER)

    if start == -1 or end == -1:  # pragma: no cover
        raise ValueError("Missing new changelog entry delimiter(s)")

    if start != changelog.rfind(START_MARKER):  # pragma: no cover
        raise ValueError("Insert marker appears more than once in changelog")

    final_entry = changelog[start + len(START_MARKER):end]

    repo = repo or util.get_repo()

    raw_entry = get_version_entry(
        f"origin/{branch}",
        repo,
        version,
        since=since,
        auth=auth,
        resolve_backports=resolve_backports,
    )

    if f"# {version}" not in final_entry:  # pragma: no cover
        util.log(final_entry)
        raise ValueError(f"Did not find entry for {version}")

    final_prs = re.findall(r"\[#(\d+)\]", final_entry)
    raw_prs = re.findall(r"\[#(\d+)\]", raw_entry)

    for pr in raw_prs:
        # Allow for changelog PR to not be in changelog itself
        skip = False
        for line in raw_entry.splitlines():
            if f"[#{pr}]" in line and "changelog" in line.lower():
                skip = True
                break
        if skip:
            continue
        if not f"[#{pr}]" in final_entry:  # pragma: no cover
            raise ValueError(f"Missing PR #{pr} in changelog")
    for pr in final_prs:
        if not f"[#{pr}]" in raw_entry:  # pragma: no cover
            raise ValueError(
                f"PR #{pr} does not belong in changelog for {version}")

    if output:
        Path(output).write_text(final_entry, encoding="utf-8")
Exemple #9
0
def start_local_pypi():
    """Start a local PyPI server"""
    temp_dir = TemporaryDirectory()
    cmd = f"pypi-server -p 8081  -P . -a . -o  -v {temp_dir.name}"
    proc = Popen(shlex.split(cmd), stderr=PIPE)
    # Wait for the server to start
    while True:
        line = proc.stderr.readline().decode("utf-8").strip()
        util.log(line)
        if "Listening on" in line:
            break
    atexit.register(proc.kill)
    atexit.register(temp_dir.cleanup)
Exemple #10
0
def build_dist(package, dist_dir):
    """Build npm dist file(s) from a package"""
    # Clean the dist folder of existing npm tarballs
    os.makedirs(dist_dir, exist_ok=True)
    dest = Path(dist_dir)
    for pkg in glob(f"{dist_dir}/*.tgz"):
        os.remove(pkg)

    if osp.isdir(package):
        basedir = package
        tarball = osp.join(package,
                           util.run("npm pack", cwd=package).split("\n")[-1])
    else:
        basedir = osp.dirname(package)
        tarball = package

    data = extract_package(tarball)

    # Move the tarball into the dist folder if public
    if not data.get("private", False) == True:
        shutil.move(str(tarball), str(dest))
    elif osp.isdir(package):
        os.remove(tarball)

    if not osp.isdir(package):
        return

    if "workspaces" in data:
        all_data = dict()
        for pattern in _get_workspace_packages(data):
            for path in glob(osp.join(basedir, pattern), recursive=True):
                path = Path(path)
                package_json = path / "package.json"
                if not osp.exists(package_json):
                    continue
                data = json.loads(package_json.read_text(encoding="utf-8"))
                if data.get("private", False) == True:
                    continue
                data["__path__"] = path
                all_data[data["name"]] = data

        i = 0
        for (name, data) in sorted(all_data.items()):
            i += 1
            path = data["__path__"]
            util.log(f'({i}/{len(all_data)}) Packing {data["name"]}...')
            tarball = path / util.run("npm pack", cwd=path, quiet=True)
            shutil.move(str(tarball), str(dest))
def publish_assets(dist_dir, npm_token, npm_cmd, twine_cmd, dry_run, use_checkout_dir):
    """Publish release asset(s)"""
    if use_checkout_dir:
        if not osp.exists(util.CHECKOUT_NAME):
            raise ValueError("Please run prep-git first")
        os.chdir(util.CHECKOUT_NAME)

    if dry_run:
        # Start local pypi server with no auth, allowing overwrites,
        # in a temporary directory
        temp_dir = TemporaryDirectory()
        cmd = f"pypi-server -p 8081  -P . -a . -o  -v {temp_dir.name}"
        proc = Popen(shlex.split(cmd), stderr=PIPE)
        # Wait for the server to start
        while True:
            line = proc.stderr.readline().decode("utf-8").strip()
            util.log(line)
            if "Listening on" in line:
                break
        atexit.register(proc.kill)
        atexit.register(temp_dir.cleanup)
        twine_cmd = "twine upload --repository-url=http://localhost:8081"
        os.environ["TWINE_USERNAME"] = "******"
        os.environ["TWINE_PASSWORD"] = "******"
        npm_cmd = "npm publish --dry-run"

    if npm_token:
        npm.handle_auth_token(npm_token)

    found = False
    for path in glob(f"{dist_dir}/*.*"):
        name = Path(path).name
        suffix = Path(path).suffix
        if suffix in [".gz", ".whl"]:
            util.run(f"{twine_cmd} {name}", cwd=dist_dir)
            found = True
        elif suffix == ".tgz":
            util.run(f"{npm_cmd} {name}", cwd=dist_dir)
            found = True
        else:
            util.log(f"Nothing to upload for {name}")

    if not found:  # pragma: no cover
        raise ValueError("No assets published, refusing to finalize release")
Exemple #12
0
def tag_workspace_packages():
    """Generate tags for npm workspace packages"""
    if not PACKAGE_JSON.exists():
        return

    data = json.loads(PACKAGE_JSON.read_text(encoding="utf-8"))
    tags = util.run("git tag").splitlines()
    if not "workspaces" in data:
        return

    for pattern in _get_workspace_packages(data):
        for path in glob(pattern, recursive=True):
            sub_package_json = Path(path) / "package.json"
            sub_data = json.loads(sub_package_json.read_text(encoding="utf-8"))
            tag_name = f"{sub_data['name']}@{sub_data['version']}"
            if tag_name in tags:
                util.log(f"Skipping existing tag {tag_name}")
            else:
                util.run(f"git tag {tag_name}")
Exemple #13
0
def publish_release(auth, dist_dir, npm_token, npm_cmd, twine_cmd, dry_run,
                    release_url):
    """Publish release asset(s) and finalize GitHub release"""
    util.log(f"Publishing {release_url} in with dry run: {dry_run}")

    match = parse_release_url(release_url)

    if npm_token:
        npm.handle_auth_token(npm_token)

    found = False
    for path in glob(f"{dist_dir}/*.*"):
        name = Path(path).name
        suffix = Path(path).suffix
        if suffix in [".gz", ".whl"]:
            util.run(f"{twine_cmd} {name}", cwd=dist_dir)
            found = True
        elif suffix == ".tgz":
            util.run(f"{npm_cmd} {name}", cwd=dist_dir)
            found = True
        else:
            util.log(f"Nothing to upload for {name}")

    if not found:  # pragma: no cover
        raise ValueError("No assets published, refusing to finalize release")

    # Take the release out of draft
    gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
    release = util.release_for_url(gh, release_url)

    release = gh.repos.update_release(
        release.id,
        release.tag_name,
        release.target_commitish,
        release.name,
        release.body,
        dry_run,
        release.prerelease,
    )

    # Set the GitHub action output
    util.actions_output("release_url", release.html_url)
def publish_release(auth, release_url):
    """Publish GitHub release"""
    util.log(f"Publishing {release_url}")

    match = parse_release_url(release_url)

    # Take the release out of draft
    gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
    release = util.release_for_url(gh, release_url)

    release = gh.repos.update_release(
        release.id,
        release.tag_name,
        release.target_commitish,
        release.name,
        release.body,
        False,
        release.prerelease,
    )

    # Set the GitHub action output
    util.actions_output("release_url", release.html_url)
Exemple #15
0
def publish_assets(dist_dir, npm_token, npm_cmd, twine_cmd, dry_run,
                   use_checkout_dir):
    """Publish release asset(s)"""
    if use_checkout_dir:
        if not osp.exists(util.CHECKOUT_NAME):
            raise ValueError("Please run prep-git first")
        os.chdir(util.CHECKOUT_NAME)

    if dry_run:
        # Start local pypi server with no auth, allowing overwrites,
        # in a temporary directory
        if len(glob(f"{dist_dir}/*.whl")):
            python.start_local_pypi()
            twine_cmd = "twine upload --repository-url=http://localhost:8081"
            os.environ["TWINE_USERNAME"] = "******"
            os.environ["TWINE_PASSWORD"] = "******"
        npm_cmd = "npm publish --dry-run"
    else:
        os.environ.setdefault("TWINE_USERNAME", "__token__")

    if len(glob(f"{dist_dir}/*.tgz")):
        npm.handle_npm_config(npm_token)

    found = False
    for path in glob(f"{dist_dir}/*.*"):
        name = Path(path).name
        suffix = Path(path).suffix
        if suffix in [".gz", ".whl"]:
            util.run(f"{twine_cmd} {name}", cwd=dist_dir)
            found = True
        elif suffix == ".tgz":
            util.run(f"{npm_cmd} {name}", cwd=dist_dir)
            found = True
        else:
            util.log(f"Nothing to upload for {name}")

    if not found:  # pragma: no cover
        raise ValueError("No assets published, refusing to finalize release")
Exemple #16
0
def check_npm(dist_dir, test_cmd):
    """Check npm package"""
    if not osp.exists("./package.json"):
        util.log("Skipping check-npm since there is no package.json file")
        return
    npm.check_dist(dist_dir, test_cmd=test_cmd)
def extract_release(auth, dist_dir, dry_run, release_url):
    """Download and verify assets from a draft GitHub release"""
    match = parse_release_url(release_url)
    owner, repo = match["owner"], match["repo"]
    gh = GhApi(owner=owner, repo=repo, token=auth)
    release = util.release_for_url(gh, release_url)
    assets = release.assets

    # Clean the dist folder
    dist = Path(dist_dir)
    if dist.exists():
        shutil.rmtree(dist, ignore_errors=True)
    os.makedirs(dist)

    # Fetch, validate, and publish assets
    for asset in assets:
        util.log(f"Fetching {asset.name}...")
        url = asset.url
        headers = dict(Authorization=f"token {auth}", Accept="application/octet-stream")
        path = dist / asset.name
        with requests.get(url, headers=headers, stream=True) as r:
            r.raise_for_status()
            with open(path, "wb") as f:
                for chunk in r.iter_content(chunk_size=8192):
                    f.write(chunk)
            suffix = Path(asset.name).suffix
            if suffix in [".gz", ".whl"]:
                python.check_dist(path)
            elif suffix == ".tgz":
                npm.check_dist(path)
            else:
                util.log(f"Nothing to check for {asset.name}")

    # Skip sha validation for dry runs since the remote tag will not exist
    if dry_run:
        return

    branch = release.target_commitish
    tag_name = release.tag_name

    sha = None
    for tag in gh.list_tags():
        if tag.ref == f"refs/tags/{tag_name}":
            sha = tag.object.sha
    if sha is None:
        raise ValueError("Could not find tag")

    # Run a git checkout
    # Fetch the branch
    # Get the commmit message for the branch
    commit_message = ""
    with TemporaryDirectory() as td:
        url = gh.repos.get().html_url
        util.run(f"git clone {url} local", cwd=td)
        checkout = osp.join(td, "local")
        if not osp.exists(url):
            util.run(f"git fetch origin {branch}", cwd=checkout)
        commit_message = util.run(f"git log --format=%B -n 1 {sha}", cwd=checkout)

    for asset in assets:
        # Check the sha against the published sha
        valid = False
        path = dist / asset.name
        sha = util.compute_sha256(path)

        for line in commit_message.splitlines():
            if asset.name in line:
                if sha in line:
                    valid = True
                else:
                    util.log("Mismatched sha!")

        if not valid:  # pragma: no cover
            raise ValueError(f"Invalid file {asset.name}")
def draft_release(
    ref,
    branch,
    repo,
    auth,
    changelog_path,
    version_cmd,
    dist_dir,
    dry_run,
    post_version_spec,
    assets,
):
    """Publish Draft GitHub release and handle post version bump"""
    branch = branch or util.get_branch()
    repo = repo or util.get_repo()
    assets = assets or glob(f"{dist_dir}/*")
    version = util.get_version()
    body = changelog.extract_current(changelog_path)
    prerelease = util.is_prerelease(version)

    # Bump to post version if given
    if post_version_spec:
        post_version = bump_version(post_version_spec, version_cmd)

        util.log(f"Bumped version to {post_version}")
        util.run(f'git commit -a -m "Bump to {post_version}"')

    if dry_run:
        return

    owner, repo_name = repo.split("/")
    gh = GhApi(owner=owner, repo=repo_name, token=auth)

    # Remove draft releases over a day old
    if bool(os.environ.get("GITHUB_ACTIONS")):
        for release in gh.repos.list_releases():
            if str(release.draft).lower() == "false":
                continue
            created = release.created_at
            d_created = datetime.strptime(created, r"%Y-%m-%dT%H:%M:%SZ")
            delta = datetime.utcnow() - d_created
            if delta.days > 0:
                gh.repos.delete_release(release.id)

    remote_url = util.run("git config --get remote.origin.url")
    if not os.path.exists(remote_url):
        util.run(f"git push origin HEAD:{branch} --follow-tags --tags")

    util.log(f"Creating release for {version}")
    util.log(f"With assets: {assets}")
    release = gh.create_release(
        f"v{version}",
        branch,
        f"Release v{version}",
        body,
        True,
        prerelease,
        files=assets,
    )

    # Set the GitHub action output
    util.actions_output("release_url", release.html_url)
def get_version_entry(branch,
                      repo,
                      version,
                      *,
                      auth=None,
                      resolve_backports=False):
    """Get a changelog for the changes since the last tag on the given branch.

    Parameters
    ----------
    branch : str
        The target branch
    respo : str
        The GitHub owner/repo
    version : str
        The new version
    auth : str, optional
        The GitHub authorization token
    resolve_backports: bool, optional
        Whether to resolve backports to the original PR

    Returns
    -------
    str
        A formatted changelog entry with markers
    """
    tags = util.run(
        f"git --no-pager tag --sort=-creatordate --merged {branch}")
    if not tags:  # pragma: no cover
        raise ValueError(f"No tags found on branch {branch}")

    since = tags.splitlines()[0]
    branch = branch.split("/")[-1]
    util.log(f"Getting changes to {repo} since {since} on branch {branch}...")

    md = generate_activity_md(repo,
                              since=since,
                              kind="pr",
                              heading_level=2,
                              auth=auth,
                              branch=branch)

    if not md:
        util.log("No PRs found")
        return f"## {version}\n\nNo merged PRs"

    entry = md.replace("[full changelog]", "[Full Changelog]")

    entry = entry.splitlines()[2:]

    for (ind, line) in enumerate(entry):
        if re.search(r"\[@meeseeksmachine\]", line) is not None:
            match = re.search(r"Backport PR #(\d+)", line)
            if match:
                entry[ind] = format_pr_entry(repo, match.groups()[0])

    # Remove github actions PRs
    gh_actions = "[@github-actions](https://github.com/github-actions)"
    entry = [e for e in entry if gh_actions not in e]

    # Remove automated changelog PRs
    entry = [e for e in entry if PR_PREFIX not in e]

    entry = "\n".join(entry).strip()

    # Replace "*" unordered list marker with "-" since this is what
    # Prettier uses
    # TODO: remove after github_activity 0.1.4+ is available
    entry = re.sub(r"^\* ", "- ", entry)
    entry = re.sub(r"\n\* ", "\n- ", entry)

    output = f"""
## {version}

{entry}
""".strip()

    return output
Exemple #20
0
    def invoke(self, ctx):
        """Handle jupyter-releaser config while invoking a command"""
        # Get the command name and make sure it is valid
        cmd_name = ctx.protected_args[0]
        if not cmd_name in self.commands:
            super().invoke(ctx)

        if cmd_name == "list-envvars":
            envvars = dict()
            for cmd_name in self.commands:
                for param in self.commands[cmd_name].params:
                    if isinstance(param, click.Option):
                        if param.envvar:
                            envvars[param.name] = param.envvar

            for key in sorted(envvars):
                util.log(f"{key.replace('_', '-')}: {envvars[key]}")

            return

        orig_dir = os.getcwd()

        if cmd_name.replace("-", "_") in self._needs_checkout_dir:
            if not osp.exists(util.CHECKOUT_NAME):
                raise ValueError("Please run prep-git first")
            os.chdir(util.CHECKOUT_NAME)

        # Read in the config
        config = util.read_config()
        hooks = config.get("hooks", {})
        options = config.get("options", {})

        # Print a separation header
        util.log(f'\n\n{"-" * 50}')
        util.log(cmd_name)
        util.log(f'{"-" * 50}\n\n')

        # Handle all of the parameters
        for param in self.commands[cmd_name].get_params(ctx):
            # Defer to env var overrides
            if param.envvar and os.environ.get(param.envvar):
                continue
            name = param.name
            if name in options or name.replace("_", "-") in options:
                arg = f"--{name.replace('_', '-')}"
                # Defer to cli overrides
                if arg not in ctx.args:
                    val = options.get(name, options.get(name.replace("_", "-")))
                    if isinstance(val, list):
                        for v in val:
                            ctx.args.append(arg)
                            ctx.args.append(v)
                    else:
                        ctx.args.append(arg)
                        ctx.args.append(val)

        # Handle before hooks
        before = f"before-{cmd_name}"
        if before in hooks:
            before_hooks = hooks[before]
            if isinstance(before_hooks, str):
                before_hooks = [before_hooks]
            for hook in before_hooks:
                util.run(hook)

        # Run the actual command
        super().invoke(ctx)

        # Handle after hooks
        after = f"after-{cmd_name}"
        if after in hooks:
            after_hooks = hooks[after]
            if isinstance(after_hooks, str):
                after_hooks = [after_hooks]
            for hook in after_hooks:
                util.run(hook)

        os.chdir(orig_dir)
def forwardport_changelog(
    auth, ref, branch, repo, username, changelog_path, dry_run, git_url, release_url
):
    """Forwardport Changelog Entries to the Default Branch"""
    # Set up the git repo with the branch
    match = parse_release_url(release_url)
    gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
    release = util.release_for_url(gh, release_url)
    tag = release.tag_name

    repo = f'{match["owner"]}/{match["repo"]}'

    # We want to target the main branch
    orig_dir = os.getcwd()
    branch = prep_git(None, None, repo, auth, username, git_url, install=False)
    os.chdir(util.CHECKOUT_NAME)

    # Bail if the tag has been merged to the branch
    tags = util.run(f"git --no-pager tag --merged {branch}")
    if tag in tags.splitlines():
        util.log(f"Skipping since tag is already merged into {branch}")
        return

    # Get the entry for the tag
    util.run(f"git checkout {tag}")
    entry = changelog.extract_current(changelog_path)

    # Get the previous header for the branch
    full_log = Path(changelog_path).read_text(encoding="utf-8")
    start = full_log.index(changelog.END_MARKER)

    prev_header = ""
    for line in full_log[start:].splitlines():
        if line.strip().startswith("#"):
            prev_header = line
            break

    if not prev_header:
        raise ValueError("No anchor for previous entry")

    # Check out the branch again
    util.run(f"git checkout -B {branch} origin/{branch}")

    default_entry = changelog.extract_current(changelog_path)

    # Look for the previous header
    default_log = Path(changelog_path).read_text(encoding="utf-8")
    if not prev_header in default_log:
        util.log(
            f'Could not find previous header "{prev_header}" in {changelog_path} on branch {branch}'
        )
        return

    # If the previous header is the current entry in the default branch, we need to move the change markers
    if prev_header in default_entry:
        default_log = changelog.insert_entry(default_log, entry)

    # Otherwise insert the new entry ahead of the previous header
    else:
        insertion_point = default_log.index(prev_header)
        default_log = changelog.format(
            default_log[:insertion_point] + entry + default_log[insertion_point:]
        )

    Path(changelog_path).write_text(default_log, encoding="utf-8")

    # Create a forward port PR
    title = f"{changelog.PR_PREFIX} Forward Ported from {tag}"
    commit_message = f'git commit -a -m "{title}"'
    body = title

    pr = make_changelog_pr(
        auth, ref, branch, repo, title, commit_message, body, dry_run=dry_run
    )

    # Clean up after ourselves
    os.chdir(orig_dir)
    shutil.rmtree(util.CHECKOUT_NAME)
def publish_release(auth, dist_dir, npm_token, npm_cmd, twine_cmd, dry_run,
                    release_url):
    """Publish release asset(s) and finalize GitHub release"""
    util.log(f"Publishing {release_url} in with dry run: {dry_run}")

    if dry_run:
        # Start local pypi server with no auth, allowing overwrites,
        # in a temporary directory
        temp_dir = TemporaryDirectory()
        cmd = f"pypi-server -p 8081  -P . -a . -o  -v {temp_dir.name}"
        proc = Popen(shlex.split(cmd), stderr=PIPE)
        # Wait for the server to start
        while True:
            line = proc.stderr.readline().decode("utf-8").strip()
            util.log(line)
            if "Listening on" in line:
                break
        atexit.register(proc.kill)
        atexit.register(temp_dir.cleanup)
        twine_cmd = "twine upload --repository-url=http://localhost:8081"
        os.environ["TWINE_USERNAME"] = "******"
        os.environ["TWINE_PASSWORD"] = "******"
        npm_cmd = "npm publish --dry-run"

    match = parse_release_url(release_url)

    if npm_token:
        npm.handle_auth_token(npm_token)

    found = False
    for path in glob(f"{dist_dir}/*.*"):
        name = Path(path).name
        suffix = Path(path).suffix
        if suffix in [".gz", ".whl"]:
            util.run(f"{twine_cmd} {name}", cwd=dist_dir)
            found = True
        elif suffix == ".tgz":
            util.run(f"{npm_cmd} {name}", cwd=dist_dir)
            found = True
        else:
            util.log(f"Nothing to upload for {name}")

    if not found:  # pragma: no cover
        raise ValueError("No assets published, refusing to finalize release")

    # Take the release out of draft
    gh = GhApi(owner=match["owner"], repo=match["repo"], token=auth)
    release = util.release_for_url(gh, release_url)

    release = gh.repos.update_release(
        release.id,
        release.tag_name,
        release.target_commitish,
        release.name,
        release.body,
        dry_run,
        release.prerelease,
    )

    # Set the GitHub action output
    util.actions_output("release_url", release.html_url)
Exemple #23
0
def build_npm(package, dist_dir):
    """Build npm package"""
    if not osp.exists("./package.json"):
        util.log("Skipping check-npm since there is no package.json file")
        return
    npm.build_dist(package, dist_dir)
Exemple #24
0
def prep_git(ref, branch, repo, auth, username, url, install=True):
    """Set up git"""
    repo = repo or util.get_repo()

    user_name = ""
    try:
        user_name = util.run("git config --global user.email")
    except Exception as e:
        pass

    if not user_name:
        # Use email address for the GitHub Actions bot
        # https://github.community/t/github-actions-bot-email-address/17204/6
        util.run(
            'git config --global user.email "41898282+github-actions[bot]@users.noreply.github.com"'
        )
        util.run('git config --global user.name "GitHub Action"')

    # Set up the repository
    checkout_dir = os.environ.get("RH_CHECKOUT_DIR", util.CHECKOUT_NAME)
    checkout_exists = False
    if osp.exists(osp.join(checkout_dir, ".git")):
        util.log("Git checkout already exists")
        checkout_exists = True

    if not checkout_exists:
        util.run(f"git init {checkout_dir}")

    orig_dir = os.getcwd()
    os.chdir(checkout_dir)

    if not url:
        if auth:
            url = f"https://{username}:{auth}@github.com/{repo}.git"
        else:
            url = f"https://github.com/{repo}.git"

    if osp.exists(url):
        url = util.normalize_path(url)

    if not checkout_exists:
        util.run(f"git remote add origin {url}")

    branch = branch or util.get_default_branch()
    ref = ref or ""

    # Make sure we have *all* tags
    util.run("git fetch origin --tags --force")

    # Handle the ref
    if ref.startswith("refs/pull/"):
        pull = ref[len("refs/pull/"):]
        ref_alias = f"refs/pull/{pull}"
    else:
        ref = None

    # Reuse existing branch if possible
    if ref:
        util.run(f"git fetch origin +{ref}:{ref_alias}")
        util.run(f"git fetch origin {ref}")
        checkout_cmd = f"git checkout -B {branch} {ref_alias}"
    else:
        util.run(f"git fetch origin {branch}")
        checkout_cmd = f"git checkout {branch}"

    if checkout_exists:
        try:
            util.run(f"git checkout {branch}")
        except Exception:
            util.run(checkout_cmd)
    else:
        util.run(checkout_cmd)

    # Install the package
    if install:
        # install python package with test deps
        if util.SETUP_PY.exists():
            util.run('pip install ".[test]"')

        # prefer yarn if yarn lock exists
        elif util.YARN_LOCK.exists():
            util.run("npm install -g yarn")
            util.run("yarn")

        # npm install otherwise
        elif util.PACKAGE_JSON.exists():
            util.run("npm install")

    os.chdir(orig_dir)

    return branch