Beispiel #1
0
 def remote(self) -> str:
     if not self._remote:
         # lines of "origin	[email protected]:ClickHouse/ClickHouse.git (fetch)"
         remotes = git_runner("git remote -v").split("\n")
         # We need the first word from the first matching result
         self._remote = tuple(
             remote.split(maxsplit=1)[0] for remote in remotes
             if f"github.com/{self._repo_name}" in remote  # https
             or f"github.com:{self._repo_name}" in remote  # ssh
         )[0]
         git_runner(f"git fetch {self._remote}")
         ReleaseBranch.REMOTE = self._remote
     return self._remote
Beispiel #2
0
def commit_push_staged(pr_info: PRInfo):
    # It works ONLY for PRs, and only over ssh, so either
    # ROBOT_CLICKHOUSE_SSH_KEY should be set or ssh-agent should work
    assert pr_info.number
    if not pr_info.head_name == pr_info.base_name:
        # We can't push to forks, sorry folks
        return
    git_staged = git_runner("git diff --cached --name-only")
    if not git_staged:
        return
    remote_url = pr_info.event["pull_request"]["base"]["repo"]["ssh_url"]
    git_prefix = (  # All commits to remote are done as robot-clickhouse
        "git -c [email protected] "
        "-c user.name=robot-clickhouse -c commit.gpgsign=false "
        "-c core.sshCommand="
        "'ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no'"
    )
    git_runner(f"{git_prefix} commit -m 'Automatic style fix'")
    push_cmd = (
        f"{git_prefix} push {remote_url} head-{pr_info.head_ref}:{pr_info.head_ref}"
    )
    if os.getenv("ROBOT_CLICKHOUSE_SSH_KEY", ""):
        with SSHKey("ROBOT_CLICKHOUSE_SSH_KEY"):
            git_runner(push_cmd)
    else:
        git_runner(push_cmd)
Beispiel #3
0
def clear_repo():
    orig_ref = git_runner("git branch --show-current") or git_runner(
        "git rev-parse HEAD")
    try:
        yield
    except (Exception, KeyboardInterrupt):
        git_runner(f"git checkout -f {orig_ref}")
        raise
    else:
        git_runner(f"git checkout -f {orig_ref}")
Beispiel #4
0
def stash():
    need_stash = bool(git_runner("git diff HEAD"))
    if need_stash:
        git_runner(
            "git stash push --no-keep-index -m 'running cherry_pick.py'")
    try:
        with clear_repo():
            yield
    except (Exception, KeyboardInterrupt):
        if need_stash:
            git_runner("git stash pop")
        raise
    else:
        if need_stash:
            git_runner("git stash pop")
Beispiel #5
0
 def receive_prs_for_backport(self):
     # The commit is the oldest open release branch's merge-base
     since_commit = git_runner(
         f"git merge-base {self.remote}/{self.release_branches[0]} "
         f"{self.remote}/{self.default_branch}")
     since_date = date.fromisoformat(
         git_runner.run(f"git log -1 --format=format:%cs {since_commit}"))
     # To not have a possible TZ issues
     tomorrow = date.today() + timedelta(days=1)
     logging.info("Receive PRs suppose to be backported")
     self.prs_for_backport = self.gh.get_pulls_from_search(
         query=f"{self._query} -label:pr-backported",
         label=",".join(self.labels_to_backport +
                        [Labels.LABEL_MUST_BACKPORT]),
         merged=[since_date, tomorrow],
     )
     logging.info(
         "PRs to be backported:\n %s",
         "\n ".join([pr.html_url for pr in self.prs_for_backport]),
     )
Beispiel #6
0
 def pre_check(self):
     branch_updated = git_runner(
         f"git branch -a --contains={self.pr.merge_commit_sha} "
         f"{self.REMOTE}/{self.name}")
     if branch_updated:
         self._backported = True
