mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-07 05:55:44 +01:00
Merged in feature/init_new_repo (pull request #394)
Feature/init new repo
This commit is contained in:
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
### Added
|
### Added
|
||||||
- Set individual page title
|
- Set individual page title
|
||||||
- Copy on write
|
- Copy on write
|
||||||
|
- A new repository can be initialized with a branch (for git and mercurial) and custom files (README.md on default)
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
- Stop fetching commits when it takes too long
|
- Stop fetching commits when it takes too long
|
||||||
|
|||||||
@@ -71,12 +71,25 @@ public final class Priorities
|
|||||||
*
|
*
|
||||||
* @return sorted class list
|
* @return sorted class list
|
||||||
*/
|
*/
|
||||||
public static <T> List<Class<? extends T>> sort(
|
public static <T> List<Class<? extends T>> sort(Iterable<Class<? extends T>> unordered)
|
||||||
Iterable<Class<? extends T>> unordered)
|
|
||||||
{
|
{
|
||||||
return new PriorityOrdering<T>().sortedCopy(unordered);
|
return new PriorityOrdering<T>().sortedCopy(unordered);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a list of instances sorted by priority.
|
||||||
|
*
|
||||||
|
* @param <T> type of class
|
||||||
|
* @param unordered unordered instances
|
||||||
|
*
|
||||||
|
* @return sorted instance list
|
||||||
|
*/
|
||||||
|
public static <T> List<T> sortInstances(Iterable<T> unordered)
|
||||||
|
{
|
||||||
|
return new PriorityInstanceOrdering<T>().sortedCopy(unordered);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
//~--- get methods ----------------------------------------------------------
|
//~--- get methods ----------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -125,4 +138,28 @@ public final class Priorities
|
|||||||
return Ints.compare(getPriority(left), getPriority(right));
|
return Ints.compare(getPriority(left), getPriority(right));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@link Ordering} which orders instances by priority.
|
||||||
|
*
|
||||||
|
* @param <T> type of instance
|
||||||
|
*/
|
||||||
|
public static class PriorityInstanceOrdering<T> extends Ordering<T>
|
||||||
|
{
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compares the left instance with the right instance.
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* @param left left instance
|
||||||
|
* @param right right instance
|
||||||
|
*
|
||||||
|
* @return compare value
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public int compare(T left, T right)
|
||||||
|
{
|
||||||
|
return Ints.compare(getPriority(left.getClass()), getPriority(right.getClass()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,72 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
|
import sonia.scm.plugin.ExtensionPoint;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this {@link RepositoryContentInitializer} to create new files with custom content
|
||||||
|
* which will be included in the initial commit of the new repository
|
||||||
|
*/
|
||||||
|
@ExtensionPoint
|
||||||
|
public interface RepositoryContentInitializer {
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param context add content to this context in order to commit files in the initial repository commit
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
void initialize(InitializerContext context) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this {@link InitializerContext} to create new files on repository initialization
|
||||||
|
* which will be included in the first commit
|
||||||
|
*/
|
||||||
|
interface InitializerContext {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return repository to which this initializerContext belongs to
|
||||||
|
*/
|
||||||
|
Repository getRepository();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* create new file which will be included in initial repository commit
|
||||||
|
* @param path path of new file
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
CreateFile create(String path);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Use this to apply content to new files which should be committed on repository initialization
|
||||||
|
*/
|
||||||
|
interface CreateFile {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies content to new file
|
||||||
|
* @param content content of file as string
|
||||||
|
* @return {@link InitializerContext}
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
InitializerContext from(String content) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies content to new file
|
||||||
|
* @param input content of file as input stream
|
||||||
|
* @return {@link InitializerContext}
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
InitializerContext from(InputStream input) throws IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies content to new file
|
||||||
|
* @param byteSource content of file as byte source
|
||||||
|
* @return {@link InitializerContext}
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
InitializerContext from(ByteSource byteSource) throws IOException;
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -102,7 +102,7 @@ public class ModifyCommandBuilder {
|
|||||||
public String execute() {
|
public String execute() {
|
||||||
AuthorUtil.setAuthorIfNotAvailable(request);
|
AuthorUtil.setAuthorIfNotAvailable(request);
|
||||||
try {
|
try {
|
||||||
Preconditions.checkArgument(request.isValid(), "commit message, branch and at least one request are required");
|
Preconditions.checkArgument(request.isValid(), "commit message and at least one request are required");
|
||||||
return command.execute(request);
|
return command.execute(request);
|
||||||
} finally {
|
} finally {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@@ -98,6 +98,21 @@ public class PrioritiesTest
|
|||||||
assertThat(cls, contains(B.class, C.class, A.class, D.class));
|
assertThat(cls, contains(B.class, C.class, A.class, D.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public void shouldSortInstances()
|
||||||
|
{
|
||||||
|
A a = new A();
|
||||||
|
B b = new B();
|
||||||
|
C c = new C();
|
||||||
|
D d = new D();
|
||||||
|
|
||||||
|
List<?> instances = ImmutableList.of(a, b, c, d);
|
||||||
|
|
||||||
|
instances = Priorities.sortInstances(instances);
|
||||||
|
assertThat(instances, contains(b, c, a, d));
|
||||||
|
}
|
||||||
|
|
||||||
//~--- inner classes --------------------------------------------------------
|
//~--- inner classes --------------------------------------------------------
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -70,6 +70,18 @@ public class SimpleGitWorkdirFactoryTest extends AbstractGitCommandTestBase {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCheckoutDefaultBranch() {
|
||||||
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||||
|
|
||||||
|
try (WorkingCopy<Repository, Repository> workingCopy = factory.createWorkingCopy(createContext(), null)) {
|
||||||
|
assertThat(new File(workingCopy.getWorkingRepository().getWorkTree(), "a.txt"))
|
||||||
|
.exists()
|
||||||
|
.isFile()
|
||||||
|
.hasContent("a\nline for blame");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cloneFromPoolShouldNotBeReused() {
|
public void cloneFromPoolShouldNotBeReused() {
|
||||||
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
SimpleGitWorkdirFactory factory = new SimpleGitWorkdirFactory(workdirProvider);
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
"nameHelpText": "Der Name des Repository. Dieser wird Teil der URL des Repository sein.",
|
||||||
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
"typeHelpText": "Der Typ des Repository (Mercurial, Git oder Subversion).",
|
||||||
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
"contactHelpText": "E-Mail Adresse der Person, die für das Repository verantwortlich ist.",
|
||||||
"descriptionHelpText": "Eine kurze Beschreibung des Repository."
|
"descriptionHelpText": "Eine kurze Beschreibung des Repository.",
|
||||||
|
"initializeRepository": "Erstellt einen ersten Branch und committet eine README.md."
|
||||||
},
|
},
|
||||||
"repositoryRoot": {
|
"repositoryRoot": {
|
||||||
"errorTitle": "Fehler",
|
"errorTitle": "Fehler",
|
||||||
@@ -97,7 +98,8 @@
|
|||||||
},
|
},
|
||||||
"repositoryForm": {
|
"repositoryForm": {
|
||||||
"subtitle": "Repository bearbeiten",
|
"subtitle": "Repository bearbeiten",
|
||||||
"submit": "Speichern"
|
"submit": "Speichern",
|
||||||
|
"initializeRepository": "Repository initiieren"
|
||||||
},
|
},
|
||||||
"sources": {
|
"sources": {
|
||||||
"file-tree": {
|
"file-tree": {
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
"nameHelpText": "The name of the repository. This name will be part of the repository url.",
|
||||||
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
"typeHelpText": "The type of the repository (e.g. Mercurial, Git or Subversion).",
|
||||||
"contactHelpText": "Email address of the person who is responsible for this repository.",
|
"contactHelpText": "Email address of the person who is responsible for this repository.",
|
||||||
"descriptionHelpText": "A short description of the repository."
|
"descriptionHelpText": "A short description of the repository.",
|
||||||
|
"initializeRepository": "Creates a initial branch and commit a basic README.md."
|
||||||
},
|
},
|
||||||
"repositoryRoot": {
|
"repositoryRoot": {
|
||||||
"errorTitle": "Error",
|
"errorTitle": "Error",
|
||||||
@@ -97,7 +98,8 @@
|
|||||||
},
|
},
|
||||||
"repositoryForm": {
|
"repositoryForm": {
|
||||||
"subtitle": "Edit Repository",
|
"subtitle": "Edit Repository",
|
||||||
"submit": "Save"
|
"submit": "Save",
|
||||||
|
"initializeRepository": "Initialize repository"
|
||||||
},
|
},
|
||||||
"sources": {
|
"sources": {
|
||||||
"file-tree": {
|
"file-tree": {
|
||||||
|
|||||||
@@ -21,7 +21,8 @@
|
|||||||
"nameHelpText": "El nombre del repositorio. Este nombre formará parte de la URL del repositorio.",
|
"nameHelpText": "El nombre del repositorio. Este nombre formará parte de la URL del repositorio.",
|
||||||
"typeHelpText": "El tipo del repositorio (Mercurial, Git or Subversion).",
|
"typeHelpText": "El tipo del repositorio (Mercurial, Git or Subversion).",
|
||||||
"contactHelpText": "Dirección del correo electrónico de la persona responsable del repositorio.",
|
"contactHelpText": "Dirección del correo electrónico de la persona responsable del repositorio.",
|
||||||
"descriptionHelpText": "Breve descripción del repositorio."
|
"descriptionHelpText": "Breve descripción del repositorio.",
|
||||||
|
"initializeRepository": "Creates a initial branch and commit a basic README.md."
|
||||||
},
|
},
|
||||||
"repositoryRoot": {
|
"repositoryRoot": {
|
||||||
"errorTitle": "Error",
|
"errorTitle": "Error",
|
||||||
@@ -97,7 +98,8 @@
|
|||||||
},
|
},
|
||||||
"repositoryForm": {
|
"repositoryForm": {
|
||||||
"subtitle": "Editar repositorio",
|
"subtitle": "Editar repositorio",
|
||||||
"submit": "Guardar"
|
"submit": "Guardar",
|
||||||
|
"initializeRepository": "Initialize repository"
|
||||||
},
|
},
|
||||||
"sources": {
|
"sources": {
|
||||||
"file-tree": {
|
"file-tree": {
|
||||||
|
|||||||
@@ -1,12 +1,27 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
|
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 { InputField, Level, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components";
|
import { Checkbox, Level, InputField, Select, SubmitButton, Subtitle, Textarea } from "@scm-manager/ui-components";
|
||||||
import * as validator from "./repositoryValidation";
|
import * as validator from "./repositoryValidation";
|
||||||
|
|
||||||
|
const CheckboxWrapper = styled.div`
|
||||||
|
margin-top: 2em;
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SelectWrapper = styled.div`
|
||||||
|
flex: 1;
|
||||||
|
`;
|
||||||
|
|
||||||
|
const SpaceBetween = styled.div`
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
`;
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
type Props = WithTranslation & {
|
||||||
submitForm: (p: Repository) => void;
|
submitForm: (repo: Repository, shouldInit: boolean) => void;
|
||||||
repository?: Repository;
|
repository?: Repository;
|
||||||
repositoryTypes?: RepositoryType[];
|
repositoryTypes?: RepositoryType[];
|
||||||
namespaceStrategy?: string;
|
namespaceStrategy?: string;
|
||||||
@@ -15,6 +30,7 @@ type Props = WithTranslation & {
|
|||||||
|
|
||||||
type State = {
|
type State = {
|
||||||
repository: Repository;
|
repository: Repository;
|
||||||
|
initRepository: boolean;
|
||||||
namespaceValidationError: boolean;
|
namespaceValidationError: boolean;
|
||||||
nameValidationError: boolean;
|
nameValidationError: boolean;
|
||||||
contactValidationError: boolean;
|
contactValidationError: boolean;
|
||||||
@@ -35,6 +51,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
description: "",
|
description: "",
|
||||||
_links: {}
|
_links: {}
|
||||||
},
|
},
|
||||||
|
initRepository: false,
|
||||||
namespaceValidationError: false,
|
namespaceValidationError: false,
|
||||||
nameValidationError: false,
|
nameValidationError: false,
|
||||||
contactValidationError: false
|
contactValidationError: false
|
||||||
@@ -71,7 +88,7 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
submit = (event: Event) => {
|
submit = (event: Event) => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
if (this.isValid()) {
|
if (this.isValid()) {
|
||||||
this.props.submitForm(this.state.repository);
|
this.props.submitForm(this.state.repository, this.state.initRepository);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -83,6 +100,12 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
return !!this.props.repository && !!this.props.repository._links.update;
|
return !!this.props.repository && !!this.props.repository._links.update;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
toggleInitCheckbox = () => {
|
||||||
|
this.setState({
|
||||||
|
initRepository: !this.state.initRepository
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { loading, t } = this.props;
|
const { loading, t } = this.props;
|
||||||
const repository = this.state.repository;
|
const repository = this.state.repository;
|
||||||
@@ -175,6 +198,8 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
errorMessage={t("validation.name-invalid")}
|
errorMessage={t("validation.name-invalid")}
|
||||||
helpText={t("help.nameHelpText")}
|
helpText={t("help.nameHelpText")}
|
||||||
/>
|
/>
|
||||||
|
<SpaceBetween>
|
||||||
|
<SelectWrapper>
|
||||||
<Select
|
<Select
|
||||||
label={t("repository.type")}
|
label={t("repository.type")}
|
||||||
onChange={this.handleTypeChange}
|
onChange={this.handleTypeChange}
|
||||||
@@ -182,6 +207,16 @@ class RepositoryForm extends React.Component<Props, State> {
|
|||||||
options={this.createSelectOptions(repositoryTypes)}
|
options={this.createSelectOptions(repositoryTypes)}
|
||||||
helpText={t("help.typeHelpText")}
|
helpText={t("help.typeHelpText")}
|
||||||
/>
|
/>
|
||||||
|
</SelectWrapper>
|
||||||
|
<CheckboxWrapper>
|
||||||
|
<Checkbox
|
||||||
|
label={t("repositoryForm.initializeRepository")}
|
||||||
|
checked={this.state.initRepository}
|
||||||
|
onChange={this.toggleInitCheckbox}
|
||||||
|
helpText={t("help.initializeRepository")}
|
||||||
|
/>
|
||||||
|
</CheckboxWrapper>
|
||||||
|
</SpaceBetween>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ type Props = WithTranslation & {
|
|||||||
// dispatch functions
|
// dispatch functions
|
||||||
fetchNamespaceStrategiesIfNeeded: () => void;
|
fetchNamespaceStrategiesIfNeeded: () => void;
|
||||||
fetchRepositoryTypesIfNeeded: () => void;
|
fetchRepositoryTypesIfNeeded: () => void;
|
||||||
createRepo: (link: string, p2: Repository, callback: (repo: Repository) => void) => void;
|
createRepo: (link: string, repository: Repository, initRepository: boolean, callback: (repo: Repository) => void) => void;
|
||||||
resetForm: () => void;
|
resetForm: () => void;
|
||||||
|
|
||||||
// context props
|
// context props
|
||||||
@@ -67,8 +67,8 @@ class Create extends React.Component<Props> {
|
|||||||
repositoryTypes={repositoryTypes}
|
repositoryTypes={repositoryTypes}
|
||||||
loading={createLoading}
|
loading={createLoading}
|
||||||
namespaceStrategy={namespaceStrategies.current}
|
namespaceStrategy={namespaceStrategies.current}
|
||||||
submitForm={repo => {
|
submitForm={(repo, initRepository) => {
|
||||||
createRepo(repoLink, repo, (repo: Repository) => this.repoCreated(repo));
|
createRepo(repoLink, repo, initRepository, (repo: Repository) => this.repoCreated(repo));
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Page>
|
</Page>
|
||||||
@@ -102,8 +102,8 @@ const mapDispatchToProps = (dispatch: any) => {
|
|||||||
fetchNamespaceStrategiesIfNeeded: () => {
|
fetchNamespaceStrategiesIfNeeded: () => {
|
||||||
dispatch(fetchNamespaceStrategiesIfNeeded());
|
dispatch(fetchNamespaceStrategiesIfNeeded());
|
||||||
},
|
},
|
||||||
createRepo: (link: string, repository: Repository, callback: () => void) => {
|
createRepo: (link: string, repository: Repository, initRepository: boolean, callback: () => void) => {
|
||||||
dispatch(createRepo(link, repository, callback));
|
dispatch(createRepo(link, repository, initRepository, callback));
|
||||||
},
|
},
|
||||||
resetForm: () => {
|
resetForm: () => {
|
||||||
dispatch(createRepoReset());
|
dispatch(createRepoReset());
|
||||||
|
|||||||
@@ -430,7 +430,7 @@ describe("repos fetch", () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(createRepo(URL, slartiFjords, callback)).then(() => {
|
return store.dispatch(createRepo(URL, slartiFjords, false, callback)).then(() => {
|
||||||
expect(callMe).toBe("yeah");
|
expect(callMe).toBe("yeah");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -441,7 +441,7 @@ describe("repos fetch", () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const store = mockStore({});
|
const store = mockStore({});
|
||||||
return store.dispatch(createRepo(URL, slartiFjords)).then(() => {
|
return store.dispatch(createRepo(URL, slartiFjords, false)).then(() => {
|
||||||
const actions = store.getActions();
|
const actions = store.getActions();
|
||||||
expect(actions[0].type).toEqual(CREATE_REPO_PENDING);
|
expect(actions[0].type).toEqual(CREATE_REPO_PENDING);
|
||||||
expect(actions[1].type).toEqual(CREATE_REPO_FAILURE);
|
expect(actions[1].type).toEqual(CREATE_REPO_FAILURE);
|
||||||
|
|||||||
@@ -155,11 +155,12 @@ export function fetchRepoFailure(namespace: string, name: string, error: Error):
|
|||||||
|
|
||||||
// create repo
|
// create repo
|
||||||
|
|
||||||
export function createRepo(link: string, repository: Repository, callback?: (repo: Repository) => void) {
|
export function createRepo(link: string, repository: Repository, initRepository: boolean, callback?: (repo: Repository) => void) {
|
||||||
return function(dispatch: any) {
|
return function(dispatch: any) {
|
||||||
dispatch(createRepoPending());
|
dispatch(createRepoPending());
|
||||||
|
const repoLink = initRepository ? link + "?initialize=true" : link;
|
||||||
return apiClient
|
return apiClient
|
||||||
.post(link, repository, CONTENT_TYPE)
|
.post(repoLink, repository, CONTENT_TYPE)
|
||||||
.then(response => {
|
.then(response => {
|
||||||
const location = response.headers.get("Location");
|
const location = response.headers.get("Location");
|
||||||
dispatch(createRepoSuccess());
|
dispatch(createRepoSuccess());
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import com.webcohesion.enunciate.metadata.rs.StatusCodes;
|
|||||||
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
import com.webcohesion.enunciate.metadata.rs.TypeHint;
|
||||||
import org.apache.shiro.SecurityUtils;
|
import org.apache.shiro.SecurityUtils;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryInitializer;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.RepositoryPermission;
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
import sonia.scm.search.SearchRequest;
|
import sonia.scm.search.SearchRequest;
|
||||||
@@ -24,7 +25,7 @@ import javax.ws.rs.Path;
|
|||||||
import javax.ws.rs.Produces;
|
import javax.ws.rs.Produces;
|
||||||
import javax.ws.rs.QueryParam;
|
import javax.ws.rs.QueryParam;
|
||||||
import javax.ws.rs.core.Response;
|
import javax.ws.rs.core.Response;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
import static com.google.common.base.Strings.isNullOrEmpty;
|
import static com.google.common.base.Strings.isNullOrEmpty;
|
||||||
@@ -38,13 +39,15 @@ public class RepositoryCollectionResource {
|
|||||||
private final RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper;
|
private final RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper;
|
||||||
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
|
private final RepositoryDtoToRepositoryMapper dtoToRepositoryMapper;
|
||||||
private final ResourceLinks resourceLinks;
|
private final ResourceLinks resourceLinks;
|
||||||
|
private final RepositoryInitializer repositoryInitializer;
|
||||||
|
|
||||||
@Inject
|
@Inject
|
||||||
public RepositoryCollectionResource(RepositoryManager manager, RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper, RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, ResourceLinks resourceLinks) {
|
public RepositoryCollectionResource(RepositoryManager manager, RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper, RepositoryDtoToRepositoryMapper dtoToRepositoryMapper, ResourceLinks resourceLinks, RepositoryInitializer repositoryInitializer) {
|
||||||
this.adapter = new CollectionResourceManagerAdapter<>(manager, Repository.class);
|
this.adapter = new CollectionResourceManagerAdapter<>(manager, Repository.class);
|
||||||
this.repositoryCollectionToDtoMapper = repositoryCollectionToDtoMapper;
|
this.repositoryCollectionToDtoMapper = repositoryCollectionToDtoMapper;
|
||||||
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
this.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||||
this.resourceLinks = resourceLinks;
|
this.resourceLinks = resourceLinks;
|
||||||
|
this.repositoryInitializer = repositoryInitializer;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -98,10 +101,18 @@ public class RepositoryCollectionResource {
|
|||||||
})
|
})
|
||||||
@TypeHint(TypeHint.NO_CONTENT.class)
|
@TypeHint(TypeHint.NO_CONTENT.class)
|
||||||
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
|
@ResponseHeaders(@ResponseHeader(name = "Location", description = "uri to the created repository"))
|
||||||
public Response create(@Valid RepositoryDto repository) {
|
public Response create(@Valid RepositoryDto repository, @QueryParam("initialize") boolean initialize) {
|
||||||
return adapter.create(repository,
|
AtomicReference<Repository> reference = new AtomicReference<>();
|
||||||
|
Response response = adapter.create(repository,
|
||||||
() -> createModelObjectFromDto(repository),
|
() -> createModelObjectFromDto(repository),
|
||||||
r -> resourceLinks.repository().self(r.getNamespace(), r.getName()));
|
r -> {
|
||||||
|
reference.set(r);
|
||||||
|
return resourceLinks.repository().self(r.getNamespace(), r.getName());
|
||||||
|
});
|
||||||
|
if (initialize) {
|
||||||
|
repositoryInitializer.initialize(reference.get());
|
||||||
|
}
|
||||||
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
private Repository createModelObjectFromDto(@Valid RepositoryDto repositoryDto) {
|
||||||
|
|||||||
@@ -0,0 +1,23 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import sonia.scm.Priority;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
@Priority(1) // should always be the first, so that plugins can overwrite the readme.md
|
||||||
|
public class ReadmeRepositoryContentInitializer implements RepositoryContentInitializer {
|
||||||
|
@Override
|
||||||
|
public void initialize(InitializerContext context) throws IOException {
|
||||||
|
Repository repository = context.getRepository();
|
||||||
|
|
||||||
|
String content = "# " + repository.getName();
|
||||||
|
String description = repository.getDescription();
|
||||||
|
if (!Strings.isNullOrEmpty(description)) {
|
||||||
|
content += "\n\n" + description;
|
||||||
|
}
|
||||||
|
context.create("README.md").from(content);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,101 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
|
import com.google.common.io.CharSource;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.Priorities;
|
||||||
|
import sonia.scm.repository.api.ModifyCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.inject.Singleton;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
@Singleton
|
||||||
|
public class RepositoryInitializer {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(RepositoryInitializer.class);
|
||||||
|
|
||||||
|
private final RepositoryServiceFactory serviceFactory;
|
||||||
|
private final Iterable<RepositoryContentInitializer> contentInitializers;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public RepositoryInitializer(RepositoryServiceFactory serviceFactory, Set<RepositoryContentInitializer> contentInitializerSet) {
|
||||||
|
this.serviceFactory = serviceFactory;
|
||||||
|
this.contentInitializers = Priorities.sortInstances(contentInitializerSet);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void initialize(Repository repository) {
|
||||||
|
try (RepositoryService service = serviceFactory.create(repository)) {
|
||||||
|
ModifyCommandBuilder modifyCommandBuilder = service.getModifyCommand();
|
||||||
|
|
||||||
|
InitializerContextImpl initializerContext = new InitializerContextImpl(repository, modifyCommandBuilder);
|
||||||
|
|
||||||
|
for (RepositoryContentInitializer initializer : contentInitializers) {
|
||||||
|
initializer.initialize(initializerContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
modifyCommandBuilder.setCommitMessage("initialize repository");
|
||||||
|
String revision = modifyCommandBuilder.execute();
|
||||||
|
LOG.info("initialized repository {} as revision {}", repository.getNamespaceAndName(), revision);
|
||||||
|
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalRepositoryException(repository, "failed to initialize repository", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class InitializerContextImpl implements RepositoryContentInitializer.InitializerContext {
|
||||||
|
|
||||||
|
private final Repository repository;
|
||||||
|
private final ModifyCommandBuilder builder;
|
||||||
|
|
||||||
|
InitializerContextImpl(Repository repository, ModifyCommandBuilder builder) {
|
||||||
|
this.repository = repository;
|
||||||
|
this.builder = builder;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Repository getRepository() {
|
||||||
|
return repository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepositoryContentInitializer.CreateFile create(String path) {
|
||||||
|
return new CreateFileImpl(this, builder.createFile(path).setOverwrite(true));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class CreateFileImpl implements RepositoryContentInitializer.CreateFile {
|
||||||
|
|
||||||
|
private final RepositoryContentInitializer.InitializerContext initializerContext;
|
||||||
|
private final ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader;
|
||||||
|
|
||||||
|
CreateFileImpl(RepositoryContentInitializer.InitializerContext initializerContext, ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader) {
|
||||||
|
this.initializerContext = initializerContext;
|
||||||
|
this.contentLoader = contentLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepositoryContentInitializer.InitializerContext from(String content) throws IOException {
|
||||||
|
return from(CharSource.wrap(content).asByteSource(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepositoryContentInitializer.InitializerContext from(InputStream input) throws IOException {
|
||||||
|
contentLoader.withData(input);
|
||||||
|
return initializerContext;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RepositoryContentInitializer.InitializerContext from(ByteSource byteSource) throws IOException {
|
||||||
|
contentLoader.withData(byteSource);
|
||||||
|
return initializerContext;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -18,6 +18,7 @@ import org.mockito.Mock;
|
|||||||
import sonia.scm.PageResult;
|
import sonia.scm.PageResult;
|
||||||
import sonia.scm.repository.NamespaceAndName;
|
import sonia.scm.repository.NamespaceAndName;
|
||||||
import sonia.scm.repository.Repository;
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryInitializer;
|
||||||
import sonia.scm.repository.RepositoryManager;
|
import sonia.scm.repository.RepositoryManager;
|
||||||
import sonia.scm.repository.api.RepositoryService;
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
@@ -76,6 +77,8 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
private ScmPathInfoStore scmPathInfoStore;
|
private ScmPathInfoStore scmPathInfoStore;
|
||||||
@Mock
|
@Mock
|
||||||
private ScmPathInfo uriInfo;
|
private ScmPathInfo uriInfo;
|
||||||
|
@Mock
|
||||||
|
private RepositoryInitializer repositoryInitializer;
|
||||||
|
|
||||||
@Captor
|
@Captor
|
||||||
private ArgumentCaptor<Predicate<Repository>> filterCaptor;
|
private ArgumentCaptor<Predicate<Repository>> filterCaptor;
|
||||||
@@ -95,7 +98,7 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
super.dtoToRepositoryMapper = dtoToRepositoryMapper;
|
||||||
super.manager = repositoryManager;
|
super.manager = repositoryManager;
|
||||||
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
RepositoryCollectionToDtoMapper repositoryCollectionToDtoMapper = new RepositoryCollectionToDtoMapper(repositoryToDtoMapper, resourceLinks);
|
||||||
super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks));
|
super.repositoryCollectionResource = Providers.of(new RepositoryCollectionResource(repositoryManager, repositoryCollectionToDtoMapper, dtoToRepositoryMapper, resourceLinks, repositoryInitializer));
|
||||||
dispatcher.addSingletonResource(getRepositoryRootResource());
|
dispatcher.addSingletonResource(getRepositoryRootResource());
|
||||||
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);
|
||||||
@@ -288,6 +291,32 @@ public class RepositoryRootResourceTest extends RepositoryTestBase {
|
|||||||
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
|
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
|
||||||
assertEquals("/v2/repositories/otherspace/repo", response.getOutputHeaders().get("Location").get(0).toString());
|
assertEquals("/v2/repositories/otherspace/repo", response.getOutputHeaders().get("Location").get(0).toString());
|
||||||
verify(repositoryManager).create(any(Repository.class));
|
verify(repositoryManager).create(any(Repository.class));
|
||||||
|
verify(repositoryInitializer, never()).initialize(any(Repository.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void shouldCreateNewRepositoryAndInitialize() throws Exception {
|
||||||
|
when(repositoryManager.create(any())).thenAnswer(invocation -> invocation.getArgument(0));
|
||||||
|
|
||||||
|
URL url = Resources.getResource("sonia/scm/api/v2/repository-test-update.json");
|
||||||
|
byte[] repositoryJson = Resources.toByteArray(url);
|
||||||
|
|
||||||
|
MockHttpRequest request = MockHttpRequest
|
||||||
|
.post("/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + "?initialize=true")
|
||||||
|
.contentType(VndMediaType.REPOSITORY)
|
||||||
|
.content(repositoryJson);
|
||||||
|
MockHttpResponse response = new MockHttpResponse();
|
||||||
|
|
||||||
|
dispatcher.invoke(request, response);
|
||||||
|
|
||||||
|
assertEquals(HttpServletResponse.SC_CREATED, response.getStatus());
|
||||||
|
|
||||||
|
ArgumentCaptor<Repository> captor = ArgumentCaptor.forClass(Repository.class);
|
||||||
|
verify(repositoryInitializer).initialize(captor.capture());
|
||||||
|
|
||||||
|
Repository repository = captor.getValue();
|
||||||
|
assertEquals("space", repository.getNamespace());
|
||||||
|
assertEquals("repo", repository.getName());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static org.junit.jupiter.api.Assertions.*;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class ReadmeRepositoryContentInitializerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryContentInitializer.InitializerContext context;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryContentInitializer.CreateFile createFile;
|
||||||
|
|
||||||
|
private Repository repository;
|
||||||
|
|
||||||
|
private ReadmeRepositoryContentInitializer initializer = new ReadmeRepositoryContentInitializer();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpContext() {
|
||||||
|
repository = RepositoryTestData.createHeartOfGold("hg");
|
||||||
|
when(context.getRepository()).thenReturn(repository);
|
||||||
|
when(context.create("README.md")).thenReturn(createFile);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateReadme() throws IOException {
|
||||||
|
initializer.initialize(context);
|
||||||
|
|
||||||
|
verify(createFile).from("# HeartOfGold\n\n" + repository.getDescription());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateReadmeWithoutDescription() throws IOException {
|
||||||
|
repository.setDescription(null);
|
||||||
|
|
||||||
|
initializer.initialize(context);
|
||||||
|
|
||||||
|
verify(createFile).from("# HeartOfGold");
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,181 @@
|
|||||||
|
package sonia.scm.repository;
|
||||||
|
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.io.ByteSource;
|
||||||
|
import com.google.common.io.ByteStreams;
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.TestTemplate;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Answers;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.Priority;
|
||||||
|
import sonia.scm.repository.api.ModifyCommandBuilder;
|
||||||
|
import sonia.scm.repository.api.RepositoryService;
|
||||||
|
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.doThrow;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
class RepositoryInitializerTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryServiceFactory repositoryServiceFactory;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private RepositoryService repositoryService;
|
||||||
|
|
||||||
|
@Mock(answer = Answers.RETURNS_SELF)
|
||||||
|
private ModifyCommandBuilder modifyCommand;
|
||||||
|
|
||||||
|
private final Repository repository = RepositoryTestData.createHeartOfGold("git");
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUpModifyCommand() {
|
||||||
|
when(repositoryServiceFactory.create(repository)).thenReturn(repositoryService);
|
||||||
|
when(repositoryService.getModifyCommand()).thenReturn(modifyCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallRepositoryContentInitializer() throws IOException {
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader readmeContentLoader = mockContentLoader("README.md");
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader licenseContentLoader = mockContentLoader("LICENSE.txt");
|
||||||
|
|
||||||
|
Set<RepositoryContentInitializer> repositoryContentInitializers = ImmutableSet.of(
|
||||||
|
new ReadmeContentInitializer(),
|
||||||
|
new LicenseContentInitializer()
|
||||||
|
);
|
||||||
|
|
||||||
|
RepositoryInitializer initializer = new RepositoryInitializer(repositoryServiceFactory, repositoryContentInitializers);
|
||||||
|
initializer.initialize(repository);
|
||||||
|
|
||||||
|
verifyFileCreation(readmeContentLoader, "# HeartOfGold");
|
||||||
|
verifyFileCreation(licenseContentLoader, "MIT");
|
||||||
|
|
||||||
|
verify(modifyCommand).setCommitMessage("initialize repository");
|
||||||
|
verify(modifyCommand).execute();
|
||||||
|
|
||||||
|
verify(repositoryService).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCallRepositoryContentInitializerWithInputStream() throws IOException {
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader = mockContentLoader("awesome.txt");
|
||||||
|
|
||||||
|
Set<RepositoryContentInitializer> repositoryContentInitializers = ImmutableSet.of(
|
||||||
|
new StreamingContentInitializer()
|
||||||
|
);
|
||||||
|
|
||||||
|
RepositoryInitializer initializer = new RepositoryInitializer(repositoryServiceFactory, repositoryContentInitializers);
|
||||||
|
initializer.initialize(repository);
|
||||||
|
|
||||||
|
verifyFileCreationWithStream(contentLoader, "awesome");
|
||||||
|
|
||||||
|
verify(modifyCommand).setCommitMessage("initialize repository");
|
||||||
|
verify(modifyCommand).execute();
|
||||||
|
|
||||||
|
verify(repositoryService).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldRespectPriorityOrder() throws IOException {
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader = mock(ModifyCommandBuilder.WithOverwriteFlagContentLoader.class);
|
||||||
|
when(contentLoader.setOverwrite(true)).thenReturn(contentLoader);
|
||||||
|
|
||||||
|
when(modifyCommand.createFile(anyString())).thenReturn(contentLoader);
|
||||||
|
|
||||||
|
AtomicReference<String> reference = new AtomicReference<>();
|
||||||
|
when(contentLoader.withData(any(ByteSource.class))).thenAnswer(ic -> {
|
||||||
|
ByteSource byteSource = ic.getArgument(0);
|
||||||
|
reference.set(byteSource.asCharSource(StandardCharsets.UTF_8).read());
|
||||||
|
return modifyCommand;
|
||||||
|
});
|
||||||
|
|
||||||
|
Set<RepositoryContentInitializer> repositoryContentInitializers = ImmutableSet.of(
|
||||||
|
new LicenseContentInitializer(),
|
||||||
|
new ReadmeContentInitializer()
|
||||||
|
);
|
||||||
|
|
||||||
|
RepositoryInitializer initializer = new RepositoryInitializer(repositoryServiceFactory, repositoryContentInitializers);
|
||||||
|
initializer.initialize(repository);
|
||||||
|
|
||||||
|
assertThat(reference.get()).isEqualTo("MIT");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCloseRepositoryServiceOnException() throws IOException {
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader = mockContentLoader("README.md");
|
||||||
|
doThrow(new IOException("epic fail")).when(contentLoader).withData(any(ByteSource.class));
|
||||||
|
|
||||||
|
RepositoryInitializer initializer = new RepositoryInitializer(repositoryServiceFactory, ImmutableSet.of(new ReadmeContentInitializer()));
|
||||||
|
assertThrows(InternalRepositoryException.class, () -> initializer.initialize(repository));
|
||||||
|
|
||||||
|
verify(repositoryService).close();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ModifyCommandBuilder.WithOverwriteFlagContentLoader mockContentLoader(String path) {
|
||||||
|
ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader = mock(ModifyCommandBuilder.WithOverwriteFlagContentLoader.class);
|
||||||
|
doReturn(contentLoader).when(modifyCommand).createFile(path);
|
||||||
|
when(contentLoader.setOverwrite(true)).thenReturn(contentLoader);
|
||||||
|
return contentLoader;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyFileCreation(ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader, String expectedContent) throws IOException {
|
||||||
|
ArgumentCaptor<ByteSource> captor = ArgumentCaptor.forClass(ByteSource.class);
|
||||||
|
verify(contentLoader).withData(captor.capture());
|
||||||
|
String content = captor.getValue().asCharSource(StandardCharsets.UTF_8).read();
|
||||||
|
assertThat(content).isEqualTo(expectedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void verifyFileCreationWithStream(ModifyCommandBuilder.WithOverwriteFlagContentLoader contentLoader, String expectedContent) throws IOException {
|
||||||
|
ArgumentCaptor<InputStream> captor = ArgumentCaptor.forClass(InputStream.class);
|
||||||
|
verify(contentLoader).withData(captor.capture());
|
||||||
|
byte[] bytes = ByteStreams.toByteArray(captor.getValue());
|
||||||
|
assertThat(new String(bytes, StandardCharsets.UTF_8)).isEqualTo(expectedContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Priority(1)
|
||||||
|
private static class ReadmeContentInitializer implements RepositoryContentInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InitializerContext context) throws IOException {
|
||||||
|
Repository repository = context.getRepository();
|
||||||
|
context.create("README.md").from("# " + repository.getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Priority(2)
|
||||||
|
private static class LicenseContentInitializer implements RepositoryContentInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InitializerContext context) throws IOException {
|
||||||
|
context.create("LICENSE.txt").from("MIT");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class StreamingContentInitializer implements RepositoryContentInitializer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(InitializerContext context) throws IOException {
|
||||||
|
context.create("awesome.txt").from(new ByteArrayInputStream("awesome".getBytes(StandardCharsets.UTF_8)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user