All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.jenkins.updatebot.commands.UpdatePullRequests Maven / Gradle / Ivy

There is a newer version: 1.1.7
Show newest version
/*
 * Copyright 2016 Red Hat, Inc.
 *
 * Red Hat licenses this file to you under the Apache License, version
 * 2.0 (the "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
 * implied.  See the License for the specific language governing
 * permissions and limitations under the License.
 */
package io.jenkins.updatebot.commands;

import com.beust.jcommander.Parameter;
import com.beust.jcommander.Parameters;
import io.jenkins.updatebot.CommandNames;
import io.jenkins.updatebot.Configuration;
import io.jenkins.updatebot.UpdateBot;
import io.jenkins.updatebot.github.GitHubHelpers;
import io.jenkins.updatebot.github.PullRequests;
import io.jenkins.updatebot.support.Markdown;
import io.jenkins.updatebot.support.Strings;
import io.fabric8.utils.Objects;
import io.jenkins.updatebot.support.Systems;
import org.kohsuke.github.GHCommitState;
import org.kohsuke.github.GHCommitStatus;
import org.kohsuke.github.GHIssue;
import org.kohsuke.github.GHIssueComment;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GHUser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.util.Arrays;
import java.util.List;

import static io.jenkins.updatebot.EnvironmentVariables.CHECK_PR_STATUS;
import static io.jenkins.updatebot.EnvironmentVariables.DELETE_MERGED_BRANCHES;
import static io.jenkins.updatebot.EnvironmentVariables.MERGE;
import static io.jenkins.updatebot.EnvironmentVariables.MERGE_METHOD;
import static io.jenkins.updatebot.github.GitHubHelpers.getLastCommitStatus;
import static io.jenkins.updatebot.github.Issues.getLabels;
import static io.jenkins.updatebot.github.Issues.isOpen;
import static io.jenkins.updatebot.support.Markdown.UPDATEBOT;

/**
 * Updates any open pull requests, rebasing any that require rebasing, merging any that are ready or responding to comments
 */
@Parameters(commandNames = CommandNames.UPDATE, commandDescription = "Updates open Pull Requests. Rebases any unmergable PRs or merge any PRs that are ready.")
public class UpdatePullRequests extends CommandSupport {
    private static final transient Logger LOG = LoggerFactory.getLogger(UpdatePullRequests.class);

    @Parameter(names = "--merge", description = "Whether we should merge Pull Requests that are Open and have a successful last commit status", arity = 1)
    private boolean mergeOnSuccess = Systems.isConfigBoolean(MERGE,true);

    @Parameter(names = "--check-pr-status", description = "Whether we should check the status of Pull Requests before merging them", arity = 1)
    private boolean checkPrStatus = Systems.isConfigBoolean(CHECK_PR_STATUS,true);

    @Parameter(names = "--delete-merged-branches", description = "Whether we should delete updatebot branches after merging them", arity = 1)
    private boolean deleteMergedBranches = Systems.isConfigBoolean(DELETE_MERGED_BRANCHES,true);

    @Parameter(names = "--merge-method", description = "merge, rebase or squash. Default is merge", arity = 1)
    private String mergeMethod = Systems.getConfigValue(MERGE_METHOD,"merge");

    public boolean isMergeOnSuccess() {
        return mergeOnSuccess;
    }

    public void setMergeOnSuccess(boolean mergeOnSuccess) {
        this.mergeOnSuccess = mergeOnSuccess;
    }

    public boolean isCheckPrStatus() {
        return checkPrStatus;
    }

    public void setCheckPrStatus(boolean checkPrStatus) {
        this.checkPrStatus = checkPrStatus;
    }

    public boolean isDeleteMergedBranches() {
        return deleteMergedBranches;
    }

    public void setDeleteMergedBranches(boolean deleteMergedBranches) {
        this.deleteMergedBranches = deleteMergedBranches;
    }

    public String getMergeMethod() {
        return mergeMethod;
    }

    public void setMergeMethod(String mergeMethod) {
        this.mergeMethod = mergeMethod;
    }

