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

org.openmrs.maven.plugins.PullRequest Maven / Gradle / Ivy

The newest version!
package org.openmrs.maven.plugins;

import com.atlassian.jira.rest.client.api.domain.Issue;
import com.google.common.collect.Iterables;
import org.apache.maven.plugin.MojoExecutionException;
import org.apache.maven.plugin.MojoFailureException;
import org.apache.maven.plugins.annotations.Mojo;
import org.apache.maven.plugins.annotations.Parameter;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.openmrs.maven.plugins.git.GithubPrRequest;
import org.openmrs.maven.plugins.model.Project;

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

/**
 * Create or update pull request to any OpenMRS repository.
 */
@Mojo(name = "pr", requiresProject = false)
public class PullRequest extends AbstractTask {

    /**
	 * The branch to create the pull request.
	 */
	@Parameter(defaultValue = "master", property = "branch")
	String branch;

	/**
	 * id of issue against which code is committed
	 */
	@Parameter(property = "issueId")
	String issueId;

	/**
	 * github username, required to push and open pull request
	 */
	@Parameter(property = "username", alias = "githubUsername")
	String username;

	/**
	 * github personal access token, required to push and open pull request
	 */
	@Parameter(property = "personalAccessToken")
	String personalAccessToken;

	/**
	 * name of github repository to open PR
	 */
	String repoName;

	private static final String COMMIT_NUM_INFO_TMPL =
			"There are %d commits, which will be included in your pull request. " +
					"It is recommended to squash them into one. Would you like to squash them?";

	@Override
	public void executeTask() throws MojoExecutionException, MojoFailureException {
		issueId = wizard.promptForValueIfMissing(issueId, "issue id");
		Issue issue = jira.getIssue(issueId);
		if (issue == null) {
			throw new MojoExecutionException("invalid issue id");
		}
		String path = mavenProject.getBasedir().getAbsolutePath();
		Repository localRepository = gitHelper.getLocalRepository(path);
		String localBranch;
		try {
			localBranch = localRepository.getBranch();
		}
		catch (IOException e) {
			throw new MojoFailureException("Error during accessing local repository", e);
		}

		Git git = new Git(localRepository);

		if (gitHelper.checkIfUncommittedChanges(git)) {
			wizard.showMessage("There are uncommitted changes. Please commit before proceeding.");
			throw new MojoExecutionException("There are uncommitted changes. Please commit before proceeding.");
		}

		if (localBranch.equals("master")) {
			boolean yes = wizard.promptYesNo(
					"Creating pull request from the master branch is not recommended. Would you like to create a feature branch '"
							+ issueId + "'?");
			if (yes) {
				gitHelper.checkoutToNewBranch(git, issueId);
				localBranch = issueId;
			}
		}

		gitHelper.pullRebase(this, branch, new Project(mavenProject.getModel()));

		String localRef = "refs/heads/" + localBranch;
		String upstreamRef = "refs/remotes/upstream/" + branch;

		squashCommitsIfAccepted(git, localRef, upstreamRef);

		boolean correctMessages = fixCommitMessagesIfAccepted(git, localRef, upstreamRef);
		if (!correctMessages) {
			return;
		}

		String scmUrl = mavenProject.getScm().getUrl();
		repoName = scmUrl.substring(scmUrl.lastIndexOf("/") + 1);
		username = wizard.promptForValueIfMissing(username, "your GitHub username");

		String originUrl = gitHelper.adjustRemoteOrigin(git, scmUrl, username);
		wizard.showMessage("Changes will be pushed to " + localBranch + " branch at " + originUrl);

		personalAccessToken = wizard.promptForPasswordIfMissing(personalAccessToken, "your GitHub personal access token");

		gitHelper.push(git, username, personalAccessToken, "refs/heads/" + localBranch, "origin", true);
		createUpdatePullRequest(issue, localBranch, repoName);
	}

