/*
* Copyright (C) 2011 JFrog Ltd.
*
* Licensed 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 org.jfrog.hudson.release.scm.git;
import com.cloudbees.plugins.credentials.CredentialsMatchers;
import com.cloudbees.plugins.credentials.CredentialsProvider;
import com.cloudbees.plugins.credentials.common.StandardCredentials;
import com.cloudbees.plugins.credentials.common.StandardUsernameCredentials;
import com.cloudbees.plugins.credentials.domains.URIRequirementBuilder;
import hudson.EnvVars;
import hudson.FilePath;
import hudson.model.AbstractBuild;
import hudson.model.TaskListener;
import hudson.plugins.git.GitException;
import hudson.plugins.git.GitSCM;
import hudson.plugins.git.UserRemoteConfig;
import hudson.security.ACL;
import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.transport.RemoteConfig;
import org.eclipse.jgit.transport.URIish;
import org.jenkinsci.plugins.gitclient.Git;
import org.jenkinsci.plugins.gitclient.GitClient;
import org.jfrog.hudson.release.ReleaseRepository;
import org.jfrog.hudson.release.scm.AbstractScmManager;
import java.io.IOException;
import java.util.List;
import java.util.logging.Logger;
import java.util.regex.Pattern;
/**
* Interacts with Git repository for the various release operations.
*
* @author Yossi Shaul
*/
public class GitManager extends AbstractScmManager<GitSCM> {
private static Logger debuggingLogger = Logger.getLogger(GitManager.class.getName());
private StandardCredentials credentials;
public GitManager(AbstractBuild<?, ?> build, TaskListener buildListener) {
super(build, buildListener);
}
public void setGitCredentials(StandardCredentials credentials) {
this.credentials = credentials;
}
public void checkoutBranch(final String branch, final boolean create) throws IOException, InterruptedException {
GitClient client = getGitClient(null);
debuggingLogger.fine(String.format("Checkout Branch '%s' with create=%s", branch, create));
if (create) {
client.checkout(null, branch);
} else {
client.checkout(branch);
}
}
public void commitWorkingCopy(final String commitMessage) throws IOException, InterruptedException {
GitClient client = getGitClient(null);
debuggingLogger.fine("Adding all files in the current directory");
client.add("-u");
debuggingLogger.fine(String.format("Committing working copy with message '%s'", commitMessage));
client.commit(commitMessage);
}
public void createTag(final String tagName, final String commitMessage)
throws IOException, InterruptedException {
GitClient client = getGitClient(null);
log(buildListener, String.format("Creating tag '%s' with message '%s'", tagName, commitMessage));
client.tag(tagName, commitMessage);
}
public void push(final ReleaseRepository releaseRepository, final String branch) throws Exception {
GitClient client = getGitClient(releaseRepository);
log(buildListener, String.format("Pushing branch '%s' to '%s'", branch, releaseRepository.getGitUri()));
client.push().tags(true).to(new URIish(releaseRepository.getTargetRepoPrivateUri()))
.ref("refs/heads/" + branch).timeout(10).execute();
}
public void revertWorkingCopy() throws IOException, InterruptedException {
GitClient client = getGitClient(null);
log(buildListener, "Reverting git working copy (reset --hard)");
client.clean();
}
public void deleteLocalBranch(final String branch) throws IOException, InterruptedException {
GitClient client = getGitClient(null);
log(buildListener, "Deleting local git branch: " + branch);
client.deleteBranch(branch);
}
public void deleteRemoteBranch(final ReleaseRepository releaseRepository, final String branch)
throws IOException, InterruptedException {
GitClient client = getGitClient(releaseRepository);
log(buildListener, String.format("Deleting remote branch '%s' on '%s'", branch, releaseRepository.getGitUri()));
client.push(releaseRepository.getRepositoryName(), ":refs/heads/" + branch);
}
public void deleteLocalTag(final String tag) throws IOException, InterruptedException {
GitClient client = getGitClient(null);
log(buildListener, "Deleting local tag: " + tag);
client.deleteTag(tag);
}
public void deleteRemoteTag(final ReleaseRepository releaseRepository, final String tag)
throws IOException, InterruptedException {
GitClient client = getGitClient(releaseRepository);
log(buildListener, String.format("Deleting remote tag '%s' from '%s'", tag, releaseRepository.getGitUri()));
client.push(releaseRepository.getRepositoryName(), ":refs/tags/" + tag);
}
// This method is currently in use only by the SvnCoordinator
public String getRemoteUrl(String defaultRemoteUrl) {
if (StringUtils.isBlank(defaultRemoteUrl)) {
RemoteConfig remoteConfig = getJenkinsScm().getRepositories().get(0);
URIish uri = remoteConfig.getURIs().get(0);
return uri.toPrivateString();
}
return defaultRemoteUrl;
}
public String getBranchNameWithoutRemote(String branchName) {
List<RemoteConfig> repositories = getJenkinsScm().getRepositories();
for (RemoteConfig remoteConfig : repositories) {
String prefix = remoteConfig.getName() + "/";
if (branchName.startsWith(prefix)) {
return StringUtils.removeStart(branchName, prefix);
}
}
return branchName;
}
public ReleaseRepository getRemoteConfig(String defaultRemoteNameOrUrl) throws IOException {
List<RemoteConfig> repositories = getJenkinsScm().getRepositories();
if (StringUtils.isBlank(defaultRemoteNameOrUrl)) {
if (repositories == null || repositories.isEmpty()) {
throw new GitException("Git remote config repositories are null or empty.");
}
return new ReleaseRepository(repositories.get(0).getURIs().get(0), repositories.get(0).getName());
}
for (RemoteConfig remoteConfig : repositories) {
if (remoteConfig.getName().equals(defaultRemoteNameOrUrl)) {
return new ReleaseRepository(remoteConfig.getURIs().get(0), remoteConfig.getName());
}
}
if (checkGitValidUri(defaultRemoteNameOrUrl)) {
return new ReleaseRepository(defaultRemoteNameOrUrl, "externalGitUrl");
}
throw new IOException("Target Remote Name: " + defaultRemoteNameOrUrl + " ,doesn`t exist");
}
private boolean checkGitValidUri(String defaultRemoteNameOrUrl) {
String regex = "(\\w+://)(.+@)*([\\w\\d\\.]+)(:[\\d]+){0,1}/*(.*)|(.+@)*([\\w\\d\\.]+):(.*)|file://(.*)";
return Pattern.compile(regex).matcher(defaultRemoteNameOrUrl).matches();
}
private GitClient getGitClient(ReleaseRepository releaseRepository) throws IOException, InterruptedException {
FilePath directory = getWorkingDirectory(getJenkinsScm(), build.getWorkspace());
EnvVars env = build.getEnvironment(buildListener);
Git git = new Git(buildListener, env);
git.in(directory);
/*
* When init the git exe, the user dons`t have to add SSH credentials in the git plugin.
* This solution automatically takes the user default SSH ($HOME/.ssh)
* */
git.using(getJenkinsScm().getGitExe(build.getBuiltOn(), buildListener)); // git.exe
GitClient client = git.getClient();
client.setCommitter(StringUtils.defaultIfEmpty(env.get("GIT_COMMITTER_NAME"), ""),
StringUtils.defaultIfEmpty(env.get("GIT_COMMITTER_EMAIL"), ""));
client.setAuthor(StringUtils.defaultIfEmpty(env.get("GIT_AUTHOR_NAME"), ""),
StringUtils.defaultIfEmpty(env.get("GIT_AUTHOR_EMAIL"), ""));
if (releaseRepository != null && releaseRepository.isTargetRepoUri()) {
client.setRemoteUrl(releaseRepository.getRepositoryName(), releaseRepository.getTargetRepoPrivateUri());
} else {
addRemoteRepoToConfig(client);
}
addCredentialsToGitClient(client);
return client;
}
private String getFirstGitURI(RemoteConfig remoteRepository) {
List<URIish> urIs = remoteRepository.getURIs();
if (urIs == null || urIs.isEmpty()) {
throw new GitException("Error performing push tag command, repository URIs are null or empty.");
}
return urIs.get(0).toString();
}
/*
* In cause the remote repository is not exists in the git config file
* */
private void addRemoteRepoToConfig(GitClient client) throws InterruptedException {
GitSCM gitScm = getJenkinsScm();
for (RemoteConfig uc : gitScm.getRepositories()) {
if (client.getRemoteUrl(uc.getName()) == null)
client.setRemoteUrl(uc.getName(), uc.getURIs().get(0).toPrivateASCIIString());
}
}
private void addCredentialsToGitClient(GitClient client) {
GitSCM gitScm = getJenkinsScm();
for (UserRemoteConfig uc : gitScm.getUserRemoteConfigs()) {
String url = uc.getUrl();
if (this.credentials != null) {
client.addCredentials(url, this.credentials);
} else if (uc.getCredentialsId() != null) {
StandardUsernameCredentials credentials = CredentialsMatchers
.firstOrNull(
CredentialsProvider.lookupCredentials(StandardUsernameCredentials.class,
build.getProject(), ACL.SYSTEM, URIRequirementBuilder.fromUri(url).build()),
CredentialsMatchers.allOf(CredentialsMatchers.withId(uc.getCredentialsId()),
GitClient.CREDENTIALS_MATCHER));
if (credentials != null) {
client.addCredentials(url, credentials);
}
}
}
}
private FilePath getWorkingDirectory(GitSCM gitSCM, FilePath ws) throws IOException {
// working directory might be relative to the workspace
String relativeTargetDir = gitSCM.getRelativeTargetDir() == null ? "" : gitSCM.getRelativeTargetDir();
return new FilePath(ws, relativeTargetDir);
}
}