/** * Copyright (C) 2010-2017 Gordon Fraser, Andrea Arcuri and EvoSuite * contributors * * This file is part of EvoSuite. * * EvoSuite is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published * by the Free Software Foundation, either version 3.0 of the License, or * (at your option) any later version. * * EvoSuite is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with EvoSuite. If not, see <http://www.gnu.org/licenses/>. */ package org.evosuite.jenkins.scm; import com.cloudbees.plugins.credentials.CredentialsMatchers; import com.cloudbees.plugins.credentials.CredentialsProvider; import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials; import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder; import org.eclipse.jgit.transport.URIish; import org.evosuite.jenkins.recorder.EvoSuiteRecorder; import org.jenkinsci.plugins.gitclient.CliGitAPIImpl; import org.jenkinsci.plugins.gitclient.GitClient; import org.jenkinsci.plugins.gitclient.PushCommand; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.LinkedHashSet; import java.util.List; import java.util.Set; import hudson.EnvVars; import hudson.FilePath; import hudson.maven.AbstractMavenProject; import hudson.maven.MavenModule; import hudson.maven.MavenModuleSet; import hudson.model.AbstractBuild; import hudson.model.BuildListener; import hudson.plugins.git.Branch; import hudson.plugins.git.GitSCM; import hudson.plugins.git.UserRemoteConfig; import hudson.security.ACL; import jenkins.plugins.git.GitSCMSource; /** * Git wrapper to handle git commands, such commit and push. * * @author José Campos */ public class Git implements SCM { private GitClient gitClient; private final String remote; public Git(GitSCM gitSCM, AbstractBuild<?, ?> build, BuildListener listener) throws IOException, InterruptedException { String gitExe = gitSCM.getGitExe(build.getBuiltOn(), listener); EnvVars environment = build.getEnvironment(listener); this.gitClient = org.jenkinsci.plugins.gitclient.Git.with(listener, environment).in(build.getWorkspace()) .using(gitExe) // only if you want to use Git CLI .getClient(); // get remote configurations, e.g., URL List<UserRemoteConfig> remotes = gitSCM.getUserRemoteConfigs(); UserRemoteConfig remoteConfig = remotes.get(0); this.remote = remoteConfig.getUrl(); listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Remote config " + remoteConfig.toString()); // get key's (i.e., username-password, ssh key, etc) hash String credentialID = remoteConfig.getCredentialsId(); if (credentialID == null || credentialID.equals("null")) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "No credentials defined."); // TODO: should we throw an exception and make the build fail? } else { // get key (i.e., username-password or ssh key / passphrase) StandardUsernameCredentials credentials = this.getCredentials(credentialID); this.gitClient.setCredentials(credentials); this.gitClient.addDefaultCredentials(credentials); } } @Override public int commit(AbstractMavenProject<?, ?> project, AbstractBuild<?, ?> build, BuildListener listener, String branchName, String ctgBestsDir) { try { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Commiting new test cases"); Set<String> branches = this.getBranches(); if (!branches.contains(branchName)) { // create a new branch called "evosuite-tests" to commit and // push the new generated test suites listener.getLogger() .println(EvoSuiteRecorder.LOG_PREFIX + "There is no branch called " + branchName); this.gitClient.branch(branchName); } this.gitClient.setAuthor("jenkins", "jenkins@localhost.com"); this.gitClient.setCommitter("jenkins", "jenkins@localhost.com"); this.gitClient.checkoutBranch(branchName, "HEAD"); EnvVars env = build.getEnvironment(listener); env.overrideAll(build.getBuildVariables()); int number_of_files_committed = 0; try { MavenModuleSet prj = (MavenModuleSet) project; // parse list of new and modified files per module StringBuilder filesToBeCommitted = new StringBuilder(); for (MavenModule module : prj.getModules()) { String status = ((CliGitAPIImpl) this.gitClient).launchCommand("ls-files", "--deleted", "--modified", "--others", (module.getRelativePath().isEmpty() ? "" : module.getRelativePath() + File.separator) + ctgBestsDir); listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Status (" + status.length() + "):\n" + status); filesToBeCommitted.append(status); } String s_filesToBeCommitted = filesToBeCommitted.toString(); if (s_filesToBeCommitted.isEmpty()) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Nothing to commit"); return 0; } for (String toCommit : s_filesToBeCommitted.split("\\R")) { String filePath = build.getWorkspace().getRemote() + File.separator + toCommit; if (new File(filePath).exists()) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "adding: " + filePath); this.gitClient.add(filePath); number_of_files_committed++; } else { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "File '" + filePath + "' reported by git status command does not exist"); } } } catch (ClassCastException e) { // FIXME when building a project remotely, we just have access to a GitClient of type // RemoteGitImpl, which cannot be cast to CliGitAPIImpl. and therefore, we cannot use // launchCommand method. as a workaround, we can simple add all files under .evosuite/best-tests // and hopefully git will take care of the rest. GitClient already supports the creation // of a new branch, checkout some branch, add files to be committed, commmit, push, etc. // there must be a way of getting the list of modified / new / deleted files just using // GitClient, however we still do not know how to get that. listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + e.getMessage() + "\nTrying a different approach!"); FilePath[] filesToCommit = build.getWorkspace().list(build.getEnvironment(listener).expand( "**" + File.separator + ctgBestsDir + File.separator + "**" + File.separator + "*")); if (filesToCommit.length == 0) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Nothing to commit"); return number_of_files_committed; } number_of_files_committed = filesToCommit.length; for (FilePath fileToCommit : filesToCommit) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "adding: " + fileToCommit.getRemote()); this.gitClient.add(fileToCommit.getRemote()); } } // commit String commit_msg = SCM.COMMIT_MSG_PREFIX + build.getProject().getName().replace(" ", "_") + "-" + build.getNumber(); listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + commit_msg); this.gitClient.commit(commit_msg); return number_of_files_committed; } catch (InterruptedException | IOException e) { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Commit failed " + e.getMessage()); e.printStackTrace(); this.rollback(build, listener); return -1; } } @Override public boolean push(AbstractBuild<?, ?> build, BuildListener listener, String branchName) { try { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Pushing new test cases"); PushCommand p = this.gitClient.push(); p.ref(branchName); p.to(new URIish("origin")); p.force().execute(); } catch (InterruptedException | URISyntaxException e) { e.printStackTrace(); return false; } return true; } @Override public void rollback(AbstractBuild<?, ?> build, BuildListener listener) { try { listener.getLogger().println(EvoSuiteRecorder.LOG_PREFIX + "Rollback, cleaning up workspace"); this.gitClient.clean(); } catch (InterruptedException e) { e.printStackTrace(); } } private StandardUsernameCredentials getCredentials(String credentialsID) { GitSCMSource source = new GitSCMSource("id", this.remote, credentialsID, "*", "", false); return CredentialsMatchers.firstOrNull( CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class, source.getOwner(), ACL.SYSTEM, URIRequirementBuilder.fromUri(source.getRemoteName()).build()), CredentialsMatchers.allOf(CredentialsMatchers.withId(credentialsID), GitClient.CREDENTIALS_MATCHER)); } private Set<String> getBranches() throws InterruptedException { Set<String> branches = new LinkedHashSet<String>(); for (Branch branch : this.gitClient.getBranches()) { String[] parts = branch.getName().split("/"); branches.add(parts[parts.length - 1]); } return branches; } }