Merge with default

This commit is contained in:
Rene Pfeuffer
2019-12-19 08:46:45 +01:00
43 changed files with 1098 additions and 846 deletions

View File

@@ -46,8 +46,11 @@ import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
import static java.util.Collections.unmodifiableCollection;
import static java.util.Optional.ofNullable;
/**
* The FileObject represents a file or a directory in a repository.
@@ -90,7 +93,9 @@ public class FileObject implements LastModifiedAware, Serializable
&& Objects.equal(description, other.description)
&& Objects.equal(length, other.length)
&& Objects.equal(subRepository, other.subRepository)
&& Objects.equal(lastModified, other.lastModified);
&& Objects.equal(commitDate, other.commitDate)
&& Objects.equal(partialResult, other.partialResult)
&& Objects.equal(computationAborted, other.computationAborted);
//J+
}
@@ -100,8 +105,16 @@ public class FileObject implements LastModifiedAware, Serializable
@Override
public int hashCode()
{
return Objects.hashCode(name, path, directory, description, length,
subRepository, lastModified);
return Objects.hashCode(
name,
path,
directory,
description,
length,
subRepository,
commitDate,
partialResult,
computationAborted);
}
/**
@@ -118,7 +131,9 @@ public class FileObject implements LastModifiedAware, Serializable
.add("description", description)
.add("length", length)
.add("subRepository", subRepository)
.add("lastModified", lastModified)
.add("commitDate", commitDate)
.add("partialResult", partialResult)
.add("computationAborted", computationAborted)
.toString();
//J+
}
@@ -130,35 +145,44 @@ public class FileObject implements LastModifiedAware, Serializable
* if the repository provider is not able to get the last commit for the path.
*
*
* @return last commit message
* @return Last commit message or <code>null</code>, when this value has not been computed
* (see {@link #isPartialResult()}).
*/
public String getDescription()
public Optional<String> getDescription()
{
return description;
return ofNullable(description);
}
/**
* Returns the last commit date for this. The method will return null,
* if the repository provider is not able to get the last commit for the path.
* if the repository provider is not able to get the last commit for the path
* or it has not been computed.
*
*
* @return last commit date
*/
@Override
public Long getLastModified()
{
return lastModified;
public Long getLastModified() {
return this.isPartialResult()? null: this.commitDate;
}
/**
* Returns the length of the file.
*
*
* @return length of file
* Returns the last commit date for this. The method will return {@link OptionalLong#empty()},
* if the repository provider is not able to get the last commit for the path or if this value has not been computed
* (see {@link #isPartialResult()} and {@link #isComputationAborted()}).
*/
public long getLength()
public OptionalLong getCommitDate()
{
return length;
return commitDate == null? OptionalLong.empty(): OptionalLong.of(commitDate);
}
/**
* Returns the length of the file or {@link OptionalLong#empty()}, when this value has not been computed
* (see {@link #isPartialResult()} and {@link #isComputationAborted()}).
*/
public OptionalLong getLength()
{
return length == null? OptionalLong.empty(): OptionalLong.of(length);
}
/**
@@ -200,7 +224,7 @@ public class FileObject implements LastModifiedAware, Serializable
}
/**
* Return sub repository informations or null if the file is not
* Return sub repository information or null if the file is not
* sub repository.
*
* @since 1.10
@@ -222,6 +246,42 @@ public class FileObject implements LastModifiedAware, Serializable
return directory;
}
/**
* Returns the children of this file.
*
* @return The children of this file if it is a directory.
*/
public Collection<FileObject> getChildren() {
return children == null? null: unmodifiableCollection(children);
}
/**
* If this is <code>true</code>, some values for this object have not been computed, yet. These values (like
* {@link #getLength()}, {@link #getDescription()} or {@link #getCommitDate()})
* will return {@link Optional#empty()} (or {@link OptionalLong#empty()} respectively), unless they are computed.
* There may be an asynchronous task running, that will set these values in the future.
*
* @since 2.0.0
*
* @return <code>true</code>, whenever some values of this object have not been computed, yet.
*/
public boolean isPartialResult() {
return partialResult;
}
/**
* If this is <code>true</code>, some values for this object have not been computed and will not be computed. These
* values (like {@link #getLength()}, {@link #getDescription()} or {@link #getCommitDate()})
* will return {@link Optional#empty()} (or {@link OptionalLong#empty()} respectively), unless they are computed.
*
* @since 2.0.0
*
* @return <code>true</code>, whenever some values of this object finally are not computed.
*/
public boolean isComputationAborted() {
return computationAborted;
}
//~--- set methods ----------------------------------------------------------
/**
@@ -247,14 +307,14 @@ public class FileObject implements LastModifiedAware, Serializable
}
/**
* Sets the last modified date of the file.
* Sets the commit date of the file.
*
*
* @param lastModified last modified date
* @param commitDate commit date
*/
public void setLastModified(Long lastModified)
public void setCommitDate(Long commitDate)
{
this.lastModified = lastModified;
this.commitDate = commitDate;
}
/**
@@ -263,7 +323,7 @@ public class FileObject implements LastModifiedAware, Serializable
*
* @param length file length
*/
public void setLength(long length)
public void setLength(Long length)
{
this.length = length;
}
@@ -302,22 +362,47 @@ public class FileObject implements LastModifiedAware, Serializable
this.subRepository = subRepository;
}
public Collection<FileObject> getChildren() {
return unmodifiableCollection(children);
/**
* Set marker, that some values for this object are not computed, yet.
*
* @since 2.0.0
*
* @param partialResult Set this to <code>true</code>, whenever some values of this object are not computed, yet.
*/
public void setPartialResult(boolean partialResult) {
this.partialResult = partialResult;
}
/**
* Set marker, that computation of some values for this object has been aborted.
*
* @since 2.0.0
*
* @param computationAborted Set this to <code>true</code>, whenever some values of this object are not computed and
* will not be computed in the future.
*/
public void setComputationAborted(boolean computationAborted) {
this.computationAborted = computationAborted;
}
/**
* Set the children for this file.
*
* @param children The new childre.
*/
public void setChildren(List<FileObject> children) {
this.children = new ArrayList<>(children);
}
/**
* Adds a child to the list of children .
*
* @param child The additional child.
*/
public void addChild(FileObject child) {
this.children.add(child);
}
public boolean hasChildren() {
return !children.isEmpty();
}
//~--- fields ---------------------------------------------------------------
/** file description */
@@ -326,11 +411,11 @@ public class FileObject implements LastModifiedAware, Serializable
/** directory indicator */
private boolean directory;
/** last modified date */
private Long lastModified;
/** commit date */
private Long commitDate;
/** file length */
private long length;
private Long length;
/** filename */
private String name;
@@ -338,9 +423,16 @@ public class FileObject implements LastModifiedAware, Serializable
/** file path */
private String path;
/** Marker for partial result. */
private boolean partialResult = false;
/** Marker for aborted computation. */
private boolean computationAborted = false;
/** sub repository informations */
@XmlElement(name = "subrepository")
private SubRepository subRepository;
/** Children of this file (aka directory). */
private Collection<FileObject> children = new ArrayList<>();
}

View File

@@ -300,6 +300,13 @@ public final class BrowseCommandBuilder
return this;
}
private void updateCache(BrowserResult updatedResult) {
if (!disableCache) {
CacheKey key = new CacheKey(repository, request);
cache.put(key, updatedResult);
}
}
//~--- inner classes --------------------------------------------------------
/**
@@ -416,5 +423,5 @@ public final class BrowseCommandBuilder
private final Repository repository;
/** request for the command */
private final BrowseCommandRequest request = new BrowseCommandRequest();
private final BrowseCommandRequest request = new BrowseCommandRequest(this::updateCache);
}

View File

@@ -37,6 +37,10 @@ package sonia.scm.repository.spi;
import com.google.common.base.MoreObjects;
import com.google.common.base.Objects;
import sonia.scm.repository.BrowserResult;
import java.util.function.Consumer;
/**
*
* @author Sebastian Sdorra
@@ -48,6 +52,14 @@ public final class BrowseCommandRequest extends FileBaseCommandRequest
/** Field description */
private static final long serialVersionUID = 7956624623516803183L;
public BrowseCommandRequest() {
this(null);
}
public BrowseCommandRequest(Consumer<BrowserResult> updater) {
this.updater = updater;
}
//~--- methods --------------------------------------------------------------
/**
@@ -220,6 +232,12 @@ public final class BrowseCommandRequest extends FileBaseCommandRequest
return recursive;
}
public void updateCache(BrowserResult update) {
if (updater != null) {
updater.accept(update);
}
}
//~--- fields ---------------------------------------------------------------
/** disable last commit */
@@ -230,4 +248,8 @@ public final class BrowseCommandRequest extends FileBaseCommandRequest
/** browse file objects recursive */
private boolean recursive = false;
// WARNING / TODO: This field creates a reverse channel from the implementation to the API. This will break
// whenever the API runs in a different process than the SPI (for example to run explicit hosts for git repositories).
private final transient Consumer<BrowserResult> updater;
}

View File

@@ -0,0 +1,83 @@
package sonia.scm.repository.spi;
import java.util.function.Consumer;
/**
* Tasks submitted to this executor will be run synchronously up to a given time, after which they will be queued and
* processed asynchronously. After a maximum amount of time consumed by these tasks, they will be skipped. Note that
* this only works for short-living tasks.
* <p>
* Get instances of this using a {@link SyncAsyncExecutorProvider}.
*/
public interface SyncAsyncExecutor {
/**
* Execute the given task (either synchronously or asynchronously). If this task is skipped due to
* timeouts, nothing will be done.
*
* @param task The {@link Runnable} to be executed.
* @return Either {@link ExecutionType#SYNCHRONOUS} when the given {@link Runnable} has been executed immediately or
* {@link ExecutionType#ASYNCHRONOUS}, when the task was queued to be executed asynchronously in the future.
*/
default ExecutionType execute(Runnable task) {
return execute(
ignored -> task.run(),
() -> {}
);
}
/**
* Execute the given <code>task</code> (either synchronously or asynchronously). If this task is
* skipped due to timeouts, the <code>abortionFallback</code> will be called.
*
* @param task The {@link Runnable} to be executed.
* @param abortionFallback This will only be run, when this and all remaining tasks are aborted. This task should
* only consume a negligible amount of time.
* @return Either {@link ExecutionType#SYNCHRONOUS} when the given {@link Runnable} has been executed immediately or
* {@link ExecutionType#ASYNCHRONOUS}, when the task was queued to be executed asynchronously in the future.
*/
default ExecutionType execute(Runnable task, Runnable abortionFallback) {
return execute(ignored -> task.run(), abortionFallback);
}
/**
* Execute the given <code>task</code> (either synchronously or asynchronously). If this task is skipped due to
* timeouts, nothing will be done.
*
* @param task The {@link Consumer} to be executed. The parameter given to this is either
* {@link ExecutionType#SYNCHRONOUS} when the given {@link Consumer} is executed immediately
* or {@link ExecutionType#ASYNCHRONOUS}, when the task had been queued and now is executed
* asynchronously.
* @return Either {@link ExecutionType#SYNCHRONOUS} when the given {@link Runnable} has been executed immediately or
* {@link ExecutionType#ASYNCHRONOUS}, when the task was queued to be executed asynchronously in the future.
*/
default ExecutionType execute(Consumer<ExecutionType> task) {
return execute(task, () -> {});
}
/**
* Execute the given <code>task</code> (either synchronously or asynchronously). If this task is
* skipped due to timeouts, the <code>abortionFallback</code> will be called.
*
* @param task The {@link Consumer} to be executed. The parameter given to this is either
* {@link ExecutionType#SYNCHRONOUS} when the given {@link Consumer} is executed immediately
* or {@link ExecutionType#ASYNCHRONOUS}, when the task had been queued and now is executed
* asynchronously.
* @param abortionFallback This will only be run, when this and all remaining tasks are aborted. This task should
* only consume a negligible amount of time.
* @return Either {@link ExecutionType#SYNCHRONOUS} when the given {@link Runnable} has been executed immediately or
* {@link ExecutionType#ASYNCHRONOUS}, when the task was queued to be executed asynchronously in the future.
*/
ExecutionType execute(Consumer<ExecutionType> task, Runnable abortionFallback);
/**
* When all submitted tasks have been executed synchronously, this will return <code>true</code>. If at least one task
* has been enqueued to be executed asynchronously, this returns <code>false</code> (even when none of the enqueued
* tasks have been run, yet).
*/
boolean hasExecutedAllSynchronously();
enum ExecutionType {
SYNCHRONOUS, ASYNCHRONOUS
}
}

View File

@@ -0,0 +1,56 @@
package sonia.scm.repository.spi;
/**
* Use this provider to get {@link SyncAsyncExecutor} instances to execute a number of normally short-lived tasks, that
* should be run asynchronously (or even be skipped) whenever they take too long in summary.
* <p>
* The goal of this is a "best effort" approach: The submitted tasks are run immediately when they are submitted, unless
* a given timespan (<code>switchToAsyncInSeconds</code>) has passed. From this moment on the tasks are put into a queue to be
* processed asynchronously. If even then they take too long and their accumulated asynchronous runtime exceeds another
* limit (<code>maxAsyncAbortSeconds</code>), the tasks are skipped.
* <p>
* Note that whenever a task has been started either synchronously or asynchronously it will neither be terminated nor
* switched from foreground to background execution, so this will only work well for short-living tasks. A long running
* task can still block this for longer than the configured amount of seconds.
*/
public interface SyncAsyncExecutorProvider {
int DEFAULT_SWITCH_TO_ASYNC_IN_SECONDS = 2;
/**
* Creates an {@link SyncAsyncExecutor} that will run tasks synchronously for
* {@link #DEFAULT_SWITCH_TO_ASYNC_IN_SECONDS} seconds. The limit of asynchronous runtime is implementation dependant.
*
* @return The executor.
*/
default SyncAsyncExecutor createExecutorWithDefaultTimeout() {
return createExecutorWithSecondsToTimeout(DEFAULT_SWITCH_TO_ASYNC_IN_SECONDS);
}
/**
* Creates an {@link SyncAsyncExecutor} that will run tasks synchronously for
* <code>switchToAsyncInSeconds</code> seconds. The limit of asynchronous runtime is implementation dependant.
*
* @param switchToAsyncInSeconds The amount of seconds submitted tasks will be run synchronously. After this time,
* further tasks will be run asynchronously. To run all tasks asynchronously no matter
* what, set this to <code>0</code>.
* @return The executor.
*/
SyncAsyncExecutor createExecutorWithSecondsToTimeout(int switchToAsyncInSeconds);
/**
* Creates an {@link SyncAsyncExecutor} that will run tasks synchronously for
* <code>switchToAsyncInSeconds</code> seconds and will abort tasks after they ran
* <code>maxAsyncAbortSeconds</code> asynchronously.
*
* @param switchToAsyncInSeconds The amount of seconds submitted tasks will be run synchronously. After this time,
* further tasks will be run asynchronously. To run all tasks asynchronously no matter
* what, set this to <code>0</code>.
* @param maxAsyncAbortSeconds The amount of seconds, tasks that were started asynchronously may run in summary
* before remaining tasks will not be executed at all anymore. To abort all tasks that
* are submitted after <code>switchToAsyncInSeconds</code> immediately, set this to
* <code>0</code>.
* @return The executor.
*/
SyncAsyncExecutor createExecutorWithSecondsToTimeout(int switchToAsyncInSeconds, int maxAsyncAbortSeconds);
}