	/**
	 * Checks if there is more than one commit to potential PR, and prompts if user wants them automatically squashed
	 * if so, squashes them
	 *
	 * @param git         JGit object handling localRepository
	 * @param localRef    reference to local branch
	 * @param upstreamRef reference to upstream branch
	 */
	private void squashCommitsIfAccepted(Git git, String localRef, String upstreamRef) throws MojoExecutionException {
		Iterable commits = gitHelper.getCommitDifferential(git, upstreamRef, localRef);

		int size = Iterables.size(commits);
		if (size > 1) {
			boolean hasToSquash = wizard.promptYesNo(String.format(COMMIT_NUM_INFO_TMPL, size));
			if (hasToSquash) {
				gitHelper.squashLastCommits(git, size);
			}
		}
	}

	/**
	 * Checks if commits in local branch, which are ahead of upstream branch have commit messages starting with issueId,
	 * and fixes them if user accepts
	 *
	 * @param git         JGit object handling localRepository
	 * @param localRef    reference to local branch
	 * @param upstreamRef reference to upstream branch
	 * @return if commits in potential PR have messages starting with issueId
	 * @throws MojoExecutionException
	 */
	private boolean fixCommitMessagesIfAccepted(Git git, String localRef, String upstreamRef) throws MojoExecutionException {
		List commitMessages = gitHelper.getCommitDifferentialMessages(git, upstreamRef, localRef);
		List messagesToModify = new ArrayList<>();

		int size = Iterables.size(commitMessages);
		for (String commit : commitMessages) {
			if (!commit.startsWith(issueId)) {
				messagesToModify.add(commit);
			}
		}

		if (messagesToModify.size() > 0) {
			String messageText = createRenamePrompt(issueId, messagesToModify);
			wizard.showMessage(messageText);
			boolean correctMessages = wizard.promptYesNo("Would you like them to be corrected automatically?");
			if (correctMessages) {
				gitHelper.addIssueIdIfMissing(git, issueId, size);
				return true;
			} else {
				return false;
			}
		} else {
			return true;
		}
	}

	/**
	 * opens PR on github, refering to passed jira issue, from given origin branch to selected repo in OpenMRS user account
	 *
	 * @param issue        jira issue which PR refers to
	 * @param originBranch name of branch to be head of PR
	 * @param repoName     name of openmrs repository to be base of PR
	 */
	private void createUpdatePullRequest(Issue issue, String originBranch, String repoName) throws MojoExecutionException {
		org.eclipse.egit.github.core.PullRequest pr = gitHelper
				.getPullRequestIfExists(branch, username + ":" + originBranch, repoName);
		if (pr == null) {
			wizard.showMessage("Creating new pull request...");
			String description = wizard
					.promptForValueIfMissingWithDefault("You can include a short %s (optional)", null, "description", "");
			description = "https://issues.openmrs.org/browse/" + issueId + "\n\n" + description;
			GithubPrRequest request = new GithubPrRequest.Builder()
					.setBase(branch)
					.setHead(username + ":" + originBranch)
					.setUsername(username)
					.setPassword(personalAccessToken)
					.setDescription(description)
					.setTitle(issue.getKey() + " " + issue.getSummary())
					.setRepository(repoName)
					.build();
			pr = gitHelper.openPullRequest(request);
			wizard.showMessage("Pull request created at " + pr.getHtmlUrl());
		} else {
			wizard.showMessage("Pull request updated at " + pr.getHtmlUrl());
		}
	}

	/**
	 * creates multiline message showing commit messages which need to be fixed,
	 * in format {message} -> {issueId}{message}
	 *
	 * @param issueId
	 * @param messagesToModify
	 * @return
	 */
	private String createRenamePrompt(String issueId, List messagesToModify) {
		StringBuilder message = new StringBuilder(
				"Some of your commits do not start from issue id. they should be corrected as following:\n");
		for (String messageToModify : messagesToModify) {
			message.append(messageToModify);
			message.append(" -> ");
			message.append(issueId);
			message.append(" ");
			message.append(messageToModify);
			message.append("\n");
		}
		return message.toString();
	}
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy