From d2d0eafae2f5b35f729e13d437fa290b9c7451d9 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Fri, 10 May 2013 14:45:56 +0200 Subject: [PATCH] implement git incoming command --- .../java/sonia/scm/repository/GitUtil.java | 73 ++++- .../repository/spi/GitIncomingCommand.java | 295 ++++++++++++++++++ .../spi/GitRepositoryServiceProvider.java | 32 +- 3 files changed, 393 insertions(+), 7 deletions(-) create mode 100644 scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java index e8c5f795dc..8172ecc584 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/GitUtil.java @@ -40,6 +40,9 @@ import com.google.common.collect.ArrayListMultimap; import com.google.common.collect.Multimap; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.errors.IncorrectObjectTypeException; +import org.eclipse.jgit.errors.MissingObjectException; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RepositoryCache; @@ -378,6 +381,54 @@ public final class GitUtil return id; } + /** + * Method description + * + * + * @param repository + * @param id + * + * @return + * + * @throws IncorrectObjectTypeException + * @throws MissingObjectException + * + * @throws IOException + */ + public static Ref getRefForCommit(org.eclipse.jgit.lib.Repository repository, + ObjectId id) + throws IOException + { + Ref ref = null; + RevWalk walk = null; + + try + { + walk = new RevWalk(repository); + + RevCommit commit = walk.parseCommit(id); + + for (Map.Entry e : repository.getAllRefs().entrySet()) + { + if (e.getKey().startsWith(Constants.R_HEADS)) + { + if (walk.isMergedInto(commit, + walk.parseCommit(e.getValue().getObjectId()))) + { + ref = e.getValue(); + } + } + } + + } + finally + { + release(walk); + } + + return ref; + } + /** * Method description * @@ -385,8 +436,11 @@ public final class GitUtil * @param repo * * @return + * + * @throws IOException */ public static ObjectId getRepositoryHead(org.eclipse.jgit.lib.Repository repo) + throws IOException { ObjectId id = null; String head = null; @@ -415,9 +469,25 @@ public final class GitUtil } } + if (id == null) + { + id = repo.resolve(Constants.HEAD); + } + if (logger.isDebugEnabled()) { - logger.debug("use {}:{} as repository head", head, id); + if ((head != null) && (id != null)) + { + logger.debug("use {}:{} as repository head", head, id.name()); + } + else if (id != null) + { + logger.debug("use {} as repository head", id.name()); + } + else + { + logger.warn("could not find repository head"); + } } return id; @@ -470,7 +540,6 @@ public final class GitUtil } return name; - } //~--- methods -------------------------------------------------------------- diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java new file mode 100644 index 0000000000..7d8f9076a5 --- /dev/null +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitIncomingCommand.java @@ -0,0 +1,295 @@ +/** + * Copyright (c) 2010, Sebastian Sdorra All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright notice, + * this list of conditions and the following disclaimer. 2. Redistributions in + * binary form must reproduce the above copyright notice, this list of + * conditions and the following disclaimer in the documentation and/or other + * materials provided with the distribution. 3. Neither the name of SCM-Manager; + * nor the names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR + * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * http://bitbucket.org/sdorra/scm-manager + * + */ + + + +package sonia.scm.repository.spi; + +//~--- non-JDK imports -------------------------------------------------------- + +import com.google.common.collect.Lists; +import com.google.common.io.Closeables; + +import org.eclipse.jgit.api.FetchCommand; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.RefSpec; + +import sonia.scm.repository.Changeset; +import sonia.scm.repository.ChangesetPagingResult; +import sonia.scm.repository.GitChangesetConverter; +import sonia.scm.repository.GitRepositoryHandler; +import sonia.scm.repository.GitUtil; +import sonia.scm.repository.Repository; +import sonia.scm.repository.RepositoryException; + +//~--- JDK imports ------------------------------------------------------------ + +import java.io.IOException; + +import java.util.List; +import java.util.Map.Entry; +import java.util.concurrent.TimeUnit; + +/** + * + * @author Sebastian Sdorra + */ +public class GitIncomingCommand extends AbstractGitCommand + implements IncomingCommand +{ + + /** Field description */ + private static final String REFSPEC = "+refs/heads/*:refs/remote/scm/%s/*"; + + /** Field description */ + private static final String REMOTE_REF = "refs/remote/scm/%s/%s"; + + /** Field description */ + private static final String REMOTE_REF_PREFIX = "refs/remote/scm/%s/"; + + /** Field description */ + private static final int TIMEOUT = 5; + + //~--- constructors --------------------------------------------------------- + + /** + * Constructs ... + * + * + * @param handler + * @param context + * @param repository + */ + GitIncomingCommand(GitRepositoryHandler handler, GitContext context, + Repository repository) + { + super(context, repository); + this.handler = handler; + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param request + * + * @return + * + * @throws IOException + * @throws RepositoryException + */ + @Override + public ChangesetPagingResult getIncomingChangesets( + IncomingCommandRequest request) + throws IOException, RepositoryException + { + Repository remoteRepository = request.getRemoteRepository(); + + Git git = Git.wrap(open()); + FetchCommand fetch = git.fetch(); + + fetch.setRemote(handler.getDirectory(remoteRepository).getAbsolutePath()); + fetch.setRefSpecs(createRefSpec(remoteRepository)); + fetch.setTimeout((int) TimeUnit.MINUTES.toSeconds(TIMEOUT)); + + try + { + fetch.call(); + } + catch (GitAPIException ex) + { + throw new RepositoryException("could not fetch", ex); + } + + ObjectId local = GitUtil.getRepositoryHead(git.getRepository()); + + Ref remoteBranch = getRemoteBranch(git.getRepository(), local, + remoteRepository); + + // TODO paging + List changesets = Lists.newArrayList(); + + if (remoteBranch != null) + { + + GitChangesetConverter converter = null; + RevWalk walk = null; + + try + { + walk = new RevWalk(git.getRepository()); + converter = new GitChangesetConverter(git.getRepository(), walk); + + org.eclipse.jgit.api.LogCommand log = git.log(); + + if (local != null) + { + log.not(local); + } + + Iterable commits = + log.add(remoteBranch.getObjectId()).call(); + + for (RevCommit commit : commits) + { + changesets.add(converter.createChangeset(commit)); + } + + changesets = Lists.reverse(changesets); + } + catch (Exception ex) + { + throw new RepositoryException("could not execute incoming command", ex); + } + finally + { + Closeables.close(converter, true); + GitUtil.release(walk); + } + + } + + return new ChangesetPagingResult(changesets.size(), changesets); + } + + //~--- methods -------------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * + * @return + */ + private RefSpec createRefSpec(Repository repository) + { + return new RefSpec(String.format(REFSPEC, repository.getId())); + } + + //~--- get methods ---------------------------------------------------------- + + /** + * Method description + * + * + * @param repository + * @param local + * @param remoteRepository + * + * @return + * + * @throws IOException + * @throws RepositoryException + */ + private Ref getRemoteBranch(org.eclipse.jgit.lib.Repository repository, + ObjectId local, Repository remoteRepository) + throws IOException, RepositoryException + { + Ref ref = null; + + if (local != null) + { + Ref localBranch = GitUtil.getRefForCommit(repository, local); + + if (localBranch != null) + { + ref = repository.getRef(getScmRemoteRefName(remoteRepository, + localBranch)); + } + } + else + { + ref = repository.getRef(getScmRemoteRefName(remoteRepository, "master")); + + if (ref == null) + { + String prefix = String.format(REMOTE_REF_PREFIX, + remoteRepository.getId()); + + for (Entry e : repository.getAllRefs().entrySet()) + { + if (e.getKey().startsWith(prefix)) + { + if (ref != null) + { + throw new RepositoryException("could not find remote branch"); + } + + ref = e.getValue(); + + break; + } + } + } + } + + return ref; + } + + /** + * Method description + * + * + * @param repository + * @param localBranch + * + * @return + */ + private String getScmRemoteRefName(Repository repository, Ref localBranch) + { + return getScmRemoteRefName(repository, localBranch.getName()); + } + + /** + * Method description + * + * + * @param repository + * @param localBranch + * + * @return + */ + private String getScmRemoteRefName(Repository repository, String localBranch) + { + return String.format(REMOTE_REF, repository.getId(), localBranch); + } + + //~--- fields --------------------------------------------------------------- + + /** Field description */ + private GitRepositoryHandler handler; +} diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index fd51c3f7c5..83ba0d6a5c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -55,11 +55,18 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider { /** Field description */ - public static final Set COMMANDS = ImmutableSet.of(Command.BLAME, - Command.BROWSE, Command.CAT, - Command.DIFF, Command.LOG, - Command.TAGS, - Command.BRANCHES); + //J- + public static final Set COMMANDS = ImmutableSet.of( + Command.BLAME, + Command.BROWSE, + Command.CAT, + Command.DIFF, + Command.LOG, + Command.TAGS, + Command.BRANCHES, + Command.INCOMING + ); + //J+ //~--- constructors --------------------------------------------------------- @@ -153,6 +160,18 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider return new GitDiffCommand(context, repository); } + /** + * Method description + * + * + * @return + */ + @Override + public IncomingCommand getIncomingCommand() + { + return new GitIncomingCommand(handler, context, repository); + } + /** * Method description * @@ -194,6 +213,9 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider /** Field description */ private GitContext context; + /** Field description */ + private GitRepositoryHandler handler; + /** Field description */ private Repository repository; }