Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add gh service #75

Merged
merged 5 commits into from
Jul 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions plugin-modernizer-core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,14 @@
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>org.openrewrite</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ public class Settings {

public static final String MAVEN_REWRITE_PLUGIN_VERSION;

public static final String GITHUB_TOKEN;

public static final String GITHUB_USERNAME;

public static final String TEST_PLUGINS_DIRECTORY;

public static final String ORGANIZATION = "jenkinsci";

public static final String RECIPE_DATA_YAML_PATH = "recipe_data.yaml";

public static final ComparableVersion MAVEN_MINIMAL_VERSION = new ComparableVersion("3.9.7");
Expand All @@ -40,6 +48,9 @@ public class Settings {
}
DEFAULT_MAVEN_HOME = getDefaultMavenHome();
MAVEN_REWRITE_PLUGIN_VERSION = getRewritePluginVersion();
GITHUB_TOKEN = getGithubToken();
GITHUB_USERNAME = getGithubUsername();
TEST_PLUGINS_DIRECTORY = getTestPluginsDirectory();
}

private static Path getDefaultMavenHome() {
Expand All @@ -57,6 +68,18 @@ private static Path getDefaultMavenHome() {
return readProperty("openrewrite.maven.plugin.version", "versions.properties");
}

private static String getGithubToken() {
return System.getenv("GITHUB_TOKEN");
}

private static String getGithubUsername() {
return System.getenv("GITHUB_USERNAME");
}

private static String getTestPluginsDirectory() {
return System.getProperty("user.dir") + "/test-plugins/";
}

/**
* Read a property from a resource file.
* @param key The key to read
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
package io.jenkins.tools.pluginmodernizer.core.github;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Optional;

import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.jenkins.tools.pluginmodernizer.core.config.Config;
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.RefAlreadyExistsException;
import org.eclipse.jgit.transport.RefSpec;
import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
import org.kohsuke.github.GHIssueState;
import org.kohsuke.github.GHPullRequest;
import org.kohsuke.github.GHRepository;
import org.kohsuke.github.GitHub;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

@SuppressFBWarnings(value = "PATH_TRAVERSAL_IN", justification = "false positive")
public class GHService {

private static final Logger LOG = LoggerFactory.getLogger(GHService.class);

private final Config config;

public GHService(Config config) {
this.config = config;
}

private static final String GITHUB_TOKEN = Settings.GITHUB_TOKEN;
private static final String FORKED_REPO_OWNER = Settings.GITHUB_USERNAME;
private static final String ORIGINAL_REPO_OWNER = Settings.ORGANIZATION;
// TODO: Change commit message and PR title based on applied recipes

Check warning on line 39 in plugin-modernizer-core/src/main/java/io/jenkins/tools/pluginmodernizer/core/github/GHService.java

View check run for this annotation

ci.jenkins.io / Open Tasks Scanner

TODO

NORMAL: Change commit message and PR title based on applied recipes
private static final String COMMIT_MESSAGE = "Applied transformations with specified recipes";
private static final String PR_TITLE = "Automated PR";

public void forkCloneAndCreateBranch(String pluginName, String branchName) throws IOException, GitAPIException, InterruptedException {
Path pluginDirectory = Paths.get(Settings.TEST_PLUGINS_DIRECTORY, pluginName);

GitHub github = GitHub.connectUsingOAuth(GITHUB_TOKEN);
GHRepository originalRepo = github.getRepository(ORIGINAL_REPO_OWNER + "/" + pluginName);

getOrCreateForkedRepo(github, originalRepo);

cloneRepositoryIfNeeded(pluginDirectory, pluginName);

createAndCheckoutBranch(pluginDirectory, branchName);
}

private void getOrCreateForkedRepo(GitHub github, GHRepository originalRepo) throws IOException, InterruptedException {
GHRepository forkedRepo = github.getMyself().getRepository(originalRepo.getName());
if (forkedRepo == null) {
LOG.info("Forking the repository...");
originalRepo.fork();
Thread.sleep(5000); // Ensure the completion of Fork
LOG.info("Repository forked successfully.");
} else {
LOG.info("Repository already forked.");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you please track an issue to ensure the GH repo is in sync with upstream. It should be possible via API

I'm using generally the gh repo sync username/repo

Without sync existing repo there is a risk to open branches from outdated main branch

}
}

private void cloneRepositoryIfNeeded(Path pluginDirectory, String pluginName) throws GitAPIException {
if (!Files.exists(pluginDirectory) || !Files.isDirectory(pluginDirectory)) {
LOG.info("Cloning {}", pluginName);
Git.cloneRepository()
.setURI("https://github.com/" + FORKED_REPO_OWNER + "/" + pluginName + ".git")
.setDirectory(pluginDirectory.toFile())
.call();
LOG.info("Cloned successfully.");
}
}

private void createAndCheckoutBranch(Path pluginDirectory, String branchName) throws IOException, GitAPIException {
try (Git git = Git.open(pluginDirectory.toFile())) {
try {
git.checkout().setCreateBranch(true).setName(branchName).call();
} catch (RefAlreadyExistsException e) {
LOG.info("Branch already exists. Checking out the branch.");
git.checkout().setName(branchName).call();
}
}
}

public void commitAndCreatePR(String pluginName, String branchName) throws IOException, GitAPIException {
if (config.isDryRun()) {
LOG.info("[Dry Run] Skipping commit and pull request creation for {}", pluginName);
return;
}

LOG.info("Creating pull request for plugin: {}", pluginName);

Path pluginDirectory = Paths.get(Settings.TEST_PLUGINS_DIRECTORY, pluginName);

commitChanges(pluginDirectory);

pushBranch(pluginDirectory, branchName);

createPullRequest(pluginName, branchName);
}

private void commitChanges(Path pluginDirectory) throws IOException, GitAPIException {
try (Git git = Git.open(pluginDirectory.toFile())) {
git.add().addFilepattern(".").call();

git.commit()
.setMessage(COMMIT_MESSAGE)
.setSign(false) // Maybe a new option to sign commit?
.call();

LOG.info("Changes committed");
}
}

private void pushBranch(Path pluginDirectory, String branchName) throws IOException, GitAPIException {
try (Git git = Git.open(pluginDirectory.toFile())) {
git.push()
.setCredentialsProvider(new UsernamePasswordCredentialsProvider(GITHUB_TOKEN, ""))
.setRemote("origin")
.setRefSpecs(new RefSpec(branchName + ":" + branchName))
.call();

LOG.info("Pushed changes to forked repository.");
}
}

private void createPullRequest(String pluginName, String branchName) throws IOException {
GitHub github = GitHub.connectUsingOAuth(GITHUB_TOKEN);
GHRepository originalRepo = github.getRepository(ORIGINAL_REPO_OWNER + "/" + pluginName);

Optional<GHPullRequest> existingPR = checkIfPullRequestExists(originalRepo, branchName);

if (existingPR.isPresent()) {
LOG.info("Pull request already exists: {}", existingPR.get().getHtmlUrl());
} else {
String prBody = String.format("Applied the following recipes: %s", String.join(", ", config.getRecipes()));
GHPullRequest pr = originalRepo.createPullRequest(
PR_TITLE,
FORKED_REPO_OWNER + ":" + branchName,
originalRepo.getDefaultBranch(),
prBody
);

LOG.info("Pull request created: {}", pr.getHtmlUrl());
}
}

private Optional<GHPullRequest> checkIfPullRequestExists(GHRepository originalRepo, String branchName) throws IOException {
List<GHPullRequest> pullRequests = originalRepo.getPullRequests(GHIssueState.OPEN);
return pullRequests.stream()
.filter(pr -> pr.getHead().getRef().equals(branchName) && pr.getTitle().equals(PR_TITLE))
.findFirst();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import io.jenkins.tools.pluginmodernizer.core.config.Config;
import io.jenkins.tools.pluginmodernizer.core.config.Settings;
import io.jenkins.tools.pluginmodernizer.core.github.GHService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand All @@ -15,24 +16,38 @@ public class PluginModernizer {

private final MavenInvoker mavenInvoker;

private final GHService ghService;

public PluginModernizer(Config config) {
this.config = config;
this.mavenInvoker = new MavenInvoker(config);
this.ghService = new GHService(config);
}

public void start() {
String projectRoot = System.getProperty("user.dir");
LOG.info("Plugins: {}", config.getPlugins());
LOG.info("Recipes: {}", config.getRecipes());
LOG.debug("Cache Path: {}", config.getCachePath());
LOG.debug("Dry Run: {}", config.isDryRun());
LOG.debug("Maven rewrite plugin version: {}", Settings.MAVEN_REWRITE_PLUGIN_VERSION);
for (String plugin : config.getPlugins()) {
String pluginPath = projectRoot + "/test-plugins/" + plugin;
LOG.info("Invoking clean phase for plugin: {}", plugin);
mavenInvoker.invokeGoal(plugin, pluginPath, "clean");
LOG.info("Invoking rewrite plugin for plugin: {}", plugin);
mavenInvoker.invokeRewrite(plugin, pluginPath);
String pluginPath = Settings.TEST_PLUGINS_DIRECTORY + plugin;
String branchName = "apply-transformation-" + plugin;

try {
LOG.info("Forking and cloning {} locally", plugin);
ghService.forkCloneAndCreateBranch(plugin, branchName);

LOG.info("Invoking clean phase for plugin: {}", plugin);
mavenInvoker.invokeGoal(plugin, pluginPath, "clean");

LOG.info("Invoking rewrite plugin for plugin: {}", plugin);
mavenInvoker.invokeRewrite(plugin, pluginPath);

ghService.commitAndCreatePR(plugin, branchName);
} catch (Exception e) {
LOG.error("Failed to process plugin: {}", plugin, e);
}
}
}

Expand Down
12 changes: 12 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@
<jackson.version>2.17.2</jackson.version>
<maven.version>3.9.8</maven.version>
<jacoco-maven-plugin.version>0.8.12</jacoco-maven-plugin.version>
<jgit.version>6.10.0.202406032230-r</jgit.version>
<github-api.version>1.323</github-api.version>
</properties>

<repositories>
Expand Down Expand Up @@ -159,6 +161,16 @@
<artifactId>maven-artifact</artifactId>
<version>${maven.version}</version>
</dependency>
<dependency>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit</artifactId>
<version>${jgit.version}</version>
</dependency>
<dependency>
<groupId>org.kohsuke</groupId>
<artifactId>github-api</artifactId>
<version>${github-api.version}</version>
</dependency>
</dependencies>
</dependencyManagement>

Expand Down
Loading