diff --git a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java index 3d0ea94536..4c8fcd7306 100644 --- a/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java +++ b/scm-core/src/main/java/sonia/scm/plugin/PluginManager.java @@ -73,6 +73,13 @@ public interface PluginManager { */ List getAvailable(); + /** + * Returns all updatable plugins. + * + * @return a list of updatable plugins. + */ + List getUpdatable(); + /** * Installs the plugin with the given name from the list of available plugins. * @@ -93,4 +100,14 @@ public interface PluginManager { * Install all pending plugins and restart the scm context. */ void executePendingAndRestart(); + + /** + * Cancel all pending plugins. + */ + void cancelPending(); + + /** + * Update all installed plugins. + */ + void updateAll(); } diff --git a/scm-core/src/main/java/sonia/scm/repository/NoCommonHistoryException.java b/scm-core/src/main/java/sonia/scm/repository/NoCommonHistoryException.java new file mode 100644 index 0000000000..ddb7793d20 --- /dev/null +++ b/scm-core/src/main/java/sonia/scm/repository/NoCommonHistoryException.java @@ -0,0 +1,22 @@ +package sonia.scm.repository; + +import sonia.scm.BadRequestException; + +import static java.util.Collections.emptyList; + +@SuppressWarnings("squid:MaximumInheritanceDepth") +public class NoCommonHistoryException extends BadRequestException { + + public NoCommonHistoryException() { + this("no common history"); + } + + public NoCommonHistoryException(String message) { + super(emptyList(), message); + } + + @Override + public String getCode() { + return "4iRct4avG1"; + } +} diff --git a/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java b/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java index 18d4e11a7f..03f4361083 100644 --- a/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java +++ b/scm-core/src/main/java/sonia/scm/repository/api/DiffCommandBuilder.java @@ -40,7 +40,6 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import sonia.scm.repository.Feature; import sonia.scm.repository.spi.DiffCommand; -import sonia.scm.util.IOUtil; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -103,16 +102,12 @@ public final class DiffCommandBuilder extends AbstractDiffCommandBuilder 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(); } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java index ca417550f4..0204ca4e3c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/Differ.java @@ -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 entries = DiffEntry.scan(treeWalk); return new Diff(commit, entries); @@ -115,4 +111,5 @@ final class Differ implements AutoCloseable { return entries; } } + } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 2a254d96ce..5ec69cccdd 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -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 = 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 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 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 subRepositories = subrepositoryCache.get(revId); @@ -410,7 +426,7 @@ public class GitBrowseCommand extends AbstractGitCommand } //~--- fields --------------------------------------------------------------- - + /** sub repository cache */ private final Map> subrepositoryCache = Maps.newHashMap(); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 7477e0aee3..35ff4d6ac2 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -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 = 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; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index 5d5f27806b..1ac64c1b5e 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -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(); + } + }; } } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java index 24dcff01d8..4c02a3a73c 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceProvider.java @@ -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); } /** @@ -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; } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java index 0730ffc9cf..547c6b25f8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitRepositoryServiceResolver.java @@ -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; diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java index 1feceba652..4b854f6209 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitBrowseCommandTest.java @@ -171,6 +171,6 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase { } private GitBrowseCommand createCommand() { - return new GitBrowseCommand(createContext(), repository); + return new GitBrowseCommand(createContext(), repository, null); } } diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java index 0418bc3e61..eea8bc0017 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitCatCommandTest.java @@ -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 diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java index fd9c45be5c..52932e83ae 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitDiffCommandTest.java @@ -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()); } } diff --git a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip index 8f689e9664..addda86090 100644 Binary files a/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip and b/scm-plugins/scm-git-plugin/src/test/resources/sonia/scm/repository/spi/scm-git-spi-test.zip differ diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java index d705f0ac14..9e43e26014 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgDiffCommand.java @@ -39,13 +39,12 @@ import com.google.common.base.Strings; import com.google.common.io.ByteStreams; import com.google.common.io.Closeables; import sonia.scm.repository.Repository; +import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; import sonia.scm.repository.spi.javahg.HgDiffInternalCommand; import sonia.scm.web.HgUtil; -import java.io.IOException; import java.io.InputStream; -import java.io.OutputStream; //~--- JDK imports ------------------------------------------------------------ @@ -71,41 +70,36 @@ public class HgDiffCommand extends AbstractCommand implements DiffCommand //~--- get methods ---------------------------------------------------------- @Override - public void getDiffResult(DiffCommandRequest request, OutputStream output) - throws IOException + public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) { - com.aragost.javahg.Repository hgRepo = open(); + return output -> { + com.aragost.javahg.Repository hgRepo = open(); - HgDiffInternalCommand cmd = HgDiffInternalCommand.on(hgRepo); - DiffFormat format = request.getFormat(); + HgDiffInternalCommand cmd = HgDiffInternalCommand.on(hgRepo); + DiffFormat format = request.getFormat(); - if (format == DiffFormat.GIT) - { - cmd.git(); - } - - cmd.change(HgUtil.getRevision(request.getRevision())); - - InputStream inputStream = null; - - try - { - - if (!Strings.isNullOrEmpty(request.getPath())) + if (format == DiffFormat.GIT) { - inputStream = cmd.stream(hgRepo.file(request.getPath())); - } - else - { - inputStream = cmd.stream(); + cmd.git(); } - ByteStreams.copy(inputStream, output); + cmd.change(HgUtil.getRevision(request.getRevision())); - } - finally - { - Closeables.close(inputStream, true); - } + InputStream inputStream = null; + + try { + + if (!Strings.isNullOrEmpty(request.getPath())) { + inputStream = cmd.stream(hgRepo.file(request.getPath())); + } else { + inputStream = cmd.stream(); + } + + ByteStreams.copy(inputStream, output); + + } finally { + Closeables.close(inputStream, true); + } + }; } } diff --git a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java index 4aaa12e28f..3bbde02844 100644 --- a/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java +++ b/scm-plugins/scm-svn-plugin/src/main/java/sonia/scm/repository/spi/SvnDiffCommand.java @@ -46,11 +46,10 @@ import org.tmatesoft.svn.core.wc.SVNRevision; import sonia.scm.repository.InternalRepositoryException; import sonia.scm.repository.Repository; import sonia.scm.repository.SvnUtil; +import sonia.scm.repository.api.DiffCommandBuilder; import sonia.scm.repository.api.DiffFormat; import sonia.scm.util.Util; -import java.io.OutputStream; - //~--- JDK imports ------------------------------------------------------------ /** @@ -70,33 +69,34 @@ public class SvnDiffCommand extends AbstractSvnCommand implements DiffCommand { } @Override - public void getDiffResult(DiffCommandRequest request, OutputStream output) { + public DiffCommandBuilder.OutputStreamConsumer getDiffResult(DiffCommandRequest request) { logger.debug("create diff for {}", request); Preconditions.checkNotNull(request, "request is required"); - Preconditions.checkNotNull(output, "outputstream is required"); String path = request.getPath(); - SVNClientManager clientManager = null; - try { - SVNURL svnurl = context.createUrl(); - if (Util.isNotEmpty(path)) { - svnurl = svnurl.appendPath(path, true); + return output -> { + SVNClientManager clientManager = null; + try { + SVNURL svnurl = context.createUrl(); + if (Util.isNotEmpty(path)) { + svnurl = svnurl.appendPath(path, true); + } + clientManager = SVNClientManager.newInstance(); + SVNDiffClient diffClient = clientManager.getDiffClient(); + diffClient.setDiffGenerator(new SvnNewDiffGenerator(new SCMSvnDiffGenerator())); + + long currentRev = SvnUtil.getRevisionNumber(request.getRevision(), repository); + + diffClient.setGitDiffFormat(request.getFormat() == DiffFormat.GIT); + + diffClient.doDiff(svnurl, SVNRevision.HEAD, + SVNRevision.create(currentRev - 1), SVNRevision.create(currentRev), + SVNDepth.INFINITY, false, output); + } catch (SVNException ex) { + throw new InternalRepositoryException(repository, "could not create diff", ex); + } finally { + SvnUtil.dispose(clientManager); } - clientManager = SVNClientManager.newInstance(); - SVNDiffClient diffClient = clientManager.getDiffClient(); - diffClient.setDiffGenerator(new SvnNewDiffGenerator(new SCMSvnDiffGenerator())); - - long currentRev = SvnUtil.getRevisionNumber(request.getRevision(), repository); - - diffClient.setGitDiffFormat(request.getFormat() == DiffFormat.GIT); - - diffClient.doDiff(svnurl, SVNRevision.HEAD, - SVNRevision.create(currentRev - 1), SVNRevision.create(currentRev), - SVNDepth.INFINITY, false, output); - } catch (SVNException ex) { - throw new InternalRepositoryException(repository, "could not create diff", ex); - } finally { - SvnUtil.dispose(clientManager); - } + }; } } diff --git a/scm-ui-components/packages/ui-components/src/apiclient.js b/scm-ui-components/packages/ui-components/src/apiclient.js index 2ad0cd1f9f..a656246c95 100644 --- a/scm-ui-components/packages/ui-components/src/apiclient.js +++ b/scm-ui-components/packages/ui-components/src/apiclient.js @@ -46,7 +46,7 @@ export function createUrl(url: string) { class ApiClient { get(url: string): Promise { - return fetch(createUrl(url), applyFetchOptions).then(handleFailure); + return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); } post(url: string, payload: any, contentType: string = "application/json") { diff --git a/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js b/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js index b73688ebbf..66b3f21052 100644 --- a/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js +++ b/scm-ui-components/packages/ui-components/src/buttons/ButtonGroup.js @@ -14,7 +14,7 @@ class ButtonGroup extends React.Component { const childWrapper = []; React.Children.forEach(children, child => { if (child) { - childWrapper.push(

