mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 14:05:44 +01:00
Merge with 2.0.0-m3
This commit is contained in:
19
scm-core/src/main/java/sonia/scm/DisplayManager.java
Normal file
19
scm-core/src/main/java/sonia/scm/DisplayManager.java
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface DisplayManager<T extends ReducedModelObject> {
|
||||||
|
|
||||||
|
int DEFAULT_LIMIT = 5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Collection} of filtered objects
|
||||||
|
*
|
||||||
|
* @param filter the searched string
|
||||||
|
* @return filtered object from the store
|
||||||
|
*/
|
||||||
|
Collection<T> autocomplete(String filter);
|
||||||
|
|
||||||
|
Optional<T> get(String id);
|
||||||
|
}
|
||||||
@@ -47,8 +47,6 @@ public interface Manager<T extends ModelObject>
|
|||||||
extends HandlerBase<T>, LastModifiedAware
|
extends HandlerBase<T>, LastModifiedAware
|
||||||
{
|
{
|
||||||
|
|
||||||
int DEFAULT_LIMIT = 5;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reloads a object from store and overwrites all changes.
|
* Reloads a object from store and overwrites all changes.
|
||||||
|
|||||||
@@ -39,8 +39,10 @@ package sonia.scm;
|
|||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*
|
*
|
||||||
* @param <T> type of objects to transform
|
* @param <T> type of objects to transform
|
||||||
|
* @param <R> result type of the transformation
|
||||||
*/
|
*/
|
||||||
public interface TransformFilter<T>
|
@FunctionalInterface
|
||||||
|
public interface TransformFilter<T, R>
|
||||||
{
|
{
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -52,5 +54,5 @@ public interface TransformFilter<T>
|
|||||||
*
|
*
|
||||||
* @return tranformed object
|
* @return tranformed object
|
||||||
*/
|
*/
|
||||||
public T accept(T item);
|
R accept(T item);
|
||||||
}
|
}
|
||||||
|
|||||||
26
scm-core/src/main/java/sonia/scm/group/DisplayGroup.java
Normal file
26
scm-core/src/main/java/sonia/scm/group/DisplayGroup.java
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
package sonia.scm.group;
|
||||||
|
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
|
|
||||||
|
public class DisplayGroup implements ReducedModelObject {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final String displayName;
|
||||||
|
|
||||||
|
public static DisplayGroup from(Group group) {
|
||||||
|
return new DisplayGroup(group.getId(), group.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DisplayGroup(String id, String displayName) {
|
||||||
|
this.id = id;
|
||||||
|
this.displayName = displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package sonia.scm.group;
|
||||||
|
|
||||||
|
import sonia.scm.DisplayManager;
|
||||||
|
|
||||||
|
public interface GroupDisplayManager extends DisplayManager<DisplayGroup> {
|
||||||
|
}
|
||||||
@@ -61,14 +61,4 @@ public interface GroupManager
|
|||||||
* @return all groups assigned to the given member
|
* @return all groups assigned to the given member
|
||||||
*/
|
*/
|
||||||
public Collection<Group> getGroupsForMember(String member);
|
public Collection<Group> getGroupsForMember(String member);
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link java.util.Collection} of filtered objects
|
|
||||||
*
|
|
||||||
* @param filter the searched string
|
|
||||||
* @return filtered object from the store
|
|
||||||
*/
|
|
||||||
Collection<Group> autocomplete(String filter);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -109,11 +109,6 @@ public class GroupManagerDecorator
|
|||||||
return decorated.getGroupsForMember(member);
|
return decorated.getGroupsForMember(member);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Group> autocomplete(String filter) {
|
|
||||||
return decorated.autocomplete(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
|
|||||||
@@ -66,7 +66,7 @@ public final class Branch implements Serializable
|
|||||||
* This constructor should only be called from JAXB.
|
* This constructor should only be called from JAXB.
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
public Branch() {}
|
Branch() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a new branch.
|
* Constructs a new branch.
|
||||||
@@ -75,10 +75,19 @@ public final class Branch implements Serializable
|
|||||||
* @param name name of the branch
|
* @param name name of the branch
|
||||||
* @param revision latest revision of the branch
|
* @param revision latest revision of the branch
|
||||||
*/
|
*/
|
||||||
public Branch(String name, String revision)
|
Branch(String name, String revision, boolean defaultBranch)
|
||||||
{
|
{
|
||||||
this.name = name;
|
this.name = name;
|
||||||
this.revision = revision;
|
this.revision = revision;
|
||||||
|
this.defaultBranch = defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Branch normalBranch(String name, String revision) {
|
||||||
|
return new Branch(name, revision, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Branch defaultBranch(String name, String revision) {
|
||||||
|
return new Branch(name, revision, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -107,7 +116,8 @@ public final class Branch implements Serializable
|
|||||||
final Branch other = (Branch) obj;
|
final Branch other = (Branch) obj;
|
||||||
|
|
||||||
return Objects.equal(name, other.name)
|
return Objects.equal(name, other.name)
|
||||||
&& Objects.equal(revision, other.revision);
|
&& Objects.equal(revision, other.revision)
|
||||||
|
&& Objects.equal(defaultBranch, other.defaultBranch);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -162,6 +172,10 @@ public final class Branch implements Serializable
|
|||||||
return revision;
|
return revision;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean isDefaultBranch() {
|
||||||
|
return defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** name of the branch */
|
/** name of the branch */
|
||||||
@@ -169,4 +183,6 @@ public final class Branch implements Serializable
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private String revision;
|
private String revision;
|
||||||
|
|
||||||
|
private boolean defaultBranch;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,73 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.spi.BranchCommand;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public final class BranchCommandBuilder {
|
||||||
|
|
||||||
|
public BranchCommandBuilder(BranchCommand command) {
|
||||||
|
this.command = command;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies the source branch, which the new branch should be based on.
|
||||||
|
*
|
||||||
|
* @param parentBranch The base branch for the new branch.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public BranchCommandBuilder from(String parentBranch) {
|
||||||
|
request.setParentBranch(parentBranch);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the command and create a new branch with the given name.
|
||||||
|
* @param name The name of the new branch.
|
||||||
|
* @return The created branch.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public Branch branch(String name) {
|
||||||
|
request.setNewBranch(name);
|
||||||
|
return command.branch(request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private BranchCommand command;
|
||||||
|
private BranchRequest request = new BranchRequest();
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
package sonia.scm.repository.api;
|
||||||
|
|
||||||
|
public class BranchRequest {
|
||||||
|
private String parentBranch;
|
||||||
|
private String newBranch;
|
||||||
|
|
||||||
|
public String getParentBranch() {
|
||||||
|
return parentBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setParentBranch(String parentBranch) {
|
||||||
|
this.parentBranch = parentBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getNewBranch() {
|
||||||
|
return newBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setNewBranch(String newBranch) {
|
||||||
|
this.newBranch = newBranch;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,11 @@ public enum Command
|
|||||||
*/
|
*/
|
||||||
BRANCHES,
|
BRANCHES,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
BRANCH,
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -46,6 +46,7 @@ import sonia.scm.repository.PreProcessorUtil;
|
|||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.spi.HookChangesetProvider;
|
import sonia.scm.repository.spi.HookChangesetProvider;
|
||||||
import sonia.scm.repository.spi.HookChangesetRequest;
|
import sonia.scm.repository.spi.HookChangesetRequest;
|
||||||
|
import sonia.scm.repository.spi.HookChangesetResponse;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -115,8 +116,8 @@ public final class HookChangesetBuilder
|
|||||||
*/
|
*/
|
||||||
public Iterable<Changeset> getChangesets()
|
public Iterable<Changeset> getChangesets()
|
||||||
{
|
{
|
||||||
Iterable<Changeset> changesets =
|
HookChangesetResponse hookChangesetResponse = provider.handleRequest(request);
|
||||||
provider.handleRequest(request).getChangesets();
|
Iterable<Changeset> changesets = hookChangesetResponse.getChangesets();
|
||||||
|
|
||||||
if (!disablePreProcessors)
|
if (!disablePreProcessors)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ import sonia.scm.repository.Changeset;
|
|||||||
import sonia.scm.repository.Feature;
|
import sonia.scm.repository.Feature;
|
||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
import sonia.scm.repository.spi.RepositoryServiceProvider;
|
||||||
|
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
@@ -82,10 +83,9 @@ import java.util.stream.Stream;
|
|||||||
* @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder
|
* @apiviz.uses sonia.scm.repository.api.MergeCommandBuilder
|
||||||
* @since 1.17
|
* @since 1.17
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
|
||||||
public final class RepositoryService implements Closeable {
|
public final class RepositoryService implements Closeable {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(RepositoryService.class);
|
private static final Logger LOG = LoggerFactory.getLogger(RepositoryService.class);
|
||||||
|
|
||||||
private final CacheManager cacheManager;
|
private final CacheManager cacheManager;
|
||||||
private final PreProcessorUtil preProcessorUtil;
|
private final PreProcessorUtil preProcessorUtil;
|
||||||
@@ -131,7 +131,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
try {
|
try {
|
||||||
provider.close();
|
provider.close();
|
||||||
} catch (IOException ex) {
|
} catch (IOException ex) {
|
||||||
log.error("Could not close repository service provider", ex);
|
LOG.error("Could not close repository service provider", ex);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -143,7 +143,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public BlameCommandBuilder getBlameCommand() {
|
public BlameCommandBuilder getBlameCommand() {
|
||||||
logger.debug("create blame command for repository {}",
|
LOG.debug("create blame command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new BlameCommandBuilder(cacheManager, provider.getBlameCommand(),
|
return new BlameCommandBuilder(cacheManager, provider.getBlameCommand(),
|
||||||
@@ -158,13 +158,28 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public BranchesCommandBuilder getBranchesCommand() {
|
public BranchesCommandBuilder getBranchesCommand() {
|
||||||
logger.debug("create branches command for repository {}",
|
LOG.debug("create branches command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new BranchesCommandBuilder(cacheManager,
|
return new BranchesCommandBuilder(cacheManager,
|
||||||
provider.getBranchesCommand(), repository);
|
provider.getBranchesCommand(), repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The branch command creates new branches.
|
||||||
|
*
|
||||||
|
* @return instance of {@link BranchCommandBuilder}
|
||||||
|
* @throws CommandNotSupportedException if the command is not supported
|
||||||
|
* by the implementation of the repository service provider.
|
||||||
|
*/
|
||||||
|
public BranchCommandBuilder getBranchCommand() {
|
||||||
|
RepositoryPermissions.push(getRepository()).check();
|
||||||
|
LOG.debug("create branch command for repository {}",
|
||||||
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
|
return new BranchCommandBuilder(provider.getBranchCommand());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The browse command allows browsing of a repository.
|
* The browse command allows browsing of a repository.
|
||||||
*
|
*
|
||||||
@@ -173,7 +188,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public BrowseCommandBuilder getBrowseCommand() {
|
public BrowseCommandBuilder getBrowseCommand() {
|
||||||
logger.debug("create browse command for repository {}",
|
LOG.debug("create browse command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new BrowseCommandBuilder(cacheManager, provider.getBrowseCommand(),
|
return new BrowseCommandBuilder(cacheManager, provider.getBrowseCommand(),
|
||||||
@@ -189,7 +204,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.43
|
* @since 1.43
|
||||||
*/
|
*/
|
||||||
public BundleCommandBuilder getBundleCommand() {
|
public BundleCommandBuilder getBundleCommand() {
|
||||||
logger.debug("create bundle command for repository {}",
|
LOG.debug("create bundle command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new BundleCommandBuilder(provider.getBundleCommand(), repository);
|
return new BundleCommandBuilder(provider.getBundleCommand(), repository);
|
||||||
@@ -203,7 +218,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public CatCommandBuilder getCatCommand() {
|
public CatCommandBuilder getCatCommand() {
|
||||||
logger.debug("create cat command for repository {}",
|
LOG.debug("create cat command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new CatCommandBuilder(provider.getCatCommand());
|
return new CatCommandBuilder(provider.getCatCommand());
|
||||||
@@ -218,7 +233,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public DiffCommandBuilder getDiffCommand() {
|
public DiffCommandBuilder getDiffCommand() {
|
||||||
logger.debug("create diff command for repository {}",
|
LOG.debug("create diff command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures());
|
return new DiffCommandBuilder(provider.getDiffCommand(), provider.getSupportedFeatures());
|
||||||
@@ -234,7 +249,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public IncomingCommandBuilder getIncomingCommand() {
|
public IncomingCommandBuilder getIncomingCommand() {
|
||||||
logger.debug("create incoming command for repository {}",
|
LOG.debug("create incoming command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new IncomingCommandBuilder(cacheManager,
|
return new IncomingCommandBuilder(cacheManager,
|
||||||
@@ -249,7 +264,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public LogCommandBuilder getLogCommand() {
|
public LogCommandBuilder getLogCommand() {
|
||||||
logger.debug("create log command for repository {}",
|
LOG.debug("create log command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
return new LogCommandBuilder(cacheManager, provider.getLogCommand(),
|
||||||
@@ -264,7 +279,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public ModificationsCommandBuilder getModificationsCommand() {
|
public ModificationsCommandBuilder getModificationsCommand() {
|
||||||
logger.debug("create modifications command for repository {}", repository.getNamespaceAndName());
|
LOG.debug("create modifications command for repository {}", repository.getNamespaceAndName());
|
||||||
return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil);
|
return new ModificationsCommandBuilder(provider.getModificationsCommand(),repository, cacheManager.getCache(ModificationsCommandBuilder.CACHE_NAME), preProcessorUtil);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -277,7 +292,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public OutgoingCommandBuilder getOutgoingCommand() {
|
public OutgoingCommandBuilder getOutgoingCommand() {
|
||||||
logger.debug("create outgoing command for repository {}",
|
LOG.debug("create outgoing command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new OutgoingCommandBuilder(cacheManager,
|
return new OutgoingCommandBuilder(cacheManager,
|
||||||
@@ -293,7 +308,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public PullCommandBuilder getPullCommand() {
|
public PullCommandBuilder getPullCommand() {
|
||||||
logger.debug("create pull command for repository {}",
|
LOG.debug("create pull command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new PullCommandBuilder(provider.getPullCommand(), repository);
|
return new PullCommandBuilder(provider.getPullCommand(), repository);
|
||||||
@@ -308,7 +323,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.31
|
* @since 1.31
|
||||||
*/
|
*/
|
||||||
public PushCommandBuilder getPushCommand() {
|
public PushCommandBuilder getPushCommand() {
|
||||||
logger.debug("create push command for repository {}",
|
LOG.debug("create push command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new PushCommandBuilder(provider.getPushCommand());
|
return new PushCommandBuilder(provider.getPushCommand());
|
||||||
@@ -331,7 +346,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* by the implementation of the repository service provider.
|
* by the implementation of the repository service provider.
|
||||||
*/
|
*/
|
||||||
public TagsCommandBuilder getTagsCommand() {
|
public TagsCommandBuilder getTagsCommand() {
|
||||||
logger.debug("create tags command for repository {}",
|
LOG.debug("create tags command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new TagsCommandBuilder(cacheManager, provider.getTagsCommand(),
|
return new TagsCommandBuilder(cacheManager, provider.getTagsCommand(),
|
||||||
@@ -347,7 +362,7 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 1.43
|
* @since 1.43
|
||||||
*/
|
*/
|
||||||
public UnbundleCommandBuilder getUnbundleCommand() {
|
public UnbundleCommandBuilder getUnbundleCommand() {
|
||||||
logger.debug("create unbundle command for repository {}",
|
LOG.debug("create unbundle command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new UnbundleCommandBuilder(provider.getUnbundleCommand(),
|
return new UnbundleCommandBuilder(provider.getUnbundleCommand(),
|
||||||
@@ -364,7 +379,8 @@ public final class RepositoryService implements Closeable {
|
|||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public MergeCommandBuilder getMergeCommand() {
|
public MergeCommandBuilder getMergeCommand() {
|
||||||
logger.debug("create merge command for repository {}",
|
RepositoryPermissions.push(getRepository()).check();
|
||||||
|
LOG.debug("create merge command for repository {}",
|
||||||
repository.getNamespaceAndName());
|
repository.getNamespaceAndName());
|
||||||
|
|
||||||
return new MergeCommandBuilder(provider.getMergeCommand());
|
return new MergeCommandBuilder(provider.getMergeCommand());
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @since 2.0
|
||||||
|
*/
|
||||||
|
public interface BranchCommand {
|
||||||
|
Branch branch(BranchRequest name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
|
import sonia.scm.ExceptionWithContext;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
public class IntegrateChangesFromWorkdirException extends ExceptionWithContext {
|
||||||
|
|
||||||
|
private static final String CODE = "CHRM7IQzo1";
|
||||||
|
|
||||||
|
public IntegrateChangesFromWorkdirException(Repository repository, String message) {
|
||||||
|
super(ContextEntry.ContextBuilder.entity(repository).build(), message);
|
||||||
|
}
|
||||||
|
|
||||||
|
public IntegrateChangesFromWorkdirException(Repository repository, String message, Exception cause) {
|
||||||
|
super(ContextEntry.ContextBuilder.entity(repository).build(), message, cause);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getCode() {
|
||||||
|
return CODE;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -101,6 +101,17 @@ public abstract class RepositoryServiceProvider implements Closeable
|
|||||||
throw new CommandNotSupportedException(Command.BRANCHES);
|
throw new CommandNotSupportedException(Command.BRANCHES);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method description
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public BranchCommand getBranchCommand()
|
||||||
|
{
|
||||||
|
throw new CommandNotSupportedException(Command.BRANCH);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class CloseableWrapper<T extends AutoCloseable> implements AutoCloseable {
|
||||||
|
|
||||||
|
private final T wrapped;
|
||||||
|
private final Consumer<T> cleanup;
|
||||||
|
|
||||||
|
public CloseableWrapper(T wrapped, Consumer<T> cleanup) {
|
||||||
|
this.wrapped = wrapped;
|
||||||
|
this.cleanup = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public T get() { return wrapped; }
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
cleanup.accept(wrapped);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,77 @@
|
|||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
|
||||||
|
public abstract class SimpleWorkdirFactory<R, C> implements WorkdirFactory<R, C> {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(SimpleWorkdirFactory.class);
|
||||||
|
|
||||||
|
private final File poolDirectory;
|
||||||
|
|
||||||
|
public SimpleWorkdirFactory() {
|
||||||
|
this(new File(System.getProperty("scm.workdir" , System.getProperty("java.io.tmpdir")), "scm-work"));
|
||||||
|
}
|
||||||
|
|
||||||
|
public SimpleWorkdirFactory(File poolDirectory) {
|
||||||
|
this.poolDirectory = poolDirectory;
|
||||||
|
if (!poolDirectory.exists() && !poolDirectory.mkdirs()) {
|
||||||
|
throw new IllegalStateException("could not create pool directory " + poolDirectory);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public WorkingCopy<R> createWorkingCopy(C context) {
|
||||||
|
try {
|
||||||
|
File directory = createNewWorkdir();
|
||||||
|
ParentAndClone<R> parentAndClone = cloneRepository(context, directory);
|
||||||
|
return new WorkingCopy<>(parentAndClone.getClone(), parentAndClone.getParent(), this::close, directory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(getScmRepository(context), "could not clone repository in temporary directory", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract Repository getScmRepository(C context);
|
||||||
|
|
||||||
|
@SuppressWarnings("squid:S00112")
|
||||||
|
// We do allow implementations to throw arbitrary exceptions here, so that we can handle them in close
|
||||||
|
protected abstract void closeRepository(R repository) throws Exception;
|
||||||
|
|
||||||
|
protected abstract ParentAndClone<R> cloneRepository(C context, File target) throws IOException;
|
||||||
|
|
||||||
|
private File createNewWorkdir() throws IOException {
|
||||||
|
return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void close(R repository) {
|
||||||
|
try {
|
||||||
|
closeRepository(repository);
|
||||||
|
} catch (Exception e) {
|
||||||
|
logger.warn("could not close temporary repository clone", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static class ParentAndClone<R> {
|
||||||
|
private final R parent;
|
||||||
|
private final R clone;
|
||||||
|
|
||||||
|
public ParentAndClone(R parent, R clone) {
|
||||||
|
this.parent = parent;
|
||||||
|
this.clone = clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public R getParent() {
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
public R getClone() {
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
public interface WorkdirFactory<R, C> {
|
||||||
|
WorkingCopy<R> createWorkingCopy(C context);
|
||||||
|
}
|
||||||
@@ -0,0 +1,49 @@
|
|||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.util.IOUtil;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
|
public class WorkingCopy<R> implements AutoCloseable {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(WorkingCopy.class);
|
||||||
|
|
||||||
|
private final File directory;
|
||||||
|
private final R workingRepository;
|
||||||
|
private final R centralRepository;
|
||||||
|
private final Consumer<R> cleanup;
|
||||||
|
|
||||||
|
public WorkingCopy(R workingRepository, R centralRepository, Consumer<R> cleanup, File directory) {
|
||||||
|
this.directory = directory;
|
||||||
|
this.workingRepository = workingRepository;
|
||||||
|
this.centralRepository = centralRepository;
|
||||||
|
this.cleanup = cleanup;
|
||||||
|
}
|
||||||
|
|
||||||
|
public R getWorkingRepository() {
|
||||||
|
return workingRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public R getCentralRepository() {
|
||||||
|
return centralRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
public File getDirectory() {
|
||||||
|
return directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
try {
|
||||||
|
cleanup.accept(workingRepository);
|
||||||
|
cleanup.accept(centralRepository);
|
||||||
|
IOUtil.delete(directory);
|
||||||
|
} catch (IOException e) {
|
||||||
|
LOG.warn("could not delete temporary workdir '{}'", directory, e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -159,17 +159,17 @@ public final class SearchUtil
|
|||||||
*
|
*
|
||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
public static <T> Collection<T> search(SearchRequest searchRequest,
|
public static <T, R> Collection<R> search(SearchRequest searchRequest,
|
||||||
Collection<T> collection, TransformFilter<T> filter)
|
Collection<T> collection, TransformFilter<T, R> filter)
|
||||||
{
|
{
|
||||||
List<T> items = new ArrayList<T>();
|
List<R> items = new ArrayList<>();
|
||||||
int index = 0;
|
int index = 0;
|
||||||
int counter = 0;
|
int counter = 0;
|
||||||
Iterator<T> it = collection.iterator();
|
Iterator<T> it = collection.iterator();
|
||||||
|
|
||||||
while (it.hasNext())
|
while (it.hasNext())
|
||||||
{
|
{
|
||||||
T item = filter.accept(it.next());
|
R item = filter.accept(it.next());
|
||||||
|
|
||||||
if (item != null)
|
if (item != null)
|
||||||
{
|
{
|
||||||
|
|||||||
32
scm-core/src/main/java/sonia/scm/user/DisplayUser.java
Normal file
32
scm-core/src/main/java/sonia/scm/user/DisplayUser.java
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import sonia.scm.ReducedModelObject;
|
||||||
|
|
||||||
|
public class DisplayUser implements ReducedModelObject {
|
||||||
|
|
||||||
|
private final String id;
|
||||||
|
private final String displayName;
|
||||||
|
private final String mail;
|
||||||
|
|
||||||
|
public static DisplayUser from(User user) {
|
||||||
|
return new DisplayUser(user.getId(), user.getDisplayName(), user.getMail());
|
||||||
|
}
|
||||||
|
|
||||||
|
private DisplayUser(String id, String displayName, String mail) {
|
||||||
|
this.id = id;
|
||||||
|
this.displayName = displayName;
|
||||||
|
this.mail = mail;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDisplayName() {
|
||||||
|
return displayName;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMail() {
|
||||||
|
return mail;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import sonia.scm.DisplayManager;
|
||||||
|
|
||||||
|
public interface UserDisplayManager extends DisplayManager<DisplayUser> {
|
||||||
|
}
|
||||||
@@ -75,14 +75,6 @@ public interface UserManager
|
|||||||
return getDefaultType().equals(user.getType());
|
return getDefaultType().equals(user.getType());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link java.util.Collection} of filtered objects
|
|
||||||
*
|
|
||||||
* @param filter the searched string
|
|
||||||
* @return filtered object from the store
|
|
||||||
*/
|
|
||||||
Collection<User> autocomplete(String filter);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Changes the password of the logged in user.
|
* Changes the password of the logged in user.
|
||||||
* @param oldPassword The current encrypted password of the user.
|
* @param oldPassword The current encrypted password of the user.
|
||||||
|
|||||||
@@ -121,11 +121,6 @@ public class UserManagerDecorator extends ManagerDecorator<User>
|
|||||||
return decorated.getDefaultType();
|
return decorated.getDefaultType();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<User> autocomplete(String filter) {
|
|
||||||
return decorated.autocomplete(filter);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changePasswordForLoggedInUser(String oldPassword, String newPassword) {
|
public void changePasswordForLoggedInUser(String oldPassword, String newPassword) {
|
||||||
decorated.changePasswordForLoggedInUser(oldPassword, newPassword);
|
decorated.changePasswordForLoggedInUser(oldPassword, newPassword);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ public class VndMediaType {
|
|||||||
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
public static final String TAG = PREFIX + "tag" + SUFFIX;
|
||||||
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
public static final String TAG_COLLECTION = PREFIX + "tagCollection" + SUFFIX;
|
||||||
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
public static final String BRANCH = PREFIX + "branch" + SUFFIX;
|
||||||
|
public static final String BRANCH_REQUEST = PREFIX + "branchRequest" + SUFFIX;
|
||||||
public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX;
|
public static final String DIFF = PLAIN_TEXT_PREFIX + "diff" + PLAIN_TEXT_SUFFIX;
|
||||||
public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX;
|
public static final String USER_COLLECTION = PREFIX + "userCollection" + SUFFIX;
|
||||||
public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX;
|
public static final String GROUP_COLLECTION = PREFIX + "groupCollection" + SUFFIX;
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
package sonia.scm.repository.util;
|
||||||
|
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.io.Closeable;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
public class SimpleWorkdirFactoryTest {
|
||||||
|
|
||||||
|
private static final Repository REPOSITORY = new Repository("1", "git", "space", "X");
|
||||||
|
|
||||||
|
private final Closeable parent = mock(Closeable.class);
|
||||||
|
private final Closeable clone = mock(Closeable.class);
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
private SimpleWorkdirFactory<Closeable, Context> simpleWorkdirFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void initFactory() throws IOException {
|
||||||
|
simpleWorkdirFactory = new SimpleWorkdirFactory<Closeable, Context>(temporaryFolder.newFolder()) {
|
||||||
|
@Override
|
||||||
|
protected Repository getScmRepository(Context context) {
|
||||||
|
return REPOSITORY;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void closeRepository(Closeable repository) throws IOException {
|
||||||
|
repository.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected ParentAndClone<Closeable> cloneRepository(Context context, File target) {
|
||||||
|
return new ParentAndClone<>(parent, clone);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateParentAndClone() {
|
||||||
|
Context context = new Context();
|
||||||
|
try (WorkingCopy<Closeable> workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) {
|
||||||
|
assertThat(workingCopy.getCentralRepository()).isSameAs(parent);
|
||||||
|
assertThat(workingCopy.getWorkingRepository()).isSameAs(clone);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCloseParent() throws IOException {
|
||||||
|
Context context = new Context();
|
||||||
|
try (WorkingCopy<Closeable> workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) {}
|
||||||
|
|
||||||
|
verify(parent).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCloseClone() throws IOException {
|
||||||
|
Context context = new Context();
|
||||||
|
try (WorkingCopy<Closeable> workingCopy = simpleWorkdirFactory.createWorkingCopy(context)) {}
|
||||||
|
|
||||||
|
verify(clone).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class Context {}
|
||||||
|
}
|
||||||
@@ -1,25 +0,0 @@
|
|||||||
package sonia.scm.repository;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class CloseableWrapper<C> implements AutoCloseable {
|
|
||||||
|
|
||||||
private final C wrapped;
|
|
||||||
private final Consumer<C> cleanup;
|
|
||||||
|
|
||||||
public CloseableWrapper(C wrapped, Consumer<C> cleanup) {
|
|
||||||
this.wrapped = wrapped;
|
|
||||||
this.cleanup = cleanup;
|
|
||||||
}
|
|
||||||
|
|
||||||
public C get() { return wrapped; }
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void close() {
|
|
||||||
try {
|
|
||||||
cleanup.accept(wrapped);
|
|
||||||
} catch (Throwable t) {
|
|
||||||
throw new RuntimeException(t);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import sonia.scm.repository.spi.GitContext;
|
import sonia.scm.repository.spi.GitContext;
|
||||||
import sonia.scm.repository.spi.WorkingCopy;
|
import sonia.scm.repository.util.WorkdirFactory;
|
||||||
|
|
||||||
public interface GitWorkdirFactory {
|
public interface GitWorkdirFactory extends WorkdirFactory<Repository, GitContext> {
|
||||||
WorkingCopy createWorkingCopy(GitContext gitContext);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,86 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2010, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.eclipse.jgit.transport.PushResult;
|
||||||
|
import org.eclipse.jgit.transport.RemoteRefUpdate;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.GitUtil;
|
||||||
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
import sonia.scm.repository.util.WorkingCopy;
|
||||||
|
|
||||||
|
import java.util.stream.StreamSupport;
|
||||||
|
|
||||||
|
public class GitBranchCommand extends AbstractGitCommand implements BranchCommand {
|
||||||
|
|
||||||
|
private final GitWorkdirFactory workdirFactory;
|
||||||
|
|
||||||
|
GitBranchCommand(GitContext context, Repository repository, GitWorkdirFactory workdirFactory) {
|
||||||
|
super(context, repository);
|
||||||
|
this.workdirFactory = workdirFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Branch branch(BranchRequest request) {
|
||||||
|
try (WorkingCopy<org.eclipse.jgit.lib.Repository> workingCopy = workdirFactory.createWorkingCopy(context)) {
|
||||||
|
Git clone = new Git(workingCopy.getWorkingRepository());
|
||||||
|
if (request.getParentBranch() != null) {
|
||||||
|
clone.checkout().setName(request.getParentBranch());
|
||||||
|
}
|
||||||
|
Ref ref = clone.branchCreate().setName(request.getNewBranch()).call();
|
||||||
|
Iterable<PushResult> call = clone.push().add(request.getNewBranch()).call();
|
||||||
|
StreamSupport.stream(call.spliterator(), false)
|
||||||
|
.flatMap(pushResult -> pushResult.getRemoteUpdates().stream())
|
||||||
|
.filter(remoteRefUpdate -> remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK)
|
||||||
|
.findFirst()
|
||||||
|
.ifPresent(r -> this.handlePushError(r, request, context.getRepository()));
|
||||||
|
return Branch.normalBranch(request.getNewBranch(), GitUtil.getId(ref.getObjectId()));
|
||||||
|
} catch (GitAPIException ex) {
|
||||||
|
throw new InternalRepositoryException(repository, "could not create branch " + request.getNewBranch(), ex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePushError(RemoteRefUpdate remoteRefUpdate, BranchRequest request, Repository repository) {
|
||||||
|
if (remoteRefUpdate.getStatus() != RemoteRefUpdate.Status.OK) {
|
||||||
|
// TODO handle failed remote update
|
||||||
|
throw new IntegrateChangesFromWorkdirException(repository,
|
||||||
|
String.format("Could not push new branch '%s' into central repository", request.getNewBranch()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,11 +34,14 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Function;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import com.google.common.collect.Lists;
|
import com.google.common.base.Strings;
|
||||||
|
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
@@ -46,6 +49,8 @@ import sonia.scm.repository.Repository;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -53,17 +58,10 @@ import java.util.List;
|
|||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
public class GitBranchesCommand extends AbstractGitCommand
|
public class GitBranchesCommand extends AbstractGitCommand implements BranchesCommand {
|
||||||
implements BranchesCommand
|
|
||||||
{
|
private static final Logger LOG = LoggerFactory.getLogger(GitBranchesCommand.class);
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param context
|
|
||||||
* @param repository
|
|
||||||
*/
|
|
||||||
public GitBranchesCommand(GitContext context, Repository repository)
|
public GitBranchesCommand(GitContext context, Repository repository)
|
||||||
{
|
{
|
||||||
super(context, repository);
|
super(context, repository);
|
||||||
@@ -73,38 +71,54 @@ public class GitBranchesCommand extends AbstractGitCommand
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<Branch> getBranches() throws IOException {
|
public List<Branch> getBranches() throws IOException {
|
||||||
List<Branch> branches = null;
|
Git git = createGit();
|
||||||
|
|
||||||
Git git = new Git(open());
|
String defaultBranchName = determineDefaultBranchName(git);
|
||||||
|
|
||||||
try
|
try {
|
||||||
{
|
return git
|
||||||
List<Ref> refs = git.branchList().call();
|
.branchList()
|
||||||
|
.call()
|
||||||
branches = Lists.transform(refs, new Function<Ref, Branch>()
|
.stream()
|
||||||
{
|
.map(ref -> createBranchObject(defaultBranchName, ref))
|
||||||
|
.collect(Collectors.toList());
|
||||||
@Override
|
} catch (GitAPIException ex) {
|
||||||
public Branch apply(Ref ref)
|
|
||||||
{
|
|
||||||
Branch branch = null;
|
|
||||||
String branchName = GitUtil.getBranch(ref);
|
|
||||||
|
|
||||||
if (branchName != null)
|
|
||||||
{
|
|
||||||
branch = new Branch(branchName, GitUtil.getId(ref.getObjectId()));
|
|
||||||
}
|
|
||||||
|
|
||||||
return branch;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
catch (GitAPIException ex)
|
|
||||||
{
|
|
||||||
throw new InternalRepositoryException(repository, "could not read branches", ex);
|
throw new InternalRepositoryException(repository, "could not read branches", ex);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return branches;
|
@VisibleForTesting
|
||||||
|
Git createGit() throws IOException {
|
||||||
|
return new Git(open());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private Branch createBranchObject(String defaultBranchName, Ref ref) {
|
||||||
|
String branchName = GitUtil.getBranch(ref);
|
||||||
|
|
||||||
|
if (branchName == null) {
|
||||||
|
LOG.warn("could not determine branch name for branch name {} at revision {}", ref.getName(), ref.getObjectId());
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
if (branchName.equals(defaultBranchName)) {
|
||||||
|
return Branch.defaultBranch(branchName, GitUtil.getId(ref.getObjectId()));
|
||||||
|
} else {
|
||||||
|
return Branch.normalBranch(branchName, GitUtil.getId(ref.getObjectId()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String determineDefaultBranchName(Git git) {
|
||||||
|
String defaultBranchName = context.getConfig().getDefaultBranch();
|
||||||
|
if (Strings.isNullOrEmpty(defaultBranchName)) {
|
||||||
|
return getRepositoryHeadRef(git).map(GitUtil::getBranch).orElse(null);
|
||||||
|
} else {
|
||||||
|
return defaultBranchName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<Ref> getRepositoryHeadRef(Git git) {
|
||||||
|
return GitUtil.getRepositoryHeadRef(git.getRepository());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import sonia.scm.repository.Person;
|
|||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
import sonia.scm.repository.api.MergeCommandResult;
|
import sonia.scm.repository.api.MergeCommandResult;
|
||||||
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
import sonia.scm.repository.api.MergeDryRunCommandResult;
|
||||||
|
import sonia.scm.repository.util.WorkingCopy;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -46,10 +47,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MergeCommandResult merge(MergeCommandRequest request) {
|
public MergeCommandResult merge(MergeCommandRequest request) {
|
||||||
RepositoryPermissions.push(context.getRepository().getId()).check();
|
try (WorkingCopy<Repository> workingCopy = workdirFactory.createWorkingCopy(context)) {
|
||||||
|
Repository repository = workingCopy.getWorkingRepository();
|
||||||
try (WorkingCopy workingCopy = workdirFactory.createWorkingCopy(context)) {
|
|
||||||
Repository repository = workingCopy.get();
|
|
||||||
logger.debug("cloned repository to folder {}", repository.getWorkTree());
|
logger.debug("cloned repository to folder {}", repository.getWorkTree());
|
||||||
return new MergeWorker(repository, request).merge();
|
return new MergeWorker(repository, request).merge();
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -186,7 +185,8 @@ public class GitMergeCommand extends AbstractGitCommand implements MergeCommand
|
|||||||
try {
|
try {
|
||||||
clone.push().call();
|
clone.push().call();
|
||||||
} catch (GitAPIException e) {
|
} catch (GitAPIException e) {
|
||||||
throw new InternalRepositoryException(context.getRepository(), "could not push merged branch " + target + " to origin", e);
|
throw new IntegrateChangesFromWorkdirException(repository,
|
||||||
|
"could not push merged branch " + target + " into central repository", e);
|
||||||
}
|
}
|
||||||
logger.debug("pushed merged branch {}", target);
|
logger.debug("pushed merged branch {}", target);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -120,6 +120,18 @@ public class GitRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new GitBranchesCommand(context, repository);
|
return new GitBranchesCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method description
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public BranchCommand getBranchCommand()
|
||||||
|
{
|
||||||
|
return new GitBranchCommand(context, repository, handler.getWorkdirFactory());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,64 +4,49 @@ import org.eclipse.jgit.api.Git;
|
|||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||||
import org.eclipse.jgit.util.FileUtils;
|
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.repository.GitWorkdirFactory;
|
import sonia.scm.repository.GitWorkdirFactory;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.util.SimpleWorkdirFactory;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
|
|
||||||
public class SimpleGitWorkdirFactory implements GitWorkdirFactory {
|
public class SimpleGitWorkdirFactory extends SimpleWorkdirFactory<Repository, GitContext> implements GitWorkdirFactory {
|
||||||
|
|
||||||
private static final Logger logger = LoggerFactory.getLogger(SimpleGitWorkdirFactory.class);
|
|
||||||
|
|
||||||
private final File poolDirectory;
|
|
||||||
|
|
||||||
public SimpleGitWorkdirFactory() {
|
public SimpleGitWorkdirFactory() {
|
||||||
this(new File(System.getProperty("java.io.tmpdir"), "scmm-git-pool"));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public SimpleGitWorkdirFactory(File poolDirectory) {
|
SimpleGitWorkdirFactory(File poolDirectory) {
|
||||||
this.poolDirectory = poolDirectory;
|
super(poolDirectory);
|
||||||
poolDirectory.mkdirs();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorkingCopy createWorkingCopy(GitContext gitContext) {
|
@Override
|
||||||
|
public ParentAndClone<Repository> cloneRepository(GitContext context, File target) {
|
||||||
try {
|
try {
|
||||||
Repository clone = cloneRepository(gitContext.getDirectory(), createNewWorkdir());
|
return new ParentAndClone<>(null, Git.cloneRepository()
|
||||||
return new WorkingCopy(clone, this::close);
|
.setURI(createScmTransportProtocolUri(context.getDirectory()))
|
||||||
} catch (GitAPIException e) {
|
|
||||||
throw new InternalRepositoryException(gitContext.getRepository(), "could not clone working copy of repository", e);
|
|
||||||
} catch (IOException e) {
|
|
||||||
throw new InternalRepositoryException(gitContext.getRepository(), "could not create temporary directory for clone of repository", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File createNewWorkdir() throws IOException {
|
|
||||||
return Files.createTempDirectory(poolDirectory.toPath(),"workdir").toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Repository cloneRepository(File bareRepository, File target) throws GitAPIException {
|
|
||||||
return Git.cloneRepository()
|
|
||||||
.setURI(createScmTransportProtocolUri(bareRepository))
|
|
||||||
.setDirectory(target)
|
.setDirectory(target)
|
||||||
.call()
|
.call()
|
||||||
.getRepository();
|
.getRepository());
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(context.getRepository(), "could not clone working copy of repository", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String createScmTransportProtocolUri(File bareRepository) {
|
private String createScmTransportProtocolUri(File bareRepository) {
|
||||||
return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath();
|
return ScmTransportProtocol.NAME + "://" + bareRepository.getAbsolutePath();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void close(Repository repository) {
|
@Override
|
||||||
|
protected void closeRepository(Repository repository) {
|
||||||
|
// we have to check for null here, because we do not create a repository for
|
||||||
|
// the parent in cloneRepository
|
||||||
|
if (repository != null) {
|
||||||
repository.close();
|
repository.close();
|
||||||
try {
|
|
||||||
FileUtils.delete(repository.getWorkTree(), FileUtils.RECURSIVE);
|
|
||||||
} catch (IOException e) {
|
|
||||||
logger.warn("could not delete temporary git workdir '{}'", repository.getWorkTree(), e);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected sonia.scm.repository.Repository getScmRepository(GitContext context) {
|
||||||
|
return context.getRepository();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,12 +0,0 @@
|
|||||||
package sonia.scm.repository.spi;
|
|
||||||
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
|
||||||
import sonia.scm.repository.CloseableWrapper;
|
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
|
|
||||||
public class WorkingCopy extends CloseableWrapper<Repository> {
|
|
||||||
WorkingCopy(Repository wrapped, Consumer<Repository> cleanup) {
|
|
||||||
super(wrapped, cleanup);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.util.CloseableWrapper;
|
||||||
|
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -11,19 +12,20 @@ public class CloseableWrapperTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldExecuteGivenMethodAtClose() {
|
public void shouldExecuteGivenMethodAtClose() {
|
||||||
Consumer<String> wrapped = new Consumer<String>() {
|
Consumer<AutoCloseable> wrapped = new Consumer<AutoCloseable>() {
|
||||||
// no this cannot be replaced with a lambda because otherwise we could not use Mockito#spy
|
// no this cannot be replaced with a lambda because otherwise we could not use Mockito#spy
|
||||||
@Override
|
@Override
|
||||||
public void accept(String s) {
|
public void accept(AutoCloseable s) {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
Consumer<String> closer = spy(wrapped);
|
Consumer<AutoCloseable> closer = spy(wrapped);
|
||||||
|
|
||||||
try (CloseableWrapper<String> wrapper = new CloseableWrapper<>("test", closer)) {
|
AutoCloseable autoCloseable = () -> {};
|
||||||
|
try (CloseableWrapper<AutoCloseable> wrapper = new CloseableWrapper<>(autoCloseable, closer)) {
|
||||||
// nothing to do here
|
// nothing to do here
|
||||||
}
|
}
|
||||||
|
|
||||||
verify(closer).accept("test");
|
verify(closer).accept(autoCloseable);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ public class GitBranchCommand implements BranchCommand
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
Ref ref = git.branchCreate().setName(name).call();
|
Ref ref = git.branchCreate().setName(name).call();
|
||||||
return new Branch(name, GitUtil.getId(ref.getObjectId()));
|
return Branch.normalBranch(name, GitUtil.getId(ref.getObjectId()));
|
||||||
}
|
}
|
||||||
catch (GitAPIException ex)
|
catch (GitAPIException ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -34,11 +34,19 @@ package sonia.scm.repository.spi;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
|
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||||
|
import org.eclipse.jgit.transport.Transport;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
|
import static com.google.inject.util.Providers.of;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
@@ -105,4 +113,5 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private GitContext context;
|
private GitContext context;
|
||||||
|
private ScmTransportProtocol scmTransportProtocol;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||||
|
import org.eclipse.jgit.transport.Transport;
|
||||||
|
import org.junit.rules.ExternalResource;
|
||||||
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
|
import sonia.scm.repository.RepositoryManager;
|
||||||
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
|
||||||
|
import static com.google.inject.util.Providers.of;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
public class BindTransportProtocolRule extends ExternalResource {
|
||||||
|
|
||||||
|
private ScmTransportProtocol scmTransportProtocol;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void before() throws Throwable {
|
||||||
|
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
||||||
|
RepositoryManager repositoryManager = mock(RepositoryManager.class);
|
||||||
|
HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory);
|
||||||
|
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
||||||
|
scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler));
|
||||||
|
|
||||||
|
Transport.register(scmTransportProtocol);
|
||||||
|
|
||||||
|
when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1");
|
||||||
|
when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void after() {
|
||||||
|
Transport.unregister(scmTransportProtocol);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
|
public class GitBranchCommandTest extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateBranch() throws IOException {
|
||||||
|
GitContext context = createContext();
|
||||||
|
|
||||||
|
Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isEmpty();
|
||||||
|
|
||||||
|
BranchRequest branchRequest = new BranchRequest();
|
||||||
|
branchRequest.setNewBranch("new_branch");
|
||||||
|
|
||||||
|
new GitBranchCommand(context, repository, new SimpleGitWorkdirFactory()).branch(branchRequest);
|
||||||
|
|
||||||
|
Assertions.assertThat(readBranches(context)).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Branch> readBranches(GitContext context) throws IOException {
|
||||||
|
return new GitBranchesCommand(context, repository).getBranches();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,117 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.ListBranchCommand;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Arrays.asList;
|
||||||
|
import static java.util.Collections.emptyList;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.mockito.Mockito.lenient;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class GitBranchesCommandTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
GitContext context;
|
||||||
|
@Mock
|
||||||
|
Git git;
|
||||||
|
@Mock
|
||||||
|
ListBranchCommand listBranchCommand;
|
||||||
|
@Mock
|
||||||
|
GitRepositoryConfig gitRepositoryConfig;
|
||||||
|
|
||||||
|
GitBranchesCommand branchesCommand;
|
||||||
|
private Ref master;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initContext() {
|
||||||
|
when(context.getConfig()).thenReturn(gitRepositoryConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void initCommand() {
|
||||||
|
master = createRef("master", "0000");
|
||||||
|
branchesCommand = new GitBranchesCommand(context, new Repository("1", "git", "space", "X")) {
|
||||||
|
@Override
|
||||||
|
Git createGit() {
|
||||||
|
return git;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
Optional<Ref> getRepositoryHeadRef(Git git) {
|
||||||
|
return of(master);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
when(git.branchList()).thenReturn(listBranchCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateEmptyListWithoutBranches() throws IOException, GitAPIException {
|
||||||
|
when(listBranchCommand.call()).thenReturn(emptyList());
|
||||||
|
|
||||||
|
List<Branch> branches = branchesCommand.getBranches();
|
||||||
|
|
||||||
|
assertThat(branches).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMapNormalBranch() throws IOException, GitAPIException {
|
||||||
|
Ref branch = createRef("branch", "1337");
|
||||||
|
when(listBranchCommand.call()).thenReturn(asList(branch));
|
||||||
|
|
||||||
|
List<Branch> branches = branchesCommand.getBranches();
|
||||||
|
|
||||||
|
assertThat(branches).containsExactly(Branch.normalBranch("branch", "1337"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMarkMasterBranchWithMasterFromConfig() throws IOException, GitAPIException {
|
||||||
|
Ref branch = createRef("branch", "1337");
|
||||||
|
when(listBranchCommand.call()).thenReturn(asList(branch));
|
||||||
|
when(gitRepositoryConfig.getDefaultBranch()).thenReturn("branch");
|
||||||
|
|
||||||
|
List<Branch> branches = branchesCommand.getBranches();
|
||||||
|
|
||||||
|
assertThat(branches).containsExactlyInAnyOrder(Branch.defaultBranch("branch", "1337"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMarkMasterBranchWithMasterFromHead() throws IOException, GitAPIException {
|
||||||
|
Ref branch = createRef("branch", "1337");
|
||||||
|
when(listBranchCommand.call()).thenReturn(asList(branch, master));
|
||||||
|
|
||||||
|
List<Branch> branches = branchesCommand.getBranches();
|
||||||
|
|
||||||
|
assertThat(branches).containsExactlyInAnyOrder(
|
||||||
|
Branch.normalBranch("branch", "1337"),
|
||||||
|
Branch.defaultBranch("master", "0000")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Ref createRef(String branchName, String revision) {
|
||||||
|
Ref ref = mock(Ref.class);
|
||||||
|
lenient().when(ref.getName()).thenReturn("refs/heads/" + branchName);
|
||||||
|
ObjectId objectId = mock(ObjectId.class);
|
||||||
|
lenient().when(objectId.name()).thenReturn(revision);
|
||||||
|
lenient().when(ref.getObjectId()).thenReturn(objectId);
|
||||||
|
return ref;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -41,27 +41,8 @@ public class GitMergeCommandTest extends AbstractGitCommandTestBase {
|
|||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public ShiroRule shiro = new ShiroRule();
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
@Rule
|
||||||
private ScmTransportProtocol scmTransportProtocol;
|
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||||
|
|
||||||
@Before
|
|
||||||
public void bindScmProtocol() {
|
|
||||||
HookContextFactory hookContextFactory = new HookContextFactory(mock(PreProcessorUtil.class));
|
|
||||||
RepositoryManager repositoryManager = mock(RepositoryManager.class);
|
|
||||||
HookEventFacade hookEventFacade = new HookEventFacade(of(repositoryManager), hookContextFactory);
|
|
||||||
GitRepositoryHandler gitRepositoryHandler = mock(GitRepositoryHandler.class);
|
|
||||||
scmTransportProtocol = new ScmTransportProtocol(of(hookEventFacade), of(gitRepositoryHandler));
|
|
||||||
|
|
||||||
Transport.register(scmTransportProtocol);
|
|
||||||
|
|
||||||
when(gitRepositoryHandler.getRepositoryId(any())).thenReturn("1");
|
|
||||||
when(repositoryManager.get("1")).thenReturn(new sonia.scm.repository.Repository());
|
|
||||||
}
|
|
||||||
|
|
||||||
@After
|
|
||||||
public void unregisterScmProtocol() {
|
|
||||||
Transport.unregister(scmTransportProtocol);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldDetectMergeableBranches() {
|
public void shouldDetectMergeableBranches() {
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
import org.eclipse.jgit.transport.ScmTransportProtocol;
|
||||||
import org.eclipse.jgit.transport.Transport;
|
import org.eclipse.jgit.transport.Transport;
|
||||||
@@ -12,6 +11,7 @@ import sonia.scm.repository.GitRepositoryHandler;
|
|||||||
import sonia.scm.repository.PreProcessorUtil;
|
import sonia.scm.repository.PreProcessorUtil;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.api.HookContextFactory;
|
import sonia.scm.repository.api.HookContextFactory;
|
||||||
|
import sonia.scm.repository.util.WorkingCopy;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -44,39 +44,29 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
|||||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
File masterRepo = createRepositoryDirectory();
|
File masterRepo = createRepositoryDirectory();
|
||||||
|
|
||||||
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
|
|
||||||
assertThat(workingCopy.get().getDirectory())
|
assertThat(workingCopy.getDirectory())
|
||||||
.exists()
|
.exists()
|
||||||
.isNotEqualTo(masterRepo)
|
.isNotEqualTo(masterRepo)
|
||||||
.isDirectory();
|
.isDirectory();
|
||||||
assertThat(new File(workingCopy.get().getWorkTree(), "a.txt"))
|
assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt"))
|
||||||
.exists()
|
.exists()
|
||||||
.isFile()
|
.isFile()
|
||||||
.hasContent("a\nline for blame");
|
.hasContent("a\nline for blame");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cloneFromPoolShouldBeClosed() throws IOException {
|
|
||||||
PoolWithSpy factory = new PoolWithSpy(temporaryFolder.newFolder());
|
|
||||||
|
|
||||||
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
|
||||||
assertThat(workingCopy).isNotNull();
|
|
||||||
}
|
|
||||||
verify(factory.createdClone).close();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cloneFromPoolShouldNotBeReused() throws IOException {
|
public void cloneFromPoolShouldNotBeReused() throws IOException {
|
||||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
|
|
||||||
File firstDirectory;
|
File firstDirectory;
|
||||||
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
firstDirectory = workingCopy.get().getDirectory();
|
firstDirectory = workingCopy.getDirectory();
|
||||||
}
|
}
|
||||||
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
File secondDirectory = workingCopy.get().getDirectory();
|
File secondDirectory = workingCopy.getDirectory();
|
||||||
assertThat(secondDirectory).isNotEqualTo(firstDirectory);
|
assertThat(secondDirectory).isNotEqualTo(firstDirectory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -86,23 +76,9 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
|||||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(temporaryFolder.newFolder());
|
||||||
|
|
||||||
File directory;
|
File directory;
|
||||||
try (WorkingCopy workingCopy = factory.createWorkingCopy(createContext())) {
|
try (WorkingCopy<Repository> workingCopy = factory.createWorkingCopy(createContext())) {
|
||||||
directory = workingCopy.get().getWorkTree();
|
directory = workingCopy.getWorkingRepository().getWorkTree();
|
||||||
}
|
}
|
||||||
assertThat(directory).doesNotExist();
|
assertThat(directory).doesNotExist();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class PoolWithSpy extends SimpleGitWorkdirFactory {
|
|
||||||
PoolWithSpy(File poolDirectory) {
|
|
||||||
super(poolDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
Repository createdClone;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Repository cloneRepository(File bareRepository, File destination) throws GitAPIException {
|
|
||||||
createdClone = spy(super.cloneRepository(bareRepository, destination));
|
|
||||||
return createdClone;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
mock-maker-inline
|
||||||
@@ -28,7 +28,6 @@
|
|||||||
</exclusion>
|
</exclusion>
|
||||||
</exclusions>
|
</exclusions>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
|||||||
@@ -35,15 +35,9 @@ package sonia.scm.repository;
|
|||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.Strings;
|
import com.google.inject.ProvisionException;
|
||||||
|
|
||||||
import org.apache.shiro.codec.Base64;
|
|
||||||
|
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import sonia.scm.security.CipherUtil;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.web.HgUtil;
|
import sonia.scm.web.HgUtil;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -59,6 +53,8 @@ import javax.servlet.http.HttpServletRequest;
|
|||||||
public final class HgEnvironment
|
public final class HgEnvironment
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HgEnvironment.class);
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
public static final String ENV_PYTHON_PATH = "PYTHONPATH";
|
||||||
|
|
||||||
@@ -68,14 +64,7 @@ public final class HgEnvironment
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String ENV_URL = "SCM_URL";
|
private static final String ENV_URL = "SCM_URL";
|
||||||
|
|
||||||
/** Field description */
|
private static final String SCM_BEARER_TOKEN = "SCM_BEARER_TOKEN";
|
||||||
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* the logger for HgEnvironment
|
|
||||||
*/
|
|
||||||
private static final Logger logger =
|
|
||||||
LoggerFactory.getLogger(HgEnvironment.class);
|
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
@@ -87,6 +76,20 @@ public final class HgEnvironment
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Method description
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param environment
|
||||||
|
* @param handler
|
||||||
|
* @param hookManager
|
||||||
|
*/
|
||||||
|
public static void prepareEnvironment(Map<String, String> environment,
|
||||||
|
HgRepositoryHandler handler, HgHookManager hookManager)
|
||||||
|
{
|
||||||
|
prepareEnvironment(environment, handler, hookManager, null);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -105,65 +108,20 @@ public final class HgEnvironment
|
|||||||
if (request != null)
|
if (request != null)
|
||||||
{
|
{
|
||||||
hookUrl = hookManager.createUrl(request);
|
hookUrl = hookManager.createUrl(request);
|
||||||
environment.put(SCM_CREDENTIALS, getCredentials(request));
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
hookUrl = hookManager.createUrl();
|
hookUrl = hookManager.createUrl();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
String credentials = hookManager.getCredentials();
|
||||||
|
environment.put(SCM_BEARER_TOKEN, credentials);
|
||||||
|
} catch (ProvisionException e) {
|
||||||
|
LOG.debug("could not create bearer token; looks like currently we are not in a request", e);
|
||||||
|
}
|
||||||
environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig()));
|
environment.put(ENV_PYTHON_PATH, HgUtil.getPythonPath(handler.getConfig()));
|
||||||
environment.put(ENV_URL, hookUrl);
|
environment.put(ENV_URL, hookUrl);
|
||||||
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
environment.put(ENV_CHALLENGE, hookManager.getChallenge());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param environment
|
|
||||||
* @param handler
|
|
||||||
* @param hookManager
|
|
||||||
*/
|
|
||||||
public static void prepareEnvironment(Map<String, String> environment,
|
|
||||||
HgRepositoryHandler handler, HgHookManager hookManager)
|
|
||||||
{
|
|
||||||
prepareEnvironment(environment, handler, hookManager, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
private static String getCredentials(HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String credentials = null;
|
|
||||||
String header = request.getHeader(HttpUtil.HEADER_AUTHORIZATION);
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(header))
|
|
||||||
{
|
|
||||||
String[] parts = header.split("\\s+");
|
|
||||||
|
|
||||||
if (parts.length > 0)
|
|
||||||
{
|
|
||||||
CipherUtil cu = CipherUtil.getInstance();
|
|
||||||
|
|
||||||
credentials = cu.encode(Base64.decodeToString(parts[1]));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.warn("invalid basic authentication header");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.warn("could not find authentication header on request");
|
|
||||||
}
|
|
||||||
|
|
||||||
return Strings.nullToEmpty(credentials);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -47,6 +47,9 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.config.ScmConfigurationChangedEvent;
|
import sonia.scm.config.ScmConfigurationChangedEvent;
|
||||||
import sonia.scm.net.ahc.AdvancedHttpClient;
|
import sonia.scm.net.ahc.AdvancedHttpClient;
|
||||||
|
import sonia.scm.security.AccessToken;
|
||||||
|
import sonia.scm.security.AccessTokenBuilderFactory;
|
||||||
|
import sonia.scm.security.CipherUtil;
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
@@ -78,19 +81,20 @@ public class HgHookManager
|
|||||||
/**
|
/**
|
||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param configuration
|
* @param configuration
|
||||||
* @param httpServletRequestProvider
|
* @param httpServletRequestProvider
|
||||||
* @param httpClient
|
* @param httpClient
|
||||||
|
* @param accessTokenBuilderFactory
|
||||||
*/
|
*/
|
||||||
@Inject
|
@Inject
|
||||||
public HgHookManager(ScmConfiguration configuration,
|
public HgHookManager(ScmConfiguration configuration,
|
||||||
Provider<HttpServletRequest> httpServletRequestProvider,
|
Provider<HttpServletRequest> httpServletRequestProvider,
|
||||||
AdvancedHttpClient httpClient)
|
AdvancedHttpClient httpClient, AccessTokenBuilderFactory accessTokenBuilderFactory)
|
||||||
{
|
{
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.httpServletRequestProvider = httpServletRequestProvider;
|
this.httpServletRequestProvider = httpServletRequestProvider;
|
||||||
this.httpClient = httpClient;
|
this.httpClient = httpClient;
|
||||||
|
this.accessTokenBuilderFactory = accessTokenBuilderFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -192,6 +196,13 @@ public class HgHookManager
|
|||||||
return this.challenge.equals(challenge);
|
return this.challenge.equals(challenge);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getCredentials()
|
||||||
|
{
|
||||||
|
AccessToken accessToken = accessTokenBuilderFactory.create().build();
|
||||||
|
|
||||||
|
return CipherUtil.getInstance().encode(accessToken.compact());
|
||||||
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -391,4 +402,6 @@ public class HgHookManager
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private Provider<HttpServletRequest> httpServletRequestProvider;
|
private Provider<HttpServletRequest> httpServletRequestProvider;
|
||||||
|
|
||||||
|
private final AccessTokenBuilderFactory accessTokenBuilderFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,6 +53,7 @@ import sonia.scm.io.INISection;
|
|||||||
import sonia.scm.plugin.Extension;
|
import sonia.scm.plugin.Extension;
|
||||||
import sonia.scm.plugin.PluginLoader;
|
import sonia.scm.plugin.PluginLoader;
|
||||||
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
import sonia.scm.repository.spi.HgRepositoryServiceProvider;
|
||||||
|
import sonia.scm.repository.spi.HgWorkdirFactory;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.util.IOUtil;
|
import sonia.scm.util.IOUtil;
|
||||||
import sonia.scm.util.SystemUtil;
|
import sonia.scm.util.SystemUtil;
|
||||||
@@ -113,10 +114,11 @@ public class HgRepositoryHandler
|
|||||||
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
public HgRepositoryHandler(ConfigurationStoreFactory storeFactory,
|
||||||
Provider<HgContext> hgContextProvider,
|
Provider<HgContext> hgContextProvider,
|
||||||
RepositoryLocationResolver repositoryLocationResolver,
|
RepositoryLocationResolver repositoryLocationResolver,
|
||||||
PluginLoader pluginLoader)
|
PluginLoader pluginLoader, HgWorkdirFactory workdirFactory)
|
||||||
{
|
{
|
||||||
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
super(storeFactory, repositoryLocationResolver, pluginLoader);
|
||||||
this.hgContextProvider = hgContextProvider;
|
this.hgContextProvider = hgContextProvider;
|
||||||
|
this.workdirFactory = workdirFactory;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -408,6 +410,10 @@ public class HgRepositoryHandler
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public HgWorkdirFactory getWorkdirFactory() {
|
||||||
|
return workdirFactory;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
@@ -415,4 +421,6 @@ public class HgRepositoryHandler
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private JAXBContext jaxbContext;
|
private JAXBContext jaxbContext;
|
||||||
|
|
||||||
|
private final HgWorkdirFactory workdirFactory;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,114 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2014, Sebastian Sdorra
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are met:
|
||||||
|
*
|
||||||
|
* 1. Redistributions of source code must retain the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer.
|
||||||
|
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
* this list of conditions and the following disclaimer in the documentation
|
||||||
|
* and/or other materials provided with the distribution.
|
||||||
|
* 3. Neither the name of SCM-Manager; nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from this
|
||||||
|
* software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||||
|
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||||
|
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||||
|
* DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
|
||||||
|
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||||
|
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||||
|
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*
|
||||||
|
* http://bitbucket.org/sdorra/scm-manager
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.aragost.javahg.Changeset;
|
||||||
|
import com.aragost.javahg.commands.CommitCommand;
|
||||||
|
import com.aragost.javahg.commands.PullCommand;
|
||||||
|
import com.aragost.javahg.commands.UpdateCommand;
|
||||||
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
import sonia.scm.repository.util.WorkingCopy;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mercurial implementation of the {@link BranchCommand}.
|
||||||
|
* Note that this creates an empty commit to "persist" the new branch.
|
||||||
|
*/
|
||||||
|
public class HgBranchCommand extends AbstractCommand implements BranchCommand {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(HgBranchCommand.class);
|
||||||
|
|
||||||
|
private final HgWorkdirFactory workdirFactory;
|
||||||
|
|
||||||
|
HgBranchCommand(HgCommandContext context, Repository repository, HgWorkdirFactory workdirFactory) {
|
||||||
|
super(context, repository);
|
||||||
|
this.workdirFactory = workdirFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Branch branch(BranchRequest request) {
|
||||||
|
try (WorkingCopy<com.aragost.javahg.Repository> workingCopy = workdirFactory.createWorkingCopy(getContext())) {
|
||||||
|
com.aragost.javahg.Repository repository = workingCopy.getWorkingRepository();
|
||||||
|
|
||||||
|
checkoutParentBranchIfSpecified(request, repository);
|
||||||
|
|
||||||
|
Changeset emptyChangeset = createNewBranchWithEmptyCommit(request, repository);
|
||||||
|
|
||||||
|
LOG.debug("Created new branch '{}' in repository {} with changeset {}",
|
||||||
|
request.getNewBranch(), getRepository().getNamespaceAndName(), emptyChangeset.getNode());
|
||||||
|
|
||||||
|
pullNewBranchIntoCentralRepository(request, workingCopy);
|
||||||
|
|
||||||
|
return Branch.normalBranch(request.getNewBranch(), emptyChangeset.getNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkoutParentBranchIfSpecified(BranchRequest request, com.aragost.javahg.Repository repository) {
|
||||||
|
if (request.getParentBranch() != null) {
|
||||||
|
try {
|
||||||
|
UpdateCommand.on(repository).rev(request.getParentBranch()).execute();
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(getRepository(), "Could not check out parent branch " + request.getParentBranch(), e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Changeset createNewBranchWithEmptyCommit(BranchRequest request, com.aragost.javahg.Repository repository) {
|
||||||
|
com.aragost.javahg.commands.BranchCommand.on(repository).set(request.getNewBranch());
|
||||||
|
User currentUser = SecurityUtils.getSubject().getPrincipals().oneByType(User.class);
|
||||||
|
return CommitCommand
|
||||||
|
.on(repository)
|
||||||
|
.user(String.format("%s <%s>", currentUser.getDisplayName(), currentUser.getMail()))
|
||||||
|
.message("Create new branch " + request.getNewBranch())
|
||||||
|
.execute();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void pullNewBranchIntoCentralRepository(BranchRequest request, WorkingCopy<com.aragost.javahg.Repository> workingCopy) {
|
||||||
|
try {
|
||||||
|
PullCommand pullCommand = PullCommand.on(workingCopy.getCentralRepository());
|
||||||
|
workdirFactory.configure(pullCommand);
|
||||||
|
pullCommand.execute(workingCopy.getDirectory().getAbsolutePath());
|
||||||
|
} catch (Exception e) {
|
||||||
|
// TODO handle failed update
|
||||||
|
throw new IntegrateChangesFromWorkdirException(getRepository(),
|
||||||
|
String.format("Could not pull new branch '%s' into central repository", request.getNewBranch()),
|
||||||
|
e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -53,6 +53,8 @@ public class HgBranchesCommand extends AbstractCommand
|
|||||||
implements BranchesCommand
|
implements BranchesCommand
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String DEFAULT_BRANCH_NAME = "default";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
@@ -88,7 +90,11 @@ public class HgBranchesCommand extends AbstractCommand
|
|||||||
node = changeset.getNode();
|
node = changeset.getNode();
|
||||||
}
|
}
|
||||||
|
|
||||||
return new Branch(hgBranch.getName(), node);
|
if (DEFAULT_BRANCH_NAME.equals(hgBranch.getName())) {
|
||||||
|
return Branch.defaultBranch(hgBranch.getName(), node);
|
||||||
|
} else {
|
||||||
|
return Branch.normalBranch(hgBranch.getName(), node);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -49,6 +49,8 @@ import sonia.scm.web.HgUtil;
|
|||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -66,7 +68,6 @@ public class HgCommandContext implements Closeable
|
|||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param hookManager
|
* @param hookManager
|
||||||
* @param handler
|
* @param handler
|
||||||
* @param repository
|
* @param repository
|
||||||
@@ -84,26 +85,26 @@ public class HgCommandContext implements Closeable
|
|||||||
* Constructs ...
|
* Constructs ...
|
||||||
*
|
*
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* @param hookManager
|
* @param hookManager
|
||||||
* @param hanlder
|
* @param handler
|
||||||
* @param repository
|
* @param repository
|
||||||
* @param directory
|
* @param directory
|
||||||
* @param pending
|
* @param pending
|
||||||
*/
|
*/
|
||||||
public HgCommandContext(HgHookManager hookManager,
|
public HgCommandContext(HgHookManager hookManager,
|
||||||
HgRepositoryHandler hanlder, sonia.scm.repository.Repository repository,
|
HgRepositoryHandler handler, sonia.scm.repository.Repository repository,
|
||||||
File directory, boolean pending)
|
File directory, boolean pending)
|
||||||
{
|
{
|
||||||
this.hookManager = hookManager;
|
this.hookManager = hookManager;
|
||||||
this.hanlder = hanlder;
|
this.handler = handler;
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
|
this.scmRepository = repository;
|
||||||
this.encoding = repository.getProperty(PROPERTY_ENCODING);
|
this.encoding = repository.getProperty(PROPERTY_ENCODING);
|
||||||
this.pending = pending;
|
this.pending = pending;
|
||||||
|
|
||||||
if (Strings.isNullOrEmpty(encoding))
|
if (Strings.isNullOrEmpty(encoding))
|
||||||
{
|
{
|
||||||
encoding = hanlder.getConfig().getEncoding();
|
encoding = handler.getConfig().getEncoding();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -134,13 +135,18 @@ public class HgCommandContext implements Closeable
|
|||||||
{
|
{
|
||||||
if (repository == null)
|
if (repository == null)
|
||||||
{
|
{
|
||||||
repository = HgUtil.open(hanlder, hookManager, directory, encoding,
|
repository = HgUtil.open(handler, hookManager, directory, encoding, pending);
|
||||||
pending);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Repository openWithSpecialEnvironment(BiConsumer<sonia.scm.repository.Repository, Map<String, String>> prepareEnvironment)
|
||||||
|
{
|
||||||
|
return HgUtil.open(handler, directory, encoding,
|
||||||
|
pending, environment -> prepareEnvironment.accept(scmRepository, environment));
|
||||||
|
}
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -151,7 +157,11 @@ public class HgCommandContext implements Closeable
|
|||||||
*/
|
*/
|
||||||
public HgConfig getConfig()
|
public HgConfig getConfig()
|
||||||
{
|
{
|
||||||
return hanlder.getConfig();
|
return handler.getConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public sonia.scm.repository.Repository getScmRepository() {
|
||||||
|
return scmRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
@@ -163,7 +173,7 @@ public class HgCommandContext implements Closeable
|
|||||||
private String encoding;
|
private String encoding;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private HgRepositoryHandler hanlder;
|
private HgRepositoryHandler handler;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private HgHookManager hookManager;
|
private HgHookManager hookManager;
|
||||||
@@ -173,4 +183,6 @@ public class HgCommandContext implements Closeable
|
|||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private Repository repository;
|
private Repository repository;
|
||||||
|
|
||||||
|
private final sonia.scm.repository.Repository scmRepository;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,6 +126,11 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
return new HgBranchesCommand(context, repository);
|
return new HgBranchesCommand(context, repository);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BranchCommand getBranchCommand() {
|
||||||
|
return new HgBranchCommand(context, repository, handler.getWorkdirFactory());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -192,6 +197,7 @@ public class HgRepositoryServiceProvider extends RepositoryServiceProvider
|
|||||||
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
* @return the corresponding {@link ModificationsCommand} implemented from the Plugins
|
||||||
* @throws CommandNotSupportedException if there is no Implementation
|
* @throws CommandNotSupportedException if there is no Implementation
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public ModificationsCommand getModificationsCommand() {
|
public ModificationsCommand getModificationsCommand() {
|
||||||
return new HgModificationsCommand(context,repository);
|
return new HgModificationsCommand(context,repository);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,9 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.aragost.javahg.Repository;
|
||||||
|
import com.aragost.javahg.commands.PullCommand;
|
||||||
|
import sonia.scm.repository.util.WorkdirFactory;
|
||||||
|
|
||||||
|
public interface HgWorkdirFactory extends WorkdirFactory<Repository, HgCommandContext> {
|
||||||
|
void configure(PullCommand pullCommand);
|
||||||
|
}
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.aragost.javahg.Repository;
|
||||||
|
import com.aragost.javahg.commands.CloneCommand;
|
||||||
|
import com.aragost.javahg.commands.PullCommand;
|
||||||
|
import sonia.scm.repository.util.SimpleWorkdirFactory;
|
||||||
|
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Provider;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.BiConsumer;
|
||||||
|
|
||||||
|
public class SimpleHgWorkdirFactory extends SimpleWorkdirFactory<Repository, HgCommandContext> implements HgWorkdirFactory {
|
||||||
|
|
||||||
|
private final Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public SimpleHgWorkdirFactory(Provider<HgRepositoryEnvironmentBuilder> hgRepositoryEnvironmentBuilder) {
|
||||||
|
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||||
|
}
|
||||||
|
@Override
|
||||||
|
public ParentAndClone<Repository> cloneRepository(HgCommandContext context, File target) throws IOException {
|
||||||
|
BiConsumer<sonia.scm.repository.Repository, Map<String, String>> repositoryMapBiConsumer =
|
||||||
|
(repository, environment) -> hgRepositoryEnvironmentBuilder.get().buildFor(repository, null, environment);
|
||||||
|
Repository centralRepository = context.openWithSpecialEnvironment(repositoryMapBiConsumer);
|
||||||
|
CloneCommand.on(centralRepository).execute(target.getAbsolutePath());
|
||||||
|
return new ParentAndClone<>(centralRepository, Repository.open(target));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void closeRepository(Repository repository) {
|
||||||
|
repository.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected sonia.scm.repository.Repository getScmRepository(HgCommandContext context) {
|
||||||
|
return context.getScmRepository();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void configure(PullCommand pullCommand) {
|
||||||
|
pullCommand.cmdAppend("--config", "hooks.changegroup.scm=python:scmhooks.postHook");
|
||||||
|
pullCommand.cmdAppend("--config", "hooks.pretxnchangegroup.scm=python:scmhooks.preHook");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -34,7 +34,6 @@
|
|||||||
package sonia.scm.web;
|
package sonia.scm.web;
|
||||||
|
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.base.Strings;
|
|
||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@@ -42,16 +41,12 @@ import org.slf4j.LoggerFactory;
|
|||||||
import sonia.scm.SCMContext;
|
import sonia.scm.SCMContext;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.repository.HgConfig;
|
import sonia.scm.repository.HgConfig;
|
||||||
import sonia.scm.repository.HgEnvironment;
|
|
||||||
import sonia.scm.repository.HgHookManager;
|
|
||||||
import sonia.scm.repository.HgPythonScript;
|
import sonia.scm.repository.HgPythonScript;
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryRequestListenerUtil;
|
import sonia.scm.repository.RepositoryRequestListenerUtil;
|
||||||
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
import sonia.scm.repository.spi.ScmProviderHttpServlet;
|
||||||
import sonia.scm.security.CipherUtil;
|
|
||||||
import sonia.scm.util.AssertUtil;
|
import sonia.scm.util.AssertUtil;
|
||||||
import sonia.scm.util.HttpUtil;
|
|
||||||
import sonia.scm.web.cgi.CGIExecutor;
|
import sonia.scm.web.cgi.CGIExecutor;
|
||||||
import sonia.scm.web.cgi.CGIExecutorFactory;
|
import sonia.scm.web.cgi.CGIExecutorFactory;
|
||||||
import sonia.scm.web.cgi.EnvList;
|
import sonia.scm.web.cgi.EnvList;
|
||||||
@@ -62,14 +57,12 @@ import java.io.File;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
import java.util.Enumeration;
|
import java.util.Enumeration;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServlet;
|
import javax.servlet.http.HttpServlet;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import javax.servlet.http.HttpSession;
|
import javax.servlet.http.HttpSession;
|
||||||
import java.util.Base64;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -79,25 +72,9 @@ import java.util.Base64;
|
|||||||
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
||||||
{
|
{
|
||||||
|
|
||||||
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
public static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
|
||||||
|
|
||||||
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
public static final String ENV_SESSION_PREFIX = "SCM_";
|
public static final String ENV_SESSION_PREFIX = "SCM_";
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private static final String SCM_CREDENTIALS = "SCM_CREDENTIALS";
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final long serialVersionUID = -3492811300905099810L;
|
private static final long serialVersionUID = -3492811300905099810L;
|
||||||
|
|
||||||
@@ -107,30 +84,18 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
//~--- constructors ---------------------------------------------------------
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param cgiExecutorFactory
|
|
||||||
* @param configuration
|
|
||||||
* @param handler
|
|
||||||
* @param hookManager
|
|
||||||
* @param requestListenerUtil
|
|
||||||
*/
|
|
||||||
@Inject
|
@Inject
|
||||||
public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory,
|
public HgCGIServlet(CGIExecutorFactory cgiExecutorFactory,
|
||||||
ScmConfiguration configuration,
|
ScmConfiguration configuration,
|
||||||
HgRepositoryHandler handler, HgHookManager hookManager,
|
HgRepositoryHandler handler,
|
||||||
RepositoryRequestListenerUtil requestListenerUtil)
|
RepositoryRequestListenerUtil requestListenerUtil,
|
||||||
|
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder)
|
||||||
{
|
{
|
||||||
this.cgiExecutorFactory = cgiExecutorFactory;
|
this.cgiExecutorFactory = cgiExecutorFactory;
|
||||||
this.configuration = configuration;
|
this.configuration = configuration;
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
this.hookManager = hookManager;
|
|
||||||
this.requestListenerUtil = requestListenerUtil;
|
this.requestListenerUtil = requestListenerUtil;
|
||||||
|
this.hgRepositoryEnvironmentBuilder = hgRepositoryEnvironmentBuilder;
|
||||||
this.exceptionHandler = new HgCGIExceptionHandler();
|
this.exceptionHandler = new HgCGIExceptionHandler();
|
||||||
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
this.command = HgPythonScript.HGWEB.getFile(SCMContext.getContext());
|
||||||
}
|
}
|
||||||
@@ -163,40 +128,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param env
|
|
||||||
* @param request
|
|
||||||
*/
|
|
||||||
private void addCredentials(EnvList env, HttpServletRequest request)
|
|
||||||
{
|
|
||||||
String authorization = request.getHeader(HttpUtil.HEADER_AUTHORIZATION);
|
|
||||||
|
|
||||||
if (!Strings.isNullOrEmpty(authorization))
|
|
||||||
{
|
|
||||||
if (authorization.startsWith(HttpUtil.AUTHORIZATION_SCHEME_BASIC))
|
|
||||||
{
|
|
||||||
String encodedUserInfo =
|
|
||||||
authorization.substring(
|
|
||||||
HttpUtil.AUTHORIZATION_SCHEME_BASIC.length()).trim();
|
|
||||||
// TODO check encoding of user-agent ?
|
|
||||||
String userInfo = new String(Base64.getDecoder().decode(encodedUserInfo));
|
|
||||||
|
|
||||||
env.set(SCM_CREDENTIALS, CipherUtil.getInstance().encode(userInfo));
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.warn("unknow authentication scheme used");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.trace("no authorization header found");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -262,7 +193,6 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
HttpServletResponse response, Repository repository)
|
HttpServletResponse response, Repository repository)
|
||||||
throws IOException, ServletException
|
throws IOException, ServletException
|
||||||
{
|
{
|
||||||
File directory = handler.getDirectory(repository.getId());
|
|
||||||
CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration,
|
CGIExecutor executor = cgiExecutorFactory.createExecutor(configuration,
|
||||||
getServletContext(), request, response);
|
getServletContext(), request, response);
|
||||||
|
|
||||||
@@ -271,41 +201,7 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
executor.setExceptionHandler(exceptionHandler);
|
executor.setExceptionHandler(exceptionHandler);
|
||||||
executor.setStatusCodeHandler(exceptionHandler);
|
executor.setStatusCodeHandler(exceptionHandler);
|
||||||
executor.setContentLengthWorkaround(true);
|
executor.setContentLengthWorkaround(true);
|
||||||
executor.getEnvironment().set(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
hgRepositoryEnvironmentBuilder.buildFor(repository, request, executor.getEnvironment().asMutableMap());
|
||||||
executor.getEnvironment().set(ENV_REPOSITORY_ID, repository.getId());
|
|
||||||
executor.getEnvironment().set(ENV_REPOSITORY_PATH,
|
|
||||||
directory.getAbsolutePath());
|
|
||||||
|
|
||||||
// add hook environment
|
|
||||||
Map<String, String> environment = executor.getEnvironment().asMutableMap();
|
|
||||||
if (handler.getConfig().isDisableHookSSLValidation()) {
|
|
||||||
// disable ssl validation
|
|
||||||
// Issue 959: https://goo.gl/zH5eY8
|
|
||||||
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
|
||||||
}
|
|
||||||
|
|
||||||
// enable experimental httppostargs protocol of mercurial
|
|
||||||
// Issue 970: https://goo.gl/poascp
|
|
||||||
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
|
||||||
|
|
||||||
//J-
|
|
||||||
HgEnvironment.prepareEnvironment(
|
|
||||||
environment,
|
|
||||||
handler,
|
|
||||||
hookManager,
|
|
||||||
request
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
|
|
||||||
addCredentials(executor.getEnvironment(), request);
|
|
||||||
|
|
||||||
// unused ???
|
|
||||||
HttpSession session = request.getSession(false);
|
|
||||||
|
|
||||||
if (session != null)
|
|
||||||
{
|
|
||||||
passSessionAttributes(executor.getEnvironment(), session);
|
|
||||||
}
|
|
||||||
|
|
||||||
String interpreter = getInterpreter();
|
String interpreter = getInterpreter();
|
||||||
|
|
||||||
@@ -358,9 +254,8 @@ public class HgCGIServlet extends HttpServlet implements ScmProviderHttpServlet
|
|||||||
/** Field description */
|
/** Field description */
|
||||||
private final HgRepositoryHandler handler;
|
private final HgRepositoryHandler handler;
|
||||||
|
|
||||||
/** Field description */
|
|
||||||
private final HgHookManager hookManager;
|
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final RepositoryRequestListenerUtil requestListenerUtil;
|
private final RepositoryRequestListenerUtil requestListenerUtil;
|
||||||
|
|
||||||
|
private final HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,6 +42,7 @@ import com.google.inject.Inject;
|
|||||||
import com.google.inject.Provider;
|
import com.google.inject.Provider;
|
||||||
import com.google.inject.Singleton;
|
import com.google.inject.Singleton;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
|
import org.apache.shiro.authc.AuthenticationToken;
|
||||||
import org.apache.shiro.subject.Subject;
|
import org.apache.shiro.subject.Subject;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
@@ -54,8 +55,8 @@ import sonia.scm.repository.api.HgHookMessage;
|
|||||||
import sonia.scm.repository.api.HgHookMessage.Severity;
|
import sonia.scm.repository.api.HgHookMessage.Severity;
|
||||||
import sonia.scm.repository.spi.HgHookContextProvider;
|
import sonia.scm.repository.spi.HgHookContextProvider;
|
||||||
import sonia.scm.repository.spi.HookEventFacade;
|
import sonia.scm.repository.spi.HookEventFacade;
|
||||||
|
import sonia.scm.security.BearerToken;
|
||||||
import sonia.scm.security.CipherUtil;
|
import sonia.scm.security.CipherUtil;
|
||||||
import sonia.scm.security.Tokens;
|
|
||||||
import sonia.scm.util.HttpUtil;
|
import sonia.scm.util.HttpUtil;
|
||||||
import sonia.scm.util.Util;
|
import sonia.scm.util.Util;
|
||||||
|
|
||||||
@@ -93,7 +94,7 @@ public class HgHookCallbackServlet extends HttpServlet
|
|||||||
private static final String PARAM_CHALLENGE = "challenge";
|
private static final String PARAM_CHALLENGE = "challenge";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String PARAM_CREDENTIALS = "credentials";
|
private static final String PARAM_TOKEN = "token";
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final String PARAM_NODE = "node";
|
private static final String PARAM_NODE = "node";
|
||||||
@@ -179,11 +180,11 @@ public class HgHookCallbackServlet extends HttpServlet
|
|||||||
|
|
||||||
if (Util.isNotEmpty(node))
|
if (Util.isNotEmpty(node))
|
||||||
{
|
{
|
||||||
String credentials = request.getParameter(PARAM_CREDENTIALS);
|
String token = request.getParameter(PARAM_TOKEN);
|
||||||
|
|
||||||
if (Util.isNotEmpty(credentials))
|
if (Util.isNotEmpty(token))
|
||||||
{
|
{
|
||||||
authenticate(request, credentials);
|
authenticate(token);
|
||||||
}
|
}
|
||||||
|
|
||||||
hookCallback(response, type, repositoryId, challenge, node);
|
hookCallback(response, type, repositoryId, challenge, node);
|
||||||
@@ -209,34 +210,20 @@ public class HgHookCallbackServlet extends HttpServlet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void authenticate(HttpServletRequest request, String credentials)
|
private void authenticate(String token)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
credentials = CipherUtil.getInstance().decode(credentials);
|
token = CipherUtil.getInstance().decode(token);
|
||||||
|
|
||||||
if (Util.isNotEmpty(credentials))
|
if (Util.isNotEmpty(token))
|
||||||
{
|
|
||||||
int index = credentials.indexOf(':');
|
|
||||||
|
|
||||||
if (index > 0 && index < credentials.length())
|
|
||||||
{
|
{
|
||||||
Subject subject = SecurityUtils.getSubject();
|
Subject subject = SecurityUtils.getSubject();
|
||||||
|
|
||||||
|
AuthenticationToken accessToken = createToken(token);
|
||||||
|
|
||||||
//J-
|
//J-
|
||||||
subject.login(
|
subject.login(accessToken);
|
||||||
Tokens.createAuthenticationToken(
|
|
||||||
request,
|
|
||||||
credentials.substring(0, index),
|
|
||||||
credentials.substring(index + 1)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
//J+
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
logger.error("could not find delimiter");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
@@ -245,6 +232,11 @@ public class HgHookCallbackServlet extends HttpServlet
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AuthenticationToken createToken(String tokenString)
|
||||||
|
{
|
||||||
|
return BearerToken.valueOf(tokenString);
|
||||||
|
}
|
||||||
|
|
||||||
private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
|
private void fireHook(HttpServletResponse response, String repositoryId, String node, RepositoryHookType type)
|
||||||
throws IOException
|
throws IOException
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
package sonia.scm.web;
|
||||||
|
|
||||||
|
import sonia.scm.repository.HgEnvironment;
|
||||||
|
import sonia.scm.repository.HgHookManager;
|
||||||
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import java.io.File;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public class HgRepositoryEnvironmentBuilder {
|
||||||
|
|
||||||
|
private static final String ENV_REPOSITORY_NAME = "REPO_NAME";
|
||||||
|
private static final String ENV_REPOSITORY_PATH = "SCM_REPOSITORY_PATH";
|
||||||
|
private static final String ENV_REPOSITORY_ID = "SCM_REPOSITORY_ID";
|
||||||
|
private static final String ENV_PYTHON_HTTPS_VERIFY = "PYTHONHTTPSVERIFY";
|
||||||
|
private static final String ENV_HTTP_POST_ARGS = "SCM_HTTP_POST_ARGS";
|
||||||
|
|
||||||
|
private final HgRepositoryHandler handler;
|
||||||
|
private final HgHookManager hookManager;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public HgRepositoryEnvironmentBuilder(HgRepositoryHandler handler, HgHookManager hookManager) {
|
||||||
|
this.handler = handler;
|
||||||
|
this.hookManager = hookManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void buildFor(Repository repository, HttpServletRequest request, Map<String, String> environment) {
|
||||||
|
File directory = handler.getDirectory(repository.getId());
|
||||||
|
|
||||||
|
environment.put(ENV_REPOSITORY_NAME, repository.getNamespace() + "/" + repository.getName());
|
||||||
|
environment.put(ENV_REPOSITORY_ID, repository.getId());
|
||||||
|
environment.put(ENV_REPOSITORY_PATH,
|
||||||
|
directory.getAbsolutePath());
|
||||||
|
|
||||||
|
// add hook environment
|
||||||
|
if (handler.getConfig().isDisableHookSSLValidation()) {
|
||||||
|
// disable ssl validation
|
||||||
|
// Issue 959: https://goo.gl/zH5eY8
|
||||||
|
environment.put(ENV_PYTHON_HTTPS_VERIFY, "0");
|
||||||
|
}
|
||||||
|
|
||||||
|
// enable experimental httppostargs protocol of mercurial
|
||||||
|
// Issue 970: https://goo.gl/poascp
|
||||||
|
environment.put(ENV_HTTP_POST_ARGS, String.valueOf(handler.getConfig().isEnableHttpPostArgs()));
|
||||||
|
|
||||||
|
HgEnvironment.prepareEnvironment(
|
||||||
|
environment,
|
||||||
|
handler,
|
||||||
|
hookManager,
|
||||||
|
request
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,6 +46,8 @@ import sonia.scm.plugin.Extension;
|
|||||||
import sonia.scm.repository.HgContext;
|
import sonia.scm.repository.HgContext;
|
||||||
import sonia.scm.repository.HgContextProvider;
|
import sonia.scm.repository.HgContextProvider;
|
||||||
import sonia.scm.repository.HgHookManager;
|
import sonia.scm.repository.HgHookManager;
|
||||||
|
import sonia.scm.repository.spi.HgWorkdirFactory;
|
||||||
|
import sonia.scm.repository.spi.SimpleHgWorkdirFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
@@ -81,5 +83,7 @@ public class HgServletModule extends ServletModule
|
|||||||
|
|
||||||
// bind servlets
|
// bind servlets
|
||||||
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
serve(MAPPING_HOOK).with(HgHookCallbackServlet.class);
|
||||||
|
|
||||||
|
bind(HgWorkdirFactory.class).to(SimpleHgWorkdirFactory.class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,6 +58,8 @@ import sonia.scm.util.Util;
|
|||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
@@ -103,6 +105,19 @@ public final class HgUtil
|
|||||||
*/
|
*/
|
||||||
public static Repository open(HgRepositoryHandler handler,
|
public static Repository open(HgRepositoryHandler handler,
|
||||||
HgHookManager hookManager, File directory, String encoding, boolean pending)
|
HgHookManager hookManager, File directory, String encoding, boolean pending)
|
||||||
|
{
|
||||||
|
return open(
|
||||||
|
handler,
|
||||||
|
directory,
|
||||||
|
encoding,
|
||||||
|
pending,
|
||||||
|
environment -> HgEnvironment.prepareEnvironment(environment, handler, hookManager)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Repository open(HgRepositoryHandler handler,
|
||||||
|
File directory, String encoding, boolean pending,
|
||||||
|
Consumer<Map<String, String>> prepareEnvironment)
|
||||||
{
|
{
|
||||||
String enc = encoding;
|
String enc = encoding;
|
||||||
|
|
||||||
@@ -113,8 +128,7 @@ public final class HgUtil
|
|||||||
|
|
||||||
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
RepositoryConfiguration repoConfiguration = RepositoryConfiguration.DEFAULT;
|
||||||
|
|
||||||
HgEnvironment.prepareEnvironment(repoConfiguration.getEnvironment(),
|
prepareEnvironment.accept(repoConfiguration.getEnvironment());
|
||||||
handler, hookManager);
|
|
||||||
|
|
||||||
repoConfiguration.addExtension(HgFileviewExtension.class);
|
repoConfiguration.addExtension(HgFileviewExtension.class);
|
||||||
repoConfiguration.setEnablePendingChangesets(pending);
|
repoConfiguration.setEnablePendingChangesets(pending);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import os, urllib, urllib2
|
|||||||
|
|
||||||
baseUrl = os.environ['SCM_URL']
|
baseUrl = os.environ['SCM_URL']
|
||||||
challenge = os.environ['SCM_CHALLENGE']
|
challenge = os.environ['SCM_CHALLENGE']
|
||||||
credentials = os.environ['SCM_CREDENTIALS']
|
token = os.environ['SCM_BEARER_TOKEN']
|
||||||
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
repositoryId = os.environ['SCM_REPOSITORY_ID']
|
||||||
|
|
||||||
def printMessages(ui, msgs):
|
def printMessages(ui, msgs):
|
||||||
@@ -54,13 +54,13 @@ def callHookUrl(ui, repo, hooktype, node):
|
|||||||
try:
|
try:
|
||||||
url = baseUrl + hooktype
|
url = baseUrl + hooktype
|
||||||
ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
|
ui.debug( "send scm-hook to " + url + " and " + node + "\n" )
|
||||||
data = urllib.urlencode({'node': node, 'challenge': challenge, 'credentials': credentials, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
|
data = urllib.urlencode({'node': node, 'challenge': challenge, 'token': token, 'repositoryPath': repo.root, 'repositoryId': repositoryId})
|
||||||
# open url but ignore proxy settings
|
# open url but ignore proxy settings
|
||||||
proxy_handler = urllib2.ProxyHandler({})
|
proxy_handler = urllib2.ProxyHandler({})
|
||||||
opener = urllib2.build_opener(proxy_handler)
|
opener = urllib2.build_opener(proxy_handler)
|
||||||
req = urllib2.Request(url, data)
|
req = urllib2.Request(url, data)
|
||||||
conn = opener.open(req)
|
conn = opener.open(req)
|
||||||
if conn.code >= 200 and conn.code < 300:
|
if 200 <= conn.code < 300:
|
||||||
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
|
ui.debug( "scm-hook " + hooktype + " success with status code " + str(conn.code) + "\n" )
|
||||||
printMessages(ui, conn)
|
printMessages(ui, conn)
|
||||||
abort = False
|
abort = False
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
protected RepositoryHandler createRepositoryHandler(ConfigurationStoreFactory factory, RepositoryLocationResolver locationResolver, File directory) {
|
||||||
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null);
|
HgRepositoryHandler handler = new HgRepositoryHandler(factory, new HgContextProvider(), locationResolver, null, null);
|
||||||
|
|
||||||
handler.init(contextProvider);
|
handler.init(contextProvider);
|
||||||
HgTestUtil.checkForSkip(handler);
|
HgTestUtil.checkForSkip(handler);
|
||||||
@@ -87,7 +87,7 @@ public class HgRepositoryHandlerTest extends SimpleRepositoryHandlerTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getDirectory() {
|
public void getDirectory() {
|
||||||
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null);
|
HgRepositoryHandler repositoryHandler = new HgRepositoryHandler(factory, provider, locationResolver, null, null);
|
||||||
|
|
||||||
HgConfig hgConfig = new HgConfig();
|
HgConfig hgConfig = new HgConfig();
|
||||||
hgConfig.setHgBinary("hg");
|
hgConfig.setHgBinary("hg");
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ public final class HgTestUtil
|
|||||||
|
|
||||||
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
|
RepositoryLocationResolver repositoryLocationResolver = new RepositoryLocationResolver(context, repoDao, new InitialRepositoryLocationResolver());
|
||||||
HgRepositoryHandler handler =
|
HgRepositoryHandler handler =
|
||||||
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null);
|
new HgRepositoryHandler(new InMemoryConfigurationStoreFactory(), new HgContextProvider(), repositoryLocationResolver, null, null);
|
||||||
Path repoDir = directory.toPath();
|
Path repoDir = directory.toPath();
|
||||||
when(repoDao.getPath(any())).thenReturn(repoDir);
|
when(repoDao.getPath(any())).thenReturn(repoDir);
|
||||||
handler.init(context);
|
handler.init(context);
|
||||||
@@ -128,6 +128,7 @@ public final class HgTestUtil
|
|||||||
"http://localhost:8081/scm/hook/hg/");
|
"http://localhost:8081/scm/hook/hg/");
|
||||||
when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn(
|
when(hookManager.createUrl(any(HttpServletRequest.class))).thenReturn(
|
||||||
"http://localhost:8081/scm/hook/hg/");
|
"http://localhost:8081/scm/hook/hg/");
|
||||||
|
when(hookManager.getCredentials()).thenReturn("");
|
||||||
|
|
||||||
return hookManager;
|
return hookManager;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ public class HgBranchCommand implements BranchCommand
|
|||||||
public Branch branch(String name) throws IOException
|
public Branch branch(String name) throws IOException
|
||||||
{
|
{
|
||||||
com.aragost.javahg.commands.BranchCommand.on(repository).set(name);
|
com.aragost.javahg.commands.BranchCommand.on(repository).set(name);
|
||||||
return new Branch(name, repository.tip().getNode());
|
return Branch.normalBranch(name, repository.tip().getNode());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -40,7 +40,6 @@ import org.junit.Before;
|
|||||||
|
|
||||||
import sonia.scm.repository.HgRepositoryHandler;
|
import sonia.scm.repository.HgRepositoryHandler;
|
||||||
import sonia.scm.repository.HgTestUtil;
|
import sonia.scm.repository.HgTestUtil;
|
||||||
import sonia.scm.repository.RepositoryPathNotFoundException;
|
|
||||||
import sonia.scm.repository.RepositoryTestData;
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
import sonia.scm.util.MockUtil;
|
import sonia.scm.util.MockUtil;
|
||||||
|
|
||||||
@@ -77,7 +76,7 @@ public class AbstractHgCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
@Before
|
@Before
|
||||||
public void initHgHandler() throws IOException, RepositoryPathNotFoundException {
|
public void initHgHandler() throws IOException {
|
||||||
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
|
this.handler = HgTestUtil.createHandler(tempFolder.newFolder());
|
||||||
|
|
||||||
HgTestUtil.checkForSkip(handler);
|
HgTestUtil.checkForSkip(handler);
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.aragost.javahg.commands.PullCommand;
|
||||||
|
import com.google.inject.util.Providers;
|
||||||
|
import org.assertj.core.api.Assertions;
|
||||||
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.Branch;
|
||||||
|
import sonia.scm.repository.HgTestUtil;
|
||||||
|
import sonia.scm.repository.api.BranchRequest;
|
||||||
|
import sonia.scm.web.HgRepositoryEnvironmentBuilder;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class HgBranchCommandTest extends AbstractHgCommandTestBase {
|
||||||
|
@Test
|
||||||
|
public void shouldCreateBranch() throws IOException {
|
||||||
|
Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isEmpty();
|
||||||
|
|
||||||
|
HgRepositoryEnvironmentBuilder hgRepositoryEnvironmentBuilder =
|
||||||
|
new HgRepositoryEnvironmentBuilder(handler, HgTestUtil.createHookManager());
|
||||||
|
|
||||||
|
SimpleHgWorkdirFactory workdirFactory = new SimpleHgWorkdirFactory(Providers.of(hgRepositoryEnvironmentBuilder)) {
|
||||||
|
@Override
|
||||||
|
public void configure(PullCommand pullCommand) {
|
||||||
|
// we do not want to configure http hooks in this unit test
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
BranchRequest branchRequest = new BranchRequest();
|
||||||
|
branchRequest.setNewBranch("new_branch");
|
||||||
|
|
||||||
|
new HgBranchCommand(cmdContext, repository, workdirFactory).branch(branchRequest);
|
||||||
|
|
||||||
|
Assertions.assertThat(readBranches()).filteredOn(b -> b.getName().equals("new_branch")).isNotEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Branch> readBranches() {
|
||||||
|
return new HgBranchesCommand(cmdContext, repository).getBranches();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
// @flow
|
// @flow
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { AsyncCreatable } from "react-select";
|
import { AsyncCreatable, Async } from "react-select";
|
||||||
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
import type { AutocompleteObject, SelectValue } from "@scm-manager/ui-types";
|
||||||
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
import LabelWithHelpIcon from "./forms/LabelWithHelpIcon";
|
||||||
|
|
||||||
@@ -13,7 +13,8 @@ type Props = {
|
|||||||
value?: SelectValue,
|
value?: SelectValue,
|
||||||
placeholder: string,
|
placeholder: string,
|
||||||
loadingMessage: string,
|
loadingMessage: string,
|
||||||
noOptionsMessage: string
|
noOptionsMessage: string,
|
||||||
|
creatable?: boolean
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
@@ -42,11 +43,12 @@ class Autocomplete extends React.Component<Props, State> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions } = this.props;
|
const { label, helpText, value, placeholder, loadingMessage, noOptionsMessage, loadSuggestions, creatable } = this.props;
|
||||||
return (
|
return (
|
||||||
<div className="field">
|
<div className="field">
|
||||||
<LabelWithHelpIcon label={label} helpText={helpText} />
|
<LabelWithHelpIcon label={label} helpText={helpText} />
|
||||||
<div className="control">
|
<div className="control">
|
||||||
|
{creatable?
|
||||||
<AsyncCreatable
|
<AsyncCreatable
|
||||||
cacheOptions
|
cacheOptions
|
||||||
loadOptions={loadSuggestions}
|
loadOptions={loadSuggestions}
|
||||||
@@ -63,6 +65,18 @@ class Autocomplete extends React.Component<Props, State> {
|
|||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
:
|
||||||
|
<Async
|
||||||
|
cacheOptions
|
||||||
|
loadOptions={loadSuggestions}
|
||||||
|
onChange={this.handleInputChange}
|
||||||
|
value={value}
|
||||||
|
placeholder={placeholder}
|
||||||
|
loadingMessage={() => loadingMessage}
|
||||||
|
noOptionsMessage={() => noOptionsMessage}
|
||||||
|
/>
|
||||||
|
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,15 +19,6 @@ class BaseUrlSettings extends React.Component<Props> {
|
|||||||
<div>
|
<div>
|
||||||
<Subtitle subtitle={t("base-url-settings.name")} />
|
<Subtitle subtitle={t("base-url-settings.name")} />
|
||||||
<div className="columns">
|
<div className="columns">
|
||||||
<div className="column is-half">
|
|
||||||
<Checkbox
|
|
||||||
checked={forceBaseUrl}
|
|
||||||
label={t("base-url-settings.force-base-url")}
|
|
||||||
onChange={this.handleForceBaseUrlChange}
|
|
||||||
disabled={!hasUpdatePermission}
|
|
||||||
helpText={t("help.forceBaseUrlHelpText")}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div className="column is-half">
|
<div className="column is-half">
|
||||||
<InputField
|
<InputField
|
||||||
label={t("base-url-settings.base-url")}
|
label={t("base-url-settings.base-url")}
|
||||||
@@ -37,6 +28,15 @@ class BaseUrlSettings extends React.Component<Props> {
|
|||||||
helpText={t("help.baseUrlHelpText")}
|
helpText={t("help.baseUrlHelpText")}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="column is-half">
|
||||||
|
<Checkbox
|
||||||
|
checked={forceBaseUrl}
|
||||||
|
label={t("base-url-settings.force-base-url")}
|
||||||
|
onChange={this.handleForceBaseUrlChange}
|
||||||
|
disabled={!hasUpdatePermission}
|
||||||
|
helpText={t("help.forceBaseUrlHelpText")}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
package sonia.scm;
|
||||||
|
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Optional;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static java.util.Optional.ofNullable;
|
||||||
|
import static sonia.scm.group.DisplayGroup.from;
|
||||||
|
|
||||||
|
public abstract class GenericDisplayManager<D, T extends ReducedModelObject> implements DisplayManager<T> {
|
||||||
|
|
||||||
|
private final GenericDAO<D> dao;
|
||||||
|
private final Function<D, T> transform;
|
||||||
|
|
||||||
|
protected GenericDisplayManager(GenericDAO<D> dao, Function<D, T> transform) {
|
||||||
|
this.dao = dao;
|
||||||
|
this.transform = transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Collection<T> autocomplete(String filter) {
|
||||||
|
checkPermission();
|
||||||
|
SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT);
|
||||||
|
return SearchUtil.search(
|
||||||
|
searchRequest,
|
||||||
|
dao.getAll(),
|
||||||
|
object -> matches(searchRequest, object)? transform.apply(object): null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void checkPermission();
|
||||||
|
|
||||||
|
protected abstract boolean matches(SearchRequest searchRequest, D object);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<T> get(String id) {
|
||||||
|
return ofNullable(dao.get(id)).map(transform);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -46,8 +46,10 @@ import sonia.scm.cache.CacheManager;
|
|||||||
import sonia.scm.cache.GuavaCacheManager;
|
import sonia.scm.cache.GuavaCacheManager;
|
||||||
import sonia.scm.config.ScmConfiguration;
|
import sonia.scm.config.ScmConfiguration;
|
||||||
import sonia.scm.event.ScmEventBus;
|
import sonia.scm.event.ScmEventBus;
|
||||||
|
import sonia.scm.group.DefaultGroupDisplayManager;
|
||||||
import sonia.scm.group.DefaultGroupManager;
|
import sonia.scm.group.DefaultGroupManager;
|
||||||
import sonia.scm.group.GroupDAO;
|
import sonia.scm.group.GroupDAO;
|
||||||
|
import sonia.scm.group.GroupDisplayManager;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupManager;
|
||||||
import sonia.scm.group.GroupManagerProvider;
|
import sonia.scm.group.GroupManagerProvider;
|
||||||
import sonia.scm.group.xml.XmlGroupDAO;
|
import sonia.scm.group.xml.XmlGroupDAO;
|
||||||
@@ -102,8 +104,10 @@ import sonia.scm.template.MustacheTemplateEngine;
|
|||||||
import sonia.scm.template.TemplateEngine;
|
import sonia.scm.template.TemplateEngine;
|
||||||
import sonia.scm.template.TemplateEngineFactory;
|
import sonia.scm.template.TemplateEngineFactory;
|
||||||
import sonia.scm.template.TemplateServlet;
|
import sonia.scm.template.TemplateServlet;
|
||||||
|
import sonia.scm.user.DefaultUserDisplayManager;
|
||||||
import sonia.scm.user.DefaultUserManager;
|
import sonia.scm.user.DefaultUserManager;
|
||||||
import sonia.scm.user.UserDAO;
|
import sonia.scm.user.UserDAO;
|
||||||
|
import sonia.scm.user.UserDisplayManager;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
import sonia.scm.user.UserManagerProvider;
|
import sonia.scm.user.UserManagerProvider;
|
||||||
import sonia.scm.user.xml.XmlUserDAO;
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
@@ -268,8 +272,11 @@ public class ScmServletModule extends ServletModule
|
|||||||
RepositoryManagerProvider.class);
|
RepositoryManagerProvider.class);
|
||||||
bindDecorated(UserManager.class, DefaultUserManager.class,
|
bindDecorated(UserManager.class, DefaultUserManager.class,
|
||||||
UserManagerProvider.class);
|
UserManagerProvider.class);
|
||||||
|
bind(UserDisplayManager.class, DefaultUserDisplayManager.class);
|
||||||
bindDecorated(GroupManager.class, DefaultGroupManager.class,
|
bindDecorated(GroupManager.class, DefaultGroupManager.class,
|
||||||
GroupManagerProvider.class);
|
GroupManagerProvider.class);
|
||||||
|
bind(GroupDisplayManager.class, DefaultGroupDisplayManager.class);
|
||||||
|
|
||||||
bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class);
|
bind(CGIExecutorFactory.class, DefaultCGIExecutorFactory.class);
|
||||||
|
|
||||||
// bind sslcontext provider
|
// bind sslcontext provider
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package sonia.scm.api;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import org.slf4j.MDC;
|
||||||
|
import sonia.scm.ExceptionWithContext;
|
||||||
|
import sonia.scm.api.v2.resources.ErrorDto;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
|
import javax.ws.rs.core.Response;
|
||||||
|
import javax.ws.rs.ext.ExceptionMapper;
|
||||||
|
import javax.ws.rs.ext.Provider;
|
||||||
|
|
||||||
|
@Provider
|
||||||
|
public class ContextualFallbackExceptionMapper implements ExceptionMapper<ExceptionWithContext> {
|
||||||
|
|
||||||
|
private static final Logger logger = LoggerFactory.getLogger(ContextualFallbackExceptionMapper.class);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Response toResponse(ExceptionWithContext exception) {
|
||||||
|
logger.warn("mapping unexpected {} to status code 500", exception.getClass().getName(), exception);
|
||||||
|
ErrorDto errorDto = new ErrorDto();
|
||||||
|
errorDto.setMessage(exception.getMessage());
|
||||||
|
errorDto.setContext(exception.getContext());
|
||||||
|
errorDto.setErrorCode(exception.getCode());
|
||||||
|
errorDto.setTransactionId(MDC.get("transaction_id"));
|
||||||
|
return Response.status(Response.Status.INTERNAL_SERVER_ERROR)
|
||||||
|
.entity(errorDto)
|
||||||
|
.type(VndMediaType.ERROR_TYPE)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,8 +4,8 @@ import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import org.hibernate.validator.constraints.NotEmpty;
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
import sonia.scm.ReducedModelObject;
|
import sonia.scm.ReducedModelObject;
|
||||||
import sonia.scm.group.GroupManager;
|
import sonia.scm.group.GroupDisplayManager;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserDisplayManager;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -30,14 +30,14 @@ public class AutoCompleteResource {
|
|||||||
|
|
||||||
private ReducedObjectModelToDtoMapper mapper;
|
private ReducedObjectModelToDtoMapper mapper;
|
||||||
|
|
||||||
private UserManager userManager;
|
private UserDisplayManager userDisplayManager;
|
||||||
private GroupManager groupManager;
|
private GroupDisplayManager groupDisplayManager;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserManager userManager, GroupManager groupManager) {
|
public AutoCompleteResource(ReducedObjectModelToDtoMapper mapper, UserDisplayManager userDisplayManager, GroupDisplayManager groupDisplayManager) {
|
||||||
this.mapper = mapper;
|
this.mapper = mapper;
|
||||||
this.userManager = userManager;
|
this.userDisplayManager = userDisplayManager;
|
||||||
this.groupManager = groupManager;
|
this.groupDisplayManager = groupDisplayManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -51,7 +51,7 @@ public class AutoCompleteResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public List<ReducedObjectModelDto> searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
public List<ReducedObjectModelDto> searchUser(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
||||||
return map(userManager.autocomplete(filter));
|
return map(userDisplayManager.autocomplete(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
@GET
|
@GET
|
||||||
@@ -65,7 +65,7 @@ public class AutoCompleteResource {
|
|||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public List<ReducedObjectModelDto> searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
public List<ReducedObjectModelDto> searchGroup(@NotEmpty(message = PARAMETER_IS_REQUIRED) @Size(min = MIN_SEARCHED_CHARS, message = INVALID_PARAMETER_LENGTH) @QueryParam("q") String filter) {
|
||||||
return map(groupManager.autocomplete(filter));
|
return map(groupDisplayManager.autocomplete(filter));
|
||||||
}
|
}
|
||||||
|
|
||||||
private <T extends ReducedModelObject> List<ReducedObjectModelDto> map(Collection<T> autocomplete) {
|
private <T extends ReducedModelObject> List<ReducedObjectModelDto> map(Collection<T> autocomplete) {
|
||||||
|
|||||||
@@ -3,9 +3,12 @@ package sonia.scm.api.v2.resources;
|
|||||||
import com.google.inject.Inject;
|
import com.google.inject.Inject;
|
||||||
import de.otto.edison.hal.Embedded;
|
import de.otto.edison.hal.Embedded;
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
|
import de.otto.edison.hal.Link;
|
||||||
import de.otto.edison.hal.Links;
|
import de.otto.edison.hal.Links;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -25,22 +28,36 @@ public class BranchCollectionToDtoMapper {
|
|||||||
this.branchToDtoMapper = branchToDtoMapper;
|
this.branchToDtoMapper = branchToDtoMapper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public HalRepresentation map(String namespace, String name, Collection<Branch> branches) {
|
public HalRepresentation map(Repository repository, Collection<Branch> branches) {
|
||||||
return new HalRepresentation(createLinks(namespace, name), embedDtos(getBranchDtoList(namespace, name, branches)));
|
return new HalRepresentation(
|
||||||
|
createLinks(repository),
|
||||||
|
embedDtos(getBranchDtoList(repository.getNamespace(), repository.getName(), branches)));
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<BranchDto> getBranchDtoList(String namespace, String name, Collection<Branch> branches) {
|
public List<BranchDto> getBranchDtoList(String namespace, String name, Collection<Branch> branches) {
|
||||||
return branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList());
|
return branches.stream().map(branch -> branchToDtoMapper.map(branch, new NamespaceAndName(namespace, name))).collect(toList());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Links createLinks(String namespace, String name) {
|
private Links createLinks(Repository repository) {
|
||||||
|
String namespace = repository.getNamespace();
|
||||||
|
String name = repository.getName();
|
||||||
String baseUrl = resourceLinks.branchCollection().self(namespace, name);
|
String baseUrl = resourceLinks.branchCollection().self(namespace, name);
|
||||||
|
|
||||||
Links.Builder linksBuilder = linkingTo()
|
Links.Builder linksBuilder = linkingTo().with(createSelfLink(baseUrl));
|
||||||
.with(Links.linkingTo().self(baseUrl).build());
|
if (RepositoryPermissions.push(repository).isPermitted()) {
|
||||||
|
linksBuilder.single(createCreateLink(namespace, name));
|
||||||
|
}
|
||||||
return linksBuilder.build();
|
return linksBuilder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private Links createSelfLink(String baseUrl) {
|
||||||
|
return Links.linkingTo().self(baseUrl).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private Link createCreateLink(String namespace, String name) {
|
||||||
|
return Link.link("create", resourceLinks.branch().create(namespace, name));
|
||||||
|
}
|
||||||
|
|
||||||
private Embedded embedDtos(List<BranchDto> dtos) {
|
private Embedded embedDtos(List<BranchDto> dtos) {
|
||||||
return embeddedBuilder()
|
return embeddedBuilder()
|
||||||
.with("branches", dtos)
|
.with("branches", dtos)
|
||||||
|
|||||||
@@ -6,12 +6,22 @@ import de.otto.edison.hal.Links;
|
|||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
import lombok.Setter;
|
import lombok.Setter;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
@Getter @Setter @NoArgsConstructor
|
@Getter @Setter @NoArgsConstructor
|
||||||
public class BranchDto extends HalRepresentation {
|
public class BranchDto extends HalRepresentation {
|
||||||
|
|
||||||
|
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||||
|
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
||||||
|
static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
|
||||||
|
|
||||||
|
@NotEmpty @Length(min = 1, max=100) @Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
private String name;
|
private String name;
|
||||||
private String revision;
|
private String revision;
|
||||||
|
private boolean defaultBranch;
|
||||||
|
|
||||||
BranchDto(Links links, Embedded embedded) {
|
BranchDto(Links links, Embedded embedded) {
|
||||||
super(links, embedded);
|
super(links, embedded);
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.Setter;
|
||||||
|
import org.hibernate.validator.constraints.Length;
|
||||||
|
import org.hibernate.validator.constraints.NotEmpty;
|
||||||
|
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import static sonia.scm.api.v2.resources.BranchDto.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@Setter
|
||||||
|
public class BranchRequestDto {
|
||||||
|
|
||||||
|
@NotEmpty @Length(min = 1, max=100) @Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
|
private String name;
|
||||||
|
private String parent;
|
||||||
|
}
|
||||||
@@ -1,9 +1,11 @@
|
|||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
import com.webcohesion.enunciate.metadata.rs.ResponseCode;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseHeader;
|
||||||
|
import com.webcohesion.enunciate.metadata.rs.ResponseHeaders;
|
||||||
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import sonia.scm.NotFoundException;
|
|
||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.repository.Branch;
|
import sonia.scm.repository.Branch;
|
||||||
import sonia.scm.repository.Branches;
|
import sonia.scm.repository.Branches;
|
||||||
@@ -12,21 +14,27 @@ import sonia.scm.repository.ChangesetPagingResult;
|
|||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
import sonia.scm.repository.RepositoryPermissions;
|
import sonia.scm.repository.RepositoryPermissions;
|
||||||
|
import sonia.scm.repository.api.BranchCommandBuilder;
|
||||||
import sonia.scm.repository.api.CommandNotSupportedException;
|
import sonia.scm.repository.api.CommandNotSupportedException;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
import javax.validation.Valid;
|
||||||
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.DefaultValue;
|
import javax.ws.rs.DefaultValue;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
|
import javax.ws.rs.POST;
|
||||||
import javax.ws.rs.Path;
|
import javax.ws.rs.Path;
|
||||||
import javax.ws.rs.PathParam;
|
import javax.ws.rs.PathParam;
|
||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.net.URI;
|
||||||
|
|
||||||
|
import static sonia.scm.AlreadyExistsException.alreadyExists;
|
||||||
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
import static sonia.scm.ContextEntry.ContextBuilder.entity;
|
||||||
import static sonia.scm.NotFoundException.notFound;
|
import static sonia.scm.NotFoundException.notFound;
|
||||||
|
|
||||||
@@ -38,12 +46,15 @@ public class BranchRootResource {
|
|||||||
|
|
||||||
private final BranchChangesetCollectionToDtoMapper branchChangesetCollectionToDtoMapper;
|
private final BranchChangesetCollectionToDtoMapper branchChangesetCollectionToDtoMapper;
|
||||||
|
|
||||||
|
private final ResourceLinks resourceLinks;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper) {
|
public BranchRootResource(RepositoryServiceFactory serviceFactory, BranchToBranchDtoMapper branchToDtoMapper, BranchCollectionToDtoMapper branchCollectionToDtoMapper, BranchChangesetCollectionToDtoMapper changesetCollectionToDtoMapper, ResourceLinks resourceLinks) {
|
||||||
this.serviceFactory = serviceFactory;
|
this.serviceFactory = serviceFactory;
|
||||||
this.branchToDtoMapper = branchToDtoMapper;
|
this.branchToDtoMapper = branchToDtoMapper;
|
||||||
this.branchCollectionToDtoMapper = branchCollectionToDtoMapper;
|
this.branchCollectionToDtoMapper = branchCollectionToDtoMapper;
|
||||||
this.branchChangesetCollectionToDtoMapper = changesetCollectionToDtoMapper;
|
this.branchChangesetCollectionToDtoMapper = changesetCollectionToDtoMapper;
|
||||||
|
this.resourceLinks = resourceLinks;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -100,12 +111,7 @@ public class BranchRootResource {
|
|||||||
@DefaultValue("0") @QueryParam("page") int page,
|
@DefaultValue("0") @QueryParam("page") int page,
|
||||||
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
@DefaultValue("10") @QueryParam("pageSize") int pageSize) throws IOException {
|
||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
boolean branchExists = repositoryService.getBranchesCommand()
|
if (!branchExists(branchName, repositoryService)){
|
||||||
.getBranches()
|
|
||||||
.getBranches()
|
|
||||||
.stream()
|
|
||||||
.anyMatch(branch -> branchName.equals(branch.getName()));
|
|
||||||
if (!branchExists){
|
|
||||||
throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
|
throw notFound(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
|
||||||
}
|
}
|
||||||
Repository repository = repositoryService.getRepository();
|
Repository repository = repositoryService.getRepository();
|
||||||
@@ -125,6 +131,58 @@ public class BranchRootResource {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new branch.
|
||||||
|
*
|
||||||
|
* @param namespace the namespace of the repository
|
||||||
|
* @param name the name of the repository
|
||||||
|
* @param branchRequest the request giving the name of the new branch and an optional parent branch
|
||||||
|
* @return A response with the link to the new branch (if created successfully).
|
||||||
|
*/
|
||||||
|
@POST
|
||||||
|
@Path("")
|
||||||
|
@Consumes(VndMediaType.BRANCH_REQUEST)
|
||||||
|
@StatusCodes({
|
||||||
|
@ResponseCode(code = 201, condition = "create success"),
|
||||||
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"push\" privilege"),
|
||||||
|
@ResponseCode(code = 409, condition = "conflict, a user with this name already exists"),
|
||||||
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
|
})
|
||||||
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created branch"))
|
||||||
|
public Response create(@PathParam("namespace") String namespace,
|
||||||
|
@PathParam("name") String name,
|
||||||
|
@Valid BranchRequestDto branchRequest) throws IOException {
|
||||||
|
NamespaceAndName namespaceAndName = new NamespaceAndName(namespace, name);
|
||||||
|
String branchName = branchRequest.getName();
|
||||||
|
String parentName = branchRequest.getParent();
|
||||||
|
try (RepositoryService repositoryService = serviceFactory.create(namespaceAndName)) {
|
||||||
|
if (branchExists(branchName, repositoryService)) {
|
||||||
|
throw alreadyExists(entity(Branch.class, branchName).in(Repository.class, namespace + "/" + name));
|
||||||
|
}
|
||||||
|
Repository repository = repositoryService.getRepository();
|
||||||
|
RepositoryPermissions.push(repository).check();
|
||||||
|
BranchCommandBuilder branchCommand = repositoryService.getBranchCommand();
|
||||||
|
if (!Strings.isNullOrEmpty(parentName)) {
|
||||||
|
if (!branchExists(parentName, repositoryService)) {
|
||||||
|
throw notFound(entity(Branch.class, parentName).in(Repository.class, namespace + "/" + name));
|
||||||
|
}
|
||||||
|
branchCommand.from(parentName);
|
||||||
|
}
|
||||||
|
Branch newBranch = branchCommand.branch(branchName);
|
||||||
|
return Response.created(URI.create(resourceLinks.branch().self(namespaceAndName, newBranch.getName()))).build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean branchExists(String branchName, RepositoryService repositoryService) throws IOException {
|
||||||
|
return repositoryService.getBranchesCommand()
|
||||||
|
.getBranches()
|
||||||
|
.getBranches()
|
||||||
|
.stream()
|
||||||
|
.anyMatch(branch -> branchName.equals(branch.getName()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the branches for a repository.
|
* Returns the branches for a repository.
|
||||||
*
|
*
|
||||||
@@ -141,14 +199,14 @@ public class BranchRootResource {
|
|||||||
@ResponseCode(code = 200, condition = "success"),
|
@ResponseCode(code = 200, condition = "success"),
|
||||||
@ResponseCode(code = 400, condition = "branches not supported for given repository"),
|
@ResponseCode(code = 400, condition = "branches not supported for given repository"),
|
||||||
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
@ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
|
||||||
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"group\" privilege"),
|
@ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"read repository\" privilege"),
|
||||||
@ResponseCode(code = 404, condition = "not found, no repository found for the given namespace and name"),
|
@ResponseCode(code = 404, condition = "not found, no repository found for the given namespace and name"),
|
||||||
@ResponseCode(code = 500, condition = "internal server error")
|
@ResponseCode(code = 500, condition = "internal server error")
|
||||||
})
|
})
|
||||||
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
|
public Response getAll(@PathParam("namespace") String namespace, @PathParam("name") String name) throws IOException {
|
||||||
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
try (RepositoryService repositoryService = serviceFactory.create(new NamespaceAndName(namespace, name))) {
|
||||||
Branches branches = repositoryService.getBranchesCommand().getBranches();
|
Branches branches = repositoryService.getBranchesCommand().getBranches();
|
||||||
return Response.ok(branchCollectionToDtoMapper.map(namespace, name, branches.getBranches())).build();
|
return Response.ok(branchCollectionToDtoMapper.map(repositoryService.getRepository(), branches.getBranches())).build();
|
||||||
} catch (CommandNotSupportedException ex) {
|
} catch (CommandNotSupportedException ex) {
|
||||||
return Response.status(Response.Status.BAD_REQUEST).build();
|
return Response.status(Response.Status.BAD_REQUEST).build();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
|
|||||||
}
|
}
|
||||||
if (repositoryService.isSupported(Command.BRANCHES)) {
|
if (repositoryService.isSupported(Command.BRANCHES)) {
|
||||||
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
|
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,
|
||||||
getListOfObjects(source.getBranches(), branchName -> new Branch(branchName, source.getId()))));
|
getListOfObjects(source.getBranches(), branchName -> Branch.normalBranch(branchName, source.getId()))));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository)));
|
embeddedBuilder.with("parents", getListOfObjects(source.getParents(), parent -> changesetToParentDtoMapper.map(new Changeset(parent, 0L, null), repository)));
|
||||||
|
|||||||
@@ -386,6 +386,9 @@ class ResourceLinks {
|
|||||||
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("history").parameters(branch).href();
|
return branchLinkBuilder.method("getRepositoryResource").parameters(namespaceAndName.getNamespace(), namespaceAndName.getName()).method("branches").parameters().method("history").parameters(branch).href();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String create(String namespace, String name) {
|
||||||
|
return branchLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("branches").parameters().method("create").parameters().href();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public IncomingLinks incoming() {
|
public IncomingLinks incoming() {
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
|||||||
import org.apache.shiro.authc.credential.PasswordService;
|
import org.apache.shiro.authc.credential.PasswordService;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
import sonia.scm.user.UserManager;
|
||||||
|
import sonia.scm.user.UserPermissions;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sonia.scm.group;
|
||||||
|
|
||||||
|
import sonia.scm.GenericDisplayManager;
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class DefaultGroupDisplayManager extends GenericDisplayManager<Group, DisplayGroup> implements GroupDisplayManager {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultGroupDisplayManager(GroupDAO groupDAO) {
|
||||||
|
super(groupDAO, DisplayGroup::from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void checkPermission() {
|
||||||
|
GroupPermissions.autocomplete().check();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean matches(SearchRequest searchRequest, Group group) {
|
||||||
|
return SearchUtil.matchesOne(searchRequest, group.getName(), group.getDescription());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -195,7 +195,7 @@ public class DefaultGroupManager extends AbstractGroupManager
|
|||||||
|
|
||||||
final PermissionActionCheck<Group> check = GroupPermissions.read();
|
final PermissionActionCheck<Group> check = GroupPermissions.read();
|
||||||
return SearchUtil.search(searchRequest, groupDAO.getAll(),
|
return SearchUtil.search(searchRequest, groupDAO.getAll(),
|
||||||
new TransformFilter<Group>()
|
new TransformFilter<Group, Group>()
|
||||||
{
|
{
|
||||||
@Override
|
@Override
|
||||||
public Group accept(Group group)
|
public Group accept(Group group)
|
||||||
@@ -241,13 +241,6 @@ public class DefaultGroupManager extends AbstractGroupManager
|
|||||||
return group;
|
return group;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<Group> autocomplete(String filter) {
|
|
||||||
GroupPermissions.autocomplete().check();
|
|
||||||
SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT);
|
|
||||||
return SearchUtil.search(searchRequest, groupDAO.getAll(), group -> matches(searchRequest,group)?group:null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package sonia.scm.user;
|
||||||
|
|
||||||
|
import sonia.scm.GenericDisplayManager;
|
||||||
|
import sonia.scm.search.SearchRequest;
|
||||||
|
import sonia.scm.search.SearchUtil;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
|
||||||
|
public class DefaultUserDisplayManager extends GenericDisplayManager<User, DisplayUser> implements UserDisplayManager {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public DefaultUserDisplayManager(UserDAO userDAO) {
|
||||||
|
super(userDAO, DisplayUser::from);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void checkPermission() {
|
||||||
|
UserPermissions.autocomplete().check();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected boolean matches(SearchRequest searchRequest, User user) {
|
||||||
|
return SearchUtil.matchesOne(searchRequest, user.getName(), user.getDisplayName(), user.getMail());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -212,13 +212,6 @@ public class DefaultUserManager extends AbstractUserManager
|
|||||||
fresh.copyProperties(user);
|
fresh.copyProperties(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public Collection<User> autocomplete(String filter) {
|
|
||||||
UserPermissions.autocomplete().check();
|
|
||||||
SearchRequest searchRequest = new SearchRequest(filter, true, DEFAULT_LIMIT);
|
|
||||||
return SearchUtil.search(searchRequest, userDAO.getAll(), user -> matches(searchRequest,user)?user:null);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Method description
|
* Method description
|
||||||
*
|
*
|
||||||
@@ -236,7 +229,7 @@ public class DefaultUserManager extends AbstractUserManager
|
|||||||
}
|
}
|
||||||
|
|
||||||
final PermissionActionCheck<User> check = UserPermissions.read();
|
final PermissionActionCheck<User> check = UserPermissions.read();
|
||||||
return SearchUtil.search(searchRequest, userDAO.getAll(), new TransformFilter<User>() {
|
return SearchUtil.search(searchRequest, userDAO.getAll(), new TransformFilter<User, User>() {
|
||||||
@Override
|
@Override
|
||||||
public User accept(User user)
|
public User accept(User user)
|
||||||
{
|
{
|
||||||
@@ -415,35 +408,6 @@ public class DefaultUserManager extends AbstractUserManager
|
|||||||
this.modify(user);
|
this.modify(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Method description
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param unmarshaller
|
|
||||||
* @param path
|
|
||||||
*/
|
|
||||||
private void createDefaultAccount(Unmarshaller unmarshaller, String path)
|
|
||||||
{
|
|
||||||
InputStream input = DefaultUserManager.class.getResourceAsStream(path);
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
User user = (User) unmarshaller.unmarshal(input);
|
|
||||||
|
|
||||||
user.setType(userDAO.getType());
|
|
||||||
user.setCreationDate(System.currentTimeMillis());
|
|
||||||
userDAO.add(user);
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
logger.error("could not create account", ex);
|
|
||||||
}
|
|
||||||
finally
|
|
||||||
{
|
|
||||||
IOUtil.close(input);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
private final UserDAO userDAO;
|
private final UserDAO userDAO;
|
||||||
|
|||||||
@@ -147,6 +147,10 @@
|
|||||||
"3zR9vPNIE1": {
|
"3zR9vPNIE1": {
|
||||||
"displayName": "Ungültige Eingabe",
|
"displayName": "Ungültige Eingabe",
|
||||||
"description": "Die eingegebenen Daten konnten nicht validiert werden. Bitte korrigieren Sie die Eingaben und senden Sie sie erneut."
|
"description": "Die eingegebenen Daten konnten nicht validiert werden. Bitte korrigieren Sie die Eingaben und senden Sie sie erneut."
|
||||||
|
},
|
||||||
|
"CHRM7IQzo1": {
|
||||||
|
"displayName": "Änderung fehlgeschlagen",
|
||||||
|
"description": "Die Änderung ist fehlgeschlagen. Bitte wenden Sie sich an ihren Administrator für weitere Hinweise."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -147,6 +147,10 @@
|
|||||||
"3zR9vPNIE1": {
|
"3zR9vPNIE1": {
|
||||||
"displayName": "Illegal input",
|
"displayName": "Illegal input",
|
||||||
"description": "The values could not be validated. Please correct your input and try again."
|
"description": "The values could not be validated. Please correct your input and try again."
|
||||||
|
},
|
||||||
|
"CHRM7IQzo1": {
|
||||||
|
"displayName": "Change failed",
|
||||||
|
"description": "The change failed. Please contact your administrator for further assistance."
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
|
|||||||
@@ -14,16 +14,14 @@ import org.junit.Rule;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.junit.MockitoJUnitRunner;
|
import org.mockito.junit.MockitoJUnitRunner;
|
||||||
import sonia.scm.Manager;
|
import sonia.scm.DisplayManager;
|
||||||
import sonia.scm.group.DefaultGroupManager;
|
import sonia.scm.group.DefaultGroupDisplayManager;
|
||||||
import sonia.scm.group.Group;
|
import sonia.scm.group.Group;
|
||||||
import sonia.scm.group.GroupManager;
|
|
||||||
import sonia.scm.group.xml.XmlGroupDAO;
|
import sonia.scm.group.xml.XmlGroupDAO;
|
||||||
import sonia.scm.store.ConfigurationStore;
|
import sonia.scm.store.ConfigurationStore;
|
||||||
import sonia.scm.store.ConfigurationStoreFactory;
|
import sonia.scm.store.ConfigurationStoreFactory;
|
||||||
import sonia.scm.user.DefaultUserManager;
|
import sonia.scm.user.DefaultUserDisplayManager;
|
||||||
import sonia.scm.user.User;
|
import sonia.scm.user.User;
|
||||||
import sonia.scm.user.UserManager;
|
|
||||||
import sonia.scm.user.xml.XmlUserDAO;
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
import sonia.scm.web.VndMediaType;
|
import sonia.scm.web.VndMediaType;
|
||||||
import sonia.scm.xml.XmlDatabase;
|
import sonia.scm.xml.XmlDatabase;
|
||||||
@@ -51,7 +49,7 @@ public class AutoCompleteResourceTest {
|
|||||||
public final ShiroRule shiroRule = new ShiroRule();
|
public final ShiroRule shiroRule = new ShiroRule();
|
||||||
|
|
||||||
public static final String URL = "/" + AutoCompleteResource.PATH;
|
public static final String URL = "/" + AutoCompleteResource.PATH;
|
||||||
private final Integer defaultLimit = Manager.DEFAULT_LIMIT;
|
private final Integer defaultLimit = DisplayManager.DEFAULT_LIMIT;
|
||||||
private Dispatcher dispatcher;
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
private XmlUserDAO userDao;
|
private XmlUserDAO userDao;
|
||||||
@@ -73,8 +71,8 @@ public class AutoCompleteResourceTest {
|
|||||||
XmlGroupDAO groupDAO = new XmlGroupDAO(storeFactory);
|
XmlGroupDAO groupDAO = new XmlGroupDAO(storeFactory);
|
||||||
groupDao = spy(groupDAO);
|
groupDao = spy(groupDAO);
|
||||||
ReducedObjectModelToDtoMapperImpl mapper = new ReducedObjectModelToDtoMapperImpl();
|
ReducedObjectModelToDtoMapperImpl mapper = new ReducedObjectModelToDtoMapperImpl();
|
||||||
UserManager userManager = new DefaultUserManager(this.userDao);
|
DefaultUserDisplayManager userManager = new DefaultUserDisplayManager(this.userDao);
|
||||||
GroupManager groupManager = new DefaultGroupManager(groupDao);
|
DefaultGroupDisplayManager groupManager = new DefaultGroupDisplayManager(groupDao);
|
||||||
AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager);
|
AutoCompleteResource autoCompleteResource = new AutoCompleteResource(mapper, userManager, groupManager);
|
||||||
dispatcher = createDispatcher(autoCompleteResource);
|
dispatcher = createDispatcher(autoCompleteResource);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
|
import org.junit.jupiter.params.provider.ValueSource;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
|
class BranchDtoTest {
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"v",
|
||||||
|
"1234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890",
|
||||||
|
"val#x",
|
||||||
|
"val&x",
|
||||||
|
"val+",
|
||||||
|
"val,kill",
|
||||||
|
"val.kill",
|
||||||
|
"val;kill",
|
||||||
|
"val<kill",
|
||||||
|
"val=",
|
||||||
|
"val>kill",
|
||||||
|
"val@",
|
||||||
|
"val]id",
|
||||||
|
"val`id",
|
||||||
|
"valid#",
|
||||||
|
"valid.t",
|
||||||
|
"val{",
|
||||||
|
"val{d",
|
||||||
|
"val{}d",
|
||||||
|
"val|kill",
|
||||||
|
"val}"
|
||||||
|
})
|
||||||
|
void shouldAcceptValidBranchName(String branchName) {
|
||||||
|
assertTrue(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
||||||
|
}
|
||||||
|
|
||||||
|
@ParameterizedTest
|
||||||
|
@ValueSource(strings = {
|
||||||
|
"",
|
||||||
|
".val",
|
||||||
|
"val.",
|
||||||
|
"/val",
|
||||||
|
"val/",
|
||||||
|
"val id"
|
||||||
|
})
|
||||||
|
void shouldRejectInvalidBranchName(String branchName) {
|
||||||
|
assertFalse(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,10 +25,12 @@ import sonia.scm.repository.ChangesetPagingResult;
|
|||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.api.BranchCommandBuilder;
|
||||||
import sonia.scm.repository.api.BranchesCommandBuilder;
|
import sonia.scm.repository.api.BranchesCommandBuilder;
|
||||||
import sonia.scm.repository.api.LogCommandBuilder;
|
import sonia.scm.repository.api.LogCommandBuilder;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
import sonia.scm.web.VndMediaType;
|
||||||
|
|
||||||
import java.net.URI;
|
import java.net.URI;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
@@ -41,6 +43,8 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
import static org.mockito.ArgumentMatchers.anyInt;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@RunWith(MockitoJUnitRunner.Silent.class)
|
@RunWith(MockitoJUnitRunner.Silent.class)
|
||||||
@@ -49,7 +53,8 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
public static final String BRANCH_PATH = "space/repo/branches/master";
|
public static final String BRANCH_PATH = "space/repo/branches/master";
|
||||||
public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH;
|
public static final String BRANCH_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + BRANCH_PATH;
|
||||||
private final Dispatcher dispatcher = MockDispatcherFactory.createDispatcher();
|
public static final String REVISION = "revision";
|
||||||
|
private Dispatcher dispatcher;
|
||||||
|
|
||||||
private final URI baseUri = URI.create("/");
|
private final URI baseUri = URI.create("/");
|
||||||
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
private final ResourceLinks resourceLinks = ResourceLinksMock.createMock(baseUri);
|
||||||
@@ -60,6 +65,8 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
private RepositoryService service;
|
private RepositoryService service;
|
||||||
@Mock
|
@Mock
|
||||||
private BranchesCommandBuilder branchesCommandBuilder;
|
private BranchesCommandBuilder branchesCommandBuilder;
|
||||||
|
@Mock
|
||||||
|
private BranchCommandBuilder branchCommandBuilder;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private LogCommandBuilder logCommandBuilder;
|
private LogCommandBuilder logCommandBuilder;
|
||||||
@@ -89,17 +96,18 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void prepareEnvironment() throws Exception {
|
public void prepareEnvironment() {
|
||||||
changesetCollectionToDtoMapper = new BranchChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
changesetCollectionToDtoMapper = new BranchChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||||
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
BranchCollectionToDtoMapper branchCollectionToDtoMapper = new BranchCollectionToDtoMapper(branchToDtoMapper, resourceLinks);
|
||||||
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper);
|
branchRootResource = new BranchRootResource(serviceFactory, branchToDtoMapper, branchCollectionToDtoMapper, changesetCollectionToDtoMapper, resourceLinks);
|
||||||
super.branchRootResource = Providers.of(branchRootResource);
|
super.branchRootResource = Providers.of(branchRootResource);
|
||||||
dispatcher.getRegistry().addSingletonResource(getRepositoryRootResource());
|
dispatcher = DispatcherMock.createDispatcher(getRepositoryRootResource());
|
||||||
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
when(serviceFactory.create(new NamespaceAndName("space", "repo"))).thenReturn(service);
|
||||||
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
when(serviceFactory.create(any(Repository.class))).thenReturn(service);
|
||||||
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
when(service.getRepository()).thenReturn(new Repository("repoId", "git", "space", "repo"));
|
||||||
|
|
||||||
when(service.getBranchesCommand()).thenReturn(branchesCommandBuilder);
|
when(service.getBranchesCommand()).thenReturn(branchesCommandBuilder);
|
||||||
|
when(service.getBranchCommand()).thenReturn(branchCommandBuilder);
|
||||||
when(service.getLogCommand()).thenReturn(logCommandBuilder);
|
when(service.getLogCommand()).thenReturn(logCommandBuilder);
|
||||||
subjectThreadState.bind();
|
subjectThreadState.bind();
|
||||||
ThreadContext.bind(subject);
|
ThreadContext.bind(subject);
|
||||||
@@ -125,7 +133,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFindExistingBranch() throws Exception {
|
public void shouldFindExistingBranch() throws Exception {
|
||||||
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(new Branch("master", "revision")));
|
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("master")));
|
||||||
|
|
||||||
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL);
|
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL);
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
@@ -139,13 +147,12 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldFindHistory() throws Exception {
|
public void shouldFindHistory() throws Exception {
|
||||||
String id = "revision_123";
|
|
||||||
Instant creationDate = Instant.now();
|
Instant creationDate = Instant.now();
|
||||||
String authorName = "name";
|
String authorName = "name";
|
||||||
String authorEmail = "em@i.l";
|
String authorEmail = "em@i.l";
|
||||||
String commit = "my branch commit";
|
String commit = "my branch commit";
|
||||||
ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class);
|
ChangesetPagingResult changesetPagingResult = mock(ChangesetPagingResult.class);
|
||||||
List<Changeset> changesetList = Lists.newArrayList(new Changeset(id, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit));
|
List<Changeset> changesetList = Lists.newArrayList(new Changeset(REVISION, Date.from(creationDate).getTime(), new Person(authorName, authorEmail), commit));
|
||||||
when(changesetPagingResult.getChangesets()).thenReturn(changesetList);
|
when(changesetPagingResult.getChangesets()).thenReturn(changesetList);
|
||||||
when(changesetPagingResult.getTotal()).thenReturn(1);
|
when(changesetPagingResult.getTotal()).thenReturn(1);
|
||||||
when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder);
|
when(logCommandBuilder.setPagingStart(anyInt())).thenReturn(logCommandBuilder);
|
||||||
@@ -153,7 +160,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder);
|
when(logCommandBuilder.setBranch(anyString())).thenReturn(logCommandBuilder);
|
||||||
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
|
when(logCommandBuilder.getChangesets()).thenReturn(changesetPagingResult);
|
||||||
Branches branches = mock(Branches.class);
|
Branches branches = mock(Branches.class);
|
||||||
List<Branch> branchList = Lists.newArrayList(new Branch("master",id));
|
List<Branch> branchList = Lists.newArrayList(createBranch("master"));
|
||||||
when(branches.getBranches()).thenReturn(branchList);
|
when(branches.getBranches()).thenReturn(branchList);
|
||||||
when(branchesCommandBuilder.getBranches()).thenReturn(branches);
|
when(branchesCommandBuilder.getBranches()).thenReturn(branches);
|
||||||
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/");
|
MockHttpRequest request = MockHttpRequest.get(BRANCH_URL + "/changesets/");
|
||||||
@@ -161,9 +168,85 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
|||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
assertEquals(200, response.getStatus());
|
assertEquals(200, response.getStatus());
|
||||||
log.info("Response :{}", response.getContentAsString());
|
log.info("Response :{}", response.getContentAsString());
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", id)));
|
assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", REVISION)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName)));
|
assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail)));
|
assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail)));
|
||||||
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateNewBranch() throws Exception {
|
||||||
|
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches());
|
||||||
|
when(branchCommandBuilder.branch("new_branch")).thenReturn(createBranch("new_branch"));
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/")
|
||||||
|
.content("{\"name\": \"new_branch\"}".getBytes())
|
||||||
|
.contentType(VndMediaType.BRANCH_REQUEST);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
assertEquals(
|
||||||
|
URI.create("/v2/repositories/space/repo/branches/new_branch"),
|
||||||
|
response.getOutputHeaders().getFirst("Location"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateNewBranchWithParent() throws Exception {
|
||||||
|
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch")));
|
||||||
|
when(branchCommandBuilder.from("existing_branch")).thenReturn(branchCommandBuilder);
|
||||||
|
when(branchCommandBuilder.branch("new_branch")).thenReturn(createBranch("new_branch"));
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/")
|
||||||
|
.content("{\"name\": \"new_branch\",\"parent\": \"existing_branch\"}".getBytes())
|
||||||
|
.contentType(VndMediaType.BRANCH_REQUEST);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(201, response.getStatus());
|
||||||
|
assertEquals(
|
||||||
|
URI.create("/v2/repositories/space/repo/branches/new_branch"),
|
||||||
|
response.getOutputHeaders().getFirst("Location"));
|
||||||
|
verify(branchCommandBuilder).from("existing_branch");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldNotCreateExistingBranchAgain() throws Exception {
|
||||||
|
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch")));
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/")
|
||||||
|
.content("{\"name\": \"existing_branch\"}".getBytes())
|
||||||
|
.contentType(VndMediaType.BRANCH_REQUEST);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(409, response.getStatus());
|
||||||
|
verify(branchCommandBuilder, never()).branch(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldFailForMissingParentBranch() throws Exception {
|
||||||
|
when(branchesCommandBuilder.getBranches()).thenReturn(new Branches(createBranch("existing_branch")));
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/branches/")
|
||||||
|
.content("{\"name\": \"new_branch\",\"parent\": \"no_such_branch\"}".getBytes())
|
||||||
|
.contentType(VndMediaType.BRANCH_REQUEST);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(404, response.getStatus());
|
||||||
|
verify(branchCommandBuilder, never()).branch(anyString());
|
||||||
|
}
|
||||||
|
|
||||||
|
private Branch createBranch(String existing_branch) {
|
||||||
|
return Branch.normalBranch(existing_branch, REVISION);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ class BranchToBranchDtoMapperTest {
|
|||||||
});
|
});
|
||||||
mapper.setRegistry(registry);
|
mapper.setRegistry(registry);
|
||||||
|
|
||||||
Branch branch = new Branch("master", "42");
|
Branch branch = Branch.normalBranch("master", "42");
|
||||||
|
|
||||||
BranchDto dto = mapper.map(branch, new NamespaceAndName("hitchhiker", "heart-of-gold"));
|
BranchDto dto = mapper.map(branch, new NamespaceAndName("hitchhiker", "heart-of-gold"));
|
||||||
assertThat(dto.getLinks().getLinkBy("ka").get().getHref()).isEqualTo("http://hitchhiker/heart-of-gold/master");
|
assertThat(dto.getLinks().getLinkBy("ka").get().getHref()).isEqualTo("http://hitchhiker/heart-of-gold/master");
|
||||||
|
|||||||
Reference in New Issue
Block a user