    @Override
    public void run(CommandContext context) throws IOException {
        Status contextStatus = Status.COMPLETE;
        GHRepository ghRepository = context.gitHubRepository();

        if (ghRepository != null) {

            // lets look for a pending issue
            GHIssue issue = getOrFindIssue(context, ghRepository);
            if (issue != null && isOpen(issue)) {
                contextStatus = Status.PENDING;
            }

            List pullRequests = PullRequests.getOpenPullRequests(ghRepository, context.getConfiguration());
            for (GHPullRequest pullRequest : pullRequests) {
                Configuration configuration = context.getConfiguration();
                if (GitHubHelpers.hasLabel(getLabels(pullRequest), configuration.getGithubPullRequestLabel())) {
                    context.setPullRequest(pullRequest);

                    if (!GitHubHelpers.isMergeable(pullRequest) && checkPrStatus) {
                        // lets re-run the update commands we can find on the PR
                        CompositeCommand commands = loadCommandsFromPullRequest(context, ghRepository, pullRequest);
                        if (commands != null) {
                            commands.run(context, ghRepository, pullRequest);
                        }
                    }

                    if (mergeOnSuccess && checkPrStatus) {
                        try {
                            GHCommitStatus status = getLastCommitStatus(ghRepository, pullRequest);
                            ghRepository.getLastCommitStatus(pullRequest.getHead().getSha());
                            if (status != null) {
                                GHCommitState state = status.getState();
                                if (state != null && state.equals(GHCommitState.SUCCESS) && GitHubHelpers.checkCommitStatus(ghRepository,pullRequest,GHCommitState.SUCCESS)) {
                                    String message = Markdown.UPDATEBOT_ICON + " merging this pull request as its CI was successful";
                                    mergePr(pullRequest, message);
                                }
                            }
                        } catch (IOException e) {
                            context.warn(LOG, "Failed to find last commit status for PR " + pullRequest.getHtmlUrl() + " " + e, e);
                        }
                    }

                    //if pr status checks are skipped then just attempt to merge
                    if (!checkPrStatus) {
                        try {
                            String message = Markdown.UPDATEBOT_ICON + " merging this pull request - checks on PR status were skipped";
                            mergePr(pullRequest,message);
                        } catch (IOException e) {
                            context.warn(LOG, "Failed to merge PR " + pullRequest.getHtmlUrl() + " " + e, e);
                        }
                    }
                    if (isOpen(pullRequest)) {
                        contextStatus = Status.PENDING;
                    }
                }
            }
        }
        context.setStatus(contextStatus);
    }

    public void mergePr(GHPullRequest pullRequest, String message) throws IOException {
        //match merge method to enum, case insensitive
        GHPullRequest.MergeMethod gitMergeMethod = Arrays.stream(GHPullRequest.MergeMethod.values())
                .filter(e -> e.name().equalsIgnoreCase(mergeMethod)).findAny().orElse(GHPullRequest.MergeMethod.MERGE);
        pullRequest.merge(message,null,gitMergeMethod);

        if(deleteMergedBranches){
            GitHubHelpers.deleteUpdateBotBranch(pullRequest.getRepository(),pullRequest.getHead().getRef());
        }
    }

    /**
     * Lets load the old command context from comments on the PullRequest so that we can re-run a command to rebase things.
     */
    protected CompositeCommand loadCommandsFromPullRequest(CommandContext context, GHRepository ghRepository, GHPullRequest pullRequest) throws IOException {
        List comments = pullRequest.getComments();
        String lastCommand = null;
        for (GHIssueComment comment : comments) {
            String command = updateBotCommentCommand(context, comment);
            if (command != null) {
                lastCommand = command;
            }
        }
        if (lastCommand == null) {
            context.warn(LOG, "No UpdateBot comment found on pull request " + pullRequest.getHtmlUrl() + " so cannot rebase!");
            return null;
        }
        return parseUpdateBotCommandComment(context, lastCommand);
    }

    public CompositeCommand parseUpdateBotCommandComment(CommandContext context, String fullCommand) {
        CompositeCommand commands = new CompositeCommand();
        CommandContext answer = new CommandContext(context.getRepository(), context.getConfiguration());

        String command = fullCommand.substring(PullRequests.COMMAND_COMMENT_PREFIX.length()).trim();
        String[] lines = command.split("\n");
        for (String line : lines) {
            String text = line.trim();
            if (Strings.notEmpty(text)) {
                addBotCommand(commands, answer, text);
            }
        }
        return commands;
    }

    private void addBotCommand(CompositeCommand commands, CommandContext context, String commandLine) {
        String subCommand = commandLine;
        if (subCommand.startsWith(UPDATEBOT)) {
            subCommand = subCommand.substring(UPDATEBOT.length()).trim();
        }
        String[] args = subCommand.split(" ");
        Configuration dummyConfig = new Configuration();
        CommandSupport command = UpdateBot.parseCommand(args, dummyConfig, false);
        if (command == null) {
            context.warn(LOG, "Could not parse command line: " + commandLine);
        } else {
            commands.addCommand(command);
        }
    }

    private String updateBotCommentCommand(CommandContext context, GHIssueComment comment) throws IOException {
        GHUser user = comment.getUser();
        if (user != null) {
            if (Objects.equal(context.getConfiguration().getGithubUsername(), user.getLogin())) {
                String body = comment.getBody();
                if (body != null) {
                    body = body.trim();
                    if (body.startsWith(PullRequests.COMMAND_COMMENT_PREFIX)) {
                        return body;
                    }
                }
            }
        }
        return null;
    }

    public void setLastCommand(CommandSupport command, Configuration configuration) throws IOException {
        setLocalRepositories(command.getLocalRepositories(configuration));
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy