Extract common code

This commit is contained in:
Rene Pfeuffer
2019-08-29 10:31:25 +02:00
parent ae8b519a92
commit de7647ba55
3 changed files with 143 additions and 216 deletions

View File

@@ -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, W extends GitCloneWorker<R>> R inClone(Function<Git, W> workerSupplier, GitWorkdirFactory workdirFactory) {
try (WorkingCopy<Repository> 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<R> {
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 */

View File

@@ -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<Repository> 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<MergeCommandResult> {
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;
}
}
}
}

View File

@@ -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<org.eclipse.jgit.lib.Repository> 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<String> 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) {