{child}

); + childWrapper.push(
{child}
); } }); diff --git a/scm-ui-components/packages/ui-components/src/layout/Level.js b/scm-ui-components/packages/ui-components/src/layout/Level.js new file mode 100644 index 0000000000..5359de7c66 --- /dev/null +++ b/scm-ui-components/packages/ui-components/src/layout/Level.js @@ -0,0 +1,21 @@ +//@flow +import * as React from "react"; +import classNames from "classnames"; + +type Props = { + className?: string, + left?: React.Node, + right?: React.Node +}; + +export default class Level extends React.Component { + render() { + const { className, left, right } = this.props; + return ( +
+
{left}
+
{right}
+
+ ); + } +} diff --git a/scm-ui-components/packages/ui-components/src/layout/index.js b/scm-ui-components/packages/ui-components/src/layout/index.js index 7708c45c99..ac7279a06a 100644 --- a/scm-ui-components/packages/ui-components/src/layout/index.js +++ b/scm-ui-components/packages/ui-components/src/layout/index.js @@ -2,6 +2,7 @@ export { default as Footer } from "./Footer.js"; export { default as Header } from "./Header.js"; +export { default as Level } from "./Level.js"; export { default as Page } from "./Page.js"; export { default as PageActions } from "./PageActions.js"; export { default as Subtitle } from "./Subtitle.js"; diff --git a/scm-ui-components/packages/ui-components/src/repos/Diff.js b/scm-ui-components/packages/ui-components/src/repos/Diff.js index 7a5bcbf4a2..99a0e1d917 100644 --- a/scm-ui-components/packages/ui-components/src/repos/Diff.js +++ b/scm-ui-components/packages/ui-components/src/repos/Diff.js @@ -4,7 +4,8 @@ import DiffFile from "./DiffFile"; import type {DiffObjectProps, File} from "./DiffTypes"; type Props = DiffObjectProps & { - diff: File[] + diff: File[], + defaultCollapse?: boolean }; class Diff extends React.Component { @@ -17,7 +18,7 @@ class Diff extends React.Component { return ( <> {diff.map((file, index) => ( - + ))} ); diff --git a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js index 24009353af..f268940709 100644 --- a/scm-ui-components/packages/ui-components/src/repos/DiffFile.js +++ b/scm-ui-components/packages/ui-components/src/repos/DiffFile.js @@ -13,6 +13,7 @@ import classNames from "classnames"; import { translate } from "react-i18next"; import { Button, ButtonGroup } from "../buttons"; import Tag from "../Tag"; +import Icon from "../Icon"; const styles = { panel: { @@ -69,7 +70,7 @@ const styles = { type Props = DiffObjectProps & { file: File, - collapsible: true, + defaultCollapse: boolean, // context props classes: any, @@ -82,16 +83,31 @@ type State = { }; class DiffFile extends React.Component { + static defaultProps = { + defaultCollapse: false + }; + constructor(props: Props) { super(props); this.state = { - collapsed: false, + collapsed: this.props.defaultCollapse, sideBySide: false }; } + // collapse diff by clicking collapseDiffs button + componentDidUpdate(prevProps) { + const { defaultCollapse } = this.props; + if (prevProps.defaultCollapse !== defaultCollapse) { + this.setState({ + collapsed: defaultCollapse + }); + } + } + toggleCollapse = () => { - if (this.props.collapsable) { + const { file } = this.props; + if (file && !file.isBinaray) { this.setState(state => ({ collapsed: !state.collapsed })); @@ -173,7 +189,8 @@ class DiffFile extends React.Component { ) { return ( <> - {file.oldPath} {file.newPath} + {file.oldPath} {" "} + {file.newPath} ); } else if (file.type === "delete") { @@ -233,7 +250,6 @@ class DiffFile extends React.Component { file, fileControlFactory, fileAnnotationFactory, - collapsible, classes, t } = this.props; @@ -241,12 +257,12 @@ class DiffFile extends React.Component { const viewType = sideBySide ? "split" : "unified"; let body = null; - let icon = "fa fa-angle-right"; + let icon = "angle-right"; if (!collapsed) { const fileAnnotations = fileAnnotationFactory ? fileAnnotationFactory(file) : null; - icon = "fa fa-angle-down"; + icon = "angle-down"; body = (
{fileAnnotations} @@ -259,7 +275,9 @@ class DiffFile extends React.Component {
); } - const collapseIcon = collapsible ? : null; + const collapseIcon = !file.isBinary ? ( + + ) : null; const fileControls = fileControlFactory ? fileControlFactory(file, this.setCollapse) @@ -285,20 +303,10 @@ class DiffFile extends React.Component { + icon={sideBySide ? "align-left" : "columns"} + label={t(sideBySide ? "diff.combined" : "diff.sideBySide")} + reducedMobile={true} + /> {fileControls} diff --git a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js index 876b757f44..16f2d8e5f1 100644 --- a/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/LoadingDiff.js @@ -9,7 +9,8 @@ import Diff from "./Diff"; import type {DiffObjectProps, File} from "./DiffTypes"; type Props = DiffObjectProps & { - url: string + url: string, + defaultCollapse?: boolean }; type State = { diff --git a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js index d5d3c4e665..5bc6b249dd 100644 --- a/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js +++ b/scm-ui-components/packages/ui-components/src/repos/changesets/ChangesetDiff.js @@ -7,6 +7,7 @@ import {translate} from "react-i18next"; type Props = { changeset: Changeset, + defaultCollapse?: boolean, // context props t: string => string @@ -23,12 +24,12 @@ class ChangesetDiff extends React.Component { } render() { - const { changeset, t } = this.props; + const { changeset, defaultCollapse, t } = this.props; if (!this.isDiffSupported(changeset)) { return {t("changeset.diffNotSupported")}; } else { const url = this.createUrl(changeset); - return ; + return ; } } diff --git a/scm-ui-components/packages/ui-types/src/Plugin.js b/scm-ui-components/packages/ui-types/src/Plugin.js index c7612a8bf8..f5a621e1ff 100644 --- a/scm-ui-components/packages/ui-types/src/Plugin.js +++ b/scm-ui-components/packages/ui-types/src/Plugin.js @@ -17,6 +17,7 @@ export type Plugin = { }; export type PluginCollection = Collection & { + _links: Links, _embedded: { plugins: Plugin[] | string[] } diff --git a/scm-ui/public/locales/de/admin.json b/scm-ui/public/locales/de/admin.json index d4f3567059..af34c5b759 100644 --- a/scm-ui/public/locales/de/admin.json +++ b/scm-ui/public/locales/de/admin.json @@ -29,7 +29,11 @@ "installedNavLink": "Installiert", "availableNavLink": "Verfügbar" }, - "executePending": "Ausstehende Plugin-Änderungen ausführen", + "executePending": "Änderungen ausführen", + "outdatedPlugins": "{{count}} veraltetes Plugin", + "outdatedPlugins_plural": "{{count}} veraltete Plugins", + "updateAll": "Alle Plugins aktualisieren", + "cancelPending": "Änderungen abbrechen", "noPlugins": "Keine Plugins gefunden.", "modal": { "title": { @@ -48,6 +52,7 @@ "updateAndRestart": "Aktualisieren und Neustarten", "uninstallAndRestart": "Deinstallieren and Neustarten", "executeAndRestart": "Ausführen und Neustarten", + "updateAll": "Alle Plugins aktualisieren", "abort": "Abbrechen", "author": "Autor", "version": "Version", @@ -58,7 +63,9 @@ "successNotification": "Das Plugin wurde erfolgreich installiert. Um Änderungen an der UI zu sehen, muss die Seite neu geladen werden:", "reload": "jetzt neu laden", "restartNotification": "Der SCM-Manager Kontext sollte nur neu gestartet werden, wenn aktuell niemand damit arbeitet.", - "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet." + "executePending": "Die folgenden Plugin-Änderungen werden ausgeführt. Anschließend wird der SCM-Manager Kontext neu gestartet.", + "cancelPending": "Die folgenden Plugin-Änderungen werden abgebrochen und zurückgesetzt.", + "updateAllInfo": "Die folgenden Plugins werden aktualisiert. Die Änderungen werden nach dem nächsten Neustart wirksam." } }, "repositoryRole": { diff --git a/scm-ui/public/locales/de/repos.json b/scm-ui/public/locales/de/repos.json index 20cb7b4418..cec34319ab 100644 --- a/scm-ui/public/locales/de/repos.json +++ b/scm-ui/public/locales/de/repos.json @@ -73,7 +73,8 @@ "errorTitle": "Fehler", "errorSubtitle": "Changesets konnten nicht abgerufen werden", "noChangesets": "Keine Changesets in diesem Branch gefunden.", - "branchSelectorLabel": "Branches" + "branchSelectorLabel": "Branches", + "collapseDiffs": "Auf-/Zuklappen" }, "changeset": { "description": "Beschreibung", diff --git a/scm-ui/public/locales/en/admin.json b/scm-ui/public/locales/en/admin.json index c23a88a920..c315d1d142 100644 --- a/scm-ui/public/locales/en/admin.json +++ b/scm-ui/public/locales/en/admin.json @@ -29,7 +29,11 @@ "installedNavLink": "Installed", "availableNavLink": "Available" }, - "executePending": "Execute pending plugin changes", + "executePending": "Execute changes", + "outdatedPlugins": "{{count}} outdated plugin", + "outdatedPlugins_plural": "{{count}} outdated plugins", + "updateAll": "Update all plugins", + "cancelPending": "Cancel changes", "noPlugins": "No plugins found.", "modal": { "title": { @@ -48,6 +52,7 @@ "updateAndRestart": "Update and Restart", "uninstallAndRestart": "Uninstall and Restart", "executeAndRestart": "Execute and Restart", + "updateAll": "Update all plugins", "abort": "Abort", "author": "Author", "version": "Version", @@ -58,7 +63,9 @@ "successNotification": "Successful installed plugin. You have to reload the page, to see ui changes:", "reload": "reload now", "restartNotification": "You should only restart the scm-manager context if no one else is currently working with it.", - "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted." + "executePending": "The following plugin changes will be executed and after that the scm-manager context will be restarted.", + "cancelPending": "The following plugin changes will be canceled.", + "updateAllInfo": "The following plugin changes will be executed. You need to restart the scm-manager to make these changes effective." } }, "repositoryRole": { diff --git a/scm-ui/public/locales/en/repos.json b/scm-ui/public/locales/en/repos.json index 29c0473ad9..76d21aa679 100644 --- a/scm-ui/public/locales/en/repos.json +++ b/scm-ui/public/locales/en/repos.json @@ -73,7 +73,8 @@ "errorTitle": "Error", "errorSubtitle": "Could not fetch changesets", "noChangesets": "No changesets found for this branch.", - "branchSelectorLabel": "Branches" + "branchSelectorLabel": "Branches", + "collapseDiffs": "Collapse" }, "changeset": { "description": "Description", diff --git a/scm-ui/public/locales/es/admin.json b/scm-ui/public/locales/es/admin.json index db9fade43c..155313ed99 100644 --- a/scm-ui/public/locales/es/admin.json +++ b/scm-ui/public/locales/es/admin.json @@ -30,6 +30,8 @@ "availableNavLink": "Disponibles" }, "executePending": "Ejecutar los complementos pendientes", + "updateAll": "Actualizar todos los complementos", + "cancelPending": "Cancelar los complementos pendientes", "noPlugins": "No se han encontrado complementos.", "modal": { "title": { diff --git a/scm-ui/public/locales/es/repos.json b/scm-ui/public/locales/es/repos.json index 08c9235593..efcbdd8c2e 100644 --- a/scm-ui/public/locales/es/repos.json +++ b/scm-ui/public/locales/es/repos.json @@ -73,7 +73,8 @@ "errorTitle": "Error", "errorSubtitle": "No se han podido recuperar los changesets", "noChangesets": "No se han encontrado changesets para esta rama branch.", - "branchSelectorLabel": "Ramas" + "branchSelectorLabel": "Ramas", + "collapseDiffs": "Colapso" }, "changeset": { "description": "Descripción", diff --git a/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js b/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js new file mode 100644 index 0000000000..6ca6400099 --- /dev/null +++ b/scm-ui/src/admin/plugins/components/CancelPendingActionModal.js @@ -0,0 +1,42 @@ +// @flow + +import React from "react"; +import PluginActionModal from "./PluginActionModal"; +import type { PendingPlugins } from "@scm-manager/ui-types"; +import { apiClient } from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; + +type Props = { + onClose: () => void, + refresh: () => void, + pendingPlugins: PendingPlugins, + + // context props + t: string => string +}; + +class CancelPendingActionModal extends React.Component { + render() { + const { onClose, pendingPlugins, t } = this.props; + + return ( + + ); + } + + cancelPending = () => { + const { pendingPlugins, refresh, onClose } = this.props; + return apiClient + .post(pendingPlugins._links.cancel.href) + .then(refresh) + .then(onClose); + }; +} + +export default translate("admin")(CancelPendingActionModal); diff --git a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js b/scm-ui/src/admin/plugins/components/ExecutePendingAction.js deleted file mode 100644 index 6c8d407205..0000000000 --- a/scm-ui/src/admin/plugins/components/ExecutePendingAction.js +++ /dev/null @@ -1,68 +0,0 @@ -// @flow -import React from "react"; -import { Button } from "@scm-manager/ui-components"; -import type { PendingPlugins } from "@scm-manager/ui-types"; -import { translate } from "react-i18next"; -import ExecutePendingModal from "./ExecutePendingModal"; - -type Props = { - pendingPlugins: PendingPlugins, - - // context props - t: string => string -}; - -type State = { - showModal: boolean -}; - -class ExecutePendingAction extends React.Component { - constructor(props: Props) { - super(props); - this.state = { - showModal: false - }; - } - - openModal = () => { - this.setState({ - showModal: true - }); - }; - - closeModal = () => { - this.setState({ - showModal: false - }); - }; - - renderModal = () => { - const { showModal } = this.state; - const { pendingPlugins } = this.props; - if (showModal) { - return ( - - ); - } - return null; - }; - - render() { - const { t } = this.props; - return ( - <> - {this.renderModal()} -