mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-12-21 15:59:48 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -42,7 +42,10 @@ import com.google.common.collect.Multimap;
|
||||
import org.eclipse.jgit.api.FetchCommand;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.attributes.Attribute;
|
||||
import org.eclipse.jgit.attributes.Attributes;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import org.eclipse.jgit.lfs.LfsPointer;
|
||||
import org.eclipse.jgit.lib.AnyObjectId;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
@@ -55,6 +58,7 @@ import org.eclipse.jgit.transport.FetchResult;
|
||||
import org.eclipse.jgit.transport.RefSpec;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.eclipse.jgit.util.LfsFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ContextEntry;
|
||||
@@ -65,10 +69,12 @@ import sonia.scm.web.GitUserAgentProvider;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static java.util.Optional.empty;
|
||||
import static java.util.Optional.of;
|
||||
|
||||
//~--- JDK imports ------------------------------------------------------------
|
||||
@@ -79,7 +85,7 @@ import static java.util.Optional.of;
|
||||
*/
|
||||
public final class GitUtil
|
||||
{
|
||||
|
||||
|
||||
private static final GitUserAgentProvider GIT_USER_AGENT_PROVIDER = new GitUserAgentProvider();
|
||||
|
||||
/** Field description */
|
||||
@@ -325,14 +331,14 @@ public final class GitUtil
|
||||
|
||||
return branch;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns {@code true} if the provided reference name is a branch name.
|
||||
*
|
||||
*
|
||||
* @param refName reference name
|
||||
*
|
||||
*
|
||||
* @return {@code true} if the name is a branch name
|
||||
*
|
||||
*
|
||||
* @since 1.50
|
||||
*/
|
||||
public static boolean isBranch(String refName)
|
||||
@@ -611,11 +617,11 @@ public final class GitUtil
|
||||
|
||||
/**
|
||||
* Returns the name of the tag or {@code null} if the the ref is not a tag.
|
||||
*
|
||||
*
|
||||
* @param refName ref name
|
||||
*
|
||||
*
|
||||
* @return name of tag or {@link null}
|
||||
*
|
||||
*
|
||||
* @since 1.50
|
||||
*/
|
||||
public static String getTagName(String refName)
|
||||
@@ -688,7 +694,7 @@ public final class GitUtil
|
||||
{
|
||||
//J-
|
||||
return fs.resolve(dir, DIRECTORY_OBJETCS).exists()
|
||||
&& fs.resolve(dir, DIRECTORY_REFS).exists()
|
||||
&& fs.resolve(dir, DIRECTORY_REFS).exists()
|
||||
&&!fs.resolve(dir, DIRECTORY_DOTGIT).exists();
|
||||
//J+
|
||||
}
|
||||
@@ -727,7 +733,26 @@ public final class GitUtil
|
||||
mergeBaseWalk.setRevFilter(RevFilter.MERGE_BASE);
|
||||
mergeBaseWalk.markStart(mergeBaseWalk.lookupCommit(revision1));
|
||||
mergeBaseWalk.markStart(mergeBaseWalk.parseCommit(revision2));
|
||||
return mergeBaseWalk.next().getId();
|
||||
RevCommit ancestor = mergeBaseWalk.next();
|
||||
if (ancestor == null) {
|
||||
String msg = "revisions %s and %s are not related and therefore do not have a common ancestor";
|
||||
throw new NoCommonHistoryException(String.format(msg, revision1.name(), revision2.name()));
|
||||
}
|
||||
return ancestor.getId();
|
||||
}
|
||||
}
|
||||
|
||||
public static Optional<LfsPointer> getLfsPointer(org.eclipse.jgit.lib.Repository repo, String path, RevCommit commit, TreeWalk treeWalk) throws IOException {
|
||||
Attributes attributes = LfsFactory.getAttributesForPath(repo, path, commit);
|
||||
|
||||
Attribute filter = attributes.get("filter");
|
||||
if (filter != null && "lfs".equals(filter.getValue())) {
|
||||
ObjectId blobId = treeWalk.getObjectId(0);
|
||||
try (InputStream is = repo.open(blobId, Constants.OBJ_BLOB).openStream()) {
|
||||
return of(LfsPointer.parseLfsPointer(is));
|
||||
}
|
||||
} else {
|
||||
return empty();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -140,8 +140,8 @@ class AbstractGitCommand
|
||||
}
|
||||
}
|
||||
|
||||
<R, W extends GitCloneWorker<R>> R inClone(Function<Git, W> workerSupplier, GitWorkdirFactory workdirFactory) {
|
||||
try (WorkingCopy<Repository> workingCopy = workdirFactory.createWorkingCopy(context)) {
|
||||
<R, W extends GitCloneWorker<R>> R inClone(Function<Git, W> workerSupplier, GitWorkdirFactory workdirFactory, String initialBranch) {
|
||||
try (WorkingCopy<Repository> workingCopy = workdirFactory.createWorkingCopy(context, initialBranch)) {
|
||||
Repository repository = workingCopy.getWorkingRepository();
|
||||
logger.debug("cloned repository to folder {}", repository.getWorkTree());
|
||||
return workerSupplier.apply(new Git(repository)).run();
|
||||
|
||||
@@ -55,7 +55,7 @@ final class Differ implements AutoCloseable {
|
||||
if (!Strings.isNullOrEmpty(request.getAncestorChangeset()))
|
||||
{
|
||||
ObjectId otherRevision = repository.resolve(request.getAncestorChangeset());
|
||||
ObjectId ancestorId = computeCommonAncestor(repository, revision, otherRevision);
|
||||
ObjectId ancestorId = GitUtil.computeCommonAncestor(repository, revision, otherRevision);
|
||||
RevTree tree = walk.parseCommit(ancestorId).getTree();
|
||||
treeWalk.addTree(tree);
|
||||
}
|
||||
@@ -82,10 +82,6 @@ final class Differ implements AutoCloseable {
|
||||
return new Differ(commit, walk, treeWalk);
|
||||
}
|
||||
|
||||
private static ObjectId computeCommonAncestor(org.eclipse.jgit.lib.Repository repository, ObjectId revision1, ObjectId revision2) throws IOException {
|
||||
return GitUtil.computeCommonAncestor(repository, revision1, revision2);
|
||||
}
|
||||
|
||||
private Diff diff() throws IOException {
|
||||
List<DiffEntry> entries = DiffEntry.scan(treeWalk);
|
||||
return new Diff(commit, entries);
|
||||
@@ -115,4 +111,5 @@ final class Differ implements AutoCloseable {
|
||||
return entries;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -58,11 +58,8 @@ public class GitBranchCommand extends AbstractGitCommand implements BranchComman
|
||||
|
||||
@Override
|
||||
public Branch branch(BranchRequest request) {
|
||||
try (WorkingCopy<org.eclipse.jgit.lib.Repository> workingCopy = workdirFactory.createWorkingCopy(context)) {
|
||||
try (WorkingCopy<org.eclipse.jgit.lib.Repository> workingCopy = workdirFactory.createWorkingCopy(context, request.getParentBranch())) {
|
||||
Git clone = new Git(workingCopy.getWorkingRepository());
|
||||
if (request.getParentBranch() != null) {
|
||||
clone.checkout().setName("origin/" + request.getParentBranch()).call();
|
||||
}
|
||||
Ref ref = clone.branchCreate().setName(request.getNewBranch()).call();
|
||||
Iterable<PushResult> call = clone.push().add(request.getNewBranch()).call();
|
||||
StreamSupport.stream(call.spliterator(), false)
|
||||
|
||||
@@ -38,6 +38,7 @@ package sonia.scm.repository.spi;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Maps;
|
||||
import org.eclipse.jgit.lfs.LfsPointer;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
@@ -57,13 +58,17 @@ import sonia.scm.repository.GitSubModuleParser;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.SubRepository;
|
||||
import sonia.scm.store.Blob;
|
||||
import sonia.scm.store.BlobStore;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
@@ -86,18 +91,20 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
*/
|
||||
private static final Logger logger =
|
||||
LoggerFactory.getLogger(GitBrowseCommand.class);
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Constructs ...
|
||||
*
|
||||
* @param context
|
||||
* @param context
|
||||
* @param repository
|
||||
* @param lfsBlobStoreFactory
|
||||
*/
|
||||
public GitBrowseCommand(GitContext context, Repository repository)
|
||||
public GitBrowseCommand(GitContext context, Repository repository, LfsBlobStoreFactory lfsBlobStoreFactory)
|
||||
{
|
||||
super(context, repository);
|
||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||
}
|
||||
|
||||
//~--- get methods ----------------------------------------------------------
|
||||
@@ -167,7 +174,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
* @throws IOException
|
||||
*/
|
||||
private FileObject createFileObject(org.eclipse.jgit.lib.Repository repo,
|
||||
BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk)
|
||||
BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk)
|
||||
throws IOException {
|
||||
|
||||
FileObject file = new FileObject();
|
||||
@@ -195,7 +202,6 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
ObjectLoader loader = repo.open(treeWalk.getObjectId(0));
|
||||
|
||||
file.setDirectory(loader.getType() == Constants.OBJ_TREE);
|
||||
file.setLength(loader.getSize());
|
||||
|
||||
// don't show message and date for directories to improve performance
|
||||
if (!file.isDirectory() &&!request.isDisableLastCommit())
|
||||
@@ -203,6 +209,16 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
logger.trace("fetch last commit for {} at {}", path, revId.getName());
|
||||
RevCommit commit = getLatestCommit(repo, revId, path);
|
||||
|
||||
Optional<LfsPointer> lfsPointer = GitUtil.getLfsPointer(repo, path, commit, treeWalk);
|
||||
|
||||
if (lfsPointer.isPresent()) {
|
||||
BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
|
||||
Blob blob = lfsBlobStore.get(lfsPointer.get().getOid().getName());
|
||||
file.setLength(blob.getSize());
|
||||
} else {
|
||||
file.setLength(loader.getSize());
|
||||
}
|
||||
|
||||
if (commit != null)
|
||||
{
|
||||
file.setLastModified(GitUtil.getCommitTime(commit));
|
||||
@@ -232,7 +248,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
* @return
|
||||
*/
|
||||
private RevCommit getLatestCommit(org.eclipse.jgit.lib.Repository repo,
|
||||
ObjectId revId, String path)
|
||||
ObjectId revId, String path)
|
||||
{
|
||||
RevCommit result = null;
|
||||
RevWalk walk = null;
|
||||
@@ -339,7 +355,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
}
|
||||
|
||||
private FileObject findFirstMatch(org.eclipse.jgit.lib.Repository repo,
|
||||
BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException {
|
||||
BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk) throws IOException {
|
||||
String[] pathElements = request.getPath().split("/");
|
||||
int currentDepth = 0;
|
||||
int limit = pathElements.length;
|
||||
@@ -364,7 +380,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String,
|
||||
SubRepository> getSubRepositories(org.eclipse.jgit.lib.Repository repo,
|
||||
ObjectId revision)
|
||||
ObjectId revision)
|
||||
throws IOException {
|
||||
if (logger.isDebugEnabled())
|
||||
{
|
||||
@@ -375,7 +391,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
Map<String, SubRepository> subRepositories;
|
||||
try ( ByteArrayOutputStream baos = new ByteArrayOutputStream() )
|
||||
{
|
||||
new GitCatCommand(context, repository).getContent(repo, revision,
|
||||
new GitCatCommand(context, repository, lfsBlobStoreFactory).getContent(repo, revision,
|
||||
PATH_MODULES, baos);
|
||||
subRepositories = GitSubModuleParser.parse(baos.toString());
|
||||
}
|
||||
@@ -389,7 +405,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
}
|
||||
|
||||
private SubRepository getSubRepository(org.eclipse.jgit.lib.Repository repo,
|
||||
ObjectId revId, String path)
|
||||
ObjectId revId, String path)
|
||||
throws IOException {
|
||||
Map<String, SubRepository> subRepositories = subrepositoryCache.get(revId);
|
||||
|
||||
@@ -410,7 +426,7 @@ public class GitBrowseCommand extends AbstractGitCommand
|
||||
}
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
|
||||
/** sub repository cache */
|
||||
private final Map<ObjectId, Map<String, SubRepository>> subrepositoryCache = Maps.newHashMap();
|
||||
}
|
||||
|
||||
@@ -33,6 +33,7 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lfs.LfsPointer;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
@@ -45,13 +46,18 @@ import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.repository.GitUtil;
|
||||
import sonia.scm.store.Blob;
|
||||
import sonia.scm.store.BlobStore;
|
||||
import sonia.scm.util.IOUtil;
|
||||
import sonia.scm.util.Util;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.Closeable;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Optional;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
@@ -61,15 +67,18 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand {
|
||||
|
||||
private static final Logger logger = LoggerFactory.getLogger(GitCatCommand.class);
|
||||
|
||||
public GitCatCommand(GitContext context, sonia.scm.repository.Repository repository) {
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||
|
||||
public GitCatCommand(GitContext context, sonia.scm.repository.Repository repository, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
||||
super(context, repository);
|
||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getCatResult(CatCommandRequest request, OutputStream output) throws IOException {
|
||||
logger.debug("try to read content for {}", request);
|
||||
try (ClosableObjectLoaderContainer closableObjectLoaderContainer = getLoader(request)) {
|
||||
closableObjectLoaderContainer.objectLoader.copyTo(output);
|
||||
try (Loader closableObjectLoaderContainer = getLoader(request)) {
|
||||
closableObjectLoaderContainer.copyTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,18 +89,18 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand {
|
||||
}
|
||||
|
||||
void getContent(org.eclipse.jgit.lib.Repository repo, ObjectId revId, String path, OutputStream output) throws IOException {
|
||||
try (ClosableObjectLoaderContainer closableObjectLoaderContainer = getLoader(repo, revId, path)) {
|
||||
closableObjectLoaderContainer.objectLoader.copyTo(output);
|
||||
try (Loader closableObjectLoaderContainer = getLoader(repo, revId, path)) {
|
||||
closableObjectLoaderContainer.copyTo(output);
|
||||
}
|
||||
}
|
||||
|
||||
private ClosableObjectLoaderContainer getLoader(CatCommandRequest request) throws IOException {
|
||||
private Loader getLoader(CatCommandRequest request) throws IOException {
|
||||
org.eclipse.jgit.lib.Repository repo = open();
|
||||
ObjectId revId = getCommitOrDefault(repo, request.getRevision());
|
||||
return getLoader(repo, revId, request.getPath());
|
||||
}
|
||||
|
||||
private ClosableObjectLoaderContainer getLoader(Repository repo, ObjectId revId, String path) throws IOException {
|
||||
private Loader getLoader(Repository repo, ObjectId revId, String path) throws IOException {
|
||||
TreeWalk treeWalk = new TreeWalk(repo);
|
||||
treeWalk.setRecursive(Util.nonNull(path).contains("/"));
|
||||
|
||||
@@ -116,21 +125,67 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand {
|
||||
treeWalk.setFilter(PathFilter.create(path));
|
||||
|
||||
if (treeWalk.next() && treeWalk.getFileMode(0).getObjectType() == Constants.OBJ_BLOB) {
|
||||
ObjectId blobId = treeWalk.getObjectId(0);
|
||||
ObjectLoader loader = repo.open(blobId);
|
||||
|
||||
return new ClosableObjectLoaderContainer(loader, treeWalk, revWalk);
|
||||
Optional<LfsPointer> lfsPointer = GitUtil.getLfsPointer(repo, path, entry, treeWalk);
|
||||
if (lfsPointer.isPresent()) {
|
||||
return loadFromLfsStore(treeWalk, revWalk, lfsPointer.get());
|
||||
} else {
|
||||
return loadFromGit(repo, treeWalk, revWalk);
|
||||
}
|
||||
} else {
|
||||
throw notFound(entity("Path", path).in("Revision", revId.getName()).in(repository));
|
||||
}
|
||||
}
|
||||
|
||||
private static class ClosableObjectLoaderContainer implements Closeable {
|
||||
private Loader loadFromGit(Repository repo, TreeWalk treeWalk, RevWalk revWalk) throws IOException {
|
||||
ObjectId blobId = treeWalk.getObjectId(0);
|
||||
ObjectLoader loader = repo.open(blobId);
|
||||
|
||||
return new GitObjectLoaderWrapper(loader, treeWalk, revWalk);
|
||||
}
|
||||
|
||||
private Loader loadFromLfsStore(TreeWalk treeWalk, RevWalk revWalk, LfsPointer lfsPointer) throws IOException {
|
||||
BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
|
||||
Blob blob = lfsBlobStore.get(lfsPointer.getOid().getName());
|
||||
GitUtil.release(revWalk);
|
||||
GitUtil.release(treeWalk);
|
||||
return new BlobLoader(blob);
|
||||
}
|
||||
|
||||
private interface Loader extends Closeable {
|
||||
void copyTo(OutputStream output) throws IOException;
|
||||
|
||||
InputStream openStream() throws IOException;
|
||||
}
|
||||
|
||||
private static class BlobLoader implements Loader {
|
||||
private final InputStream inputStream;
|
||||
|
||||
private BlobLoader(Blob blob) throws IOException {
|
||||
this.inputStream = blob.getInputStream();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void copyTo(OutputStream output) throws IOException {
|
||||
IOUtil.copy(inputStream, output);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream openStream() {
|
||||
return inputStream;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
this.inputStream.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static class GitObjectLoaderWrapper implements Loader {
|
||||
private final ObjectLoader objectLoader;
|
||||
private final TreeWalk treeWalk;
|
||||
private final RevWalk revWalk;
|
||||
|
||||
private ClosableObjectLoaderContainer(ObjectLoader objectLoader, TreeWalk treeWalk, RevWalk revWalk) {
|
||||
private GitObjectLoaderWrapper(ObjectLoader objectLoader, TreeWalk treeWalk, RevWalk revWalk) {
|
||||
this.objectLoader = objectLoader;
|
||||
this.treeWalk = treeWalk;
|
||||
this.revWalk = revWalk;
|
||||
@@ -141,14 +196,22 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand {
|
||||
GitUtil.release(revWalk);
|
||||
GitUtil.release(treeWalk);
|
||||
}
|
||||
|
||||
public void copyTo(OutputStream output) throws IOException {
|
||||
this.objectLoader.copyTo(output);
|
||||
}
|
||||
|
||||
public InputStream openStream() throws IOException {
|
||||
return objectLoader.openStream();
|
||||
}
|
||||
}
|
||||
|
||||
private static class InputStreamWrapper extends FilterInputStream {
|
||||
|
||||
private final ClosableObjectLoaderContainer container;
|
||||
private final Loader container;
|
||||
|
||||
private InputStreamWrapper(ClosableObjectLoaderContainer container) throws IOException {
|
||||
super(container.objectLoader.openStream());
|
||||
private InputStreamWrapper(Loader container) throws IOException {
|
||||
super(container.openStream());
|
||||
this.container = container;
|
||||
}
|
||||
|
||||
|
||||
@@ -31,15 +31,12 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
|
||||
import org.eclipse.jgit.diff.DiffEntry;
|
||||
import org.eclipse.jgit.diff.DiffFormatter;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.DiffCommandBuilder;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -52,22 +49,25 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void getDiffResult(DiffCommandRequest request, OutputStream output) throws IOException {
|
||||
public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) throws IOException {
|
||||
@SuppressWarnings("squid:S2095") // repository will be closed with the RepositoryService
|
||||
org.eclipse.jgit.lib.Repository repository = open();
|
||||
try (DiffFormatter formatter = new DiffFormatter(new BufferedOutputStream(output))) {
|
||||
formatter.setRepository(repository);
|
||||
|
||||
Differ.Diff diff = Differ.diff(repository, request);
|
||||
Differ.Diff diff = Differ.diff(repository, request);
|
||||
|
||||
for (DiffEntry e : diff.getEntries()) {
|
||||
if (!e.getOldId().equals(e.getNewId())) {
|
||||
formatter.format(e);
|
||||
return output -> {
|
||||
try (DiffFormatter formatter = new DiffFormatter(output)) {
|
||||
formatter.setRepository(repository);
|
||||
|
||||
for (DiffEntry e : diff.getEntries()) {
|
||||
if (!e.getOldId().equals(e.getNewId())) {
|
||||
formatter.format(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
formatter.flush();
|
||||
}
|
||||
formatter.flush();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.eclipse.jgit.attributes.FilterCommand;
|
||||
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.SCMContextProvider;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import javax.servlet.ServletContextEvent;
|
||||
import javax.servlet.ServletContextListener;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import static java.nio.file.StandardOpenOption.CREATE;
|
||||
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
|
||||
|
||||
@Extension
|
||||
public class GitLfsFilterContextListener implements ServletContextListener {
|
||||
|
||||
public static final String GITCONFIG = "[filter \"lfs\"]\n" +
|
||||
"clean = git-lfs clean -- %f\n" +
|
||||
"smudge = git-lfs smudge -- %f\n" +
|
||||
"process = git-lfs filter-process\n" +
|
||||
"required = true\n";
|
||||
public static final Pattern COMMAND_NAME_PATTERN = Pattern.compile("git-lfs (smudge|clean) -- .*");
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitLfsFilterContextListener.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
|
||||
@Inject
|
||||
public GitLfsFilterContextListener(SCMContextProvider contextProvider) {
|
||||
this.contextProvider = contextProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextInitialized(ServletContextEvent sce) {
|
||||
Path gitconfig = contextProvider.getBaseDirectory().toPath().resolve("gitconfig");
|
||||
try {
|
||||
Files.write(gitconfig, GITCONFIG.getBytes(Charset.defaultCharset()), TRUNCATE_EXISTING, CREATE);
|
||||
FS.DETECTED.setGitSystemConfig(gitconfig.toFile());
|
||||
LOG.info("wrote git config file: {}", gitconfig);
|
||||
} catch (IOException e) {
|
||||
LOG.error("could not write git config in path {}; git lfs support may not work correctly", gitconfig, e);
|
||||
}
|
||||
FilterCommandRegistry.register(COMMAND_NAME_PATTERN, NoOpFilterCommand::new);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void contextDestroyed(ServletContextEvent sce) {
|
||||
FilterCommandRegistry.unregister(COMMAND_NAME_PATTERN);
|
||||
}
|
||||
|
||||
private static class NoOpFilterCommand extends FilterCommand {
|
||||
NoOpFilterCommand(Repository db, InputStream in, OutputStream out) {
|
||||
super(in, out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int run() throws IOException {
|
||||
ByteStreams.copy(in, out);
|
||||
in.close();
|
||||
out.close();
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,7 +38,7 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
|
||||
@Override
|
||||
public MergeCommandResult merge(MergeCommandRequest request) {
|
||||
return inClone(clone -> new MergeWorker(clone, request), workdirFactory);
|
||||
return inClone(clone -> new MergeWorker(clone, request), workdirFactory, request.getTargetBranch());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -72,7 +72,6 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
|
||||
@Override
|
||||
MergeCommandResult run() throws IOException {
|
||||
checkOutTargetBranch();
|
||||
MergeResult result = doMergeInClone();
|
||||
if (result.getMergeStatus().isSuccessful()) {
|
||||
doCommit();
|
||||
@@ -83,10 +82,6 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
||||
}
|
||||
}
|
||||
|
||||
private void checkOutTargetBranch() throws IOException {
|
||||
checkOutBranch(target);
|
||||
}
|
||||
|
||||
private MergeResult doMergeInClone() throws IOException {
|
||||
MergeResult result;
|
||||
try {
|
||||
|
||||
@@ -1,47 +1,46 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.util.concurrent.Striped;
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import sonia.scm.BadRequestException;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.ConcurrentModificationException;
|
||||
import sonia.scm.ContextEntry;
|
||||
import sonia.scm.NoChangesMadeException;
|
||||
import sonia.scm.repository.GitWorkdirFactory;
|
||||
import sonia.scm.repository.InternalRepositoryException;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.FileAlreadyExistsException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.NoSuchFileException;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Optional;
|
||||
|
||||
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
|
||||
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
|
||||
import java.util.concurrent.locks.Lock;
|
||||
|
||||
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
|
||||
|
||||
private final GitWorkdirFactory workdirFactory;
|
||||
private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class);
|
||||
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
|
||||
|
||||
GitModifyCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) {
|
||||
private final GitWorkdirFactory workdirFactory;
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||
|
||||
GitModifyCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
||||
super(context, repository);
|
||||
this.workdirFactory = workdirFactory;
|
||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String execute(ModifyCommandRequest request) {
|
||||
return inClone(clone -> new ModifyWorker(clone, request), workdirFactory);
|
||||
return inClone(clone -> new ModifyWorker(clone, request), workdirFactory, request.getBranch());
|
||||
}
|
||||
|
||||
private class ModifyWorker extends GitCloneWorker<String> implements Worker {
|
||||
private class ModifyWorker extends GitCloneWorker<String> implements ModifyWorkerHelper {
|
||||
|
||||
private final File workDir;
|
||||
private final ModifyCommandRequest request;
|
||||
@@ -54,58 +53,43 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
||||
|
||||
@Override
|
||||
String run() throws IOException {
|
||||
if (!StringUtils.isEmpty(request.getBranch())) {
|
||||
checkOutBranch(request.getBranch());
|
||||
}
|
||||
Ref head = getClone().getRepository().exactRef(Constants.HEAD);
|
||||
doThrow().violation("branch has to be a valid branch, no revision", "branch", request.getBranch()).when(head == null || !head.isSymbolic());
|
||||
getClone().getRepository().getFullBranch();
|
||||
if (!StringUtils.isEmpty(request.getExpectedRevision())) {
|
||||
if (!request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
||||
throw new ConcurrentModificationException("branch", request.getBranch() == null? "default": request.getBranch());
|
||||
}
|
||||
if (!StringUtils.isEmpty(request.getExpectedRevision())
|
||||
&& !request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
||||
throw new ConcurrentModificationException("branch", request.getBranch() == null ? "default" : request.getBranch());
|
||||
}
|
||||
for (ModifyCommandRequest.PartialRequest r : request.getRequests()) {
|
||||
r.execute(this);
|
||||
}
|
||||
failIfNotChanged(NoChangesMadeException::new);
|
||||
failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()));
|
||||
Optional<RevCommit> revCommit = doCommit(request.getCommitMessage(), request.getAuthor());
|
||||
push();
|
||||
return revCommit.orElseThrow(NoChangesMadeException::new).name();
|
||||
return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void create(String toBeCreated, File file, boolean overwrite) throws IOException {
|
||||
Path targetFile = new File(workDir, toBeCreated).toPath();
|
||||
createDirectories(targetFile);
|
||||
if (overwrite) {
|
||||
Files.move(file.toPath(), targetFile, REPLACE_EXISTING);
|
||||
} else {
|
||||
public void addFileToScm(String name, Path file) {
|
||||
addToGitWithLfsSupport(name, file);
|
||||
}
|
||||
|
||||
private void addToGitWithLfsSupport(String path, Path targetFile) {
|
||||
REGISTER_LOCKS.get(targetFile).lock();
|
||||
try {
|
||||
LfsBlobStoreCleanFilterFactory cleanFilterFactory = new LfsBlobStoreCleanFilterFactory(lfsBlobStoreFactory, repository, targetFile);
|
||||
|
||||
String registerKey = "git-lfs clean -- '" + path + "'";
|
||||
LOG.debug("register lfs filter command factory for command '{}'", registerKey);
|
||||
FilterCommandRegistry.register(registerKey, cleanFilterFactory::createFilter);
|
||||
try {
|
||||
Files.move(file.toPath(), targetFile);
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
throw alreadyExists(createFileContext(toBeCreated));
|
||||
addFileToGit(path);
|
||||
} catch (GitAPIException e) {
|
||||
throwInternalRepositoryException("could not add file to index", e);
|
||||
} finally {
|
||||
LOG.debug("unregister lfs filter command factory for command \"{}\"", registerKey);
|
||||
FilterCommandRegistry.unregister(registerKey);
|
||||
}
|
||||
}
|
||||
try {
|
||||
addFileToGit(toBeCreated);
|
||||
} catch (GitAPIException e) {
|
||||
throwInternalRepositoryException("could not add new file to index", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void modify(String path, File file) throws IOException {
|
||||
Path targetFile = new File(workDir, path).toPath();
|
||||
createDirectories(targetFile);
|
||||
if (!targetFile.toFile().exists()) {
|
||||
throw notFound(createFileContext(path));
|
||||
}
|
||||
Files.move(file.toPath(), targetFile, REPLACE_EXISTING);
|
||||
try {
|
||||
addFileToGit(path);
|
||||
} catch (GitAPIException e) {
|
||||
throwInternalRepositoryException("could not add new file to index", e);
|
||||
} finally {
|
||||
REGISTER_LOCKS.get(targetFile).unlock();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -114,13 +98,7 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
||||
}
|
||||
|
||||
@Override
|
||||
public void delete(String toBeDeleted) throws IOException {
|
||||
Path fileToBeDeleted = new File(workDir, toBeDeleted).toPath();
|
||||
try {
|
||||
Files.delete(fileToBeDeleted);
|
||||
} catch (NoSuchFileException e) {
|
||||
throw notFound(createFileContext(toBeDeleted));
|
||||
}
|
||||
public void doScmDelete(String toBeDeleted) {
|
||||
try {
|
||||
getClone().rm().addFilepattern(removeStartingPathSeparators(toBeDeleted)).call();
|
||||
} catch (GitAPIException e) {
|
||||
@@ -128,6 +106,21 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getWorkDir() {
|
||||
return workDir;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getBranch() {
|
||||
return request.getBranch();
|
||||
}
|
||||
|
||||
private String removeStartingPathSeparators(String path) {
|
||||
while (path.startsWith(File.separator)) {
|
||||
path = path.substring(1);
|
||||
@@ -135,41 +128,8 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
||||
return path;
|
||||
}
|
||||
|
||||
private void createDirectories(Path targetFile) throws IOException {
|
||||
try {
|
||||
Files.createDirectories(targetFile.getParent());
|
||||
} catch (FileAlreadyExistsException e) {
|
||||
throw alreadyExists(createFileContext(targetFile.toString()));
|
||||
}
|
||||
private String throwInternalRepositoryException(String message, Exception e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), message, e);
|
||||
}
|
||||
|
||||
private ContextEntry.ContextBuilder createFileContext(String path) {
|
||||
ContextEntry.ContextBuilder contextBuilder = entity("file", path);
|
||||
if (!StringUtils.isEmpty(request.getBranch())) {
|
||||
contextBuilder.in("branch", request.getBranch());
|
||||
}
|
||||
contextBuilder.in(context.getRepository());
|
||||
return contextBuilder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void move(String sourcePath, String targetPath) {
|
||||
|
||||
}
|
||||
|
||||
private class NoChangesMadeException extends BadRequestException {
|
||||
public NoChangesMadeException() {
|
||||
super(ContextEntry.ContextBuilder.entity(context.getRepository()).build(), "no changes detected to branch " + ModifyWorker.this.request.getBranch());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "40RaYIeeR1";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private String throwInternalRepositoryException(String message, Exception e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), message, e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.EnumSet;
|
||||
@@ -76,9 +77,10 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
|
||||
//~--- constructors ---------------------------------------------------------
|
||||
|
||||
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider) {
|
||||
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
||||
this.handler = handler;
|
||||
this.repository = repository;
|
||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||
this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
|
||||
}
|
||||
|
||||
@@ -143,7 +145,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
@Override
|
||||
public BrowseCommand getBrowseCommand()
|
||||
{
|
||||
return new GitBrowseCommand(context, repository);
|
||||
return new GitBrowseCommand(context, repository, lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -155,7 +157,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
@Override
|
||||
public CatCommand getCatCommand()
|
||||
{
|
||||
return new GitCatCommand(context, repository);
|
||||
return new GitCatCommand(context, repository, lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -271,7 +273,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
|
||||
@Override
|
||||
public ModifyCommand getModifyCommand() {
|
||||
return new GitModifyCommand(context, repository, handler.getWorkdirFactory());
|
||||
return new GitModifyCommand(context, repository, handler.getWorkdirFactory(), lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -281,11 +283,13 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
/** Field description */
|
||||
private GitContext context;
|
||||
private final GitContext context;
|
||||
|
||||
/** Field description */
|
||||
private GitRepositoryHandler handler;
|
||||
private final GitRepositoryHandler handler;
|
||||
|
||||
/** Field description */
|
||||
private Repository repository;
|
||||
private final Repository repository;
|
||||
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,7 @@ import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||
import sonia.scm.plugin.Extension;
|
||||
import sonia.scm.repository.GitRepositoryHandler;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
@@ -49,11 +50,13 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||
|
||||
private final GitRepositoryHandler handler;
|
||||
private final GitRepositoryConfigStoreProvider storeProvider;
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||
|
||||
@Inject
|
||||
public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider) {
|
||||
public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
||||
this.handler = handler;
|
||||
this.storeProvider = storeProvider;
|
||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,7 +64,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
|
||||
GitRepositoryServiceProvider provider = null;
|
||||
|
||||
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
|
||||
provider = new GitRepositoryServiceProvider(handler, repository, storeProvider);
|
||||
provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
return provider;
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.ByteStreams;
|
||||
import org.eclipse.jgit.attributes.FilterCommand;
|
||||
import org.eclipse.jgit.lfs.LfsPointer;
|
||||
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
|
||||
import org.eclipse.jgit.lfs.lib.Constants;
|
||||
import org.eclipse.jgit.lfs.lib.LongObjectId;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.store.Blob;
|
||||
import sonia.scm.store.BlobStore;
|
||||
import sonia.scm.util.IOUtil;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.security.DigestOutputStream;
|
||||
|
||||
/**
|
||||
* Adapted version of JGit's {@link org.eclipse.jgit.lfs.CleanFilter} to write the
|
||||
* lfs file directly to the lfs blob store.
|
||||
*/
|
||||
class LfsBlobStoreCleanFilter extends FilterCommand {
|
||||
|
||||
private static final Logger LOG = LoggerFactory.getLogger(LfsBlobStoreCleanFilter.class);
|
||||
|
||||
private final BlobStore lfsBlobStore;
|
||||
private final Path targetFile;
|
||||
|
||||
LfsBlobStoreCleanFilter(InputStream in, OutputStream out, BlobStore lfsBlobStore, Path targetFile) {
|
||||
super(in, out);
|
||||
this.lfsBlobStore = lfsBlobStore;
|
||||
this.targetFile = targetFile;
|
||||
}
|
||||
|
||||
@Override
|
||||
// Suppress warning for RuntimeException after check for wrong size, because mathematicians say this will never happen
|
||||
@SuppressWarnings("squid:S00112")
|
||||
public int run() throws IOException {
|
||||
LOG.debug("running scm lfs filter for file {}", targetFile);
|
||||
DigestOutputStream digestOutputStream = createDigestStream();
|
||||
try {
|
||||
long size = ByteStreams.copy(in, digestOutputStream);
|
||||
AnyLongObjectId loid = LongObjectId.fromRaw(digestOutputStream.getMessageDigest().digest());
|
||||
String hash = loid.getName();
|
||||
|
||||
Blob existingBlob = lfsBlobStore.get(hash);
|
||||
if (existingBlob != null) {
|
||||
LOG.debug("found existing lfs blob for oid {}", hash);
|
||||
long blobSize = existingBlob.getSize();
|
||||
if (blobSize != size) {
|
||||
throw new RuntimeException("lfs entry already exists for loid " + hash + " but has wrong size");
|
||||
}
|
||||
} else {
|
||||
LOG.debug("uploading new lfs blob for oid {}", hash);
|
||||
Blob newBlob = lfsBlobStore.create(hash);
|
||||
OutputStream outputStream = newBlob.getOutputStream();
|
||||
Files.copy(targetFile, outputStream);
|
||||
newBlob.commit();
|
||||
}
|
||||
|
||||
LfsPointer lfsPointer = new LfsPointer(loid, size);
|
||||
lfsPointer.encode(out);
|
||||
return -1;
|
||||
} finally {
|
||||
IOUtil.close(digestOutputStream);
|
||||
IOUtil.close(in);
|
||||
IOUtil.close(out);
|
||||
}
|
||||
}
|
||||
|
||||
private DigestOutputStream createDigestStream() {
|
||||
return new DigestOutputStream(new OutputStream() {
|
||||
@Override
|
||||
public void write(int b) {
|
||||
// no further target here, we are just interested in the digest
|
||||
}
|
||||
}, Constants.newMessageDigest());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Path;
|
||||
|
||||
class LfsBlobStoreCleanFilterFactory {
|
||||
|
||||
private final LfsBlobStoreFactory blobStoreFactory;
|
||||
private final sonia.scm.repository.Repository repository;
|
||||
private final Path targetFile;
|
||||
|
||||
LfsBlobStoreCleanFilterFactory(LfsBlobStoreFactory blobStoreFactory, sonia.scm.repository.Repository repository, Path targetFile) {
|
||||
this.blobStoreFactory = blobStoreFactory;
|
||||
this.repository = repository;
|
||||
this.targetFile = targetFile;
|
||||
}
|
||||
|
||||
@SuppressWarnings("squid:S1172") // suppress unused parameter to keep the api compatible to jgit's FilterCommandFactory
|
||||
LfsBlobStoreCleanFilter createFilter(Repository db, InputStream in, OutputStream out) {
|
||||
return new LfsBlobStoreCleanFilter(in, out, blobStoreFactory.getLfsBlobStore(repository), targetFile);
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@ package sonia.scm.repository.spi;
|
||||
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||
import sonia.scm.repository.GitWorkdirFactory;
|
||||
@@ -11,6 +13,10 @@ import sonia.scm.repository.util.WorkdirProvider;
|
||||
|
||||
import javax.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
import static sonia.scm.NotFoundException.notFound;
|
||||
|
||||
public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory<Repository, GitContext> implements GitWorkdirFactory {
|
||||
|
||||
@@ -20,14 +26,23 @@ public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory<Repository, Gi
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParentAndClone<Repository> cloneRepository(GitContext context, File target) {
|
||||
public ParentAndClone<Repository> cloneRepository(GitContext context, File target, String initialBranch) {
|
||||
try {
|
||||
return new ParentAndClone<>(null, Git.cloneRepository()
|
||||
Repository clone = Git.cloneRepository()
|
||||
.setURI(createScmTransportProtocolUri(context.getDirectory()))
|
||||
.setDirectory(target)
|
||||
.setBranch(initialBranch)
|
||||
.call()
|
||||
.getRepository());
|
||||
} catch (GitAPIException e) {
|
||||
.getRepository();
|
||||
|
||||
Ref head = clone.exactRef(Constants.HEAD);
|
||||
|
||||
if (head == null || !head.isSymbolic() || (initialBranch != null && !head.getTarget().getName().endsWith(initialBranch))) {
|
||||
throw notFound(entity("Branch", initialBranch).in(context.getRepository()));
|
||||
}
|
||||
|
||||
return new ParentAndClone<>(null, clone);
|
||||
} catch (GitAPIException | IOException e) {
|
||||
throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -171,6 +171,6 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
private GitBrowseCommand createCommand() {
|
||||
return new GitBrowseCommand(createContext(), repository);
|
||||
return new GitBrowseCommand(createContext(), repository, null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,12 +39,18 @@ import org.junit.Test;
|
||||
import org.junit.rules.ExpectedException;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.store.Blob;
|
||||
import sonia.scm.store.BlobStore;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link GitCatCommand}.
|
||||
@@ -136,7 +142,7 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase {
|
||||
CatCommandRequest request = new CatCommandRequest();
|
||||
request.setPath("b.txt");
|
||||
|
||||
InputStream catResultStream = new GitCatCommand(createContext(), repository).getCatResultStream(request);
|
||||
InputStream catResultStream = new GitCatCommand(createContext(), repository, null).getCatResultStream(request);
|
||||
|
||||
assertEquals('b', catResultStream.read());
|
||||
assertEquals('\n', catResultStream.read());
|
||||
@@ -145,13 +151,38 @@ public class GitCatCommandTest extends AbstractGitCommandTestBase {
|
||||
catResultStream.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testLfsStream() throws IOException {
|
||||
LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||
BlobStore blobStore = mock(BlobStore.class);
|
||||
Blob blob = mock(Blob.class);
|
||||
when(lfsBlobStoreFactory.getLfsBlobStore(repository)).thenReturn(blobStore);
|
||||
when(blobStore.get("d2252bd9fde1bb2ae7531b432c48262c3cbe4df4376008986980de40a7c9cf8b"))
|
||||
.thenReturn(blob);
|
||||
when(blob.getInputStream()).thenReturn(new ByteArrayInputStream(new byte[]{'i', 's'}));
|
||||
|
||||
CatCommandRequest request = new CatCommandRequest();
|
||||
request.setRevision("lfs-test");
|
||||
request.setPath("lfs-image.png");
|
||||
|
||||
InputStream catResultStream = new GitCatCommand(createContext(), repository, lfsBlobStoreFactory)
|
||||
.getCatResultStream(request);
|
||||
|
||||
assertEquals('i', catResultStream.read());
|
||||
assertEquals('s', catResultStream.read());
|
||||
|
||||
assertEquals(-1, catResultStream.read());
|
||||
|
||||
catResultStream.close();
|
||||
}
|
||||
|
||||
private String execute(CatCommandRequest request) throws IOException {
|
||||
String content = null;
|
||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||
|
||||
try
|
||||
{
|
||||
new GitCatCommand(createContext(), repository).getCatResult(request,
|
||||
new GitCatCommand(createContext(), repository, null).getCatResult(request,
|
||||
baos);
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -44,7 +44,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||
diffCommandRequest.setRevision("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest, output);
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_A + DIFF_FILE_B, output.toString());
|
||||
}
|
||||
|
||||
@@ -54,7 +54,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
DiffCommandRequest diffCommandRequest = new DiffCommandRequest();
|
||||
diffCommandRequest.setRevision("test-branch");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest, output);
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_A + DIFF_FILE_B, output.toString());
|
||||
}
|
||||
|
||||
@@ -65,7 +65,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
diffCommandRequest.setRevision("test-branch");
|
||||
diffCommandRequest.setPath("a.txt");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest, output);
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_A, output.toString());
|
||||
}
|
||||
|
||||
@@ -76,7 +76,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
diffCommandRequest.setRevision("master");
|
||||
diffCommandRequest.setAncestorChangeset("test-branch");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest, output);
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_A_MULTIPLE_REVISIONS + DIFF_FILE_F_MULTIPLE_REVISIONS, output.toString());
|
||||
}
|
||||
|
||||
@@ -88,7 +88,7 @@ public class GitDiffCommandTest extends AbstractGitCommandTestBase {
|
||||
diffCommandRequest.setAncestorChangeset("test-branch");
|
||||
diffCommandRequest.setPath("a.txt");
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest, output);
|
||||
gitDiffCommand.getDiffResult(diffCommandRequest).accept(output);
|
||||
assertEquals(DIFF_FILE_A_MULTIPLE_REVISIONS, output.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,12 +20,14 @@ import sonia.scm.NotFoundException;
|
||||
import sonia.scm.ScmConstraintViolationException;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.util.WorkdirProvider;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
||||
public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
||||
@@ -37,6 +39,8 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||
|
||||
@Test
|
||||
public void shouldCreateCommit() throws IOException, GitAPIException {
|
||||
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
|
||||
@@ -263,8 +267,8 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
||||
command.execute(request);
|
||||
}
|
||||
|
||||
@Test(expected = ScmConstraintViolationException.class)
|
||||
public void shouldFailWithConstraintViolationIfBranchIsNoBranch() throws IOException {
|
||||
@Test(expected = NotFoundException.class)
|
||||
public void shouldFailWithNotFoundExceptionIfBranchIsNoBranch() throws IOException {
|
||||
File newFile = Files.write(temporaryFolder.newFile().toPath(), "irrelevant\n".getBytes()).toFile();
|
||||
|
||||
GitModifyCommand command = createCommand();
|
||||
@@ -296,7 +300,7 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
private GitModifyCommand createCommand() {
|
||||
return new GitModifyCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider()));
|
||||
return new GitModifyCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.util.WorkdirProvider;
|
||||
import sonia.scm.store.Blob;
|
||||
import sonia.scm.store.BlobStore;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
||||
public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
@Rule
|
||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||
|
||||
@Before
|
||||
public void registerFilter() {
|
||||
new GitLfsFilterContextListener(contextProvider).contextInitialized(null);
|
||||
}
|
||||
|
||||
@After
|
||||
public void unregisterFilter() {
|
||||
new GitLfsFilterContextListener(contextProvider).contextDestroyed(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateCommit() throws IOException, GitAPIException {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
String newRef = createCommit("new_lfs.png", "new content", "fe32608c9ef5b6cf7e3f946480253ff76f24f4ec0678f3d0f07f9844cbff9601", outputStream);
|
||||
|
||||
try (Git git = new Git(createContext().open())) {
|
||||
RevCommit lastCommit = getLastCommit(git);
|
||||
assertThat(lastCommit.getFullMessage()).isEqualTo("test commit");
|
||||
assertThat(lastCommit.getAuthorIdent().getName()).isEqualTo("Dirk Gently");
|
||||
assertThat(newRef).isEqualTo(lastCommit.toObjectId().name());
|
||||
}
|
||||
|
||||
assertThat(outputStream.toString()).isEqualTo("new content");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldCreateSecondCommits() throws IOException, GitAPIException {
|
||||
createCommit("new_lfs.png", "new content", "fe32608c9ef5b6cf7e3f946480253ff76f24f4ec0678f3d0f07f9844cbff9601", new ByteArrayOutputStream());
|
||||
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
String newRef = createCommit("more_lfs.png", "more content", "2c2316737c9313956dfc0083da3a2a62ce259f66484f3e26440f0d1b02dd4128", outputStream);
|
||||
|
||||
try (Git git = new Git(createContext().open())) {
|
||||
RevCommit lastCommit = getLastCommit(git);
|
||||
assertThat(lastCommit.getFullMessage()).isEqualTo("test commit");
|
||||
assertThat(lastCommit.getAuthorIdent().getName()).isEqualTo("Dirk Gently");
|
||||
assertThat(newRef).isEqualTo(lastCommit.toObjectId().name());
|
||||
}
|
||||
|
||||
assertThat(outputStream.toString()).isEqualTo("more content");
|
||||
}
|
||||
|
||||
private String createCommit(String fileName, String content, String hashOfContent, ByteArrayOutputStream outputStream) throws IOException {
|
||||
BlobStore blobStore = mock(BlobStore.class);
|
||||
Blob blob = mock(Blob.class);
|
||||
when(lfsBlobStoreFactory.getLfsBlobStore(any())).thenReturn(blobStore);
|
||||
when(blobStore.create(hashOfContent)).thenReturn(blob);
|
||||
when(blobStore.get(hashOfContent)).thenReturn(null, blob);
|
||||
when(blob.getOutputStream()).thenReturn(outputStream);
|
||||
when(blob.getSize()).thenReturn((long) content.length());
|
||||
|
||||
File newFile = Files.write(temporaryFolder.newFile().toPath(), content.getBytes()).toFile();
|
||||
|
||||
GitModifyCommand command = createCommand();
|
||||
|
||||
ModifyCommandRequest request = new ModifyCommandRequest();
|
||||
request.setCommitMessage("test commit");
|
||||
request.addRequest(new ModifyCommandRequest.CreateFileRequest(fileName, newFile, false));
|
||||
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||
|
||||
return command.execute(request);
|
||||
}
|
||||
|
||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
||||
return git.log().setMaxCount(1).call().iterator().next();
|
||||
}
|
||||
|
||||
private GitModifyCommand createCommand() {
|
||||
return new GitModifyCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getZippedRepositoryResource() {
|
||||
return "sonia/scm/repository/spi/scm-git-spi-lfs-test.zip";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,88 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.github.sdorra.shiro.ShiroRule;
|
||||
import com.github.sdorra.shiro.SubjectAware;
|
||||
import org.eclipse.jgit.api.Git;
|
||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.rules.TemporaryFolder;
|
||||
import sonia.scm.ScmConstraintViolationException;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.util.WorkdirProvider;
|
||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
||||
public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@Rule
|
||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||
@Rule
|
||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||
@Rule
|
||||
public ShiroRule shiro = new ShiroRule();
|
||||
|
||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||
|
||||
@Test
|
||||
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
|
||||
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
|
||||
|
||||
GitModifyCommand command = createCommand();
|
||||
|
||||
ModifyCommandRequest request = new ModifyCommandRequest();
|
||||
request.setCommitMessage("test commit");
|
||||
request.addRequest(new ModifyCommandRequest.CreateFileRequest("new_file", newFile, false));
|
||||
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||
|
||||
command.execute(request);
|
||||
|
||||
TreeAssertions assertions = canonicalTreeParser -> assertThat(canonicalTreeParser.findFile("new_file")).isTrue();
|
||||
|
||||
assertInTree(assertions);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getZippedRepositoryResource() {
|
||||
return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
|
||||
}
|
||||
|
||||
private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
||||
try (Git git = new Git(createContext().open())) {
|
||||
RevCommit lastCommit = getLastCommit(git);
|
||||
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
||||
RevCommit commit = walk.parseCommit(lastCommit);
|
||||
ObjectId treeId = commit.getTree().getId();
|
||||
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
||||
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
||||
return git.log().setMaxCount(1).call().iterator().next();
|
||||
}
|
||||
|
||||
private GitModifyCommand createCommand() {
|
||||
return new GitModifyCommand(createContext(), repository, new SimpleGitWorkdirFactory(new WorkdirProvider()), lfsBlobStoreFactory);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
private interface TreeAssertions {
|
||||
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
||||
}
|
||||
}
|
||||
@@ -20,8 +20,6 @@ import java.io.IOException;
|
||||
import static com.google.inject.util.Providers.of;
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
||||
|
||||
@@ -43,11 +41,11 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyPoolShouldCreateNewWorkdir() throws IOException {
|
||||
public void emptyPoolShouldCreateNewWorkdir() {
|
||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||
File masterRepo = createRepositoryDirectory();
|
||||
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||
|
||||
assertThat(workingCopy.getDirectory())
|
||||
.exists()
|
||||
@@ -61,25 +59,37 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloneFromPoolShouldNotBeReused() throws IOException {
|
||||
public void shouldCheckoutInitialBranch() {
|
||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext(), "test-branch")) {
|
||||
assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt"))
|
||||
.exists()
|
||||
.isFile()
|
||||
.hasContent("a and b");
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloneFromPoolShouldNotBeReused() {
|
||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||
|
||||
File firstDirectory;
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||
firstDirectory = workingCopy.getDirectory();
|
||||
}
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||
File secondDirectory = workingCopy.getDirectory();
|
||||
assertThat(secondDirectory).isNotEqualTo(firstDirectory);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void cloneFromPoolShouldBeDeletedOnClose() throws IOException {
|
||||
public void cloneFromPoolShouldBeDeletedOnClose() {
|
||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||
|
||||
File directory;
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||
directory = workingCopy.getWorkingRepository().getWorkTree();
|
||||
}
|
||||
assertThat(directory).doesNotExist();
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user