Merge pull request #1218 from scm-manager/feature/rename_repo

Feature/rename repo
This commit is contained in:
eheimbuch
2020-07-02 08:25:30 +02:00
committed by GitHub
36 changed files with 1193 additions and 552 deletions

View File

@@ -7,6 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
## Unreleased ## Unreleased
### Added ### Added
- Rename repository name (and namespace if permitted) ([#1218](https://github.com/scm-manager/scm-manager/pull/1218))
- enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210)) - enrich commit mentions in markdown viewer by internal links ([#1210](https://github.com/scm-manager/scm-manager/pull/1210))
- restart service after rpm or deb package upgrade - restart service after rpm or deb package upgrade

View File

@@ -0,0 +1,43 @@
/*
* 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;
import sonia.scm.BadRequestException;
import sonia.scm.ContextEntry;
@SuppressWarnings("squid:MaximumInheritanceDepth") // exceptions have a deep inheritance depth themselves; therefore we accept this here
public class ChangeNamespaceNotAllowedException extends BadRequestException {
public ChangeNamespaceNotAllowedException(Repository repository) {
super(ContextEntry.ContextBuilder.entity(repository).build(), "change of namespace is not allowed in current namespace strategy");
}
private static final String CODE = "ERS2vYb7U1";
@Override
public String getCode() {
return CODE;
}
}

View File

@@ -36,8 +36,16 @@ public interface NamespaceStrategy {
* Create new namespace for the given repository. * Create new namespace for the given repository.
* *
* @param repository repository * @param repository repository
*
* @return namespace * @return namespace
*/ */
String createNamespace(Repository repository); String createNamespace(Repository repository);
/**
* Checks if the namespace can be changed when using this namespace strategy
*
* @return namespace can be changed
*/
default boolean canBeChanged() {
return false;
}
} }

View File

