package nl.minicom.gitolite.manager.git; import java.io.File; import java.io.IOException; import java.util.Iterator; import java.util.List; import com.google.common.collect.Lists; import nl.minicom.gitolite.manager.exceptions.GitException; import nl.minicom.gitolite.manager.exceptions.ServiceUnavailable; import org.eclipse.jgit.api.AddCommand; import org.eclipse.jgit.api.CloneCommand; import org.eclipse.jgit.api.CommitCommand; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.InitCommand; import org.eclipse.jgit.api.PullCommand; import org.eclipse.jgit.api.RmCommand; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.transport.CredentialsProvider; import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.RemoteRefUpdate; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.base.Preconditions; /** * The {@link JGitManager} class is responsible for communicating with the * remote git repository containing the gitolite configuration. * * @author Michael de Jong <<a href="mailto:michaelj@minicom.nl">michaelj@minicom.nl</a>> */ public class JGitManager implements GitManager { private static final Logger log = LoggerFactory.getLogger(JGitManager.class); private final File workingDirectory; private final CredentialsProvider credentialProvider; private final Object gitLock = new Object(); private Git git; /** * Constructs a new {@link JGitManager} object. * * @param workingDirectory The working directory where we will clone to, and * manipulate the configuration files in. It's recommended to use a * temporary directory, unless you wish to keep the git repository. * * @param credentialProvider The {@link CredentialsProvider} to use to * authenticate when cloning, pulling or pushing, from or to. */ public JGitManager(File workingDirectory, CredentialsProvider credentialProvider) { Preconditions.checkNotNull(workingDirectory); this.workingDirectory = workingDirectory; this.credentialProvider = credentialProvider; } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#open() */ @Override public void open() throws IOException { synchronized (gitLock) { git = Git.open(workingDirectory); } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#remove(java.lang.String) */ @Override public void remove(String filePattern) throws IOException, GitException { synchronized (gitLock) { RmCommand rm = git.rm(); rm.addFilepattern(filePattern); try { rm.call(); } catch (NoFilepatternException e) { throw new IOException(e); } catch (GitAPIException e) { throw new GitException(e); } } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#clone(java.lang.String) */ @Override public void clone(String uri) throws ServiceUnavailable, GitException { Preconditions.checkNotNull(uri); synchronized (gitLock) { CloneCommand clone = Git.cloneRepository(); clone.setDirectory(workingDirectory); clone.setURI(uri); clone.setCredentialsProvider(credentialProvider); try { git = clone.call(); } catch (NullPointerException e) { throw new ServiceUnavailable(e); } catch (GitAPIException e) { throw new GitException(e); } } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#init() */ @Override public void init() throws GitException { synchronized (gitLock) { InitCommand initCommand = Git.init(); initCommand.setDirectory(workingDirectory); try { git = initCommand.call(); } catch (GitAPIException e) { throw new GitException(e); } } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#pull() */ @Override public boolean pull() throws ServiceUnavailable, GitException { log.info("Pulling changes from remote git repo"); synchronized (gitLock) { try { PullCommand pull = git.pull(); return !pull.call().getFetchResult().getTrackingRefUpdates().isEmpty(); } catch (NullPointerException e) { throw new ServiceUnavailable(e); } catch (GitAPIException e) { throw new GitException(e); } } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#commitChanges() */ @Override public void commitChanges() throws IOException, GitException { synchronized (gitLock) { add(git, "."); commit(git, "Changed config..."); } } private void commit(Git git, String message) throws GitException { synchronized (gitLock) { log.info("Commiting changes to local git repo"); CommitCommand commit = git.commit(); try { commit.setMessage(message).call(); } catch (GitAPIException e) { throw new GitException(e); } } } private void add(Git git, String pathToAdd) throws IOException, GitException { synchronized (gitLock) { log.info("Adding changes to commit"); AddCommand add = git.add(); try { add.addFilepattern(pathToAdd).call(); } catch (NoFilepatternException e) { throw new IOException(e); } catch (GitAPIException e) { throw new GitException(e); } } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#push() */ @Override public void push() throws ServiceUnavailable, GitException { synchronized (gitLock) { try { log.info("Pushing changes to remote git repo"); PushResult pushResult = git.push() .setCredentialsProvider(credentialProvider) .call().iterator().next(); for(RemoteRefUpdate update : pushResult.getRemoteUpdates()) { checkPushSuccess(update); } } catch (NullPointerException e) { throw new ServiceUnavailable(e); } catch (GitAPIException | JGitInternalException e) { throw new GitException(e); } } } /** * Check if the push succedded (remote is either up to date or the push could be fast forwarded) * @param update {@code RemoteRefUpdate} to check */ private void checkPushSuccess(RemoteRefUpdate update) { switch(update.getStatus()){ case OK: case UP_TO_DATE: return; default: throw new IllegalStateException("Cannot push config to gitolite config: " + update.getStatus()); } } /* * (non-Javadoc) * * @see nl.minicom.gitolite.manager.git.GitManager#getWorkingDirectory() */ @Override public File getWorkingDirectory() { return workingDirectory; } }