Пример #1
0
def check_branch_version(
    current_branch: str, kind: BranchKind, next_version: version.Version, prev_version: Optional[version.Version]
):
    if prev_version and kind == BranchKind.RELEASE and prev_version.release != next_version.release:
        raise ValueError("The semantic version cannot be changed on a release branch.")
    expected_release_branch_name = "release/v" + ".".join(str(i) for i in next_version.release)
    if kind == BranchKind.RELEASE and current_branch != expected_release_branch_name:
        raise StateError(
            "The semantic version does not match the release branch name: "
            f"{expected_release_branch_name} != {current_branch}"
        )
    if prev_version and kind != BranchKind.VARIANT and prev_version.local:
        raise StateError(
            f"The non-variant branch {current_branch} has a variant. This should not happen. Somebody broke the rules."
        )
    if kind != BranchKind.VARIANT and next_version.local:
        raise ValueError(f"The variant cannot be set on the non-variant branch {current_branch}.")
    if kind == BranchKind.VARIANT and next_version.local != current_branch.split("/", maxsplit=1)[1]:
        branch_variant = current_branch.split("/", maxsplit=1)[1]
        raise StateError(
            "The variant in the version and the variant in the branch name must be the same: "
            f"{next_version.local} != {branch_variant}"
        )
    if prev_version and next_version <= prev_version:
        raise ValueError(f"The next version ({next_version}) must be greater than the current one ({prev_version})")
    if prev_version and kind == BranchKind.VARIANT and next_version.release != prev_version.release:
        raise ValueError(
            "To change the version of a variant branch, merge master into the variant branch. "
            "Bumping the version directly on the variant branch is not allowed."
        )
Пример #2
0
def check_clean(args, config):
    if not config.open:
        raise StateError("Action cannot run in a source tree that is not a git clone.")

    is_dirty = git.is_dirty(config.open) or (config.has_enterprise and git.is_dirty(config.enterprise))
    if not args.clean and is_dirty:
        raise StateError("Action only supported in clean repositories. (Stash your changes.)")
Пример #3
0
def check_at_branch(branch, config):
    check_remotes(config)
    if git.get_hash(f"{config.open.upstream_remote}/{branch}", config.open) != git.get_hash(git.HEAD, config.open):
        raise StateError(f"{config.open.dir} HEAD is up to date with {branch}")

    if config.has_enterprise and git.get_hash(
        f"{config.enterprise.upstream_remote}/{branch}", config.enterprise
    ) != git.get_hash(git.HEAD, config.enterprise):
        raise StateError(f"{config.enterprise.dir} HEAD is up to date with {branch}")
Пример #4
0
def get_current_branch_from_either_repository(config: Configuration):
    current_branch = git.get_branch_checked_out(config.open, ref_only=True)
    if config.has_enterprise:
        current_branch = current_branch or git.get_branch_checked_out(config.enterprise, ref_only=True)
    if not current_branch:
        raise StateError("Operation is not supported without a branch checked out (currently HEAD is detached).")
    return current_branch
Пример #5
0
def tag_subcommand(args):
    config: Configuration = args.configuration

    check_remotes(config)
    check_clean(args, config)

    commit = git.HEAD

    current_branch = get_current_branch_from_either_repository(config)
    kind = get_branch_kind(current_branch, (BranchKind.RELEASE, BranchKind.VARIANT))

    if (
        not git.is_ancestor_of(commit, f"{config.open.upstream_remote}/{current_branch}", config.open)
        and args.require_upstream
        and not args.pretend_upstream
    ):
        raise StateError(f"HEAD of {current_branch} is not upstream")

    if (
        not git.is_ancestor_of(commit, f"{config.enterprise.upstream_remote}/{current_branch}", config.enterprise)
        and args.require_upstream
        and not args.pretend_upstream
    ):
        raise StateError(f"HEAD of {current_branch} is not upstream")

    next_version = version.Version(args.version)

    check_branch_version(current_branch, kind, next_version, prev_version=None)

    tag_name = f"v{format_version_pep440(next_version)}"
    title = f"Version {format_version_pep440(next_version)}"

    g = GithubFacade(config)

    def tag_repo(repo: Repo):
        if git.is_ancestor_of(commit, f"{repo.upstream_remote}/{current_branch}", repo) or args.pretend_upstream:
            g.create_tag(repo.upstream_url, git.get_hash(commit, repo, pretend_clean=True), tag_name, message=title)
        else:
            raise NotImplementedError("To tag a release commit, the commit must already be in the upstream branch")

    tag_repo(config.open)
    if config.has_enterprise:
        tag_repo(config.enterprise)
    fetch_upstream(config)

    warn_dry_run(args)
