Merge with default

This commit is contained in:
Rene Pfeuffer
2020-01-08 11:22:47 +01:00
66 changed files with 2676 additions and 2494 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

@@ -199,7 +199,7 @@ public final class BrowseCommandBuilder
return this;
}
/**
* Disabling the last commit means that every call to
* {@link FileObject#getDescription()} and
@@ -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

@@ -12,18 +12,25 @@ import static java.util.Collections.unmodifiableCollection;
* case you can use {@link #getFilesWithConflict()} to get a list of files with merge conflicts.
*/
public class MergeCommandResult {
private final Collection<String> filesWithConflict;
private final String newHeadRevision;
private final String targetRevision;
private final String revisionToMerge;
private MergeCommandResult(Collection<String> filesWithConflict) {
private MergeCommandResult(Collection<String> filesWithConflict, String targetRevision, String revisionToMerge, String newHeadRevision) {
this.filesWithConflict = filesWithConflict;
this.targetRevision = targetRevision;
this.revisionToMerge = revisionToMerge;
this.newHeadRevision = newHeadRevision;
}
public static MergeCommandResult success() {
return new MergeCommandResult(emptyList());
public static MergeCommandResult success(String targetRevision, String revisionToMerge, String newHeadRevision) {
return new MergeCommandResult(emptyList(), targetRevision, revisionToMerge, newHeadRevision);
}
public static MergeCommandResult failure(Collection<String> filesWithConflict) {
return new MergeCommandResult(new HashSet<>(filesWithConflict));
public static MergeCommandResult failure(String targetRevision, String revisionToMerge, Collection<String> filesWithConflict) {
return new MergeCommandResult(new HashSet<>(filesWithConflict), targetRevision, revisionToMerge, null);
}
/**
@@ -31,7 +38,7 @@ public class MergeCommandResult {
* merge conflicts. In this case you can use {@link #getFilesWithConflict()} to check what files could not be merged.
*/
public boolean isSuccess() {
return filesWithConflict.isEmpty();
return filesWithConflict.isEmpty() && newHeadRevision != null;
}
/**
@@ -41,4 +48,26 @@ public class MergeCommandResult {
public Collection<String> getFilesWithConflict() {
return unmodifiableCollection(filesWithConflict);
}
/**
* Returns the revision of the new head of the target branch, if the merge was successful ({@link #isSuccess()})
*/
public String getNewHeadRevision() {
return newHeadRevision;
}
/**
* Returns the revision of the target branch prior to the merge.
*/
public String getTargetRevision() {
return targetRevision;
}
/**
* Returns the revision of the branch that was merged into the target (or in case of a conflict of the revision that
* should have been merged).
*/
public String getRevisionToMerge() {
return revisionToMerge;
}
}

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

@@ -7,6 +7,12 @@ import sonia.scm.repository.api.MergeStrategy;
import java.util.Set;
public interface MergeCommand {
/**
* Executes the merge.
* @param request The parameters specifying the merge.
* @return Result holding either the new revision or a list of conflicting files.
* @throws sonia.scm.NoChangesMadeException If the merge neither had a conflict nor made any change.
*/
MergeCommandResult merge(MergeCommandRequest request);
MergeDryRunCommandResult dryRun(MergeCommandRequest request);

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);
}