Beispiel #7
0
    def create_backport(self):
        # Checkout the backport branch from the remote and make all changes to
        # apply like they are only one cherry-pick commit on top of release
        git_runner(f"{self.git_prefix} checkout -f {self.backport_branch}")
        git_runner(
            f"{self.git_prefix} pull --ff-only {self.REMOTE} {self.backport_branch}"
        )
        merge_base = git_runner(
            f"{self.git_prefix} merge-base "
            f"{self.REMOTE}/{self.name} {self.backport_branch}")
        git_runner(f"{self.git_prefix} reset --soft {merge_base}")
        title = f"Backport #{self.pr.number} to {self.name}: {self.pr.title}"
        git_runner(f"{self.git_prefix} commit -a --allow-empty -F -",
                   input=title)

        # Push with force, create the backport PR, lable and assign it
        git_runner(f"{self.git_prefix} push -f {self.REMOTE} "
                   f"{self.backport_branch}:{self.backport_branch}")
        self.backport_pr = self.pr.base.repo.create_pull(
            title=title,
            body=f"Original pull-request #{self.pr.number}\n"
            f"Cherry-pick pull-request #{self.cherrypick_pr.number}\n\n"
            f"{self.BACKPORT_DESCRIPTION}",
            base=self.name,
            head=self.backport_branch,
        )
        self.backport_pr.add_to_labels(Labels.LABEL_BACKPORT)
        if self.pr.assignee is not None:
            self.cherrypick_pr.add_to_assignees(self.pr.assignee)
        self.backport_pr.add_to_assignees(self.pr.user)
Beispiel #8
0
    def create_cherrypick(self):
        # First, create backport branch:
        # Checkout release branch with discarding every change
        git_runner(f"{self.git_prefix} checkout -f {self.name}")
        # Create or reset backport branch
        git_runner(f"{self.git_prefix} checkout -B {self.backport_branch}")
        # Merge all changes from PR's the first parent commit w/o applying anything
        # It will allow to create a merge commit like it would be a cherry-pick
        first_parent = git_runner(
            f"git rev-parse {self.pr.merge_commit_sha}^1")
        git_runner(f"{self.git_prefix} merge -s ours --no-edit {first_parent}")

        # Second step, create cherrypick branch
        git_runner(f"{self.git_prefix} branch -f "
                   f"{self.cherrypick_branch} {self.pr.merge_commit_sha}")

        # Check if there actually any changes between branches. If no, then no
        # other actions are required. It's possible when changes are backported
        # manually to the release branch already
        try:
            output = git_runner(
                f"{self.git_prefix} merge --no-commit --no-ff {self.cherrypick_branch}"
            )
            # 'up-to-date', 'up to date', who knows what else (╯°v°)╯ ^┻━┻
            if output.startswith("Already up") and output.endswith("date."):
                # The changes are already in the release branch, we are done here
                logging.info(
                    "Release branch %s already contain changes from %s",
                    self.name,
                    self.pr.number,
                )
                self._backported = True
                return
        except CalledProcessError:
            # There are most probably conflicts, they'll be resolved in PR
            git_runner(f"{self.git_prefix} reset --merge")
        else:
            # There are changes to apply, so continue
            git_runner(f"{self.git_prefix} reset --merge")

        # Push, create the cherrypick PR, lable and assign it
        for branch in [self.cherrypick_branch, self.backport_branch]:
            git_runner(
                f"{self.git_prefix} push -f {self.REMOTE} {branch}:{branch}")

        self.cherrypick_pr = self.pr.base.repo.create_pull(
            title=
            f"Cherry pick #{self.pr.number} to {self.name}: {self.pr.title}",
            body=f"Original pull-request #{self.pr.number}\n\n"
            f"{self.CHERRYPICK_DESCRIPTION}",
            base=self.backport_branch,
            head=self.cherrypick_branch,
        )
        self.cherrypick_pr.add_to_labels(Labels.LABEL_CHERRYPICK)
        self.cherrypick_pr.add_to_labels(Labels.LABEL_DO_NOT_TEST)
        if self.pr.assignee is not None:
            self.cherrypick_pr.add_to_assignees(self.pr.assignee)
        self.cherrypick_pr.add_to_assignees(self.pr.user)