@@ -53,7 +53,7 @@ import java.util.Set;
*/ */
@StaticPermissions( @StaticPermissions(
value = "repository", value = "repository",
permissions = {"read", "modify", "delete", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"}, permissions = {"read", "modify", "delete", "rename", "healthCheck", "pull", "push", "permissionRead", "permissionWrite"},
custom = true, customGlobal = true custom = true, customGlobal = true
) )
@XmlAccessorType(XmlAccessType.FIELD) @XmlAccessorType(XmlAccessType.FIELD)

View File

@@ -38,18 +38,15 @@ import java.util.Collection;
* This class is a singleton and is available via injection. * This class is a singleton and is available via injection.
* *
* @author Sebastian Sdorra * @author Sebastian Sdorra
*
* @apiviz.uses sonia.scm.repository.RepositoryHandler * @apiviz.uses sonia.scm.repository.RepositoryHandler
*/ */
public interface RepositoryManager public interface RepositoryManager
extends TypeManager<Repository, RepositoryHandler> extends TypeManager<Repository, RepositoryHandler> {
{
/** /**
* Fire {@link RepositoryHookEvent} to the event bus. * Fire {@link RepositoryHookEvent} to the event bus.
* *
* @param event hook event * @param event hook event
*
* @since 2.0.0 * @since 2.0.0
*/ */
public void fireHookEvent(RepositoryHookEvent event); public void fireHookEvent(RepositoryHookEvent event);
@@ -58,9 +55,7 @@ public interface RepositoryManager
* Imports an existing {@link Repository}. * Imports an existing {@link Repository}.
* Note: This method should only be called from a {@link RepositoryHandler}. * Note: This method should only be called from a {@link RepositoryHandler}.
* *
*
* @param repository {@link Repository} to import * @param repository {@link Repository} to import
*
* @throws IOException * @throws IOException
*/ */
public void importRepository(Repository repository) throws IOException; public void importRepository(Repository repository) throws IOException;
@@ -71,10 +66,7 @@ public interface RepositoryManager
* Returns a {@link Repository} by its namespace and name or * Returns a {@link Repository} by its namespace and name or
* null if the {@link Repository} could not be found. * null if the {@link Repository} could not be found.
* *
*
* @param namespaceAndName namespace and name of the {@link Repository} * @param namespaceAndName namespace and name of the {@link Repository}
*
*
* @return {@link Repository} by its namespace and name or null * @return {@link Repository} by its namespace and name or null
* if the {@link Repository} could not be found * if the {@link Repository} could not be found
*/ */
@@ -83,7 +75,6 @@ public interface RepositoryManager
/** /**
* Returns all configured repository types. * Returns all configured repository types.
* *
*
* @return all configured repository types * @return all configured repository types
*/ */
public Collection<RepositoryType> getConfiguredTypes(); public Collection<RepositoryType> getConfiguredTypes();
@@ -91,11 +82,17 @@ public interface RepositoryManager
/** /**
* Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...). * Returns a {@link RepositoryHandler} by the given type (hg, git, svn ...).
* *
*
* @param type the type of the {@link RepositoryHandler} * @param type the type of the {@link RepositoryHandler}
*
* @return {@link RepositoryHandler} by the given type * @return {@link RepositoryHandler} by the given type
*/ */
@Override @Override
public RepositoryHandler getHandler(String type); public RepositoryHandler getHandler(String type);
/**
* @param repository the repository {@link Repository}
* @param newNameSpace the new repository namespace
* @param newName the new repository name
* @return {@link Repository} the renamed repository
*/
public Repository rename(Repository repository, String newNameSpace, String newName);
} }

View File

@@ -42,17 +42,14 @@ import java.util.Collection;
*/ */
public class RepositoryManagerDecorator public class RepositoryManagerDecorator
extends ManagerDecorator<Repository> extends ManagerDecorator<Repository>
implements RepositoryManager implements RepositoryManager {
{
/** /**
* Constructs ... * Constructs ...
* *
*
* @param decorated * @param decorated
*/ */
public RepositoryManagerDecorator(RepositoryManager decorated) public RepositoryManagerDecorator(RepositoryManager decorated) {
{
super(decorated); super(decorated);
this.decorated = decorated; this.decorated = decorated;
} }
@@ -63,8 +60,7 @@ public class RepositoryManagerDecorator
* {@inheritDoc} * {@inheritDoc}
*/ */
@Override @Override
public void fireHookEvent(RepositoryHookEvent event) public void fireHookEvent(RepositoryHookEvent event) {
{
decorated.fireHookEvent(event); decorated.fireHookEvent(event);
} }
@@ -79,65 +75,66 @@ public class RepositoryManagerDecorator
//~--- get methods ---------------------------------------------------------- //~--- get methods ----------------------------------------------------------
@Override @Override
public Repository get(NamespaceAndName namespaceAndName) public Repository get(NamespaceAndName namespaceAndName) {
{
return decorated.get(namespaceAndName); return decorated.get(namespaceAndName);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
*
* @return * @return
*/ */
@Override @Override
public Collection<RepositoryType> getConfiguredTypes() public Collection<RepositoryType> getConfiguredTypes() {
{
return decorated.getConfiguredTypes(); return decorated.getConfiguredTypes();
} }
/** /**
* Returns the decorated {@link RepositoryManager}. * Returns the decorated {@link RepositoryManager}.
* *
*
* @return decorated {@link RepositoryManager} * @return decorated {@link RepositoryManager}
*
* @since 1.34 * @since 1.34
*/ */
public RepositoryManager getDecorated() public RepositoryManager getDecorated() {
{
return decorated; return decorated;
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
*
* @param type * @param type
*
* @return * @return
*/ */
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public RepositoryHandler getHandler(String type) public RepositoryHandler getHandler(String type) {
{
return decorated.getHandler(type); return decorated.getHandler(type);
} }
/** /**
* {@inheritDoc} * {@inheritDoc}
* *
* @return
*/
@Override
public Collection<Type> getTypes() {
return decorated.getTypes();
}
/**
* {@inheritDoc}
* *
* @return * @return
*/ */
@Override @Override
public Collection<Type> getTypes() public Repository rename(Repository repository, String newNamespace, String newName) {
{ return decorated.rename(repository, newNamespace, newName);
return decorated.getTypes();
} }
//~--- fields --------------------------------------------------------------- //~--- fields ---------------------------------------------------------------
/** Field description */ /**
* Field description
*/
private final RepositoryManager decorated; private final RepositoryManager decorated;
} }

View File

@@ -49,7 +49,7 @@ public final class RepositoryModificationEvent extends RepositoryEvent implement
*/ */
public RepositoryModificationEvent(HandlerEventType eventType, Repository item, Repository itemBeforeModification) public RepositoryModificationEvent(HandlerEventType eventType, Repository item, Repository itemBeforeModification)
{ {
super(eventType, item); super(eventType, item, itemBeforeModification);
this.itemBeforeModification = itemBeforeModification; this.itemBeforeModification = itemBeforeModification;
} }

View File

@@ -113,7 +113,8 @@
"repositoryForm": { "repositoryForm": {
"subtitle": "Repository bearbeiten", "subtitle": "Repository bearbeiten",
"submit": "Speichern", "submit": "Speichern",
"initializeRepository": "Repository initiieren" "initializeRepository": "Repository initiieren",
"dangerZone": "Gefahrenzone"
}, },
"sources": { "sources": {
"file-tree": { "file-tree": {
@@ -196,6 +197,8 @@
}, },
"deleteRepo": { "deleteRepo": {
"button": "Repository löschen", "button": "Repository löschen",
"subtitle": "Löscht dieses Repository",
"description": "Diese Aktion kann nicht rückgangig gemacht werden.",
"confirmAlert": { "confirmAlert": {
"title": "Repository löschen", "title": "Repository löschen",
"message": "Soll das Repository wirklich gelöscht werden?", "message": "Soll das Repository wirklich gelöscht werden?",
@@ -203,6 +206,22 @@
"cancel": "Nein" "cancel": "Nein"
} }
}, },
"renameRepo": {
"button": "Repository umbenennen",
"subtitle": "Benennt dieses Repository um",
"description": "Es werden keine Weiterleitung auf den neuen Namen eingerichtet.",
"modal": {
"title": "Repository umbenennen",
"label": {
"repoName": "Repository Name",
"repoNamespace": "Repository Namespace"
},
"button": {
"rename": "Umbenennen",
"cancel": "Abbrechen"
}
}
},
"diff": { "diff": {
"sideBySide": "Zur zweispaltigen Ansicht wechseln", "sideBySide": "Zur zweispaltigen Ansicht wechseln",
"combined": "Zur kombinierten Ansicht wechseln", "combined": "Zur kombinierten Ansicht wechseln",

View File

@@ -113,7 +113,8 @@
"repositoryForm": { "repositoryForm": {
"subtitle": "Edit Repository", "subtitle": "Edit Repository",
"submit": "Save", "submit": "Save",
"initializeRepository": "Initialize repository" "initializeRepository": "Initialize repository",
"dangerZone": "Danger Zone"
}, },
"sources": { "sources": {
"file-tree": { "file-tree": {
@@ -196,6 +197,8 @@
}, },
"deleteRepo": { "deleteRepo": {
"button": "Delete Repository", "button": "Delete Repository",
"subtitle": "Deletes this repository",
"description": "Once a repository was deleted, this cannot be undone. Please be careful with this action.",
"confirmAlert": { "confirmAlert": {
"title": "Delete repository", "title": "Delete repository",
"message": "Do you really want to delete the repository?", "message": "Do you really want to delete the repository?",
@@ -203,6 +206,22 @@
"cancel": "No" "cancel": "No"
} }
}, },
"renameRepo": {
"button": "Rename Repository",
"subtitle": "Renames this repository",
"description": "There will be no redirects to the renamed repository.",
"modal": {
"title": "Rename repository",
"label": {
"repoName": "Repository name",
"repoNamespace": "Repository namespace"
},
"button": {
"rename": "Rename",
"cancel": "Cancel"
}
}
},
"diff": { "diff": {
"changes": { "changes": {
"add": "added", "add": "added",

View File

@@ -44,7 +44,7 @@ import GlobalConfig from "./GlobalConfig";
import RepositoryRoles from "../roles/containers/RepositoryRoles"; import RepositoryRoles from "../roles/containers/RepositoryRoles";
import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole"; import SingleRepositoryRole from "../roles/containers/SingleRepositoryRole";
import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole"; import CreateRepositoryRole from "../roles/containers/CreateRepositoryRole";
import { StateMenuContextProvider } from "@scm-manager/ui-components/src/navigation/MenuContext"; import { StateMenuContextProvider } from "@scm-manager/ui-components";
type Props = RouteComponentProps & type Props = RouteComponentProps &
WithTranslation & { WithTranslation & {

View File

@@ -54,7 +54,7 @@ import PluginBottomActions from "../components/PluginBottomActions";
import ExecutePendingActionModal from "../components/ExecutePendingActionModal"; import ExecutePendingActionModal from "../components/ExecutePendingActionModal";
import CancelPendingActionModal from "../components/CancelPendingActionModal"; import CancelPendingActionModal from "../components/CancelPendingActionModal";
import UpdateAllActionModal from "../components/UpdateAllActionModal"; import UpdateAllActionModal from "../components/UpdateAllActionModal";
import { Plugin } from "@scm-manager/ui-types/src"; import { Plugin } from "@scm-manager/ui-types";
import ShowPendingModal from "../components/ShowPendingModal"; import ShowPendingModal from "../components/ShowPendingModal";
type Props = WithTranslation & { type Props = WithTranslation & {

View File

@@ -31,7 +31,7 @@ import { Page } from "@scm-manager/ui-components";
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource"; import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups"; import { createGroup, createGroupReset, getCreateGroupFailure, isCreateGroupPending } from "../modules/groups";
import GroupForm from "../components/GroupForm"; import GroupForm from "../components/GroupForm";
import { apiClient } from "@scm-manager/ui-components/src"; import { apiClient } from "@scm-manager/ui-components";
type Props = WithTranslation & { type Props = WithTranslation & {
createGroup: (link: string, group: Group, callback?: () => void) => void; createGroup: (link: string, group: Group, callback?: () => void) => void;

View File

@@ -31,7 +31,7 @@ import { DisplayedUser, Group } from "@scm-manager/ui-types";
import { ErrorNotification } from "@scm-manager/ui-components"; import { ErrorNotification } from "@scm-manager/ui-components";
import { getUserAutoCompleteLink } from "../../modules/indexResource"; import { getUserAutoCompleteLink } from "../../modules/indexResource";
import DeleteGroup from "./DeleteGroup"; import DeleteGroup from "./DeleteGroup";
import { apiClient } from "@scm-manager/ui-components/src"; import { apiClient } from "@scm-manager/ui-components";
import { compose } from "redux"; import { compose } from "redux";
type Props = { type Props = {

View File

@@ -26,8 +26,9 @@ import styled from "styled-components";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { Repository, RepositoryType } from "@scm-manager/ui-types"; import { Repository, RepositoryType } from "@scm-manager/ui-types";
import { Checkbox, Level, InputField, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components"; import { Checkbox, InputField, Level, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components";
import * as validator from "./repositoryValidation"; import * as validator from "./repositoryValidation";
import { CUSTOM_NAMESPACE_STRATEGY } from "../../modules/repos";
const CheckboxWrapper = styled.div` const CheckboxWrapper = styled.div`
margin-top: 2em; margin-top: 2em;
@@ -59,8 +60,6 @@ type State = {
contactValidationError: boolean; contactValidationError: boolean;
}; };
const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
class RepositoryForm extends React.Component<Props, State> { class RepositoryForm extends React.Component<Props, State> {
constructor(props: Props) { constructor(props: Props) {
super(props); super(props);
@@ -108,7 +107,7 @@ class RepositoryForm extends React.Component<Props, State> {
); );
}; };
submit = (event: Event) => { submit = (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault(); event.preventDefault();
if (this.isValid()) { if (this.isValid()) {
this.props.submitForm(this.state.repository, this.state.initRepository); this.props.submitForm(this.state.repository, this.state.initRepository);

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.
*/
import React, { FC } from "react";
import { Repository, Links } from "@scm-manager/ui-types";
import RenameRepository from "./RenameRepository";
import DeleteRepo from "./DeleteRepo";
import styled from "styled-components";
import { Subtitle } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
type Props = {
repository: Repository;
indexLinks: Links;
};
const DangerZoneContainer = styled.div`
padding: 1rem;
border: 1px solid #ff6a88;
border-radius: 5px;
> *:not(:last-child) {
padding-bottom: 1.5rem;
border-bottom: solid 2px whitesmoke;
}
`;
const DangerZone: FC<Props> = ({ repository, indexLinks }) => {
const [t] = useTranslation("repos");
const dangerZone = [];
if (repository?._links?.rename || repository?._links?.renameWithNamespace) {
dangerZone.push(<RenameRepository repository={repository} indexLinks={indexLinks} />);
}
if (repository?._links?.delete) {
// @ts-ignore
dangerZone.push(<DeleteRepo repository={repository} />);
}
if (dangerZone.length === 0) {
return null;
}
return (
<>
<hr />
<Subtitle subtitle={t("repositoryForm.dangerZone")} />
<DangerZoneContainer>{dangerZone.map(entry => entry)}</DangerZoneContainer>
</>
);
};
export default DangerZone;

View File

@@ -24,22 +24,20 @@
import React from "react"; import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { compose } from "redux"; import { compose } from "redux";
import { withRouter } from "react-router-dom"; import { RouteComponentProps, withRouter } from "react-router-dom";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
import { History } from "history"; import { History } from "history";
import { Repository } from "@scm-manager/ui-types"; import { Repository } from "@scm-manager/ui-types";
import { confirmAlert, DeleteButton, ErrorNotification, Level } from "@scm-manager/ui-components"; import { confirmAlert, DeleteButton, ErrorNotification, Level, ButtonGroup } from "@scm-manager/ui-components";
import { deleteRepo, getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos"; import { deleteRepo, getDeleteRepoFailure, isDeleteRepoPending } from "../modules/repos";
type Props = WithTranslation & { type Props = RouteComponentProps &
WithTranslation & {
loading: boolean; loading: boolean;
error: Error; error: Error;
repository: Repository; repository: Repository;
confirmDialog?: boolean; confirmDialog?: boolean;
deleteRepo: (p1: Repository, p2: () => void) => void; deleteRepo: (p1: Repository, p2: () => void) => void;
// context props
history: History;
}; };
class DeleteRepo extends React.Component<Props> { class DeleteRepo extends React.Component<Props> {
@@ -88,9 +86,16 @@ class DeleteRepo extends React.Component<Props> {
return ( return (
<> <>
<hr />
<ErrorNotification error={error} /> <ErrorNotification error={error} />
<Level right={<DeleteButton label={t("deleteRepo.button")} action={action} loading={loading} />} /> <Level
left={
<div>
<strong>{t("deleteRepo.subtitle")}</strong>
<p>{t("deleteRepo.description")}</p>
</div>
}
right={<DeleteButton label={t("deleteRepo.button")} action={action} loading={loading} />}
/>
</> </>
); );
} }

View File

@@ -25,17 +25,19 @@ import React from "react";
import { connect } from "react-redux"; import { connect } from "react-redux";
import { withRouter } from "react-router-dom"; import { withRouter } from "react-router-dom";
import RepositoryForm from "../components/form"; import RepositoryForm from "../components/form";
import DeleteRepo from "./DeleteRepo"; import { Repository, Links } from "@scm-manager/ui-types";
import { Repository } from "@scm-manager/ui-types";
import { getModifyRepoFailure, isModifyRepoPending, modifyRepo, modifyRepoReset } from "../modules/repos"; import { getModifyRepoFailure, isModifyRepoPending, modifyRepo, modifyRepoReset } from "../modules/repos";
import { History } from "history"; import { History } from "history";
import { ErrorNotification } from "@scm-manager/ui-components"; import { ErrorNotification } from "@scm-manager/ui-components";
import { ExtensionPoint } from "@scm-manager/ui-extensions"; import { ExtensionPoint } from "@scm-manager/ui-extensions";
import { compose } from "redux"; import { compose } from "redux";
import DangerZone from "./DangerZone";
import { getLinks } from "../../modules/indexResource";
type Props = { type Props = {
loading: boolean; loading: boolean;
error: Error; error: Error;
indexLinks: Links;
modifyRepo: (p1: Repository, p2: () => void) => void; modifyRepo: (p1: Repository, p2: () => void) => void;
modifyRepoReset: (p: Repository) => void; modifyRepoReset: (p: Repository) => void;
@@ -69,7 +71,7 @@ class EditRepo extends React.Component<Props> {
}; };
render() { render() {
const { loading, error, repository } = this.props; const { loading, error, repository, indexLinks } = this.props;
const url = this.matchedUrl(); const url = this.matchedUrl();
@@ -79,7 +81,7 @@ class EditRepo extends React.Component<Props> {
}; };
return ( return (
<div> <>
<ErrorNotification error={error} /> <ErrorNotification error={error} />
<RepositoryForm <RepositoryForm
repository={this.props.repository} repository={this.props.repository}
@@ -89,8 +91,8 @@ class EditRepo extends React.Component<Props> {
}} }}
/> />
<ExtensionPoint name="repo-config.route" props={extensionProps} renderAll={true} /> <ExtensionPoint name="repo-config.route" props={extensionProps} renderAll={true} />
<DeleteRepo repository={repository} /> <DangerZone repository={repository} indexLinks={indexLinks} />
</div> </>
); );
} }
} }
@@ -99,9 +101,12 @@ const mapStateToProps = (state: any, ownProps: Props) => {
const { namespace, name } = ownProps.repository; const { namespace, name } = ownProps.repository;
const loading = isModifyRepoPending(state, namespace, name); const loading = isModifyRepoPending(state, namespace, name);
const error = getModifyRepoFailure(state, namespace, name); const error = getModifyRepoFailure(state, namespace, name);
const indexLinks = getLinks(state);
return { return {
loading, loading,
error error,
indexLinks
}; };
}; };

View File

@@ -0,0 +1,178 @@
/*
* 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.
*/
import React, { FC, useEffect, useState } from "react";
import { Link, Links, Repository } from "@scm-manager/ui-types";
import { CONTENT_TYPE, CUSTOM_NAMESPACE_STRATEGY } from "../modules/repos";
import { Button, ButtonGroup, ErrorNotification, InputField, Level, Loading, Modal } from "@scm-manager/ui-components";
import { useTranslation } from "react-i18next";
import { apiClient } from "@scm-manager/ui-components";
import { useHistory } from "react-router-dom";
import { ExtensionPoint } from "@scm-manager/ui-extensions";
import * as validator from "../components/form/repositoryValidation";
type Props = {
repository: Repository;
indexLinks: Links;
};
const RenameRepository: FC<Props> = ({ repository, indexLinks }) => {
let history = useHistory();
const [t] = useTranslation("repos");
const [error, setError] = useState<Error | undefined>(undefined);
const [loading, setLoading] = useState(false);
const [showModal, setShowModal] = useState(false);
const [name, setName] = useState(repository.name);
const [namespace, setNamespace] = useState(repository.namespace);
const [nameValidationError, setNameValidationError] = useState(false);
const [namespaceValidationError, setNamespaceValidationError] = useState(false);
const [currentNamespaceStrategie, setCurrentNamespaceStrategy] = useState("");
useEffect(() => {
apiClient
.get((indexLinks?.namespaceStrategies as Link).href)
.then(result => result.json())
.then(result => setCurrentNamespaceStrategy(result.current))
.catch(setError);
}, [repository]);
if (error) {
return <ErrorNotification error={error} />;
}
if (loading) {
return <Loading />;
}
const isValid =
!nameValidationError &&
!namespaceValidationError &&
(repository.name !== name || repository.namespace !== namespace);
const handleNamespaceChange = (namespace: string) => {
setNamespaceValidationError(!validator.isNameValid(namespace));
setNamespace(namespace);
};
const handleNameChange = (name: string) => {
setNameValidationError(!validator.isNameValid(name));
setName(name);
};
const renderNamespaceField = () => {
const props = {
label: t("repository.namespace"),
helpText: t("help.namespaceHelpText"),
value: namespace,
onChange: handleNamespaceChange,
errorMessage: t("validation.namespace-invalid"),
validationError: namespaceValidationError
};
if (currentNamespaceStrategie === CUSTOM_NAMESPACE_STRATEGY) {
return <InputField {...props} />;
}
return <ExtensionPoint name="repos.create.namespace" props={props} renderAll={false} />;
};
const rename = () => {
setLoading(true);
const url = repository?._links?.renameWithNamespace
? (repository?._links?.renameWithNamespace as Link).href
: (repository?._links?.rename as Link).href;
apiClient
.post(url, { name, namespace }, CONTENT_TYPE)
.then(() => setLoading(false))
.then(() => history.push(`/repo/${namespace}/${name}`))
.catch(setError);
};
const modalBody = (
<div>
<InputField
label={t("renameRepo.modal.label.repoName")}
name={t("renameRepo.modal.label.repoName")}
errorMessage={t("validation.name-invalid")}
helpText={t("help.nameHelpText")}
validationError={nameValidationError}
value={name}
onChange={handleNameChange}
/>
{renderNamespaceField()}
</div>
);
const footer = (
<>
<ButtonGroup>
<Button
color="warning"
icon="exclamation-triangle"
label={t("renameRepo.modal.button.rename")}
disabled={!isValid}
title={t("renameRepo.modal.button.rename")}
action={rename}
/>
<Button
label={t("renameRepo.modal.button.cancel")}
title={t("renameRepo.modal.button.cancel")}
action={() => setShowModal(false)}
/>
</ButtonGroup>
</>
);
return (
<>
<Modal
active={showModal}
title={t("renameRepo.modal.title")}
footer={footer}
body={modalBody}
closeFunction={() => setShowModal(false)}
/>
<Level
left={
<div>
<strong>{t("renameRepo.subtitle")}</strong>
<p>{t("renameRepo.description")}</p>
</div>
}
right={
<Button
label={t("renameRepo.button")}
action={() => setShowModal(true)}
loading={loading}
color="warning"
icon="edit"
/>
}
/>
</>
);
};
export default RenameRepository;

View File

@@ -74,6 +74,13 @@ class RepositoryRoot extends React.Component<Props> {
fetchRepoByName(repoLink, namespace, name); fetchRepoByName(repoLink, namespace, name);
} }
componentDidUpdate(prevProps: Props) {
const { fetchRepoByName, namespace, name, repoLink } = this.props;
if (namespace !== prevProps.namespace || name !== prevProps.name) {
fetchRepoByName(repoLink, namespace, name);
}
}
stripEndingSlash = (url: string) => { stripEndingSlash = (url: string) => {
if (url.endsWith("/")) { if (url.endsWith("/")) {
return url.substring(0, url.length - 1); return url.substring(0, url.length - 1);

View File

@@ -27,7 +27,6 @@ import * as types from "../../modules/types";
import { Action, Repository, RepositoryCollection } from "@scm-manager/ui-types"; import { Action, Repository, RepositoryCollection } from "@scm-manager/ui-types";
import { isPending } from "../../modules/pending"; import { isPending } from "../../modules/pending";
import { getFailure } from "../../modules/failure"; import { getFailure } from "../../modules/failure";
import React from "react";
export const FETCH_REPOS = "scm/repos/FETCH_REPOS"; export const FETCH_REPOS = "scm/repos/FETCH_REPOS";
export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`; export const FETCH_REPOS_PENDING = `${FETCH_REPOS}_${types.PENDING_SUFFIX}`;
@@ -56,7 +55,9 @@ export const DELETE_REPO_PENDING = `${DELETE_REPO}_${types.PENDING_SUFFIX}`;
export const DELETE_REPO_SUCCESS = `${DELETE_REPO}_${types.SUCCESS_SUFFIX}`; export const DELETE_REPO_SUCCESS = `${DELETE_REPO}_${types.SUCCESS_SUFFIX}`;
export const DELETE_REPO_FAILURE = `${DELETE_REPO}_${types.FAILURE_SUFFIX}`; export const DELETE_REPO_FAILURE = `${DELETE_REPO}_${types.FAILURE_SUFFIX}`;
const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2"; export const CONTENT_TYPE = "application/vnd.scmm-repository+json;v=2";
export const CUSTOM_NAMESPACE_STRATEGY = "CustomNamespaceStrategy";
// fetch repos // fetch repos

View File

@@ -29,7 +29,7 @@ import { binder, ExtensionPoint } from "@scm-manager/ui-extensions";
import { File } from "@scm-manager/ui-types"; import { File } from "@scm-manager/ui-types";
import { DateFromNow, FileSize, Tooltip } from "@scm-manager/ui-components"; import { DateFromNow, FileSize, Tooltip } from "@scm-manager/ui-components";
import FileIcon from "./FileIcon"; import FileIcon from "./FileIcon";
import { Icon } from "@scm-manager/ui-components/src"; import { Icon } from "@scm-manager/ui-components";
import { WithTranslation, withTranslation } from "react-i18next"; import { WithTranslation, withTranslation } from "react-i18next";
type Props = WithTranslation & { type Props = WithTranslation & {

View File

@@ -0,0 +1,39 @@
/*
* 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.api.v2.resources;
import lombok.Getter;
import lombok.NoArgsConstructor;
import sonia.scm.util.ValidationUtil;
import javax.validation.constraints.Pattern;
@Getter
@NoArgsConstructor
public class RepositoryRenameDto {
@Pattern(regexp = ValidationUtil.REGEX_REPOSITORYNAME)
private String name;
private String namespace;
}

View File

@@ -34,11 +34,11 @@ import sonia.scm.repository.RepositoryManager;
import sonia.scm.web.VndMediaType; import sonia.scm.web.VndMediaType;
import javax.inject.Inject; import javax.inject.Inject;
import javax.inject.Provider;
import javax.validation.Valid; import javax.validation.Valid;
import javax.ws.rs.Consumes; import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE; import javax.ws.rs.DELETE;
import javax.ws.rs.GET; import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT; import javax.ws.rs.PUT;
import javax.ws.rs.Path; import javax.ws.rs.Path;
import javax.ws.rs.PathParam; import javax.ws.rs.PathParam;
@@ -64,8 +64,7 @@ public class RepositoryResource {
public RepositoryResource( public RepositoryResource(
RepositoryToRepositoryDtoMapper repositoryToDtoMapper, RepositoryToRepositoryDtoMapper repositoryToDtoMapper,
RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager, RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, RepositoryManager manager,
RepositoryBasedResourceProvider resourceProvider RepositoryBasedResourceProvider resourceProvider) {
) {
this.dtoToRepositoryMapper = dtoToRepositoryMapper; this.dtoToRepositoryMapper = dtoToRepositoryMapper;
this.manager = manager; this.manager = manager;
this.repositoryToDtoMapper = repositoryToDtoMapper; this.repositoryToDtoMapper = repositoryToDtoMapper;
@@ -80,7 +79,6 @@ public class RepositoryResource {
* *
* @param namespace the namespace of the repository * @param namespace the namespace of the repository
* @param name the name of the repository * @param name the name of the repository
*
*/ */
@GET @GET
@Path("") @Path("")
@@ -129,7 +127,6 @@ public class RepositoryResource {
* *
* @param namespace the namespace of the repository to delete * @param namespace the namespace of the repository to delete
* @param name the name of the repository to delete * @param name the name of the repository to delete
*
*/ */
@DELETE @DELETE
@Path("") @Path("")
@@ -176,6 +173,37 @@ public class RepositoryResource {
); );
} }
/**
* Renames the given repository.
*
* <strong>Note:</strong> This method requires "repository" privilege.
*
* @param namespace the namespace of the repository to be modified
* @param name the name of the repository to be modified
* @param renameDto renameDto object to modify
*/
@POST
@Path("rename")
@Consumes(VndMediaType.REPOSITORY)
@Operation(summary = "Rename repository", description = "Renames the repository for the given namespace and name.", tags = "Repository")
@ApiResponse(responseCode = "204", description = "update success")
@ApiResponse(responseCode = "400", description = "invalid body, e.g. illegal change of namespace or name")
@ApiResponse(responseCode = "401", description = "not authenticated / invalid credentials")
@ApiResponse(responseCode = "403", description = "not authorized, the current user does not have the \"repository:renameDto\" privilege")
@ApiResponse(
responseCode = "404",
description = "not found, no repository with the specified namespace and name available",
content = @Content(
mediaType = VndMediaType.ERROR_TYPE,
schema = @Schema(implementation = ErrorDto.class)
))
@ApiResponse(responseCode = "500", description = "internal server error")
public Response rename(@PathParam("namespace") String namespace, @PathParam("name") String name, @Valid RepositoryRenameDto renameDto) {
Repository repository = loadBy(namespace, name).get();
manager.rename(repository, renameDto.getNamespace(), renameDto.getName());
return Response.status(204).build();
}
private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) { private Repository processUpdate(RepositoryDto repositoryDto, Repository existing) {
Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId()); Repository changedRepository = dtoToRepositoryMapper.map(repositoryDto, existing.getId());
changedRepository.setPermissions(existing.getPermissions()); changedRepository.setPermissions(existing.getPermissions());

View File

@@ -24,14 +24,15 @@
package sonia.scm.api.v2.resources; package sonia.scm.api.v2.resources;
import com.google.inject.Inject;
import de.otto.edison.hal.Embedded; import de.otto.edison.hal.Embedded;
import de.otto.edison.hal.Link; import de.otto.edison.hal.Link;
import de.otto.edison.hal.Links; import de.otto.edison.hal.Links;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.ObjectFactory; import org.mapstruct.ObjectFactory;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.Feature; import sonia.scm.repository.Feature;
import sonia.scm.repository.HealthCheckFailure; import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryPermissions; import sonia.scm.repository.RepositoryPermissions;
import sonia.scm.repository.api.Command; import sonia.scm.repository.api.Command;
@@ -41,7 +42,9 @@ import sonia.scm.repository.api.ScmProtocol;
import sonia.scm.web.EdisonHalAppender; import sonia.scm.web.EdisonHalAppender;
import sonia.scm.web.api.RepositoryToHalMapper; import sonia.scm.web.api.RepositoryToHalMapper;
import javax.inject.Inject;
import java.util.List; import java.util.List;
import java.util.Set;
import static de.otto.edison.hal.Embedded.embeddedBuilder; import static de.otto.edison.hal.Embedded.embeddedBuilder;
import static de.otto.edison.hal.Link.link; import static de.otto.edison.hal.Link.link;
@@ -56,7 +59,11 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
@Inject @Inject
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@Inject @Inject
private ScmConfiguration scmConfiguration;
@Inject
private RepositoryServiceFactory serviceFactory; private RepositoryServiceFactory serviceFactory;
@Inject
private Set<NamespaceStrategy> strategies;
abstract HealthCheckFailureDto toDto(HealthCheckFailure failure); abstract HealthCheckFailureDto toDto(HealthCheckFailure failure);
@@ -72,6 +79,13 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
if (RepositoryPermissions.modify(repository).isPermitted()) { if (RepositoryPermissions.modify(repository).isPermitted()) {
linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName()))); linksBuilder.single(link("update", resourceLinks.repository().update(repository.getNamespace(), repository.getName())));
} }
if (RepositoryPermissions.rename(repository).isPermitted()) {
if (isRenameNamespacePossible()) {
linksBuilder.single(link("renameWithNamespace", resourceLinks.repository().rename(repository.getNamespace(), repository.getName())));
} else {
linksBuilder.single(link("rename", resourceLinks.repository().rename(repository.getNamespace(), repository.getName())));
}
}
if (RepositoryPermissions.permissionRead(repository).isPermitted()) { if (RepositoryPermissions.permissionRead(repository).isPermitted()) {
linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName()))); linksBuilder.single(link("permissions", resourceLinks.repositoryPermission().all(repository.getNamespace(), repository.getName())));
} }
@@ -105,6 +119,15 @@ public abstract class RepositoryToRepositoryDtoMapper extends BaseMapper<Reposit
return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build()); return new RepositoryDto(linksBuilder.build(), embeddedBuilder.build());
} }
private boolean isRenameNamespacePossible() {
for (NamespaceStrategy strategy : strategies) {
if (strategy.getClass().getSimpleName().equals(scmConfiguration.getNamespaceStrategy())) {
return strategy.canBeChanged();
}
}
return false;
}
private Link createProtocolLink(ScmProtocol protocol) { private Link createProtocolLink(ScmProtocol protocol) {
return Link.linkBuilder("protocol", protocol.getUrl()).withName(protocol.getType()).build(); return Link.linkBuilder("protocol", protocol.getUrl()).withName(protocol.getType()).build();
} }

View File

@@ -279,6 +279,10 @@ class ResourceLinks {
String update(String namespace, String name) { String update(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("update").parameters().href(); return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("update").parameters().href();
} }
String rename(String namespace, String name) {
return repositoryLinkBuilder.method("getRepositoryResource").parameters(namespace, name).method("rename").parameters().href();
}
} }
RepositoryCollectionLinks repositoryCollection() { RepositoryCollectionLinks repositoryCollection() {

View File

@@ -41,4 +41,9 @@ public class CustomNamespaceStrategy implements NamespaceStrategy {
return namespace; return namespace;
} }
@Override
public boolean canBeChanged() {
return true;
}
} }

View File

@@ -25,6 +25,7 @@
package sonia.scm.repository; package sonia.scm.repository;
import com.github.sdorra.ssp.PermissionActionCheck; import com.github.sdorra.ssp.PermissionActionCheck;
import com.google.common.base.Strings;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;
import com.google.common.util.concurrent.ThreadFactoryBuilder; import com.google.common.util.concurrent.ThreadFactoryBuilder;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -35,6 +36,7 @@ import org.slf4j.LoggerFactory;
import sonia.scm.ConfigurationException; import sonia.scm.ConfigurationException;
import sonia.scm.HandlerEventType; import sonia.scm.HandlerEventType;
import sonia.scm.ManagerDaoAdapter; import sonia.scm.ManagerDaoAdapter;
import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.SCMContextProvider; import sonia.scm.SCMContextProvider;
import sonia.scm.Type; import sonia.scm.Type;
@@ -83,7 +85,6 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
private final Provider<NamespaceStrategy> namespaceStrategyProvider; private final Provider<NamespaceStrategy> namespaceStrategyProvider;
private final ManagerDaoAdapter<Repository> managerDaoAdapter; private final ManagerDaoAdapter<Repository> managerDaoAdapter;
@Inject @Inject
public DefaultRepositoryManager(ScmConfiguration configuration, public DefaultRepositoryManager(ScmConfiguration configuration,
SCMContextProvider contextProvider, KeyGenerator keyGenerator, SCMContextProvider contextProvider, KeyGenerator keyGenerator,
@@ -243,6 +244,40 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
return repository; return repository;
} }
public Repository rename(Repository repository, String newNamespace, String newName) {
if (hasNamespaceOrNameNotChanged(repository, newNamespace, newName)) {
throw new NoChangesMadeException(repository);
}
Repository changedRepository = repository.clone();
if (!Strings.isNullOrEmpty(newName)) {
changedRepository.setName(newName);
}
if (!Strings.isNullOrEmpty(newNamespace) && !repository.getNamespace().equals(newNamespace)) {
NamespaceStrategy strategy = namespaceStrategyProvider.get();
if (!strategy.canBeChanged()) {
throw new ChangeNamespaceNotAllowedException(repository);
}
changedRepository.setNamespace(strategy.createNamespace(changedRepository));
}
managerDaoAdapter.modify(
changedRepository,
RepositoryPermissions::rename,
notModified -> {
},
notModified -> fireEvent(HandlerEventType.MODIFY, changedRepository, repository));
return changedRepository;
}
private boolean hasNamespaceOrNameNotChanged(Repository repository, String newNamespace, String newName) {
return repository.getName().equals(newName)
&& repository.getNamespace().equals(newNamespace);
}
@Override @Override
public Collection<Repository> getAll(Predicate<Repository> filter, Comparator<Repository> comparator) { public Collection<Repository> getAll(Predicate<Repository> filter, Comparator<Repository> comparator) {
List<Repository> repositories = Lists.newArrayList(); List<Repository> repositories = Lists.newArrayList();
@@ -345,8 +380,7 @@ public class DefaultRepositoryManager extends AbstractRepositoryManager {
types.add(type); types.add(type);
} }
private RepositoryHandler getHandler(Repository repository) private RepositoryHandler getHandler(Repository repository) {
{
String type = repository.getType(); String type = repository.getType();
RepositoryHandler handler = handlerMap.get(type); RepositoryHandler handler = handlerMap.get(type);

View File

@@ -35,6 +35,9 @@
<permission> <permission>
<value>repository:read,pull,push:*</value> <value>repository:read,pull,push:*</value>
</permission> </permission>
<permission>
<value>repository:read,rename:*</value>
</permission>
<permission> <permission>
<value>repository:*</value> <value>repository:*</value>
</permission> </permission>

View File

@@ -28,6 +28,7 @@
<verb>read</verb> <verb>read</verb>
<verb>modify</verb> <verb>modify</verb>
<verb>delete</verb> <verb>delete</verb>
<verb>rename</verb>
<verb>pull</verb> <verb>pull</verb>
<verb>push</verb> <verb>push</verb>
<verb>permissionRead</verb> <verb>permissionRead</verb>

View File

@@ -28,6 +28,12 @@
"description": "Darf alle Repositories lesen, klonen und schreiben." "description": "Darf alle Repositories lesen, klonen und schreiben."
} }
}, },
"read,rename": {
"*": {
"displayName": "Alle Repositories umbenennen",
"description": "Darf alle Repositories lesen und umbenennen."
}
},
"*": { "*": {
"displayName": "Alle Repositories besitzen (Owner)", "displayName": "Alle Repositories besitzen (Owner)",
"description": "Darf alle Repositories lesen, klonen, schreiben, konfigurieren und löschen." "description": "Darf alle Repositories lesen, klonen, schreiben, konfigurieren und löschen."
@@ -103,6 +109,10 @@
"displayName": "Repository Löschen", "displayName": "Repository Löschen",
"description": "Darf das Repository löschen." "description": "Darf das Repository löschen."
}, },
"rename": {
"displayName": "Repository umbenennen",
"description": "Darf das Repository umbenennen."
},
"pull": { "pull": {
"displayName": "Pull/Checkout", "displayName": "Pull/Checkout",
"description": "Darf pull/checkout auf das Repository ausführen." "description": "Darf pull/checkout auf das Repository ausführen."
@@ -187,6 +197,10 @@
"displayName": "Es wurden keine Änderungen durchgeführt", "displayName": "Es wurden keine Änderungen durchgeführt",
"description": "Das Repository wurde nicht verändert. Daher konnte kein neuer Commit erzeugt werden. Womöglich werden Änderungen aufgrund einer .ignore-Datei Definition nicht berücksichtigt." "description": "Das Repository wurde nicht verändert. Daher konnte kein neuer Commit erzeugt werden. Womöglich werden Änderungen aufgrund einer .ignore-Datei Definition nicht berücksichtigt."
}, },
"ERS2vYb7U1": {
"displayName": "Änderung des Namespace nicht möglich",
"description": "Namespaces dürfen nur mit der Namespace Strategie \"Benutzerdefiniert\" verändert werden."
},
"4iRct4avG1": { "4iRct4avG1": {
"displayName": "Die Revisionen haben keinen gemeinsamen Ursprung", "displayName": "Die Revisionen haben keinen gemeinsamen Ursprung",
"description": "Die Historie der Revisionen hat keinen gemeinsamen Urspung und kann somit auch nicht gegen einen solchen verglichen werden." "description": "Die Historie der Revisionen hat keinen gemeinsamen Urspung und kann somit auch nicht gegen einen solchen verglichen werden."

View File

@@ -28,6 +28,12 @@
"description": "May see, clone and push to all repositories" "description": "May see, clone and push to all repositories"
} }
}, },
"read,rename": {
"*": {
"displayName": "Rename all repositories",
"description": "May see and rename all repositories"
}
},
"*": { "*": {
"displayName": "Own all repositories", "displayName": "Own all repositories",
"description": "May see, clone, push to, configure and delete all repositories" "description": "May see, clone, push to, configure and delete all repositories"
@@ -103,6 +109,10 @@
"displayName": "delete repository", "displayName": "delete repository",
"description": "May delete the repository" "description": "May delete the repository"
}, },
"rename": {
"displayName": "rename repository",
"description": "May rename the repository."
},
"pull": { "pull": {
"displayName": "pull/checkout repository", "displayName": "pull/checkout repository",
"description": "May pull/checkout the repository" "description": "May pull/checkout the repository"
@@ -187,6 +197,10 @@
"displayName": "No changes were made", "displayName": "No changes were made",
"description": "No changes were made to the files of the repository. Therefor no new commit could be created. Possibly changes cannot be applied due to an .ignore-File definition." "description": "No changes were made to the files of the repository. Therefor no new commit could be created. Possibly changes cannot be applied due to an .ignore-File definition."
}, },
"ERS2vYb7U1": {
"displayName": "Illegal change of namespace",
"description": "Namespaces can only be changed if namespace strategy is \"custom\"."
},
"4iRct4avG1": { "4iRct4avG1": {
"displayName": "The revisions have unrelated histories", "displayName": "The revisions have unrelated histories",
"description": "The revisions have unrelated histories. Therefor there is no common commit to compare with." "description": "The revisions have unrelated histories. Therefor there is no common commit to compare with."

View File

@@ -26,8 +26,8 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet;
import com.google.common.io.Resources; import com.google.common.io.Resources;
import com.google.inject.util.Providers;
import org.apache.shiro.subject.SimplePrincipalCollection; import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.Subject; import org.apache.shiro.subject.Subject;
import org.jboss.resteasy.mock.MockHttpRequest; import org.jboss.resteasy.mock.MockHttpRequest;
@@ -40,7 +40,10 @@ import org.mockito.Captor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import sonia.scm.PageResult; import sonia.scm.PageResult;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.CustomNamespaceStrategy;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.RepositoryInitializer; import sonia.scm.repository.RepositoryInitializer;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
@@ -56,6 +59,7 @@ import java.io.UnsupportedEncodingException;
import java.net.URI; import java.net.URI;
import java.net.URISyntaxException; import java.net.URISyntaxException;
import java.net.URL; import java.net.URL;
import java.util.Set;
import java.util.function.Predicate; import java.util.function.Predicate;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
@@ -72,6 +76,7 @@ import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyObject; import static org.mockito.ArgumentMatchers.anyObject;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.never; import static org.mockito.Mockito.never;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@@ -103,6 +108,10 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
private ScmPathInfo uriInfo; private ScmPathInfo uriInfo;
@Mock @Mock
private RepositoryInitializer repositoryInitializer; private RepositoryInitializer repositoryInitializer;
@Mock
private ScmConfiguration configuration;
@Mock
private Set<NamespaceStrategy> strategies;
@Captor @Captor
private ArgumentCaptor<Predicate<Repository>> filterCaptor; private ArgumentCaptor<Predicate<Repository>> filterCaptor;
@@ -127,6 +136,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
when(serviceFactory.create(any(Repository.class))).thenReturn(service); when(serviceFactory.create(any(Repository.class))).thenReturn(service);
when(scmPathInfoStore.get()).thenReturn(uriInfo); when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y")); when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM); SimplePrincipalCollection trillian = new SimplePrincipalCollection("trillian", REALM);
trillian.add(new User("trillian"), REALM); trillian.add(new User("trillian"), REALM);
shiro.setSubject( shiro.setSubject(
@@ -152,6 +162,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
@Test @Test
public void shouldFindExistingRepository() throws URISyntaxException, UnsupportedEncodingException { public void shouldFindExistingRepository() throws URISyntaxException, UnsupportedEncodingException {
mockRepository("space", "repo"); mockRepository("space", "repo");
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -166,6 +177,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException { public void shouldGetAll() throws URISyntaxException, UnsupportedEncodingException {
PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo")); PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
when(repositoryManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); when(repositoryManager.getPage(any(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2);
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -180,6 +192,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldCreateFilterForSearch() throws URISyntaxException { public void shouldCreateFilterForSearch() throws URISyntaxException {
PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo")); PageResult<Repository> singletonPageResult = createSingletonPageResult(mockRepository("space", "repo"));
when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult); when(repositoryManager.getPage(filterCaptor.capture(), any(), eq(0), eq(10))).thenReturn(singletonPageResult);
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?q=Rep");
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -362,6 +375,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
public void shouldCreateArrayOfProtocolUrls() throws Exception { public void shouldCreateArrayOfProtocolUrls() throws Exception {
mockRepository("space", "repo"); mockRepository("space", "repo");
when(service.getSupportedProtocols()).thenReturn(of(new MockScmProtocol("http", "http://"), new MockScmProtocol("ssh", "ssh://"))); when(service.getSupportedProtocols()).thenReturn(of(new MockScmProtocol("http", "http://"), new MockScmProtocol("ssh", "ssh://")));
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo"); MockHttpRequest request = MockHttpRequest.get("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo");
MockHttpResponse response = new MockHttpResponse(); MockHttpResponse response = new MockHttpResponse();
@@ -372,6 +386,28 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
assertTrue(response.getContentAsString().contains("\"protocol\":[{\"href\":\"http://\",\"name\":\"http\"},{\"href\":\"ssh://\",\"name\":\"ssh\"}]")); assertTrue(response.getContentAsString().contains("\"protocol\":[{\"href\":\"http://\",\"name\":\"http\"},{\"href\":\"ssh://\",\"name\":\"ssh\"}]"));
} }
@Test
public void shouldRenameRepository() throws Exception {
String namespace = "space";
String name = "repo";
Repository repository1 = mockRepository(namespace, name);
when(manager.get(new NamespaceAndName(namespace, name))).thenReturn(repository1);
URL url = Resources.getResource("sonia/scm/api/v2/rename-repo.json");
byte[] repository = Resources.toByteArray(url);
MockHttpRequest request = MockHttpRequest
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "space/repo/rename")
.contentType(VndMediaType.REPOSITORY)
.content(repository);
MockHttpResponse response = new MockHttpResponse();
dispatcher.invoke(request, response);
assertEquals(SC_NO_CONTENT, response.getStatus());
verify(repositoryManager).rename(repository1, "space", "x");
}
private PageResult<Repository> createSingletonPageResult(Repository repository) { private PageResult<Repository> createSingletonPageResult(Repository repository) {
return new PageResult<>(singletonList(repository), 0); return new PageResult<>(singletonList(repository), 0);
} }

View File

@@ -27,7 +27,6 @@ package sonia.scm.api.v2.resources;
import sonia.scm.repository.RepositoryManager; import sonia.scm.repository.RepositoryManager;
import static com.google.inject.util.Providers.of; import static com.google.inject.util.Providers.of;
import static org.mockito.Mockito.mock;
abstract class RepositoryTestBase { abstract class RepositoryTestBase {
@@ -47,7 +46,6 @@ abstract class RepositoryTestBase {
RepositoryCollectionResource repositoryCollectionResource; RepositoryCollectionResource repositoryCollectionResource;
AnnotateResource annotateResource; AnnotateResource annotateResource;
RepositoryRootResource getRepositoryRootResource() { RepositoryRootResource getRepositoryRootResource() {
RepositoryBasedResourceProvider repositoryBasedResourceProvider = new RepositoryBasedResourceProvider( RepositoryBasedResourceProvider repositoryBasedResourceProvider = new RepositoryBasedResourceProvider(
of(tagRootResource), of(tagRootResource),
@@ -66,8 +64,7 @@ abstract class RepositoryTestBase {
repositoryToDtoMapper, repositoryToDtoMapper,
dtoToRepositoryMapper, dtoToRepositoryMapper,
manager, manager,
repositoryBasedResourceProvider repositoryBasedResourceProvider)),
)),
of(repositoryCollectionResource)); of(repositoryCollectionResource));
} }
} }

View File

@@ -26,6 +26,7 @@ package sonia.scm.api.v2.resources;
import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet;
import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadContext;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
@@ -33,7 +34,10 @@ import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import sonia.scm.config.ScmConfiguration;
import sonia.scm.repository.CustomNamespaceStrategy;
import sonia.scm.repository.HealthCheckFailure; import sonia.scm.repository.HealthCheckFailure;
import sonia.scm.repository.NamespaceStrategy;
import sonia.scm.repository.Repository; import sonia.scm.repository.Repository;
import sonia.scm.repository.api.Command; import sonia.scm.repository.api.Command;
import sonia.scm.repository.api.RepositoryService; import sonia.scm.repository.api.RepositoryService;
@@ -41,13 +45,16 @@ import sonia.scm.repository.api.RepositoryServiceFactory;
import sonia.scm.repository.api.ScmProtocol; import sonia.scm.repository.api.ScmProtocol;
import java.net.URI; import java.net.URI;
import java.util.Set;
import static java.util.Collections.singleton;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static java.util.stream.Stream.of; import static java.util.stream.Stream.of;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
import static org.mockito.MockitoAnnotations.initMocks; import static org.mockito.MockitoAnnotations.initMocks;
@@ -72,6 +79,10 @@ public class RepositoryToRepositoryDtoMapperTest {
private ScmPathInfoStore scmPathInfoStore; private ScmPathInfoStore scmPathInfoStore;
@Mock @Mock
private ScmPathInfo uriInfo; private ScmPathInfo uriInfo;
@Mock
private ScmConfiguration configuration;
@Mock
private Set<NamespaceStrategy> strategies;
@InjectMocks @InjectMocks
private RepositoryToRepositoryDtoMapperImpl mapper; private RepositoryToRepositoryDtoMapperImpl mapper;
@@ -83,7 +94,9 @@ public class RepositoryToRepositoryDtoMapperTest {
when(repositoryService.isSupported(any(Command.class))).thenReturn(true); when(repositoryService.isSupported(any(Command.class))).thenReturn(true);
when(repositoryService.getSupportedProtocols()).thenReturn(of()); when(repositoryService.getSupportedProtocols()).thenReturn(of());
when(scmPathInfoStore.get()).thenReturn(uriInfo); when(scmPathInfoStore.get()).thenReturn(uriInfo);
when(configuration.getNamespaceStrategy()).thenReturn("CustomNamespaceStrategy");
when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y")); when(uriInfo.getApiRestUri()).thenReturn(URI.create("/x/y"));
doReturn(ImmutableSet.of(new CustomNamespaceStrategy()).iterator()).when(strategies).iterator();
} }
@After @After
@@ -129,6 +142,23 @@ public class RepositoryToRepositoryDtoMapperTest {
dto.getLinks().getLinkBy("update").get().getHref()); dto.getLinks().getLinkBy("update").get().getHref());
} }
@Test
public void shouldCreateRenameLink() {
when(configuration.getNamespaceStrategy()).thenReturn("test");
RepositoryDto dto = mapper.map(createTestRepository());
assertEquals(
"http://example.com/base/v2/repositories/testspace/test/rename",
dto.getLinks().getLinkBy("rename").get().getHref());
}
@Test
public void shouldCreateRenameWithNamespaceLink() {
RepositoryDto dto = mapper.map(createTestRepository());
assertEquals(
"http://example.com/base/v2/repositories/testspace/test/rename",
dto.getLinks().getLinkBy("renameWithNamespace").get().getHref());
}
@Test @Test
public void shouldMapHealthCheck() { public void shouldMapHealthCheck() {
RepositoryDto dto = mapper.map(createTestRepository()); RepositoryDto dto = mapper.map(createTestRepository());

View File

@@ -30,7 +30,6 @@ import com.github.legman.Subscribe;
import com.github.sdorra.shiro.ShiroRule; import com.github.sdorra.shiro.ShiroRule;
import com.github.sdorra.shiro.SubjectAware; import com.github.sdorra.shiro.SubjectAware;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import com.google.inject.util.Providers; import com.google.inject.util.Providers;
import org.apache.shiro.authz.UnauthorizedException; import org.apache.shiro.authz.UnauthorizedException;
import org.apache.shiro.util.ThreadContext; import org.apache.shiro.util.ThreadContext;
@@ -44,32 +43,45 @@ import sonia.scm.AlreadyExistsException;
import sonia.scm.HandlerEventType; import sonia.scm.HandlerEventType;
import sonia.scm.Manager; import sonia.scm.Manager;
import sonia.scm.ManagerTestBase; import sonia.scm.ManagerTestBase;
import sonia.scm.NoChangesMadeException;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.SCMContext; import sonia.scm.SCMContext;
import sonia.scm.config.ScmConfiguration; import sonia.scm.config.ScmConfiguration;
import sonia.scm.event.ScmEventBus; import sonia.scm.event.ScmEventBus;
import sonia.scm.io.DefaultFileSystem;
import sonia.scm.repository.api.HookContext; import sonia.scm.repository.api.HookContext;
import sonia.scm.repository.api.HookContextFactory; import sonia.scm.repository.api.HookContextFactory;
import sonia.scm.repository.api.HookFeature; import sonia.scm.repository.api.HookFeature;
import sonia.scm.repository.spi.HookContextProvider; import sonia.scm.repository.spi.HookContextProvider;
import sonia.scm.repository.xml.PathBasedRepositoryLocationResolver;
import sonia.scm.repository.xml.XmlRepositoryDAO;
import sonia.scm.security.DefaultKeyGenerator; import sonia.scm.security.DefaultKeyGenerator;
import sonia.scm.security.KeyGenerator; import sonia.scm.security.KeyGenerator;
import sonia.scm.store.ConfigurationStoreFactory; import sonia.scm.store.ConfigurationStoreFactory;
import sonia.scm.store.JAXBConfigurationStoreFactory;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import static org.hamcrest.Matchers.*; import static java.util.Collections.emptySet;
import static org.junit.Assert.*; import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.mockito.Mockito.*; import static org.hamcrest.Matchers.hasProperty;
import static org.hamcrest.Matchers.is;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.anyString;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.when;
//~--- JDK imports ------------------------------------------------------------ //~--- JDK imports ------------------------------------------------------------
@@ -83,447 +95,492 @@ import static org.mockito.Mockito.*;
password = "secret", password = "secret",
configuration = "classpath:sonia/scm/repository/shiro.ini" configuration = "classpath:sonia/scm/repository/shiro.ini"
) )
public class DefaultRepositoryManagerTest {//extends ManagerTestBase<Repository> { public class DefaultRepositoryManagerTest extends ManagerTestBase<Repository> {
{
ThreadContext.unbindSubject();
}
@Rule
public ShiroRule shiro = new ShiroRule();
@Rule
public ExpectedException thrown = ExpectedException.none();
private NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
private ScmConfiguration configuration;
private String mockedNamespace = "default_namespace";
@Before
public void initContext() {
((TempSCMContextProvider) SCMContext.getContext()).setBaseDirectory(temp);
}
@Test
public void testCreate() {
Repository heartOfGold = createTestRepository();
Repository dbRepo = manager.get(heartOfGold.getId());
assertNotNull(dbRepo);
assertRepositoriesEquals(dbRepo, heartOfGold);
}
@SubjectAware(
username = "unpriv"
)
@Test(expected = UnauthorizedException.class)
public void testCreateWithoutPrivileges() {
createTestRepository();
}
@Test
public void testCreateExisting() {
createTestRepository();
thrown.expect(AlreadyExistsException.class);
createTestRepository();
}
@Test
public void testDelete() {
delete(manager, createTestRepository());
}
@SubjectAware(
username = "unpriv"
)
@Test(expected = UnauthorizedException.class)
public void testDeleteWithoutPrivileges() {
delete(manager, createTestRepository());
}
@Test(expected = NotFoundException.class)
public void testDeleteNotFound() {
manager.delete(createRepositoryWithId());
}
@Test
public void testGet() {
Repository heartOfGold = createTestRepository();
String id = heartOfGold.getId();
String description = heartOfGold.getDescription();
assertNotNull(description);
// test for reference
heartOfGold.setDescription("prototype ship");
heartOfGold = manager.get(id);
assertNotNull(heartOfGold);
assertEquals(description, heartOfGold.getDescription());
}
@Test
@SubjectAware(
username = "crato"
)
public void testGetWithoutRequiredPrivileges() {
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
manager.create(heartOfGold);
thrown.expect(UnauthorizedException.class);
manager.get(heartOfGold.getId());
}
@Test
public void testGetAll() {
Repository heartOfGold = createTestRepository();
Repository happyVerticalPeopleTransporter = createSecondTestRepository();
boolean foundHeart = false;
boolean foundTransporter = false;
Collection<Repository> repositories = manager.getAll();
assertNotNull(repositories);
assertFalse(repositories.isEmpty());
assertTrue(repositories.size() >= 2);
Repository heartReference = null;
for (Repository repository : repositories) {
if (repository.getId().equals(heartOfGold.getId())) {
assertRepositoriesEquals(heartOfGold, repository);
foundHeart = true;
heartReference = repository;
} else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
foundTransporter = true;
}
}
assertTrue(foundHeart);
assertTrue(foundTransporter);
// test for reference
assertNotSame(heartOfGold, heartReference);
heartReference.setDescription("prototype ship");
assertNotEquals(heartOfGold.getDescription(), heartReference.getDescription());
}
@Test
@SuppressWarnings("unchecked")
@SubjectAware(username = "dent")
public void testGetAllWithPermissionsForTwoOrThreeRepos() {
// mock key generator
KeyGenerator keyGenerator = mock(KeyGenerator.class);
Stack<String> keys = new Stack<>();
keys.push("rateotu");
keys.push("p42");
keys.push("hof");
when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
return keys.pop();
});
// create repository manager
RepositoryManager repositoryManager = createRepositoryManager(keyGenerator);
// create first test repository
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
repositoryManager.create(heartOfGold);
assertEquals("hof", heartOfGold.getId());
// create second test repository
Repository puzzle42 = RepositoryTestData.create42Puzzle();
repositoryManager.create(puzzle42);
assertEquals("p42", puzzle42.getId());
// create third test repository
Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
repositoryManager.create(restaurant);
assertEquals("rateotu", restaurant.getId());
// assert returned repositories
Collection<Repository> repositories = repositoryManager.getAll();
assertEquals(2, repositories.size());
assertThat(repositories, containsInAnyOrder(
hasProperty("id", is("p42")),
hasProperty("id", is("hof"))
)
);
}
@Test
public void testEvents() {
RepositoryManager repoManager = createManager();
repoManager.init(contextProvider);
TestListener listener = new TestListener();
ScmEventBus.getInstance().register(listener);
Repository repository = RepositoryTestData.create42Puzzle();
repoManager.create(repository);
assertRepositoriesEquals(repository, listener.preRepository);
assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
assertRepositoriesEquals(repository, listener.postRepository);
assertSame(HandlerEventType.CREATE, listener.postEvent);
repository.setDescription("changed description");
repoManager.modify(repository);
assertRepositoriesEquals(repository, listener.preRepository);
assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
assertRepositoriesEquals(repository, listener.postRepository);
assertSame(HandlerEventType.MODIFY, listener.postEvent);
repoManager.delete(repository);
assertRepositoriesEquals(repository, listener.preRepository);
assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
assertRepositoriesEquals(repository, listener.postRepository);
assertSame(HandlerEventType.DELETE, listener.postEvent);
}
@Test
public void testModify() {
Repository heartOfGold = createTestRepository();
heartOfGold.setDescription("prototype ship");
manager.modify(heartOfGold);
Repository hearReference = manager.get(heartOfGold.getId());
assertNotNull(hearReference);
assertEquals("prototype ship", hearReference.getDescription());
}
@Test
@SubjectAware(username = "crato")
public void testModifyWithoutRequiredPermissions() {
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
manager.create(heartOfGold);
heartOfGold.setDescription("prototype ship");
thrown.expect(UnauthorizedException.class);
manager.modify(heartOfGold);
}
@Test(expected = NotFoundException.class)
public void testModifyNotFound() {
manager.modify(createRepositoryWithId());
}
@Test
public void testRefresh() {
Repository heartOfGold = createTestRepository();
String description = heartOfGold.getDescription();
heartOfGold.setDescription("prototype ship");
manager.refresh(heartOfGold);
assertEquals(description, heartOfGold.getDescription());
}
@Test
@SubjectAware(username = "crato")
public void testRefreshWithoutRequiredPermissions() {
Repository heartOfGold = RepositoryTestData.createHeartOfGold();
manager.create(heartOfGold);
heartOfGold.setDescription("prototype ship");
thrown.expect(UnauthorizedException.class);
manager.refresh(heartOfGold);
}
@Test(expected = NotFoundException.class)
public void testRefreshNotFound() {
manager.refresh(createRepositoryWithId());
}
@Test
public void testRepositoryHook() {
CountingReceiveHook hook = new CountingReceiveHook();
RepositoryManager repoManager = createManager();
ScmEventBus.getInstance().register(hook);
assertEquals(0, hook.eventsReceived);
Repository repository = createTestRepository();
HookContext ctx = createHookContext(repository);
repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
RepositoryHookType.POST_RECEIVE));
assertEquals(1, hook.eventsReceived);
repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
RepositoryHookType.POST_RECEIVE));
assertEquals(2, hook.eventsReceived);
}
@Test
public void testNamespaceSet() {
RepositoryManager repoManager = createManager();
Repository repository = spy(createTestRepository());
repository.setName("Testrepo");
repoManager.create(repository);
assertEquals("default_namespace", repository.getNamespace());
}
@Test
public void shouldSetNamespace() {
Repository repository = new Repository(null, "hg", null, "scm");
manager.create(repository);
assertNotNull(repository.getId());
assertNotNull(repository.getNamespace());
}
@Test
public void shouldThrowChangeNamespaceNotAllowedException() {
Repository repository = new Repository("1", "hg", "space", "x");
RepositoryManager repoManager = createManager();
when(namespaceStrategy.canBeChanged()).thenReturn(false);
thrown.expect(ChangeNamespaceNotAllowedException.class);
repoManager.rename(repository, "hitchhiker", "heart-of-gold");
}
@Test
public void shouldThrowNoChangesMadeException() {
Repository repository = new Repository("1", "hg", "space", "x");
RepositoryManager repoManager = createManager();
thrown.expect(NoChangesMadeException.class);
repoManager.rename(repository, "space", "x");
}
@Test
public void shouldOnlyChangeRepositoryName() {
Repository repository = createTestRepository();
RepositoryManager repoManager = (RepositoryManager) manager;
Repository changedRepo = repoManager.rename(repository, "default_namespace", "puzzle42");
assertNotEquals(changedRepo.getName(), repository.getName());
}
@Test
public void shouldRenameRepositoryNamespaceAndName() {
Repository repository = createTestRepository();
RepositoryManager repoManager = (RepositoryManager) manager;
when(namespaceStrategy.canBeChanged()).thenReturn(true);
when(namespaceStrategy.createNamespace(any(Repository.class))).thenReturn("hitchhiker");
Repository changedRepo = repoManager.rename(repository, "hitchhiker", "puzzle42");
assertEquals("puzzle42", changedRepo.getName());
assertEquals("hitchhiker", changedRepo.getNamespace());
}
//~--- methods --------------------------------------------------------------
@Override
protected DefaultRepositoryManager createManager() {
return createRepositoryManager(new DefaultKeyGenerator());
}
private DefaultRepositoryManager createRepositoryManager(KeyGenerator keyGenerator) {
Set<RepositoryHandler> handlerSet = new HashSet<>();
RepositoryDAO repositoryDAO = createRepositoryDaoMock();
mock(ConfigurationStoreFactory.class);
handlerSet.add(createRepositoryHandler("dummy", "Dummy"));
handlerSet.add(createRepositoryHandler("git", "Git"));
handlerSet.add(createRepositoryHandler("hg", "Mercurial"));
handlerSet.add(createRepositoryHandler("svn", "SVN"));
this.configuration = new ScmConfiguration();
when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
return new DefaultRepositoryManager(configuration, contextProvider,
keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
}
private RepositoryDAO createRepositoryDaoMock() {
Map<String, Repository> repositoriesById = new HashMap<>();
Map<NamespaceAndName, Repository> repositoriesByNamespaceAndName = new HashMap<>();
RepositoryDAO mock = mock(RepositoryDAO.class);
doAnswer(invocation -> {
Repository repo = invocation.getArgument(0, Repository.class);
if (repositoriesById.containsKey(repo.getId()) || repositoriesByNamespaceAndName.containsKey(repo.getNamespaceAndName())) {
throw new AlreadyExistsException(repo);
}
Repository clone = repo.clone();
repositoriesById.put(repo.getId(), clone);
repositoriesByNamespaceAndName.put(repo.getNamespaceAndName(), clone);
return null;
}).when(mock).add(any());
doAnswer(invocation -> {
Repository repo = invocation.getArgument(0, Repository.class);
Repository clone = repo.clone();
repositoriesById.put(repo.getId(), clone);
repositoriesByNamespaceAndName.put(repo.getNamespaceAndName(), clone);
return null;
}).when(mock).modify(any());
when(mock.get(anyString())).thenAnswer(invocation -> repositoriesById.get(invocation.getArgument(0, String.class)));
when(mock.get(any(NamespaceAndName.class))).thenAnswer(invocation -> repositoriesByNamespaceAndName.get(invocation.getArgument(0, NamespaceAndName.class)));
when(mock.getAll()).thenAnswer(invocation -> repositoriesById.values());
when(mock.contains(anyString())).thenAnswer(invocation -> repositoriesById.containsKey(invocation.getArgument(0, String.class)));
when(mock.contains(any(Repository.class))).thenAnswer(invocation -> repositoriesById.containsKey(invocation.getArgument(0, Repository.class).getId()));
doAnswer(invocation -> {
Repository repo = invocation.getArgument(0, Repository.class);
repositoriesById.remove(repo.getId());
repositoriesByNamespaceAndName.remove(repo.getNamespaceAndName());
return null;
}).when(mock).delete(any(Repository.class));
return mock;
}
private RepositoryHandler createRepositoryHandler(String name, String diplayName) {
RepositoryHandler handler = mock(RepositoryHandler.class);
when(handler.getType()).thenReturn(new RepositoryType(name, diplayName, emptySet()));
when(handler.isConfigured()).thenReturn(true);
return handler;
}
private HookContext createHookContext(Repository repository) {
PreProcessorUtil ppu = mock(PreProcessorUtil.class);
HookContextProvider provider = mock(HookContextProvider.class);
Set<HookFeature> features = ImmutableSet.of();
when(provider.getSupportedFeatures()).thenReturn(features);
return new HookContextFactory(ppu).createContext(provider, repository);
}
private void assertRepositoriesEquals(Repository repo, Repository other) {
assertEquals(repo.getId(), other.getId());
assertEquals(repo.getName(), other.getName());
assertEquals(repo.getDescription(), other.getDescription());
assertEquals(repo.getContact(), other.getContact());
assertEquals(repo.getCreationDate(), other.getCreationDate());
assertEquals(repo.getLastModified(), other.getLastModified());
}
private Repository createRepository(Repository repository) {
manager.create(repository);
assertNotNull(repository.getId());
assertNotNull(manager.get(repository.getId()));
assertTrue(repository.getCreationDate() > 0);
return repository;
}
private Repository createRepositoryWithId() {
Repository repository = RepositoryTestData.createHeartOfGold();
repository.setId("abc");
return repository;
}
private Repository createSecondTestRepository() {
return createRepository(
RepositoryTestData.createHappyVerticalPeopleTransporter());
}
private Repository createTestRepository() {
return createRepository(RepositoryTestData.createHeartOfGold());
}
private void delete(Manager<Repository> manager, Repository repository) {
String id = repository.getId();
manager.delete(repository);
assertNull(manager.get(id));
}
private static class CountingReceiveHook {
private int eventsReceived = 0;
@Subscribe(async = false)
public void onEvent(PostReceiveRepositoryHookEvent event) {
eventsReceived++;
}
@Subscribe(async = false)
public void onEvent(PreReceiveRepositoryHookEvent event) {
eventsReceived++;
}
}
private class TestListener {
private HandlerEventType postEvent;
private Repository postRepository;
private HandlerEventType preEvent;
private Repository preRepository;
@Subscribe(async = false)
public void onEvent(RepositoryEvent event) {
if (event.getEventType().isPost()) {
this.postRepository = event.getItem();
this.postEvent = event.getEventType();
} else if (event.getEventType().isPre()) {
this.preRepository = event.getItem();
this.preEvent = event.getEventType();
}
}
}
// {
// ThreadContext.unbindSubject();
// }
//
// @Rule
// public ShiroRule shiro = new ShiroRule();
//
// @Rule
// public ExpectedException thrown = ExpectedException.none();
//
// private ScmConfiguration configuration;
//
// private String mockedNamespace = "default_namespace";
//
// @Before
// public void initContext() {
// ((TempSCMContextProvider)SCMContext.getContext()).setBaseDirectory(temp);
// }
//
// @Test
// public void testCreate() {
// Repository heartOfGold = createTestRepository();
// Repository dbRepo = manager.get(heartOfGold.getId());
//
// assertNotNull(dbRepo);
// assertRepositoriesEquals(dbRepo, heartOfGold);
// }
//
// @SubjectAware(
// username = "unpriv"
// )
// @Test(expected = UnauthorizedException.class)
// public void testCreateWithoutPrivileges() {
// createTestRepository();
// }
//
// @Test
// public void testCreateExisting() {
// Repository testRepository = createTestRepository();
// String expectedNamespaceAndName = testRepository.getNamespaceAndName().logString();
// thrown.expect(AlreadyExistsException.class);
// thrown.expectMessage(expectedNamespaceAndName);
// createTestRepository();
// }
//
// @Test
// public void testDelete() {
// delete(manager, createTestRepository());
// }
//
// @SubjectAware(
// username = "unpriv"
// )
// @Test(expected = UnauthorizedException.class)
// public void testDeleteWithoutPrivileges() {
// delete(manager, createTestRepository());
// }
//
// @Test(expected = RepositoryIsNotArchivedException.class)
// public void testDeleteNonArchived() {
// configuration.setEnableRepositoryArchive(true);
// delete(manager, createTestRepository());
// }
//
// @Test(expected = NotFoundException.class)
// public void testDeleteNotFound(){
// manager.delete(createRepositoryWithId());
// }
//
// @Test
// public void testDeleteWithEnabledArchive() {
// Repository repository = createTestRepository();
//
// repository.setArchived(true);
// RepositoryManager drm = createRepositoryManager(true);
// drm.init(contextProvider);
// delete(drm, repository);
// }
//
// @Test
// public void testGet() {
// Repository heartOfGold = createTestRepository();
// String id = heartOfGold.getId();
// String description = heartOfGold.getDescription();
//
// assertNotNull(description);
//
// // test for reference
// heartOfGold.setDescription("prototype ship");
// heartOfGold = manager.get(id);
// assertNotNull(heartOfGold);
// assertEquals(description, heartOfGold.getDescription());
// }
//
// @Test
// @SubjectAware(
// username = "crato"
// )
// public void testGetWithoutRequiredPrivileges() {
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
// manager.create(heartOfGold);
//
// thrown.expect(UnauthorizedException.class);
// manager.get(heartOfGold.getId());
// }
//
// @Test
// public void testGetAll() {
// Repository heartOfGold = createTestRepository();
// Repository happyVerticalPeopleTransporter = createSecondTestRepository();
// boolean foundHeart = false;
// boolean foundTransporter = false;
// Collection<Repository> repositories = manager.getAll();
//
// assertNotNull(repositories);
// assertFalse(repositories.isEmpty());
// assertTrue(repositories.size() >= 2);
//
// Repository heartReference = null;
//
// for (Repository repository : repositories) {
// if (repository.getId().equals(heartOfGold.getId())) {
// assertRepositoriesEquals(heartOfGold, repository);
// foundHeart = true;
// heartReference = repository;
// }
// else if (repository.getId().equals(happyVerticalPeopleTransporter.getId())) {
// assertRepositoriesEquals(happyVerticalPeopleTransporter, repository);
// foundTransporter = true;
// }
// }
//
// assertTrue(foundHeart);
// assertTrue(foundTransporter);
//
// // test for reference
// assertNotSame(heartOfGold, heartReference);
// heartReference.setDescription("prototype ship");
// assertFalse(
// heartOfGold.getDescription().equals(heartReference.getDescription()));
// }
//
// @Test
// @SuppressWarnings("unchecked")
// @SubjectAware(username = "dent")
// public void testGetAllWithPermissionsForTwoOrThreeRepos() {
// // mock key generator
// KeyGenerator keyGenerator = mock(KeyGenerator.class);
// Stack<String> keys = new Stack<>();
// keys.push("rateotu");
// keys.push("p42");
// keys.push("hof");
//
// when(keyGenerator.createKey()).then((InvocationOnMock invocation) -> {
// return keys.pop();
// });
//
// // create repository manager
// RepositoryManager repositoryManager = createRepositoryManager(false, keyGenerator);
//
// // create first test repository
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
// repositoryManager.create(heartOfGold);
// assertEquals("hof", heartOfGold.getId());
//
// // create second test repository
// Repository puzzle42 = RepositoryTestData.create42Puzzle();
// repositoryManager.create(puzzle42);
// assertEquals("p42", puzzle42.getId());
//
// // create third test repository
// Repository restaurant = RepositoryTestData.createRestaurantAtTheEndOfTheUniverse();
// repositoryManager.create(restaurant);
// assertEquals("rateotu", restaurant.getId());
//
// // assert returned repositories
// Collection<Repository> repositories = repositoryManager.getAll();
// assertEquals(2, repositories.size());
// assertThat(repositories, containsInAnyOrder(
// hasProperty("id", is("p42")),
// hasProperty("id", is("hof"))
// )
// );
// }
//
// @Test
// public void testEvents() {
// RepositoryManager repoManager = createRepositoryManager(false);
// repoManager.init(contextProvider);
// TestListener listener = new TestListener();
//
// ScmEventBus.getInstance().register(listener);
//
// Repository repository = RepositoryTestData.create42Puzzle();
//
// repoManager.create(repository);
// assertRepositoriesEquals(repository, listener.preRepository);
// assertSame(HandlerEventType.BEFORE_CREATE, listener.preEvent);
// assertRepositoriesEquals(repository, listener.postRepository);
// assertSame(HandlerEventType.CREATE, listener.postEvent);
//
// repository.setDescription("changed description");
// repoManager.modify(repository);
// assertRepositoriesEquals(repository, listener.preRepository);
// assertSame(HandlerEventType.BEFORE_MODIFY, listener.preEvent);
// assertRepositoriesEquals(repository, listener.postRepository);
// assertSame(HandlerEventType.MODIFY, listener.postEvent);
//
// repoManager.delete(repository);
//
// assertRepositoriesEquals(repository, listener.preRepository);
// assertSame(HandlerEventType.BEFORE_DELETE, listener.preEvent);
// assertRepositoriesEquals(repository, listener.postRepository);
// assertSame(HandlerEventType.DELETE, listener.postEvent);
// }
//
// @Test
// public void testModify() {
// Repository heartOfGold = createTestRepository();
//
// heartOfGold.setDescription("prototype ship");
// manager.modify(heartOfGold);
//
// Repository hearReference = manager.get(heartOfGold.getId());
//
// assertNotNull(hearReference);
// assertEquals(hearReference.getDescription(), "prototype ship");
// }
//
// @Test
// @SubjectAware(username = "crato")
// public void testModifyWithoutRequiredPermissions() {
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
// manager.create(heartOfGold);
// heartOfGold.setDescription("prototype ship");
//
// thrown.expect(UnauthorizedException.class);
// manager.modify(heartOfGold);
// }
//
// @Test(expected = NotFoundException.class)
// public void testModifyNotFound(){
// manager.modify(createRepositoryWithId());
// }
//
// @Test
// public void testRefresh() {
// Repository heartOfGold = createTestRepository();
// String description = heartOfGold.getDescription();
//
// heartOfGold.setDescription("prototype ship");
// manager.refresh(heartOfGold);
// assertEquals(description, heartOfGold.getDescription());
// }
//
// @Test
// @SubjectAware(username = "crato")
// public void testRefreshWithoutRequiredPermissions() {
// Repository heartOfGold = RepositoryTestData.createHeartOfGold();
// manager.create(heartOfGold);
// heartOfGold.setDescription("prototype ship");
//
// thrown.expect(UnauthorizedException.class);
// manager.refresh(heartOfGold);
// }
//
// @Test(expected = NotFoundException.class)
// public void testRefreshNotFound(){
// manager.refresh(createRepositoryWithId());
// }
//
// @Test
// public void testRepositoryHook() {
// CountingReceiveHook hook = new CountingReceiveHook();
// RepositoryManager repoManager = createRepositoryManager(false);
//
// ScmEventBus.getInstance().register(hook);
//
// assertEquals(0, hook.eventsReceived);
//
// Repository repository = createTestRepository();
// HookContext ctx = createHookContext(repository);
//
// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
// RepositoryHookType.POST_RECEIVE));
// assertEquals(1, hook.eventsReceived);
// repoManager.fireHookEvent(new RepositoryHookEvent(ctx, repository,
// RepositoryHookType.POST_RECEIVE));
// assertEquals(2, hook.eventsReceived);
// }
//
// @Test
// public void testNamespaceSet() {
// RepositoryManager repoManager = createRepositoryManager(false);
// Repository repository = spy(createTestRepository());
// repository.setName("Testrepo");
// repoManager.create(repository);
// assertEquals("default_namespace", repository.getNamespace());
// }
//
// @Test
// public void shouldSetNamespace() {
// Repository repository = new Repository(null, "hg", null, "scm");
// manager.create(repository);
// assertNotNull(repository.getId());
// assertNotNull(repository.getNamespace());
// }
//
// //~--- methods --------------------------------------------------------------
//
// @Override
// protected DefaultRepositoryManager createManager() {
// return createRepositoryManager(false);
// }
//
// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled) {
// return createRepositoryManager(archiveEnabled, new DefaultKeyGenerator());
// }
//
// private DefaultRepositoryManager createRepositoryManager(boolean archiveEnabled, KeyGenerator keyGenerator) {
// DefaultFileSystem fileSystem = new DefaultFileSystem();
// Set<RepositoryHandler> handlerSet = new HashSet<>();
// PathBasedRepositoryLocationResolver repositoryLocationResolver = mock(PathBasedRepositoryLocationResolver.class, RETURNS_DEEP_STUBS);
// when(repositoryLocationResolver.forClass(Path.class).getLocation(anyString())).thenReturn(Paths.get("."));
// XmlRepositoryDAO repositoryDAO = new XmlRepositoryDAO(contextProvider, repositoryLocationResolver, fileSystem);
// ConfigurationStoreFactory factory = new JAXBConfigurationStoreFactory(contextProvider, repositoryLocationResolver);
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver));
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
// @Override
// public RepositoryType getType() {
// return new RepositoryType("hg", "Mercurial", Sets.newHashSet());
// }
// });
// handlerSet.add(new DummyRepositoryHandler(factory, repositoryLocationResolver) {
// @Override
// public RepositoryType getType() {
// return new RepositoryType("git", "Git", Sets.newHashSet());
// }
// });
//
//
// this.configuration = new ScmConfiguration();
//
// configuration.setEnableRepositoryArchive(archiveEnabled);
//
// NamespaceStrategy namespaceStrategy = mock(NamespaceStrategy.class);
// when(namespaceStrategy.createNamespace(Mockito.any(Repository.class))).thenAnswer(invocation -> mockedNamespace);
//
// return new DefaultRepositoryManager(configuration, contextProvider,
// keyGenerator, repositoryDAO, handlerSet, Providers.of(namespaceStrategy));
// }
//
// private HookContext createHookContext(Repository repository) {
// PreProcessorUtil ppu = mock(PreProcessorUtil.class);
// HookContextProvider provider = mock(HookContextProvider.class);
// Set<HookFeature> features = ImmutableSet.of();
//
// when(provider.getSupportedFeatures()).thenReturn(features);
//
// return new HookContextFactory(ppu).createContext(provider, repository);
// }
//
// private void assertRepositoriesEquals(Repository repo, Repository other) {
// assertEquals(repo.getId(), other.getId());
// assertEquals(repo.getName(), other.getName());
// assertEquals(repo.getDescription(), other.getDescription());
// assertEquals(repo.getContact(), other.getContact());
// assertEquals(repo.getCreationDate(), other.getCreationDate());
// assertEquals(repo.getLastModified(), other.getLastModified());
// }
//
// private Repository createRepository(Repository repository) {
// manager.create(repository);
// assertNotNull(repository.getId());
// assertNotNull(manager.get(repository.getId()));
// assertTrue(repository.getCreationDate() > 0);
//
// return repository;
// }
//
// private Repository createRepositoryWithId() {
// Repository repository = RepositoryTestData.createHeartOfGold();
// repository.setId("abc");
// return repository;
// }
//
// private Repository createSecondTestRepository() {
// return createRepository(
// RepositoryTestData.createHappyVerticalPeopleTransporter());
// }
//
// private Repository createTestRepository() {
// return createRepository(RepositoryTestData.createHeartOfGold());
// }
//
// private void delete(Manager<Repository> manager, Repository repository){
//
// String id = repository.getId();
//
// manager.delete(repository);
// assertNull(manager.get(id));
// }
//
// private static class CountingReceiveHook {
//
// private int eventsReceived = 0;
//
// @Subscribe(async = false)
// public void onEvent(PostReceiveRepositoryHookEvent event) {
// eventsReceived++;
// }
//
// @Subscribe(async = false)
// public void onEvent(PreReceiveRepositoryHookEvent event) {
// eventsReceived++;
// }
// }
//
// private class TestListener {
//
// private HandlerEventType postEvent;
//
// private Repository postRepository;
//
// private HandlerEventType preEvent;
//
// private Repository preRepository;
//
// @Subscribe(async = false)
// public void onEvent(RepositoryEvent event) {
// if (event.getEventType().isPost()) {
// this.postRepository = event.getItem();
// this.postEvent = event.getEventType();
// }
// else if (event.getEventType().isPre()) {
// this.preRepository = event.getItem();
// this.preEvent = event.getEventType();
// }
// }
// }
//
} }

View File

@@ -0,0 +1,4 @@
{
"name": "x",
"namespace": "space"
}