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,
|
||||
"SCM Anonymous",
|
||||
"scm-anonymous@scm-manager.com");
|
||||
"scm-anonymous@scm-manager.org");
|
||||
|
||||
/** Singleton instance of {@link SCMContextProvider} */
|
||||
private static volatile SCMContextProvider provider;
|
||||
|
||||
@@ -25,7 +25,7 @@ public class AvailablePlugin implements Plugin {
|
||||
return pending;
|
||||
}
|
||||
|
||||
public AvailablePlugin install() {
|
||||
AvailablePlugin install() {
|
||||
Preconditions.checkState(!pending, "installation is already pending");
|
||||
return new AvailablePlugin(pluginDescriptor, true);
|
||||
}
|
||||
|
||||
@@ -113,7 +113,6 @@ public abstract class AbstactImportHandler implements AdvancedImportHandler
|
||||
Repository repository = new Repository();
|
||||
|
||||
repository.setName(repositoryName);
|
||||
repository.setPublicReadable(false);
|
||||
repository.setType(getTypeName());
|
||||
|
||||
return repository;
|
||||
|
||||
@@ -83,8 +83,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
private String name;
|
||||
@XmlElement(name = "permission")
|
||||
private Set<RepositoryPermission> permissions = new HashSet<>();
|
||||
@XmlElement(name = "public")
|
||||
private boolean publicReadable = false;
|
||||
private String type;
|
||||
|
||||
|
||||
@@ -225,15 +223,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
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.
|
||||
* <ul>
|
||||
@@ -292,10 +281,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
return this.permissions.remove(permission);
|
||||
}
|
||||
|
||||
public void setPublicReadable(boolean publicReadable) {
|
||||
this.publicReadable = publicReadable;
|
||||
}
|
||||
|
||||
public void setType(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
@@ -332,7 +317,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
repository.setLastModified(lastModified);
|
||||
repository.setDescription(description);
|
||||
repository.setPermissions(permissions);
|
||||
repository.setPublicReadable(publicReadable);
|
||||
|
||||
// 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(contact, other.contact)
|
||||
&& Objects.equal(description, other.description)
|
||||
&& Objects.equal(publicReadable, other.publicReadable)
|
||||
&& Objects.equal(permissions, other.permissions)
|
||||
&& Objects.equal(type, other.type)
|
||||
&& Objects.equal(creationDate, other.creationDate)
|
||||
@@ -371,7 +354,7 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
|
||||
@Override
|
||||
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,
|
||||
healthCheckFailures);
|
||||
}
|
||||
@@ -384,7 +367,6 @@ public class Repository extends BasicPropertiesAware implements ModelObject, Per
|
||||
.add("name", name)
|
||||
.add("contact", contact)
|
||||
.add("description", description)
|
||||
.add("publicReadable", publicReadable)
|
||||
.add("permissions", permissions)
|
||||
.add("type", type)
|
||||
.add("lastModified", lastModified)
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package sonia.scm.it;
|
||||
|
||||
import org.apache.http.HttpStatus;
|
||||
import org.assertj.core.api.AbstractCharSequenceAssert;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.junit.Before;
|
||||
import org.junit.Ignore;
|
||||
@@ -28,6 +29,7 @@ import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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_USERNAME;
|
||||
|
||||
@@ -94,8 +96,7 @@ public class DiffITCase {
|
||||
String gitDiff = getDiff(RepositoryUtil.createAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt", "content of a"), gitRepositoryResponse);
|
||||
|
||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(expected);
|
||||
assertDiffsAreEqual(svnDiff, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -107,8 +108,7 @@ public class DiffITCase {
|
||||
String gitDiff = getDiff(RepositoryUtil.removeAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, "a.txt"), gitRepositoryResponse);
|
||||
|
||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(expected);
|
||||
assertDiffsAreEqual(svnDiff, expected);
|
||||
}
|
||||
|
||||
@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 expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(expected);
|
||||
assertDiffsAreEqual(svnDiff, expected);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -161,21 +160,17 @@ public class DiffITCase {
|
||||
String fileContent = getFileContent("/diff/largefile/original/SvnDiffGenerator_forTest");
|
||||
String svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||
String gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
||||
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||
|
||||
fileContent = getFileContent("/diff/largefile/modified/v1/SvnDiffGenerator_forTest");
|
||||
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
||||
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||
|
||||
fileContent = getFileContent("/diff/largefile/modified/v2/SvnDiffGenerator_forTest");
|
||||
svnDiff = getDiff(RepositoryUtil.updateAndCommitFile(svnRepositoryClient, ADMIN_USERNAME, fileName, fileContent), svnRepositoryResponse);
|
||||
gitDiff = getDiff(RepositoryUtil.updateAndCommitFile(gitRepositoryClient, ADMIN_USERNAME, fileName, fileContent), gitRepositoryResponse);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
||||
|
||||
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -196,8 +191,7 @@ public class DiffITCase {
|
||||
Changeset commit1 = RepositoryUtil.addFileAndCommit(gitRepositoryClient, fileName, ADMIN_USERNAME, "");
|
||||
String svnDiff = getDiff(commit, svnRepositoryResponse);
|
||||
String gitDiff = getDiff(commit1, gitRepositoryResponse);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(getGitDiffWithoutIndexLine(gitDiff));
|
||||
assertDiffsAreEqual(svnDiff, getGitDiffWithoutIndexLine(gitDiff));
|
||||
|
||||
}
|
||||
|
||||
@@ -218,8 +212,7 @@ public class DiffITCase {
|
||||
String gitDiff = getDiff(RepositoryUtil.addFileAndCommit(gitRepositoryClient, newFileName, ADMIN_USERNAME, "renamed file"), gitRepositoryResponse);
|
||||
|
||||
String expected = getGitDiffWithoutIndexLine(gitDiff);
|
||||
assertThat(svnDiff)
|
||||
.isEqualTo(expected);
|
||||
assertDiffsAreEqual(svnDiff, expected);
|
||||
}
|
||||
|
||||
public String getFileContent(String name) throws URISyntaxException, IOException {
|
||||
@@ -242,6 +235,12 @@ public class DiffITCase {
|
||||
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) {
|
||||
return svnRepositoryResponse.requestChangesets()
|
||||
.requestDiffInGitFormat(svnChangeset.getId())
|
||||
|
||||
@@ -110,6 +110,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand {
|
||||
return;
|
||||
}
|
||||
numberOfPotentialBeginning = -1;
|
||||
inPotentialQuotedLine = false;
|
||||
}
|
||||
|
||||
if (inPotentialQuotedLine && i == '"') {
|
||||
|
||||
@@ -16,7 +16,7 @@ public class GitDiffCommand_DequoteOutputStreamTest {
|
||||
"--- /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" +
|
||||
"@@ -0,0 +1 @@\n" +
|
||||
"+rthms";
|
||||
"+String s = \"quotes shall be kept\";";
|
||||
|
||||
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
|
||||
GitDiffCommand.DequoteOutputStream stream = new GitDiffCommand.DequoteOutputStream(buffer);
|
||||
@@ -30,6 +30,6 @@ public class GitDiffCommand_DequoteOutputStreamTest {
|
||||
"--- /dev/null\n" +
|
||||
"+++ b/úüþëéåëåé ågðfß\n" +
|
||||
"@@ -0,0 +1 @@\n" +
|
||||
"+rthms");
|
||||
"+String s = \"quotes shall be kept\";");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from "react";
|
||||
import { SelectValue, AutocompleteObject } from "@scm-manager/ui-types";
|
||||
import Autocomplete from "./Autocomplete";
|
||||
import { apiClient } from "./apiclient";
|
||||
|
||||
export type AutocompleteProps = {
|
||||
autocompleteLink?: string;
|
||||
@@ -19,7 +20,8 @@ export default class UserGroupAutocomplete extends React.Component<Props> {
|
||||
loadSuggestions = (inputValue: string): Promise<SelectValue[]> => {
|
||||
const url = this.props.autocompleteLink;
|
||||
const link = url + "?q=";
|
||||
return fetch(link + inputValue)
|
||||
return apiClient
|
||||
.get(link + inputValue)
|
||||
.then(response => response.json())
|
||||
.then((json: AutocompleteObject[]) => {
|
||||
return json.map(element => {
|
||||
|
||||
@@ -27,13 +27,17 @@ const extractXsrfToken = () => {
|
||||
};
|
||||
|
||||
const applyFetchOptions: (p: RequestInit) => RequestInit = o => {
|
||||
const headers: { [key: string]: string } = {
|
||||
Cache: "no-cache",
|
||||
if (!o.headers) {
|
||||
o.headers = {};
|
||||
}
|
||||
|
||||
// @ts-ignore We are sure that here we only get headers of type Record<string, string>
|
||||
const headers: Record<string, string> = o.headers;
|
||||
headers["Cache"] = "no-cache";
|
||||
// identify the request as ajax request
|
||||
"X-Requested-With": "XMLHttpRequest",
|
||||
headers["X-Requested-With"] = "XMLHttpRequest";
|
||||
// identify the web interface
|
||||
"X-SCM-Client": "WUI"
|
||||
};
|
||||
headers["X-SCM-Client"] = "WUI";
|
||||
|
||||
const xsrf = extractXsrfToken();
|
||||
if (xsrf) {
|
||||
@@ -80,23 +84,32 @@ class ApiClient {
|
||||
return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure);
|
||||
}
|
||||
|
||||
post(url: string, payload?: any, contentType = "application/json") {
|
||||
return this.httpRequestWithJSONBody("POST", url, contentType, payload);
|
||||
post(url: string, payload?: any, contentType = "application/json", additionalHeaders: Record<string, string> = {}) {
|
||||
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();
|
||||
fileAppender(formData);
|
||||
|
||||
const options: RequestInit = {
|
||||
method: "POST",
|
||||
body: formData
|
||||
body: formData,
|
||||
headers: additionalHeaders
|
||||
};
|
||||
return this.httpRequestWithBinaryBody(options, url);
|
||||
}
|
||||
|
||||
put(url: string, payload: any, contentType = "application/json") {
|
||||
return this.httpRequestWithJSONBody("PUT", url, contentType, payload);
|
||||
put(url: string, payload: any, contentType = "application/json", additionalHeaders: Record<string, string> = {}) {
|
||||
return this.httpRequestWithJSONBody("PUT", url, contentType, additionalHeaders, payload);
|
||||
}
|
||||
|
||||
head(url: string) {
|
||||
@@ -115,9 +128,16 @@ class ApiClient {
|
||||
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 = {
|
||||
method: method
|
||||
method: method,
|
||||
headers: additionalHeaders
|
||||
};
|
||||
if (payload) {
|
||||
options.body = JSON.stringify(payload);
|
||||
@@ -125,13 +145,27 @@ class ApiClient {
|
||||
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) {
|
||||
options = applyFetchOptions(options);
|
||||
if (contentType) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,14 @@
|
||||
import React from "react";
|
||||
import DiffFile from "./DiffFile";
|
||||
import { DiffObjectProps, File } from "./DiffTypes";
|
||||
import Notification from "../Notification";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
|
||||
type Props = DiffObjectProps & {
|
||||
type Props = WithTranslation &
|
||||
DiffObjectProps & {
|
||||
diff: File[];
|
||||
defaultCollapse?: boolean;
|
||||
};
|
||||
};
|
||||
|
||||
class Diff extends React.Component<Props> {
|
||||
static defaultProps: Partial<Props> = {
|
||||
@@ -13,15 +16,17 @@ class Diff extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { diff, ...fileProps } = this.props;
|
||||
const { diff, t, ...fileProps } = this.props;
|
||||
return (
|
||||
<>
|
||||
{diff.map((file, index) => (
|
||||
<DiffFile key={index} file={file} {...fileProps} {...this.props} />
|
||||
))}
|
||||
{diff.length === 0 ? (
|
||||
<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 = () => {
|
||||
const { url } = this.props;
|
||||
this.setState({loading: true});
|
||||
apiClient
|
||||
.get(url)
|
||||
.then(response => response.text())
|
||||
|
||||
@@ -7,41 +7,6 @@ const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin");
|
||||
const root = path.resolve(process.cwd(), "scm-ui");
|
||||
|
||||
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,
|
||||
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),
|
||||
entry: {
|
||||
|
||||
@@ -809,10 +809,6 @@ form .field:not(.is-grouped) {
|
||||
}
|
||||
}
|
||||
|
||||
.modal-card-body div div:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
// cursor
|
||||
.has-cursor-pointer {
|
||||
cursor: pointer;
|
||||
|
||||
@@ -177,7 +177,8 @@
|
||||
},
|
||||
"diff": {
|
||||
"sideBySide": "Zweispaltig",
|
||||
"combined": "Kombiniert"
|
||||
"combined": "Kombiniert",
|
||||
"noDiffFound": "Kein Diff zwischen den ausgewählten Branches gefunden."
|
||||
},
|
||||
"fileUpload": {
|
||||
"clickHere": "Klicken Sie hier um Ihre Datei hochzuladen.",
|
||||
|
||||
@@ -184,7 +184,8 @@
|
||||
"copy": "copied"
|
||||
},
|
||||
"sideBySide": "side-by-side",
|
||||
"combined": "combined"
|
||||
"combined": "combined",
|
||||
"noDiffFound": "No Diff between the selected branches found."
|
||||
},
|
||||
"fileUpload": {
|
||||
"clickHere": "Click here to select your file",
|
||||
|
||||
@@ -184,7 +184,8 @@
|
||||
"copy": "copiado"
|
||||
},
|
||||
"sideBySide": "dos columnas",
|
||||
"combined": "combinado"
|
||||
"combined": "combinado",
|
||||
"noDiffFound": "No se encontraron diferencias entre las ramas seleccionadas."
|
||||
},
|
||||
"fileUpload": {
|
||||
"clickHere": "Haga click aquí para seleccionar su fichero",
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { apiClient } from "@scm-manager/ui-components";
|
||||
|
||||
const waitForRestart = () => {
|
||||
const endTime = Number(new Date()) + 10000;
|
||||
const endTime = Number(new Date()) + 60000;
|
||||
let started = false;
|
||||
|
||||
const executor = (resolve, reject) => {
|
||||
|
||||
@@ -7,6 +7,7 @@ import { Page } from "@scm-manager/ui-components";
|
||||
import { getGroupsLink, getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
import { createGroup, isCreateGroupPending, getCreateGroupFailure, createGroupReset } from "../modules/groups";
|
||||
import GroupForm from "../components/GroupForm";
|
||||
import { apiClient } from "@scm-manager/ui-components/src";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
createGroup: (link: string, group: Group, callback?: () => void) => void;
|
||||
@@ -40,7 +41,8 @@ class CreateGroup extends React.Component<Props> {
|
||||
|
||||
loadUserAutocompletion = (inputValue: string) => {
|
||||
const url = this.props.autocompleteLink + "?q=";
|
||||
return fetch(url + inputValue)
|
||||
return apiClient
|
||||
.get(url + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { Group } from "@scm-manager/ui-types";
|
||||
import { ErrorNotification } from "@scm-manager/ui-components";
|
||||
import { getUserAutoCompleteLink } from "../../modules/indexResource";
|
||||
import DeleteGroup from "./DeleteGroup";
|
||||
import { apiClient } from "@scm-manager/ui-components/src";
|
||||
|
||||
type Props = {
|
||||
group: Group;
|
||||
@@ -36,7 +37,8 @@ class EditGroup extends React.Component<Props> {
|
||||
|
||||
loadUserAutocompletion = (inputValue: string) => {
|
||||
const url = this.props.autocompleteLink + "?q=";
|
||||
return fetch(url + inputValue)
|
||||
return apiClient
|
||||
.get(url + inputValue)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
return json.map(element => {
|
||||
|
||||
@@ -49,13 +49,22 @@ class Groups extends React.Component<Props> {
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const { loading, list, page, groupLink, location, fetchGroupsByPage } = this.props;
|
||||
if (list && page && !loading) {
|
||||
const statePage: number = list.page + 1;
|
||||
const statePage: number = this.resolveStatePage();
|
||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||
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() {
|
||||
const { groups, loading, error, canAddGroups, t } = this.props;
|
||||
return (
|
||||
|
||||
@@ -114,7 +114,7 @@ const mapDispatchToProps = dispatch => {
|
||||
const mapStateToProps = (state, ownProps) => {
|
||||
const { repository } = ownProps;
|
||||
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 createBranchesLink = getBranchCreateLink(state, repository);
|
||||
return {
|
||||
|
||||
@@ -482,14 +482,14 @@ describe("branches", () => {
|
||||
it("should return error when create branch did fail", () => {
|
||||
const state = {
|
||||
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", () => {
|
||||
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));
|
||||
}
|
||||
|
||||
export function getCreateBranchFailure(state: object) {
|
||||
return getFailure(state, CREATE_BRANCH);
|
||||
export function getCreateBranchFailure(state: object, repository: Repository) {
|
||||
return getFailure(state, CREATE_BRANCH, createKey(repository));
|
||||
}
|
||||
|
||||
export function createBranchPending(repository: Repository): Action {
|
||||
|
||||
@@ -49,13 +49,22 @@ class Users extends React.Component<Props> {
|
||||
componentDidUpdate = (prevProps: Props) => {
|
||||
const { loading, list, page, usersLink, location, fetchUsersByPage } = this.props;
|
||||
if (list && page && !loading) {
|
||||
const statePage: number = list.page + 1;
|
||||
const statePage: number = this.resolveStatePage();
|
||||
if (page !== statePage || prevProps.location.search !== location.search) {
|
||||
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() {
|
||||
const { users, loading, error, canAddUsers, t } = this.props;
|
||||
return (
|
||||
|
||||
@@ -8,7 +8,6 @@ public abstract class RepositoryDtoToRepositoryMapper extends BaseDtoMapper {
|
||||
|
||||
@Mapping(target = "creationDate", ignore = true)
|
||||
@Mapping(target = "id", ignore = true)
|
||||
@Mapping(target = "publicReadable", ignore = true)
|
||||
@Mapping(target = "healthCheckFailures", ignore = true)
|
||||
public abstract Repository map(RepositoryDto repositoryDto, @Context String id);
|
||||
|
||||
|
||||
@@ -61,7 +61,7 @@ public class SetupContextListener implements ServletContextListener {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (isFirstStart()) {
|
||||
if (shouldCreateAdminAccount()) {
|
||||
createAdminAccount();
|
||||
}
|
||||
if (anonymousUserRequiredButNotExists()) {
|
||||
@@ -73,8 +73,12 @@ public class SetupContextListener implements ServletContextListener {
|
||||
return scmConfiguration.isAnonymousAccessEnabled() && !userManager.contains(SCMContext.USER_ANONYMOUS);
|
||||
}
|
||||
|
||||
private boolean isFirstStart() {
|
||||
return userManager.getAll().isEmpty();
|
||||
private boolean shouldCreateAdminAccount() {
|
||||
return userManager.getAll().isEmpty() || onlyAnonymousUserExists();
|
||||
}
|
||||
|
||||
private boolean onlyAnonymousUserExists() {
|
||||
return userManager.getAll().size() == 1 && userManager.contains(SCMContext.USER_ANONYMOUS);
|
||||
}
|
||||
|
||||
private void createAdminAccount() {
|
||||
|
||||
@@ -187,7 +187,7 @@ public class DefaultPluginManager implements PluginManager {
|
||||
|
||||
if (!pendingInstallations.isEmpty()) {
|
||||
if (restartAfterInstallation) {
|
||||
restart("plugin installation");
|
||||
triggerRestart("plugin installation");
|
||||
} else {
|
||||
pendingInstallQueue.addAll(pendingInstallations);
|
||||
updateMayUninstallFlag();
|
||||
@@ -205,7 +205,7 @@ public class DefaultPluginManager implements PluginManager {
|
||||
markForUninstall(installed);
|
||||
|
||||
if (restartAfterInstallation) {
|
||||
restart("plugin installation");
|
||||
triggerRestart("plugin installation");
|
||||
} else {
|
||||
updateMayUninstallFlag();
|
||||
}
|
||||
@@ -238,12 +238,19 @@ public class DefaultPluginManager implements PluginManager {
|
||||
public void executePendingAndRestart() {
|
||||
PluginPermissions.manage().check();
|
||||
if (!pendingInstallQueue.isEmpty() || getInstalled().stream().anyMatch(InstalledPlugin::isMarkedForUninstall)) {
|
||||
restart("execute pending plugin changes");
|
||||
triggerRestart("execute pending plugin changes");
|
||||
}
|
||||
}
|
||||
|
||||
private void restart(String cause) {
|
||||
@VisibleForTesting
|
||||
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) {
|
||||
|
||||
@@ -168,8 +168,7 @@ public class AuthorizationChangedEventProducer {
|
||||
}
|
||||
|
||||
private boolean isAuthorizationDataModified(Repository repository, Repository beforeModification) {
|
||||
return repository.isPublicReadable() != beforeModification.isPublicReadable()
|
||||
|| !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
||||
return !(repository.getPermissions().containsAll(beforeModification.getPermissions()) && beforeModification.getPermissions().containsAll(repository.getPermissions()));
|
||||
}
|
||||
|
||||
private void fireEventForEveryUser() {
|
||||
|
||||
@@ -90,7 +90,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
|
||||
repository.setCreationDate(oldRepository.creationDate);
|
||||
repository.setHealthCheckFailures(oldRepository.healthCheckFailures);
|
||||
repository.setLastModified(oldRepository.lastModified);
|
||||
repository.setPublicReadable(oldRepository.publicReadable);
|
||||
return repository;
|
||||
}
|
||||
|
||||
@@ -149,8 +148,6 @@ public class MigrateVerbsToPermissionRoles implements UpdateStep {
|
||||
private String name;
|
||||
@XmlElement(name = "permission")
|
||||
private final Set<RepositoryPermission> permissions = new HashSet<>();
|
||||
@XmlElement(name = "public")
|
||||
private boolean publicReadable = false;
|
||||
private boolean archived = false;
|
||||
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.XmlAccessorType;
|
||||
import javax.xml.bind.annotation.XmlElement;
|
||||
import javax.xml.bind.annotation.XmlRootElement;
|
||||
import java.util.List;
|
||||
|
||||
@@ -16,6 +17,7 @@ public class V1Repository {
|
||||
private String description;
|
||||
private String id;
|
||||
private String name;
|
||||
@XmlElement(name="public")
|
||||
private boolean isPublic;
|
||||
private boolean archived;
|
||||
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 javax.inject.Inject;
|
||||
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.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
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.repository.V1RepositoryHelper.resolveV1File;
|
||||
import static sonia.scm.version.Version.parse;
|
||||
|
||||
/**
|
||||
@@ -59,6 +51,8 @@ import static sonia.scm.version.Version.parse;
|
||||
@Extension
|
||||
public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
|
||||
private final String V1_REPOSITORY_FILENAME = "repositories" + StoreConstants.FILE_EXTENSION;
|
||||
|
||||
private static Logger LOG = LoggerFactory.getLogger(XmlRepositoryV1UpdateStep.class);
|
||||
|
||||
private final SCMContextProvider contextProvider;
|
||||
@@ -97,12 +91,11 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
|
||||
@Override
|
||||
public void doUpdate() throws JAXBException {
|
||||
if (!resolveV1File().exists()) {
|
||||
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
|
||||
LOG.info("no v1 repositories database file found");
|
||||
return;
|
||||
}
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(V1RepositoryDatabase.class);
|
||||
readV1Database(jaxbContext).ifPresent(
|
||||
V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME).ifPresent(
|
||||
v1Database -> {
|
||||
v1Database.repositoryList.repositories.forEach(this::readMigrationEntry);
|
||||
v1Database.repositoryList.repositories.forEach(this::update);
|
||||
@@ -112,13 +105,12 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
}
|
||||
|
||||
public List<V1Repository> getRepositoriesWithoutMigrationStrategies() {
|
||||
if (!resolveV1File().exists()) {
|
||||
if (!resolveV1File(contextProvider, V1_REPOSITORY_FILENAME).isPresent()) {
|
||||
LOG.info("no v1 repositories database file found");
|
||||
return emptyList();
|
||||
}
|
||||
try {
|
||||
JAXBContext jaxbContext = JAXBContext.newInstance(XmlRepositoryV1UpdateStep.V1RepositoryDatabase.class);
|
||||
return readV1Database(jaxbContext)
|
||||
return V1RepositoryHelper.readV1Database(contextProvider, V1_REPOSITORY_FILENAME)
|
||||
.map(v1Database -> v1Database.repositoryList.repositories.stream())
|
||||
.orElse(Stream.empty())
|
||||
.filter(v1Repository -> !this.findMigrationStrategy(v1Repository).isPresent())
|
||||
@@ -196,33 +188,4 @@ public class XmlRepositoryV1UpdateStep implements CoreUpdateStep {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +94,6 @@ public class RepositorySimplePermissionITCase
|
||||
|
||||
repository.setName("test-repo");
|
||||
repository.setType("git");
|
||||
// repository.setPublicReadable(false);
|
||||
|
||||
ScmClient client = createAdminClient();
|
||||
|
||||
|
||||
@@ -70,7 +70,19 @@ class SetupContextListenerTest {
|
||||
}
|
||||
|
||||
@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");
|
||||
|
||||
setupContextListener.contextInitialized(null);
|
||||
|
||||
@@ -12,13 +12,10 @@ 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.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import sonia.scm.NotFoundException;
|
||||
import sonia.scm.ScmConstraintViolationException;
|
||||
import sonia.scm.event.ScmEventBus;
|
||||
import sonia.scm.lifecycle.RestartEvent;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
@@ -48,9 +45,6 @@ import static sonia.scm.plugin.PluginTestHelper.createInstalled;
|
||||
@ExtendWith(TempDirectory.class)
|
||||
class DefaultPluginManagerTest {
|
||||
|
||||
@Mock
|
||||
private ScmEventBus eventBus;
|
||||
|
||||
@Mock
|
||||
private PluginLoader loader;
|
||||
|
||||
@@ -60,12 +54,13 @@ class DefaultPluginManagerTest {
|
||||
@Mock
|
||||
private PluginInstaller installer;
|
||||
|
||||
@InjectMocks
|
||||
private DefaultPluginManager manager;
|
||||
|
||||
@Mock
|
||||
private Subject subject;
|
||||
|
||||
private boolean restartTriggered = false;
|
||||
|
||||
@BeforeEach
|
||||
void mockInstaller() {
|
||||
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
|
||||
class WithAdminPermissions {
|
||||
|
||||
@@ -180,7 +185,7 @@ class DefaultPluginManagerTest {
|
||||
manager.install("scm-git-plugin", false);
|
||||
|
||||
verify(installer).install(git);
|
||||
verify(eventBus, never()).post(any());
|
||||
assertThat(restartTriggered).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -258,7 +263,7 @@ class DefaultPluginManagerTest {
|
||||
manager.install("scm-git-plugin", true);
|
||||
|
||||
verify(installer).install(git);
|
||||
verify(eventBus).post(any(RestartEvent.class));
|
||||
assertThat(restartTriggered).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -267,7 +272,7 @@ class DefaultPluginManagerTest {
|
||||
when(loader.getInstalledPlugins()).thenReturn(ImmutableList.of(gitInstalled));
|
||||
|
||||
manager.install("scm-git-plugin", true);
|
||||
verify(eventBus, never()).post(any());
|
||||
assertThat(restartTriggered).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -289,14 +294,14 @@ class DefaultPluginManagerTest {
|
||||
manager.install("scm-review-plugin", false);
|
||||
manager.executePendingAndRestart();
|
||||
|
||||
verify(eventBus).post(any(RestartEvent.class));
|
||||
assertThat(restartTriggered).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotSendRestartEventWithoutPendingPlugins() {
|
||||
manager.executePendingAndRestart();
|
||||
|
||||
verify(eventBus, never()).post(any());
|
||||
assertThat(restartTriggered).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -447,7 +452,7 @@ class DefaultPluginManagerTest {
|
||||
|
||||
manager.executePendingAndRestart();
|
||||
|
||||
verify(eventBus).post(any(RestartEvent.class));
|
||||
assertThat(restartTriggered).isTrue();
|
||||
}
|
||||
|
||||
@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