mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-02 03:25:56 +01:00
Introduce Git Revert functionality to SCM-Manager
This commit is contained in:
46
scm-core/src/main/java/sonia/scm/ConflictException.java
Normal file
46
scm-core/src/main/java/sonia/scm/ConflictException.java
Normal file
@@ -0,0 +1,46 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm;
|
||||
|
||||
import sonia.scm.repository.NamespaceAndName;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||
|
||||
public class ConflictException extends ExceptionWithContext {
|
||||
private static final String CODE = "7XUd94Iwo1";
|
||||
|
||||
public ConflictException(NamespaceAndName namespaceAndName, Collection<String> conflictingFiles) {
|
||||
super(
|
||||
createContext(namespaceAndName, conflictingFiles),
|
||||
"conflict"
|
||||
);
|
||||
}
|
||||
|
||||
private static List<ContextEntry> createContext(NamespaceAndName namespaceAndName, Collection<String> conflictingFiles) {
|
||||
return entity("files", String.join(", ", conflictingFiles))
|
||||
.in(namespaceAndName)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return CODE;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import sonia.scm.BadRequestException;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
|
||||
public class MultipleParentsNotAllowedException extends BadRequestException {
|
||||
public MultipleParentsNotAllowedException(String changeset) {
|
||||
super(
|
||||
Collections.emptyList(),
|
||||
String.format("%s has more than one parent changeset, which is not allowed with this request.", changeset));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "3a47Hzu1e3";
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import lombok.Getter;
|
||||
import sonia.scm.BadRequestException;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Thrown when a changeset has no parent.
|
||||
* @since 3.8
|
||||
*/
|
||||
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
|
||||
@Getter
|
||||
public class NoParentException extends BadRequestException {
|
||||
|
||||
public NoParentException(String changeset) {
|
||||
super(emptyList(), String.format("%s has no parent.", changeset));
|
||||
this.revision = changeset;
|
||||
}
|
||||
|
||||
private final String revision;
|
||||
|
||||
@Override
|
||||
public String getCode() {
|
||||
return "a37jI66dup";
|
||||
}
|
||||
}
|
||||
@@ -82,5 +82,10 @@ public enum Command
|
||||
/**
|
||||
* @since 2.39.0
|
||||
*/
|
||||
CHANGESETS
|
||||
CHANGESETS,
|
||||
|
||||
/**
|
||||
* @since 3.8
|
||||
*/
|
||||
REVERT
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import jakarta.annotation.Nullable;
|
||||
import lombok.Getter;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import sonia.scm.cache.CacheManager;
|
||||
@@ -55,22 +56,6 @@ import java.util.stream.Stream;
|
||||
* after work is finished. For closing the connection to the repository use the
|
||||
* {@link #close()} method.
|
||||
*
|
||||
* @apiviz.uses sonia.scm.repository.Feature
|
||||
* @apiviz.uses sonia.scm.repository.api.Command
|
||||
* @apiviz.uses sonia.scm.repository.api.BlameCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.BrowseCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.CatCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.DiffCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.LogCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.TagsCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.BranchesCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.IncomingCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.OutgoingCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.PullCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.PushCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.BundleCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.UnbundleCommandBuilder
|
||||
* @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder
|
||||
* @since 1.17
|
||||
*/
|
||||
public final class RepositoryService implements Closeable {
|
||||
@@ -80,7 +65,10 @@ public final class RepositoryService implements Closeable {
|
||||
private final CacheManager cacheManager;
|
||||
private final PreProcessorUtil preProcessorUtil;
|
||||
private final RepositoryServiceProvider provider;
|
||||
|
||||
@Getter
|
||||
private final Repository repository;
|
||||
|
||||
@SuppressWarnings({"rawtypes", "java:S3740"})
|
||||
private final Set<ScmProtocolProvider> protocolProviders;
|
||||
private final WorkdirProvider workdirProvider;
|
||||
@@ -119,7 +107,7 @@ public final class RepositoryService implements Closeable {
|
||||
|
||||
/**
|
||||
* Closes the connection to the repository and releases all locks
|
||||
* and resources. This method should be called in a finally block e.g.:
|
||||
* and resources. This method should be called in a finally block; e.g.:
|
||||
*
|
||||
* <pre><code>
|
||||
* RepositoryService service = null;
|
||||
@@ -143,7 +131,29 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The blame command shows changeset information by line for a given file.
|
||||
* Returns true if the command is supported by the repository service.
|
||||
*
|
||||
* @param command command
|
||||
* @return true if the command is supported
|
||||
*/
|
||||
public boolean isSupported(Command command) {
|
||||
return provider.getSupportedCommands().contains(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the feature is supported by the repository service.
|
||||
*
|
||||
* @param feature feature
|
||||
* @return true if the feature is supported
|
||||
* @since 1.25
|
||||
*/
|
||||
public boolean isSupported(Feature feature) {
|
||||
return provider.getSupportedFeatures().contains(feature);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BlameCommandBuilder}. It can take the respective parameters and be executed to show
|
||||
* changeset information by line for a given file.
|
||||
*
|
||||
* @return instance of {@link BlameCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -157,21 +167,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The branches command list all repository branches.
|
||||
*
|
||||
* @return instance of {@link BranchesCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
*/
|
||||
public BranchesCommandBuilder getBranchesCommand() {
|
||||
LOG.debug("create branches command for repository {}", repository);
|
||||
|
||||
return new BranchesCommandBuilder(cacheManager,
|
||||
provider.getBranchesCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* The branch command creates new branches.
|
||||
* Creates a {@link BranchCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* create new branches, if supported by the particular SCM system.
|
||||
*
|
||||
* @return instance of {@link BranchCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -186,7 +183,37 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The browse command allows browsing of a repository.
|
||||
* Creates a {@link BranchDetailsCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* get details for a branch.
|
||||
*
|
||||
* @return instance of {@link BranchDetailsCommand}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.28.0
|
||||
*/
|
||||
public BranchDetailsCommandBuilder getBranchDetailsCommand() {
|
||||
LOG.debug("create branch details command for repository {}", repository);
|
||||
return new BranchDetailsCommandBuilder(repository, provider.getBranchDetailsCommand(), cacheManager);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BranchesCommandBuilder}. It can take the respective parameters and be executed to list
|
||||
* all repository branches.
|
||||
*
|
||||
* @return instance of {@link BranchesCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
*/
|
||||
public BranchesCommandBuilder getBranchesCommand() {
|
||||
LOG.debug("create branches command for repository {}", repository);
|
||||
|
||||
return new BranchesCommandBuilder(cacheManager,
|
||||
provider.getBranchesCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link BrowseCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* browse for content within a repository.
|
||||
*
|
||||
* @return instance of {@link BrowseCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -200,7 +227,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The bundle command creates an archive from the repository.
|
||||
* Creates a {@link BundleCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* create an archive from the repository.
|
||||
*
|
||||
* @return instance of {@link BundleCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -214,7 +242,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The cat command show the content of a given file.
|
||||
* Creates a {@link CatCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show the content of a given file.
|
||||
*
|
||||
* @return instance of {@link CatCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -227,8 +256,21 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The diff command shows differences between revisions for a specified file
|
||||
* or the entire revision.
|
||||
* Creates a {@link ChangesetsCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* retrieve a set of at least one changeset.
|
||||
*
|
||||
* @return Instance of {@link ChangesetsCommandBuilder}.
|
||||
* @throws CommandNotSupportedException if the command is not supported by
|
||||
* the implementation of the {@link RepositoryServiceProvider}.
|
||||
*/
|
||||
public ChangesetsCommandBuilder getChangesetsCommand() {
|
||||
LOG.debug("create changesets command for repository {}", repository);
|
||||
return new ChangesetsCommandBuilder(repository, provider.getChangesetsCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link DiffCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show differences between revisions for a specified file or the entire revision.
|
||||
*
|
||||
* @return instance of {@link DiffCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -241,8 +283,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The diff command shows differences between revisions for a specified file
|
||||
* or the entire revision.
|
||||
* Creates a {@link DiffResultCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show differences between revisions for a specified file or the entire revision.
|
||||
*
|
||||
* @return instance of {@link DiffResultCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -255,8 +297,36 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The incoming command shows new {@link Changeset}s found in a different
|
||||
* repository location.
|
||||
* Creates a {@link FullHealthCheckCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* inspect a repository profoundly. This might take a while in contrast to the lighter checks executed at startup.
|
||||
*
|
||||
* @return instance of {@link FullHealthCheckCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.17.0
|
||||
*/
|
||||
public FullHealthCheckCommandBuilder getFullCheckCommand() {
|
||||
LOG.debug("create full check command for repository {}", repository);
|
||||
return new FullHealthCheckCommandBuilder(provider.getFullHealthCheckCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link FileLockCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* lock and unlock files.
|
||||
*
|
||||
* @return instance of {@link FileLockCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.26.0
|
||||
*/
|
||||
public FileLockCommandBuilder getLockCommand() {
|
||||
LOG.debug("create lock command for repository {}", repository);
|
||||
return new FileLockCommandBuilder(provider.getFileLockCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link IncomingCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show new {@link Changeset}s found in a different repository location.
|
||||
*
|
||||
* @return instance of {@link IncomingCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -271,7 +341,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The log command shows revision history of entire repository or files.
|
||||
* Creates a {@link LogCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show revision history of entire repository or files.
|
||||
*
|
||||
* @return instance of {@link LogCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -285,7 +356,54 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The modification command shows file modifications in a revision.
|
||||
* Creates a {@link LookupCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* conduct a lookup which returns additional information for the repository.
|
||||
*
|
||||
* @return instance of {@link LookupCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.10.0
|
||||
*/
|
||||
public LookupCommandBuilder getLookupCommand() {
|
||||
LOG.debug("create lookup command for repository {}", repository);
|
||||
return new LookupCommandBuilder(provider.getLookupCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MergeCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* conduct a merge of two branches.
|
||||
*
|
||||
* @return instance of {@link MergeCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public MergeCommandBuilder getMergeCommand() {
|
||||
RepositoryReadOnlyChecker.checkReadOnly(getRepository());
|
||||
LOG.debug("create merge command for repository {}", repository);
|
||||
|
||||
return new MergeCommandBuilder(provider.getMergeCommand(), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link MirrorCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* create a 'mirror' of an existing repository (specified by a URL) by copying all data
|
||||
* to the repository of this service. Therefore, this repository has to be empty (otherwise the behaviour is
|
||||
* not specified).
|
||||
*
|
||||
* @return instance of {@link MirrorCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.19.0
|
||||
*/
|
||||
public MirrorCommandBuilder getMirrorCommand() {
|
||||
LOG.debug("create mirror command for repository {}", repository);
|
||||
return new MirrorCommandBuilder(provider.getMirrorCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ModificationsCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show file modifications in a revision.
|
||||
*
|
||||
* @return instance of {@link ModificationsCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -297,7 +415,25 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The outgoing command show {@link Changeset}s not found in a remote repository.
|
||||
* Creates a {@link ModifyCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* makes changes to the files within a changeset.
|
||||
*
|
||||
* @return instance of {@link ModifyCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @see ModifyCommandBuilder
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public ModifyCommandBuilder getModifyCommand() {
|
||||
RepositoryReadOnlyChecker.checkReadOnly(getRepository());
|
||||
LOG.debug("create modify command for repository {}", repository);
|
||||
|
||||
return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider, repository.getId(), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link OutgoingCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* show {@link Changeset}s not found in a remote repository.
|
||||
*
|
||||
* @return instance of {@link OutgoingCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -312,7 +448,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The pull command pull changes from a other repository.
|
||||
* Creates a {@link PullCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* pull changes from another repository.
|
||||
*
|
||||
* @return instance of {@link PullCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -327,7 +464,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The push command pushes changes to a other repository.
|
||||
* Creates a {@link PushCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* push changes to another repository.
|
||||
*
|
||||
* @return instance of {@link PushCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -341,12 +479,18 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the repository of this service.
|
||||
* Creates a {@link RevertCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* apply a revert of a chosen changeset onto the given repository/branch combination.
|
||||
*
|
||||
* @return repository of this service
|
||||
* @return Instance of {@link RevertCommandBuilder}.
|
||||
* @throws CommandNotSupportedException if the command is not supported by
|
||||
* the implementation of the {@link RepositoryServiceProvider}.
|
||||
* @since 3.8
|
||||
* @see RevertCommandBuilder
|
||||
*/
|
||||
public Repository getRepository() {
|
||||
return repository;
|
||||
public RevertCommandBuilder getRevertCommand() {
|
||||
LOG.debug("create revert command for repository {}", repository);
|
||||
return new RevertCommandBuilder(provider.getRevertCommand(), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -376,7 +520,8 @@ public final class RepositoryService implements Closeable {
|
||||
}
|
||||
|
||||
/**
|
||||
* The unbundle command restores a repository from the given bundle.
|
||||
* Creates an {@link UnbundleCommandBuilder}. It can take the respective parameters and be executed to
|
||||
* restore a repository from the given bundle.
|
||||
*
|
||||
* @return instance of {@link UnbundleCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
@@ -390,137 +535,6 @@ public final class RepositoryService implements Closeable {
|
||||
repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* The merge command executes a merge of two branches. It is possible to do a dry run to check, whether the given
|
||||
* branches can be merged without conflicts.
|
||||
*
|
||||
* @return instance of {@link MergeCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public MergeCommandBuilder getMergeCommand() {
|
||||
RepositoryReadOnlyChecker.checkReadOnly(getRepository());
|
||||
LOG.debug("create merge command for repository {}", repository);
|
||||
|
||||
return new MergeCommandBuilder(provider.getMergeCommand(), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
* The modify command makes changes to the head of a branch. It is possible to
|
||||
* <ul>
|
||||
* <li>create new files</li>
|
||||
* <li>delete existing files</li>
|
||||
* <li>modify/replace files</li>
|
||||
* <li>move files</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return instance of {@link ModifyCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public ModifyCommandBuilder getModifyCommand() {
|
||||
RepositoryReadOnlyChecker.checkReadOnly(getRepository());
|
||||
LOG.debug("create modify command for repository {}", repository);
|
||||
|
||||
return new ModifyCommandBuilder(provider.getModifyCommand(), workdirProvider, repository.getId(), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
* The lookup command executes a lookup which returns additional information for the repository.
|
||||
*
|
||||
* @return instance of {@link LookupCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.10.0
|
||||
*/
|
||||
public LookupCommandBuilder getLookupCommand() {
|
||||
LOG.debug("create lookup command for repository {}", repository);
|
||||
return new LookupCommandBuilder(provider.getLookupCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* The full health check command inspects a repository in a way, that might take a while in contrast to the
|
||||
* light checks executed at startup.
|
||||
*
|
||||
* @return instance of {@link FullHealthCheckCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.17.0
|
||||
*/
|
||||
public FullHealthCheckCommandBuilder getFullCheckCommand() {
|
||||
LOG.debug("create full check command for repository {}", repository);
|
||||
return new FullHealthCheckCommandBuilder(provider.getFullHealthCheckCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* The mirror command creates a 'mirror' of an existing repository (specified by a URL) by copying all data
|
||||
* to the repository of this service. Therefore this repository has to be empty (otherwise the behaviour is
|
||||
* not specified).
|
||||
*
|
||||
* @return instance of {@link MirrorCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.19.0
|
||||
*/
|
||||
public MirrorCommandBuilder getMirrorCommand() {
|
||||
LOG.debug("create mirror command for repository {}", repository);
|
||||
return new MirrorCommandBuilder(provider.getMirrorCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lock and unlock files.
|
||||
*
|
||||
* @return instance of {@link FileLockCommandBuilder}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.26.0
|
||||
*/
|
||||
public FileLockCommandBuilder getLockCommand() {
|
||||
LOG.debug("create lock command for repository {}", repository);
|
||||
return new FileLockCommandBuilder(provider.getFileLockCommand(), repository);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get details for a branch.
|
||||
*
|
||||
* @return instance of {@link BranchDetailsCommand}
|
||||
* @throws CommandNotSupportedException if the command is not supported
|
||||
* by the implementation of the repository service provider.
|
||||
* @since 2.28.0
|
||||
*/
|
||||
public BranchDetailsCommandBuilder getBranchDetailsCommand() {
|
||||
LOG.debug("create branch details command for repository {}", repository);
|
||||
return new BranchDetailsCommandBuilder(repository, provider.getBranchDetailsCommand(), cacheManager);
|
||||
}
|
||||
|
||||
public ChangesetsCommandBuilder getChangesetsCommand() {
|
||||
LOG.debug("create changesets command for repository {}", repository);
|
||||
return new ChangesetsCommandBuilder(repository, provider.getChangesetsCommand());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the command is supported by the repository service.
|
||||
*
|
||||
* @param command command
|
||||
* @return true if the command is supported
|
||||
*/
|
||||
public boolean isSupported(Command command) {
|
||||
return provider.getSupportedCommands().contains(command);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the feature is supported by the repository service.
|
||||
*
|
||||
* @param feature feature
|
||||
* @return true if the feature is supported
|
||||
* @since 1.25
|
||||
*/
|
||||
public boolean isSupported(Feature feature) {
|
||||
return provider.getSupportedFeatures().contains(feature);
|
||||
}
|
||||
|
||||
public Stream<ScmProtocol> getSupportedProtocols() {
|
||||
return protocolProviders.stream()
|
||||
.filter(protocolProvider -> protocolProvider.getType().equals(getRepository().getType()))
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import jakarta.annotation.Nullable;
|
||||
import sonia.scm.repository.spi.RevertCommand;
|
||||
import sonia.scm.repository.spi.RevertCommandRequest;
|
||||
import sonia.scm.repository.util.AuthorUtil;
|
||||
import sonia.scm.user.DisplayUser;
|
||||
import sonia.scm.user.EMail;
|
||||
|
||||
/**
|
||||
* Applies a revert of a chosen changeset onto the given repository/branch combination.
|
||||
*
|
||||
* @since 3.8
|
||||
*/
|
||||
public final class RevertCommandBuilder {
|
||||
|
||||
private final RevertCommand command;
|
||||
private final RevertCommandRequest request;
|
||||
|
||||
@Nullable
|
||||
private final EMail email;
|
||||
|
||||
/**
|
||||
* @param command A {@link RevertCommand} implementation provided by some source.
|
||||
*/
|
||||
public RevertCommandBuilder(RevertCommand command, @Nullable EMail email) {
|
||||
this.command = command;
|
||||
this.email = email;
|
||||
this.request = new RevertCommandRequest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this to set the author of the revert commit manually. If this is omitted, the currently logged-in user will be
|
||||
* used instead. If the given user object does not have an email address, we will use {@link EMail} to compute a
|
||||
* fallback address.
|
||||
*
|
||||
* @param author Author entity.
|
||||
* @return This instance.
|
||||
*/
|
||||
public RevertCommandBuilder setAuthor(DisplayUser author) {
|
||||
request.setAuthor(AuthorUtil.createAuthorWithMailFallback(author, email));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obligatory value.
|
||||
*
|
||||
* @param revision Identifier of the revision.
|
||||
* @return This instance.
|
||||
*/
|
||||
public RevertCommandBuilder setRevision(String revision) {
|
||||
request.setRevision(revision);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an optional parameter. Not every SCM system supports branches.
|
||||
* If null or empty and supported by the SCM, the default branch of the repository shall be used.
|
||||
*
|
||||
* @param branch Name of the branch.
|
||||
* @return This instance.
|
||||
*/
|
||||
public RevertCommandBuilder setBranch(String branch) {
|
||||
request.setBranch(branch);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an optional parameter. If null or empty, a default message will be set.
|
||||
*
|
||||
* @param message Particular message.
|
||||
* @return This instance.
|
||||
*/
|
||||
public RevertCommandBuilder setMessage(String message) {
|
||||
request.setMessage(message);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the revert with the given builder parameters.
|
||||
*
|
||||
* @return {@link RevertCommandResult} with information about the executed revert.
|
||||
*/
|
||||
public RevertCommandResult execute() {
|
||||
AuthorUtil.setAuthorIfNotAvailable(request, email);
|
||||
Preconditions.checkArgument(request.isValid(), "Revert request is invalid, request was: %s", request);
|
||||
return command.revert(request);
|
||||
}
|
||||
|
||||
protected RevertCommandRequest getRequest() {
|
||||
return this.request;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import lombok.Getter;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import static java.util.Collections.emptyList;
|
||||
|
||||
/**
|
||||
* Contains the result of an executed revert command.
|
||||
*
|
||||
* @since 3.8
|
||||
*/
|
||||
@Getter
|
||||
public class RevertCommandResult {
|
||||
|
||||
/**
|
||||
* The identifier of the revision after the applied revert.
|
||||
*/
|
||||
private final String revision;
|
||||
/**
|
||||
* A collection of files where conflicts occur.
|
||||
*/
|
||||
private final Collection<String> filesWithConflict;
|
||||
|
||||
/**
|
||||
* Creates a {@link RevertCommandResult}.
|
||||
*
|
||||
* @param revision revision identifier
|
||||
* @param filesWithConflict a collection of files where conflicts occur
|
||||
*/
|
||||
public RevertCommandResult(String revision, Collection<String> filesWithConflict) {
|
||||
this.revision = revision;
|
||||
this.filesWithConflict = filesWithConflict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to indicate a successful revert.
|
||||
*
|
||||
* @param newHeadRevision id of the newly created revert
|
||||
* @return {@link RevertCommandResult}
|
||||
*/
|
||||
public static RevertCommandResult success(String newHeadRevision) {
|
||||
return new RevertCommandResult(newHeadRevision, emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to indicate a failed revert.
|
||||
*
|
||||
* @param filesWithConflict collection of conflicting files
|
||||
* @return {@link RevertCommandResult}
|
||||
*/
|
||||
public static RevertCommandResult failure(Collection<String> filesWithConflict) {
|
||||
return new RevertCommandResult(null, new HashSet<>(filesWithConflict));
|
||||
}
|
||||
|
||||
/**
|
||||
* If this returns <code>true</code>, the revert was successful. If this returns <code>false</code>, there may have
|
||||
* been problems like a merge conflict after the revert.
|
||||
*/
|
||||
public boolean isSuccessful() {
|
||||
return filesWithConflict.isEmpty() && revision != null;
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import sonia.scm.repository.Feature;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||
@@ -26,13 +27,17 @@ import java.util.Collections;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This class is an extension base for SCM system providers to implement command functionalitites.
|
||||
* If unimplemented, the methods within this class throw {@link CommandNotSupportedException}. These are not supposed
|
||||
* to be called if unimplemented for an SCM system.
|
||||
*
|
||||
* @see sonia.scm.repository.api.RepositoryService
|
||||
* @since 1.17
|
||||
*/
|
||||
public abstract class RepositoryServiceProvider implements Closeable
|
||||
{
|
||||
@Slf4j
|
||||
public abstract class RepositoryServiceProvider implements Closeable {
|
||||
|
||||
|
||||
|
||||
public abstract Set<Command> getSupportedCommands();
|
||||
|
||||
|
||||
@@ -41,195 +46,118 @@ public abstract class RepositoryServiceProvider implements Closeable
|
||||
* free resources, close connections or release locks than you have to
|
||||
* override this method.
|
||||
*
|
||||
*
|
||||
* @throws IOException
|
||||
*/
|
||||
@Override
|
||||
public void close() throws IOException
|
||||
{
|
||||
|
||||
// should be implmentented from a service provider
|
||||
public void close() throws IOException {
|
||||
log.warn("warning: close() has been called without implementation from a service provider.");
|
||||
}
|
||||
|
||||
|
||||
|
||||
public BlameCommand getBlameCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.BLAME);
|
||||
}
|
||||
|
||||
|
||||
public BranchesCommand getBranchesCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.BRANCHES);
|
||||
}
|
||||
|
||||
|
||||
public BranchCommand getBranchCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.BRANCH);
|
||||
}
|
||||
|
||||
|
||||
public BrowseCommand getBrowseCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.BROWSE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.43
|
||||
*/
|
||||
public BundleCommand getBundleCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.BUNDLE);
|
||||
}
|
||||
|
||||
public CatCommand getCatCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.CAT);
|
||||
}
|
||||
|
||||
|
||||
public DiffCommand getDiffCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.DIFF);
|
||||
}
|
||||
|
||||
public DiffResultCommand getDiffResultCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.DIFF_RESULT);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.31
|
||||
*/
|
||||
public IncomingCommand getIncomingCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.INCOMING);
|
||||
}
|
||||
|
||||
|
||||
public LogCommand getLogCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.LOG);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
*
|
||||
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||
* @throws CommandNotSupportedException if there is no Implementation
|
||||
*/
|
||||
public ModificationsCommand getModificationsCommand() {
|
||||
throw new CommandNotSupportedException(Command.MODIFICATIONS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.31
|
||||
*/
|
||||
public OutgoingCommand getOutgoingCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.OUTGOING);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.31
|
||||
*/
|
||||
public PullCommand getPullCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.PULL);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.31
|
||||
*/
|
||||
public PushCommand getPushCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.PUSH);
|
||||
}
|
||||
|
||||
|
||||
public Set<Feature> getSupportedFeatures()
|
||||
{
|
||||
public Set<Feature> getSupportedFeatures() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
|
||||
public TagsCommand getTagsCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.TAGS);
|
||||
public BlameCommand getBlameCommand() {
|
||||
throw new CommandNotSupportedException(Command.BLAME);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @since 2.11.0
|
||||
*/
|
||||
public TagCommand getTagCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.TAG);
|
||||
public BranchesCommand getBranchesCommand() {
|
||||
throw new CommandNotSupportedException(Command.BRANCHES);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 1.43
|
||||
*/
|
||||
public UnbundleCommand getUnbundleCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.UNBUNDLE);
|
||||
public BranchCommand getBranchCommand() {
|
||||
throw new CommandNotSupportedException(Command.BRANCH);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
public MergeCommand getMergeCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.MERGE);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.0
|
||||
*/
|
||||
public ModifyCommand getModifyCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.MODIFY);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.10.0
|
||||
*/
|
||||
public LookupCommand getLookupCommand()
|
||||
{
|
||||
throw new CommandNotSupportedException(Command.LOOKUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.17.0
|
||||
*/
|
||||
public FullHealthCheckCommand getFullHealthCheckCommand() {
|
||||
throw new CommandNotSupportedException(Command.FULL_HEALTH_CHECK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.19.0
|
||||
*/
|
||||
public MirrorCommand getMirrorCommand() {
|
||||
throw new CommandNotSupportedException(Command.MIRROR);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.26.0
|
||||
*/
|
||||
public FileLockCommand getFileLockCommand() {
|
||||
throw new CommandNotSupportedException(Command.FILE_LOCK);
|
||||
}
|
||||
|
||||
/**
|
||||
* @since 2.28.0
|
||||
*/
|
||||
public BranchDetailsCommand getBranchDetailsCommand() {
|
||||
throw new CommandNotSupportedException(Command.BRANCH_DETAILS);
|
||||
}
|
||||
|
||||
public BrowseCommand getBrowseCommand() {
|
||||
throw new CommandNotSupportedException(Command.BROWSE);
|
||||
}
|
||||
|
||||
public BundleCommand getBundleCommand() {
|
||||
throw new CommandNotSupportedException(Command.BUNDLE);
|
||||
}
|
||||
|
||||
public CatCommand getCatCommand() {
|
||||
throw new CommandNotSupportedException(Command.CAT);
|
||||
}
|
||||
|
||||
public ChangesetsCommand getChangesetsCommand() {
|
||||
throw new CommandNotSupportedException(Command.CHANGESETS);
|
||||
}
|
||||
|
||||
public DiffCommand getDiffCommand() {
|
||||
throw new CommandNotSupportedException(Command.DIFF);
|
||||
}
|
||||
|
||||
public DiffResultCommand getDiffResultCommand() {
|
||||
throw new CommandNotSupportedException(Command.DIFF_RESULT);
|
||||
}
|
||||
|
||||
public FileLockCommand getFileLockCommand() {
|
||||
throw new CommandNotSupportedException(Command.FILE_LOCK);
|
||||
}
|
||||
|
||||
public FullHealthCheckCommand getFullHealthCheckCommand() {
|
||||
throw new CommandNotSupportedException(Command.FULL_HEALTH_CHECK);
|
||||
}
|
||||
|
||||
public IncomingCommand getIncomingCommand() {
|
||||
throw new CommandNotSupportedException(Command.INCOMING);
|
||||
}
|
||||
|
||||
public LogCommand getLogCommand() {
|
||||
throw new CommandNotSupportedException(Command.LOG);
|
||||
}
|
||||
|
||||
public LookupCommand getLookupCommand() {
|
||||
throw new CommandNotSupportedException(Command.LOOKUP);
|
||||
}
|
||||
|
||||
public MergeCommand getMergeCommand() {
|
||||
throw new CommandNotSupportedException(Command.MERGE);
|
||||
}
|
||||
|
||||
public MirrorCommand getMirrorCommand() {
|
||||
throw new CommandNotSupportedException(Command.MIRROR);
|
||||
}
|
||||
|
||||
public ModificationsCommand getModificationsCommand() {
|
||||
throw new CommandNotSupportedException(Command.MODIFICATIONS);
|
||||
}
|
||||
|
||||
public ModifyCommand getModifyCommand() {
|
||||
throw new CommandNotSupportedException(Command.MODIFY);
|
||||
}
|
||||
|
||||
public OutgoingCommand getOutgoingCommand() {
|
||||
throw new CommandNotSupportedException(Command.OUTGOING);
|
||||
}
|
||||
|
||||
public PullCommand getPullCommand() {
|
||||
throw new CommandNotSupportedException(Command.PULL);
|
||||
}
|
||||
|
||||
public PushCommand getPushCommand() {
|
||||
throw new CommandNotSupportedException(Command.PUSH);
|
||||
}
|
||||
|
||||
public RevertCommand getRevertCommand() {
|
||||
throw new CommandNotSupportedException(Command.REVERT);
|
||||
}
|
||||
|
||||
public TagsCommand getTagsCommand() {
|
||||
throw new CommandNotSupportedException(Command.TAGS);
|
||||
}
|
||||
|
||||
public TagCommand getTagCommand() {
|
||||
throw new CommandNotSupportedException(Command.TAG);
|
||||
}
|
||||
|
||||
public UnbundleCommand getUnbundleCommand() {
|
||||
throw new CommandNotSupportedException(Command.UNBUNDLE);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,10 +17,12 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
/**
|
||||
* @deprecated This interface may get removed at some point in the future.
|
||||
* @since 1.17
|
||||
*/
|
||||
@Deprecated(since = "3.8")
|
||||
public interface Resetable
|
||||
{
|
||||
|
||||
public void reset();
|
||||
void reset();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import sonia.scm.repository.api.RevertCommandResult;
|
||||
|
||||
/**
|
||||
* Removes the changes from a particular changeset as a revert. This, in turn, will result a new changeset.
|
||||
*
|
||||
* @since 3.8
|
||||
*/
|
||||
public interface RevertCommand {
|
||||
|
||||
/**
|
||||
* Executes a revert.
|
||||
* @param request parameter set for this command.
|
||||
* @see RevertCommand
|
||||
* @return result set of the executed command (see {@link RevertCommandResult}).
|
||||
*/
|
||||
RevertCommandResult revert(RevertCommandRequest request);
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.ToString;
|
||||
import sonia.scm.Validateable;
|
||||
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.util.AuthorUtil.CommandWithAuthor;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* This class contains the information to run {@link RevertCommand#revert(RevertCommandRequest)}.
|
||||
|
||||
* @since 3.8
|
||||
*/
|
||||
@Setter
|
||||
@ToString
|
||||
public class RevertCommandRequest implements Validateable, CommandWithAuthor {
|
||||
|
||||
@Getter
|
||||
private Person author;
|
||||
|
||||
@Getter
|
||||
private String revision;
|
||||
|
||||
/**
|
||||
* Reverts can be signed with a GPG key. This is set as <tt>true</tt> by default.
|
||||
*/
|
||||
@Getter
|
||||
private boolean sign = true;
|
||||
|
||||
private String branch;
|
||||
|
||||
private String message;
|
||||
|
||||
public Optional<String> getBranch() {
|
||||
return Optional.ofNullable(branch);
|
||||
}
|
||||
|
||||
public Optional<String> getMessage() {
|
||||
return Optional.ofNullable(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isValid() {
|
||||
boolean validBranch = branch == null || !branch.isEmpty();
|
||||
boolean validMessage = message == null || !message.isEmpty();
|
||||
return revision != null && author != null && validBranch && validMessage;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -20,29 +20,61 @@ import jakarta.annotation.Nullable;
|
||||
import org.apache.shiro.SecurityUtils;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.user.DisplayUser;
|
||||
import sonia.scm.user.EMail;
|
||||
import sonia.scm.user.User;
|
||||
|
||||
/**
|
||||
* Contains convenience methods to manage {@link CommandWithAuthor} classes.
|
||||
*/
|
||||
public class AuthorUtil {
|
||||
|
||||
/**
|
||||
* @see AuthorUtil#createAuthorFromSubject(EMail)
|
||||
* @param request {@link CommandWithAuthor}
|
||||
*/
|
||||
public static void setAuthorIfNotAvailable(CommandWithAuthor request) {
|
||||
setAuthorIfNotAvailable(request, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see AuthorUtil#createAuthorFromSubject(EMail)
|
||||
* @param request {@link CommandWithAuthor}
|
||||
* @param eMail {@link EMail}
|
||||
*/
|
||||
public static void setAuthorIfNotAvailable(CommandWithAuthor request, @Nullable EMail eMail) {
|
||||
if (request.getAuthor() == null) {
|
||||
request.setAuthor(createAuthorFromSubject(eMail));
|
||||
}
|
||||
}
|
||||
|
||||
private static Person createAuthorFromSubject(@Nullable EMail eMail) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
User user = subject.getPrincipals().oneByType(User.class);
|
||||
/**
|
||||
* Depending on the mail input, the {@link Person} is either created by the given nullable {@link EMail}
|
||||
* or the information from {@link DisplayUser} if the mail remains null.
|
||||
* @param user {@link DisplayUser}
|
||||
* @param eMail (nullable) {@link EMail}
|
||||
* @return {@link Person}
|
||||
*/
|
||||
public static Person createAuthorWithMailFallback(DisplayUser user, @Nullable EMail eMail) {
|
||||
String name = user.getDisplayName();
|
||||
String mailAddress = eMail != null ? eMail.getMailOrFallback(user) : user.getMail();
|
||||
return new Person(name, mailAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an author from the Apache Shiro {@link Subject} given by the {@link SecurityUtils}.
|
||||
* @param eMail {@link EMail}
|
||||
* @return {@link Person}
|
||||
*/
|
||||
private static Person createAuthorFromSubject(@Nullable EMail eMail) {
|
||||
Subject subject = SecurityUtils.getSubject();
|
||||
User user = subject.getPrincipals().oneByType(User.class);
|
||||
return createAuthorWithMailFallback(DisplayUser.from(user), eMail);
|
||||
}
|
||||
|
||||
/**
|
||||
* Command whose execution includes an author as a {@link Person}.
|
||||
*/
|
||||
public interface CommandWithAuthor {
|
||||
Person getAuthor();
|
||||
|
||||
|
||||
@@ -16,8 +16,10 @@
|
||||
|
||||
package sonia.scm.user;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import sonia.scm.ReducedModelObject;
|
||||
|
||||
@EqualsAndHashCode
|
||||
public class DisplayUser implements ReducedModelObject {
|
||||
|
||||
private final String id;
|
||||
|
||||
@@ -40,6 +40,7 @@ public class VndMediaType {
|
||||
public static final String REPOSITORY_PERMISSION = PREFIX + "repositoryPermission" + SUFFIX;
|
||||
public static final String CHANGESET = PREFIX + "changeset" + SUFFIX;
|
||||
public static final String CHANGESET_COLLECTION = PREFIX + "changesetCollection" + SUFFIX;
|
||||
public static final String REVERT = PREFIX + "revert" + SUFFIX;
|
||||
public static final String MODIFICATIONS = PREFIX + "modifications" + SUFFIX;
|
||||
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
||||
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2020 - present Cloudogu GmbH
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify it under
|
||||
* the terms of the GNU Affero General Public License as published by the Free
|
||||
* Software Foundation, version 3.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT
|
||||
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
|
||||
* FOR A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
||||
* details.
|
||||
*
|
||||
* You should have received a copy of the GNU Affero General Public License
|
||||
* along with this program. If not, see https://www.gnu.org/licenses/.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository.api;
|
||||
|
||||
import org.apache.shiro.subject.PrincipalCollection;
|
||||
import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.spi.RevertCommand;
|
||||
import sonia.scm.repository.spi.RevertCommandRequest;
|
||||
import sonia.scm.user.DisplayUser;
|
||||
import sonia.scm.user.EMail;
|
||||
import sonia.scm.user.User;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class RevertCommandBuilderTest {
|
||||
|
||||
@Mock
|
||||
private RevertCommand revertCommand;
|
||||
@Mock
|
||||
private EMail eMail;
|
||||
|
||||
@InjectMocks
|
||||
private RevertCommandBuilder revertCommandBuilder;
|
||||
|
||||
@BeforeEach
|
||||
void prepareCommandBuilder() {
|
||||
revertCommandBuilder.setRevision("irrelevant");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseMailAddressFromEMailFallback() {
|
||||
User user = new User("dent", "Arthur Dent", null);
|
||||
DisplayUser author = DisplayUser.from(user);
|
||||
when(eMail.getMailOrFallback(author)).thenReturn("dent@hitchhiker.com");
|
||||
revertCommandBuilder.setAuthor(author);
|
||||
revertCommandBuilder.execute();
|
||||
|
||||
verify(revertCommand).revert(argThat(revertCommandRequest -> {
|
||||
assertThat(revertCommandRequest.getAuthor().getMail()).isEqualTo("dent@hitchhiker.com");
|
||||
return true;
|
||||
}));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetAuthorFromShiroSubjectIfNotSet() {
|
||||
User user = new User("dent", "Arthur Dent", null);DisplayUser author = DisplayUser.from(user);
|
||||
when(eMail.getMailOrFallback(author)).thenReturn("dent@hitchhiker.com");
|
||||
mockLoggedInUser(user);
|
||||
revertCommandBuilder.execute();
|
||||
RevertCommandRequest request = revertCommandBuilder.getRequest();
|
||||
|
||||
assertThat(request.getAuthor().getName()).isEqualTo("Arthur Dent");
|
||||
assertThat(request.getAuthor().getMail()).isEqualTo("dent@hitchhiker.com");
|
||||
|
||||
mockLogout();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetAllFieldsInRequest() {
|
||||
User user = new User("dent", "Arthur Dent", null);
|
||||
DisplayUser author = DisplayUser.from(user);
|
||||
when(eMail.getMailOrFallback(author)).thenReturn("dent@hitchhiker.com");
|
||||
revertCommandBuilder.setAuthor(author);
|
||||
revertCommandBuilder.setBranch("someBranch");
|
||||
revertCommandBuilder.setMessage("someMessage");
|
||||
|
||||
RevertCommandRequest request = revertCommandBuilder.getRequest();
|
||||
|
||||
assertThat(request.getAuthor().getName()).isEqualTo(author.getDisplayName());
|
||||
assertThat(request.getBranch()).contains("someBranch");
|
||||
assertThat(request.getMessage()).contains("someMessage");
|
||||
assertThat(request.getRevision()).isEqualTo("irrelevant");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotExecuteInvalidRequestDueToEmptyBranch() {
|
||||
User user = new User("dent", "Arthur Dent", "dent@hitchhiker.com");
|
||||
revertCommandBuilder.setAuthor(DisplayUser.from(user));
|
||||
revertCommandBuilder.setBranch("");
|
||||
assertThatThrownBy(() -> revertCommandBuilder.execute())
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("Revert request is invalid, request was: RevertCommandRequest(author=Arthur Dent, revision=irrelevant, sign=true, branch=Optional[], message=Optional.empty)");
|
||||
}
|
||||
|
||||
private void mockLoggedInUser(User loggedInUser) {
|
||||
Subject subject = mock(Subject.class);
|
||||
ThreadContext.bind(subject);
|
||||
PrincipalCollection principals = mock(PrincipalCollection.class);
|
||||
when(subject.getPrincipals()).thenReturn(principals);
|
||||
when(principals.oneByType(User.class)).thenReturn(loggedInUser);
|
||||
}
|
||||
|
||||
private void mockLogout() {
|
||||
ThreadContext.unbindSubject();
|
||||
}
|
||||
}
|
||||
@@ -26,6 +26,7 @@ import org.mockito.Answers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.user.DisplayUser;
|
||||
import sonia.scm.user.EMail;
|
||||
import sonia.scm.user.User;
|
||||
|
||||
@@ -54,9 +55,9 @@ class AuthorUtilTest {
|
||||
@Test
|
||||
void shouldCreateMailAddressFromEmail() {
|
||||
User trillian = new User("trillian");
|
||||
when(subject.getPrincipals().oneByType(User.class)).thenReturn(trillian);
|
||||
when(eMail.getMailOrFallback(trillian)).thenReturn("tricia@hitchhicker.com");
|
||||
|
||||
when(subject.getPrincipals().oneByType(User.class)).thenReturn(trillian);
|
||||
when(eMail.getMailOrFallback(DisplayUser.from(trillian))).thenReturn("tricia@hitchhicker.com");
|
||||
Command command = new Command(null);
|
||||
AuthorUtil.setAuthorIfNotAvailable(command, eMail);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user