mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-12 00:15:44 +01:00
Merge pull request #1399 from scm-manager/feature/default_branch
Make default branch for git configurable
This commit is contained in:
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
## Unreleased
|
## Unreleased
|
||||||
### Added
|
### Added
|
||||||
- Automatic user converter for external users ([#1380](https://github.com/scm-manager/scm-manager/pull/1380))
|
- Automatic user converter for external users ([#1380](https://github.com/scm-manager/scm-manager/pull/1380))
|
||||||
|
- The name of the initial git branch can be configured and is set to `main` by default ([#1399](https://github.com/scm-manager/scm-manager/pull/1399))
|
||||||
|
|
||||||
### Fixed
|
### Fixed
|
||||||
- Internal server error for git sub modules without tree object ([#1397](https://github.com/scm-manager/scm-manager/pull/1397))
|
- Internal server error for git sub modules without tree object ([#1397](https://github.com/scm-manager/scm-manager/pull/1397))
|
||||||
|
|||||||
BIN
docs/de/user/admin/assets/administration-settings-git.png
Normal file
BIN
docs/de/user/admin/assets/administration-settings-git.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 162 KiB |
23
docs/de/user/admin/git.md
Normal file
23
docs/de/user/admin/git.md
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
title: Administration
|
||||||
|
subtitle: Git
|
||||||
|
---
|
||||||
|
Unter dem Eintrag Git können die folgenden Git-spezifischen Einstellungen vorgenommen werden:
|
||||||
|
|
||||||
|
- GC Cron Ausdruck
|
||||||
|
|
||||||
|
Wenn hier ein Wert gesetzt wird, führt der SCM-Manager zu den
|
||||||
|
[entsprechenden Zeiten](https://de.wikipedia.org/wiki/Cron)
|
||||||
|
eine "Git Garbage Collection" aus.
|
||||||
|
|
||||||
|
- Deaktiviere "Non Fast-Forward"
|
||||||
|
|
||||||
|
Wenn dieses aktiviert ist, werden "forcierte" Pushs abgelehnt, wenn diese keine "fast forwards" sind.
|
||||||
|
|
||||||
|
- Default Branch
|
||||||
|
|
||||||
|
Der hier gesetzte Branch Name wird bei der Initialisierung von neuen Repositories genutzt.
|
||||||
|
Bitte beachten Sie, dass dieser Name aufgrund von Git-Spezifika nicht bei leeren Repositories genutzt
|
||||||
|
werden kann (hier wird immer der Git-interne Default Name genutzt, derzeit also `master`).
|
||||||
|
|
||||||
|

|
||||||
@@ -8,6 +8,7 @@ Im Bereich Administration kann die SCM-Manager Instanz administriert werden. Von
|
|||||||
* [Plugins](plugins/)
|
* [Plugins](plugins/)
|
||||||
* [Berechtigungsrollen](roles/)
|
* [Berechtigungsrollen](roles/)
|
||||||
* [Einstellungen](settings/)
|
* [Einstellungen](settings/)
|
||||||
|
* [Git](git/)
|
||||||
<!--- AppendLinkContentEnd -->
|
<!--- AppendLinkContentEnd -->
|
||||||
|
|
||||||
### Information
|
### Information
|
||||||
|
|||||||
BIN
docs/en/user/admin/assets/administration-settings-git.png
Normal file
BIN
docs/en/user/admin/assets/administration-settings-git.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 160 KiB |
22
docs/en/user/admin/git.md
Normal file
22
docs/en/user/admin/git.md
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
---
|
||||||
|
title: Administration
|
||||||
|
subtitle: Git
|
||||||
|
---
|
||||||
|
In the git section there are the following git specific settings:
|
||||||
|
|
||||||
|
- GC Cron Expression
|
||||||
|
|
||||||
|
If this is set, SCM-Manager will execute a git garbage collection matching the given
|
||||||
|
[cron expression](https://en.wikipedia.org/wiki/Cron#CRON_expression).
|
||||||
|
|
||||||
|
- Disable Non Fast-Forward
|
||||||
|
|
||||||
|
Activate this to reject forced pushes that are not fast forwards.
|
||||||
|
|
||||||
|
- Default Branch
|
||||||
|
|
||||||
|
The branch name configured here will be used for the initialization of new repositories.
|
||||||
|
Please mind, that due to git internals this cannot work for empty repositories (here git
|
||||||
|
will always use its internal default branch, so at the time being `master`).
|
||||||
|
|
||||||
|

|
||||||
@@ -7,6 +7,7 @@ The SCM-Manager instance can be administered in the Administration area. From he
|
|||||||
* [Plugins](plugins/)
|
* [Plugins](plugins/)
|
||||||
* [Permission Roles](roles/)
|
* [Permission Roles](roles/)
|
||||||
* [Settings](settings/)
|
* [Settings](settings/)
|
||||||
|
* [Git](git/)
|
||||||
|
|
||||||
### Information
|
### Information
|
||||||
On the information page in the administration area you can find the version of your SCM-Manager instance and helpful links to get in touch with the SCM-Manager support team. If there is a newer version for SCM-Manager, it will be shown with the link to the download section on the official SCM-Manager homepage.
|
On the information page in the administration area you can find the version of your SCM-Manager instance and helpful links to get in touch with the SCM-Manager support team. If there is a newer version for SCM-Manager, it will be shown with the link to the download section on the official SCM-Manager homepage.
|
||||||
|
|||||||
@@ -21,18 +21,20 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- non-JDK imports --------------------------------------------------------
|
//~--- non-JDK imports --------------------------------------------------------
|
||||||
|
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
|
import sonia.scm.Validateable;
|
||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
|
|
||||||
@@ -44,9 +46,14 @@ import java.io.Serializable;
|
|||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "branch")
|
@XmlRootElement(name = "branch")
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
public final class Branch implements Serializable
|
public final class Branch implements Serializable, Validateable
|
||||||
{
|
{
|
||||||
|
|
||||||
|
private static final String VALID_CHARACTERS_AT_START_AND_END = "\\w-,;\\]{}@&+=$#`|<>";
|
||||||
|
private static final String VALID_CHARACTERS = VALID_CHARACTERS_AT_START_AND_END + "/.";
|
||||||
|
public static final String VALID_BRANCH_NAMES = "[" + VALID_CHARACTERS_AT_START_AND_END + "]([" + VALID_CHARACTERS + "]*[" + VALID_CHARACTERS_AT_START_AND_END + "])?";
|
||||||
|
public static final Pattern VALID_BRANCH_NAME_PATTERN = Pattern.compile(VALID_BRANCH_NAMES);
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private static final long serialVersionUID = -4602244691711222413L;
|
private static final long serialVersionUID = -4602244691711222413L;
|
||||||
|
|
||||||
@@ -83,6 +90,11 @@ public final class Branch implements Serializable
|
|||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isValid() {
|
||||||
|
return VALID_BRANCH_NAME_PATTERN.matcher(name).matches();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.it;
|
package sonia.scm.it;
|
||||||
|
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
@@ -185,7 +185,7 @@ public class GitNonFastForwardITCase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
private static void setNonFastForwardDisallowed(boolean nonFastForwardDisallowed) {
|
||||||
String config = String.format("{'disabled': false, 'gcExpression': null, 'nonFastForwardDisallowed': %s}", nonFastForwardDisallowed)
|
String config = String.format("{'disabled': false, 'gcExpression': null, 'defaultBranch': 'main', 'nonFastForwardDisallowed': %s}", nonFastForwardDisallowed)
|
||||||
.replace('\'', '"');
|
.replace('\'', '"');
|
||||||
|
|
||||||
given(VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX)
|
given(VndMediaType.PREFIX + "gitConfig" + VndMediaType.SUFFIX)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import de.otto.edison.hal.HalRepresentation;
|
import de.otto.edison.hal.HalRepresentation;
|
||||||
@@ -29,6 +29,12 @@ 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 javax.validation.constraints.NotEmpty;
|
||||||
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@Getter
|
@Getter
|
||||||
@@ -41,6 +47,11 @@ public class GitConfigDto extends HalRepresentation {
|
|||||||
|
|
||||||
private boolean nonFastForwardDisallowed;
|
private boolean nonFastForwardDisallowed;
|
||||||
|
|
||||||
|
@NotEmpty
|
||||||
|
@Length(min = 1, max = 100)
|
||||||
|
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
|
private String defaultBranch;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
@SuppressWarnings("squid:S1185") // We want to have this method available in this package
|
||||||
protected HalRepresentation add(Links links) {
|
protected HalRepresentation add(Links links) {
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
import io.swagger.v3.oas.annotations.OpenAPIDefinition;
|
||||||
@@ -38,6 +38,7 @@ import sonia.scm.web.VndMediaType;
|
|||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.inject.Provider;
|
import javax.inject.Provider;
|
||||||
|
import javax.validation.Valid;
|
||||||
import javax.ws.rs.Consumes;
|
import javax.ws.rs.Consumes;
|
||||||
import javax.ws.rs.GET;
|
import javax.ws.rs.GET;
|
||||||
import javax.ws.rs.PUT;
|
import javax.ws.rs.PUT;
|
||||||
@@ -126,7 +127,7 @@ public class GitConfigResource {
|
|||||||
mediaType = VndMediaType.ERROR_TYPE,
|
mediaType = VndMediaType.ERROR_TYPE,
|
||||||
schema = @Schema(implementation = ErrorDto.class)
|
schema = @Schema(implementation = ErrorDto.class)
|
||||||
))
|
))
|
||||||
public Response update(GitConfigDto configDto) {
|
public Response update(@Valid GitConfigDto configDto) {
|
||||||
|
|
||||||
GitConfig config = dtoToConfigMapper.map(configDto);
|
GitConfig config = dtoToConfigMapper.map(configDto);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository;
|
package sonia.scm.repository;
|
||||||
|
|
||||||
//~--- JDK imports ------------------------------------------------------------
|
//~--- JDK imports ------------------------------------------------------------
|
||||||
@@ -33,7 +33,6 @@ import javax.xml.bind.annotation.XmlRootElement;
|
|||||||
import javax.xml.bind.annotation.XmlTransient;
|
import javax.xml.bind.annotation.XmlTransient;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
|
||||||
* @author Sebastian Sdorra
|
* @author Sebastian Sdorra
|
||||||
*/
|
*/
|
||||||
@XmlRootElement(name = "config")
|
@XmlRootElement(name = "config")
|
||||||
@@ -49,6 +48,9 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
@XmlElement(name = "disallow-non-fast-forward")
|
@XmlElement(name = "disallow-non-fast-forward")
|
||||||
private boolean nonFastForwardDisallowed;
|
private boolean nonFastForwardDisallowed;
|
||||||
|
|
||||||
|
@XmlElement(name = "default-branch")
|
||||||
|
private String defaultBranch = "main";
|
||||||
|
|
||||||
public String getGcExpression() {
|
public String getGcExpression() {
|
||||||
return gcExpression;
|
return gcExpression;
|
||||||
}
|
}
|
||||||
@@ -65,6 +67,14 @@ public class GitConfig extends RepositoryConfig {
|
|||||||
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
|
this.nonFastForwardDisallowed = nonFastForwardDisallowed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String getDefaultBranch() {
|
||||||
|
return defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setDefaultBranch(String defaultBranch) {
|
||||||
|
this.defaultBranch = defaultBranch;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@XmlTransient // Only for permission checks, don't serialize to XML
|
@XmlTransient // Only for permission checks, don't serialize to XML
|
||||||
public String getId() {
|
public String getId() {
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitRepositoryConfig;
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
import sonia.scm.repository.GitUtil;
|
import sonia.scm.repository.GitUtil;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
@@ -49,20 +50,12 @@ public class GitContext implements Closeable, RepositoryProvider
|
|||||||
private static final Logger logger =
|
private static final Logger logger =
|
||||||
LoggerFactory.getLogger(GitContext.class);
|
LoggerFactory.getLogger(GitContext.class);
|
||||||
|
|
||||||
//~--- constructors ---------------------------------------------------------
|
public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider, GitConfig config)
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs ...
|
|
||||||
*
|
|
||||||
*
|
|
||||||
* @param directory
|
|
||||||
* @param repository
|
|
||||||
*/
|
|
||||||
public GitContext(File directory, Repository repository, GitRepositoryConfigStoreProvider storeProvider)
|
|
||||||
{
|
{
|
||||||
this.directory = directory;
|
this.directory = directory;
|
||||||
this.repository = repository;
|
this.repository = repository;
|
||||||
this.storeProvider = storeProvider;
|
this.storeProvider = storeProvider;
|
||||||
|
this.config = config;
|
||||||
}
|
}
|
||||||
|
|
||||||
//~--- methods --------------------------------------------------------------
|
//~--- methods --------------------------------------------------------------
|
||||||
@@ -126,12 +119,17 @@ public class GitContext implements Closeable, RepositoryProvider
|
|||||||
storeProvider.get(repository).set(newConfig);
|
storeProvider.get(repository).set(newConfig);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
GitConfig getGlobalConfig() {
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
//~--- fields ---------------------------------------------------------------
|
//~--- fields ---------------------------------------------------------------
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private final File directory;
|
private final File directory;
|
||||||
private final Repository repository;
|
private final Repository repository;
|
||||||
private final GitRepositoryConfigStoreProvider storeProvider;
|
private final GitRepositoryConfigStoreProvider storeProvider;
|
||||||
|
private final GitConfig config;
|
||||||
|
|
||||||
/** Field description */
|
/** Field description */
|
||||||
private org.eclipse.jgit.lib.Repository gitRepository;
|
private org.eclipse.jgit.lib.Repository gitRepository;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ class GitContextFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
GitContext create(Repository repository) {
|
GitContext create(Repository repository) {
|
||||||
return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider);
|
return new GitContext(handler.getDirectory(repository.getId()), repository, storeProvider, handler.getConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -30,14 +30,16 @@ import org.eclipse.jgit.api.Git;
|
|||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
import org.eclipse.jgit.attributes.FilterCommandRegistry;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.slf4j.Logger;
|
|
||||||
import org.slf4j.LoggerFactory;
|
|
||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
|
import sonia.scm.ContextEntry;
|
||||||
import sonia.scm.NoChangesMadeException;
|
import sonia.scm.NoChangesMadeException;
|
||||||
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
import sonia.scm.repository.GitRepositoryHandler;
|
import sonia.scm.repository.GitRepositoryHandler;
|
||||||
import sonia.scm.repository.GitWorkingCopyFactory;
|
import sonia.scm.repository.GitWorkingCopyFactory;
|
||||||
import sonia.scm.repository.InternalRepositoryException;
|
import sonia.scm.repository.InternalRepositoryException;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
@@ -49,21 +51,22 @@ import java.util.concurrent.locks.Lock;
|
|||||||
|
|
||||||
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
|
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
|
||||||
|
|
||||||
private static final Logger LOG = LoggerFactory.getLogger(GitModifyCommand.class);
|
|
||||||
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
|
private static final Striped<Lock> REGISTER_LOCKS = Striped.lock(5);
|
||||||
|
|
||||||
private final GitWorkingCopyFactory workingCopyFactory;
|
private final GitWorkingCopyFactory workingCopyFactory;
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
private final LfsBlobStoreFactory lfsBlobStoreFactory;
|
||||||
|
private final GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
GitModifyCommand(GitContext context, GitRepositoryHandler repositoryHandler, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
|
||||||
this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory);
|
this(context, repositoryHandler.getWorkingCopyFactory(), lfsBlobStoreFactory, gitRepositoryConfigStoreProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory) {
|
GitModifyCommand(GitContext context, GitWorkingCopyFactory workingCopyFactory, LfsBlobStoreFactory lfsBlobStoreFactory, GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider) {
|
||||||
super(context);
|
super(context);
|
||||||
this.workingCopyFactory = workingCopyFactory;
|
this.workingCopyFactory = workingCopyFactory;
|
||||||
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
this.lfsBlobStoreFactory = lfsBlobStoreFactory;
|
||||||
|
this.gitRepositoryConfigStoreProvider = gitRepositoryConfigStoreProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -85,19 +88,49 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
|
|||||||
@Override
|
@Override
|
||||||
String run() throws IOException {
|
String run() throws IOException {
|
||||||
getClone().getRepository().getFullBranch();
|
getClone().getRepository().getFullBranch();
|
||||||
|
|
||||||
|
boolean initialCommit = getClone().getRepository().getRefDatabase().getRefs().isEmpty();
|
||||||
|
|
||||||
if (!StringUtils.isEmpty(request.getExpectedRevision())
|
if (!StringUtils.isEmpty(request.getExpectedRevision())
|
||||||
&& !request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
&& !request.getExpectedRevision().equals(getCurrentRevision().getName())) {
|
||||||
throw new ConcurrentModificationException("branch", request.getBranch() == null ? "default" : request.getBranch());
|
throw new ConcurrentModificationException(ContextEntry.ContextBuilder.entity("Branch", request.getBranch() == null ? "default" : request.getBranch()).in(repository).build());
|
||||||
}
|
}
|
||||||
for (ModifyCommandRequest.PartialRequest r : request.getRequests()) {
|
for (ModifyCommandRequest.PartialRequest r : request.getRequests()) {
|
||||||
r.execute(this);
|
r.execute(this);
|
||||||
}
|
}
|
||||||
failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()));
|
failIfNotChanged(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch()));
|
||||||
Optional<RevCommit> revCommit = doCommit(request.getCommitMessage(), request.getAuthor(), request.isSign());
|
Optional<RevCommit> revCommit = doCommit(request.getCommitMessage(), request.getAuthor(), request.isSign());
|
||||||
|
|
||||||
|
if (initialCommit) {
|
||||||
|
handleBranchForInitialCommit();
|
||||||
|
}
|
||||||
|
|
||||||
push();
|
push();
|
||||||
return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name();
|
return revCommit.orElseThrow(() -> new NoChangesMadeException(repository, ModifyWorker.this.request.getBranch())).name();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void handleBranchForInitialCommit() {
|
||||||
|
String branch = StringUtils.isNotBlank(request.getBranch()) ? request.getBranch() : context.getGlobalConfig().getDefaultBranch();
|
||||||
|
if (StringUtils.isNotBlank(branch)) {
|
||||||
|
try {
|
||||||
|
getClone().checkout().setName(branch).setCreateBranch(true).call();
|
||||||
|
setBranchInConfig(branch);
|
||||||
|
} catch (GitAPIException e) {
|
||||||
|
throw new InternalRepositoryException(repository, "could not create default branch for initial commit", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void setBranchInConfig(String branch) {
|
||||||
|
ConfigurationStore<GitRepositoryConfig> store = gitRepositoryConfigStoreProvider
|
||||||
|
.get(repository);
|
||||||
|
GitRepositoryConfig gitRepositoryConfig = store
|
||||||
|
.getOptional()
|
||||||
|
.orElse(new GitRepositoryConfig());
|
||||||
|
gitRepositoryConfig.setDefaultBranch(branch);
|
||||||
|
store.set(gitRepositoryConfig);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addFileToScm(String name, Path file) {
|
public void addFileToScm(String name, Path file) {
|
||||||
addToGitWithLfsSupport(name, file);
|
addToGitWithLfsSupport(name, file);
|
||||||
|
|||||||
@@ -67,7 +67,11 @@ class GitWorkingCopyInitializer {
|
|||||||
Ref head = clone.exactRef(Constants.HEAD);
|
Ref head = clone.exactRef(Constants.HEAD);
|
||||||
|
|
||||||
if (head == null || !head.isSymbolic() || (initialBranch != null && !head.getTarget().getName().endsWith(initialBranch))) {
|
if (head == null || !head.isSymbolic() || (initialBranch != null && !head.getTarget().getName().endsWith(initialBranch))) {
|
||||||
throw notFound(entity("Branch", initialBranch).in(context.getRepository()));
|
if (clone.getRefDatabase().getRefs().isEmpty()) {
|
||||||
|
LOG.warn("could not initialize empty clone with given branch {}; this has to be handled later on", initialBranch);
|
||||||
|
} else {
|
||||||
|
throw notFound(entity("Branch", initialBranch).in(context.getRepository()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ParentAndClone<>(null, clone, target);
|
return new ParentAndClone<>(null, clone, target);
|
||||||
|
|||||||
@@ -24,12 +24,13 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { WithTranslation, withTranslation } from "react-i18next";
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
import { Links } from "@scm-manager/ui-types";
|
import { Links } from "@scm-manager/ui-types";
|
||||||
import { InputField, Checkbox } from "@scm-manager/ui-components";
|
import { InputField, Checkbox, validation as validator } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
type Configuration = {
|
type Configuration = {
|
||||||
repositoryDirectory?: string;
|
repositoryDirectory?: string;
|
||||||
gcExpression?: string;
|
gcExpression?: string;
|
||||||
nonFastForwardDisallowed: boolean;
|
nonFastForwardDisallowed: boolean;
|
||||||
|
defaultBranch: string;
|
||||||
_links: Links;
|
_links: Links;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -68,8 +69,21 @@ class GitConfigurationForm extends React.Component<Props, State> {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
onDefaultBranchChange = (value: string) => {
|
||||||
|
this.setState(
|
||||||
|
{
|
||||||
|
defaultBranch: value
|
||||||
|
},
|
||||||
|
() => this.props.onConfigurationChange(this.state, this.isValidDefaultBranch())
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
isValidDefaultBranch = () => {
|
||||||
|
return validator.isBranchValid(this.state.defaultBranch);
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { gcExpression, nonFastForwardDisallowed } = this.state;
|
const { gcExpression, nonFastForwardDisallowed, defaultBranch } = this.state;
|
||||||
const { readOnly, t } = this.props;
|
const { readOnly, t } = this.props;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -90,6 +104,16 @@ class GitConfigurationForm extends React.Component<Props, State> {
|
|||||||
onChange={this.onNonFastForwardDisallowed}
|
onChange={this.onNonFastForwardDisallowed}
|
||||||
disabled={readOnly}
|
disabled={readOnly}
|
||||||
/>
|
/>
|
||||||
|
<InputField
|
||||||
|
name="defaultBranch"
|
||||||
|
label={t("scm-git-plugin.config.defaultBranch")}
|
||||||
|
helpText={t("scm-git-plugin.config.defaultBranchHelpText")}
|
||||||
|
value={defaultBranch}
|
||||||
|
onChange={this.onDefaultBranchChange}
|
||||||
|
disabled={readOnly}
|
||||||
|
validationError={!this.isValidDefaultBranch()}
|
||||||
|
errorMessage={t("scm-git-plugin.config.defaultBranchValidationError")}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
"gcExpressionHelpText": "Benutze Quartz Cron Ausdrücke (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK), um git GC regelmäßig auszuführen.",
|
||||||
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
"nonFastForwardDisallowed": "Deaktiviere \"Non Fast-Forward\"",
|
||||||
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
"nonFastForwardDisallowedHelpText": "Git Pushes ablehnen, die nicht \"fast-forward\" sind, wie \"--force\".",
|
||||||
|
"defaultBranch": "Default Branch",
|
||||||
|
"defaultBranchHelpText": "Dieser Name wird bei der Initialisierung neuer Git Repositories genutzt. Er hat keine weiteren Auswirkungen (insbesondere hat er keinen Einfluss auf den Branchnamen bei leeren Repositories).",
|
||||||
|
"defaultBranchValidationError": "Dies ist kein valider Branchname",
|
||||||
"disabled": "Deaktiviert",
|
"disabled": "Deaktiviert",
|
||||||
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
"disabledHelpText": "Aktiviere oder deaktiviere das Git Plugin",
|
||||||
"submit": "Speichern"
|
"submit": "Speichern"
|
||||||
|
|||||||
@@ -24,6 +24,9 @@
|
|||||||
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
|
"gcExpressionHelpText": "Use Quartz Cron Expressions (SECOND MINUTE HOUR DAYOFMONTH MONTH DAYOFWEEK) to run git gc in intervals.",
|
||||||
"nonFastForwardDisallowed": "Disallow Non Fast-Forward",
|
"nonFastForwardDisallowed": "Disallow Non Fast-Forward",
|
||||||
"nonFastForwardDisallowedHelpText": "Reject git pushes which are non fast-forward such as --force.",
|
"nonFastForwardDisallowedHelpText": "Reject git pushes which are non fast-forward such as --force.",
|
||||||
|
"defaultBranch": "Default Branch",
|
||||||
|
"defaultBranchHelpText": "This name will be used for the initialization of new git repositories. It has no effect otherwise (especially this cannot change the initial branch name for empty repositories).",
|
||||||
|
"defaultBranchValidationError": "This is not a valid branch name",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
"disabledHelpText": "Enable or disable the Git plugin",
|
"disabledHelpText": "Enable or disable the Git plugin",
|
||||||
"submit": "Submit"
|
"submit": "Submit"
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
@@ -261,7 +261,7 @@ public class GitConfigResourceTest {
|
|||||||
private MockHttpResponse put() throws URISyntaxException {
|
private MockHttpResponse put() throws URISyntaxException {
|
||||||
MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2)
|
MockHttpRequest request = MockHttpRequest.put("/" + GitConfigResource.GIT_CONFIG_PATH_V2)
|
||||||
.contentType(GitVndMediaType.GIT_CONFIG)
|
.contentType(GitVndMediaType.GIT_CONFIG)
|
||||||
.content("{\"disabled\":true}".getBytes());
|
.content("{\"disabled\":true, \"defaultBranch\":\"main\"}".getBytes());
|
||||||
|
|
||||||
MockHttpResponse response = new MockHttpResponse();
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
dispatcher.invoke(request, response);
|
dispatcher.invoke(request, response);
|
||||||
|
|||||||
@@ -21,24 +21,17 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.repository.spi;
|
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.GitConfig;
|
||||||
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
|
||||||
@@ -69,7 +62,7 @@ public class AbstractGitCommandTestBase extends ZippedRepositoryTestBase
|
|||||||
{
|
{
|
||||||
if (context == null)
|
if (context == null)
|
||||||
{
|
{
|
||||||
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()));
|
context = new GitContext(repositoryDirectory, repository, new GitRepositoryConfigStoreProvider(InMemoryConfigurationStoreFactory.create()), new GitConfig());
|
||||||
}
|
}
|
||||||
|
|
||||||
return context;
|
return context;
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import org.junit.Ignore;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())));
|
GitPullCommand pull = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()));
|
||||||
PullCommandRequest req = new PullCommandRequest();
|
PullCommandRequest req = new PullCommandRequest();
|
||||||
req.setRemoteRepository(outgoingRepository);
|
req.setRemoteRepository(outgoingRepository);
|
||||||
pull.pull(req);
|
pull.pull(req);
|
||||||
@@ -177,7 +177,7 @@ public class GitIncomingCommandTest
|
|||||||
|
|
||||||
private GitIncomingCommand createCommand() {
|
private GitIncomingCommand createCommand() {
|
||||||
return new GitIncomingCommand(
|
return new GitIncomingCommand(
|
||||||
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
new GitContext(incomingDirectory, incomingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||||
handler,
|
handler,
|
||||||
GitTestHelper.createConverterFactory()
|
GitTestHelper.createConverterFactory()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.Modifications;
|
import sonia.scm.repository.Modifications;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -42,8 +43,8 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
public void init() {
|
public void init() {
|
||||||
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null));
|
incomingModificationsCommand = new GitModificationsCommand(new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
|
||||||
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null));
|
outgoingModificationsCommand = new GitModificationsCommand(new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -106,11 +107,11 @@ public class GitModificationsCommandTest extends AbstractRemoteCommandTestBase {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void pushOutgoingAndPullIncoming() throws IOException {
|
void pushOutgoingAndPullIncoming() throws IOException {
|
||||||
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
GitPushCommand cmd = new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
PushCommandRequest request = new PushCommandRequest();
|
PushCommandRequest request = new PushCommandRequest();
|
||||||
request.setRemoteRepository(incomingRepository);
|
request.setRemoteRepository(incomingRepository);
|
||||||
cmd.push(request);
|
cmd.push(request);
|
||||||
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null));
|
GitPullCommand pullCommand = new GitPullCommand(handler, new GitContext(incomingDirectory, incomingRepository, null, new GitConfig()));
|
||||||
PullCommandRequest pullRequest = new PullCommandRequest();
|
PullCommandRequest pullRequest = new PullCommandRequest();
|
||||||
pullRequest.setRemoteRepository(incomingRepository);
|
pullRequest.setRemoteRepository(incomingRepository);
|
||||||
pullCommand.pull(pullRequest);
|
pullCommand.pull(pullRequest);
|
||||||
|
|||||||
@@ -24,61 +24,26 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.api.errors.CanceledException;
|
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
|
||||||
import org.eclipse.jgit.lib.GpgSignature;
|
|
||||||
import org.eclipse.jgit.lib.GpgSigner;
|
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.transport.CredentialsProvider;
|
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
|
||||||
import org.junit.BeforeClass;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.AlreadyExistsException;
|
import sonia.scm.AlreadyExistsException;
|
||||||
import sonia.scm.BadRequestException;
|
import sonia.scm.BadRequestException;
|
||||||
import sonia.scm.ConcurrentModificationException;
|
import sonia.scm.ConcurrentModificationException;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.security.PublicKey;
|
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommandTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@BeforeClass
|
|
||||||
public static void setSigner() {
|
|
||||||
GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateCommit() throws IOException, GitAPIException {
|
public void shouldCreateCommit() throws IOException, GitAPIException {
|
||||||
@@ -362,30 +327,4 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
|
|||||||
assertThat(lastCommit.getRawGpgSignature()).isNullOrEmpty();
|
assertThat(lastCommit.getRawGpgSignature()).isNullOrEmpty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
|
||||||
try (Git git = new Git(createContext().open())) {
|
|
||||||
RevCommit lastCommit = getLastCommit(git);
|
|
||||||
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
|
||||||
RevCommit commit = walk.parseCommit(lastCommit);
|
|
||||||
ObjectId treeId = commit.getTree().getId();
|
|
||||||
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
|
||||||
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
|
||||||
private interface TreeAssertions {
|
|
||||||
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,97 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import com.github.sdorra.shiro.ShiroRule;
|
||||||
|
import com.github.sdorra.shiro.SubjectAware;
|
||||||
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
|
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||||
|
import org.eclipse.jgit.lib.GpgSigner;
|
||||||
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.rules.TemporaryFolder;
|
||||||
|
import sonia.scm.repository.GitTestHelper;
|
||||||
|
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
||||||
|
import sonia.scm.repository.work.WorkdirProvider;
|
||||||
|
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static sonia.scm.repository.spi.GitRepositoryConfigStoreProviderTestUtil.createGitRepositoryConfigStoreProvider;
|
||||||
|
|
||||||
|
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
||||||
|
class GitModifyCommandTestBase extends AbstractGitCommandTestBase {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
||||||
|
@Rule
|
||||||
|
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
||||||
|
@Rule
|
||||||
|
public ShiroRule shiro = new ShiroRule();
|
||||||
|
|
||||||
|
final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void setSigner() {
|
||||||
|
GpgSigner.setDefault(new GitTestHelper.SimpleGpgSigner());
|
||||||
|
}
|
||||||
|
|
||||||
|
RevCommit getLastCommit(Git git) throws GitAPIException, IOException {
|
||||||
|
return git.log().setMaxCount(1).call().iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
GitModifyCommand createCommand() {
|
||||||
|
return new GitModifyCommand(
|
||||||
|
createContext(),
|
||||||
|
new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())),
|
||||||
|
lfsBlobStoreFactory,
|
||||||
|
createGitRepositoryConfigStoreProvider());
|
||||||
|
}
|
||||||
|
|
||||||
|
void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
RevCommit lastCommit = getLastCommit(git);
|
||||||
|
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
||||||
|
RevCommit commit = walk.parseCommit(lastCommit);
|
||||||
|
ObjectId treeId = commit.getTree().getId();
|
||||||
|
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
||||||
|
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
interface TreeAssertions {
|
||||||
|
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -24,22 +24,15 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
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.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.store.Blob;
|
import sonia.scm.store.Blob;
|
||||||
import sonia.scm.store.BlobStore;
|
import sonia.scm.store.BlobStore;
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
@@ -51,17 +44,7 @@ import static org.mockito.ArgumentMatchers.any;
|
|||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommand_LFSTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
public static void registerFilter() {
|
public static void registerFilter() {
|
||||||
@@ -126,14 +109,6 @@ public class GitModifyCommand_LFSTest extends AbstractGitCommandTestBase {
|
|||||||
return command.execute(request);
|
return command.execute(request);
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getZippedRepositoryResource() {
|
protected String getZippedRepositoryResource() {
|
||||||
return "sonia/scm/repository/spi/scm-git-spi-lfs-test.zip";
|
return "sonia/scm/repository/spi/scm-git-spi-lfs-test.zip";
|
||||||
|
|||||||
@@ -24,42 +24,21 @@
|
|||||||
|
|
||||||
package sonia.scm.repository.spi;
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
import com.github.sdorra.shiro.ShiroRule;
|
|
||||||
import com.github.sdorra.shiro.SubjectAware;
|
|
||||||
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.errors.CorruptObjectException;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
|
||||||
import org.eclipse.jgit.treewalk.CanonicalTreeParser;
|
|
||||||
import org.junit.Rule;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.rules.TemporaryFolder;
|
|
||||||
import sonia.scm.repository.Person;
|
import sonia.scm.repository.Person;
|
||||||
import sonia.scm.repository.work.NoneCachingWorkingCopyPool;
|
|
||||||
import sonia.scm.repository.work.WorkdirProvider;
|
|
||||||
import sonia.scm.web.lfs.LfsBlobStoreFactory;
|
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
|
|
||||||
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
|
public class GitModifyCommand_withEmptyRepositoryTest extends GitModifyCommandTestBase {
|
||||||
public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommandTestBase {
|
|
||||||
|
|
||||||
@Rule
|
|
||||||
public TemporaryFolder temporaryFolder = new TemporaryFolder();
|
|
||||||
@Rule
|
|
||||||
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
|
|
||||||
@Rule
|
|
||||||
public ShiroRule shiro = new ShiroRule();
|
|
||||||
|
|
||||||
private final LfsBlobStoreFactory lfsBlobStoreFactory = mock(LfsBlobStoreFactory.class);
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
|
public void shouldCreateNewFileInEmptyRepository() throws IOException, GitAPIException {
|
||||||
@@ -79,34 +58,65 @@ public class GitModifyCommand_withEmptyRepositoryTest extends AbstractGitCommand
|
|||||||
assertInTree(assertions);
|
assertInTree(assertions);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitOnMasterByDefault() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("");
|
||||||
|
|
||||||
|
executeModifyCommand();
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/master");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitWithConfiguredDefaultBranch() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("main");
|
||||||
|
|
||||||
|
executeModifyCommand();
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/main");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateCommitWithBranchFromRequestIfPresent() throws IOException, GitAPIException {
|
||||||
|
createContext().getGlobalConfig().setDefaultBranch("main");
|
||||||
|
|
||||||
|
ModifyCommandRequest request = createRequest();
|
||||||
|
request.setBranch("different");
|
||||||
|
createCommand().execute(request);
|
||||||
|
|
||||||
|
try (Git git = new Git(createContext().open())) {
|
||||||
|
List<Ref> branches = git.branchList().call();
|
||||||
|
assertThat(branches).extracting("name").containsExactly("refs/heads/different");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getZippedRepositoryResource() {
|
protected String getZippedRepositoryResource() {
|
||||||
return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
|
return "sonia/scm/repository/spi/scm-git-empty-repo.zip";
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
|
@Override
|
||||||
try (Git git = new Git(createContext().open())) {
|
RevCommit getLastCommit(Git git) throws GitAPIException, IOException {
|
||||||
RevCommit lastCommit = getLastCommit(git);
|
return git.log().setMaxCount(1).all().call().iterator().next();
|
||||||
try (RevWalk walk = new RevWalk(git.getRepository())) {
|
|
||||||
RevCommit commit = walk.parseCommit(lastCommit);
|
|
||||||
ObjectId treeId = commit.getTree().getId();
|
|
||||||
try (ObjectReader reader = git.getRepository().newObjectReader()) {
|
|
||||||
assertions.checkAssertions(new CanonicalTreeParser(null, reader, treeId));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private RevCommit getLastCommit(Git git) throws GitAPIException {
|
private void executeModifyCommand() throws IOException {
|
||||||
return git.log().setMaxCount(1).call().iterator().next();
|
createCommand().execute(createRequest());
|
||||||
}
|
}
|
||||||
|
|
||||||
private GitModifyCommand createCommand() {
|
private ModifyCommandRequest createRequest() throws IOException {
|
||||||
return new GitModifyCommand(createContext(), new SimpleGitWorkingCopyFactory(new NoneCachingWorkingCopyPool(new WorkdirProvider())), lfsBlobStoreFactory);
|
File newFile = Files.write(temporaryFolder.newFile().toPath(), "new content".getBytes()).toFile();
|
||||||
}
|
|
||||||
|
|
||||||
@FunctionalInterface
|
ModifyCommandRequest request = new ModifyCommandRequest();
|
||||||
private interface TreeAssertions {
|
request.setCommitMessage("initial commit");
|
||||||
void checkAssertions(CanonicalTreeParser treeParser) throws CorruptObjectException;
|
request.addRequest(new ModifyCommandRequest.CreateFileRequest("new_file", newFile, false));
|
||||||
|
request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det"));
|
||||||
|
return request;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ import org.eclipse.jgit.revwalk.RevCommit;
|
|||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
import sonia.scm.repository.ChangesetPagingResult;
|
import sonia.scm.repository.ChangesetPagingResult;
|
||||||
import sonia.scm.repository.GitChangesetConverterFactory;
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.GitTestHelper;
|
import sonia.scm.repository.GitTestHelper;
|
||||||
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
import sonia.scm.store.InMemoryConfigurationStoreFactory;
|
||||||
|
|
||||||
@@ -99,7 +99,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
commit(outgoing, "added a");
|
commit(outgoing, "added a");
|
||||||
|
|
||||||
GitPushCommand push = new GitPushCommand(handler,
|
GitPushCommand push = new GitPushCommand(handler,
|
||||||
new GitContext(outgoingDirectory, outgoingRepository, null)
|
new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig())
|
||||||
);
|
);
|
||||||
PushCommandRequest req = new PushCommandRequest();
|
PushCommandRequest req = new PushCommandRequest();
|
||||||
|
|
||||||
@@ -154,7 +154,7 @@ public class GitOutgoingCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
private GitOutgoingCommand createCommand()
|
private GitOutgoingCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitOutgoingCommand(
|
return new GitOutgoingCommand(
|
||||||
new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory())),
|
new GitContext(outgoingDirectory, outgoingRepository, new GitRepositoryConfigStoreProvider(new InMemoryConfigurationStoreFactory()), new GitConfig()),
|
||||||
handler,
|
handler,
|
||||||
GitTestHelper.createConverterFactory()
|
GitTestHelper.createConverterFactory()
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ package sonia.scm.repository.spi;
|
|||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import sonia.scm.repository.GitConfig;
|
||||||
import sonia.scm.repository.api.PushResponse;
|
import sonia.scm.repository.api.PushResponse;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -89,6 +90,6 @@ public class GitPushCommandTest extends AbstractRemoteCommandTestBase
|
|||||||
*/
|
*/
|
||||||
private GitPushCommand createCommand()
|
private GitPushCommand createCommand()
|
||||||
{
|
{
|
||||||
return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null));
|
return new GitPushCommand(handler, new GitContext(outgoingDirectory, outgoingRepository, null, new GitConfig()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*
|
||||||
|
* MIT License
|
||||||
|
*
|
||||||
|
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||||
|
*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
* of this software and associated documentation files (the "Software"), to deal
|
||||||
|
* in the Software without restriction, including without limitation the rights
|
||||||
|
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
* copies of the Software, and to permit persons to whom the Software is
|
||||||
|
* furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all
|
||||||
|
* copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
* SOFTWARE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package sonia.scm.repository.spi;
|
||||||
|
|
||||||
|
import sonia.scm.api.v2.resources.GitRepositoryConfigStoreProvider;
|
||||||
|
import sonia.scm.repository.GitRepositoryConfig;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.store.ConfigurationStore;
|
||||||
|
import sonia.scm.store.InMemoryConfigurationStore;
|
||||||
|
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
class GitRepositoryConfigStoreProviderTestUtil {
|
||||||
|
|
||||||
|
static GitRepositoryConfigStoreProvider createGitRepositoryConfigStoreProvider() {
|
||||||
|
GitRepositoryConfigStoreProvider gitRepositoryConfigStoreProvider = mock(GitRepositoryConfigStoreProvider.class);
|
||||||
|
HashMap<String, ConfigurationStore<GitRepositoryConfig>> storeMap = new HashMap<>();
|
||||||
|
when(gitRepositoryConfigStoreProvider.get(any())).thenAnswer(invocation -> storeMap.computeIfAbsent(invocation.getArgument(0, Repository.class).getId(), id -> new InMemoryConfigurationStore<>()));
|
||||||
|
return gitRepositoryConfigStoreProvider;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -28,6 +28,12 @@ export const isNameValid = (name: string) => {
|
|||||||
return nameRegex.test(name);
|
return nameRegex.test(name);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const branchRegex = /^[\w-,;\]{}@&+=$#`|<>]([\w-,;\]{}@&+=$#`|<>/.]*[\w-,;\]{}@&+=$#`|<>])?$/;
|
||||||
|
|
||||||
|
export const isBranchValid = (name: string) => {
|
||||||
|
return branchRegex.test(name);
|
||||||
|
};
|
||||||
|
|
||||||
const mailRegex = /^[ -~]+@[A-Za-z0-9][\w\-.]*\.[A-Za-z0-9][A-Za-z0-9-]+$/;
|
const mailRegex = /^[ -~]+@[A-Za-z0-9][\w\-.]*\.[A-Za-z0-9][A-Za-z0-9-]+$/;
|
||||||
|
|
||||||
export const isMailValid = (mail: string) => {
|
export const isMailValid = (mail: string) => {
|
||||||
|
|||||||
@@ -124,15 +124,13 @@ class BranchForm extends React.Component<Props, State> {
|
|||||||
|
|
||||||
handleSourceChange = (source: string) => {
|
handleSourceChange = (source: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
...this.state,
|
|
||||||
source
|
source
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
handleNameChange = (name: string) => {
|
handleNameChange = (name: string) => {
|
||||||
this.setState({
|
this.setState({
|
||||||
nameValidationError: !validator.isNameValid(name),
|
nameValidationError: !validator.isBranchValid(name),
|
||||||
...this.state,
|
|
||||||
name
|
name
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -35,16 +35,14 @@ import org.hibernate.validator.constraints.Length;
|
|||||||
import javax.validation.constraints.NotEmpty;
|
import javax.validation.constraints.NotEmpty;
|
||||||
import javax.validation.constraints.Pattern;
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
|
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
@NoArgsConstructor
|
@NoArgsConstructor
|
||||||
@SuppressWarnings("java:S2160") // we do not need this for dto
|
@SuppressWarnings("java:S2160") // we do not need this for dto
|
||||||
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
|
@NotEmpty
|
||||||
@Length(min = 1, max = 100)
|
@Length(min = 1, max = 100)
|
||||||
@Pattern(regexp = VALID_BRANCH_NAMES)
|
@Pattern(regexp = VALID_BRANCH_NAMES)
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
@@ -31,7 +31,7 @@ import javax.validation.constraints.NotEmpty;
|
|||||||
|
|
||||||
import javax.validation.constraints.Pattern;
|
import javax.validation.constraints.Pattern;
|
||||||
|
|
||||||
import static sonia.scm.api.v2.resources.BranchDto.VALID_BRANCH_NAMES;
|
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
@Getter
|
@Getter
|
||||||
@Setter
|
@Setter
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
* SOFTWARE.
|
* SOFTWARE.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package sonia.scm.api.v2.resources;
|
package sonia.scm.api.v2.resources;
|
||||||
|
|
||||||
import org.junit.jupiter.params.ParameterizedTest;
|
import org.junit.jupiter.params.ParameterizedTest;
|
||||||
@@ -29,6 +29,7 @@ import org.junit.jupiter.params.provider.ValueSource;
|
|||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static sonia.scm.repository.Branch.VALID_BRANCH_NAMES;
|
||||||
|
|
||||||
class BranchDtoTest {
|
class BranchDtoTest {
|
||||||
|
|
||||||
@@ -54,10 +55,11 @@ class BranchDtoTest {
|
|||||||
"val{d",
|
"val{d",
|
||||||
"val{}d",
|
"val{}d",
|
||||||
"val|kill",
|
"val|kill",
|
||||||
"val}"
|
"val}",
|
||||||
|
"va/li/d"
|
||||||
})
|
})
|
||||||
void shouldAcceptValidBranchName(String branchName) {
|
void shouldAcceptValidBranchName(String branchName) {
|
||||||
assertTrue(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
assertTrue(branchName.matches(VALID_BRANCH_NAMES));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ParameterizedTest
|
@ParameterizedTest
|
||||||
@@ -70,6 +72,6 @@ class BranchDtoTest {
|
|||||||
"val id"
|
"val id"
|
||||||
})
|
})
|
||||||
void shouldRejectInvalidBranchName(String branchName) {
|
void shouldRejectInvalidBranchName(String branchName) {
|
||||||
assertFalse(branchName.matches(BranchDto.VALID_BRANCH_NAMES));
|
assertFalse(branchName.matches(VALID_BRANCH_NAMES));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user