diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java index f94ccd59f6..d8c0ff1c60 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/AbstractGitCommand.java @@ -35,15 +35,29 @@ package sonia.scm.repository.spi; //~--- non-JDK imports -------------------------------------------------------- import com.google.common.base.Strings; +import org.apache.shiro.SecurityUtils; +import org.apache.shiro.subject.Subject; +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.GitUtil; +import sonia.scm.repository.GitWorkdirFactory; +import sonia.scm.repository.InternalRepositoryException; +import sonia.scm.repository.Person; +import sonia.scm.repository.util.WorkingCopy; +import sonia.scm.user.User; import java.io.IOException; import java.util.Optional; +import java.util.function.Function; + +import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.NotFoundException.notFound; //~--- JDK imports ------------------------------------------------------------ @@ -51,7 +65,7 @@ import java.util.Optional; * * @author Sebastian Sdorra */ -public class AbstractGitCommand +class AbstractGitCommand { /** @@ -66,7 +80,7 @@ public class AbstractGitCommand * @param context * @param repository */ - protected AbstractGitCommand(GitContext context, + AbstractGitCommand(GitContext context, sonia.scm.repository.Repository repository) { this.repository = repository; @@ -83,12 +97,12 @@ public class AbstractGitCommand * * @throws IOException */ - protected Repository open() throws IOException + Repository open() throws IOException { return context.open(); } - protected ObjectId getCommitOrDefault(Repository gitRepository, String requestedCommit) throws IOException { + ObjectId getCommitOrDefault(Repository gitRepository, String requestedCommit) throws IOException { ObjectId commit; if ( Strings.isNullOrEmpty(requestedCommit) ) { commit = getDefaultBranch(gitRepository); @@ -98,7 +112,7 @@ public class AbstractGitCommand return commit; } - protected ObjectId getDefaultBranch(Repository gitRepository) throws IOException { + ObjectId getDefaultBranch(Repository gitRepository) throws IOException { Ref ref = getBranchOrDefault(gitRepository, null); if (ref == null) { return null; @@ -107,7 +121,7 @@ public class AbstractGitCommand } } - protected Ref getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException { + Ref getBranchOrDefault(Repository gitRepository, String requestedBranch) throws IOException { if ( Strings.isNullOrEmpty(requestedBranch) ) { String defaultBranchName = context.getConfig().getDefaultBranch(); if (!Strings.isNullOrEmpty(defaultBranchName)) { @@ -122,6 +136,108 @@ public class AbstractGitCommand } } + > R inClone(Function workerSupplier, GitWorkdirFactory workdirFactory) { + try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { + Repository repository = workingCopy.getWorkingRepository(); + logger.debug("cloned repository to folder {}", repository.getWorkTree()); + return workerSupplier.apply(new Git(repository)).run(); + } catch (IOException e) { + throw new InternalRepositoryException(context.getRepository(), "could not clone repository", e); + } + } + + ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision) throws IOException { + ObjectId resolved = repository.resolve(revision); + if (resolved == null) { + throw notFound(entity("Revision", revision).in(context.getRepository())); + } else { + return resolved; + } + } + + abstract class GitCloneWorker { + private final Git clone; + + GitCloneWorker(Git clone) { + this.clone = clone; + } + + abstract R run() throws IOException; + + Git getClone() { + return clone; + } + + void checkOutBranch(String branchName) throws IOException { + try { + clone.checkout().setName(branchName).call(); + } catch (RefNotFoundException e) { + logger.trace("could not checkout branch {} directly; trying to create local branch", branchName, e); + checkOutTargetAsNewLocalBranch(branchName); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not checkout branch: " + branchName, e); + } + } + + private void checkOutTargetAsNewLocalBranch(String branchName) throws IOException { + try { + ObjectId targetRevision = resolveRevision(branchName); + clone.checkout().setStartPoint(targetRevision.getName()).setName(branchName).setCreateBranch(true).call(); + } catch (RefNotFoundException e) { + logger.debug("could not checkout branch {} as local branch", branchName, e); + throw notFound(entity("Revision", branchName).in(context.getRepository())); + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not checkout branch as local branch: " + branchName, e); + } + } + + ObjectId resolveRevision(String revision) throws IOException { + ObjectId resolved = clone.getRepository().resolve(revision); + if (resolved == null) { + return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision); + } else { + return resolved; + } + } + + void doCommit(String message, Person author) { + Person authorToUse = determineAuthor(author); + try { + if (!clone.status().call().isClean()) { + clone.commit() + .setAuthor(authorToUse.getName(), authorToUse.getMail()) + .setMessage(message) + .call(); + } + } catch (GitAPIException e) { + throw new InternalRepositoryException(context.getRepository(), "could not commit changes", e); + } + } + + void push() { + try { + clone.push().call(); + } catch (GitAPIException e) { + throw new IntegrateChangesFromWorkdirException(repository, + "could not push changes into central repository", e); + } + logger.debug("pushed changes"); + } + + private Person determineAuthor(Person author) { + if (author == null) { + Subject subject = SecurityUtils.getSubject(); + User user = subject.getPrincipals().oneByType(User.class); + String name = user.getDisplayName(); + String email = user.getMail(); + logger.debug("no author set; using logged in user: {} <{}>", name, email); + return new Person(name, email); + } else { + return author; + } + } + } + //~--- fields --------------------------------------------------------------- /** Field description */ diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java index 58c71b8dac..b4a7954b02 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitMergeCommand.java @@ -1,13 +1,10 @@ package sonia.scm.repository.spi; import com.google.common.base.Strings; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.MergeCommand.FastForwardMode; import org.eclipse.jgit.api.MergeResult; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.merge.MergeStrategy; @@ -17,18 +14,12 @@ import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Person; -import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.api.MergeCommandResult; import sonia.scm.repository.api.MergeDryRunCommandResult; -import sonia.scm.repository.util.WorkingCopy; -import sonia.scm.user.User; import java.io.IOException; import java.text.MessageFormat; -import static sonia.scm.ContextEntry.ContextBuilder.entity; -import static sonia.scm.NotFoundException.notFound; - public class GitMergeCommand extends AbstractGitCommand implements MergeCommand { private static final Logger logger = LoggerFactory.getLogger(GitMergeCommand.class); @@ -47,13 +38,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand @Override public MergeCommandResult merge(MergeCommandRequest request) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { - Repository repository = workingCopy.getWorkingRepository(); - logger.debug("cloned repository to folder {}", repository.getWorkTree()); - return new MergeWorker(repository, request).merge(); - } catch (IOException e) { - throw new InternalRepositoryException(context.getRepository(), "could not clone repository for merge", e); - } + return inClone(clone -> new MergeWorker(clone, request), workdirFactory); } @Override @@ -70,32 +55,23 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand } } - private ObjectId resolveRevisionOrThrowNotFound(Repository repository, String revision) throws IOException { - ObjectId resolved = repository.resolve(revision); - if (resolved == null) { - throw notFound(entity("Revision", revision).in(context.getRepository())); - } else { - return resolved; - } - } - - private class MergeWorker { + private class MergeWorker extends GitCloneWorker { private final String target; private final String toMerge; private final Person author; - private final Git clone; private final String messageTemplate; - private MergeWorker(Repository clone, MergeCommandRequest request) { + private MergeWorker(Git clone, MergeCommandRequest request) { + super(clone); this.target = request.getTargetBranch(); this.toMerge = request.getBranchToMerge(); this.author = request.getAuthor(); this.messageTemplate = request.getMessageTemplate(); - this.clone = new Git(clone); } - private MergeCommandResult merge() throws IOException { + @Override + MergeCommandResult run() throws IOException { checkOutTargetBranch(); MergeResult result = doMergeInClone(); if (result.getMergeStatus().isSuccessful()) { @@ -108,33 +84,14 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand } private void checkOutTargetBranch() throws IOException { - try { - clone.checkout().setName(target).call(); - } catch (RefNotFoundException e) { - logger.trace("could not checkout target branch {} for merge directly; trying to create local branch", target, e); - checkOutTargetAsNewLocalBranch(); - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not checkout target branch for merge: " + target, e); - } - } - - private void checkOutTargetAsNewLocalBranch() throws IOException { - try { - ObjectId targetRevision = resolveRevision(target); - clone.checkout().setStartPoint(targetRevision.getName()).setName(target).setCreateBranch(true).call(); - } catch (RefNotFoundException e) { - logger.debug("could not checkout target branch {} for merge as local branch", target, e); - throw notFound(entity("Revision", target).in(context.getRepository())); - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not checkout target branch for merge as local branch: " + target, e); - } + checkOutBranch(target); } private MergeResult doMergeInClone() throws IOException { MergeResult result; try { ObjectId sourceRevision = resolveRevision(toMerge); - result = clone.merge() + result = getClone().merge() .setFastForward(FastForwardMode.NO_FF) .setCommit(false) // we want to set the author manually .include(toMerge, sourceRevision) @@ -147,17 +104,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand private void doCommit() { logger.debug("merged branch {} into {}", toMerge, target); - Person authorToUse = determineAuthor(); - try { - if (!clone.status().call().isClean()) { - clone.commit() - .setAuthor(authorToUse.getName(), authorToUse.getMail()) - .setMessage(MessageFormat.format(determineMessageTemplate(), toMerge, target)) - .call(); - } - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not commit merge between branch " + toMerge + " and " + target, e); - } + doCommit(MessageFormat.format(determineMessageTemplate(), toMerge, target), author); } private String determineMessageTemplate() { @@ -168,41 +115,9 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand } } - private Person determineAuthor() { - if (author == null) { - Subject subject = SecurityUtils.getSubject(); - User user = subject.getPrincipals().oneByType(User.class); - String name = user.getDisplayName(); - String email = user.getMail(); - logger.debug("no author set; using logged in user: {} <{}>", name, email); - return new Person(name, email); - } else { - return author; - } - } - - private void push() { - try { - clone.push().call(); - } catch (GitAPIException e) { - throw new IntegrateChangesFromWorkdirException(repository, - "could not push merged branch " + target + " into central repository", e); - } - logger.debug("pushed merged branch {}", target); - } - private MergeCommandResult analyseFailure(MergeResult result) { logger.info("could not merged branch {} into {} due to conflict in paths {}", toMerge, target, result.getConflicts().keySet()); return MergeCommandResult.failure(result.getConflicts().keySet()); } - - private ObjectId resolveRevision(String revision) throws IOException { - ObjectId resolved = clone.getRepository().resolve(revision); - if (resolved == null) { - return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision); - } else { - return resolved; - } - } } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index 91c7700737..8825645a3c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -1,72 +1,50 @@ package sonia.scm.repository.spi; -import org.apache.shiro.SecurityUtils; -import org.apache.shiro.subject.Subject; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; -import org.eclipse.jgit.api.errors.RefNotFoundException; -import org.eclipse.jgit.lib.ObjectId; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; import sonia.scm.repository.GitWorkdirFactory; import sonia.scm.repository.InternalRepositoryException; -import sonia.scm.repository.Person; import sonia.scm.repository.Repository; -import sonia.scm.repository.util.WorkingCopy; -import sonia.scm.user.User; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import static sonia.scm.ContextEntry.ContextBuilder.entity; -import static sonia.scm.NotFoundException.notFound; - public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand { - private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class); - private final GitWorkdirFactory workdirFactory; - public GitModifyCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) { + GitModifyCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) { super(context, repository); this.workdirFactory = workdirFactory; } @Override public String execute(ModifyCommandRequest request) { - try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) { - org.eclipse.jgit.lib.Repository repository = workingCopy.getWorkingRepository(); - LOG.debug("cloned repository to folder {}", repository.getWorkTree()); - try { - return new ModifyWorker(repository, request).execute(); - } catch (IOException e) { - return throwInternalRepositoryException("could not apply modifications to cloned repository", e); - } - } + return inClone(clone -> new ModifyWorker(clone, request), workdirFactory); } - private class ModifyWorker implements Worker { + private class ModifyWorker extends GitCloneWorker implements Worker { - private final Git clone; private final File workDir; private final ModifyCommandRequest request; - ModifyWorker(org.eclipse.jgit.lib.Repository repository, ModifyCommandRequest request) { - this.clone = new Git(repository); - this.workDir = repository.getWorkTree(); + ModifyWorker(Git clone, ModifyCommandRequest request) { + super(clone); + this.workDir = clone.getRepository().getWorkTree(); this.request = request; } - String execute() throws IOException { - checkOutBranch(); + @Override + String run() throws IOException { + checkOutBranch(request.getBranch()); for (ModifyCommandRequest.PartialRequest r: request.getRequests()) { r.execute(this); } - doCommit(); + doCommit(request.getCommitMessage(), request.getAuthor()); push(); - return clone.getRepository().getRefDatabase().findRef("HEAD").getObjectId().name(); + return getClone().getRepository().getRefDatabase().findRef("HEAD").getObjectId().name(); } @Override @@ -75,7 +53,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman Files.createDirectories(targetFile.getParent()); Files.copy(file.toPath(), targetFile); try { - clone.add().addFilepattern(toBeCreated).call(); + getClone().add().addFilepattern(toBeCreated).call(); } catch (GitAPIException e) { throwInternalRepositoryException("could not add new file to index", e); } @@ -95,88 +73,6 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman public void move(String sourcePath, String targetPath) { } - - private void checkOutBranch() throws IOException { - String branch = request.getBranch(); - try { - clone.checkout().setName(branch).call(); - } catch (RefNotFoundException e) { - LOG.trace("could not checkout branch {} for modifications directly; trying to create local branch", branch, e); - checkOutTargetAsNewLocalBranch(); - } catch (GitAPIException e) { - throwInternalRepositoryException("could not checkout target branch for merge: " + branch, e); - } - } - - private void checkOutTargetAsNewLocalBranch() throws IOException { - String branch = request.getBranch(); - try { - ObjectId targetRevision = resolveRevision(branch); - clone.checkout().setStartPoint(targetRevision.getName()).setName(branch).setCreateBranch(true).call(); - } catch (RefNotFoundException e) { - LOG.debug("could not checkout branch {} for modifications as local branch", branch, e); - throw notFound(entity("Branch", branch).in(context.getRepository())); - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not checkout branch for modifications as local branch: " + branch, e); - } - } - - private ObjectId resolveRevision(String revision) throws IOException { - ObjectId resolved = clone.getRepository().resolve(revision); - if (resolved == null) { - return resolveRevisionOrThrowNotFound(clone.getRepository(), "origin/" + revision); - } else { - return resolved; - } - } - - private ObjectId resolveRevisionOrThrowNotFound(org.eclipse.jgit.lib.Repository repository, String revision) throws IOException { - ObjectId resolved = repository.resolve(revision); - if (resolved == null) { - throw notFound(entity("Revision", revision).in(context.getRepository())); - } else { - return resolved; - } - } - - private void doCommit() { - String branch = request.getBranch(); - LOG.debug("modified branch {}", branch); - Person authorToUse = determineAuthor(); - try { - if (!clone.status().call().isClean()) { - clone.commit() - .setAuthor(authorToUse.getName(), authorToUse.getMail()) - .setMessage(request.getCommitMessage()) - .call(); - } - } catch (GitAPIException e) { - throw new InternalRepositoryException(context.getRepository(), "could not commit modifications on branch " + request.getBranch(), e); - } - } - - private Person determineAuthor() { - if (request.getAuthor() == null) { - Subject subject = SecurityUtils.getSubject(); - User user = subject.getPrincipals().oneByType(User.class); - String name = user.getDisplayName(); - String email = user.getMail(); - LOG.debug("no author set; using logged in user: {} <{}>", name, email); - return new Person(name, email); - } else { - return request.getAuthor(); - } - } - - private void push() { - try { - clone.push().call(); - } catch (GitAPIException e) { - throw new IntegrateChangesFromWorkdirException(repository, - "could not push modified branch " + request.getBranch() + " into central repository", e); - } - LOG.debug("pushed modified branch {}", request.getBranch()); - } } private String throwInternalRepositoryException(String message, Exception e) {