diff --git a/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java b/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java index 17c447eb87..44c6b963ad 100644 --- a/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java +++ b/scm-core/src/main/java/sonia/scm/repository/BrowserResult.java @@ -53,13 +53,19 @@ import java.io.Serializable; public class BrowserResult implements Serializable { private String revision; + private String requestedRevision; private FileObject file; public BrowserResult() { } public BrowserResult(String revision, FileObject file) { + this(revision, revision, file); + } + + public BrowserResult(String revision, String requestedRevision, FileObject file) { this.revision = revision; + this.requestedRevision = requestedRevision; this.file = file; } @@ -67,6 +73,10 @@ public class BrowserResult implements Serializable { return revision; } + public String getRequestedRevision() { + return requestedRevision; + } + public FileObject getFile() { return file; } 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 93fb01176b..2a254d96ce 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 @@ -124,7 +124,7 @@ public class GitBrowseCommand extends AbstractGitCommand if (revId != null) { - result = new BrowserResult(revId.getName(), getEntry(repo, request, revId)); + result = new BrowserResult(revId.getName(), request.getRevision(), getEntry(repo, request, revId)); } else { @@ -138,7 +138,7 @@ public class GitBrowseCommand extends AbstractGitCommand logger.warn("could not find head of repository, empty?"); } - result = new BrowserResult(Constants.HEAD, createEmtpyRoot()); + result = new BrowserResult(Constants.HEAD, request.getRevision(), createEmtpyRoot()); } return result; diff --git a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java index 66dceaa0f7..806f667515 100644 --- a/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java +++ b/scm-plugins/scm-git-plugin/src/main/java/sonia/scm/repository/spi/GitModifyCommand.java @@ -3,6 +3,8 @@ package sonia.scm.repository.spi; import org.apache.commons.lang.StringUtils; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import sonia.scm.BadRequestException; import sonia.scm.ConcurrentModificationException; @@ -23,6 +25,7 @@ import java.util.Optional; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static sonia.scm.AlreadyExistsException.alreadyExists; import static sonia.scm.ContextEntry.ContextBuilder.entity; +import static sonia.scm.ScmConstraintViolationException.Builder.doThrow; public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand { @@ -54,6 +57,9 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman if (!StringUtils.isEmpty(request.getBranch())) { checkOutBranch(request.getBranch()); } + Ref head = getClone().getRepository().exactRef(Constants.HEAD); + doThrow().violation("branch has to be a valid branch, no revision", "branch", request.getBranch()).when(head == null || !head.isSymbolic()); + getClone().getRepository().getFullBranch(); if (!StringUtils.isEmpty(request.getExpectedRevision())) { if (!request.getExpectedRevision().equals(getCurrentRevision().getName())) { throw new ConcurrentModificationException("branch", request.getBranch() == null? "default": request.getBranch()); diff --git a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java index 49ab28ca49..1a9bd5e04a 100644 --- a/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java +++ b/scm-plugins/scm-git-plugin/src/test/java/sonia/scm/repository/spi/GitModifyCommandTest.java @@ -17,6 +17,7 @@ import sonia.scm.AlreadyExistsException; import sonia.scm.BadRequestException; import sonia.scm.ConcurrentModificationException; import sonia.scm.NotFoundException; +import sonia.scm.ScmConstraintViolationException; import sonia.scm.repository.Person; import sonia.scm.repository.util.WorkdirProvider; @@ -198,6 +199,21 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase { command.execute(request); } + @Test(expected = ScmConstraintViolationException.class) + public void shouldFailWithConstraintViolationIfBranchIsNoBranch() throws IOException { + File newFile = Files.write(temporaryFolder.newFile().toPath(), "irrelevant\n".getBytes()).toFile(); + + GitModifyCommand command = createCommand(); + + ModifyCommandRequest request = new ModifyCommandRequest(); + request.setCommitMessage("test commit"); + request.setBranch("3f76a12f08a6ba0dc988c68b7f0b2cd190efc3c4"); + request.addRequest(new ModifyCommandRequest.CreateFileRequest("irrelevant", newFile, true)); + request.setAuthor(new Person("Dirk Gently", "dirk@holistic.det")); + + command.execute(request); + } + private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException { try (Git git = new Git(createContext().open())) { RevCommit lastCommit = getLastCommit(git); diff --git a/scm-ui-components/packages/ui-components/src/Breadcrumb.js b/scm-ui-components/packages/ui-components/src/Breadcrumb.js index eae11c248c..d81a4cbf63 100644 --- a/scm-ui-components/packages/ui-components/src/Breadcrumb.js +++ b/scm-ui-components/packages/ui-components/src/Breadcrumb.js @@ -10,6 +10,7 @@ import classNames from "classnames"; type Props = { branch: Branch, defaultBranch: Branch, + branches: Branch[], revision: string, path: string, baseUrl: string, @@ -62,7 +63,7 @@ class Breadcrumb extends React.Component { } render() { - const { classes, baseUrl, branch, defaultBranch, path } = this.props; + const { classes, baseUrl, branch, defaultBranch, branches, revision, path } = this.props; return ( <> @@ -76,7 +77,7 @@ class Breadcrumb extends React.Component { b.name === revision).length > 0 }} renderAll={true} /> diff --git a/scm-ui/src/repos/sources/containers/Sources.js b/scm-ui/src/repos/sources/containers/Sources.js index 130326e8f4..9a485459a6 100644 --- a/scm-ui/src/repos/sources/containers/Sources.js +++ b/scm-ui/src/repos/sources/containers/Sources.js @@ -1,20 +1,25 @@ // @flow import React from "react"; -import {connect} from "react-redux"; -import {withRouter} from "react-router-dom"; -import type {Branch, Repository} from "@scm-manager/ui-types"; +import { connect } from "react-redux"; +import { withRouter } from "react-router-dom"; +import type { Branch, Repository } from "@scm-manager/ui-types"; import FileTree from "../components/FileTree"; -import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components"; -import {translate} from "react-i18next"; +import { + BranchSelector, + Breadcrumb, + ErrorNotification, + Loading +} from "@scm-manager/ui-components"; +import { translate } from "react-i18next"; import { fetchBranches, getBranches, getFetchBranchesFailure, isFetchBranchesPending } from "../../branches/modules/branches"; -import {compose} from "redux"; +import { compose } from "redux"; import Content from "./Content"; -import {fetchSources, isDirectory} from "../modules/sources"; +import { fetchSources, isDirectory } from "../modules/sources"; type Props = { repository: Repository, @@ -33,6 +38,7 @@ type Props = { // Context props history: any, match: any, + location: any, t: string => string }; @@ -55,31 +61,62 @@ class Sources extends React.Component { repository, revision, path, + branches, + baseUrl, fetchSources } = this.props; fetchBranches(repository); fetchSources(repository, revision, path); + + if (branches) { + const defaultBranches = branches.filter(b => b.defaultBranch); + + if (defaultBranches.length > 0) + this.setState({ selectedBranch: defaultBranches[0] }); + this.props.history.push(`${baseUrl}/${defaultBranches[0].name}/`); + } } + componentDidUpdate(prevProps) { - const { fetchSources, repository, revision, path } = this.props; + const { + fetchSources, + repository, + revision, + path, + branches, + baseUrl + } = this.props; if (prevProps.revision !== revision || prevProps.path !== path) { fetchSources(repository, revision, path); } + + const currentUrl = this.props.location.pathname; + + if ( + branches && + (currentUrl.endsWith("sources") || currentUrl.endsWith("sources/")) + ) { + const defaultBranches = branches.filter(b => b.defaultBranch); + + if (defaultBranches.length > 0) + this.setState({ selectedBranch: defaultBranches[0] }); + this.props.history.push(`${baseUrl}/${defaultBranches[0].name}/`); + } } branchSelected = (branch?: Branch) => { const { baseUrl, history, path } = this.props; let url; if (branch) { - this.setState({selectedBranch: branch}); + this.setState({ selectedBranch: branch }); if (path) { url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`; } else { url = `${baseUrl}/${encodeURIComponent(branch.name)}/`; } } else { - this.setState({selectedBranch: null}); + this.setState({ selectedBranch: null }); url = `${baseUrl}/`; } history.push(url); @@ -97,7 +134,7 @@ class Sources extends React.Component { currentFileIsDirectory } = this.props; - const {selectedBranch} = this.state; + const { selectedBranch } = this.state; if (error) { return ; @@ -119,6 +156,7 @@ class Sources extends React.Component { defaultBranch={ branches && branches.filter(b => b.defaultBranch === true)[0] } + branches={branches && branches} />