From 4885a0cb804dcaa369ad01dba99aa083da41cd46 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 21 Oct 2019 16:25:57 +0200 Subject: [PATCH 01/23] Filter git diff stream to decode file names --- .../scm/repository/spi/GitDiffCommand.java | 69 ++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index 1ac64c1b5e..e568320ad5 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -33,10 +33,13 @@ package sonia.scm.repository.spi; import org.eclipse.jgit.diff.DiffEntry; import org.eclipse.jgit.diff.DiffFormatter; +import org.eclipse.jgit.util.QuotedString; import sonia.scm.repository.Repository; import sonia.scm.repository.api.DiffCommandBuilder; +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.io.OutputStream; /** * @@ -56,7 +59,7 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { Differ.Diff diff = Differ.diff(repository, request); return output -> { - try (DiffFormatter formatter = new DiffFormatter(output)) { + try (DiffFormatter formatter = new DiffFormatter(new DequoteOutputStream(output))) { formatter.setRepository(repository); for (DiffEntry e : diff.getEntries()) { @@ -70,4 +73,68 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { }; } + private static class DequoteOutputStream extends OutputStream { + + private final OutputStream target; + + private boolean afterNL = false; + private int minusCount = 0; + private int plusCount = 0; + private ByteArrayOutputStream buffer; + + private DequoteOutputStream(OutputStream target) { + this.target = target; + } + + @Override + public void write(int i) throws IOException { + if (i == (int) '+' && afterNL) { + plusCount = 1; + afterNL = false; + target.write(i); + } else if (i == (int) '+' && plusCount > 0) { + ++plusCount; + afterNL = false; + target.write(i); + } else if (i == (int) '-' && afterNL) { + minusCount = 1; + afterNL = false; + target.write(i); + } else if (i == (int) '-' && minusCount > 0) { + ++minusCount; + afterNL = false; + target.write(i); + } else if (i == (int) ' ' && plusCount == 3) { + buffer = new ByteArrayOutputStream(); + afterNL = false; + plusCount = 0; + target.write(i); + } else if (i == (int) ' ' && minusCount == 3) { + minusCount = 0; + afterNL = false; + buffer = new ByteArrayOutputStream(); + target.write(i); + } else if (buffer != null) { + if (i == (int) '\n') { + afterNL = true; + byte[] bytes = buffer.toByteArray(); + String dequote = QuotedString.GIT_PATH.dequote(bytes, 0, bytes.length); + target.write(dequote.getBytes()); + target.write(i); + buffer = null; + } else { + buffer.write(i); + afterNL = false; + } + } else if (i == (int) '\n') { + afterNL = true; + target.write(i); + } else { + target.write(i); + afterNL = false; + minusCount = 0; + plusCount = 0; + } + } + } } From 7209702643704396bdbbd1672dbcb90609dca528 Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Mon, 21 Oct 2019 16:30:57 +0200 Subject: [PATCH 02/23] Rearrange ifs --- .../scm/repository/spi/GitDiffCommand.java | 22 ++++++++++--------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java index e568320ad5..0fbe45d3cd 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitDiffCommand.java @@ -114,8 +114,8 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { afterNL = false; buffer = new ByteArrayOutputStream(); target.write(i); - } else if (buffer != null) { - if (i == (int) '\n') { + } else if (i == (int) '\n') { + if (buffer != null) { afterNL = true; byte[] bytes = buffer.toByteArray(); String dequote = QuotedString.GIT_PATH.dequote(bytes, 0, bytes.length); @@ -123,17 +123,19 @@ public class GitDiffCommand extends AbstractGitCommand implements DiffCommand { target.write(i); buffer = null; } else { + afterNL = true; + target.write(i); + } + } else { + if (buffer != null) { buffer.write(i); afterNL = false; + } else { + target.write(i); + afterNL = false; + minusCount = 0; + plusCount = 0; } - } else if (i == (int) '\n') { - afterNL = true; - target.write(i); - } else { - target.write(i); - afterNL = false; - minusCount = 0; - plusCount = 0; } } } From 5d7bdc24d4d2957f1da97b079d36f5db3d0b78dd Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 5 Nov 2019 16:10:41 +0100 Subject: [PATCH 03/23] Make payload parameter optional --- scm-ui/ui-components/src/apiclient.ts | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 396200f1c1..115b241724 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -47,7 +47,7 @@ class ApiClient { return fetch(createUrl(url), applyFetchOptions({})).then(handleFailure); } - post(url: string, payload: any, contentType = "application/json") { + post(url: string, payload?: any, contentType = "application/json") { return this.httpRequestWithJSONBody("POST", url, contentType, payload); } @@ -82,11 +82,13 @@ class ApiClient { return fetch(createUrl(url), options).then(handleFailure); } - httpRequestWithJSONBody(method: string, url: string, contentType: string, payload: any): Promise { + httpRequestWithJSONBody(method: string, url: string, contentType: string, payload?: any): Promise { const options: RequestInit = { - method: method, - body: JSON.stringify(payload) + method: method }; + if (payload) { + options.body = JSON.stringify(payload); + } return this.httpRequestWithBinaryBody(options, url, contentType); } From 234d98aee7364defa8db9e634627c46075cc8913 Mon Sep 17 00:00:00 2001 From: Eduard Heimbuch Date: Tue, 12 Nov 2019 13:49:37 +0100 Subject: [PATCH 04/23] Don't use anonymous access after access token expires --- .../main/java/sonia/scm/web/filter/AuthenticationFilter.java | 2 +- scm-ui/ui-components/src/apiclient.ts | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java index 87209ce409..3b64e6b5ac 100644 --- a/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java +++ b/scm-core/src/main/java/sonia/scm/web/filter/AuthenticationFilter.java @@ -127,7 +127,7 @@ public class AuthenticationFilter extends HttpFilter logger.trace("user is already authenticated"); processChain(request, response, chain, subject); } - else if (isAnonymousAccessEnabled()) + else if (isAnonymousAccessEnabled() && !HttpUtil.isWUIRequest(request)) { logger.trace("anonymous access granted"); subject.login(new AnonymousToken()); diff --git a/scm-ui/ui-components/src/apiclient.ts b/scm-ui/ui-components/src/apiclient.ts index 396200f1c1..8af093fac7 100644 --- a/scm-ui/ui-components/src/apiclient.ts +++ b/scm-ui/ui-components/src/apiclient.ts @@ -7,7 +7,8 @@ const applyFetchOptions: (p: RequestInit) => RequestInit = o => { o.headers = { Cache: "no-cache", // identify the request as ajax request - "X-Requested-With": "XMLHttpRequest" + "X-Requested-With": "XMLHttpRequest", + "X-SCM-Client": "WUI" }; return o; }; From 6d2bdb6f294f96162712a0b5fb6ec96f54f8b1e4 Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 12 Nov 2019 14:48:32 +0100 Subject: [PATCH 05/23] Add IconButton --- .../src/__snapshots__/storyshots.test.ts.snap | 20 +++++++ .../ui-components/src/buttons/IconButton.tsx | 56 +++++++++++++++++++ .../src/buttons/index.stories.tsx | 3 +- scm-ui/ui-components/src/buttons/index.ts | 1 + 4 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 scm-ui/ui-components/src/buttons/IconButton.tsx diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index 0350b44219..b7852bd927 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -282,6 +282,26 @@ exports[`Storyshots Buttons|EditButton Default 1`] = ` `; +exports[`Storyshots Buttons|IconButton Default 1`] = ` +
+ +
+`; + exports[`Storyshots Buttons|SubmitButton Default 1`] = `
void; + link?: string; + className?: string; +}; + +type Props = ButtonProps & + RouteComponentProps & { + type?: "button" | "submit" | "reset"; + color?: string; + }; + +class IconButton extends React.Component { + static defaultProps: Partial = { + type: "button", + color: "default" + }; + + onClick = (event: React.MouseEvent) => { + const { action, link, history } = this.props; + if (action) { + action(event); + } else if (link) { + history.push(link); + } + }; + + render() { + const { icon, title, loading, disabled, type, color, className } = this.props; + const loadingClass = loading ? "is-loading" : ""; + return ( + + ); + } +} + +export default withRouter(IconButton); diff --git a/scm-ui/ui-components/src/buttons/index.stories.tsx b/scm-ui/ui-components/src/buttons/index.stories.tsx index acfd513139..4da801f8a1 100644 --- a/scm-ui/ui-components/src/buttons/index.stories.tsx +++ b/scm-ui/ui-components/src/buttons/index.stories.tsx @@ -3,6 +3,7 @@ import Button from "./Button"; import { storiesOf } from "@storybook/react"; import styled from "styled-components"; import { MemoryRouter } from "react-router-dom"; +import IconButton from "./IconButton"; import AddButton from "./AddButton"; import CreateButton from "./CreateButton"; import DeleteButton from "./DeleteButton"; @@ -48,7 +49,7 @@ const buttonStory = (name: string, storyFn: () => ReactElement) => { .addDecorator(SpacingDecorator) .add("Default", storyFn); }; - +buttonStory("IconButton", () => ); buttonStory("AddButton", () => Add); buttonStory("CreateButton", () => Create); buttonStory("DeleteButton", () => Delete); diff --git a/scm-ui/ui-components/src/buttons/index.ts b/scm-ui/ui-components/src/buttons/index.ts index c5d536133f..3bbfd05c4b 100644 --- a/scm-ui/ui-components/src/buttons/index.ts +++ b/scm-ui/ui-components/src/buttons/index.ts @@ -2,6 +2,7 @@ export { default as AddButton } from "./AddButton"; export { default as Button } from "./Button"; +export { default as IconButton } from "./IconButton"; export { default as CreateButton } from "./CreateButton"; export { default as DeleteButton } from "./DeleteButton"; export { default as EditButton } from "./EditButton"; From 9132167a6cd9a18a325bc4fec2b7021a46f5ba9b Mon Sep 17 00:00:00 2001 From: Florian Scholdei Date: Tue, 12 Nov 2019 17:30:37 +0100 Subject: [PATCH 06/23] Add title option to Button and remove IconButton --- .../src/__snapshots__/storyshots.test.ts.snap | 20 ------- scm-ui/ui-components/src/buttons/Button.tsx | 17 +++++- .../ui-components/src/buttons/IconButton.tsx | 56 ------------------- .../src/buttons/index.stories.tsx | 2 - scm-ui/ui-components/src/buttons/index.ts | 1 - 5 files changed, 16 insertions(+), 80 deletions(-) delete mode 100644 scm-ui/ui-components/src/buttons/IconButton.tsx diff --git a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap index b7852bd927..0350b44219 100644 --- a/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap +++ b/scm-ui/ui-components/src/__snapshots__/storyshots.test.ts.snap @@ -282,26 +282,6 @@ exports[`Storyshots Buttons|EditButton Default 1`] = `
`; -exports[`Storyshots Buttons|IconButton Default 1`] = ` -
- -
-`; - exports[`Storyshots Buttons|SubmitButton Default 1`] = `
{ }; render() { - const { label, loading, disabled, type, color, className, icon, fullWidth, reducedMobile, children } = this.props; + const { + label, + title, + loading, + disabled, + type, + color, + className, + icon, + fullWidth, + reducedMobile, + children + } = this.props; const loadingClass = loading ? "is-loading" : ""; const fullWidthClass = fullWidth ? "is-fullwidth" : ""; const reducedMobileClass = reducedMobile ? "is-reduced-mobile" : ""; @@ -46,6 +59,7 @@ class Button extends React.Component { return ( - ); - } -} - -export default withRouter(IconButton); diff --git a/scm-ui/ui-components/src/buttons/index.stories.tsx b/scm-ui/ui-components/src/buttons/index.stories.tsx index 4da801f8a1..79d5be448c 100644 --- a/scm-ui/ui-components/src/buttons/index.stories.tsx +++ b/scm-ui/ui-components/src/buttons/index.stories.tsx @@ -3,7 +3,6 @@ import Button from "./Button"; import { storiesOf } from "@storybook/react"; import styled from "styled-components"; import { MemoryRouter } from "react-router-dom"; -import IconButton from "./IconButton"; import AddButton from "./AddButton"; import CreateButton from "./CreateButton"; import DeleteButton from "./DeleteButton"; @@ -49,7 +48,6 @@ const buttonStory = (name: string, storyFn: () => ReactElement) => { .addDecorator(SpacingDecorator) .add("Default", storyFn); }; -buttonStory("IconButton", () => ); buttonStory("AddButton", () => Add); buttonStory("CreateButton", () => Create); buttonStory("DeleteButton", () => Delete); diff --git a/scm-ui/ui-components/src/buttons/index.ts b/scm-ui/ui-components/src/buttons/index.ts index 3bbfd05c4b..c5d536133f 100644 --- a/scm-ui/ui-components/src/buttons/index.ts +++ b/scm-ui/ui-components/src/buttons/index.ts @@ -2,7 +2,6 @@ export { default as AddButton } from "./AddButton"; export { default as Button } from "./Button"; -export { default as IconButton } from "./IconButton"; export { default as CreateButton } from "./CreateButton"; export { default as DeleteButton } from "./DeleteButton"; export { default as EditButton } from "./EditButton"; From 2f5095e76a62aafa715dce9f2b2a2992713a9c9e Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 15 Nov 2019 09:33:38 +0100 Subject: [PATCH 07/23] Handle missing blobs in blob store for lfs pointers --- .../sonia/scm/repository/spi/GitBrowseCommand.java | 10 ++++++++-- .../java/sonia/scm/repository/spi/GitCatCommand.java | 7 ++++++- 2 files changed, 14 insertions(+), 3 deletions(-) diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java index 5ec69cccdd..2048d13dea 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitBrowseCommand.java @@ -213,8 +213,14 @@ public class GitBrowseCommand extends AbstractGitCommand if (lfsPointer.isPresent()) { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); - Blob blob = lfsBlobStore.get(lfsPointer.get().getOid().getName()); - file.setLength(blob.getSize()); + String oid = lfsPointer.get().getOid().getName(); + Blob blob = lfsBlobStore.get(oid); + if (blob == null) { + logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName()); + file.setLength(-1); + } else { + file.setLength(blob.getSize()); + } } else { file.setLength(loader.getSize()); } diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java index 35ff4d6ac2..193ab5bbc8 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitCatCommand.java @@ -145,7 +145,12 @@ public class GitCatCommand extends AbstractGitCommand implements CatCommand { private Loader loadFromLfsStore(TreeWalk treeWalk, RevWalk revWalk, LfsPointer lfsPointer) throws IOException { BlobStore lfsBlobStore = lfsBlobStoreFactory.getLfsBlobStore(repository); - Blob blob = lfsBlobStore.get(lfsPointer.getOid().getName()); + String oid = lfsPointer.getOid().getName(); + Blob blob = lfsBlobStore.get(oid); + if (blob == null) { + logger.error("lfs blob for lob id {} not found in lfs store of repository {}", oid, repository.getNamespaceAndName()); + throw notFound(entity("LFS", oid).in(repository)); + } GitUtil.release(revWalk); GitUtil.release(treeWalk); return new BlobLoader(blob); From cc4c4f0822ea9a5763c661e67131dbffd47379be Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 15 Nov 2019 09:50:28 +0100 Subject: [PATCH 08/23] Fix browse of empty repository In an empty hg repository the Changeset c is null. This leads to an NullPointerException in the original code. So we check whether we have a Changeset and otherwise use the old functionality. --- .../main/java/sonia/scm/repository/spi/HgBrowseCommand.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java index 48772ad5e5..b3e8e89a5f 100644 --- a/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java +++ b/scm-plugins/scm-hg-plugin/src/main/java/sonia/scm/repository/spi/HgBrowseCommand.java @@ -77,7 +77,9 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand String revision = MoreObjects.firstNonNull(request.getRevision(), "tip"); Changeset c = LogCommand.on(getContext().open()).rev(revision).limit(1).single(); - cmd.rev(c.getNode()); + if (c != null) { + cmd.rev(c.getNode()); + } if (!Strings.isNullOrEmpty(request.getPath())) { @@ -100,6 +102,6 @@ public class HgBrowseCommand extends AbstractCommand implements BrowseCommand } FileObject file = cmd.execute(); - return new BrowserResult(c.getNode(), revision, file); + return new BrowserResult(c == null? "tip": c.getNode(), revision, file); } } From f5b7d9f8f9702b2eb776b3b13ae9b06f69f4863f Mon Sep 17 00:00:00 2001 From: Rene Pfeuffer Date: Fri, 15 Nov 2019 11:35:27 +0000 Subject: [PATCH 09/23] Close branch feature/pr_approval From 5ea3c53613dbdb2f1bc97fc7af2406e03cf72f67 Mon Sep 17 00:00:00 2001 From: Sebastian Sdorra Date: Mon, 18 Nov 2019 10:56:40 +0100 Subject: [PATCH 10/23] create separate styles bundle --- scm-ui/ui-scripts/package.json | 2 + scm-ui/ui-scripts/src/webpack.config.js | 43 +- scm-ui/ui-webapp/public/index.mustache | 1 + .../sonia/scm/lifecycle/view/SingleView.java | 2 +- .../main/resources/templates/layout.mustache | 2 +- yarn.lock | 569 +++++++++++++++++- 6 files changed, 604 insertions(+), 15 deletions(-) diff --git a/scm-ui/ui-scripts/package.json b/scm-ui/ui-scripts/package.json index d647427a1a..7ef5d2819c 100644 --- a/scm-ui/ui-scripts/package.json +++ b/scm-ui/ui-scripts/package.json @@ -14,8 +14,10 @@ "cache-loader": "^4.1.0", "css-loader": "^3.2.0", "file-loader": "^4.2.0", + "mini-css-extract-plugin": "^0.8.0", "mustache": "^3.1.0", "node-sass": "^4.12.0", + "optimize-css-assets-webpack-plugin": "^5.0.3", "sass-loader": "^8.0.0", "script-loader": "^0.7.2", "style-loader": "^1.0.0", diff --git a/scm-ui/ui-scripts/src/webpack.config.js b/scm-ui/ui-scripts/src/webpack.config.js index c4cf6d609b..639872c6b8 100644 --- a/scm-ui/ui-scripts/src/webpack.config.js +++ b/scm-ui/ui-scripts/src/webpack.config.js @@ -1,19 +1,52 @@ const path = require("path"); const createIndexMiddleware = require("./middleware/IndexMiddleware"); const createContextPathMiddleware = require("./middleware/ContextPathMiddleware"); +const MiniCssExtractPlugin = require("mini-css-extract-plugin"); +const OptimizeCSSAssetsPlugin = require("optimize-css-assets-webpack-plugin"); const root = path.resolve(process.cwd(), "scm-ui"); module.exports = [ { context: root, - entry: { - webapp: [ - path.resolve(__dirname, "webpack-public-path.js"), - "./ui-styles/src/scm.scss", - "./ui-webapp/src/index.tsx" + 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: { + webapp: [path.resolve(__dirname, "webpack-public-path.js"), "./ui-webapp/src/index.tsx"] + }, devtool: "cheap-module-eval-source-map", target: "web", node: { diff --git a/scm-ui/ui-webapp/public/index.mustache b/scm-ui/ui-webapp/public/index.mustache index 64846128a1..d94bc552ee 100644 --- a/scm-ui/ui-webapp/public/index.mustache +++ b/scm-ui/ui-webapp/public/index.mustache @@ -14,6 +14,7 @@ SCM-Manager +