mirror of
https://github.com/scm-manager/scm-manager.git
synced 2025-11-15 09:46:16 +01:00
Merge with 2.0.0-m3
This commit is contained in:
@@ -59,7 +59,7 @@ public final class SCMContext
|
|||||||
*/
|
*/
|
||||||
public static final User ANONYMOUS = new User(USER_ANONYMOUS,
|
public static final User ANONYMOUS = new User(USER_ANONYMOUS,
|
||||||
"SCM Anonymous",
|
"SCM Anonymous",
|
||||||
"scm-anonymous@scm-manager.com");
|
"scm-anonymous@scm-manager.org");
|
||||||
|
|
||||||
/** Singleton instance of {@link SCMContextProvider} */
|
/** Singleton instance of {@link SCMContextProvider} */
|
||||||
private static volatile SCMContextProvider provider;
|
private static volatile SCMContextProvider provider;
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ public class AvailablePlugin implements Plugin {
|
|||||||
return pending;
|
return pending;
|
||||||
}
|
}
|
||||||
|
|
||||||
public AvailablePlugin install() {
|
AvailablePlugin install() {
|
||||||
Preconditions.checkState(!pending, "installation is already pending");
|
Preconditions.checkState(!pending, "installation is already pending");
|
||||||
return new AvailablePlugin(pluginDescriptor, true);
|
return new AvailablePlugin(pluginDescriptor, true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -113,7 +113,6 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler
|
|||||||
Repository repository = new Repository();
|
Repository repository = new Repository();
|
||||||
|
|
||||||
repository.setName(repositoryName);
|
repository.setName(repositoryName);
|
||||||
repository.setPublicReadable(false);
|
|
||||||
repository.setType(getTypeName());
|
repository.setType(getTypeName());
|
||||||
|
|
||||||
return repository;
|
return repository;
|
||||||
|
|||||||
@@ -83,8 +83,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
private String name;
|
private String name;
|
||||||
@XmlElement(name = "permission")
|
@XmlElement(name = "permission")
|
||||||
private Set<RepositoryPermission> permissions = new HashSet<>();
|
private Set<RepositoryPermission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
|
||||||
private boolean publicReadable = false;
|
|
||||||
private String type;
|
private String type;
|
||||||
|
|
||||||
|
|
||||||
@@ -225,15 +223,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
return Util.isEmpty(healthCheckFailures);
|
return Util.isEmpty(healthCheckFailures);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the {@link Repository} is public readable.
|
|
||||||
*
|
|
||||||
* @return true if the {@link Repository} is public readable
|
|
||||||
*/
|
|
||||||
public boolean isPublicReadable() {
|
|
||||||
return publicReadable;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if the {@link Repository} is valid.
|
* Returns true if the {@link Repository} is valid.
|
||||||
* <ul>
|
* <ul>
|
||||||
@@ -292,10 +281,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
return this.permissions.remove(permission);
|
return this.permissions.remove(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPublicReadable(boolean publicReadable) {
|
|
||||||
this.publicReadable = publicReadable;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setType(String type) {
|
public void setType(String type) {
|
||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
@@ -332,7 +317,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
repository.setLastModified(lastModified);
|
repository.setLastModified(lastModified);
|
||||||
repository.setDescription(description);
|
repository.setDescription(description);
|
||||||
repository.setPermissions(permissions);
|
repository.setPermissions(permissions);
|
||||||
repository.setPublicReadable(publicReadable);
|
|
||||||
|
|
||||||
// do not copy health check results
|
// do not copy health check results
|
||||||
}
|
}
|
||||||
@@ -360,7 +344,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
&& Objects.equal(name, other.name)
|
&& Objects.equal(name, other.name)
|
||||||
&& Objects.equal(contact, other.contact)
|
&& Objects.equal(contact, other.contact)
|
||||||
&& Objects.equal(description, other.description)
|
&& Objects.equal(description, other.description)
|
||||||
&& Objects.equal(publicReadable, other.publicReadable)
|
|
||||||
&& Objects.equal(permissions, other.permissions)
|
&& Objects.equal(permissions, other.permissions)
|
||||||
&& Objects.equal(type, other.type)
|
&& Objects.equal(type, other.type)
|
||||||
&& Objects.equal(creationDate, other.creationDate)
|
&& Objects.equal(creationDate, other.creationDate)
|
||||||
@@ -371,7 +354,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode(id, namespace, name, contact, description, publicReadable,
|
return Objects.hashCode(id, namespace, name, contact, description,
|
||||||
permissions, type, creationDate, lastModified, properties,
|
permissions, type, creationDate, lastModified, properties,
|
||||||
healthCheckFailures);
|
healthCheckFailures);
|
||||||
}
|
}
|
||||||
@@ -384,7 +367,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
|||||||
.add("name", name)
|
.add("name", name)
|
||||||
.add("contact", contact)
|
.add("contact", contact)
|
||||||
.add("description", description)
|
.add("description", description)
|
||||||
.add("publicReadable", publicReadable)
|
|
||||||
.add("permissions", permissions)
|
.add("permissions", permissions)
|
||||||
.add("type", type)
|
.add("type", type)
|
||||||
.add("lastModified", lastModified)
|
.add("lastModified", lastModified)
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package sonia.scm.it;
|
package sonia.scm.it;
|
||||||
|
|
||||||
import org.apache.http.HttpStatus;
|
import org.apache.http.HttpStatus;
|
||||||
|
import org.assertj.core.api.AbstractCharSequenceAssert;
|
||||||
import org.assertj.core.util.Lists;
|
import org.assertj.core.util.Lists;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Ignore;
|
import org.junit.Ignore;
|
||||||
@@ -28,6 +29,7 @@ import java.util.stream.Collectors;
|
|||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD;
|
import static sonia.scm.it.utils.RestUtil.ADMIN_PASSWORD;
|
||||||
import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME;
|
import static sonia.scm.it.utils.RestUtil.ADMIN_USERNAME;
|
||||||
|
|
||||||
@@ -94,8 +96,7 @@ public class DiffITCase {
|
|||||||
String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse);
|
String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse);
|
||||||
|
|
||||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, expected);
|
||||||
.isEqualTo(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -107,8 +108,7 @@ public class DiffITCase {
|
|||||||
String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse);
|
String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse);
|
||||||
|
|
||||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, expected);
|
||||||
.isEqualTo(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -120,8 +120,7 @@ public class DiffITCase {
|
|||||||
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), gitRepositoryResponse);
|
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "the updated content of a"), gitRepositoryResponse);
|
||||||
|
|
||||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, expected);
|
||||||
.isEqualTo(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -161,21 +160,17 @@ public class DiffITCase {
|
|||||||
String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest");
|
String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest");
|
||||||
String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||||
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
|
||||||
|
|
||||||
fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest");
|
fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest");
|
||||||
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||||
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
|
||||||
|
|
||||||
fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest");
|
fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest");
|
||||||
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||||
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -196,8 +191,7 @@ public class DiffITCase {
|
|||||||
Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, "");
|
Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, "");
|
||||||
String svnDiff = getDiff(commit, svnRepositoryResponse);
|
String svnDiff = getDiff(commit, svnRepositoryResponse);
|
||||||
String gitDiff = getDiff(commit1, gitRepositoryResponse);
|
String gitDiff = getDiff(commit1, gitRepositoryResponse);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -218,8 +212,7 @@ public class DiffITCase {
|
|||||||
String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse);
|
String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse);
|
||||||
|
|
||||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||||
assertThat(svnDiff)
|
assertDiffsAreEqual(svnDiff, expected);
|
||||||
.isEqualTo(expected);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getFileContent(String name) throws URISyntaxException, IOException {
|
public String getFileContent(String name) throws URISyntaxException, IOException {
|
||||||
@@ -242,6 +235,12 @@ public class DiffITCase {
|
|||||||
return gitDiff.replaceAll(".*(index.*\n)", "");
|
return gitDiff.replaceAll(".*(index.*\n)", "");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertDiffsAreEqual(String svnDiff, String gitDiff) {
|
||||||
|
assertThat(svnDiff)
|
||||||
|
.as("diffs are different\n\nsvn:\n==================================================\n\n%s\n\ngit:\n==================================================\n\n%s)", svnDiff, gitDiff)
|
||||||
|
.isEqualTo(gitDiff);
|
||||||
|
}
|
||||||
|
|
||||||
private String getDiff(Changeset svnChangeset, ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> svnRepositoryResponse) {
|
private String getDiff(Changeset svnChangeset, ScmRequests.RepositoryResponse<ScmRequests.IndexResponse> svnRepositoryResponse) {
|
||||||
return svnRepositoryResponse.requestChangesets()
|
return svnRepositoryResponse.requestChangesets()
|
||||||
.requestDiffInGitFormat(svnChangeset.getId())
|
.requestDiffInGitFormat(svnChangeset.getId())
|
||||||
|
|||||||
@@ -110,6 +110,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
numberOfPotentialBeginning = -1;
|
numberOfPotentialBeginning = -1;
|
||||||
|
inPotentialQuotedLine = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (inPotentialQuotedLine && i == '"') {
|
if (inPotentialQuotedLine && i == '"') {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ public class GitDiffCommand_DequoteOutputStreamTest {
|
|||||||
"--- /dev/null\n" +
|
"--- /dev/null\n" +
|
||||||
"+++ \"b/\\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 \\303\\245g\\303\\260f\\303\\237\"\n" +
|
"+++ \"b/\\303\\272\\303\\274\\303\\276\\303\\253\\303\\251\\303\\245\\303\\253\\303\\245\\303\\251 \\303\\245g\\303\\260f\\303\\237\"\n" +
|
||||||
"@@ -0,0 +1 @@\n" +
|
"@@ -0,0 +1 @@\n" +
|
||||||
"+rthms";
|
"+String s = \"quotes shall be kept\";";
|
||||||
|
|
||||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||||
GitDiffCommand.DequoteOutputStream stream = new GitDiffCommand.DequoteOutputStream(buffer);
|
GitDiffCommand.DequoteOutputStream stream = new GitDiffCommand.DequoteOutputStream(buffer);
|
||||||
@@ -30,6 +30,6 @@ public class GitDiffCommand_DequoteOutputStreamTest {
|
|||||||
"--- /dev/null\n" +
|
"--- /dev/null\n" +
|
||||||
"+++ b/úüþëéåëåé ågðfß\n" +
|
"+++ b/úüþëéåëåé ågðfß\n" +
|
||||||
"@@ -0,0 +1 @@\n" +
|
"@@ -0,0 +1 @@\n" +
|
||||||
"+rthms");
|
"+String s = \"quotes shall be kept\";");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types";
|
import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types";
|
||||||
import Autocomplete from "./Autocomplete";
|
import Autocomplete from "./Autocomplete";
|
||||||
|
import { apiClient } from "./apiclient";
|
||||||
|
|
||||||
export type AutocompleteProps = {
|
export type AutocompleteProps = {
|
||||||
autocompleteLink?: string;
|
autocompleteLink?: string;
|
||||||
@@ -19,7 +20,8 @@ export default class UserGroupAutocomplete extends React.Component<Props> {
|
|||||||
loadSuggestions = (inputValue: string): Promise<SelectValue[]> => {
|
loadSuggestions = (inputValue: string): Promise<SelectValue[]> => {
|
||||||
const url = this.props.autocompleteLink;
|
const url = this.props.autocompleteLink;
|
||||||
const link = url + "?q=";
|
const link = url + "?q=";
|
||||||
return fetch(link + inputValue)
|
return apiClient
|
||||||
|
.get(link + inputValue)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then((json: AutocompleteObject[]) => {
|
.then((json: AutocompleteObject[]) => {
|
||||||
return json.map(element => {
|
return json.map(element => {
|
||||||
|
|||||||
@@ -27,13 +27,17 @@ const extractXsrfToken = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
||||||
const headers: { [key: string]: string } = {
|
if (!o.headers) {
|
||||||
Cache: "no-cache",
|
o.headers = {};
|
||||||
// identify the request as ajax request
|
}
|
||||||
"X-Requested-With": "XMLHttpRequest",
|
|
||||||
// identify the web interface
|
// @ts-ignore We are sure that here we only get headers of type Record<string, string>
|
||||||
"X-SCM-Client": "WUI"
|
const headers: Record<string, string> = o.headers;
|
||||||
};
|
headers["Cache"] = "no-cache";
|
||||||
|
// identify the request as ajax request
|
||||||
|
headers["X-Requested-With"] = "XMLHttpRequest";
|
||||||
|
// identify the web interface
|
||||||
|
headers["X-SCM-Client"] = "WUI";
|
||||||
|
|
||||||
const xsrf = extractXsrfToken();
|
const xsrf = extractXsrfToken();
|
||||||
if (xsrf) {
|
if (xsrf) {
|
||||||
@@ -80,23 +84,32 @@ class ApiClient {
|
|||||||
return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure);
|
return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
post(url: string, payload?: any, contentType = "application/json") {
|
post(url: string, payload?: any, contentType = "application/json", additionalHeaders: Record<string, string> = {}) {
|
||||||
return this.httpRequestWithJSONBody("POST", url, contentType, payload);
|
return this.httpRequestWithJSONBody("POST", url, contentType, additionalHeaders, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
postBinary(url: string, fileAppender: (p: FormData) => void) {
|
postText(url: string, payload: string, additionalHeaders: Record<string, string> = {}) {
|
||||||
|
return this.httpRequestWithTextBody("POST", url, additionalHeaders, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
putText(url: string, payload: string, additionalHeaders: Record<string, string> = {}) {
|
||||||
|
return this.httpRequestWithTextBody("PUT", url, additionalHeaders, payload);
|
||||||
|
}
|
||||||
|
|
||||||
|
postBinary(url: string, fileAppender: (p: FormData) => void, additionalHeaders: Record<string, string> = {}) {
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
fileAppender(formData);
|
fileAppender(formData);
|
||||||
|
|
||||||
const options: RequestInit = {
|
const options: RequestInit = {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
body: formData
|
body: formData,
|
||||||
|
headers: additionalHeaders
|
||||||
};
|
};
|
||||||
return this.httpRequestWithBinaryBody(options, url);
|
return this.httpRequestWithBinaryBody(options, url);
|
||||||
}
|
}
|
||||||
|
|
||||||
put(url: string, payload: any, contentType = "application/json") {
|
put(url: string, payload: any, contentType = "application/json", additionalHeaders: Record<string, string> = {}) {
|
||||||
return this.httpRequestWithJSONBody("PUT", url, contentType, payload);
|
return this.httpRequestWithJSONBody("PUT", url, contentType, additionalHeaders, payload);
|
||||||
}
|
}
|
||||||
|
|
||||||
head(url: string) {
|
head(url: string) {
|
||||||
@@ -115,9 +128,16 @@ class ApiClient {
|
|||||||
return fetch(createUrl(url), options).then(handleFailure);
|
return fetch(createUrl(url), options).then(handleFailure);
|
||||||
}
|
}
|
||||||
|
|
||||||
httpRequestWithJSONBody(method: string, url: string, contentType: string, payload?: any): Promise<Response> {
|
httpRequestWithJSONBody(
|
||||||
|
method: string,
|
||||||
|
url: string,
|
||||||
|
contentType: string,
|
||||||
|
additionalHeaders: Record<string, string>,
|
||||||
|
payload?: any
|
||||||
|
): Promise<Response> {
|
||||||
const options: RequestInit = {
|
const options: RequestInit = {
|
||||||
method: method
|
method: method,
|
||||||
|
headers: additionalHeaders
|
||||||
};
|
};
|
||||||
if (payload) {
|
if (payload) {
|
||||||
options.body = JSON.stringify(payload);
|
options.body = JSON.stringify(payload);
|
||||||
@@ -125,13 +145,27 @@ class ApiClient {
|
|||||||
return this.httpRequestWithBinaryBody(options, url, contentType);
|
return this.httpRequestWithBinaryBody(options, url, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpRequestWithTextBody(
|
||||||
|
method: string,
|
||||||
|
url: string,
|
||||||
|
additionalHeaders: Record<string, string> = {},
|
||||||
|
payload: string
|
||||||
|
) {
|
||||||
|
const options: RequestInit = {
|
||||||
|
method: method,
|
||||||
|
headers: additionalHeaders
|
||||||
|
};
|
||||||
|
options.body = payload;
|
||||||
|
return this.httpRequestWithBinaryBody(options, url, "text/plain");
|
||||||
|
}
|
||||||
|
|
||||||
httpRequestWithBinaryBody(options: RequestInit, url: string, contentType?: string) {
|
httpRequestWithBinaryBody(options: RequestInit, url: string, contentType?: string) {
|
||||||
options = applyFetchOptions(options);
|
options = applyFetchOptions(options);
|
||||||
if (contentType) {
|
if (contentType) {
|
||||||
if (!options.headers) {
|
if (!options.headers) {
|
||||||
options.headers = new Headers();
|
options.headers = {};
|
||||||
}
|
}
|
||||||
// @ts-ignore
|
// @ts-ignore We are sure that here we only get headers of type Record<string, string>
|
||||||
options.headers["Content-Type"] = contentType;
|
options.headers["Content-Type"] = contentType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
import React from "react";
|
import React from "react";
|
||||||
import DiffFile from "./DiffFile";
|
import DiffFile from "./DiffFile";
|
||||||
import { DiffObjectProps, File } from "./DiffTypes";
|
import { DiffObjectProps, File } from "./DiffTypes";
|
||||||
|
import Notification from "../Notification";
|
||||||
|
import { WithTranslation, withTranslation } from "react-i18next";
|
||||||
|
|
||||||
type Props = DiffObjectProps & {
|
type Props = WithTranslation &
|
||||||
diff: File[];
|
DiffObjectProps & {
|
||||||
defaultCollapse?: boolean;
|
diff: File[];
|
||||||
};
|
defaultCollapse?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
class Diff extends React.Component<Props> {
|
class Diff extends React.Component<Props> {
|
||||||
static defaultProps: Partial<Props> = {
|
static defaultProps: Partial<Props> = {
|
||||||
@@ -13,15 +16,17 @@ class Diff extends React.Component<Props> {
|
|||||||
};
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { diff, ...fileProps } = this.props;
|
const { diff, t, ...fileProps } = this.props;
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{diff.map((file, index) => (
|
{diff.length === 0 ? (
|
||||||
<DiffFile key={index} file={file} {...fileProps} {...this.props} />
|
<Notification type="info">{t("diff.noDiffFound")}</Notification>
|
||||||
))}
|
) : (
|
||||||
|
diff.map((file, index) => <DiffFile key={index} file={file} {...fileProps} {...this.props} />)
|
||||||
|
)}
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Diff;
|
export default withTranslation("repos")(Diff);
|
||||||
|
|||||||
@@ -43,6 +43,7 @@ class LoadingDiff extends React.Component<Props, State> {
|
|||||||
|
|
||||||
fetchDiff = () => {
|
fetchDiff = () => {
|
||||||
const { url } = this.props;
|
const { url } = this.props;
|
||||||
|
this.setState({loading: true});
|
||||||
apiClient
|
apiClient
|
||||||
.get(url)
|
.get(url)
|
||||||
.then(response => response.text())
|
.then(response => response.text())
|
||||||
|
|||||||
@@ -7,41 +7,6 @@ const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
|||||||
const root = path.resolve(process.cwd(), "scm-ui");
|
const root = path.resolve(process.cwd(), "scm-ui");
|
||||||
|
|
||||||
module.exports = [
|
module.exports = [
|
||||||
{
|
|
||||||
context: root,
|
|
||||||
entry: "./ui-styles/src/scm.scss",
|
|
||||||
module: {
|
|
||||||
rules: [
|
|
||||||
{
|
|
||||||
test: /\.(css|scss|sass)$/i,
|
|
||||||
use: [
|
|
||||||
{
|
|
||||||
loader: MiniCssExtractPlugin.loader
|
|
||||||
},
|
|
||||||
"css-loader",
|
|
||||||
"sass-loader"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
test: /\.(png|svg|jpg|gif|woff2?|eot|ttf)$/,
|
|
||||||
use: ["file-loader"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
plugins: [
|
|
||||||
new MiniCssExtractPlugin({
|
|
||||||
filename: "ui-styles.css",
|
|
||||||
ignoreOrder: false
|
|
||||||
})
|
|
||||||
],
|
|
||||||
optimization: {
|
|
||||||
minimizer: [new OptimizeCSSAssetsPlugin({})]
|
|
||||||
},
|
|
||||||
output: {
|
|
||||||
path: path.join(root, "target", "assets"),
|
|
||||||
filename: "ui-styles.bundle.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
context: root,
|
context: root,
|
||||||
entry: {
|
entry: {
|
||||||
@@ -142,6 +107,41 @@ module.exports = [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
context: root,
|
||||||
|
entry: "./ui-styles/src/scm.scss",
|
||||||
|
module: {
|
||||||
|
rules: [
|
||||||
|
{
|
||||||
|
test: /\.(css|scss|sass)$/i,
|
||||||
|
use: [
|
||||||
|
{
|
||||||
|
loader: MiniCssExtractPlugin.loader
|
||||||
|
},
|
||||||
|
"css-loader",
|
||||||
|
"sass-loader"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
test: /\.(png|svg|jpg|gif|woff2?|eot|ttf)$/,
|
||||||
|
use: ["file-loader"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
plugins: [
|
||||||
|
new MiniCssExtractPlugin({
|
||||||
|
filename: "ui-styles.css",
|
||||||
|
ignoreOrder: false
|
||||||
|
})
|
||||||
|
],
|
||||||
|
optimization: {
|
||||||
|
minimizer: [new OptimizeCSSAssetsPlugin({})]
|
||||||
|
},
|
||||||
|
output: {
|
||||||
|
path: path.join(root, "target", "assets"),
|
||||||
|
filename: "ui-styles.bundle.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
context: path.resolve(root),
|
context: path.resolve(root),
|
||||||
entry: {
|
entry: {
|
||||||
|
|||||||
@@ -809,10 +809,6 @@ form .field:not(.is-grouped) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.modal-card-body div div:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
// cursor
|
// cursor
|
||||||
.has-cursor-pointer {
|
.has-cursor-pointer {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
|
|||||||
@@ -177,7 +177,8 @@
|
|||||||
},
|
},
|
||||||
"diff": {
|
"diff": {
|
||||||
"sideBySide": "Zweispaltig",
|
"sideBySide": "Zweispaltig",
|
||||||
"combined": "Kombiniert"
|
"combined": "Kombiniert",
|
||||||
|
"noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden."
|
||||||
},
|
},
|
||||||
"fileUpload": {
|
"fileUpload": {
|
||||||
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
|
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
|
||||||
|
|||||||
@@ -184,7 +184,8 @@
|
|||||||
"copy": "copied"
|
"copy": "copied"
|
||||||
},
|
},
|
||||||
"sideBySide": "side-by-side",
|
"sideBySide": "side-by-side",
|
||||||
"combined": "combined"
|
"combined": "combined",
|
||||||
|
"noDiffFound": "No Diff between the selected branches found."
|
||||||
},
|
},
|
||||||
"fileUpload": {
|
"fileUpload": {
|
||||||
"clickHere": "Click here to select your file",
|
"clickHere": "Click here to select your file",
|
||||||
|
|||||||
@@ -184,7 +184,8 @@
|
|||||||
"copy": "copiado"
|
"copy": "copiado"
|
||||||
},
|
},
|
||||||
"sideBySide": "dos columnas",
|
"sideBySide": "dos columnas",
|
||||||
"combined": "combinado"
|
"combined": "combinado",
|
||||||
|
"noDiffFound": "No se encontraron diferencias entre las ramas seleccionadas."
|
||||||
},
|
},
|
||||||
"fileUpload": {
|
"fileUpload": {
|
||||||
"clickHere": "Haga click aquí para seleccionar su fichero",
|
"clickHere": "Haga click aquí para seleccionar su fichero",
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { apiClient } from "@scm-manager/ui-components";
|
import { apiClient } from "@scm-manager/ui-components";
|
||||||
|
|
||||||
const waitForRestart = () => {
|
const waitForRestart = () => {
|
||||||
const endTime = Number(new Date()) + 10000;
|
const endTime = Number(new Date()) + 60000;
|
||||||
let started = false;
|
let started = false;
|
||||||
|
|
||||||
const executor = (resolve, reject) => {
|
const executor = (resolve, reject) => {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import { Page } from "@scm-manager/ui-components";
|
|||||||
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
|
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||||
import { createGroup, isCreateGroupPending, getCreateGroupFailure, createGroupReset } from "../modules/groups";
|
import { createGroup, isCreateGroupPending, getCreateGroupFailure, createGroupReset } from "../modules/groups";
|
||||||
import GroupForm from "../components/GroupForm";
|
import GroupForm from "../components/GroupForm";
|
||||||
|
import { apiClient } from "@scm-manager/ui-components/src";
|
||||||
|
|
||||||
type Props = WithTranslation & {
|
type Props = WithTranslation & {
|
||||||
createGroup: (link: string, group: Group, callback?: () => void) => void;
|
createGroup: (link: string, group: Group, callback?: () => void) => void;
|
||||||
@@ -40,7 +41,8 @@ class CreateGroup extends React.Component<Props> {
|
|||||||
|
|
||||||
loadUserAutocompletion = (inputValue: string) => {
|
loadUserAutocompletion = (inputValue: string) => {
|
||||||
const url = this.props.autocompleteLink + "?q=";
|
const url = this.props.autocompleteLink + "?q=";
|
||||||
return fetch(url + inputValue)
|
return apiClient
|
||||||
|
.get(url + inputValue)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
return json.map(element => {
|
return json.map(element => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { 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";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
group: Group;
|
group: Group;
|
||||||
@@ -36,7 +37,8 @@ class EditGroup extends React.Component<Props> {
|
|||||||
|
|
||||||
loadUserAutocompletion = (inputValue: string) => {
|
loadUserAutocompletion = (inputValue: string) => {
|
||||||
const url = this.props.autocompleteLink + "?q=";
|
const url = this.props.autocompleteLink + "?q=";
|
||||||
return fetch(url + inputValue)
|
return apiClient
|
||||||
|
.get(url + inputValue)
|
||||||
.then(response => response.json())
|
.then(response => response.json())
|
||||||
.then(json => {
|
.then(json => {
|
||||||
return json.map(element => {
|
return json.map(element => {
|
||||||
|
|||||||
@@ -49,13 +49,22 @@ class Groups extends React.Component<Props> {
|
|||||||
componentDidUpdate = (prevProps: Props) => {
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
const { loading, list, page, groupLink, location, fetchGroupsByPage } = this.props;
|
const { loading, list, page, groupLink, location, fetchGroupsByPage } = this.props;
|
||||||
if (list && page && !loading) {
|
if (list && page && !loading) {
|
||||||
const statePage: number = list.page + 1;
|
const statePage: number = this.resolveStatePage();
|
||||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
fetchGroupsByPage(groupLink, page, urls.getQueryStringFromLocation(location));
|
fetchGroupsByPage(groupLink, page, urls.getQueryStringFromLocation(location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resolveStatePage = () => {
|
||||||
|
const { list } = this.props;
|
||||||
|
if (list.page) {
|
||||||
|
return list.page + 1;
|
||||||
|
}
|
||||||
|
// set page to 1 if undefined, because if groups couldn't be fetched it would lead to an fetch-loop otherwise
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { groups, loading, error, canAddGroups, t } = this.props;
|
const { groups, loading, error, canAddGroups, t } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ const mapDispatchToProps = dispatch => {
|
|||||||
const mapStateToProps = (state, ownProps) => {
|
const mapStateToProps = (state, ownProps) => {
|
||||||
const { repository } = ownProps;
|
const { repository } = ownProps;
|
||||||
const loading = isFetchBranchesPending(state, repository) || isCreateBranchPending(state, repository);
|
const loading = isFetchBranchesPending(state, repository) || isCreateBranchPending(state, repository);
|
||||||
const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state);
|
const error = getFetchBranchesFailure(state, repository) || getCreateBranchFailure(state, repository);
|
||||||
const branches = getBranches(state, repository);
|
const branches = getBranches(state, repository);
|
||||||
const createBranchesLink = getBranchCreateLink(state, repository);
|
const createBranchesLink = getBranchCreateLink(state, repository);
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -482,14 +482,14 @@ describe("branches", () => {
|
|||||||
it("should return error when create branch did fail", () => {
|
it("should return error when create branch did fail", () => {
|
||||||
const state = {
|
const state = {
|
||||||
failure: {
|
failure: {
|
||||||
[CREATE_BRANCH]: error
|
[CREATE_BRANCH + `/${repository.namespace}/${repository.name}`]: error
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
expect(getCreateBranchFailure(state)).toEqual(error);
|
expect(getCreateBranchFailure(state, repository)).toEqual(error);
|
||||||
});
|
});
|
||||||
|
|
||||||
it("should return undefined when create branch did not fail", () => {
|
it("should return undefined when create branch did not fail", () => {
|
||||||
expect(getCreateBranchFailure({})).toBe(undefined);
|
expect(getCreateBranchFailure({}, repository)).toBe(undefined);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -186,8 +186,8 @@ export function isCreateBranchPending(state: object, repository: Repository) {
|
|||||||
return isPending(state, CREATE_BRANCH, createKey(repository));
|
return isPending(state, CREATE_BRANCH, createKey(repository));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function getCreateBranchFailure(state: object) {
|
export function getCreateBranchFailure(state: object, repository: Repository) {
|
||||||
return getFailure(state, CREATE_BRANCH);
|
return getFailure(state, CREATE_BRANCH, createKey(repository));
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createBranchPending(repository: Repository): Action {
|
export function createBranchPending(repository: Repository): Action {
|
||||||
|
|||||||
@@ -49,13 +49,22 @@ class Users extends React.Component<Props> {
|
|||||||
componentDidUpdate = (prevProps: Props) => {
|
componentDidUpdate = (prevProps: Props) => {
|
||||||
const { loading, list, page, usersLink, location, fetchUsersByPage } = this.props;
|
const { loading, list, page, usersLink, location, fetchUsersByPage } = this.props;
|
||||||
if (list && page && !loading) {
|
if (list && page && !loading) {
|
||||||
const statePage: number = list.page + 1;
|
const statePage: number = this.resolveStatePage();
|
||||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||||
fetchUsersByPage(usersLink, page, urls.getQueryStringFromLocation(location));
|
fetchUsersByPage(usersLink, page, urls.getQueryStringFromLocation(location));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
resolveStatePage = (props = this.props) => {
|
||||||
|
const { list } = props;
|
||||||
|
if (list.page) {
|
||||||
|
return list.page + 1;
|
||||||
|
}
|
||||||
|
// set page to 1 if undefined, because if users couldn't be fetched it would lead to a fetch-loop otherwise
|
||||||
|
return 1;
|
||||||
|
};
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const { users, loading, error, canAddUsers, t } = this.props;
|
const { users, loading, error, canAddUsers, t } = this.props;
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
|
|||||||
|
|
||||||
@Mapping(target = "creationDate", ignore = true)
|
@Mapping(target = "creationDate", ignore = true)
|
||||||
@Mapping(target = "id", ignore = true)
|
@Mapping(target = "id", ignore = true)
|
||||||
@Mapping(target = "publicReadable", ignore = true)
|
|
||||||
@Mapping(target = "healthCheckFailures", ignore = true)
|
@Mapping(target = "healthCheckFailures", ignore = true)
|
||||||
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
||||||
|
|
||||||
|
|||||||
@@ -61,7 +61,7 @@ public class SetupContextListener implements ServletContextListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (isFirstStart()) {
|
if (shouldCreateAdminAccount()) {
|
||||||
createAdminAccount();
|
createAdminAccount();
|
||||||
}
|
}
|
||||||
if (anonymousUserRequiredButNotExists()) {
|
if (anonymousUserRequiredButNotExists()) {
|
||||||
@@ -73,8 +73,12 @@ public class SetupContextListener implements ServletContextListener {
|
|||||||
return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains(SCMContext.USER_ANONYMOUS);
|
return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains(SCMContext.USER_ANONYMOUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isFirstStart() {
|
private boolean shouldCreateAdminAccount() {
|
||||||
return userManager.getAll().isEmpty();
|
return userManager.getAll().isEmpty() || onlyAnonymousUserExists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean onlyAnonymousUserExists() {
|
||||||
|
return userManager.getAll().size() == 1 && userManager.contains(SCMContext.USER_ANONYMOUS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void createAdminAccount() {
|
private void createAdminAccount() {
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ public class DefaultPluginManager implements PluginManager {
|
|||||||
|
|
||||||
if (!pendingInstallations.isEmpty()) {
|
if (!pendingInstallations.isEmpty()) {
|
||||||
if (restartAfterInstallation) {
|
if (restartAfterInstallation) {
|
||||||
restart("plugin installation");
|
triggerRestart("plugin installation");
|
||||||
} else {
|
} else {
|
||||||
pendingInstallQueue.addAll(pendingInstallations);
|
pendingInstallQueue.addAll(pendingInstallations);
|
||||||
updateMayUninstallFlag();
|
updateMayUninstallFlag();
|
||||||
@@ -205,7 +205,7 @@ public class DefaultPluginManager implements PluginManager {
|
|||||||
markForUninstall(installed);
|
markForUninstall(installed);
|
||||||
|
|
||||||
if (restartAfterInstallation) {
|
if (restartAfterInstallation) {
|
||||||
restart("plugin installation");
|
triggerRestart("plugin installation");
|
||||||
} else {
|
} else {
|
||||||
updateMayUninstallFlag();
|
updateMayUninstallFlag();
|
||||||
}
|
}
|
||||||
@@ -238,12 +238,19 @@ public class DefaultPluginManager implements PluginManager {
|
|||||||
public void executePendingAndRestart() {
|
public void executePendingAndRestart() {
|
||||||
PluginPermissions.manage().check();
|
PluginPermissions.manage().check();
|
||||||
if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) {
|
if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) {
|
||||||
restart("execute pending plugin changes");
|
triggerRestart("execute pending plugin changes");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void restart(String cause) {
|
@VisibleForTesting
|
||||||
eventBus.post(new RestartEvent(PluginManager.class, cause));
|
void triggerRestart(String cause) {
|
||||||
|
new Thread(() -> {
|
||||||
|
try {
|
||||||
|
Thread.sleep(200);
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
}
|
||||||
|
eventBus.post(new RestartEvent(PluginManager.class, cause));
|
||||||
|
}).start();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void cancelPending(List<PendingPluginInstallation> pendingInstallations) {
|
private void cancelPending(List<PendingPluginInstallation> pendingInstallations) {
|
||||||
|
|||||||
@@ -168,8 +168,7 @@ public class AuthorizationChangedEventProducer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
||||||
return repository.isPublicReadable() != beforeModification.isPublicReadable()
|
return !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
||||||
|| !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void fireEventForEveryUser() {
|
private void fireEventForEveryUser() {
|
||||||
|
|||||||
@@ -90,7 +90,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
|
|||||||
repository.setCreationDate(oldRepository.creationDate);
|
repository.setCreationDate(oldRepository.creationDate);
|
||||||
repository.setHealthCheckFailures(oldRepository.healthCheckFailures);
|
repository.setHealthCheckFailures(oldRepository.healthCheckFailures);
|
||||||
repository.setLastModified(oldRepository.lastModified);
|
repository.setLastModified(oldRepository.lastModified);
|
||||||
repository.setPublicReadable(oldRepository.publicReadable);
|
|
||||||
return repository;
|
return repository;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -149,8 +148,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
|
|||||||
private String name;
|
private String name;
|
||||||
@XmlElement(name = "permission")
|
@XmlElement(name = "permission")
|
||||||
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||||
@XmlElement(name = "public")
|
|
||||||
private boolean publicReadable = false;
|
|
||||||
private boolean archived = false;
|
private boolean archived = false;
|
||||||
private String type;
|
private String type;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
import sonia.scm.migration.UpdateStep;
|
||||||
|
import sonia.scm.plugin.Extension;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
|
import sonia.scm.version.Version;
|
||||||
|
|
||||||
|
import javax.inject.Inject;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
|
||||||
|
import static sonia.scm.version.Version.parse;
|
||||||
|
|
||||||
|
@Extension
|
||||||
|
public class PublicFlagUpdateStep implements UpdateStep {
|
||||||
|
|
||||||
|
private static final Logger LOG = LoggerFactory.getLogger(PublicFlagUpdateStep.class);
|
||||||
|
|
||||||
|
private static final String V1_REPOSITORY_BACKUP_FILENAME = "repositories.xml.v1.backup";
|
||||||
|
|
||||||
|
private final SCMContextProvider contextProvider;
|
||||||
|
private final XmlUserDAO userDAO;
|
||||||
|
private final XmlRepositoryDAO repositoryDAO;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
public PublicFlagUpdateStep(SCMContextProvider contextProvider, XmlUserDAO userDAO, XmlRepositoryDAO repositoryDAO) {
|
||||||
|
this.contextProvider = contextProvider;
|
||||||
|
this.userDAO = userDAO;
|
||||||
|
this.repositoryDAO = repositoryDAO;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void doUpdate() throws JAXBException {
|
||||||
|
LOG.info("Migrating public flags of repositories as RepositoryRolePermission 'READ' for user '_anonymous'");
|
||||||
|
V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_BACKUP_FILENAME).ifPresent(
|
||||||
|
v1RepositoryDatabase -> {
|
||||||
|
createNewAnonymousUserIfNotExists();
|
||||||
|
deleteOldAnonymousUserIfAvailable();
|
||||||
|
addRepositoryReadPermissionForAnonymousUser(v1RepositoryDatabase);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Version getTargetVersion() {
|
||||||
|
return parse("2.0.3");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getAffectedDataType() {
|
||||||
|
return "sonia.scm.repository.xml";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRepositoryReadPermissionForAnonymousUser(V1RepositoryHelper.V1RepositoryDatabase v1RepositoryDatabase) {
|
||||||
|
User v2AnonymousUser = userDAO.get(SCMContext.USER_ANONYMOUS);
|
||||||
|
v1RepositoryDatabase.repositoryList.repositories
|
||||||
|
.stream()
|
||||||
|
.filter(V1Repository::isPublic)
|
||||||
|
.forEach(v1Repository -> {
|
||||||
|
Repository v2Repository = repositoryDAO.get(v1Repository.getId());
|
||||||
|
LOG.info(String.format("Add RepositoryRole 'READ' to _anonymous user for repository: %s - %s/%s", v2Repository.getId(), v2Repository.getNamespace(), v2Repository.getName()));
|
||||||
|
v2Repository.addPermission(new RepositoryPermission(v2AnonymousUser.getId(), "READ", false));
|
||||||
|
repositoryDAO.modify(v2Repository);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void createNewAnonymousUserIfNotExists() {
|
||||||
|
if (!userExists(SCMContext.USER_ANONYMOUS)) {
|
||||||
|
LOG.info("Create new _anonymous user");
|
||||||
|
userDAO.add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteOldAnonymousUserIfAvailable() {
|
||||||
|
String oldAnonymous = "anonymous";
|
||||||
|
if (userExists(oldAnonymous)) {
|
||||||
|
User anonymousUser = userDAO.get(oldAnonymous);
|
||||||
|
LOG.info("Delete obsolete anonymous user");
|
||||||
|
userDAO.delete(anonymousUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean userExists(String username) {
|
||||||
|
return userDAO
|
||||||
|
.getAll()
|
||||||
|
.stream()
|
||||||
|
.anyMatch(user -> user.getName().equals(username));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import sonia.scm.update.V1Properties;
|
|||||||
|
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -16,6 +17,7 @@ public class V1Repository {
|
|||||||
private String description;
|
private String description;
|
||||||
private String id;
|
private String id;
|
||||||
private String name;
|
private String name;
|
||||||
|
@XmlElement(name="public")
|
||||||
private boolean isPublic;
|
private boolean isPublic;
|
||||||
private boolean archived;
|
private boolean archived;
|
||||||
private String type;
|
private String type;
|
||||||
|
|||||||
@@ -0,0 +1,57 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import sonia.scm.SCMContextProvider;
|
||||||
|
import sonia.scm.store.StoreConstants;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBContext;
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessType;
|
||||||
|
import javax.xml.bind.annotation.XmlAccessorType;
|
||||||
|
import javax.xml.bind.annotation.XmlElement;
|
||||||
|
import javax.xml.bind.annotation.XmlRootElement;
|
||||||
|
import java.io.File;
|
||||||
|
import java.nio.file.Paths;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static java.util.Optional.empty;
|
||||||
|
import static java.util.Optional.of;
|
||||||
|
|
||||||
|
class V1RepositoryHelper {
|
||||||
|
|
||||||
|
static Optional<File> resolveV1File(SCMContextProvider contextProvider, String filename) {
|
||||||
|
File v1XmlFile = contextProvider.resolve(Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve(filename)).toFile();
|
||||||
|
if (v1XmlFile.exists()) {
|
||||||
|
return Optional.of(v1XmlFile);
|
||||||
|
}
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static Optional<V1RepositoryDatabase> readV1Database(SCMContextProvider contextProvider, String filename) throws JAXBException {
|
||||||
|
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
||||||
|
Optional<File> file = resolveV1File(contextProvider, filename);
|
||||||
|
if (file.isPresent()) {
|
||||||
|
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(file.get());
|
||||||
|
if (unmarshal instanceof V1RepositoryDatabase) {
|
||||||
|
return of((V1RepositoryDatabase) unmarshal);
|
||||||
|
} else {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class RepositoryList {
|
||||||
|
@XmlElement(name = "repository")
|
||||||
|
List<V1Repository> repositories;
|
||||||
|
}
|
||||||
|
|
||||||
|
@XmlRootElement(name = "repository-db")
|
||||||
|
@XmlAccessorType(XmlAccessType.FIELD)
|
||||||
|
static class V1RepositoryDatabase {
|
||||||
|
long creationTime;
|
||||||
|
Long lastModified;
|
||||||
|
@XmlElement(name = "repositories")
|
||||||
|
RepositoryList repositoryList;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -17,26 +17,18 @@ import sonia.scm.update.V1Properties;
|
|||||||
import sonia.scm.version.Version;
|
import sonia.scm.version.Version;
|
||||||
|
|
||||||
import javax.inject.Inject;
|
import javax.inject.Inject;
|
||||||
import javax.xml.bind.JAXBContext;
|
|
||||||
import javax.xml.bind.JAXBException;
|
import javax.xml.bind.JAXBException;
|
||||||
import javax.xml.bind.annotation.XmlAccessType;
|
|
||||||
import javax.xml.bind.annotation.XmlAccessorType;
|
|
||||||
import javax.xml.bind.annotation.XmlElement;
|
|
||||||
import javax.xml.bind.annotation.XmlRootElement;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
import static java.util.Collections.emptyList;
|
import static java.util.Collections.emptyList;
|
||||||
import static java.util.Optional.empty;
|
|
||||||
import static java.util.Optional.of;
|
|
||||||
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
|
import static sonia.scm.update.V1PropertyReader.REPOSITORY_PROPERTY_READER;
|
||||||
|
import static sonia.scm.update.repository.V1RepositoryHelper.resolveV1File;
|
||||||
import static sonia.scm.version.Version.parse;
|
import static sonia.scm.version.Version.parse;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,6 +51,8 @@ import static sonia.scm.version.Version.parse;
|
|||||||
@Extension
|
@Extension
|
||||||
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||||
|
|
||||||
|
private final String V1_REPOSITORY_FILENAME = "repositories" + StoreConstants.FILE_EXTENSION;
|
||||||
|
|
||||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||||
|
|
||||||
private final SCMContextProvider contextProvider;
|
private final SCMContextProvider contextProvider;
|
||||||
@@ -97,12 +91,11 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doUpdate() throws JAXBException {
|
public void doUpdate() throws JAXBException {
|
||||||
if (!resolveV1File().exists()) {
|
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
|
||||||
LOG.info("no v1 repositories database file found");
|
LOG.info("no v1 repositories database file found");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME).ifPresent(
|
||||||
readV1Database(jaxbContext).ifPresent(
|
|
||||||
v1Database -> {
|
v1Database -> {
|
||||||
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
|
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
|
||||||
v1Database.repositoryList.repositories.forEach(this::update);
|
v1Database.repositoryList.repositories.forEach(this::update);
|
||||||
@@ -112,13 +105,12 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public List<V1Repository> getRepositoriesWithoutMigrationStrategies() {
|
public List<V1Repository> getRepositoriesWithoutMigrationStrategies() {
|
||||||
if (!resolveV1File().exists()) {
|
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
|
||||||
LOG.info("no v1 repositories database file found");
|
LOG.info("no v1 repositories database file found");
|
||||||
return emptyList();
|
return emptyList();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class);
|
return V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME)
|
||||||
return readV1Database(jaxbContext)
|
|
||||||
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
||||||
.orElse(Stream.empty())
|
.orElse(Stream.empty())
|
||||||
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
||||||
@@ -196,33 +188,4 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
|||||||
return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission());
|
return new RepositoryPermission(v1Permission.getName(), v1Permission.getType(), v1Permission.isGroupPermission());
|
||||||
}
|
}
|
||||||
|
|
||||||
private Optional<V1RepositoryDatabase> readV1Database(JAXBContext jaxbContext) throws JAXBException {
|
|
||||||
Object unmarshal = jaxbContext.createUnmarshaller().unmarshal(resolveV1File());
|
|
||||||
if (unmarshal instanceof V1RepositoryDatabase) {
|
|
||||||
return of((V1RepositoryDatabase) unmarshal);
|
|
||||||
} else {
|
|
||||||
return empty();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private File resolveV1File() {
|
|
||||||
return contextProvider
|
|
||||||
.resolve(
|
|
||||||
Paths.get(StoreConstants.CONFIG_DIRECTORY_NAME).resolve("repositories" + StoreConstants.FILE_EXTENSION)
|
|
||||||
).toFile();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static class RepositoryList {
|
|
||||||
@XmlElement(name = "repository")
|
|
||||||
private List<V1Repository> repositories;
|
|
||||||
}
|
|
||||||
|
|
||||||
@XmlRootElement(name = "repository-db")
|
|
||||||
@XmlAccessorType(XmlAccessType.FIELD)
|
|
||||||
private static class V1RepositoryDatabase {
|
|
||||||
private long creationTime;
|
|
||||||
private Long lastModified;
|
|
||||||
@XmlElement(name = "repositories")
|
|
||||||
private RepositoryList repositoryList;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -198,9 +198,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"namespaceStrategies": {
|
"namespaceStrategies": {
|
||||||
"UsernameNamespaceStrategy": "Username",
|
"UsernameNamespaceStrategy": "Username",
|
||||||
"CustomNamespaceStrategy": "Custom",
|
"CustomNamespaceStrategy": "Custom",
|
||||||
"CurrentYearNamespaceStrategy": "Current year",
|
"CurrentYearNamespaceStrategy": "Current year",
|
||||||
"RepositoryTypeNamespaceStrategy": "Repository type"
|
"RepositoryTypeNamespaceStrategy": "Repository type"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -94,7 +94,6 @@ public class RepositorySimplePermissionITCase
|
|||||||
|
|
||||||
repository.setName("test-repo");
|
repository.setName("test-repo");
|
||||||
repository.setType("git");
|
repository.setType("git");
|
||||||
// repository.setPublicReadable(false);
|
|
||||||
|
|
||||||
ScmClient client = createAdminClient();
|
ScmClient client = createAdminClient();
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,19 @@ class SetupContextListenerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldCreateAdminAccountAndAssignPermissions() {
|
void shouldCreateAdminAccountIfNoUserExistsAndAssignPermissions() {
|
||||||
|
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
|
||||||
|
|
||||||
|
setupContextListener.contextInitialized(null);
|
||||||
|
|
||||||
|
verifyAdminCreated();
|
||||||
|
verifyAdminPermissionsAssigned();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateAdminAccountIfOnlyAnonymousUserExistsAndAssignPermissions() {
|
||||||
|
when(userManager.getAll()).thenReturn(Lists.newArrayList(SCMContext.ANONYMOUS));
|
||||||
|
when(userManager.contains(SCMContext.USER_ANONYMOUS)).thenReturn(true);
|
||||||
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
|
when(passwordService.encryptPassword("scmadmin")).thenReturn("secret");
|
||||||
|
|
||||||
setupContextListener.contextInitialized(null);
|
setupContextListener.contextInitialized(null);
|
||||||
|
|||||||
@@ -12,13 +12,10 @@ import org.junit.jupiter.api.Test;
|
|||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
import org.junitpioneer.jupiter.TempDirectory;
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.InjectMocks;
|
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import sonia.scm.NotFoundException;
|
import sonia.scm.NotFoundException;
|
||||||
import sonia.scm.ScmConstraintViolationException;
|
import sonia.scm.ScmConstraintViolationException;
|
||||||
import sonia.scm.event.ScmEventBus;
|
|
||||||
import sonia.scm.lifecycle.RestartEvent;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
@@ -48,9 +45,6 @@ import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
|||||||
@ExtendWith(TempDirectory.class)
|
@ExtendWith(TempDirectory.class)
|
||||||
class DefaultPluginManagerTest {
|
class DefaultPluginManagerTest {
|
||||||
|
|
||||||
@Mock
|
|
||||||
private ScmEventBus eventBus;
|
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private PluginLoader loader;
|
private PluginLoader loader;
|
||||||
|
|
||||||
@@ -60,12 +54,13 @@ class DefaultPluginManagerTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private PluginInstaller installer;
|
private PluginInstaller installer;
|
||||||
|
|
||||||
@InjectMocks
|
|
||||||
private DefaultPluginManager manager;
|
private DefaultPluginManager manager;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private Subject subject;
|
private Subject subject;
|
||||||
|
|
||||||
|
private boolean restartTriggered = false;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void mockInstaller() {
|
void mockInstaller() {
|
||||||
lenient().when(installer.install(any())).then(ic -> {
|
lenient().when(installer.install(any())).then(ic -> {
|
||||||
@@ -74,6 +69,16 @@ class DefaultPluginManagerTest {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void createPluginManagerToTestWithCapturedRestart() {
|
||||||
|
manager = new DefaultPluginManager(null, loader, center, installer) { // event bus is only used in restart and this is replaced here
|
||||||
|
@Override
|
||||||
|
void triggerRestart(String cause) {
|
||||||
|
restartTriggered = true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
class WithAdminPermissions {
|
class WithAdminPermissions {
|
||||||
|
|
||||||
@@ -180,7 +185,7 @@ class DefaultPluginManagerTest {
|
|||||||
manager.install("scm-git-plugin", false);
|
manager.install("scm-git-plugin", false);
|
||||||
|
|
||||||
verify(installer).install(git);
|
verify(installer).install(git);
|
||||||
verify(eventBus, never()).post(any());
|
assertThat(restartTriggered).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -258,7 +263,7 @@ class DefaultPluginManagerTest {
|
|||||||
manager.install("scm-git-plugin", true);
|
manager.install("scm-git-plugin", true);
|
||||||
|
|
||||||
verify(installer).install(git);
|
verify(installer).install(git);
|
||||||
verify(eventBus).post(any(RestartEvent.class));
|
assertThat(restartTriggered).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -267,7 +272,7 @@ class DefaultPluginManagerTest {
|
|||||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
|
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
|
||||||
|
|
||||||
manager.install("scm-git-plugin", true);
|
manager.install("scm-git-plugin", true);
|
||||||
verify(eventBus, never()).post(any());
|
assertThat(restartTriggered).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -289,14 +294,14 @@ class DefaultPluginManagerTest {
|
|||||||
manager.install("scm-review-plugin", false);
|
manager.install("scm-review-plugin", false);
|
||||||
manager.executePendingAndRestart();
|
manager.executePendingAndRestart();
|
||||||
|
|
||||||
verify(eventBus).post(any(RestartEvent.class));
|
assertThat(restartTriggered).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldNotSendRestartEventWithoutPendingPlugins() {
|
void shouldNotSendRestartEventWithoutPendingPlugins() {
|
||||||
manager.executePendingAndRestart();
|
manager.executePendingAndRestart();
|
||||||
|
|
||||||
verify(eventBus, never()).post(any());
|
assertThat(restartTriggered).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -447,7 +452,7 @@ class DefaultPluginManagerTest {
|
|||||||
|
|
||||||
manager.executePendingAndRestart();
|
manager.executePendingAndRestart();
|
||||||
|
|
||||||
verify(eventBus).post(any(RestartEvent.class));
|
assertThat(restartTriggered).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package sonia.scm.update.repository;
|
||||||
|
|
||||||
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junitpioneer.jupiter.TempDirectory;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.Captor;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
import sonia.scm.SCMContext;
|
||||||
|
import sonia.scm.repository.Repository;
|
||||||
|
import sonia.scm.repository.RepositoryPermission;
|
||||||
|
import sonia.scm.repository.RepositoryRolePermissions;
|
||||||
|
import sonia.scm.repository.RepositoryTestData;
|
||||||
|
import sonia.scm.repository.xml.XmlRepositoryDAO;
|
||||||
|
import sonia.scm.update.UpdateStepTestUtil;
|
||||||
|
import sonia.scm.user.User;
|
||||||
|
import sonia.scm.user.xml.XmlUserDAO;
|
||||||
|
|
||||||
|
import javax.xml.bind.JAXBException;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.junitpioneer.jupiter.TempDirectory.TempDir;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.doReturn;
|
||||||
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
|
@ExtendWith(TempDirectory.class)
|
||||||
|
class PublicFlagUpdateStepTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
XmlUserDAO userDAO;
|
||||||
|
@Mock
|
||||||
|
XmlRepositoryDAO repositoryDAO;
|
||||||
|
@Captor
|
||||||
|
ArgumentCaptor<Repository> repositoryCaptor;
|
||||||
|
|
||||||
|
private UpdateStepTestUtil testUtil;
|
||||||
|
private PublicFlagUpdateStep updateStep;
|
||||||
|
private Repository REPOSITORY = RepositoryTestData.createHeartOfGold();
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void mockScmHome(@TempDir Path tempDir) throws IOException {
|
||||||
|
testUtil = new UpdateStepTestUtil(tempDir);
|
||||||
|
updateStep = new PublicFlagUpdateStep(testUtil.getContextProvider(), userDAO, repositoryDAO);
|
||||||
|
|
||||||
|
//prepare backup xml
|
||||||
|
V1RepositoryFileSystem.createV1Home(tempDir);
|
||||||
|
Files.move(tempDir.resolve("config").resolve("repositories.xml"), tempDir.resolve("config").resolve("repositories.xml.v1.backup"));
|
||||||
|
when(repositoryDAO.get((String) any())).thenReturn(REPOSITORY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldDeleteOldAnonymousUserIfExists() throws JAXBException {
|
||||||
|
User anonymous = new User("anonymous");
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(anonymous));
|
||||||
|
doReturn(anonymous).when(userDAO).get("anonymous");
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO).delete(anonymous);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotTryToDeleteOldAnonymousUserIfNotExists() throws JAXBException {
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.emptyList());
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO, never()).delete(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldCreateNewAnonymousUserIfNotExists() throws JAXBException {
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(new User("trillian")));
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO).add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldNotCreateNewAnonymousUserIfAlreadyExists() throws JAXBException {
|
||||||
|
doReturn(SCMContext.ANONYMOUS).when(userDAO).get(SCMContext.USER_ANONYMOUS);
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.singleton(new User("_anonymous")));
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(userDAO, never()).add(SCMContext.ANONYMOUS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldMigratePublicFlagToAnonymousRepositoryPermission() throws JAXBException {
|
||||||
|
when(userDAO.getAll()).thenReturn(Collections.emptyList());
|
||||||
|
when(userDAO.get("_anonymous")).thenReturn(SCMContext.ANONYMOUS);
|
||||||
|
|
||||||
|
updateStep.doUpdate();
|
||||||
|
|
||||||
|
verify(repositoryDAO, times(2)).modify(repositoryCaptor.capture());
|
||||||
|
|
||||||
|
RepositoryPermission migratedRepositoryPermission = repositoryCaptor.getValue().getPermissions().iterator().next();
|
||||||
|
assertThat(migratedRepositoryPermission.getName()).isEqualTo(SCMContext.USER_ANONYMOUS);
|
||||||
|
assertThat(migratedRepositoryPermission.getRole()).isEqualTo("READ");
|
||||||
|
assertThat(migratedRepositoryPermission.isGroupPermission()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
||||||
Binary file not shown.
Reference in New Issue
Block a user