Merge branch 'develop' into feature/template_plugin

This commit is contained in:
Eduard Heimbuch
2020-09-08 15:44:47 +02:00
51 changed files with 38889 additions and 38437 deletions

View File

@@ -4,6 +4,15 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Added
- Tags now have date information attached ([#1305](https://github.com/scm-manager/scm-manager/pull/1305))
- Add support for scroll anchors in url hash of diff page ([#1304](https://github.com/scm-manager/scm-manager/pull/1304))
- Documentation regarding data and plugin migration from v1 to v2 ([#1321](https://github.com/scm-manager/scm-manager/pull/1321))
### Fixed
- Redirection to requested page after login in anonymous mode
## [2.4.1] - 2020-09-01
### Added
- Add "sonia.scm.restart-migration.wait" to set wait in milliseconds before restarting scm-server after migration ([#1308](https://github.com/scm-manager/scm-manager/pull/1308))

View File

@@ -3,9 +3,15 @@ title: Migrate from v1 to v2
subtitle: How to use the Migration-Wizard
---
# Preparation
To upgrade an SCM-Manager from version 1 to version 2, some changes have to be made according the home directory of the SCM-Manager. So before you start, **make sure that you have an up to date backup of your SCM home folder!**
Before the migration process can be started, the last running version of SCM-Manager had to be (at least) 1.60. Data of older versions cannot be migrated automatically. If this is the case, you can stop version 1 and start a version 2 SCM-Manager (make sure that you have configured the same SCM home folder). When SCM-Manager starts for the first time, you have to choose how to migrate your existing repositories. The background of this is the following:
Before the migration process can be started, the last running version of SCM-Manager had to be (at least) 1.60. Data of older versions cannot be migrated automatically. If this is the case, you can stop version 1 and start a version 2 SCM-Manager (make sure that you have configured the same SCM home folder).
# Repository migration
When SCM-Manager starts for the first time, you have to choose how to migrate your existing repositories. The background of this is the following:
While in version 1 of SCM-Manager the repositories were stored in a directory according to their type (`git`, `hg` or `svn`) and their name, in version 2 the directory is independent of the type and name. Therefore a repository is no longer named with an arbitrary number of name parts devided by slashes (`/`), but it has a namespace and a name (both of which must not contain slashes). The namespace should be used to group your repositories (for example you can use this to distinguish between the types of repositories like *git* and *hg* like version 1 or to assign them to different projects or users).
@@ -37,6 +43,16 @@ In the figure you can see an example of the page. We tried to guess meaningful n
The probably most safe strategy (but also the most costly) is *COPY*. The old folder of the repository will be kept and all data will be copied to the new default folder (so this also is the default). *MOVE* and *INLINE* are more efficient. When you have a lot of repositories, maybe you will take the chance to clean them up and *IGNORE* or even *DELETE* old stuff.
# Migration of other data
For version 2 of SCM-Manager we introduced a new way to store data for repositories. We did our best to migrate old data like settings in plugins, so that nothing will be lost during update. What we did **not** do is to automatically install the new versions of your plugins. When you start your new instance, you will get a clean instance. You can install your new plugins from the administration page. Any plugin related data or settings will be migrated automatically.
# Manual plugin installation
If however you have to install plugins manually (for example because you cannot log in without the LDAP plugin), you can download them from the [plugins section](https://www.scm-manager.org/plugins/#categories) on our homepage. The download can be found in the "Releases" section of each plugin. Just store the `smp` file in the `plugin` directory of your SCM home and restart your server.
# Huge number of repositories
If you have more than 100 Repositories to migrate, you may have to adapt some configuration and increase the limit of jetty form keys. You can do this by setting the `maxFormKeys` and `maxFormContentSize` of the webapp in `conf/server-config.xml`. You have to add the keys to the `WebAppContext` with the id `"scm-webapp"` e.g.:
```

View File

@@ -5,5 +5,5 @@
],
"npmClient": "yarn",
"useWorkspaces": true,
"version": "2.4.1"
"version": "2.5.0-SNAPSHOT"
}

View File

@@ -903,7 +903,7 @@
<properties>
<!-- test libraries -->
<mockito.version>2.28.2</mockito.version>
<mockito.version>3.5.6</mockito.version>
<hamcrest.version>2.1</hamcrest.version>
<junit.version>5.6.2</junit.version>
@@ -930,7 +930,7 @@
<!-- security libraries -->
<ssp.version>1.2.0</ssp.version>
<shiro.version>1.5.3</shiro.version>
<shiro.version>1.6.0</shiro.version>
<!-- repository libraries -->
<jgit.version>5.6.1.202002131546-r-scm1</jgit.version>

View File

@@ -28,6 +28,8 @@ import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.ToString;
import java.util.Optional;
/**
* Represents a tag in a repository.
*
@@ -41,6 +43,7 @@ public final class Tag {
private final String name;
private final String revision;
private final Long date;
/**
* Constructs a new tag.
@@ -49,7 +52,41 @@ public final class Tag {
* @param revision tagged revision
*/
public Tag(String name, String revision) {
this(name, revision, null);
}
/**
* Constructs a new tag.
*
* @param name name of the tag
* @param revision tagged revision
* @param date the creation timestamp (milliseconds) of the tag
*
* @since 2.5.0
*/
public Tag(String name, String revision, Long date) {
this.name = name;
this.revision = revision;
this.date = date;
}
/**
* Depending on the underlying source code management system
* (like git or hg) and depending on the type of this tag
* (for example git has <i>lightweight</i> and <i>annotated</i>
* tags), this date has different meaning. For annotated tags
* in git, this is the date the tag was created. In other cases
* (for lightweight tags in git or all tags in hg) this is the
* date of the referenced changeset.
* <p>
* Please note, that the date is retrieved in a best-effort fashion.
* In certain situations (for example if this tag is announced in
* a pre or post receive hook), it might not be available.
* In these cases, this method returns an empty {@link Optional}.
*
* @since 2.5.0
*/
public Optional<Long> getDate() {
return Optional.ofNullable(date);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.it;
import groovy.util.logging.Slf4j;
@@ -178,9 +178,9 @@ public class RepositoryAccessITCase {
.isGreaterThan(0);
assertThat(response.body().jsonPath().getMap("_embedded.tags.find{it.name=='" + tagName + "'}"))
.as("assert tag name and revision")
.as("assert tag has attributes for name, revision, date and links")
.isNotNull()
.hasSize(3)
.hasSize(4)
.containsEntry("name", tagName)
.containsEntry("revision", changeset.getId());

View File

@@ -22,7 +22,7 @@
# SOFTWARE.
#
FROM adoptopenjdk/openjdk11:jdk-11.0.7_10-alpine-slim
FROM adoptopenjdk/openjdk11:jdk-11.0.8_10-alpine-slim
ENV SCM_HOME=/var/lib/scm
ENV CACHE_DIR=/var/cache/scm/work

View File

@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-git-plugin",
"private": true,
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.ts",
"scripts": {
@@ -20,6 +20,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
"@scm-manager/ui-plugins": "^2.4.1"
"@scm-manager/ui-plugins": "^2.5.0-SNAPSHOT"
}
}

View File

@@ -42,6 +42,8 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevTag;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
@@ -387,6 +389,31 @@ public final class GitUtil
return ref;
}
/**
* @since 2.5.0
*/
public static Long getTagTime(org.eclipse.jgit.lib.Repository repository, ObjectId objectId) throws IOException {
try (RevWalk walk = new RevWalk(repository)) {
return GitUtil.getTagTime(walk, objectId);
}
}
/**
* @since 2.5.0
*/
public static Long getTagTime(RevWalk revWalk, ObjectId objectId) throws IOException {
if (objectId != null) {
final RevObject revObject = revWalk.parseAny(objectId);
if (revObject instanceof RevTag) {
return ((RevTag) revObject).getTaggerIdent().getWhen().getTime();
} else if (revObject instanceof RevCommit) {
return getCommitTime((RevCommit) revObject);
}
}
return null;
}
/**
* Method description
*

View File

@@ -21,66 +21,75 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.api;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.Tag;
import java.io.IOException;
import java.util.List;
/**
* Git provider implementation of {@link HookTagProvider}.
*
* @since 1.50
*
* @author Sebastian Sdorra
* @since 1.50
*/
public class GitHookTagProvider implements HookTagProvider {
private static final Logger logger = LoggerFactory.getLogger(GitHookTagProvider.class);
private static final Logger LOG = LoggerFactory.getLogger(GitHookTagProvider.class);
private final List<Tag> createdTags;
private final List<Tag> deletedTags;
/**
* Constructs new instance.
*
*
* @param commands received commands
*/
public GitHookTagProvider(List<ReceiveCommand> commands) {
public GitHookTagProvider(List<ReceiveCommand> commands, Repository repository) {
ImmutableList.Builder<Tag> createdTagBuilder = ImmutableList.builder();
ImmutableList.Builder<Tag> deletedTagBuilder = ImmutableList.builder();
for ( ReceiveCommand rc : commands ){
for (ReceiveCommand rc : commands) {
String refName = rc.getRefName();
String tag = GitUtil.getTagName(refName);
if (Strings.isNullOrEmpty(tag)){
logger.debug("received ref name {} is not a tag", refName);
} else if (isCreate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag));
} else if (isDelete(rc)){
deletedTagBuilder.add(createTagFromOldId(rc, tag));
} else if (isUpdate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag));
deletedTagBuilder.add(createTagFromOldId(rc, tag));
if (Strings.isNullOrEmpty(tag)) {
LOG.debug("received ref name {} is not a tag", refName);
} else {
try {
if (isCreate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag, GitUtil.getTagTime(repository, rc.getNewId())));
} else if (isDelete(rc)) {
deletedTagBuilder.add(createTagFromOldId(rc, tag, GitUtil.getTagTime(repository, rc.getOldId())));
} else if (isUpdate(rc)) {
createdTagBuilder.add(createTagFromNewId(rc, tag, GitUtil.getTagTime(repository, rc.getNewId())));
deletedTagBuilder.add(createTagFromOldId(rc, tag, GitUtil.getTagTime(repository, rc.getOldId())));
}
} catch (IOException e) {
LOG.error("Could not read tag time", e);
}
}
}
createdTags = createdTagBuilder.build();
deletedTags = deletedTagBuilder.build();
}
private Tag createTagFromNewId(ReceiveCommand rc, String tag) {
return new Tag(tag, GitUtil.getId(rc.getNewId()));
private Tag createTagFromNewId(ReceiveCommand rc, String tag, Long tagTime) {
return new Tag(tag, GitUtil.getId(rc.getNewId()), tagTime);
}
private Tag createTagFromOldId(ReceiveCommand rc, String tag) {
return new Tag(tag, GitUtil.getId(rc.getOldId()));
private Tag createTagFromOldId(ReceiveCommand rc, String tag, Long tagTime) {
return new Tag(tag, GitUtil.getId(rc.getOldId()), tagTime);
}
private boolean isUpdate(ReceiveCommand rc) {

View File

@@ -103,7 +103,7 @@ public class GitHookContextProvider extends HookContextProvider
@Override
public HookTagProvider getTagProvider() {
return new GitHookTagProvider(receiveCommands);
return new GitHookTagProvider(receiveCommands, repository);
}
@Override

View File

@@ -31,7 +31,7 @@ import com.google.common.collect.Lists;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@@ -45,34 +45,28 @@ import java.util.List;
//~--- JDK imports ------------------------------------------------------------
/**
*
* @author Sebastian Sdorra
*/
public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
{
public class GitTagsCommand extends AbstractGitCommand implements TagsCommand {
/**
* Constructs ...
*
* @param context
*
* @param context
*/
public GitTagsCommand(GitContext context)
{
public GitTagsCommand(GitContext context) {
super(context);
}
//~--- get methods ----------------------------------------------------------
@Override
public List<Tag> getTags() throws IOException
{
public List<Tag> getTags() throws IOException {
List<Tag> tags = null;
RevWalk revWalk = null;
try
{
try {
final Git git = new Git(open());
revWalk = new RevWalk(git.getRepository());
@@ -81,13 +75,9 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
tags = Lists.transform(tagList,
new TransformFuntion(git.getRepository(), revWalk));
}
catch (GitAPIException ex)
{
} catch (GitAPIException ex) {
throw new InternalRepositoryException(repository, "could not read tags from repository", ex);
}
finally
{
} finally {
GitUtil.release(revWalk);
}
@@ -99,12 +89,10 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
/**
* Class description
*
*
* @version Enter version here..., 12/07/06
* @author Enter your name here...
* @author Enter your name here...
* @version Enter version here..., 12/07/06
*/
private static class TransformFuntion implements Function<Ref, Tag>
{
private static class TransformFuntion implements Function<Ref, Tag> {
/**
* the logger for TransformFuntion
@@ -117,13 +105,11 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
/**
* Constructs ...
*
*
* @param repository
* @param revWalk
*/
public TransformFuntion(org.eclipse.jgit.lib.Repository repository,
RevWalk revWalk)
{
RevWalk revWalk) {
this.repository = repository;
this.revWalk = revWalk;
}
@@ -133,30 +119,23 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
/**
* Method description
*
*
* @param ref
*
* @return
*/
@Override
public Tag apply(Ref ref)
{
public Tag apply(Ref ref) {
Tag tag = null;
try
{
RevCommit commit = GitUtil.getCommit(repository, revWalk, ref);
try {
RevObject revObject = GitUtil.getCommit(repository, revWalk, ref);
if (commit != null)
{
if (revObject != null) {
String name = GitUtil.getTagName(ref);
tag = new Tag(name, commit.getId().name());
tag = new Tag(name, revObject.getId().name(), GitUtil.getTagTime(revWalk, ref.getObjectId()));
}
}
catch (IOException ex)
{
} catch (IOException ex) {
logger.error("could not get commit for tag", ex);
}
@@ -165,10 +144,14 @@ public class GitTagsCommand extends AbstractGitCommand implements TagsCommand
//~--- fields -------------------------------------------------------------
/** Field description */
/**
* Field description
*/
private org.eclipse.jgit.lib.Repository repository;
/** Field description */
/**
* Field description
*/
private RevWalk revWalk;
}
}

View File

@@ -21,30 +21,35 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.api;
import com.google.common.collect.Lists;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.transport.ReceiveCommand;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockedStatic;
import org.mockito.Mockito;
import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.GitUtil;
import sonia.scm.repository.Tag;
import java.util.List;
import static org.hamcrest.Matchers.empty;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link GitHookTagProvider}.
*
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
@@ -52,19 +57,19 @@ public class GitHookTagProviderTest {
private static final String ZERO = ObjectId.zeroId().getName();
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Mock
private ReceiveCommand command;
@Mock
private Repository repository;
private List<ReceiveCommand> commands;
/**
* Set up mocks for upcoming tests.
*/
@Before
public void setUpMocks(){
public void setUpMocks() {
commands = Lists.newArrayList(command);
}
@@ -73,65 +78,125 @@ public class GitHookTagProviderTest {
*/
@Test
public void testGetCreatedTags() {
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/tags/1.0.0", revision, ZERO);
assertTag("1.0.0", revision, provider.getCreatedTags());
assertThat(provider.getDeletedTags(), empty());
try (MockedStatic<GitUtil> dummy = Mockito.mockStatic(GitUtil.class)) {
String revision = "86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1";
Long timestamp = 1339416344000L;
String tagName = "1.0.0";
String ref = "refs/tags/" + tagName;
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(revision))).thenReturn(timestamp);
dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision);
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, ref, revision, ZERO);
assertTag(tagName, revision, timestamp, provider.getCreatedTags());
assertThat(provider.getDeletedTags(), empty());
}
}
/**
* Tests {@link GitHookTagProvider#getDeletedTags()}.
*/
@Test
public void testGetDeletedTags() {
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, "refs/tags/1.0.0", ZERO, revision);
assertThat(provider.getCreatedTags(), empty());
assertTag("1.0.0", revision, provider.getDeletedTags());
try (MockedStatic<GitUtil> dummy = Mockito.mockStatic(GitUtil.class)) {
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
Long timestamp = 1339416344000L;
String tagName = "1.0.0";
String ref = "refs/tags/" + tagName;
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(revision))).thenReturn(timestamp);
dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(revision))).thenReturn(revision);
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.DELETE, ref, ZERO, revision);
assertThat(provider.getCreatedTags(), empty());
assertTag("1.0.0", revision, 1339416344000L, provider.getDeletedTags());
}
}
/**
* Tests {@link GitHookTagProvider} with a branch ref instead of a tag.
*/
@Test
public void testWithBranch(){
public void testWithBranch() {
String revision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.CREATE, "refs/heads/1.0.0", revision, revision);
assertThat(provider.getCreatedTags(), empty());
assertThat(provider.getDeletedTags(), empty());
}
/**
* Tests {@link GitHookTagProvider} with update command.
* Tests {@link GitHookTagProvider} with update command pre receive.
*/
@Test
public void testUpdateTags() {
String newId = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
String oldId = "e0f2be968b147ff7043684a7715d2fe852553db4";
public void testUpdateTagsPreReceive() {
try (MockedStatic<GitUtil> dummy = Mockito.mockStatic(GitUtil.class)) {
String oldRevision = "e0f2be968b147ff7043684a7715d2fe852553db4";
String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, "refs/tags/1.0.0", newId, oldId);
assertTag("1.0.0", newId, provider.getCreatedTags());
assertTag("1.0.0", oldId, provider.getDeletedTags());
Long timestamp = 1339416344000L;
String tagName = "1.0.0";
String ref = "refs/tags/" + tagName;
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(oldRevision))).thenReturn(timestamp);
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(newRevision))).thenReturn(null);
dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(oldRevision))).thenReturn(oldRevision);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(newRevision))).thenReturn(newRevision);
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, ref, newRevision, oldRevision);
assertTag(tagName, newRevision, null, provider.getCreatedTags());
assertTag(tagName, oldRevision, timestamp, provider.getDeletedTags());
}
}
private void assertTag(String name, String revision, List<Tag> tags){
/**
* Tests {@link GitHookTagProvider} with update command post receive.
*/
@Test
public void testUpdateTagsPostReceive() {
try (MockedStatic<GitUtil> dummy = Mockito.mockStatic(GitUtil.class)) {
String oldRevision = "e0f2be968b147ff7043684a7715d2fe852553db4";
String newRevision = "b2002b64013e54b78eac251df0672bd5d6a83aa7";
Long timestamp = 1339416344000L;
String tagName = "1.0.0";
String ref = "refs/tags/" + tagName;
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(newRevision))).thenReturn(timestamp);
dummy.when(() -> GitUtil.getTagTime(repository, ObjectId.fromString(oldRevision))).thenReturn(null);
dummy.when(() -> GitUtil.getTagName(ref)).thenReturn(tagName);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(oldRevision))).thenReturn(oldRevision);
dummy.when(() -> GitUtil.getId(ObjectId.fromString(newRevision))).thenReturn(newRevision);
GitHookTagProvider provider = createProvider(ReceiveCommand.Type.UPDATE, ref, newRevision, oldRevision);
assertTag(tagName, newRevision, timestamp, provider.getCreatedTags());
assertTag(tagName, oldRevision, null, provider.getDeletedTags());
}
}
private void assertTag(String name, String revision, Long date, List<Tag> tags) {
assertNotNull(tags);
assertFalse(tags.isEmpty());
assertEquals(1, tags.size());
Tag tag = tags.get(0);
assertEquals(name, tag.getName());
assertEquals(revision, tag.getRevision());
assertEquals(date, tag.getDate().orElse(null));
}
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId){
private GitHookTagProvider createProvider(ReceiveCommand.Type type, String ref, String newId, String oldId) {
when(command.getNewId()).thenReturn(ObjectId.fromString(newId));
when(command.getOldId()).thenReturn(ObjectId.fromString(oldId));
when(command.getType()).thenReturn(type);
when(command.getRefName()).thenReturn(ref);
return new GitHookTagProvider(commands);
return new GitHookTagProvider(commands, repository);
}
}

View File

@@ -21,7 +21,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.client.spi;
//~--- non-JDK imports --------------------------------------------------------
@@ -78,6 +78,7 @@ public class GitTagCommand implements TagCommand
String revision = request.getRevision();
RevObject revObject = null;
Long tagTime = null;
if (!Strings.isNullOrEmpty(revision))
{
@@ -88,6 +89,7 @@ public class GitTagCommand implements TagCommand
{
walk = new RevWalk(git.getRepository());
revObject = walk.parseAny(id);
tagTime = GitUtil.getTagTime(walk, id);
}
finally
{
@@ -110,9 +112,9 @@ public class GitTagCommand implements TagCommand
}
if (ref.isPeeled()) {
tag = new Tag(request.getName(), ref.getPeeledObjectId().toString());
tag = new Tag(request.getName(), ref.getPeeledObjectId().toString(), tagTime);
} else {
tag = new Tag(request.getName(), ref.getObjectId().toString());
tag = new Tag(request.getName(), ref.getObjectId().toString(), tagTime);
}
}

View File

@@ -0,0 +1,73 @@
/*
* 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.junit.Rule;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import sonia.scm.repository.Tag;
import java.io.IOException;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
@SubjectAware(configuration = "classpath:sonia/scm/configuration/shiro.ini", username = "admin", password = "secret")
public class GitTagsCommandTest extends AbstractGitCommandTestBase {
@Rule
public TemporaryFolder temporaryFolder = new TemporaryFolder();
@Rule
public BindTransportProtocolRule transportProtocolRule = new BindTransportProtocolRule();
@Rule
public ShiroRule shiro = new ShiroRule();
@Test
public void shouldGetDatesCorrectly() throws IOException {
final GitContext gitContext = createContext();
final GitTagsCommand tagsCommand = new GitTagsCommand(gitContext);
final List<Tag> tags = tagsCommand.getTags();
assertThat(tags).hasSize(2);
Tag annotatedTag = tags.get(0);
assertThat(annotatedTag.getName()).isEqualTo("1.0.0");
assertThat(annotatedTag.getDate()).contains(1598348105000L); // Annotated - Take tag date
assertThat(annotatedTag.getRevision()).isEqualTo("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
Tag lightweightTag = tags.get(1);
assertThat(lightweightTag.getName()).isEqualTo("test-tag");
assertThat(lightweightTag.getDate()).contains(1339416344000L); // Lightweight - Take commit date
assertThat(lightweightTag.getRevision()).isEqualTo("86a6645eceefe8b9a247db5eb16e3d89a7e6e6d1");
}
@Override
protected String getZippedRepositoryResource() {
return "sonia/scm/repository/spi/scm-git-spi-test-tags.zip";
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-hg-plugin",
"private": true,
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.ts",
"scripts": {
@@ -19,6 +19,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
"@scm-manager/ui-plugins": "^2.4.1"
"@scm-manager/ui-plugins": "^2.5.0-SNAPSHOT"
}
}

View File

@@ -92,7 +92,7 @@ public class HgHookTagProvider implements HookTagProvider {
if (tagNames != null){
for ( String tagName : tagNames ){
logger.trace("found tag {} at changeset {}", tagName, c.getId());
tags.add(new Tag(tagName, c.getId()));
tags.add(new Tag(tagName, c.getId(), c.getDate()));
}
}
}

View File

@@ -29,29 +29,22 @@ package sonia.scm.repository.spi;
import com.google.common.base.Function;
import com.google.common.base.Strings;
import com.google.common.collect.Lists;
import sonia.scm.repository.Tag;
import sonia.scm.util.Util;
//~--- JDK imports ------------------------------------------------------------
import java.util.List;
/**
*
* @author Sebastian Sdorra
*/
public class HgTagsCommand extends AbstractCommand implements TagsCommand
{
public class HgTagsCommand extends AbstractCommand implements TagsCommand {
/**
* Constructs ...
*
* @param context
*
* @param context
*/
public HgTagsCommand(HgCommandContext context)
{
public HgTagsCommand(HgCommandContext context) {
super(context);
}
@@ -60,12 +53,10 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
/**
* Method description
*
*
* @return
*/
@Override
public List<Tag> getTags()
{
public List<Tag> getTags() {
com.aragost.javahg.commands.TagsCommand cmd =
com.aragost.javahg.commands.TagsCommand.on(open());
@@ -74,13 +65,11 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
List<Tag> tags = null;
// check for empty repository
if (Util.isNotEmpty(tagList) && tagList.get(0).getChangeset() != null)
{
if (Util.isNotEmpty(tagList) && tagList.get(0).getChangeset() != null) {
tags = Lists.transform(tagList, new TagTransformer());
}
if (tags == null)
{
if (tags == null) {
tags = Lists.newArrayList();
}
@@ -92,31 +81,25 @@ public class HgTagsCommand extends AbstractCommand implements TagsCommand
/**
* Class description
*
*
* @version Enter version here..., 12/08/03
* @author Enter your name here...
* @author Enter your name here...
* @version Enter version here..., 12/08/03
*/
private static class TagTransformer
implements Function<com.aragost.javahg.Tag, Tag>
{
implements Function<com.aragost.javahg.Tag, Tag> {
/**
* Method description
*
*
* @param f
*
* @return
*/
@Override
public Tag apply(com.aragost.javahg.Tag f)
{
public Tag apply(com.aragost.javahg.Tag f) {
Tag t = null;
if ((f != null) &&!Strings.isNullOrEmpty(f.getName())
&& (f.getChangeset() != null))
{
t = new Tag(f.getName(), f.getChangeset().getNode());
if ((f != null) && !Strings.isNullOrEmpty(f.getName())
&& (f.getChangeset() != null)) {
t = new Tag(f.getName(), f.getChangeset().getNode(), f.getChangeset().getTimestamp().getDate().getTime());
}
return t;

View File

@@ -53,7 +53,7 @@ class ProtocolInformation extends React.Component<Props> {
echo "[paths]" &gt; .hg/hgrc
<br />
echo "default = {href}
" &gt; .hg/hgrc
" &gt;&gt; .hg/hgrc
<br />
echo "# {repository.name}
" &gt; README.md

View File

@@ -21,15 +21,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.repository.api;
import com.google.common.collect.Lists;
import java.util.List;
import org.junit.Test;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import static org.hamcrest.Matchers.*;
import org.junit.runner.RunWith;
import org.mockito.InjectMocks;
import org.mockito.Mock;
@@ -41,9 +37,16 @@ import sonia.scm.repository.spi.HookChangesetProvider;
import sonia.scm.repository.spi.HookChangesetRequest;
import sonia.scm.repository.spi.HookChangesetResponse;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.when;
/**
* Unit tests for {@link HgHookTagProvider}.
*
*
* @author Sebastian Sdorra
*/
@RunWith(MockitoJUnitRunner.class)
@@ -51,7 +54,7 @@ public class HgHookTagProviderTest {
@Mock
private HookChangesetProvider changesetProvider;
@InjectMocks
private HgHookTagProvider tagProvider;
@@ -61,9 +64,9 @@ public class HgHookTagProviderTest {
@Test
public void testGetDeletedTags() {
prepareChangesets(new Changeset("1", Long.MIN_VALUE, null));
assertThat(tagProvider.getDeletedTags(), empty());
assertThat(tagProvider.getDeletedTags()).isEmpty();
}
/**
* Tests {@link HgHookTagProvider#getCreatedTags()}.
*/
@@ -71,22 +74,24 @@ public class HgHookTagProviderTest {
public void testGetCreatedTags(){
Changeset c1 = new Changeset("1", Long.MIN_VALUE, null);
c1.getTags().add("1.0.0");
Changeset c2 = new Changeset("2", Long.MIN_VALUE, null);
Changeset c2 = new Changeset("2", Long.MAX_VALUE, null);
c2.getTags().add("2.0.0");
Changeset c3 = new Changeset("3", Long.MIN_VALUE, null);
prepareChangesets(c1, c2, c3);
List<Tag> tags = tagProvider.getCreatedTags();
assertNotNull(tags);
assertEquals(2, tags.size());
Tag t1 = tags.get(0);
assertEquals("1", t1.getRevision());
assertEquals("1.0.0", t1.getName());
assertThat(t1.getDate()).contains(Long.MIN_VALUE);
Tag t2 = tags.get(1);
assertEquals("2", t2.getRevision());
assertEquals("2.0.0", t2.getName());
assertThat(t2.getDate()).contains(Long.MAX_VALUE);
}
private void prepareChangesets(Changeset... changesets){

View File

@@ -0,0 +1,45 @@
/*
* 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 org.junit.Test;
import sonia.scm.repository.Tag;
import java.util.List;
import static org.assertj.core.api.Assertions.assertThat;
public class HgTagsCommandTest extends AbstractHgCommandTestBase {
@Test
public void shouldGetTagDatesCorrectly() {
HgTagsCommand hgTagsCommand = new HgTagsCommand(cmdContext);
final List<Tag> tags = hgTagsCommand.getTags();
assertThat(tags).hasSize(1);
assertThat(tags.get(0).getName()).isEqualTo("tip");
assertThat(tags.get(0).getDate()).contains(1339586381000L);
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-legacy-plugin",
"private": true,
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.tsx",
"scripts": {
@@ -19,6 +19,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
"@scm-manager/ui-plugins": "^2.4.1"
"@scm-manager/ui-plugins": "^2.5.0-SNAPSHOT"
}
}

View File

@@ -1,7 +1,7 @@
{
"name": "@scm-manager/scm-svn-plugin",
"private": true,
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"main": "./src/main/js/index.ts",
"scripts": {
@@ -19,6 +19,6 @@
},
"prettier": "@scm-manager/prettier-config",
"dependencies": {
"@scm-manager/ui-plugins": "^2.4.1"
"@scm-manager/ui-plugins": "^2.5.0-SNAPSHOT"
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/babel-preset",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"description": "Babel configuration for scm-manager and its plugins",
"main": "index.js",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/e2e-tests",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"description": "End to end Tests for SCM-Manager",
"main": "index.js",
"author": "Eduard Heimbuch <eduard.heimbuch@cloudogu.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/eslint-config",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"description": "ESLint configuration for scm-manager and its plugins",
"main": "src/index.js",
"author": "Sebastian Sdorra <s.sdorra@gmail.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/jest-preset",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"description": "Jest presets for SCM-Manager and its plugins",
"main": "src/index.js",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/prettier-config",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"description": "Prettier configuration",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/tsconfig",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"description": "TypeScript configuration",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-components",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"description": "UI Components for SCM-Manager and its plugins",
"main": "src/index.ts",
"files": [
@@ -18,7 +18,7 @@
"update-storyshots": "jest --testPathPattern=\"storyshots.test.ts\" --collectCoverage=false -u"
},
"devDependencies": {
"@scm-manager/ui-tests": "^2.1.0",
"@scm-manager/ui-tests": "^2.5.0-SNAPSHOT",
"@storybook/addon-actions": "^5.2.3",
"@storybook/addon-storyshots": "^5.2.3",
"@storybook/react": "^5.2.3",
@@ -46,8 +46,8 @@
"worker-plugin": "^3.2.0"
},
"dependencies": {
"@scm-manager/ui-extensions": "^2.1.0",
"@scm-manager/ui-types": "^2.4.0",
"@scm-manager/ui-extensions": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-types": "^2.5.0-SNAPSHOT",
"classnames": "^2.2.6",
"date-fns": "^2.4.1",
"gitdiff-parser": "^0.1.2",

View File

@@ -72,10 +72,10 @@ class ErrorBoundary extends React.Component<Props, State> {
}
redirectToLogin = (error: Error) => {
const { loginLink } = this.props;
const { loginLink, location } = this.props;
if (error instanceof MissingLinkError) {
if (loginLink) {
window.location.assign(withContextPath("/login"));
window.location.assign(withContextPath("/login?from=" + location.pathname));
}
}
};

File diff suppressed because it is too large Load Diff

View File

@@ -24,32 +24,74 @@
import React from "react";
import DiffFile from "./DiffFile";
import { DiffObjectProps, File, FileControlFactory } from "./DiffTypes";
import { escapeWhitespace } from "./diffs";
import Notification from "../Notification";
import { WithTranslation, withTranslation } from "react-i18next";
import { RouteComponentProps, withRouter } from "react-router-dom";
type Props = WithTranslation &
type Props = RouteComponentProps &
WithTranslation &
DiffObjectProps & {
diff: File[];
fileControlFactory?: FileControlFactory;
};
class Diff extends React.Component<Props> {
type State = {
contentRef?: HTMLElement | null;
};
function getAnchorSelector(uriHashContent: string) {
return "#" + escapeWhitespace(decodeURIComponent(uriHashContent));
}
class Diff extends React.Component<Props, State> {
static defaultProps: Partial<Props> = {
sideBySide: false
};
constructor(props: Readonly<Props>) {
super(props);
this.state = {
contentRef: undefined
};
}
componentDidUpdate() {
const { contentRef } = this.state;
// we have to use componentDidUpdate, because we have to wait until all
// children are rendered and componentDidMount is called before the
// changeset content was rendered.
const hash = this.props.location.hash;
const match = hash && hash.match(/^#diff-(.*)$/);
if (contentRef && match) {
const selector = getAnchorSelector(match[1]);
const element = contentRef.querySelector(selector);
if (element && element.scrollIntoView) {
element.scrollIntoView();
}
}
}
shouldComponentUpdate(nextProps: Readonly<Props>, nextState: Readonly<State>): boolean {
// We have check if the contentRef changed and update afterwards so the page can scroll to the anchor links.
// Otherwise it can happen that componentDidUpdate is never executed depending on how fast the markdown got rendered
return this.state.contentRef !== nextState.contentRef || this.props !== nextProps;
}
render() {
const { diff, t, ...fileProps } = this.props;
return (
<>
<div ref={el => this.setState({ contentRef: el })}>
{diff.length === 0 ? (
<Notification type="info">{t("diff.noDiffFound")}</Notification>
) : (
diff.map((file, index) => <DiffFile key={index} file={file} {...fileProps} {...this.props} />)
)}
</>
</div>
);
}
}
export default withTranslation("repos")(Diff);
export default withRouter(withTranslation("repos")(Diff));

View File

@@ -39,6 +39,7 @@ import HunkExpandLink from "./HunkExpandLink";
import { Modal } from "../modals";
import ErrorNotification from "../ErrorNotification";
import HunkExpandDivider from "./HunkExpandDivider";
import { escapeWhitespace } from "./diffs";
const EMPTY_ANNOTATION_FACTORY = {};
@@ -106,7 +107,7 @@ class DiffFile extends React.Component<Props, State> {
};
}
componentDidUpdate(prevProps: Readonly<Props>, prevState: Readonly<State>, snapshot?: any): void {
componentDidUpdate(prevProps: Readonly<Props>) {
if (this.props.defaultCollapse !== prevProps.defaultCollapse) {
this.setState({
collapsed: this.defaultCollapse()
@@ -350,6 +351,16 @@ class DiffFile extends React.Component<Props, State> {
}
};
getAnchorId(file: File) {
let path: string;
if (file.type === "delete") {
path = file.oldPath;
} else {
path = file.newPath;
}
return escapeWhitespace(path);
}
renderFileTitle = (file: File) => {
if (file.oldPath !== file.newPath && (file.type === "copy" || file.type === "rename")) {
return (
@@ -454,7 +465,7 @@ class DiffFile extends React.Component<Props, State> {
}
return (
<DiffFilePanel className={classNames("panel", "is-size-6")} collapsed={(file && file.isBinary) || collapsed}>
<DiffFilePanel className={classNames("panel", "is-size-6")} collapsed={(file && file.isBinary) || collapsed} id={this.getAnchorId(file)}>
{errorModal}
<div className="panel-heading">
<FlexWrapLevel className="level">

View File

@@ -23,7 +23,7 @@
*/
import { File, FileChangeType, Hunk } from "./DiffTypes";
import { getPath, createHunkIdentifier, createHunkIdentifierFromContext } from "./diffs";
import { getPath, createHunkIdentifier, createHunkIdentifierFromContext, escapeWhitespace } from "./diffs";
describe("tests for diff util functions", () => {
const file = (type: FileChangeType, oldPath: string, newPath: string): File => {
@@ -88,4 +88,15 @@ describe("tests for diff util functions", () => {
expect(identifier).toBe("delete_/etc/passwd_@@ -1,42 +1,39 @@");
});
});
describe("escapeWhitespace tests", () => {
it("should escape whitespaces", () => {
const escaped = escapeWhitespace("spaceship hog");
expect(escaped).toBe("spaceship-hog");
});
it("should escape multiple whitespaces", () => {
const escaped = escapeWhitespace("spaceship heart of gold");
expect(escaped).toBe("spaceship-heart-of-gold");
});
});
});

View File

@@ -39,3 +39,7 @@ export function createHunkIdentifier(file: File, hunk: Hunk) {
export function createHunkIdentifierFromContext(ctx: BaseContext) {
return createHunkIdentifier(ctx.file, ctx.hunk);
}
export function escapeWhitespace(path: string) {
return path.toLowerCase().replace(/\W/g, "-");
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-extensions",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"main": "src/index.ts",
"license": "MIT",
"private": false,

View File

@@ -1,13 +1,13 @@
{
"name": "@scm-manager/ui-plugins",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"license": "MIT",
"bin": {
"ui-plugins": "./bin/ui-plugins.js"
},
"dependencies": {
"@scm-manager/ui-components": "^2.4.1",
"@scm-manager/ui-extensions": "^2.1.0",
"@scm-manager/ui-components": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-extensions": "^2.5.0-SNAPSHOT",
"classnames": "^2.2.6",
"query-string": "^5.0.1",
"react": "^16.10.2",
@@ -18,14 +18,14 @@
"styled-components": "^5.1.0"
},
"devDependencies": {
"@scm-manager/babel-preset": "^2.1.0",
"@scm-manager/eslint-config": "^2.1.0",
"@scm-manager/jest-preset": "^2.1.0",
"@scm-manager/prettier-config": "^2.1.0",
"@scm-manager/tsconfig": "^2.1.0",
"@scm-manager/ui-scripts": "^2.4.1",
"@scm-manager/ui-tests": "^2.1.0",
"@scm-manager/ui-types": "^2.4.0",
"@scm-manager/babel-preset": "^2.5.0-SNAPSHOT",
"@scm-manager/eslint-config": "^2.5.0-SNAPSHOT",
"@scm-manager/jest-preset": "^2.5.0-SNAPSHOT",
"@scm-manager/prettier-config": "^2.5.0-SNAPSHOT",
"@scm-manager/tsconfig": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-scripts": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-tests": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-types": "^2.5.0-SNAPSHOT",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
"@types/fetch-mock": "^7.3.1",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-polyfill",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"description": "Polyfills for SCM-Manager UI",
"main": "src/index.js",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-scripts",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"description": "Build scripts for SCM-Manager",
"main": "src/index.js",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",

View File

@@ -37,16 +37,8 @@ const yarn = args => {
}
};
const version = version => {
yarn([
"run",
"lerna",
"--no-git-tag-version",
"--no-push",
"version",
"--yes",
version
]);
const version = v => {
yarn(["run", "lerna", "--no-git-tag-version", "--no-push", "version", "--force-publish", "--yes", v]);
};
const publish = () => {

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-styles",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"description": "Styles for SCM-Manager",
"main": "src/scm.scss",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-tests",
"version": "2.1.0",
"version": "2.5.0-SNAPSHOT",
"description": "UI-Tests helpers",
"author": "Sebastian Sdorra <sebastian.sdorra@cloudogu.com>",
"license": "MIT",

View File

@@ -1,6 +1,6 @@
{
"name": "@scm-manager/ui-types",
"version": "2.4.0",
"version": "2.5.0-SNAPSHOT",
"description": "Flow types for SCM-Manager related Objects",
"main": "src/index.ts",
"files": [

View File

@@ -1,10 +1,10 @@
{
"name": "@scm-manager/ui-webapp",
"version": "2.4.1",
"version": "2.5.0-SNAPSHOT",
"private": true,
"dependencies": {
"@scm-manager/ui-components": "^2.4.1",
"@scm-manager/ui-extensions": "^2.1.0",
"@scm-manager/ui-components": "^2.5.0-SNAPSHOT",
"@scm-manager/ui-extensions": "^2.5.0-SNAPSHOT",
"classnames": "^2.2.5",
"history": "^4.10.1",
"i18next": "^19.6.0",
@@ -29,7 +29,7 @@
"test": "jest"
},
"devDependencies": {
"@scm-manager/ui-tests": "^2.1.0",
"@scm-manager/ui-tests": "^2.5.0-SNAPSHOT",
"@types/classnames": "^2.2.9",
"@types/enzyme": "^3.10.3",
"@types/fetch-mock": "^7.3.1",

View File

@@ -29,13 +29,15 @@ import de.otto.edison.hal.Links;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.ObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import sonia.scm.repository.Branch;
import sonia.scm.repository.Changeset;
import sonia.scm.repository.Contributor;
import sonia.scm.repository.Person;
import sonia.scm.repository.Repository;
import sonia.scm.repository.Signature;
import sonia.scm.repository.Tag;
import sonia.scm.repository.Tags;
import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService;
import sonia.scm.repository.api.RepositoryServiceFactory;
@@ -45,6 +47,7 @@ import sonia.scm.security.gpg.RawGpgKey;
import sonia.scm.web.EdisonHalAppender;
import javax.inject.Inject;
import java.io.IOException;
import java.util.List;
import java.util.Optional;
import java.util.function.Function;
@@ -57,6 +60,8 @@ import static de.otto.edison.hal.Links.linkingTo;
@Mapper
public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper {
private static Logger LOG = LoggerFactory.getLogger(DefaultChangesetToChangesetDtoMapper.class);
@Inject
private RepositoryServiceFactory serviceFactory;
@@ -115,8 +120,16 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
try (RepositoryService repositoryService = serviceFactory.create(repository)) {
if (repositoryService.isSupported(Command.TAGS)) {
embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
getListOfObjects(source.getTags(), tagName -> new Tag(tagName, source.getId()))));
Tags tags = null;
try {
tags = repositoryService.getTagsCommand().getTags();
} catch (IOException e) {
LOG.error("Error while retrieving tags from repository", e);
}
if (tags != null) {
embeddedBuilder.with("tags", tagCollectionToDtoMapper.getTagDtoList(namespace, name,
getListOfObjects(source.getTags(), tags::getTagByName)));
}
}
if (repositoryService.isSupported(Command.BRANCHES)) {
embeddedBuilder.with("branches", branchCollectionToDtoMapper.getBranchDtoList(namespace, name,

View File

@@ -21,9 +21,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package sonia.scm.api.v2.resources;
import com.fasterxml.jackson.annotation.JsonInclude;
import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.HalRepresentation;
import de.otto.edison.hal.Links;
@@ -31,6 +32,8 @@ import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.time.Instant;
@Getter
@Setter
@NoArgsConstructor
@@ -40,6 +43,9 @@ public class TagDto extends HalRepresentation {
private String revision;
@JsonInclude(JsonInclude.Include.NON_NULL)
private Instant date;
TagDto(Links links, Embedded embedded) {
super(links, embedded);
}

View File

@@ -29,6 +29,7 @@ import de.otto.edison.hal.Links;
import org.mapstruct.Context;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import org.mapstruct.Named;
import org.mapstruct.ObjectFactory;
import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Tag;
@@ -36,6 +37,9 @@ import sonia.scm.web.EdisonHalAppender;
import javax.inject.Inject;
import java.time.Instant;
import java.util.Optional;
import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link;
import static de.otto.edison.hal.Links.linkingTo;
@@ -46,6 +50,7 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
@Inject
private ResourceLinks resourceLinks;
@Mapping(target = "date", source = "date", qualifiedByName = "mapDate")
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
public abstract TagDto map(Tag tag, @Context NamespaceAndName namespaceAndName);
@@ -61,4 +66,9 @@ public abstract class TagToTagDtoMapper extends HalAppenderMapper {
return new TagDto(linksBuilder.build(), embeddedBuilder.build());
}
@Named("mapDate")
Instant map(Optional<Long> value) {
return value.map(Instant::ofEpochMilli).orElse(null);
}
}

View File

@@ -30,6 +30,12 @@
<hr>
{{/validationErrorsFound}}
<p>
Please note that your old plugins from version 1 will <b>not</b> be installed automatically.
After this migration step you can install plugins from the "Plugins" section in the "Administration"
panel again. Data and settings for plugins will then be migrated on the fly.
</p>
<form action="{{submitUrl}}" method="post">
<table class="card-table table is-hoverable is-fullwidth">
<tr>

View File

@@ -32,6 +32,7 @@ import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.Tag;
import java.net.URI;
import java.time.Instant;
import static org.assertj.core.api.Assertions.assertThat;
@@ -58,4 +59,11 @@ class TagToTagDtoMapperTest {
assertThat(dto.getLinks().getLinkBy("yo").get().getHref()).isEqualTo("http://hitchhiker/hog/1.0.0");
}
@Test
void shouldMapDate() {
final long now = Instant.now().getEpochSecond() * 1000;
TagDto dto = mapper.map(new Tag("1.0.0", "42", now), new NamespaceAndName("hitchhiker", "hog"));
assertThat(dto.getDate()).isEqualTo(Instant.ofEpochMilli(now));
}
}