Merge with base branch

This commit is contained in:
Rene Pfeuffer
2019-09-04 13:35:40 +02:00
9 changed files with 99 additions and 26 deletions

View File

@@ -53,13 +53,19 @@ import java.io.Serializable;
public class BrowserResult implements Serializable { public class BrowserResult implements Serializable {
private String revision; private String revision;
private String requestedRevision;
private FileObject file; private FileObject file;
public BrowserResult() { public BrowserResult() {
} }
public BrowserResult(String revision, FileObject file) { public BrowserResult(String revision, FileObject file) {
this(revision, revision, file);
}
public BrowserResult(String revision, String requestedRevision, FileObject file) {
this.revision = revision; this.revision = revision;
this.requestedRevision = requestedRevision;
this.file = file; this.file = file;
} }
@@ -67,6 +73,10 @@ public class BrowserResult implements Serializable {
return revision; return revision;
} }
public String getRequestedRevision() {
return requestedRevision;
}
public FileObject getFile() { public FileObject getFile() {
return file; return file;
} }

View File

@@ -124,7 +124,7 @@ public class GitBrowseCommand extends AbstractGitCommand
if (revId != null) if (revId != null)
{ {
result = new BrowserResult(revId.getName(), getEntry(repo, request, revId)); result = new BrowserResult(revId.getName(), request.getRevision(), getEntry(repo, request, revId));
} }
else else
{ {
@@ -138,7 +138,7 @@ public class GitBrowseCommand extends AbstractGitCommand
logger.warn("could not find head of repository, empty?"); 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; return result;

View File

@@ -3,6 +3,8 @@ package sonia.scm.repository.spi;
import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.StringUtils;
import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.errors.GitAPIException; 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 org.eclipse.jgit.revwalk.RevCommit;
import sonia.scm.BadRequestException; import sonia.scm.BadRequestException;
import sonia.scm.ConcurrentModificationException; import sonia.scm.ConcurrentModificationException;
@@ -23,6 +25,7 @@ import java.util.Optional;
import static java.nio.file.StandardCopyOption.REPLACE_EXISTING; import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
import static sonia.scm.AlreadyExistsException.alreadyExists; import static sonia.scm.AlreadyExistsException.alreadyExists;
import static sonia.scm.ContextEntry.ContextBuilder.entity; import static sonia.scm.ContextEntry.ContextBuilder.entity;
import static sonia.scm.ScmConstraintViolationException.Builder.doThrow;
public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand { public class GitModifyCommand extends AbstractGitCommand implements ModifyCommand {
@@ -54,6 +57,9 @@ public class GitModifyCommand extends AbstractGitCommand implements ModifyComman
if (!StringUtils.isEmpty(request.getBranch())) { if (!StringUtils.isEmpty(request.getBranch())) {
checkOutBranch(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 (!StringUtils.isEmpty(request.getExpectedRevision())) {
if (!request.getExpectedRevision().equals(getCurrentRevision().getName())) { if (!request.getExpectedRevision().equals(getCurrentRevision().getName())) {
throw new ConcurrentModificationException("branch", request.getBranch() == null? "default": request.getBranch()); throw new ConcurrentModificationException("branch", request.getBranch() == null? "default": request.getBranch());

View File

@@ -17,6 +17,7 @@ import sonia.scm.AlreadyExistsException;
import sonia.scm.BadRequestException; import sonia.scm.BadRequestException;
import sonia.scm.ConcurrentModificationException; import sonia.scm.ConcurrentModificationException;
import sonia.scm.NotFoundException; import sonia.scm.NotFoundException;
import sonia.scm.ScmConstraintViolationException;
import sonia.scm.repository.Person; import sonia.scm.repository.Person;
import sonia.scm.repository.util.WorkdirProvider; import sonia.scm.repository.util.WorkdirProvider;
@@ -198,6 +199,21 @@ public class GitModifyCommandTest extends AbstractGitCommandTestBase {
command.execute(request); 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 { private void assertInTree(TreeAssertions assertions) throws IOException, GitAPIException {
try (Git git = new Git(createContext().open())) { try (Git git = new Git(createContext().open())) {
RevCommit lastCommit = getLastCommit(git); RevCommit lastCommit = getLastCommit(git);

View File

@@ -10,6 +10,7 @@ import classNames from "classnames";
type Props = { type Props = {
branch: Branch, branch: Branch,
defaultBranch: Branch, defaultBranch: Branch,
branches: Branch[],
revision: string, revision: string,
path: string, path: string,
baseUrl: string, baseUrl: string,
@@ -62,7 +63,7 @@ class Breadcrumb extends React.Component<Props> {
} }
render() { render() {
const { classes, baseUrl, branch, defaultBranch, path } = this.props; const { classes, baseUrl, branch, defaultBranch, branches, revision, path } = this.props;
return ( return (
<> <>
@@ -76,7 +77,7 @@ class Breadcrumb extends React.Component<Props> {
<ButtonGroup> <ButtonGroup>
<ExtensionPoint <ExtensionPoint
name="repos.sources.actionbar" name="repos.sources.actionbar"
props={{ baseUrl, branch: branch ? branch : defaultBranch, path }} props={{ baseUrl, branch: branch ? branch : defaultBranch, path, isBranchUrl: branches && branches.filter(b => b.name === revision).length > 0 }}
renderAll={true} renderAll={true}
/> />
</ButtonGroup> </ButtonGroup>

View File

@@ -1,20 +1,25 @@
// @flow // @flow
import React from "react"; import React from "react";
import {connect} from "react-redux"; import { connect } from "react-redux";
import {withRouter} from "react-router-dom"; import { withRouter } from "react-router-dom";
import type {Branch, Repository} from "@scm-manager/ui-types"; import type { Branch, Repository } from "@scm-manager/ui-types";
import FileTree from "../components/FileTree"; import FileTree from "../components/FileTree";
import {BranchSelector, Breadcrumb, ErrorNotification, Loading} from "@scm-manager/ui-components"; import {
import {translate} from "react-i18next"; BranchSelector,
Breadcrumb,
ErrorNotification,
Loading
} from "@scm-manager/ui-components";
import { translate } from "react-i18next";
import { import {
fetchBranches, fetchBranches,
getBranches, getBranches,
getFetchBranchesFailure, getFetchBranchesFailure,
isFetchBranchesPending isFetchBranchesPending
} from "../../branches/modules/branches"; } from "../../branches/modules/branches";
import {compose} from "redux"; import { compose } from "redux";
import Content from "./Content"; import Content from "./Content";
import {fetchSources, isDirectory} from "../modules/sources"; import { fetchSources, isDirectory } from "../modules/sources";
type Props = { type Props = {
repository: Repository, repository: Repository,
@@ -33,6 +38,7 @@ type Props = {
// Context props // Context props
history: any, history: any,
match: any, match: any,
location: any,
t: string => string t: string => string
}; };
@@ -55,31 +61,62 @@ class Sources extends React.Component<Props, State> {
repository, repository,
revision, revision,
path, path,
branches,
baseUrl,
fetchSources fetchSources
} = this.props; } = this.props;
fetchBranches(repository); fetchBranches(repository);
fetchSources(repository, revision, path); 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) { 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) { if (prevProps.revision !== revision || prevProps.path !== path) {
fetchSources(repository, revision, 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) => { branchSelected = (branch?: Branch) => {
const { baseUrl, history, path } = this.props; const { baseUrl, history, path } = this.props;
let url; let url;
if (branch) { if (branch) {
this.setState({selectedBranch: branch}); this.setState({ selectedBranch: branch });
if (path) { if (path) {
url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`; url = `${baseUrl}/${encodeURIComponent(branch.name)}/${path}`;
} else { } else {
url = `${baseUrl}/${encodeURIComponent(branch.name)}/`; url = `${baseUrl}/${encodeURIComponent(branch.name)}/`;
} }
} else { } else {
this.setState({selectedBranch: null}); this.setState({ selectedBranch: null });
url = `${baseUrl}/`; url = `${baseUrl}/`;
} }
history.push(url); history.push(url);
@@ -97,7 +134,7 @@ class Sources extends React.Component<Props, State> {
currentFileIsDirectory currentFileIsDirectory
} = this.props; } = this.props;
const {selectedBranch} = this.state; const { selectedBranch } = this.state;
if (error) { if (error) {
return <ErrorNotification error={error} />; return <ErrorNotification error={error} />;
@@ -119,6 +156,7 @@ class Sources extends React.Component<Props, State> {
defaultBranch={ defaultBranch={
branches && branches.filter(b => b.defaultBranch === true)[0] branches && branches.filter(b => b.defaultBranch === true)[0]
} }
branches={branches && branches}
/> />
<FileTree <FileTree
repository={repository} repository={repository}

View File

@@ -15,7 +15,7 @@ public class BrowserResultToFileObjectDtoMapper {
} }
public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) { public FileObjectDto map(BrowserResult browserResult, NamespaceAndName namespaceAndName) {
FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult.getRevision()); FileObjectDto fileObjectDto = fileObjectToFileObjectDtoMapper.map(browserResult.getFile(), namespaceAndName, browserResult);
fileObjectDto.setRevision( browserResult.getRevision() ); fileObjectDto.setRevision( browserResult.getRevision() );
return fileObjectDto; return fileObjectDto;
} }

View File

@@ -6,6 +6,7 @@ import org.mapstruct.Context;
import org.mapstruct.Mapper; import org.mapstruct.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.ObjectFactory; import org.mapstruct.ObjectFactory;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject; import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository; import sonia.scm.repository.SubRepository;
@@ -22,23 +23,23 @@ public abstract class FileObjectToFileObjectDtoMapper extends HalAppenderMapper
private ResourceLinks resourceLinks; private ResourceLinks resourceLinks;
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes @Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context String revision); protected abstract FileObjectDto map(FileObject fileObject, @Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult);
abstract SubRepositoryDto mapSubrepository(SubRepository subRepository); abstract SubRepositoryDto mapSubrepository(SubRepository subRepository);
@ObjectFactory @ObjectFactory
FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context String revision, FileObject fileObject) { FileObjectDto createDto(@Context NamespaceAndName namespaceAndName, @Context BrowserResult browserResult, FileObject fileObject) {
String path = removeFirstSlash(fileObject.getPath()); String path = removeFirstSlash(fileObject.getPath());
Links.Builder links = Links.linkingTo(); Links.Builder links = Links.linkingTo();
if (fileObject.isDirectory()) { if (fileObject.isDirectory()) {
links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); links.self(resourceLinks.source().sourceWithPath(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path));
} else { } else {
links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path)); links.self(resourceLinks.source().content(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path));
links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), revision, path))); links.single(link("history", resourceLinks.fileHistory().self(namespaceAndName.getNamespace(), namespaceAndName.getName(), browserResult.getRevision(), path)));
} }
Embedded.Builder embeddedBuilder = embeddedBuilder(); Embedded.Builder embeddedBuilder = embeddedBuilder();
applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, revision); applyEnrichers(new EdisonHalAppender(links, embeddedBuilder), fileObject, namespaceAndName, browserResult, browserResult.getRevision());
return new FileObjectDto(links.build(), embeddedBuilder.build()); return new FileObjectDto(links.build(), embeddedBuilder.build());
} }

View File

@@ -10,6 +10,7 @@ import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.junit.MockitoJUnitRunner; import org.mockito.junit.MockitoJUnitRunner;
import sonia.scm.repository.BrowserResult;
import sonia.scm.repository.FileObject; import sonia.scm.repository.FileObject;
import sonia.scm.repository.NamespaceAndName; import sonia.scm.repository.NamespaceAndName;
import sonia.scm.repository.SubRepository; import sonia.scm.repository.SubRepository;
@@ -49,7 +50,7 @@ public class FileObjectToFileObjectDtoMapperTest {
@Test @Test
public void shouldMapAttributesCorrectly() { public void shouldMapAttributesCorrectly() {
FileObject fileObject = createFileObject(); FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision"); FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertEqualAttributes(fileObject, dto); assertEqualAttributes(fileObject, dto);
} }
@@ -57,7 +58,7 @@ public class FileObjectToFileObjectDtoMapperTest {
@Test @Test
public void shouldHaveCorrectSelfLinkForDirectory() { public void shouldHaveCorrectSelfLinkForDirectory() {
FileObject fileObject = createDirectoryObject(); FileObject fileObject = createDirectoryObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision"); FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/sources/revision/foo/bar").toString()); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/sources/revision/foo/bar").toString());
} }
@@ -66,7 +67,7 @@ public class FileObjectToFileObjectDtoMapperTest {
public void shouldHaveCorrectContentLink() { public void shouldHaveCorrectContentLink() {
FileObject fileObject = createFileObject(); FileObject fileObject = createFileObject();
fileObject.setDirectory(false); fileObject.setDirectory(false);
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), "revision"); FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("namespace", "name"), new BrowserResult("revision", fileObject));
assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString()); assertThat(dto.getLinks().getLinkBy("self").get().getHref()).isEqualTo(expectedBaseUri.resolve("namespace/name/content/revision/foo/bar").toString());
} }
@@ -84,7 +85,7 @@ public class FileObjectToFileObjectDtoMapperTest {
mapper.setRegistry(registry); mapper.setRegistry(registry);
FileObject fileObject = createFileObject(); FileObject fileObject = createFileObject();
FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("hitchhiker", "hog"), "42"); FileObjectDto dto = mapper.map(fileObject, new NamespaceAndName("hitchhiker", "hog"), new BrowserResult("42", fileObject));
assertThat(dto.getLinks().getLinkBy("hog").get().getHref()).isEqualTo("http://hitchhiker/hog/foo/42"); assertThat(dto.getLinks().getLinkBy("hog").get().getHref()).isEqualTo("http://hitchhiker/hog/foo/42");
} }