package org.jfrog.bamboo.release.vcs.git; import com.atlassian.bamboo.build.logger.BuildLogger; import com.atlassian.bamboo.v2.build.BuildContext; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; import com.opensymphony.xwork.TextProvider; import org.apache.commons.lang.StringUtils; import org.apache.log4j.Logger; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.PushCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.internal.storage.file.FileRepository; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.storage.file.FileRepositoryBuilder; import org.eclipse.jgit.transport.*; import org.eclipse.jgit.util.FS; import org.jetbrains.annotations.Nullable; import org.jfrog.bamboo.context.AbstractBuildContext; import org.jfrog.bamboo.release.vcs.AbstractVcsManager; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.util.Arrays; import java.util.Map; /** * Manager that manages the Git repository. * * @author Tomer Cohen */ public class GitManager extends AbstractVcsManager { private static final Logger log = Logger.getLogger(GitManager.class); private static final String REF_PREFIX = "refs/heads/"; private static final String REFS_TAGS = "refs/tags/"; private BuildLogger buildLogger; private TextProvider textProvider; private String username = ""; private String password = ""; private String url = ""; private String sshKey = ""; private String sshPassphrase = ""; private String authenticationType = ""; public GitManager(BuildContext context, BuildLogger buildLogger) { super(context, buildLogger); this.buildLogger = buildLogger; initVcsConfiguration(); } private void initVcsConfiguration() { Map<String, String> confMap = getTaskConfiguration(); if (confMap != null) { url = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_URL); authenticationType = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_AUTHENTICATION_TYPE); username = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_USERNAME); password = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_PASSWORD); sshKey = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_SSH_KEY); sshPassphrase = confMap.get(AbstractBuildContext.VCS_PREFIX + AbstractBuildContext.GIT_PASSPHRASE); } } @Override public void commitWorkingCopy(String commitMessage) throws IOException, InterruptedException { try (Git git = createGitApi()) { git.commit().setMessage(commitMessage).setAll(true).setCommitter(new PersonIdent(git.getRepository())).call(); } catch (Exception e) { String message = "An error " + e.getMessage() + " occurred while committing the working copy"; log.error(buildLogger.addErrorLogEntry("[RELEASE]" + message)); throw new IOException(message, e); } } @Override public void createTag(String tagUrl, String commitMessage) throws IOException, InterruptedException { try (Git git = createGitApi()) { git.tag().setMessage(commitMessage).setName(tagUrl).call(); } catch (Exception e) { String message = "An error " + e.getMessage() + " occurred while creating a tag " + tagUrl; log.error(buildLogger.addErrorLogEntry("[RELEASE]" + message)); throw new IOException("An error occurred while creating a tag", e); } } @Override public String getRemoteUrl() { return url; } public String getCurrentCommitHash() throws IOException { File workingDir = getGitDir(); FileRepository localRepository = null; try { localRepository = new FileRepository(workingDir); ObjectId objId = localRepository.resolve(Constants.HEAD); return (objId != null ? objId.getName() : null); } catch (IOException e) { log.warn(buildLogger .addBuildLogEntry(textProvider.getText("repository.git.messages.cannotDetermineRevision", Arrays .asList(workingDir)) + " " + e.getMessage()), e); return null; } finally { if (localRepository != null) { localRepository.close(); } } } public String getCurrentBranch() throws IOException { File workingDir = getGitDir(); FileRepository localRepository = null; try { localRepository = new FileRepository(workingDir); return localRepository.getBranch(); } catch (IOException e) { log.warn(buildLogger .addBuildLogEntry(textProvider.getText("repository.git.messages.cannotDetermineRevision", Arrays .asList(workingDir)) + " " + e.getMessage()), e); return null; } finally { if (localRepository != null) { localRepository.close(); } } } public void setBuildLogger(BuildLogger buildLogger) { this.buildLogger = buildLogger; } public void setTextProvider(TextProvider textProvider) { this.textProvider = textProvider; } public void checkoutBranch(final String branch, final boolean create) throws IOException { log("Checking out branch: " + branch); try (Git git = createGitApi()) { git.checkout().setCreateBranch(create).setForce(create).setName(branch).call(); } catch (Exception e) { String message = "An error '" + e.getMessage() + "' occurred while checking out branch: " + branch; log.error(buildLogger.addErrorLogEntry(message)); throw new IOException(message, e); } } public void push(String url, String branch) throws IOException { try (Git git = createGitApi()) { PushCommand pushCommand = buildPushCommand(git); pushCommand.setRefSpecs(new RefSpec(REF_PREFIX + branch)).setRemote(url); Iterable<PushResult> result; log("Pushing branch: " + branch + " to url: " + url); try { result = pushCommand.call(); } catch (Exception ire) { String message = "An error '" + ire.getMessage() + "' occurred while pushing branch: " + branch + " to url: " + url; log.error(buildLogger.addErrorLogEntry("[RELEASE] " + message)); throw new IOException(message, ire); } for (PushResult pushResult : result) { if (StringUtils.isNotBlank(pushResult.getMessages())) { log(pushResult.getMessages()); } } } } public void pushTag(String remoteUrl, String tagUrl) throws IOException { String escapedTagName = tagUrl.replace(' ', '_'); Iterable<PushResult> result; try (Git git = createGitApi()) { PushCommand pushCommand = buildPushCommand(git); pushCommand.setRefSpecs(new RefSpec(REFS_TAGS + escapedTagName)).setRemote(remoteUrl); log("Pushing tag: " + escapedTagName + " to url " + remoteUrl); try { result = pushCommand.call(); } catch (Exception ire) { String message = "An error '" + ire.getMessage() + "' occurred while pushing tag: " + tagUrl + " to:" + remoteUrl; log.error(buildLogger.addErrorLogEntry("[RELEASE] " + message)); throw new IOException(message, ire); } for (PushResult pushResult : result) { if (StringUtils.isNotBlank(pushResult.getMessages())) { log(pushResult.getMessages()); } } } } public void deleteLocalBranch(String branch) throws IOException { try (Git git = createGitApi()) { log("Deleting local branch: " + branch); git.branchDelete().setBranchNames(branch).setForce(true).call(); } catch (Exception e) { String message = "An error '" + e.getMessage() + "' occurred while deleting local branch: " + branch; log.error(buildLogger.addErrorLogEntry("[RELEASE] " + message)); throw new IOException(message, e); } } public void deleteRemoteBranch(String repository, String branch) throws IOException { Iterable<PushResult> results; try (Git git = createGitApi()) { PushCommand pushCommand = buildPushCommand(git); pushCommand.setRefSpecs(new RefSpec(":" + REF_PREFIX + branch)).setRemote(repository); log("Deleting remote branch: " + branch + " from " + repository); try { results = pushCommand.call(); } catch (Exception e) { String message = "An error '" + e.getMessage() + "' occurred while deleting remote branch: " + branch + " from remote: " + repository; log.error(buildLogger.addErrorLogEntry("[RELEASE] " + message)); throw new IOException(message, e); } for (PushResult result : results) { if (StringUtils.isNotBlank(result.getMessages())) { log(result.getMessages()); } } } } public void deleteLocalTag(String tag) throws IOException { try (Git git = createGitApi()) { DeleteTagCommand deleteTagCommand = new DeleteTagCommand(git.getRepository()); deleteTagCommand.setName(tag); log("Deleting local tag: " + tag); try { RefUpdate.Result result = deleteTagCommand.call(); log.debug("Result of deletion of local tag: " + result); } catch (Exception e) { String message = "An error '" + e.getMessage() + "' occurred when deleting local tag: " + tag; log.error(buildLogger.addErrorLogEntry("[RELEASE] " + message)); throw new IOException(message, e); } } } public void revertWorkingCopy(String ish) throws GitAPIException, IOException { log("Reverting local copy to: " + ish); try (Git git = createGitApi()) { org.eclipse.jgit.lib.Repository repository = git.getRepository(); ResetCommand resetCommand = new ResetCommand(repository); resetCommand.setMode(ResetCommand.ResetType.HARD).setRef(ish).call(); } } public void deleteRemoteTag(String repository, String tag) throws IOException { try (Git git = createGitApi()) { PushCommand pushCommand = buildPushCommand(git); pushCommand.setRefSpecs(new RefSpec(":" + REFS_TAGS + tag)).setRemote(repository); Iterable<PushResult> results; log("Deleting remote tag: " + tag + " from " + repository); try { results = pushCommand.call(); } catch (Exception e) { String message = "An error '" + e.getMessage() + "' occurred when deleting remote tag: " + tag + " from remote: " + repository; log.error(buildLogger.addErrorLogEntry("[RELEASE]" + message)); throw new IOException(message, e); } for (PushResult result : results) { if (StringUtils.isNotBlank(result.getMessages())) { log(result.getMessages()); } } } } private PushCommand buildPushCommand(Git git) throws IOException { GitAuthenticationType authType = getAuthType(); PushCommand pushCommand; if (authType == GitAuthenticationType.PASSWORD || authType == GitAuthenticationType.NONE) { pushCommand = git.push(); pushCommand.setForce(true); UsernamePasswordCredentialsProvider provider = new UsernamePasswordCredentialsProvider(username, password); pushCommand.setCredentialsProvider(provider); } else { Transport transport = createSshTransport(); pushCommand = new SshPushCommand(git.getRepository(), transport); } return pushCommand; } private Git createGitApi() throws IOException { File workingDir = getGitDir(); FileRepositoryBuilder builder = new FileRepositoryBuilder(); builder.setGitDir(workingDir); return new Git(builder.setup().build()); } private File getGitDir() { File gitDirectory = new File(getAndValidateCheckoutDirectory(), Constants.DOT_GIT); if (!gitDirectory.exists()) { throw new IllegalStateException("Git Dir: " + gitDirectory.getAbsolutePath() + " Does not exist"); } return gitDirectory; } private Transport createSshTransport() throws IOException { String url = getRemoteUrl(); Transport transport = null; try (Git git = createGitApi()) { transport = Transport.open(git.getRepository(), url); if (transport instanceof SshTransport) { SshSessionFactory factory = new GitSshSessionFactory(sshKey, sshPassphrase); ((SshTransport) transport).setSshSessionFactory(factory); } } catch (URISyntaxException e) { throw new IOException(e); } finally { if (transport != null) { transport.close(); } } return transport; } private GitAuthenticationType getAuthType() { if (authenticationType.equals("SSH_KEYPAIR")) { return GitAuthenticationType.SSH_KEYPAIR; } else if (authenticationType.equals("PASSWORD")) { return GitAuthenticationType.PASSWORD; } return GitAuthenticationType.NONE; } private static class GitSshSessionFactory extends JschConfigSessionFactory { final private String key; final private String passphrase; GitSshSessionFactory(@Nullable final String key, @Nullable final String passphrase) { this.key = key; this.passphrase = passphrase; } @Override protected void configure(OpenSshConfig.Host hc, Session session) { session.setConfig("StrictHostKeyChecking", "no"); } @Override protected JSch getJSch(final OpenSshConfig.Host hc, FS fs) throws JSchException { JSch jsch = super.getJSch(hc, fs); jsch.removeAllIdentity(); if (StringUtils.isNotEmpty(key)) { jsch.addIdentity("identityName", key.getBytes(), null, passphrase.getBytes()); } return jsch; } } }