View File

@@ -745,6 +745,10 @@ public final class GitUtil
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);
return getLfsPointer(repo, treeWalk, attributes);
}
public static Optional<LfsPointer> getLfsPointer(org.eclipse.jgit.lib.Repository repo, TreeWalk treeWalk, Attributes attributes) throws IOException {
Attribute filter = attributes.get("filter");
if (filter != null && "lfs".equals(filter.getValue())) {
ObjectId blobId = treeWalk.getObjectId(0);

View File

@@ -35,9 +35,11 @@ package sonia.scm.repository.spi;
//~--- non-JDK imports --------------------------------------------------------
import com.google.common.base.Stopwatch;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.lfs.LfsPointer;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
@@ -49,6 +51,7 @@ import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
import org.eclipse.jgit.treewalk.filter.PathFilter;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.LfsFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.NotFoundException;
@@ -56,6 +59,7 @@ import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.GitSubModuleParser;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.InternalRepositoryException;
import sonia.scm.repository.Repository;
import sonia.scm.repository.SubRepository;
import sonia.scm.store.Blob;
@@ -69,10 +73,13 @@ import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import static java.util.Optional.empty;
import static java.util.Optional.of;
import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.NotFoundException.notFound;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.ASYNCHRONOUS;
//~--- JDK imports ------------------------------------------------------------
@@ -90,71 +97,56 @@ public class GitBrowseCommand extends AbstractGitCommand
/**
* the logger for GitBrowseCommand
*/
private static final Logger logger =
LoggerFactory.getLogger(GitBrowseCommand.class);
private static final Logger logger = LoggerFactory.getLogger(GitBrowseCommand.class);
/** sub repository cache */
private final Map<ObjectId, Map<String, SubRepository>> subrepositoryCache = Maps.newHashMap();
private final Object asyncMonitor = new Object();
private final LfsBlobStoreFactory lfsBlobStoreFactory;
//~--- constructors ---------------------------------------------------------
private final SyncAsyncExecutor executor;
/**
* Constructs ...
* @param context
* @param repository
* @param lfsBlobStoreFactory
*/
public GitBrowseCommand(GitContext context, Repository repository, LfsBlobStoreFactory lfsBlobStoreFactory)
{
private BrowserResult browserResult;
public GitBrowseCommand(GitContext context, Repository repository, LfsBlobStoreFactory lfsBlobStoreFactory, SyncAsyncExecutor executor) {
super(context, repository);
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.executor = executor;
}
//~--- get methods ----------------------------------------------------------
@Override
@SuppressWarnings("unchecked")
public BrowserResult getBrowserResult(BrowseCommandRequest request)
throws IOException {
logger.debug("try to create browse result for {}", request);
BrowserResult result;
org.eclipse.jgit.lib.Repository repo = open();
ObjectId revId;
ObjectId revId = computeRevIdToBrowse(request, repo);
if (Util.isEmpty(request.getRevision()))
{
revId = getDefaultBranch(repo);
if (revId != null) {
browserResult = new BrowserResult(revId.getName(), request.getRevision(), getEntry(repo, request, revId));
return browserResult;
} else {
logger.warn("could not find head of repository {}, empty?", repository.getNamespaceAndName());
return new BrowserResult(Constants.HEAD, request.getRevision(), createEmptyRoot());
}
else
{
revId = GitUtil.getRevisionId(repo, request.getRevision());
}
if (revId != null)
{
result = new BrowserResult(revId.getName(), request.getRevision(), getEntry(repo, request, revId));
}
else
{
if (Util.isNotEmpty(request.getRevision()))
{
private ObjectId computeRevIdToBrowse(BrowseCommandRequest request, org.eclipse.jgit.lib.Repository repo) throws IOException {
if (Util.isEmpty(request.getRevision())) {
return getDefaultBranch(repo);
} else {
ObjectId revId = GitUtil.getRevisionId(repo, request.getRevision());
if (revId == null) {
logger.error("could not find revision {}", request.getRevision());
throw notFound(entity("Revision", request.getRevision()).in(this.repository));
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find head of repository, empty?");
return revId;
}
}
result = new BrowserResult(Constants.HEAD, request.getRevision(), createEmtpyRoot());
}
return result;
}
//~--- methods --------------------------------------------------------------
private FileObject createEmtpyRoot() {
private FileObject createEmptyRoot() {
FileObject fileObject = new FileObject();
fileObject.setName("");
fileObject.setPath("");
@@ -162,18 +154,6 @@ public class GitBrowseCommand extends AbstractGitCommand
return fileObject;
}
/**
* Method description
*
* @param repo
* @param request
* @param revId
* @param treeWalk
*
* @return
*
* @throws IOException
*/
private FileObject createFileObject(org.eclipse.jgit.lib.Repository repo,
BrowseCommandRequest request, ObjectId revId, TreeWalk treeWalk)
throws IOException {
@@ -207,128 +187,63 @@ public class GitBrowseCommand extends AbstractGitCommand
// don't show message and date for directories to improve performance
if (!file.isDirectory() &&!request.isDisableLastCommit())
{
logger.trace("fetch last commit for {} at {}", path, revId.getName());
RevCommit commit = getLatestCommit(repo, revId, path);
Optional<LfsPointer> lfsPointer = commit == null? empty(): GitUtil.getLfsPointer(repo, path, commit, treeWalk);
file.setPartialResult(true);
RevCommit commit;
try (RevWalk walk = new RevWalk(repo)) {
commit = walk.parseCommit(revId);
}
Optional<LfsPointer> lfsPointer = getLfsPointer(repo, path, commit, treeWalk);
if (lfsPointer.isPresent()) {
BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
String oid = lfsPointer.get().getOid().getName();
Blob blob = lfsBlobStore.get(oid);
if (blob == null) {
logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName());
file.setLength(-1);
} else {
file.setLength(blob.getSize());
}
setFileLengthFromLfsBlob(lfsPointer.get(), file);
} else {
file.setLength(loader.getSize());
}
if (commit != null)
{
file.setLastModified(GitUtil.getCommitTime(commit));
file.setDescription(commit.getShortMessage());
}
else if (logger.isWarnEnabled())
{
logger.warn("could not find latest commit for {} on {}", path,
revId);
}
executor.execute(
new CompleteFileInformation(path, revId, repo, file, request),
new AbortFileInformation(request)
);
}
}
return file;
}
//~--- get methods ----------------------------------------------------------
/**
* Method description
*
*
*
* @param repo
* @param revId
* @param path
*
* @return
*/
private RevCommit getLatestCommit(org.eclipse.jgit.lib.Repository repo,
ObjectId revId, String path)
{
RevCommit result = null;
RevWalk walk = null;
try
{
walk = new RevWalk(repo);
walk.setTreeFilter(AndTreeFilter.create(PathFilter.create(path),
TreeFilter.ANY_DIFF));
RevCommit commit = walk.parseCommit(revId);
walk.markStart(commit);
result = Util.getFirst(walk);
}
catch (IOException ex)
{
logger.error("could not parse commit for file", ex);
}
finally
{
GitUtil.release(walk);
}
return result;
private void updateCache(BrowseCommandRequest request) {
request.updateCache(browserResult);
logger.info("updated browser result for repository {}", repository.getNamespaceAndName());
}
private FileObject getEntry(org.eclipse.jgit.lib.Repository repo, BrowseCommandRequest request, ObjectId revId) throws IOException {
RevWalk revWalk = null;
TreeWalk treeWalk = null;
FileObject result;
try {
try (RevWalk revWalk = new RevWalk(repo); TreeWalk treeWalk = new TreeWalk(repo)) {
logger.debug("load repository browser for revision {}", revId.name());
treeWalk = new TreeWalk(repo);
if (!isRootRequest(request)) {
treeWalk.setFilter(PathFilter.create(request.getPath()));
}
revWalk = new RevWalk(repo);
RevTree tree = revWalk.parseTree(revId);
if (tree != null)
{
if (tree != null) {
treeWalk.addTree(tree);
}
else
{
} else {
throw new IllegalStateException("could not find tree for " + revId.name());
}
if (isRootRequest(request)) {
result = createEmtpyRoot();
FileObject result = createEmptyRoot();
findChildren(result, repo, request, revId, treeWalk);
return result;
} else {
result = findFirstMatch(repo, request, revId, treeWalk);
FileObject result = findFirstMatch(repo, request, revId, treeWalk);
if ( result.isDirectory() ) {
treeWalk.enterSubtree();
findChildren(result, repo, request, revId, treeWalk);
}
}
}
finally
{
GitUtil.release(revWalk);
GitUtil.release(treeWalk);
}
return result;
}
}
}
private boolean isRootRequest(BrowseCommandRequest request) {
return Strings.isNullOrEmpty(request.getPath()) || "/".equals(request.getPath());
@@ -384,56 +299,144 @@ public class GitBrowseCommand extends AbstractGitCommand
throw notFound(entity("File", request.getPath()).in("Revision", revId.getName()).in(this.repository));
}
@SuppressWarnings("unchecked")
private Map<String,
SubRepository> getSubRepositories(org.eclipse.jgit.lib.Repository repo,
ObjectId revision)
private Map<String, SubRepository> getSubRepositories(org.eclipse.jgit.lib.Repository repo, ObjectId revision)
throws IOException {
if (logger.isDebugEnabled())
{
logger.debug("read submodules of {} at {}", repository.getName(),
revision);
}
Map<String, SubRepository> subRepositories;
try ( ByteArrayOutputStream baos = new ByteArrayOutputStream() )
{
logger.debug("read submodules of {} at {}", repository.getName(), revision);
try ( ByteArrayOutputStream baos = new ByteArrayOutputStream() ) {
new GitCatCommand(context, repository, lfsBlobStoreFactory).getContent(repo, revision,
PATH_MODULES, baos);
subRepositories = GitSubModuleParser.parse(baos.toString());
return GitSubModuleParser.parse(baos.toString());
} catch (NotFoundException ex) {
logger.trace("could not find .gitmodules: {}", ex.getMessage());
return Collections.emptyMap();
}
catch (NotFoundException ex)
{
logger.trace("could not find .gitmodules", ex);
subRepositories = Collections.EMPTY_MAP;
}
return subRepositories;
}
private SubRepository getSubRepository(org.eclipse.jgit.lib.Repository repo,
ObjectId revId, String path)
private SubRepository getSubRepository(org.eclipse.jgit.lib.Repository repo, ObjectId revId, String path)
throws IOException {
Map<String, SubRepository> subRepositories = subrepositoryCache.get(revId);
if (subRepositories == null)
{
if (subRepositories == null) {
subRepositories = getSubRepositories(repo, revId);
subrepositoryCache.put(revId, subRepositories);
}
SubRepository sub = null;
if (subRepositories != null)
{
sub = subRepositories.get(path);
if (subRepositories != null) {
return subRepositories.get(path);
}
return null;
}
return sub;
private Optional<LfsPointer> getLfsPointer(org.eclipse.jgit.lib.Repository repo, String path, RevCommit commit, TreeWalk treeWalk) {
try {
Attributes attributes = LfsFactory.getAttributesForPath(repo, path, commit);
return GitUtil.getLfsPointer(repo, treeWalk, attributes);
} catch (IOException e) {
throw new InternalRepositoryException(repository, "could not read lfs pointer", e);
}
}
//~--- fields ---------------------------------------------------------------
/** sub repository cache */
private final Map<ObjectId, Map<String, SubRepository>> subrepositoryCache = Maps.newHashMap();
private void setFileLengthFromLfsBlob(LfsPointer lfsPointer, FileObject file) {
BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository);
String oid = lfsPointer.getOid().getName();
Blob blob = lfsBlobStore.get(oid);
if (blob == null) {
logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName());
file.setLength(null);
} else {
file.setLength(blob.getSize());
}
}
private class CompleteFileInformation implements Consumer<SyncAsyncExecutor.ExecutionType> {
private final String path;
private final ObjectId revId;
private final org.eclipse.jgit.lib.Repository repo;
private final FileObject file;
private final BrowseCommandRequest request;
public CompleteFileInformation(String path, ObjectId revId, org.eclipse.jgit.lib.Repository repo, FileObject file, BrowseCommandRequest request) {
this.path = path;
this.revId = revId;
this.repo = repo;
this.file = file;
this.request = request;
}
@Override
public void accept(SyncAsyncExecutor.ExecutionType executionType) {
logger.trace("fetch last commit for {} at {}", path, revId.getName());
Stopwatch sw = Stopwatch.createStarted();
Optional<RevCommit> commit = getLatestCommit(repo, revId, path);
synchronized (asyncMonitor) {
file.setPartialResult(false);
if (commit.isPresent()) {
applyValuesFromCommit(executionType, commit.get());
} else {
logger.warn("could not find latest commit for {} on {}", path, revId);
}
}
logger.trace("finished loading of last commit {} of {} in {}", revId.getName(), path, sw.stop());
}
private Optional<RevCommit> getLatestCommit(org.eclipse.jgit.lib.Repository repo,
ObjectId revId, String path) {
try (RevWalk walk = new RevWalk(repo)) {
walk.setTreeFilter(AndTreeFilter.create(TreeFilter.ANY_DIFF, PathFilter.create(path)));
RevCommit commit = walk.parseCommit(revId);
walk.markStart(commit);
return of(Util.getFirst(walk));
} catch (IOException ex) {
logger.error("could not parse commit for file", ex);
return empty();
}
}
private void applyValuesFromCommit(SyncAsyncExecutor.ExecutionType executionType, RevCommit commit) {
file.setCommitDate(GitUtil.getCommitTime(commit));
file.setDescription(commit.getShortMessage());
if (executionType == ASYNCHRONOUS && browserResult != null) {
updateCache(request);
}
}
}
private class AbortFileInformation implements Runnable {
private final BrowseCommandRequest request;
public AbortFileInformation(BrowseCommandRequest request) {
this.request = request;
}
@Override
public void run() {
synchronized (asyncMonitor) {
if (markPartialAsAborted(browserResult.getFile())) {
updateCache(request);
}
}
}
private boolean markPartialAsAborted(FileObject file) {
boolean changed = false;
if (file.isPartialResult()) {
file.setPartialResult(false);
file.setComputationAborted(true);
changed = true;
}
for (FileObject child : file.getChildren()) {
changed |= markPartialAsAborted(child);
}
return changed;
}
}
}

View File

@@ -80,12 +80,13 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
//~--- constructors ---------------------------------------------------------
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
public GitRepositoryServiceProvider(GitRepositoryHandler handler, Repository repository, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus, SyncAsyncExecutorProvider executorProvider) {
this.handler = handler;
this.repository = repository;
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.hookContextFactory = hookContextFactory;
this.eventBus = eventBus;
this.executorProvider = executorProvider;
this.context = new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
}
@@ -150,7 +151,7 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
@Override
public BrowseCommand getBrowseCommand()
{
return new GitBrowseCommand(context, repository, lfsBlobStoreFactory);
return new GitBrowseCommand(context, repository, lfsBlobStoreFactory, executorProvider.createExecutorWithDefaultTimeout());
}
/**
@@ -301,4 +302,6 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
private final HookContextFactory hookContextFactory;
private final ScmEventBus eventBus;
private final SyncAsyncExecutorProvider executorProvider;
}

View File

@@ -55,14 +55,16 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
private final LfsBlobStoreFactory lfsBlobStoreFactory;
private final HookContextFactory hookContextFactory;
private final ScmEventBus eventBus;
private final SyncAsyncExecutorProvider executorProvider;
@Inject
public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus) {
public GitRepositoryServiceResolver(GitRepositoryHandler handler, GitRepositoryConfigStoreProvider storeProvider, LfsBlobStoreFactory lfsBlobStoreFactory, HookContextFactory hookContextFactory, ScmEventBus eventBus, SyncAsyncExecutorProvider executorProvider) {
this.handler = handler;
this.storeProvider = storeProvider;
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
this.hookContextFactory = hookContextFactory;
this.eventBus = eventBus;
this.executorProvider = executorProvider;
}
@Override
@@ -70,7 +72,7 @@ public class GitRepositoryServiceResolver implements RepositoryServiceResolver {
GitRepositoryServiceProvider provider = null;
if (GitRepositoryHandler.TYPE_NAME.equalsIgnoreCase(repository.getType())) {
provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory, hookContextFactory, eventBus);
provider = new GitRepositoryServiceProvider(handler, repository, storeProvider, lfsBlobStoreFactory, hookContextFactory, eventBus, executorProvider);
}
return provider;

View File

@@ -35,15 +35,21 @@ import org.junit.Test;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.GitRepositoryConfig;
import sonia.scm.repository.spi.SyncAsyncExecutors.AsyncExecutorStepper;
import java.io.IOException;
import java.util.Collection;
import java.util.LinkedList;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static sonia.scm.repository.spi.SyncAsyncExecutors.stepperAsynchronousExecutor;
import static sonia.scm.repository.spi.SyncAsyncExecutors.synchronousExecutor;
/**
* Unit tests for {@link GitBrowseCommand}.
@@ -102,15 +108,55 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase {
assertFalse(a.isDirectory());
assertEquals("a.txt", a.getName());
assertEquals("a.txt", a.getPath());
assertEquals("added new line for blame", a.getDescription());
assertTrue(a.getLength() > 0);
checkDate(a.getLastModified());
assertEquals("added new line for blame", a.getDescription().get());
assertTrue(a.getLength().getAsLong() > 0);
checkDate(a.getCommitDate().getAsLong());
assertTrue(c.isDirectory());
assertEquals("c", c.getName());
assertEquals("c", c.getPath());
}
@Test
public void testAsynchronousBrowse() throws IOException {
try (AsyncExecutorStepper executor = stepperAsynchronousExecutor()) {
GitBrowseCommand command = new GitBrowseCommand(createContext(), repository, null, executor);
List<BrowserResult> updatedResults = new LinkedList<>();
BrowseCommandRequest request = new BrowseCommandRequest(updatedResults::add);
FileObject root = command.getBrowserResult(request).getFile();
assertNotNull(root);
Collection<FileObject> foList = root.getChildren();
FileObject a = findFile(foList, "a.txt");
FileObject b = findFile(foList, "b.txt");
assertTrue(a.isPartialResult());
assertFalse("expected empty name before commit could have been read", a.getDescription().isPresent());
assertFalse("expected empty date before commit could have been read", a.getCommitDate().isPresent());
assertTrue(b.isPartialResult());
assertFalse("expected empty name before commit could have been read", b.getDescription().isPresent());
assertFalse("expected empty date before commit could have been read", b.getCommitDate().isPresent());
executor.next();
assertEquals(1, updatedResults.size());
assertFalse(a.isPartialResult());
assertNotNull("expected correct name after commit could have been read", a.getDescription());
assertTrue("expected correct date after commit could have been read", a.getCommitDate().isPresent());
assertTrue(b.isPartialResult());
assertFalse("expected empty name before commit could have been read", b.getDescription().isPresent());
assertFalse("expected empty date before commit could have been read", b.getCommitDate().isPresent());
executor.next();
assertEquals(2, updatedResults.size());
assertFalse(b.isPartialResult());
assertNotNull("expected correct name after commit could have been read", b.getDescription());
assertTrue("expected correct date after commit could have been read", b.getCommitDate().isPresent());
}
}
@Test
public void testBrowseSubDirectory() throws IOException {
BrowseCommandRequest request = new BrowseCommandRequest();
@@ -129,20 +175,20 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase {
assertFalse(d.isDirectory());
assertEquals("d.txt", d.getName());
assertEquals("c/d.txt", d.getPath());
assertEquals("added file d and e in folder c", d.getDescription());
assertTrue(d.getLength() > 0);
checkDate(d.getLastModified());
assertEquals("added file d and e in folder c", d.getDescription().get());
assertTrue(d.getLength().getAsLong() > 0);
checkDate(d.getCommitDate().getAsLong());
assertFalse(e.isDirectory());
assertEquals("e.txt", e.getName());
assertEquals("c/e.txt", e.getPath());
assertEquals("added file d and e in folder c", e.getDescription());
assertTrue(e.getLength() > 0);
checkDate(e.getLastModified());
assertEquals("added file d and e in folder c", e.getDescription().get());
assertTrue(e.getLength().getAsLong() > 0);
checkDate(e.getCommitDate().getAsLong());
}
@Test
public void testRecusive() throws IOException {
public void testRecursive() throws IOException {
BrowseCommandRequest request = new BrowseCommandRequest();
request.setRecursive(true);
@@ -171,6 +217,6 @@ public class GitBrowseCommandTest extends AbstractGitCommandTestBase {
}
private GitBrowseCommand createCommand() {
return new GitBrowseCommand(createContext(), repository, null);
return new GitBrowseCommand(createContext(), repository, null, synchronousExecutor());
}
}

View File

@@ -231,13 +231,13 @@ public class HgFileviewCommand extends AbstractCommand
file.setName(getNameFromPath(path));
file.setPath(path);
file.setDirectory(false);
file.setLength(stream.decimalIntUpTo(' '));
file.setLength((long) stream.decimalIntUpTo(' '));
DateTime timestamp = stream.dateTimeUpTo(' ');
String description = stream.textUpTo('\0');
if (!disableLastCommit) {
file.setLastModified(timestamp.getDate().getTime());
file.setCommitDate(timestamp.getDate().getTime());
file.setDescription(description);
}

View File

@@ -61,7 +61,7 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase {
FileObject file = new HgBrowseCommand(cmdContext, repository).getBrowserResult(request).getFile();
assertEquals("a.txt", file.getName());
assertFalse(file.isDirectory());
assertTrue(file.getChildren().isEmpty());
assertTrue(file.getChildren() == null || file.getChildren().isEmpty());
}
@Test
@@ -73,9 +73,9 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase {
assertFalse(a.isDirectory());
assertEquals("a.txt", a.getName());
assertEquals("a.txt", a.getPath());
assertEquals("added new line for blame", a.getDescription());
assertTrue(a.getLength() > 0);
checkDate(a.getLastModified());
assertEquals("added new line for blame", a.getDescription().get());
assertTrue(a.getLength().getAsLong() > 0);
checkDate(a.getCommitDate().getAsLong());
assertTrue(c.isDirectory());
assertEquals("c", c.getName());
assertEquals("c", c.getPath());
@@ -132,16 +132,16 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase {
assertFalse(d.isDirectory());
assertEquals("d.txt", d.getName());
assertEquals("c/d.txt", d.getPath());
assertEquals("added file d and e in folder c", d.getDescription());
assertTrue(d.getLength() > 0);
checkDate(d.getLastModified());
assertEquals("added file d and e in folder c", d.getDescription().get());
assertTrue(d.getLength().getAsLong() > 0);
checkDate(d.getCommitDate().getAsLong());
assertNotNull(e);
assertFalse(e.isDirectory());
assertEquals("e.txt", e.getName());
assertEquals("c/e.txt", e.getPath());
assertEquals("added file d and e in folder c", e.getDescription());
assertTrue(e.getLength() > 0);
checkDate(e.getLastModified());
assertEquals("added file d and e in folder c", e.getDescription().get());
assertTrue(e.getLength().getAsLong() > 0);
checkDate(e.getCommitDate().getAsLong());
}
@Test
@@ -154,8 +154,8 @@ public class HgBrowseCommandTest extends AbstractHgCommandTestBase {
FileObject a = getFileObject(foList, "a.txt");
assertNull(a.getDescription());
assertNull(a.getLastModified());
assertFalse(a.getDescription().isPresent());
assertFalse(a.getCommitDate().isPresent());
}
@Test

View File

@@ -173,7 +173,7 @@ public class SvnBrowseCommand extends AbstractSvnCommand
{
if (entry.getDate() != null)
{
fileObject.setLastModified(entry.getDate().getTime());
fileObject.setCommitDate(entry.getDate().getTime());
}
fileObject.setDescription(entry.getCommitMessage());

View File

@@ -60,7 +60,7 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
FileObject file = createCommand().getBrowserResult(request).getFile();
assertEquals("a.txt", file.getName());
assertFalse(file.isDirectory());
assertTrue(file.getChildren().isEmpty());
assertTrue(file.getChildren() == null || file.getChildren().isEmpty());
}
@Test
@@ -73,9 +73,9 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
assertFalse(a.isDirectory());
assertEquals("a.txt", a.getName());
assertEquals("a.txt", a.getPath());
assertEquals("added line for blame test", a.getDescription());
assertTrue(a.getLength() > 0);
checkDate(a.getLastModified());
assertEquals("added line for blame test", a.getDescription().get());
assertTrue(a.getLength().getAsLong() > 0);
checkDate(a.getCommitDate().getAsLong());
assertTrue(c.isDirectory());
assertEquals("c", c.getName());
assertEquals("c", c.getPath());
@@ -122,16 +122,16 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
assertFalse(d.isDirectory());
assertEquals("d.txt", d.getName());
assertEquals("c/d.txt", d.getPath());
assertEquals("added d and e in folder c", d.getDescription());
assertTrue(d.getLength() > 0);
checkDate(d.getLastModified());
assertEquals("added d and e in folder c", d.getDescription().get());
assertTrue(d.getLength().getAsLong() > 0);
checkDate(d.getCommitDate().getAsLong());
assertNotNull(e);
assertFalse(e.isDirectory());
assertEquals("e.txt", e.getName());
assertEquals("c/e.txt", e.getPath());
assertEquals("added d and e in folder c", e.getDescription());
assertTrue(e.getLength() > 0);
checkDate(e.getLastModified());
assertEquals("added d and e in folder c", e.getDescription().get());
assertTrue(e.getLength().getAsLong() > 0);
checkDate(e.getCommitDate().getAsLong());
}
@Test
@@ -144,8 +144,8 @@ public class SvnBrowseCommandTest extends AbstractSvnCommandTestBase
FileObject a = getFileObject(foList, "a.txt");
assertNull(a.getDescription());
assertNull(a.getLastModified());
assertFalse(a.getDescription().isPresent());
assertFalse(a.getCommitDate().isPresent());
}
@Test

View File

@@ -0,0 +1,107 @@
package sonia.scm.repository.spi;
import java.io.Closeable;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;
import java.util.function.Consumer;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.ASYNCHRONOUS;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.SYNCHRONOUS;
public final class SyncAsyncExecutors {
public static SyncAsyncExecutor synchronousExecutor() {
return new SyncAsyncExecutor() {
@Override
public ExecutionType execute(Consumer<ExecutionType> runnable, Runnable abortionFallback) {
runnable.accept(SYNCHRONOUS);
return SYNCHRONOUS;
}
@Override
public boolean hasExecutedAllSynchronously() {
return true;
}
};
}
public static SyncAsyncExecutor asynchronousExecutor() {
Executor executor = Executors.newSingleThreadExecutor();
return new SyncAsyncExecutor() {
@Override
public ExecutionType execute(Consumer<ExecutionType> runnable, Runnable abortionFallback) {
executor.execute(() -> runnable.accept(ASYNCHRONOUS));
return ASYNCHRONOUS;
}
@Override
public boolean hasExecutedAllSynchronously() {
return true;
}
};
}
public static AsyncExecutorStepper stepperAsynchronousExecutor() {
return new AsyncExecutorStepper() {
Executor executor = Executors.newSingleThreadExecutor();
Semaphore enterSemaphore = new Semaphore(0);
Semaphore exitSemaphore = new Semaphore(0);
boolean timedOut = false;
@Override
public void close() {
enterSemaphore.release(Integer.MAX_VALUE/2);
exitSemaphore.release(Integer.MAX_VALUE/2);
}
@Override
public ExecutionType execute(Consumer<ExecutionType> runnable, Runnable abortionFallback) {
executor.execute(() -> {
try {
enterSemaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
if (timedOut) {
abortionFallback.run();
} else {
runnable.accept(ASYNCHRONOUS);
exitSemaphore.release();
}
});
return ASYNCHRONOUS;
}
@Override
public void next() {
enterSemaphore.release();
try {
exitSemaphore.acquire();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void timeout() {
timedOut = true;
close();
}
@Override
public boolean hasExecutedAllSynchronously() {
return true;
}
};
}
public interface AsyncExecutorStepper extends SyncAsyncExecutor, Closeable {
void next();
void timeout();
}
}

View File

@@ -8,8 +8,8 @@
"private": false,
"prettier": "@scm-manager/prettier-config",
"dependencies": {
"@typescript-eslint/eslint-plugin": "^2.4.0",
"@typescript-eslint/parser": "^2.4.0",
"@typescript-eslint/eslint-plugin": "^2.12.0",
"@typescript-eslint/parser": "^2.12.0",
"babel-eslint": "^10.0.3",
"eslint": "^6.5.1",
"eslint-config-prettier": "^6.4.0",

View File

@@ -37,8 +37,6 @@
"enzyme-context": "^1.1.2",
"enzyme-context-react-router-4": "^2.0.0",
"fetch-mock": "^7.5.1",
"flow-bin": "^0.109.0",
"flow-typed": "^2.5.1",
"raf": "^3.4.0",
"react-test-renderer": "^16.10.2",
"storybook-addon-i18next": "^1.2.1",

View File

@@ -2559,6 +2559,8 @@ exports[`Storyshots Table|Table TextColumn 1`] = `
</table>
`;
exports[`Storyshots Toast Click to close 1`] = `null`;
exports[`Storyshots Toast Danger 1`] = `null`;
exports[`Storyshots Toast Info 1`] = `null`;

View File

@@ -241,7 +241,7 @@ class ApiClient {
es.addEventListener(type, listeners[type]);
}
return es.close;
return () => es.close();
}
}

View File

@@ -1,12 +1,13 @@
import React, { FC, useContext } from "react";
import React, { FC, useContext, MouseEvent } from "react";
import { ToastThemeContext, Themeable } from "./themes";
import styled from "styled-components";
type Props = {
icon?: string;
onClick?: () => void;
};
const ThemedButton = styled.div.attrs(props => ({
const ThemedButton = styled.button.attrs(props => ({
className: "button"
}))<Themeable>`
color: ${props => props.theme.primary};
@@ -25,10 +26,18 @@ const ToastButtonIcon = styled.i`
margin-right: 0.25rem;
`;
const ToastButton: FC<Props> = ({ icon, children }) => {
const ToastButton: FC<Props> = ({ icon, onClick, children }) => {
const theme = useContext(ToastThemeContext);
const handleClick = (e: MouseEvent<HTMLButtonElement>) => {
if (onClick) {
e.preventDefault();
onClick();
}
};
return (
<ThemedButton theme={theme}>
<ThemedButton theme={theme} onClick={handleClick}>
{icon && <ToastButtonIcon className={`fas fa-fw fa-${icon}`} />} {children}
</ThemedButton>
);

View File

@@ -26,7 +26,31 @@ const Animator = () => {
);
};
const Closeable = () => {
const [show, setShow] = useState(true);
const hide = () => {
setShow(false);
};
if (!show) {
return null;
}
return (
<Toast type="success" title="Awesome feature">
<p>Close the message with a click</p>
<ToastButtons>
<ToastButton icon="times" onClick={hide}>
Click to close
</ToastButton>
</ToastButtons>
</Toast>
);
};
toastStories.add("Open/Close", () => <Animator />);
toastStories.add("Click to close", () => <Closeable />);
types.forEach(type => {
toastStories.add(type.charAt(0).toUpperCase() + type.slice(1), () => (

View File

@@ -13,9 +13,11 @@ export type File = {
directory: boolean;
description?: string;
revision: string;
length: number;
lastModified?: string;
length?: number;
commitDate?: string;
subRepository?: SubRepository; // TODO
partialResult: boolean;
computationAborted: boolean;
_links: Links;
_embedded: {
children: File[] | null | undefined;

View File

@@ -42,8 +42,6 @@
"@types/styled-components": "^4.1.19",
"@types/systemjs": "^0.20.6",
"fetch-mock": "^7.5.1",
"flow-bin": "^0.109.0",
"flow-typed": "^2.6.1",
"react-test-renderer": "^16.10.2",
"redux-mock-store": "^1.5.3"
},

View File

@@ -101,7 +101,9 @@
"length": "Größe",
"lastModified": "Zuletzt bearbeitet",
"description": "Beschreibung",
"branch": "Branch"
"branch": "Branch",
"notYetComputed": "Noch nicht berechnet; Der Wert wird in Kürze aktualisiert",
"computationAborted": "Die Berechnung dauert zu lange und wurde abgebrochen"
},
"content": {
"historyButton": "History",

View File

@@ -101,7 +101,9 @@
"length": "Length",
"lastModified": "Last modified",
"description": "Description",
"branch": "Branch"
"branch": "Branch",
"notYetComputed": "Not yet computed, will be updated in a short while",
"computationAborted": "The computation took too long and was aborted"
},
"content": {
"historyButton": "History",

View File

@@ -7,7 +7,7 @@ import styled from "styled-components";
import { binder } from "@scm-manager/ui-extensions";
import { Repository, File } from "@scm-manager/ui-types";
import { ErrorNotification, Loading, Notification } from "@scm-manager/ui-components";
import { getFetchSourcesFailure, isFetchSourcesPending, getSources } from "../modules/sources";
import { getFetchSourcesFailure, isFetchSourcesPending, getSources, fetchSources } from "../modules/sources";
import FileTreeLeaf from "./FileTreeLeaf";
type Props = WithTranslation & {
@@ -19,10 +19,16 @@ type Props = WithTranslation & {
path: string;
baseUrl: string;
updateSources: () => void;
// context props
match: any;
};
type State = {
stoppableUpdateHandler?: number;
};
const FixedWidthTh = styled.th`
width: 16px;
`;
@@ -39,7 +45,28 @@ export function findParent(path: string) {
return "";
}
class FileTree extends React.Component<Props> {
class FileTree extends React.Component<Props, State> {
constructor(props: Props) {
super(props);
this.state = {};
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>): void {
if (prevState.stoppableUpdateHandler === this.state.stoppableUpdateHandler) {
const { tree, updateSources } = this.props;
if (tree?._embedded?.children && tree._embedded.children.find(c => c.partialResult)) {
const stoppableUpdateHandler = setTimeout(updateSources, 3000);
this.setState({ stoppableUpdateHandler: stoppableUpdateHandler });
}
}
}
componentWillUnmount(): void {
if (this.state.stoppableUpdateHandler) {
clearTimeout(this.state.stoppableUpdateHandler);
}
}
render() {
const { error, loading, tree } = this.props;
@@ -106,7 +133,7 @@ class FileTree extends React.Component<Props> {
<FixedWidthTh />
<th>{t("sources.file-tree.name")}</th>
<th className="is-hidden-mobile">{t("sources.file-tree.length")}</th>
<th className="is-hidden-mobile">{t("sources.file-tree.lastModified")}</th>
<th className="is-hidden-mobile">{t("sources.file-tree.commitDate")}</th>
<th className="is-hidden-touch">{t("sources.file-tree.description")}</th>
{binder.hasExtension("repos.sources.tree.row.right") && <th className="is-hidden-mobile" />}
</tr>
@@ -123,6 +150,14 @@ class FileTree extends React.Component<Props> {
}
}
const mapDispatchToProps = (dispatch: any, ownProps: Props) => {
const { repository, revision, path } = ownProps;
const updateSources = () => dispatch(fetchSources(repository, revision, path, false));
return { updateSources };
};
const mapStateToProps = (state: any, ownProps: Props) => {
const { repository, revision, path } = ownProps;
@@ -141,5 +176,8 @@ const mapStateToProps = (state: any, ownProps: Props) => {
export default compose(
withRouter,
connect(mapStateToProps)
connect(
mapStateToProps,
mapDispatchToProps
)
)(withTranslation("repos")(FileTree));

View File

@@ -4,10 +4,12 @@ import classNames from "classnames";
import styled from "styled-components";
import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { File } from "@scm-manager/ui-types";
import { DateFromNow, FileSize } from "@scm-manager/ui-components";
import { DateFromNow, FileSize, Tooltip } from "@scm-manager/ui-components";
import FileIcon from "./FileIcon";
import { Icon } from "@scm-manager/ui-components/src";
import { WithTranslation, withTranslation } from "react-i18next";
type Props = {
type Props = WithTranslation & {
file: File;
baseUrl: string;
};
@@ -35,7 +37,7 @@ export function createLink(base: string, file: File) {
return link;
}
export default class FileTreeLeaf extends React.Component<Props> {
class FileTreeLeaf extends React.Component<Props> {
createLink = (file: File) => {
return createLink(this.props.baseUrl, file);
};
@@ -62,20 +64,42 @@ export default class FileTreeLeaf extends React.Component<Props> {
return <Link to={this.createLink(file)}>{file.name}</Link>;
};
contentIfPresent = (file: File, attribute: string, content: (file: File) => any) => {
const { t } = this.props;
if (file.hasOwnProperty(attribute)) {
return content(file);
} else if (file.computationAborted) {
return (
<Tooltip location="top" message={t("sources.file-tree.computationAborted")}>
<Icon name={"question-circle"} />
</Tooltip>
);
} else if (file.partialResult) {
return (
<Tooltip location="top" message={t("sources.file-tree.notYetComputed")}>
<Icon name={"hourglass"} />
</Tooltip>
);
} else {
return content(file);
}
};
render() {
const { file } = this.props;
const fileSize = file.directory ? "" : <FileSize bytes={file.length} />;
const renderFileSize = (file: File) => <FileSize bytes={file.length} />;
const renderCommitDate = (file: File) => <DateFromNow date={file.commitDate} />;
return (
<tr>
<td>{this.createFileIcon(file)}</td>
<MinWidthTd className="is-word-break">{this.createFileName(file)}</MinWidthTd>
<NoWrapTd className="is-hidden-mobile">{fileSize}</NoWrapTd>
<td className="is-hidden-mobile">
<DateFromNow date={file.lastModified} />
</td>
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>{file.description}</MinWidthTd>
<NoWrapTd className="is-hidden-mobile">{file.directory ? "" : this.contentIfPresent(file, "length", renderFileSize)}</NoWrapTd>
<td className="is-hidden-mobile">{this.contentIfPresent(file, "commitDate", renderCommitDate)}</td>
<MinWidthTd className={classNames("is-word-break", "is-hidden-touch")}>
{this.contentIfPresent(file, "description", file => file.description)}
</MinWidthTd>
{binder.hasExtension("repos.sources.tree.row.right") && (
<td className="is-hidden-mobile">
{!file.directory && (
@@ -93,3 +117,5 @@ export default class FileTreeLeaf extends React.Component<Props> {
);
}
}
export default withTranslation("repos")(FileTreeLeaf);

View File

@@ -115,7 +115,7 @@ class Content extends React.Component<Props, State> {
showMoreInformation() {
const collapsed = this.state.collapsed;
const { file, revision, t, repository } = this.props;
const date = <DateFromNow date={file.lastModified} />;
const date = <DateFromNow date={file.commitDate} />;
const description = file.description ? (
<p>
{file.description.split("\n").map((item, key) => {

View File

@@ -49,10 +49,8 @@ const collection = {
name: "src",
path: "src",
directory: true,
description: "",
length: 176,
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
lastModified: "",
subRepository: undefined,
_links: {
self: {
@@ -71,7 +69,7 @@ const collection = {
description: "bump version",
length: 780,
revision: "76aae4bb4ceacf0e88938eb5b6832738b7d537b4",
lastModified: "2017-07-31T11:17:19Z",
commitDate: "2017-07-31T11:17:19Z",
subRepository: undefined,
_links: {
self: {
@@ -127,7 +125,7 @@ describe("sources fetch", () => {
{
type: FETCH_SOURCES_SUCCESS,
itemId: "scm/core/_/",
payload: collection
payload: { updatePending: false, sources: collection }
}
];
@@ -148,7 +146,7 @@ describe("sources fetch", () => {
{
type: FETCH_SOURCES_SUCCESS,
itemId: "scm/core/abc/src",
payload: collection
payload: { updatePending: false, sources: collection }
}
];
@@ -182,14 +180,14 @@ describe("reducer tests", () => {
it("should store the collection, without revision and path", () => {
const expectedState = {
"scm/core/_/": collection
"scm/core/_/": { updatePending: false, sources: collection }
};
expect(reducer({}, fetchSourcesSuccess(repository, "", "", collection))).toEqual(expectedState);
});
it("should store the collection, with revision and path", () => {
const expectedState = {
"scm/core/abc/src/main": collection
"scm/core/abc/src/main": { updatePending: false, sources: collection }
};
expect(reducer({}, fetchSourcesSuccess(repository, "abc", "src/main", collection))).toEqual(expectedState);
});
@@ -200,7 +198,7 @@ describe("selector tests", () => {
const state = {
sources: {
"scm/core/abc/src/main/package.json": {
noDirectory
sources: {noDirectory}
}
}
};
@@ -223,7 +221,9 @@ describe("selector tests", () => {
it("should return the source collection without revision and path", () => {
const state = {
sources: {
"scm/core/_/": collection
"scm/core/_/": {
sources: collection
}
}
};
expect(getSources(state, repository, "", "")).toBe(collection);
@@ -232,7 +232,9 @@ describe("selector tests", () => {
it("should return the source collection with revision and path", () => {
const state = {
sources: {
"scm/core/abc/src/main": collection
"scm/core/abc/src/main": {
sources: collection
}
}
};
expect(getSources(state, repository, "abc", "src/main")).toBe(collection);

View File

@@ -9,13 +9,25 @@ export const FETCH_SOURCES_PENDING = `${FETCH_SOURCES}_${types.PENDING_SUFFIX}`;
export const FETCH_SOURCES_SUCCESS = `${FETCH_SOURCES}_${types.SUCCESS_SUFFIX}`;
export const FETCH_SOURCES_FAILURE = `${FETCH_SOURCES}_${types.FAILURE_SUFFIX}`;
export function fetchSources(repository: Repository, revision: string, path: string) {
return function(dispatch: any) {
export function fetchSources(repository: Repository, revision: string, path: string, initialLoad = true) {
return function(dispatch: any, getState: () => any) {
const state = getState();
if (
isFetchSourcesPending(state, repository, revision, path) ||
isUpdateSourcePending(state, repository, revision, path)
) {
return;
}
if (initialLoad) {
dispatch(fetchSourcesPending(repository, revision, path));
} else {
dispatch(updateSourcesPending(repository, revision, path, getSources(state, repository, revision, path)));
}
return apiClient
.get(createUrl(repository, revision, path))
.then(response => response.json())
.then(sources => {
.then((sources: File) => {
dispatch(fetchSourcesSuccess(repository, revision, path, sources));
})
.catch(err => {
@@ -42,10 +54,23 @@ export function fetchSourcesPending(repository: Repository, revision: string, pa
};
}
export function updateSourcesPending(
repository: Repository,
revision: string,
path: string,
currentSources: any
): Action {
return {
type: "UPDATE_PENDING",
payload: { updatePending: true, sources: currentSources },
itemId: createItemId(repository, revision, path)
};
}
export function fetchSourcesSuccess(repository: Repository, revision: string, path: string, sources: File) {
return {
type: FETCH_SOURCES_SUCCESS,
payload: sources,
payload: { updatePending: false, sources },
itemId: createItemId(repository, revision, path)
};
}
@@ -72,7 +97,7 @@ export default function reducer(
type: "UNKNOWN"
}
): any {
if (action.itemId && action.type === FETCH_SOURCES_SUCCESS) {
if (action.itemId && (action.type === FETCH_SOURCES_SUCCESS || action.type === "UPDATE_PENDING")) {
return {
...state,
[action.itemId]: action.payload
@@ -99,13 +124,17 @@ export function getSources(
path: string
): File | null | undefined {
if (state.sources) {
return state.sources[createItemId(repository, revision, path)];
return state.sources[createItemId(repository, revision, path)]?.sources;
}
return null;
}
export function isFetchSourcesPending(state: any, repository: Repository, revision: string, path: string): boolean {
return isPending(state, FETCH_SOURCES, createItemId(repository, revision, path));
return state && isPending(state, FETCH_SOURCES, createItemId(repository, revision, path));
}
function isUpdateSourcePending(state: any, repository: Repository, revision: string, path: string): boolean {
return state?.sources && state.sources[createItemId(repository, revision, path)]?.updatePending;
}
export function getFetchSourcesFailure(

View File

@@ -1,6 +1,5 @@
package sonia.scm.api.v2.resources;
import com.google.common.annotations.VisibleForTesting;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links;
import org.mapstruct.Context;
@@ -16,18 +15,13 @@ import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.time.Instant;
import java.util.Optional;
import java.util.OptionalLong;
@Mapper
public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectDtoMapper {
@Inject
private FileObjectToFileObjectDtoMapper childrenMapper;
@VisibleForTesting
void setChildrenMapper(FileObjectToFileObjectDtoMapper childrenMapper) {
this.childrenMapper = childrenMapper;
}
FileObjectDto map(BrowserResult browserResult, @Context NamespaceAndName namespaceAndName) {
FileObjectDto fileObjectDto = fileObjectToDto(browserResult.getFile(), namespaceAndName, browserResult);
fileObjectDto.setRevision(browserResult.getRevision());
@@ -36,12 +30,8 @@ public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectD
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
@Mapping(target = "children", qualifiedBy = Children.class)
protected abstract FileObjectDto fileObjectToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult);
@Children
protected FileObjectDto childrenToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult) {
return childrenMapper.map(fileObject, namespaceAndName, browserResult);
}
protected abstract FileObjectDto fileObjectToDto(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult);
@Override
void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) {
@@ -52,6 +42,14 @@ public abstract class BrowserResultToFileObjectDtoMapper extends BaseFileObjectD
applyEnrichers(appender, fileObject, namespaceAndName, browserResult, browserResult.getRevision());
}
Optional<Instant> mapOptionalInstant(OptionalLong optionalLong) {
if (optionalLong.isPresent()) {
return Optional.of(Instant.ofEpochMilli(optionalLong.getAsLong()));
} else {
return Optional.empty();
}
}
@Qualifier
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.CLASS)

View File

@@ -10,6 +10,8 @@ import lombok.Setter;
import java.time.Instant;
import java.util.List;
import java.util.Optional;
import java.util.OptionalLong;
@Getter
@Setter
@@ -19,14 +21,16 @@ public class FileObjectDto extends HalRepresentation {
private String path;
private boolean directory;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String description;
private long length;
private Optional<String> description;
private OptionalLong length;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private Instant lastModified;
private Optional<Instant> commitDate;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private SubRepositoryDto subRepository;
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private String revision;
private boolean partialResult;
private boolean computationAborted;
public FileObjectDto(Links links, Embedded embedded) {
super(links, embedded);

View File

@@ -1,22 +0,0 @@
package sonia.scm.api.v2.resources;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Links;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
@Mapper
public abstract class FileObjectToFileObjectDtoMapper extends BaseFileObjectDtoMapper {
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult);
@Override
void applyEnrichers(Links.Builder links, Embedded.Builder embeddedBuilder, NamespaceAndName namespaceAndName, BrowserResult browserResult, FileObject fileObject) {
applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, browserResult, browserResult.getRevision());
}
}

View File

@@ -37,7 +37,6 @@ public class MapperModule extends AbstractModule {
bind(TagToTagDtoMapper.class).to(Mappers.getMapper(TagToTagDtoMapper.class).getClass());
bind(FileObjectToFileObjectDtoMapper.class).to(Mappers.getMapper(FileObjectToFileObjectDtoMapper.class).getClass());
bind(BrowserResultToFileObjectDtoMapper.class).to(Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class).getClass());
bind(ModificationsToDtoMapper.class).to(Mappers.getMapper(ModificationsToDtoMapper.class).getClass());

View File

@@ -12,6 +12,7 @@ import sonia.scm.debug.DebugModule;
import sonia.scm.filter.WebElementModule;
import sonia.scm.plugin.ExtensionProcessor;
import sonia.scm.plugin.PluginLoader;
import sonia.scm.repository.ExecutorModule;
import javax.servlet.ServletContext;
import java.util.ArrayList;
@@ -51,6 +52,7 @@ public class ApplicationModuleProvider implements ModuleProvider {
moduleList.add(new DebugModule());
}
moduleList.add(new MapperModule());
moduleList.add(new ExecutorModule());
return moduleList;
}

View File

@@ -0,0 +1,49 @@
package sonia.scm.repository;
import sonia.scm.repository.spi.SyncAsyncExecutor;
import java.time.Instant;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.ASYNCHRONOUS;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.SYNCHRONOUS;
public class DefaultSyncAsyncExecutor implements SyncAsyncExecutor {
private final Executor executor;
private final Instant switchToAsyncTime;
private final long maxAsyncAbortMilliseconds;
private AtomicLong accumulatedAsyncRuntime = new AtomicLong(0L);
private boolean executedAllSynchronously = true;
DefaultSyncAsyncExecutor(Executor executor, Instant switchToAsyncTime, int maxAsyncAbortSeconds) {
this.executor = executor;
this.switchToAsyncTime = switchToAsyncTime;
this.maxAsyncAbortMilliseconds = maxAsyncAbortSeconds * 1000L;
}
public ExecutionType execute(Consumer<ExecutionType> task, Runnable abortionFallback) {
if (switchToAsyncTime.isBefore(Instant.now())) {
executor.execute(() -> {
if (accumulatedAsyncRuntime.get() < maxAsyncAbortMilliseconds) {
long chunkStartTime = System.currentTimeMillis();
task.accept(ASYNCHRONOUS);
accumulatedAsyncRuntime.addAndGet(System.currentTimeMillis() - chunkStartTime);
} else {
abortionFallback.run();
}
});
executedAllSynchronously = false;
return ASYNCHRONOUS;
} else {
task.accept(SYNCHRONOUS);
return SYNCHRONOUS;
}
}
public boolean hasExecutedAllSynchronously() {
return executedAllSynchronously;
}
}

View File

@@ -0,0 +1,55 @@
package sonia.scm.repository;
import sonia.scm.repository.spi.SyncAsyncExecutor;
import sonia.scm.repository.spi.SyncAsyncExecutorProvider;
import javax.inject.Inject;
import javax.inject.Singleton;
import java.io.Closeable;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@Singleton
public class DefaultSyncAsyncExecutorProvider implements SyncAsyncExecutorProvider, Closeable {
public static final int DEFAULT_MAX_ASYNC_ABORT_SECONDS = 60;
public static final String MAX_ASYNC_ABORT_SECONDS_PROPERTY = "scm.maxAsyncAbortSeconds";
public static final int DEFAULT_NUMBER_OF_THREADS = 4;
public static final String NUMBER_OF_THREADS_PROPERTY = "scm.asyncThreads";
private final ExecutorService executor;
private final int defaultMaxAsyncAbortSeconds;
@Inject
public DefaultSyncAsyncExecutorProvider() {
this(Executors.newFixedThreadPool(getProperty(NUMBER_OF_THREADS_PROPERTY, DEFAULT_NUMBER_OF_THREADS)));
}
public DefaultSyncAsyncExecutorProvider(ExecutorService executor) {
this.executor = executor;
this.defaultMaxAsyncAbortSeconds = getProperty(MAX_ASYNC_ABORT_SECONDS_PROPERTY, DEFAULT_MAX_ASYNC_ABORT_SECONDS);
}
public SyncAsyncExecutor createExecutorWithSecondsToTimeout(int switchToAsyncInSeconds) {
return createExecutorWithSecondsToTimeout(switchToAsyncInSeconds, defaultMaxAsyncAbortSeconds);
}
public SyncAsyncExecutor createExecutorWithSecondsToTimeout(int switchToAsyncInSeconds, int maxAsyncAbortSeconds) {
return new DefaultSyncAsyncExecutor(
executor,
Instant.now().plus(switchToAsyncInSeconds, ChronoUnit.SECONDS),
maxAsyncAbortSeconds);
}
@Override
public void close() {
executor.shutdownNow();
}
private static int getProperty(String key, int defaultValue) {
return Integer.parseInt(System.getProperty(key, Integer.toString(defaultValue)));
}
}

View File

@@ -0,0 +1,12 @@
package sonia.scm.repository;
import com.google.inject.AbstractModule;
import sonia.scm.lifecycle.modules.CloseableModule;
import sonia.scm.repository.spi.SyncAsyncExecutorProvider;
public class ExecutorModule extends AbstractModule {
@Override
protected void configure() {
bind(SyncAsyncExecutorProvider.class).to(DefaultSyncAsyncExecutorProvider.class);
}
}

View File

@@ -8,7 +8,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
@@ -24,9 +23,6 @@ public class BrowserResultToFileObjectDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private FileObjectToFileObjectDtoMapperImpl fileObjectToFileObjectDtoMapper;
private BrowserResultToFileObjectDtoMapper mapper;
private final Subject subject = mock(Subject.class);
@@ -34,28 +30,28 @@ public class BrowserResultToFileObjectDtoMapperTest {
private FileObject fileObject1 = new FileObject();
private FileObject fileObject2 = new FileObject();
private FileObject partialFileObject = new FileObject();
@Before
public void init() {
initMocks(this);
mapper = Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class);
mapper.setChildrenMapper(fileObjectToFileObjectDtoMapper);
mapper.setResourceLinks(resourceLinks);
subjectThreadState.bind();
ThreadContext.bind(subject);
fileObject1.setName("FO 1");
fileObject1.setLength(100);
fileObject1.setLastModified(0L);
fileObject1.setLength(100L);
fileObject1.setCommitDate(0L);
fileObject1.setPath("/path/object/1");
fileObject1.setDescription("description of file object 1");
fileObject1.setDirectory(false);
fileObject2.setName("FO 2");
fileObject2.setLength(100);
fileObject2.setLastModified(101L);
fileObject2.setLength(100L);
fileObject2.setCommitDate(101L);
fileObject2.setPath("/path/object/2");
fileObject2.setDescription("description of file object 2");
fileObject2.setDirectory(true);

View File

@@ -1,121 +0,0 @@
package sonia.scm.api.v2.resources;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.subject.support.SubjectThreadState;
import org.apache.shiro.util.ThreadContext;
import org.apache.shiro.util.ThreadState;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository;
import java.net.URI;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.mock;
@RunWith(MockitoJUnitRunner.Silent.class)
public class FileObjectToFileObjectDtoMapperTest {
private final URI baseUri = URI.create("http://example.com/base/");
@SuppressWarnings("unused") // Is injected
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
@InjectMocks
private FileObjectToFileObjectDtoMapperImpl mapper;
private final Subject subject = mock(Subject.class);
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
private URI expectedBaseUri;
@Before
public void init() {
expectedBaseUri = baseUri.resolve(RepositoryRootResource.REPOSITORIES_PATH_V2 + "/");
subjectThreadState.bind();
ThreadContext.bind(subject);
}
@After
public void unbind() {
ThreadContext.unbindSubject();
}
@Test
public void shouldMapAttributesCorrectly() {
FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertEqualAttributes(fileObject, dto);
}
@Test
public void shouldHaveCorrectSelfLinkForDirectory() {
FileObject fileObject = createDirectoryObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/sources/revision/foo/bar").toString());
}
@Test
public void shouldHaveCorrectContentLink() {
FileObject fileObject = createFileObject();
fileObject.setDirectory(false);
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString());
}
@Test
public void shouldAppendLinks() {
HalEnricherRegistry registry = new HalEnricherRegistry();
registry.register(FileObject.class, (ctx, appender) -> {
NamespaceAndName repository = ctx.oneRequireByType(NamespaceAndName.class);
FileObject fo = ctx.oneRequireByType(FileObject.class);
String rev = ctx.oneRequireByType(String.class);
appender.appendLink("hog", "http://" + repository.logString() + "/" + fo.getName() + "/" + rev);
});
mapper.setRegistry(registry);
FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("hitchhiker", "hog"), new BrowserResult("42", fileObject));
assertThat(dto.getLinks().getLinkBy("hog").get().getHref()).isEqualTo("http://hitchhiker/hog/foo/42");
}
private FileObject createDirectoryObject() {
FileObject fileObject = createFileObject();
fileObject.setDirectory(true);
return fileObject;
}
private FileObject createFileObject() {
FileObject fileObject = new FileObject();
fileObject.setName("foo");
fileObject.setDescription("bar");
fileObject.setPath("foo/bar");
fileObject.setDirectory(false);
fileObject.setLength(100);
fileObject.setLastModified(123L);
fileObject.setSubRepository(new SubRepository("repo.url"));
return fileObject;
}
private void assertEqualAttributes(FileObject fileObject, FileObjectDto dto) {
assertThat(dto.getName()).isEqualTo(fileObject.getName());
assertThat(dto.getDescription()).isEqualTo(fileObject.getDescription());
assertThat(dto.getPath()).isEqualTo(fileObject.getPath());
assertThat(dto.isDirectory()).isEqualTo(fileObject.isDirectory());
assertThat(dto.getLength()).isEqualTo(fileObject.getLength());
assertThat(dto.getLastModified().toEpochMilli()).isEqualTo((long) fileObject.getLastModified());
assertThat(dto.getSubRepository().getBrowserUrl()).isEqualTo(fileObject.getSubRepository().getBrowserUrl());
}
}

View File

@@ -7,7 +7,6 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mapstruct.factory.Mappers;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.NotFoundException;
@@ -41,16 +40,12 @@ public class SourceRootResourceTest extends RepositoryTestBase {
@Mock
private BrowseCommandBuilder browseCommandBuilder;
@InjectMocks
private FileObjectToFileObjectDtoMapperImpl fileObjectToFileObjectDtoMapper;
private BrowserResultToFileObjectDtoMapper browserResultToFileObjectDtoMapper;
@Before
public void prepareEnvironment() throws Exception {
public void prepareEnvironment() {
browserResultToFileObjectDtoMapper = Mappers.getMapper(BrowserResultToFileObjectDtoMapper.class);
browserResultToFileObjectDtoMapper.setChildrenMapper(fileObjectToFileObjectDtoMapper);
browserResultToFileObjectDtoMapper.setResourceLinks(resourceLinks);
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
when(service.getBrowseCommand()).thenReturn(browseCommandBuilder);
@@ -127,7 +122,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
fileObject1.setDescription("File object 1");
fileObject1.setPath("/foo/bar/fo1");
fileObject1.setLength(1024L);
fileObject1.setLastModified(0L);
fileObject1.setCommitDate(0L);
parent.addChild(fileObject1);
FileObject fileObject2 = new FileObject();
@@ -136,7 +131,7 @@ public class SourceRootResourceTest extends RepositoryTestBase {
fileObject2.setDescription("File object 2");
fileObject2.setPath("/foo/bar/fo2");
fileObject2.setLength(4096L);
fileObject2.setLastModified(1234L);
fileObject2.setCommitDate(1234L);
parent.addChild(fileObject2);
return parent;

View File

@@ -0,0 +1,54 @@
package sonia.scm.repository;
import org.junit.jupiter.api.Test;
import sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType;
import java.time.Instant;
import static java.lang.Integer.MAX_VALUE;
import static java.time.Instant.MAX;
import static java.time.temporal.ChronoUnit.MILLIS;
import static org.assertj.core.api.Assertions.assertThat;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.ASYNCHRONOUS;
import static sonia.scm.repository.spi.SyncAsyncExecutor.ExecutionType.SYNCHRONOUS;
class DefaultSyncAsyncExecutorTest {
ExecutionType calledWithType = null;
boolean aborted = false;
@Test
void shouldExecuteSynchronouslyBeforeTimeout() {
DefaultSyncAsyncExecutor executor = new DefaultSyncAsyncExecutor(Runnable::run, MAX, MAX_VALUE);
ExecutionType result = executor.execute(type -> calledWithType = type, () -> aborted = true);
assertThat(result).isEqualTo(SYNCHRONOUS);
assertThat(calledWithType).isEqualTo(SYNCHRONOUS);
assertThat(executor.hasExecutedAllSynchronously()).isTrue();
assertThat(aborted).isFalse();
}
@Test
void shouldExecuteAsynchronouslyAfterTimeout() {
DefaultSyncAsyncExecutor executor = new DefaultSyncAsyncExecutor(Runnable::run, Instant.now().minus(1, MILLIS), MAX_VALUE);
ExecutionType result = executor.execute(type -> calledWithType = type, () -> aborted = true);
assertThat(result).isEqualTo(ASYNCHRONOUS);
assertThat(calledWithType).isEqualTo(ASYNCHRONOUS);
assertThat(executor.hasExecutedAllSynchronously()).isFalse();
assertThat(aborted).isFalse();
}
@Test
void shouldCallFallbackAfterAbortion() {
DefaultSyncAsyncExecutor executor = new DefaultSyncAsyncExecutor(Runnable::run, Instant.now().minus(1, MILLIS), 0);
ExecutionType result = executor.execute(type -> calledWithType = type, () -> aborted = true);
assertThat(result).isEqualTo(ASYNCHRONOUS);
assertThat(calledWithType).isNull();
assertThat(aborted).isTrue();
}
}

414
yarn.lock
View File

@@ -840,7 +840,7 @@
"@babel/helper-create-regexp-features-plugin" "^7.7.4"
"@babel/helper-plugin-utils" "^7.0.0"
"@babel/polyfill@^7.0.0", "@babel/polyfill@^7.6.0":
"@babel/polyfill@^7.6.0":
version "7.6.0"
resolved "https://registry.yarnpkg.com/@babel/polyfill/-/polyfill-7.6.0.tgz#6d89203f8b6cd323e8d946e47774ea35dc0619cc"
integrity sha512-q5BZJI0n/B10VaQQvln1IlDK3BTBJFbADx7tv+oXDPIDZuTo37H5Adb9jhlXm/fEN4Y7/64qD9mnrJJG7rmaTw==
@@ -2238,7 +2238,7 @@
once "^1.4.0"
universal-user-agent "^4.0.0"
"@octokit/rest@^16.28.4", "@octokit/rest@^16.33.1":
"@octokit/rest@^16.28.4":
version "16.34.0"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.34.0.tgz#8703e46d7e9f6aec24a7e591b073f325ca13f6e2"
integrity sha512-EBe5qMQQOZRuezahWCXCnSe0J6tAqrW2hrEH9U8esXzKor1+HUDf8jgImaZf5lkTyWCQA296x9kAH5c0pxEgVQ==
@@ -2274,11 +2274,6 @@
react-lifecycles-compat "^3.0.4"
warning "^3.0.0"
"@sindresorhus/is@^0.7.0":
version "0.7.0"
resolved "https://registry.yarnpkg.com/@sindresorhus/is/-/is-0.7.0.tgz#9a06f4f137ee84d7df0460c1fdb1135ffa6c50fd"
integrity sha512-ONhaKPIufzzrlNbqtWFFd+jlnemX6lJAgq9ZeiZtS7I1PIf/la7CW4m83rTXRnVnsMbW2k56pGYu7AUFJD9Pow==
"@storybook/addon-actions@^5.2.3":
version "5.2.8"
resolved "https://registry.yarnpkg.com/@storybook/addon-actions/-/addon-actions-5.2.8.tgz#f63c6e1afb59e94ca1ebc776b7cad9b815e7419e"
@@ -3177,46 +3172,48 @@
dependencies:
"@types/yargs-parser" "*"
"@typescript-eslint/eslint-plugin@^2.4.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.6.0.tgz#e82ed43fc4527b21bfe35c20a2d6e4ed49fc7957"
integrity sha512-iCcXREU4RciLmLniwKLRPCOFVXrkF7z27XuHq5DrykpREv/mz6ztKAyLg2fdkM0hQC7659p5ZF5uStH7uzAJ/w==
"@typescript-eslint/eslint-plugin@^2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.12.0.tgz#0da7cbca7b24f4c6919e9eb31c704bfb126f90ad"
integrity sha512-1t4r9rpLuEwl3hgt90jY18wJHSyb0E3orVL3DaqwmpiSDHmHiSspVsvsFF78BJ/3NNG3qmeso836jpuBWYziAA==
dependencies:
"@typescript-eslint/experimental-utils" "2.6.0"
eslint-utils "^1.4.2"
"@typescript-eslint/experimental-utils" "2.12.0"
eslint-utils "^1.4.3"
functional-red-black-tree "^1.0.1"
regexpp "^2.0.1"
regexpp "^3.0.0"
tsutils "^3.17.1"
"@typescript-eslint/experimental-utils@2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.6.0.tgz#ed70bef72822bff54031ff0615fc888b9e2b6e8a"
integrity sha512-34BAFpNOwHXeqT+AvdalLxOvcPYnCxA5JGmBAFL64RGMdP0u65rXjii7l/nwpgk5aLEE1LaqF+SsCU0/Cb64xA==
"@typescript-eslint/experimental-utils@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.12.0.tgz#e0a76ffb6293e058748408a191921e453c31d40d"
integrity sha512-jv4gYpw5N5BrWF3ntROvCuLe1IjRenLy5+U57J24NbPGwZFAjhnM45qpq0nDH1y/AZMb3Br25YiNVwyPbz6RkA==
dependencies:
"@types/json-schema" "^7.0.3"
"@typescript-eslint/typescript-estree" "2.6.0"
"@typescript-eslint/typescript-estree" "2.12.0"
eslint-scope "^5.0.0"
"@typescript-eslint/parser@^2.4.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.6.0.tgz#5106295c6a7056287b4719e24aae8d6293d5af49"
integrity sha512-AvLejMmkcjRTJ2KD72v565W4slSrrzUIzkReu1JN34b8JnsEsxx7S9Xx/qXEuMQas0mkdUfETr0j3zOhq2DIqQ==
"@typescript-eslint/parser@^2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.12.0.tgz#393f1604943a4ca570bb1a45bc8834e9b9158884"
integrity sha512-lPdkwpdzxEfjI8TyTzZqPatkrswLSVu4bqUgnB03fHSOwpC7KSerPgJRgIAf11UGNf7HKjJV6oaPZI4AghLU6g==
dependencies:
"@types/eslint-visitor-keys" "^1.0.0"
"@typescript-eslint/experimental-utils" "2.6.0"
"@typescript-eslint/typescript-estree" "2.6.0"
"@typescript-eslint/experimental-utils" "2.12.0"
"@typescript-eslint/typescript-estree" "2.12.0"
eslint-visitor-keys "^1.1.0"
"@typescript-eslint/typescript-estree@2.6.0":
version "2.6.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.6.0.tgz#d3e9d8e001492e2b9124c4d4bd4e7f03c0fd7254"
integrity sha512-A3lSBVIdj2Gp0lFEL6in2eSPqJ33uAc3Ko+Y4brhjkxzjbzLnwBH22CwsW2sCo+iwogfIyvb56/AJri15H0u5Q==
"@typescript-eslint/typescript-estree@2.12.0":
version "2.12.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.12.0.tgz#bd9e547ccffd17dfab0c3ab0947c80c8e2eb914c"
integrity sha512-rGehVfjHEn8Frh9UW02ZZIfJs6SIIxIu/K1bbci8rFfDE/1lQ8krIJy5OXOV3DVnNdDPtoiPOdEANkLMrwXbiQ==
dependencies:
debug "^4.1.1"
glob "^7.1.4"
eslint-visitor-keys "^1.1.0"
glob "^7.1.6"
is-glob "^4.0.1"
lodash.unescape "4.0.1"
semver "^6.3.0"
tsutils "^3.17.1"
"@webassemblyjs/ast@1.8.5":
version "1.8.5"
@@ -4380,11 +4377,6 @@ before-after-hook@^2.0.0:
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
big-integer@^1.6.17:
version "1.6.47"
resolved "https://registry.yarnpkg.com/big-integer/-/big-integer-1.6.47.tgz#e1e9320e26c4cc81f64fbf4b3bb20e025bf18e2d"
integrity sha512-9t9f7X3as2XGX8b52GqG6ox0GvIdM86LyIXASJnDCFhYNgt+A+MByQZ3W2PyMRZjEvG5f8TEbSPfEotVuMJnQg==
big.js@^5.2.2:
version "5.2.2"
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
@@ -4395,14 +4387,6 @@ binary-extensions@^1.0.0:
resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.13.1.tgz#598afe54755b2868a5330d2aff9d4ebb53209b65"
integrity sha512-Un7MIEDdUC5gNpcGDV97op1Ywk748MpHcFTHoYs6qnj1Z3j7I53VG3nwZhKzoBZmbdRNnb6WRdFlwl7tSDuZGw==
binary@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/binary/-/binary-0.3.0.tgz#9f60553bc5ce8c3386f3b553cff47462adecaa79"
integrity sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=
dependencies:
buffers "~0.1.1"
chainsaw "~0.1.0"
block-stream@*:
version "0.0.9"
resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a"
@@ -4420,11 +4404,6 @@ bluebird@^3.5.1, bluebird@^3.5.3:
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.7.1.tgz#df70e302b471d7473489acf26a93d63b53f874de"
integrity sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==
bluebird@~3.4.1:
version "3.4.7"
resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.4.7.tgz#f72d760be09b7f76d08ed8fae98b289a8d05fab3"
integrity sha1-9y12C+Cbf3bQjtj66Ysomo0F+rM=
bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.1.1, bn.js@^4.4.0:
version "4.11.8"
resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.8.tgz#2cde09eb5ee341f484746bb0309b3253b1b1442f"
@@ -4621,11 +4600,6 @@ buffer-from@^1.0.0:
resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.1.tgz#32713bc028f75c02fdb710d7c7bcec1f2c6070ef"
integrity sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==
buffer-indexof-polyfill@~1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/buffer-indexof-polyfill/-/buffer-indexof-polyfill-1.0.1.tgz#a9fb806ce8145d5428510ce72f278bb363a638bf"
integrity sha1-qfuAbOgUXVQoUQznLyeLs2OmOL8=
buffer-indexof@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/buffer-indexof/-/buffer-indexof-1.1.1.tgz#52fabcc6a606d1a00302802648ef68f639da268c"
@@ -4650,11 +4624,6 @@ buffer@^4.3.0:
ieee754 "^1.1.4"
isarray "^1.0.0"
buffers@~0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/buffers/-/buffers-0.1.1.tgz#b24579c3bed4d6d396aeee6d9a8ae7f5482ab7bb"
integrity sha1-skV5w77U1tOWru5tmorn9Ugqt7s=
builtin-status-codes@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz#85982878e21b98e1c66425e03d0174788f569ee8"
@@ -4748,19 +4717,6 @@ cache-loader@^4.1.0:
neo-async "^2.6.1"
schema-utils "^2.0.0"
cacheable-request@^2.1.1:
version "2.1.4"
resolved "https://registry.yarnpkg.com/cacheable-request/-/cacheable-request-2.1.4.tgz#0d808801b6342ad33c91df9d0b44dc09b91e5c3d"
integrity sha1-DYCIAbY0KtM8kd+dC0TcCbkeXD0=
dependencies:
clone-response "1.0.2"
get-stream "3.0.0"
http-cache-semantics "3.8.1"
keyv "3.0.0"
lowercase-keys "1.0.0"
normalize-url "2.0.1"
responselike "1.0.2"
call-me-maybe@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
@@ -4882,13 +4838,6 @@ caseless@~0.12.0:
resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc"
integrity sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=
chainsaw@~0.1.0:
version "0.1.0"
resolved "https://registry.yarnpkg.com/chainsaw/-/chainsaw-0.1.0.tgz#5eab50b28afe58074d0d58291388828b5e5fbc98"
integrity sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=
dependencies:
traverse ">=0.3.0 <0.4"
chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.1.0, chalk@^2.3.1, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
@@ -4929,11 +4878,6 @@ chardet@^0.7.0:
resolved "https://registry.yarnpkg.com/chardet/-/chardet-0.7.0.tgz#90094849f0937f2eedc2425d0d28a9e5f0cbad9e"
integrity sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==
charenc@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/charenc/-/charenc-0.0.2.tgz#c0a1d2f3a7092e03774bfa83f14c0fc5790a8667"
integrity sha1-wKHS86cJLgN3S/qD8UwPxXkKhmc=
cheerio@^1.0.0-rc.2:
version "1.0.0-rc.3"
resolved "https://registry.yarnpkg.com/cheerio/-/cheerio-1.0.0-rc.3.tgz#094636d425b2e9c0f4eb91a46c05630c9a1a8bf6"
@@ -5102,13 +5046,6 @@ clone-deep@^4.0.1:
kind-of "^6.0.2"
shallow-clone "^3.0.0"
clone-response@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/clone-response/-/clone-response-1.0.2.tgz#d1dc973920314df67fbeb94223b4ee350239e96b"
integrity sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=
dependencies:
mimic-response "^1.0.0"
clone@^1.0.2:
version "1.0.4"
resolved "https://registry.yarnpkg.com/clone/-/clone-1.0.4.tgz#da309cc263df15994c688ca902179ca3c7cd7c7e"
@@ -5179,7 +5116,7 @@ color@^3.0.0:
color-convert "^1.9.1"
color-string "^1.5.2"
colors@^1.1.2, colors@^1.3.2:
colors@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/colors/-/colors-1.4.0.tgz#c50491479d4c1bdaed2c9ced32cf7c7dc2360f78"
integrity sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==
@@ -5623,11 +5560,6 @@ cross-spawn@^5.0.1:
shebang-command "^1.2.0"
which "^1.2.9"
crypt@~0.0.1:
version "0.0.2"
resolved "https://registry.yarnpkg.com/crypt/-/crypt-0.0.2.tgz#88d7ff7ec0dfb86f713dc87bbb42d044d3e6c41b"
integrity sha1-iNf/fsDfuG9xPch7u0LQRNPmxBs=
crypto-browserify@^3.11.0:
version "3.12.0"
resolved "https://registry.yarnpkg.com/crypto-browserify/-/crypto-browserify-3.12.0.tgz#396cf9f3137f03e4b8e532c58f698254e00f80ec"
@@ -5959,13 +5891,6 @@ decode-uri-component@^0.2.0:
resolved "https://registry.yarnpkg.com/decode-uri-component/-/decode-uri-component-0.2.0.tgz#eb3913333458775cb84cd1a1fae062106bb87545"
integrity sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=
decompress-response@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/decompress-response/-/decompress-response-3.3.0.tgz#80a4dd323748384bfa248083622aedec982adff3"
integrity sha1-gKTdMjdIOEv6JICDYirt7Jgq3/M=
dependencies:
mimic-response "^1.0.0"
dedent@^0.7.0:
version "0.7.0"
resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
@@ -6387,18 +6312,6 @@ dotenv@^8.0.0:
resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-8.2.0.tgz#97e619259ada750eea3e4ea3e26bceea5424b16a"
integrity sha512-8sJ78ElpbDJBHNeBzUbUVLsqKdccaa/BXF1uPTw3GrvQTBgrQrtObr2mUrE38vzYd8cEv+m/JBfDLioYcfXoaw==
duplexer2@~0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1"
integrity sha1-ixLauHjA1p4+eJEFFmKjL8a93ME=
dependencies:
readable-stream "^2.0.2"
duplexer3@^0.1.4:
version "0.1.4"
resolved "https://registry.yarnpkg.com/duplexer3/-/duplexer3-0.1.4.tgz#ee01dd1cac0ed3cbc7fdbea37dc0a8f1ce002ce2"
integrity sha1-7gHdHKwO08vH/b6jfcCo8c4ALOI=
duplexer@^0.1.1:
version "0.1.1"
resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1"
@@ -6844,7 +6757,7 @@ eslint-scope@^5.0.0:
esrecurse "^4.1.0"
estraverse "^4.1.1"
eslint-utils@^1.4.2, eslint-utils@^1.4.3:
eslint-utils@^1.4.3:
version "1.4.3"
resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f"
integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q==
@@ -7380,49 +7293,6 @@ flatted@^2.0.0:
resolved "https://registry.yarnpkg.com/flatted/-/flatted-2.0.1.tgz#69e57caa8f0eacbc281d2e2cb458d46fdb449e08"
integrity sha512-a1hQMktqW9Nmqr5aktAux3JMNqaucxGcjtjWnZLHX7yyPCmlSV3M54nGYbqT8K+0GhF3NBgmJCc3ma+WOgX8Jg==
flow-bin@^0.109.0:
version "0.109.0"
resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.109.0.tgz#dcdcb7402dd85b58200392d8716ccf14e5a8c24c"
integrity sha512-tpcMTpAGIRivYhFV3KJq+zHI2HzcXo8MoGe9pXS4G/UZuey2Faq/e8/gdph2WF0erRlML5hmwfwiq7v9c25c7w==
flow-typed@^2.5.1, flow-typed@^2.6.1:
version "2.6.2"
resolved "https://registry.yarnpkg.com/flow-typed/-/flow-typed-2.6.2.tgz#6d324a96c4df300e0f823c13ca879c824bef40ce"
integrity sha512-brTh8SukLidVpR1u8hSR3OcZSvLtptpwLEGgEhK/qBhWCB7zxPZmnmChYi40JQH6vB448ck380+qbkDo3fJ6qA==
dependencies:
"@babel/polyfill" "^7.0.0"
"@octokit/rest" "^16.33.1"
colors "^1.3.2"
flowgen "^1.9.0"
fs-extra "^7.0.0"
glob "^7.1.3"
got "^8.3.2"
md5 "^2.2.1"
mkdirp "^0.5.1"
prettier "^1.18.2"
rimraf "^2.6.2"
semver "^5.5.1"
table "^5.0.2"
through "^2.3.8"
unzipper "^0.9.3"
which "^1.3.1"
yargs "^12.0.2"
flowgen@^1.9.0:
version "1.10.0"
resolved "https://registry.yarnpkg.com/flowgen/-/flowgen-1.10.0.tgz#a041ae31d543d22166e7ba7c296b8445deb3c2e4"
integrity sha512-3lsoaa1vxGXhnkHuoE4mLPJi/klvpR3ID8R9CFJ/GBNi+cxJXecWQaUPrWMdNI5tGs8Y+7wrIZaCVFKFLQiGOg==
dependencies:
"@babel/code-frame" "^7.0.0"
"@babel/highlight" "^7.0.0"
commander "^2.11.0"
lodash "^4.17.4"
paralleljs "^0.2.1"
prettier "^1.16.4"
shelljs "^0.8.3"
typescript "^3.4"
typescript-compiler "^1.4.1-2"
flush-write-stream@^1.0.0:
version "1.1.1"
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-1.1.1.tgz#8dd7d873a1babc207d94ead0c2e0e44276ebf2e8"
@@ -7510,7 +7380,7 @@ fresh@0.5.2:
resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7"
integrity sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=
from2@^2.1.0, from2@^2.1.1:
from2@^2.1.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/from2/-/from2-2.3.0.tgz#8bfb5502bde4a4d36cfdeea007fcca21d7e382af"
integrity sha1-i/tVAr3kpNNs/e6gB/zKIdfjgq8=
@@ -7529,15 +7399,6 @@ fs-extra@^0.30.0:
path-is-absolute "^1.0.0"
rimraf "^2.2.8"
fs-extra@^7.0.0:
version "7.0.1"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-7.0.1.tgz#4f189c44aa123b895f722804f55ea23eadc348e9"
integrity sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==
dependencies:
graceful-fs "^4.1.2"
jsonfile "^4.0.0"
universalify "^0.1.0"
fs-extra@^8.0.1, fs-extra@^8.1.0:
version "8.1.0"
resolved "https://registry.yarnpkg.com/fs-extra/-/fs-extra-8.1.0.tgz#49d43c45a88cd9677668cb7be1b46efdb8d2e1c0"
@@ -7679,7 +7540,7 @@ get-stdin@^6.0.0:
resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-6.0.0.tgz#9e09bf712b360ab9225e812048f71fde9c89657b"
integrity sha512-jp4tHawyV7+fkkSKyvjuLZswblUtz+SQKzSWnBbii16BuZksJlU1wuBYXY75r+duh/llF1ur6oNwi+2ZzjKZ7g==
get-stream@3.0.0, get-stream@^3.0.0:
get-stream@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
@@ -7781,7 +7642,7 @@ glob-to-regexp@^0.4.0:
resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e"
integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==
glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
glob@^7.0.0, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@@ -7917,29 +7778,6 @@ good-listener@^1.2.2:
dependencies:
delegate "^3.1.2"
got@^8.3.2:
version "8.3.2"
resolved "https://registry.yarnpkg.com/got/-/got-8.3.2.tgz#1d23f64390e97f776cac52e5b936e5f514d2e937"
integrity sha512-qjUJ5U/hawxosMryILofZCkm3C84PLJS/0grRIpjAwu+Lkxxj5cxeCU25BG0/3mDSpXKTyZr8oh8wIgLaH0QCw==
dependencies:
"@sindresorhus/is" "^0.7.0"
cacheable-request "^2.1.1"
decompress-response "^3.3.0"
duplexer3 "^0.1.4"
get-stream "^3.0.0"
into-stream "^3.1.0"
is-retry-allowed "^1.1.0"
isurl "^1.0.0-alpha5"
lowercase-keys "^1.0.0"
mimic-response "^1.0.0"
p-cancelable "^0.4.0"
p-timeout "^2.0.1"
pify "^3.0.0"
safe-buffer "^5.1.1"
timed-out "^4.0.1"
url-parse-lax "^3.0.0"
url-to-options "^1.0.1"
graceful-fs@^4.1.11, graceful-fs@^4.1.15, graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.1.9, graceful-fs@^4.2.0:
version "4.2.3"
resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423"
@@ -8004,23 +7842,11 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-symbol-support-x@^1.4.1:
version "1.4.2"
resolved "https://registry.yarnpkg.com/has-symbol-support-x/-/has-symbol-support-x-1.4.2.tgz#1409f98bc00247da45da67cee0a36f282ff26455"
integrity sha512-3ToOva++HaW+eCpgqZrCfN51IPB+7bJNVT6CUATzueB5Heb8o6Nam0V3HG5dlDvZU1Gn5QLcbahiKw/XVk5JJw==
has-symbols@^1.0.0, has-symbols@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.1.tgz#9f5214758a44196c406d9bd76cebf81ec2dd31e8"
integrity sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==
has-to-string-tag-x@^1.2.0:
version "1.4.1"
resolved "https://registry.yarnpkg.com/has-to-string-tag-x/-/has-to-string-tag-x-1.4.1.tgz#a045ab383d7b4b2012a00148ab0aa5f290044d4d"
integrity sha512-vdbKfmw+3LoOYVr+mtxHaX5a96+0f3DljYd8JOqvOLsf5mw2Otda2qCDT9qRqLAhrjyQ0h7ual5nOiASpsGNFw==
dependencies:
has-symbol-support-x "^1.4.1"
has-unicode@^2.0.0, has-unicode@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9"
@@ -8270,7 +8096,7 @@ htmlparser2@^4.0:
domutils "^2.0.0"
entities "^2.0.0"
http-cache-semantics@3.8.1, http-cache-semantics@^3.8.1:
http-cache-semantics@^3.8.1:
version "3.8.1"
resolved "https://registry.yarnpkg.com/http-cache-semantics/-/http-cache-semantics-3.8.1.tgz#39b0e16add9b605bf0a9ef3d9daaf4843b4cacd2"
integrity sha512-5ai2iksyV8ZXmnZhHH4rWPoxxistEexSi5936zIQ1bnNTW5VnA85B6P/VpXiRM017IgRvb2kKo1a//y+0wSp3w==
@@ -8635,14 +8461,6 @@ interpret@1.2.0, interpret@^1.0.0, interpret@^1.2.0:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.2.0.tgz#d5061a6224be58e8083985f5014d844359576296"
integrity sha512-mT34yGKMNceBQUoVn7iCDKDntA7SC6gycMAWzGx1z/CMCTV7b2AAtXlo3nRyHZ1FelRkQbQjprHSYGwzLtkVbw==
into-stream@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/into-stream/-/into-stream-3.1.0.tgz#96fb0a936c12babd6ff1752a17d05616abd094c6"
integrity sha1-lvsKk2wSur1v8XUqF9BWFqvQlMY=
dependencies:
from2 "^2.1.1"
p-is-promise "^1.1.0"
invariant@^2.2.2, invariant@^2.2.3, invariant@^2.2.4:
version "2.2.4"
resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.4.tgz#610f3c92c9359ce1db616e538008d23ff35158e6"
@@ -8744,7 +8562,7 @@ is-boolean-object@^1.0.0:
resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.0.0.tgz#98f8b28030684219a95f375cfbd88ce3405dff93"
integrity sha1-mPiygDBoQhmpXzdc+9iM40Bd/5M=
is-buffer@^1.0.2, is-buffer@^1.1.4, is-buffer@^1.1.5, is-buffer@~1.1.1:
is-buffer@^1.0.2, is-buffer@^1.1.4, is-buffer@^1.1.5:
version "1.1.6"
resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.6.tgz#efaa2ea9daa0d7ab2ea13a97b2b8ad51fefbe8be"
integrity sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==
@@ -8975,11 +8793,6 @@ is-resolvable@^1.0.0:
resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
is-retry-allowed@^1.1.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/is-retry-allowed/-/is-retry-allowed-1.2.0.tgz#d778488bd0a4666a3be8a1482b9f2baafedea8b4"
integrity sha512-RUbUeKwvm3XG2VYamhJL1xFktgjvPzL0Hq8C+6yrWIswDy3BIXGqCxhxkc30N9jqK311gVU137K8Ei55/zVJRg==
is-root@2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/is-root/-/is-root-2.1.0.tgz#809e18129cf1129644302a4f8544035d51984a9c"
@@ -9158,14 +8971,6 @@ istanbul-reports@^2.2.6:
dependencies:
handlebars "^4.1.2"
isurl@^1.0.0-alpha5:
version "1.0.0"
resolved "https://registry.yarnpkg.com/isurl/-/isurl-1.0.0.tgz#b27f4f49f3cdaa3ea44a0a5b7f3462e6edc39d67"
integrity sha512-1P/yWsxPlDtn7QeRD+ULKQPaIaN6yF368GZ2vDfv0AL0NwpStafjWCDDdn0k8wgFMWpVAqG7oJhxHnlud42i9w==
dependencies:
has-to-string-tag-x "^1.2.0"
is-object "^1.0.1"
jest-changed-files@^24.9.0:
version "24.9.0"
resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039"
@@ -9612,11 +9417,6 @@ jsesc@~0.5.0:
resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d"
integrity sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=
json-buffer@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/json-buffer/-/json-buffer-3.0.0.tgz#5b1f397afc75d677bde8bcfc0e47e1f9a3d9a898"
integrity sha1-Wx85evx11ne96Lz8Dkfh+aPZqJg=
json-parse-better-errors@^1.0.0, json-parse-better-errors@^1.0.1, json-parse-better-errors@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
@@ -9698,13 +9498,6 @@ jsx-ast-utils@^2.2.1:
array-includes "^3.0.3"
object.assign "^4.1.0"
keyv@3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/keyv/-/keyv-3.0.0.tgz#44923ba39e68b12a7cec7df6c3268c031f2ef373"
integrity sha512-eguHnq22OE3uVoSYG0LVWNP+4ppamWr9+zWBe1bsNcovIMy6huUJFPgy4mGwCd/rnl3vOLGW1MTlu4c57CT1xA==
dependencies:
json-buffer "3.0.0"
killable@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/killable/-/killable-1.0.1.tgz#4c8ce441187a061c7474fb87ca08e2a638194892"
@@ -9847,11 +9640,6 @@ lines-and-columns@^1.1.6:
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
integrity sha1-HADHQ7QzzQpOgHWPe2SldEDZ/wA=
listenercount@~1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/listenercount/-/listenercount-1.0.1.tgz#84c8a72ab59c4725321480c975e6508342e70937"
integrity sha1-hMinKrWcRyUyFIDJdeZQg0LnCTc=
load-json-file@^1.0.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0"
@@ -10046,7 +9834,7 @@ lodash.uniq@^4.5.0:
resolved "https://registry.yarnpkg.com/lodash.uniq/-/lodash.uniq-4.5.0.tgz#d0225373aeb652adc1bc82e4945339a842754773"
integrity sha1-0CJTc662Uq3BvILklFM5qEJ1R3M=
lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.4, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10:
lodash@^4.0.0, lodash@^4.15.0, lodash@^4.17.11, lodash@^4.17.12, lodash@^4.17.13, lodash@^4.17.14, lodash@^4.17.15, lodash@^4.17.5, lodash@^4.2.1, lodash@~4.17.10:
version "4.17.15"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.15.tgz#b447f6670a0455bbfeedd11392eff330ea097548"
integrity sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==
@@ -10076,16 +9864,6 @@ lower-case@^1.1.1:
resolved "https://registry.yarnpkg.com/lower-case/-/lower-case-1.1.4.tgz#9a2cabd1b9e8e0ae993a4bf7d5875c39c42e8eac"
integrity sha1-miyr0bno4K6ZOkv31YdcOcQujqw=
lowercase-keys@1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.0.tgz#4e3366b39e7f5457e35f1324bdf6f88d0bfc7306"
integrity sha1-TjNms55/VFfjXxMkvfb4jQv8cwY=
lowercase-keys@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/lowercase-keys/-/lowercase-keys-1.0.1.tgz#6f9e30b47084d971a7c820ff15a6c5167b74c26f"
integrity sha512-G2Lj61tXDnVFFOi8VZds+SoQjtQC3dgokKdDG2mTm1tx4m50NUHBOZSBwQQHyy0V12A0JTG4icfZQH+xPyh8VA==
lowlight@~1.11.0:
version "1.11.0"
resolved "https://registry.yarnpkg.com/lowlight/-/lowlight-1.11.0.tgz#1304d83005126d4e8b1dc0f07981e9b689ec2efc"
@@ -10229,15 +10007,6 @@ md5.js@^1.3.4:
inherits "^2.0.1"
safe-buffer "^5.1.2"
md5@^2.2.1:
version "2.2.1"
resolved "https://registry.yarnpkg.com/md5/-/md5-2.2.1.tgz#53ab38d5fe3c8891ba465329ea23fac0540126f9"
integrity sha1-U6s41f48iJG6RlMp6iP6wFQBJvk=
dependencies:
charenc "~0.0.1"
crypt "~0.0.1"
is-buffer "~1.1.1"
mdast-add-list-metadata@1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mdast-add-list-metadata/-/mdast-add-list-metadata-1.0.1.tgz#95e73640ce2fc1fa2dcb7ec443d09e2bfe7db4cf"
@@ -10435,11 +10204,6 @@ mimic-fn@^2.0.0, mimic-fn@^2.1.0:
resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
mimic-response@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/mimic-response/-/mimic-response-1.0.1.tgz#4923538878eef42063cb8a3e3b0798781487ab1b"
integrity sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==
min-document@^2.19.0:
version "2.19.0"
resolved "https://registry.yarnpkg.com/min-document/-/min-document-2.19.0.tgz#7bd282e3f5842ed295bb748cdd9f1ffa2c824685"
@@ -10954,15 +10718,6 @@ normalize-url@1.9.1:
query-string "^4.1.0"
sort-keys "^1.0.0"
normalize-url@2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-2.0.1.tgz#835a9da1551fa26f70e92329069a23aa6574d7e6"
integrity sha512-D6MUW4K/VzoJ4rJ01JFKxDrtY1v9wrgzCX5f2qj/lzH1m/lW6MhUZFKerVsnyjOhOsYzI9Kqqak+10l4LvLpMw==
dependencies:
prepend-http "^2.0.0"
query-string "^5.0.1"
sort-keys "^2.0.0"
normalize-url@^3.0.0, normalize-url@^3.3.0:
version "3.3.0"
resolved "https://registry.yarnpkg.com/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
@@ -11303,11 +11058,6 @@ osenv@0, osenv@^0.1.4, osenv@^0.1.5:
os-homedir "^1.0.0"
os-tmpdir "^1.0.0"
p-cancelable@^0.4.0:
version "0.4.1"
resolved "https://registry.yarnpkg.com/p-cancelable/-/p-cancelable-0.4.1.tgz#35f363d67d52081c8d9585e37bcceb7e0bbcb2a0"
integrity sha512-HNa1A8LvB1kie7cERyy21VNeHb2CWJJYqyyC2o3klWFfMGlFmWv2Z7sFgZH8ZiaYL95ydToKTFVXgMV/Os0bBQ==
p-defer@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-defer/-/p-defer-1.0.0.tgz#9f6eb182f6c9aa8cd743004a7d4f96b196b0fb0c"
@@ -11325,11 +11075,6 @@ p-finally@^1.0.0:
resolved "https://registry.yarnpkg.com/p-finally/-/p-finally-1.0.0.tgz#3fbcfb15b899a44123b34b6dcc18b724336a2cae"
integrity sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=
p-is-promise@^1.1.0:
version "1.1.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-1.1.0.tgz#9c9456989e9f6588017b0434d56097675c3da05e"
integrity sha1-nJRWmJ6fZYgBewQ01WCXZ1w9oF4=
p-is-promise@^2.0.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/p-is-promise/-/p-is-promise-2.1.0.tgz#918cebaea248a62cf7ffab8e3bca8c5f882fc42e"
@@ -11406,13 +11151,6 @@ p-retry@^3.0.1:
dependencies:
retry "^0.12.0"
p-timeout@^2.0.1:
version "2.0.1"
resolved "https://registry.yarnpkg.com/p-timeout/-/p-timeout-2.0.1.tgz#d8dd1979595d2dc0139e1fe46b8b646cb3cdf038"
integrity sha512-88em58dDVB/KzPEx1X0N3LwFfYZPyDc4B6eF38M1rk9VTZMbxXXgjugz8mmwpS9Ox4BDZ+t6t3QP5+/gazweIA==
dependencies:
p-finally "^1.0.0"
p-try@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/p-try/-/p-try-1.0.0.tgz#cbc79cdbaf8fd4228e13f621f2b1a237c1b207b3"
@@ -11444,11 +11182,6 @@ parallel-transform@^1.1.0:
inherits "^2.0.3"
readable-stream "^2.1.5"
paralleljs@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/paralleljs/-/paralleljs-0.2.1.tgz#ebca745d3e09c01e2bebcc14858891ff4510e926"
integrity sha1-68p0XT4JwB4r68wUhYiR/0UQ6SY=
param-case@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/param-case/-/param-case-2.1.1.tgz#df94fd8cf6531ecf75e6bef9a0858fbc72be2247"
@@ -12141,11 +11874,6 @@ prepend-http@^1.0.0:
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-1.0.4.tgz#d4f4562b0ce3696e41ac52d0e002e57a635dc6dc"
integrity sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=
prepend-http@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/prepend-http/-/prepend-http-2.0.0.tgz#e92434bfa5ea8c19f41cdfd401d741a3c819d897"
integrity sha1-6SQ0v6XqjBn0HN/UAddBo8gZ2Jc=
prettier-linter-helpers@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz#d23d41fe1375646de2d0104d3454a3008802cf7b"
@@ -12158,11 +11886,6 @@ prettier-reflow@^1.18.2-1:
resolved "https://registry.yarnpkg.com/prettier-reflow/-/prettier-reflow-1.18.2-2.tgz#8959aabf7b23138fa85b54a26f65afd15a52cfde"
integrity sha512-Pd/rr0Si0f5qPQM+OheBsGV8w/u1mjpfXZPyVxLieG3aOLIOhbwzeCkoCPIlQ3VefWwKDq6gijoPlsa/fHQlFw==
prettier@^1.16.4, prettier@^1.18.2:
version "1.18.2"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.18.2.tgz#6823e7c5900017b4bd3acf46fe9ac4b4d7bda9ea"
integrity sha512-OeHeMc0JhFE9idD4ZdtNibzY0+TPHSpSSb9h8FqtP+YnoZZ1sl8Vc9b1sasjfymH3SonAF4QcA2+mzHPhMvIiw==
prettier@^1.19.1:
version "1.19.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.19.1.tgz#f7d7f5ff8a9cd872a7be4ca142095956a60797cb"
@@ -13191,6 +12914,11 @@ regexpp@^2.0.1:
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-2.0.1.tgz#8d19d31cf632482b589049f8281f93dbcba4d07f"
integrity sha512-lv0M6+TkDVniA3aD1Eg0DVpfU/booSu7Eev3TDO/mZKHBfVjgCGTV4t4buppESEYDtkArYFOxTJWv6S5C+iaNw==
regexpp@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.0.0.tgz#dd63982ee3300e67b41c1956f850aa680d9d330e"
integrity sha512-Z+hNr7RAVWxznLPuA7DIh8UNX1j9CDrUQxskw9IrBE1Dxue2lyXT+shqEIeLUjrokxIP8CMy1WkjgG3rTsd5/g==
regexpu-core@^4.6.0:
version "4.6.0"
resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-4.6.0.tgz#2037c18b327cfce8a6fea2a4ec441f2432afb8b6"
@@ -13405,13 +13133,6 @@ resolve@^1.12.0, resolve@^1.5.0:
dependencies:
path-parse "^1.0.6"
responselike@1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/responselike/-/responselike-1.0.2.tgz#918720ef3b631c5642be068f15ade5a46f4ba1e7"
integrity sha1-kYcg7ztjHFZCvgaPFa3lpG9Loec=
dependencies:
lowercase-keys "^1.0.0"
restore-cursor@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf"
@@ -13738,7 +13459,7 @@ set-value@^2.0.0, set-value@^2.0.1:
is-plain-object "^2.0.3"
split-string "^3.0.1"
setimmediate@^1.0.4, setimmediate@^1.0.5, setimmediate@~1.0.4:
setimmediate@^1.0.4, setimmediate@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285"
integrity sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU=
@@ -14521,7 +14242,7 @@ systemjs@0.21.6:
resolved "https://registry.yarnpkg.com/systemjs/-/systemjs-0.21.6.tgz#9d15e79d9f60abbac23f0d179f887ec01f260a1b"
integrity sha512-R+5S9eV9vcQgWOoS4D87joZ4xkFJHb19ZsyKY07D1+VBDE9bwYcU+KXE0r5XlDA8mFoJGyuWDbfrNoh90JsA8g==
table@^5.0.2, table@^5.2.3:
table@^5.2.3:
version "5.4.6"
resolved "https://registry.yarnpkg.com/table/-/table-5.4.6.tgz#1292d19500ce3f86053b05f0e8e7e4a3bb21079e"
integrity sha512-wmEc8m4fjnob4gt5riFRtTu/6+4rSe12TpAELNSqHMfF3IqnA+CH37USM6/YR3qRZv7e56kAEAtd6nKZaxe0Ug==
@@ -14688,7 +14409,7 @@ through2@^3.0.0:
dependencies:
readable-stream "2 || 3"
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6, through@^2.3.8:
through@2, "through@>=2.2.7 <3", through@^2.3.4, through@^2.3.6:
version "2.3.8"
resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5"
integrity sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=
@@ -14698,11 +14419,6 @@ thunky@^1.0.2:
resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d"
integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA==
timed-out@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/timed-out/-/timed-out-4.0.1.tgz#f32eacac5a175bea25d7fab565ab3ed8741ef56f"
integrity sha1-8y6srFoXW+ol1/q1Zas+2HQe9W8=
timers-browserify@^2.0.4:
version "2.0.11"
resolved "https://registry.yarnpkg.com/timers-browserify/-/timers-browserify-2.0.11.tgz#800b1f3eee272e5bc53ee465a04d0e804c31211f"
@@ -14817,11 +14533,6 @@ tr46@^1.0.1:
dependencies:
punycode "^2.1.0"
"traverse@>=0.3.0 <0.4":
version "0.3.9"
resolved "https://registry.yarnpkg.com/traverse/-/traverse-0.3.9.tgz#717b8f220cc0bb7b44e40514c22b2e8bbc70d8b9"
integrity sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk=
trim-newlines@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/trim-newlines/-/trim-newlines-1.0.0.tgz#5887966bb582a4503a41eb524f7d35011815a613"
@@ -14938,16 +14649,6 @@ typedarray@^0.0.6:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
typescript-compiler@^1.4.1-2:
version "1.4.1-2"
resolved "https://registry.yarnpkg.com/typescript-compiler/-/typescript-compiler-1.4.1-2.tgz#ba4f7db22d91534a1929d90009dce161eb72fd3f"
integrity sha1-uk99si2RU0oZKdkACdzhYety/T8=
typescript@^3.4:
version "3.6.4"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.6.4.tgz#b18752bb3792bc1a0281335f7f6ebf1bbfc5b91d"
integrity sha512-unoCll1+l+YK4i4F8f22TaNVPRHcD9PA3yCuZ8g5e0qGqlVlJ/8FSateOLLSagn+Yg5+ZwuPkL8LFUc0Jcvksg==
typescript@^3.7.2:
version "3.7.2"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb"
@@ -15124,21 +14825,6 @@ unset-value@^1.0.0:
has-value "^0.3.1"
isobject "^3.0.0"
unzipper@^0.9.3:
version "0.9.15"
resolved "https://registry.yarnpkg.com/unzipper/-/unzipper-0.9.15.tgz#97d99203dad17698ee39882483c14e4845c7549c"
integrity sha512-2aaUvO4RAeHDvOCuEtth7jrHFaCKTSXPqUkXwADaLBzGbgZGzUDccoEdJ5lW+3RmfpOZYNx0Rw6F6PUzM6caIA==
dependencies:
big-integer "^1.6.17"
binary "~0.3.0"
bluebird "~3.4.1"
buffer-indexof-polyfill "~1.0.0"
duplexer2 "~0.1.4"
fstream "^1.0.12"
listenercount "~1.0.1"
readable-stream "~2.3.6"
setimmediate "~1.0.4"
upath@^1.1.1:
version "1.2.0"
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
@@ -15170,13 +14856,6 @@ url-loader@^2.0.1:
mime "^2.4.4"
schema-utils "^2.5.0"
url-parse-lax@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/url-parse-lax/-/url-parse-lax-3.0.0.tgz#16b5cafc07dbe3676c1b1999177823d6503acb0c"
integrity sha1-FrXK/Afb42dsGxmZF3gj1lA6yww=
dependencies:
prepend-http "^2.0.0"
url-parse@^1.4.3:
version "1.4.7"
resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.4.7.tgz#a8a83535e8c00a316e403a5db4ac1b9b853ae278"
@@ -15185,11 +14864,6 @@ url-parse@^1.4.3:
querystringify "^2.1.1"
requires-port "^1.0.0"
url-to-options@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/url-to-options/-/url-to-options-1.0.1.tgz#1505a03a289a48cbd7a434efbaeec5055f5633a9"
integrity sha1-FQWgOiiaSMvXpDTvuu7FBV9WM6k=
url@^0.11.0:
version "0.11.0"
resolved "https://registry.yarnpkg.com/url/-/url-0.11.0.tgz#3838e97cfc60521eb73c525a8e55bfdd9e2e28f1"
@@ -15793,7 +15467,7 @@ yargs-parser@^5.0.0:
dependencies:
camelcase "^3.0.0"
yargs@12.0.5, yargs@^12.0.2:
yargs@12.0.5:
version "12.0.5"
resolved "https://registry.yarnpkg.com/yargs/-/yargs-12.0.5.tgz#05f5997b609647b64f66b81e3b4b10a368e7ad13"
integrity sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==