Пример #6
0
def update_dependent_pr_subcommand(args):
    config: Configuration = args.configuration

    check_remotes(config)
    check_clean(args, config)

    g = GithubFacade(config)

    enterprise_pr = g.get_pr(config.enterprise.upstream_url, number=args.number)
    if enterprise_pr.commits > 1:
        raise NotImplementedError(
            "update_dependent_pr only supports single commit PRs. (It could be implemented if needed.)"
        )
    after_match = PR_AFTER_RE.search(enterprise_pr.body)
    if not after_match:
        raise ValueError(
            f"PR {enterprise_pr.base.repo.full_name}#{enterprise_pr.number} does not have an 'After:' annotation."
        )
    if after_match.group("external_number"):
        repo_full_name = "{username}/{repository}".format(**after_match.groupdict())
        open_repo = g.github.get_repo(repo_full_name)
        open_pr = open_repo.get_pull(int(after_match.group("external_number")))
        if not open_pr.merged:
            raise StateError(f"The dependency {open_repo.full_name}#{open_pr.number} is not merged.")

        enterprise_original_branch = git.get_branch_checked_out(config.enterprise)
        open_original_branch = git.get_branch_checked_out(config.open)

        git.switch(enterprise_pr.head.ref, config.enterprise, config.dry_run)
        git.switch(open_pr.merge_commit_sha, config.open, config.dry_run)

        git.commit_amend([SUBMODULE_PATH], config.enterprise, config.dry_run)
        git.push(config.enterprise.origin_remote, enterprise_pr.head.ref, config.enterprise, config.dry_run, force=True)

        git.switch(enterprise_original_branch, config.enterprise, config.dry_run)
        git.switch(open_original_branch, config.open, config.dry_run)

        warn_dry_run(args)

        return [f"TODO: Merge {enterprise_pr.html_url} as soon as possible."]
    raise StateError(
        "PR does not have an acceptable 'After:' annotation. Only external PR references are supported. "
        f"(Was '{after_match.group(0)}')"
    )
Пример #7
0
def release_branch_subcommand(args):
    config: Configuration = args.configuration
    check_clean(args, config)
    if not args.allow_arbitrary_branch:
        current_branch = get_current_branch_from_either_repository(config)
        get_branch_kind(current_branch, [BranchKind.MASTER])
        check_at_branch("master", config)
        if config.has_enterprise:
            git.switch("master", config.enterprise, config.dry_run)
        else:
            # Do not switch branches in open if we did so in enterprise. This allows external/katana to lag
            # at branch time.
            git.switch("master", config.open, config.dry_run)
    else:
        print(
            "WARNING: Branching from HEAD instead of upstream/master. Be careful! This will create an out of date "
            "release branch."
        )

    # Check if HEAD is on the upstream master branch.
    if (
        not git.is_ancestor_of(git.HEAD, f"{config.open.upstream_remote}/master", config.open)
        and not args.pretend_upstream
    ):
        raise StateError(f"{config.open.dir} HEAD is not on upstream master")

    if (
        config.has_enterprise
        and not git.is_ancestor_of(git.HEAD, f"{config.enterprise.upstream_remote}/master", config.enterprise)
        and not args.pretend_upstream
    ):
        raise StateError(f"{config.enterprise.dir} HEAD is not on upstream master")

    prev_version, _ = get_explicit_version(git.HEAD, True, config.open, config.version_file, no_dev=True)
    next_version = version.Version(args.next_version)
    rc_version = version.Version(f"{prev_version}rc1")

    # Always pretend we are on master. We either actually are, or the user has overridden things.
    check_branch_version("master", BranchKind.MASTER, next_version, prev_version)

    g = GithubFacade(config)

    # Create release branches.
    release_branch_name = f"release/v{format_version_pep440(prev_version)}"
    check_branch_version(release_branch_name, BranchKind.RELEASE, rc_version, add_dev_to_version(prev_version))
    g.create_branch(
        config.open.upstream_url, git.get_hash(git.HEAD, config.open, pretend_clean=True), release_branch_name,
    )
    if config.has_enterprise:
        g.create_branch(
            config.enterprise.upstream_url,
            git.get_hash(git.HEAD, config.enterprise, pretend_clean=True),
            release_branch_name,
        )

    # Create a PR on master which updates the version.txt to {next version}.
    todos = bump_both_repos(config, g, prev_version, next_version, "master")
    # Create a PR on the release branch which updates the version.txt to {version}rc1.
    todos.extend(bump_both_repos(config, g, prev_version, rc_version, release_branch_name))

    warn_dry_run(args)
    return todos
Пример #8
0
 def check_remote(repo, remote, remote_name):
     if not remote:
         raise StateError(f"{repo}: Repository must have an {remote_name} remote. Your work flow is not supported.")
Пример #9
0
def check_branch_not_exist(config: Configuration, branch_name):
    check_remotes(config)
    if git.ref_exists(branch_name, config.open):
        raise StateError(f"Branch {branch_name} already exists in {config.open.dir.name}")
    if git.ref_exists(branch_name, config.enterprise):
        raise StateError(f"Branch {branch_name} already exists in {config.enterprise.dir.name}")
Пример #10
0
def get_branch_kind(current_branch, kinds: Iterable[BranchKind]):
    for kind in kinds:
        if re.match(kind.value, current_branch):
            return kind
    kinds_str = ", ".join(k.value for k in kinds)
    raise StateError(f"The current branch ({current_branch}) should be one of: {kinds_str}")