diff --git a/scm-plugins/scm-legacy-plugin/package.json b/scm-plugins/scm-legacy-plugin/package.json
new file mode 100644
index 0000000000..84f78f419a
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "@scm-manager/legacy-plugin",
+ "license": "BSD-3-Clause",
+ "main": "src/main/js/index.js",
+ "scripts": {
+ "build": "ui-bundler plugin",
+ "watch": "ui-bundler plugin -w",
+ "lint": "ui-bundler lint",
+ "flow": "flow check"
+ },
+ "dependencies": {
+ "@scm-manager/ui-components": "latest",
+ "@scm-manager/ui-extensions": "^0.1.1",
+ "react-redux": "^5.0.7",
+ "@scm-manager/ui-types": "latest"
+ },
+ "devDependencies": {
+ "@scm-manager/ui-bundler": "^0.0.25"
+ }
+}
diff --git a/scm-plugins/scm-legacy-plugin/pom.xml b/scm-plugins/scm-legacy-plugin/pom.xml
index 52aff51dd2..6cfa74ea61 100644
--- a/scm-plugins/scm-legacy-plugin/pom.xml
+++ b/scm-plugins/scm-legacy-plugin/pom.xml
@@ -21,7 +21,13 @@
${servlet.version}
provided
-
+
+ javax.ws.rs
+ jsr311-api
+ 1.1.1
+ compile
+
+
diff --git a/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyIndexHalEnricher.java b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyIndexHalEnricher.java
new file mode 100644
index 0000000000..0914f92e25
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyIndexHalEnricher.java
@@ -0,0 +1,38 @@
+package sonia.scm.legacy;
+
+import com.google.inject.Inject;
+import sonia.scm.api.v2.resources.Enrich;
+import sonia.scm.api.v2.resources.HalAppender;
+import sonia.scm.api.v2.resources.HalEnricher;
+import sonia.scm.api.v2.resources.HalEnricherContext;
+import sonia.scm.api.v2.resources.Index;
+import sonia.scm.api.v2.resources.LinkBuilder;
+import sonia.scm.api.v2.resources.ScmPathInfoStore;
+import sonia.scm.plugin.Extension;
+
+import javax.inject.Provider;
+
+@Extension
+@Enrich(Index.class)
+public class LegacyIndexHalEnricher implements HalEnricher {
+
+ private Provider scmPathInfoStoreProvider;
+
+ @Inject
+ public LegacyIndexHalEnricher(Provider scmPathInfoStoreProvider) {
+ this.scmPathInfoStoreProvider = scmPathInfoStoreProvider;
+ }
+
+ private String createLink() {
+ return new LinkBuilder(scmPathInfoStoreProvider.get().get(), LegacyRepositoryService.class)
+ .method("getNameAndNamespaceForRepositoryId")
+ .parameters("REPOID")
+ .href()
+ .replace("REPOID", "{id}");
+ }
+
+ @Override
+ public void enrich(HalEnricherContext context, HalAppender appender) {
+ appender.appendLink("nameAndNamespace", createLink());
+ }
+}
diff --git a/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyRepositoryService.java b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyRepositoryService.java
new file mode 100644
index 0000000000..d6b923a927
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/LegacyRepositoryService.java
@@ -0,0 +1,43 @@
+package sonia.scm.legacy;
+
+import com.google.inject.Inject;
+import com.webcohesion.enunciate.metadata.rs.ResponseCode;
+import com.webcohesion.enunciate.metadata.rs.StatusCodes;
+import sonia.scm.NotFoundException;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryManager;
+
+import javax.ws.rs.GET;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+
+@Path("v2/legacy/repository")
+public class LegacyRepositoryService {
+
+ private RepositoryManager repositoryManager;
+
+ @Inject
+ public LegacyRepositoryService(RepositoryManager repositoryManager) {
+ this.repositoryManager = repositoryManager;
+ }
+
+ @GET
+ @Path("{id}")
+ @Produces(MediaType.APPLICATION_JSON)
+ @StatusCodes({
+ @ResponseCode(code = 200, condition = "success"),
+ @ResponseCode(code = 401, condition = "not authenticated / invalid credentials"),
+ @ResponseCode(code = 403, condition = "not authorized, the current user does not have the \"repository:read:global\" privilege"),
+ @ResponseCode(code = 500, condition = "internal server error")
+ })
+ public NamespaceAndNameDto getNameAndNamespaceForRepositoryId(@PathParam("id") String repositoryId) {
+ Repository repo = repositoryManager.get(repositoryId);
+ if (repo == null) {
+ throw new NotFoundException(Repository.class, repositoryId);
+ }
+ return new NamespaceAndNameDto(repo.getName(), repo.getNamespace());
+ }
+}
+
diff --git a/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/NamespaceAndNameDto.java b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/NamespaceAndNameDto.java
new file mode 100644
index 0000000000..2f7aae2dae
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/main/java/sonia/scm/legacy/NamespaceAndNameDto.java
@@ -0,0 +1,11 @@
+package sonia.scm.legacy;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class NamespaceAndNameDto {
+ private String name;
+ private String namespace;
+}
diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js
new file mode 100644
index 0000000000..396558f852
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/main/js/DummyComponent.js
@@ -0,0 +1,14 @@
+//@flow
+import React from "react";
+import { withRouter } from "react-router-dom";
+
+class DummyComponent extends React.Component {
+ render() {
+ return (
+ <>
+ >
+ );
+ }
+}
+
+export default withRouter(DummyComponent);
diff --git a/scm-plugins/scm-legacy-plugin/src/main/js/index.js b/scm-plugins/scm-legacy-plugin/src/main/js/index.js
new file mode 100644
index 0000000000..97c3eb7e32
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/main/js/index.js
@@ -0,0 +1,95 @@
+// @flow
+
+import React from "react";
+import { withRouter } from "react-router-dom";
+import { binder } from "@scm-manager/ui-extensions";
+import {
+ ProtectedRoute,
+ apiClient,
+ ErrorNotification,
+ ErrorBoundary
+} from "@scm-manager/ui-components";
+import DummyComponent from "./DummyComponent";
+import type {Links} from "@scm-manager/ui-types";
+
+type Props = {
+ authenticated?: boolean,
+ links: Links,
+
+ //context objects
+ history: History
+};
+
+type State = {
+ error?: Error
+};
+
+class LegacyRepositoryRedirect extends React.Component {
+ constructor(props: Props, state: State) {
+ super(props, state);
+ this.state = { error: null };
+ }
+
+ handleError = (error: Error) => {
+ this.setState({
+ error
+ });
+ };
+
+ redirectLegacyRepository() {
+ const { history, links } = this.props;
+ if (location.href && location.href.includes("#diffPanel;")) {
+ let splittedUrl = location.href.split(";");
+ let repoId = splittedUrl[1];
+ let changeSetId = splittedUrl[2];
+
+ apiClient
+ .get(links.nameAndNamespace.href.replace("{id}", repoId))
+ .then(response => response.json())
+ .then(payload =>
+ history.push(
+ "/repo/" +
+ payload.namespace +
+ "/" +
+ payload.name +
+ "/changeset/" +
+ changeSetId
+ )
+ )
+ .catch(this.handleError);
+ }
+ }
+
+ render() {
+ const { authenticated } = this.props;
+ const { error } = this.state;
+
+ if (error) {
+ return (
+
+ );
+ }
+
+ return (
+ <>
+ {authenticated ? (
+ this.redirectLegacyRepository()
+ ) : (
+
+ )}
+ >
+ );
+ }
+}
+
+binder.bind("main.route", withRouter(LegacyRepositoryRedirect));
diff --git a/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java
new file mode 100644
index 0000000000..169c80eae2
--- /dev/null
+++ b/scm-plugins/scm-legacy-plugin/src/test/java/sonia/scm/legacy/LegacyRepositoryServiceTest.java
@@ -0,0 +1,43 @@
+package sonia.scm.legacy;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import sonia.scm.NotFoundException;
+import sonia.scm.repository.Repository;
+import sonia.scm.repository.RepositoryManager;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.when;
+
+@RunWith(MockitoJUnitRunner.class)
+public class LegacyRepositoryServiceTest {
+
+ @Mock
+ private RepositoryManager repositoryManager;
+
+ private LegacyRepositoryService legacyRepositoryService;
+ private final Repository repository = new Repository("abc123", "git", "space", "repo");
+
+ @Before
+ public void init() {
+ legacyRepositoryService = new LegacyRepositoryService(repositoryManager);
+ }
+
+ @Test
+ public void findRepositoryNameSpaceAndNameForRepositoryId() {
+ when(repositoryManager.get(any(String.class))).thenReturn(repository);
+ NamespaceAndNameDto namespaceAndName = legacyRepositoryService.getNameAndNamespaceForRepositoryId("abc123");
+ assertThat(namespaceAndName.getName()).isEqualToIgnoringCase("repo");
+ assertThat(namespaceAndName.getNamespace()).isEqualToIgnoringCase("space");
+ }
+
+ @Test(expected = NotFoundException.class)
+ public void shouldGetNotFoundExceptionIfRepositoryNotExists() throws NotFoundException {
+ when(repositoryManager.get(any(String.class))).thenReturn(null);
+ legacyRepositoryService.getNameAndNamespaceForRepositoryId("456def");
+ }
+}
diff --git a/scm-ui/src/containers/Main.js b/scm-ui/src/containers/Main.js
index 8681f2457b..1d358ecc87 100644
--- a/scm-ui/src/containers/Main.js
+++ b/scm-ui/src/containers/Main.js
@@ -122,7 +122,6 @@ class Main extends React.Component {
component={Profile}
authenticated={authenticated}
/>
-