mirror of
https://github.com/scm-manager/scm-manager.git
synced 2026-03-06 12:20:56 +01:00
Merge pull request #1169 from scm-manager/feature/show-git-trailer
Show git trailer
This commit is contained in:
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
### Added
|
||||
- Option to configure jvm parameter of docker container with env JAVA_OPTS or with arguments ([#1175](https://github.com/scm-manager/scm-manager/pull/1175))
|
||||
- Added links in diff views to expand the gaps between "hunks" ([#1178](https://github.com/scm-manager/scm-manager/pull/1178))
|
||||
- Show commit contributors in table on changeset details view ([#1169](https://github.com/scm-manager/scm-manager/pull/1169))
|
||||
|
||||
### Fixed
|
||||
- Avoid caching of detected browser language ([#1176](https://github.com/scm-manager/scm-manager/pull/1176))
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
@@ -32,6 +32,7 @@ import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@@ -58,6 +59,8 @@ public class ChangesetDto extends HalRepresentation {
|
||||
*/
|
||||
private String description;
|
||||
|
||||
private List<ContributorDto> contributors;
|
||||
|
||||
public ChangesetDto(Links links, Embedded embedded) {
|
||||
super(links, embedded);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
import lombok.Setter;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@NoArgsConstructor
|
||||
public class ContributorDto {
|
||||
private String type;
|
||||
private PersonDto person;
|
||||
}
|
||||
@@ -31,6 +31,7 @@ import sonia.scm.util.Util;
|
||||
import sonia.scm.util.ValidationUtil;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
@@ -79,6 +80,11 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
*/
|
||||
private List<String> tags;
|
||||
|
||||
/**
|
||||
* Trailers for this changeset like reviewers or co-authors
|
||||
*/
|
||||
private Collection<Contributor> contributors;
|
||||
|
||||
public Changeset() {}
|
||||
|
||||
public Changeset(String id, Long date, Person author)
|
||||
@@ -225,6 +231,15 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
return tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns collection of contributors for this changeset.
|
||||
* @return collection of contributors
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public Collection<Contributor> getContributors() {
|
||||
return contributors;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the changeset is valid.
|
||||
*
|
||||
@@ -300,4 +315,37 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
|
||||
this.tags = tags;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the collection of contributors.
|
||||
* @param contributors collection of contributors
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void setContributors(Collection<Contributor> contributors) {
|
||||
this.contributors = new ArrayList<>(contributors);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a contributor to the list of contributors.
|
||||
* @param contributor contributor to add
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void addContributor(Contributor contributor) {
|
||||
if (contributors == null) {
|
||||
contributors = new ArrayList<>();
|
||||
}
|
||||
contributors.add(contributor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds all contributors from the given collection to the list of contributors.
|
||||
* @param contributors collection of contributor
|
||||
* @since 2.1.0
|
||||
*/
|
||||
public void addContributors(Collection<Contributor> contributors) {
|
||||
if (this.contributors == null) {
|
||||
this.contributors = new ArrayList<>(contributors);
|
||||
} else {
|
||||
this.contributors.addAll(contributors);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
35
scm-core/src/main/java/sonia/scm/repository/Contributor.java
Normal file
35
scm-core/src/main/java/sonia/scm/repository/Contributor.java
Normal file
@@ -0,0 +1,35 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import lombok.Value;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
@Value
|
||||
public class Contributor implements Serializable {
|
||||
private String type;
|
||||
private Person person;
|
||||
}
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
//~--- non-JDK imports --------------------------------------------------------
|
||||
@@ -171,8 +171,8 @@ public class GitChangesetConverter implements Closeable
|
||||
|
||||
long date = GitUtil.getCommitTime(commit);
|
||||
PersonIdent authorIndent = commit.getAuthorIdent();
|
||||
Person author = new Person(authorIndent.getName(),
|
||||
authorIndent.getEmailAddress());
|
||||
PersonIdent committerIdent = commit.getCommitterIdent();
|
||||
Person author = createPersonFor(authorIndent);
|
||||
String message = commit.getFullMessage();
|
||||
|
||||
if (message != null)
|
||||
@@ -181,6 +181,9 @@ public class GitChangesetConverter implements Closeable
|
||||
}
|
||||
|
||||
Changeset changeset = new Changeset(id, date, author, message);
|
||||
if (!committerIdent.equals(authorIndent)) {
|
||||
changeset.addContributor(new Contributor("Committed-by", createPersonFor(committerIdent)));
|
||||
}
|
||||
|
||||
if (parentList != null)
|
||||
{
|
||||
@@ -201,6 +204,9 @@ public class GitChangesetConverter implements Closeable
|
||||
return changeset;
|
||||
}
|
||||
|
||||
public Person createPersonFor(PersonIdent personIndent) {
|
||||
return new Person(personIndent.getName(), personIndent.getEmailAddress());
|
||||
}
|
||||
|
||||
|
||||
//~--- fields ---------------------------------------------------------------
|
||||
|
||||
@@ -25,7 +25,6 @@
|
||||
package sonia.scm.repository.spi;
|
||||
|
||||
import com.google.common.io.Files;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
@@ -34,6 +33,7 @@ import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.ChangesetPagingResult;
|
||||
import sonia.scm.repository.GitRepositoryConfig;
|
||||
import sonia.scm.repository.Modifications;
|
||||
import sonia.scm.repository.Person;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
@@ -271,6 +271,20 @@ public class GitLogCommandTest extends AbstractGitCommandTestBase
|
||||
assertEquals("master", changesets.getBranchName());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void shouldAppendCommitterAsContributor() {
|
||||
LogCommandRequest request = new LogCommandRequest();
|
||||
request.setStartChangeset("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
request.setEndChangeset("fcd0ef1831e4002ac43ea539f4094334c79ea9ec");
|
||||
|
||||
ChangesetPagingResult changesets = createCommand().getChangesets(request);
|
||||
Changeset changeset = changesets.getChangesets().get(0);
|
||||
|
||||
assertThat(changeset.getContributors()).hasSize(1);
|
||||
assertThat(changeset.getContributors().iterator().next().getPerson())
|
||||
.isEqualTo(new Person("Sebastian Sdorra", "s.sdorra@ostfalia.de"));
|
||||
}
|
||||
|
||||
private void setRepositoryHeadReference(String s) throws IOException {
|
||||
Files.write(s, repositoryHeadReferenceFile(), defaultCharset());
|
||||
}
|
||||
|
||||
51
scm-ui/ui-components/src/CommaSeparatedList.tsx
Normal file
51
scm-ui/ui-components/src/CommaSeparatedList.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import React, { FC } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
const CommaSeparatedList: FC = ({ children }) => {
|
||||
const [t] = useTranslation("commons");
|
||||
const childArray = React.Children.toArray(children);
|
||||
return (
|
||||
<>
|
||||
{childArray.map((p, i) => {
|
||||
if (i === 0) {
|
||||
return <React.Fragment key={i}>{p}</React.Fragment>;
|
||||
} else if (i + 1 === childArray.length) {
|
||||
return (
|
||||
<React.Fragment key={i}>
|
||||
{" "}
|
||||
{t("commaSeparatedList.lastDivider")} {p}{" "}
|
||||
</React.Fragment>
|
||||
);
|
||||
} else {
|
||||
return <React.Fragment key={i}>, {p}</React.Fragment>;
|
||||
}
|
||||
})}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommaSeparatedList;
|
||||
@@ -27,6 +27,7 @@ import { withContextPath } from "./urls";
|
||||
type Props = {
|
||||
src: string;
|
||||
alt: string;
|
||||
title?: string;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
@@ -40,8 +41,8 @@ class Image extends React.Component<Props> {
|
||||
};
|
||||
|
||||
render() {
|
||||
const { alt, className } = this.props;
|
||||
return <img className={className} src={this.createImageSrc()} alt={alt} />;
|
||||
const { alt, title, className } = this.props;
|
||||
return <img className={className} src={this.createImageSrc()} alt={alt} title={title} />;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
250
scm-ui/ui-components/src/__resources__/changesets.tsx
Normal file
250
scm-ui/ui-components/src/__resources__/changesets.tsx
Normal file
@@ -0,0 +1,250 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { Changeset, PagedCollection } from "@scm-manager/ui-types";
|
||||
|
||||
const one: Changeset = {
|
||||
id: "a88567ef1e9528a700555cad8c4576b72fc7c6dd",
|
||||
author: { mail: "scm-admin@scm-manager.org", name: "SCM Administrator" },
|
||||
date: new Date("2020-06-09T06:34:47Z"),
|
||||
description:
|
||||
"The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive. The craft was stolen by then-President Zaphod Beeblebrox at the official launch of the ship, as he was supposed to be officiating the launch. Later, during the use of the Infinite Improbability Drive, the ship picked up Arthur Dent and Ford Prefect, who were floating unprotected in deep space in the same star sector, having just escaped the destruction of the same planet.\n\n",
|
||||
contributors: [
|
||||
{
|
||||
type: "Committed-by",
|
||||
person: { mail: "zaphod.beeblebrox@hitchhiker.cm", name: "Zaphod Beeblebrox" }
|
||||
},
|
||||
{ type: "Co-authored-by", person: { mail: "ford.prefect@hitchhiker.com", name: "Ford Prefect" } }
|
||||
],
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/sources/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
},
|
||||
modifications: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/modifications/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
},
|
||||
diffParsed: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/a88567ef1e9528a700555cad8c4576b72fc7c6dd/parsed"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
parents: [
|
||||
{
|
||||
id: "d21cc6c359270aef2196796f4d96af65f51866dc",
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const two: Changeset = {
|
||||
id: "d21cc6c359270aef2196796f4d96af65f51866dc",
|
||||
author: { mail: "scm-admin@scm-manager.org", name: "SCM Administrator" },
|
||||
date: new Date("2020-06-09T05:39:50Z"),
|
||||
description: 'Change heading to "Heart Of Gold"\n\n',
|
||||
contributors: [
|
||||
{
|
||||
type: "Committed-by",
|
||||
person: { mail: "zaphod.beeblebrox@hitchhiker.cm", name: "Zaphod Beeblebrox" }
|
||||
}
|
||||
],
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/sources/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
},
|
||||
modifications: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/modifications/d21cc6c359270aef2196796f4d96af65f51866dc"
|
||||
},
|
||||
diffParsed: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/d21cc6c359270aef2196796f4d96af65f51866dc/parsed"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
parents: [
|
||||
{
|
||||
id: "e163c8f632db571c9aa51a8eb440e37cf550b825",
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const three: Changeset = {
|
||||
id: "e163c8f632db571c9aa51a8eb440e37cf550b825",
|
||||
author: { mail: "scm-admin@scm-manager.org", name: "SCM Administrator" },
|
||||
date: new Date("2020-06-09T05:25:16Z"),
|
||||
description: "initialize repository",
|
||||
contributors: [],
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/sources/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
},
|
||||
modifications: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/modifications/e163c8f632db571c9aa51a8eb440e37cf550b825"
|
||||
},
|
||||
diffParsed: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/e163c8f632db571c9aa51a8eb440e37cf550b825/parsed"
|
||||
}
|
||||
},
|
||||
_embedded: { tags: [], branches: [], parents: [] }
|
||||
};
|
||||
|
||||
const four: Changeset = {
|
||||
id: "b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930",
|
||||
author: { mail: "scm-admin@scm-manager.org", name: "SCM Administrator" },
|
||||
date: new Date("2020-06-09T09:23:49Z"),
|
||||
description: "Added design docs\n\n",
|
||||
contributors: [
|
||||
{ type: "Co-authored-by", person: { mail: "ford.prefect@hitchhiker.com", name: "Ford Prefect" } },
|
||||
{ type: "Co-authored-by", person: { mail: "zaphod.beeblebrox@hitchhiker.cm", name: "Zaphod Beeblebrox" } },
|
||||
{ type: "Co-authored-by", person: { mail: "trillian@hitchhiker.cm", name: "Tricia Marie McMillan" } }
|
||||
],
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930"
|
||||
},
|
||||
sources: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/sources/b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930"
|
||||
},
|
||||
modifications: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/modifications/b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930"
|
||||
},
|
||||
diffParsed: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/b6c6f8fbd0d490936fae7d26ffdd4695cc2a0930/parsed"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
tags: [],
|
||||
branches: [],
|
||||
parents: [
|
||||
{
|
||||
id: "a88567ef1e9528a700555cad8c4576b72fc7c6dd",
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/changesets/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
},
|
||||
diff: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/diff/a88567ef1e9528a700555cad8c4576b72fc7c6dd"
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const changesets: PagedCollection = {
|
||||
page: 0,
|
||||
pageTotal: 1,
|
||||
_links: {
|
||||
self: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/branches/master/changesets/?page=0&pageSize=10"
|
||||
},
|
||||
first: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/branches/master/changesets/?page=0&pageSize=10"
|
||||
},
|
||||
last: {
|
||||
href:
|
||||
"http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/branches/master/changesets/?page=0&pageSize=10"
|
||||
}
|
||||
},
|
||||
_embedded: {
|
||||
changesets: [one, two, three, four],
|
||||
branch: {
|
||||
name: "master",
|
||||
_links: {
|
||||
self: { href: "http://localhost:8081/scm/api/v2/repositories/hitchhiker/heart-of-gold/branches/master" }
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
export { one, two, three, four };
|
||||
export default changesets;
|
||||
@@ -831,6 +831,937 @@ exports[`Storyshots CardColumnSmall Minimal 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets Co-Authors with avatar 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<figure
|
||||
className="ChangesetRow__AvatarFigure-tkpti5-1 cqLirB media-left"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__FixedSizedAvatar-tkpti5-2 fqLFVn image"
|
||||
>
|
||||
<img
|
||||
alt="SCM Administrator"
|
||||
className="has-rounded-border"
|
||||
src="https://robohash.org/scm-admin@scm-manager.org"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
Added design docs
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
|
||||
commaSeparatedList.lastDivider
|
||||
|
||||
changeset.contributors.coAuthoredBy
|
||||
|
||||
<span
|
||||
className="ChangesetAuthor__AvatarList-sc-1oz0xgw-0 glabbT"
|
||||
>
|
||||
<a
|
||||
href="mailto:ford.prefect@hitchhiker.com"
|
||||
title="changeset.contributors.mailto ford.prefect@hitchhiker.com"
|
||||
>
|
||||
<img
|
||||
alt="Ford Prefect"
|
||||
className="ContributorAvatar-sc-1yz8zn-0 lgGcHZ"
|
||||
src="https://robohash.org/ford.prefect@hitchhiker.com"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:zaphod.beeblebrox@hitchhiker.cm"
|
||||
title="changeset.contributors.mailto zaphod.beeblebrox@hitchhiker.cm"
|
||||
>
|
||||
<img
|
||||
alt="Zaphod Beeblebrox"
|
||||
className="ContributorAvatar-sc-1yz8zn-0 lgGcHZ"
|
||||
src="https://robohash.org/zaphod.beeblebrox@hitchhiker.cm"
|
||||
/>
|
||||
</a>
|
||||
<a
|
||||
href="mailto:trillian@hitchhiker.cm"
|
||||
title="changeset.contributors.mailto trillian@hitchhiker.cm"
|
||||
>
|
||||
<img
|
||||
alt="Tricia Marie McMillan"
|
||||
className="ContributorAvatar-sc-1yz8zn-0 lgGcHZ"
|
||||
src="https://robohash.org/trillian@hitchhiker.cm"
|
||||
/>
|
||||
</a>
|
||||
</span>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets Commiter and Co-Authors with avatar 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<figure
|
||||
className="ChangesetRow__AvatarFigure-tkpti5-1 cqLirB media-left"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__FixedSizedAvatar-tkpti5-2 fqLFVn image"
|
||||
>
|
||||
<img
|
||||
alt="SCM Administrator"
|
||||
className="has-rounded-border"
|
||||
src="https://robohash.org/scm-admin@scm-manager.org"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive. The craft was stolen by then-President Zaphod Beeblebrox at the official launch of the ship, as he was supposed to be officiating the launch. Later, during the use of the Infinite Improbability Drive, the ship picked up Arthur Dent and Ford Prefect, who were floating unprotected in deep space in the same star sector, having just escaped the destruction of the same planet.
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
,
|
||||
changeset.contributors.committedBy
|
||||
|
||||
<a
|
||||
href="mailto:zaphod.beeblebrox@hitchhiker.cm"
|
||||
title="changeset.contributors.mailto zaphod.beeblebrox@hitchhiker.cm"
|
||||
>
|
||||
<img
|
||||
alt="Zaphod Beeblebrox"
|
||||
className="ContributorAvatar-sc-1yz8zn-0 lgGcHZ"
|
||||
src="https://robohash.org/zaphod.beeblebrox@hitchhiker.cm"
|
||||
/>
|
||||
</a>
|
||||
|
||||
commaSeparatedList.lastDivider
|
||||
|
||||
changeset.contributors.coAuthoredBy
|
||||
|
||||
<a
|
||||
href="mailto:ford.prefect@hitchhiker.com"
|
||||
title="changeset.contributors.mailto ford.prefect@hitchhiker.com"
|
||||
>
|
||||
<img
|
||||
alt="Ford Prefect"
|
||||
className="ContributorAvatar-sc-1yz8zn-0 lgGcHZ"
|
||||
src="https://robohash.org/ford.prefect@hitchhiker.com"
|
||||
/>
|
||||
</a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets Default 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
initialize repository
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets With Committer 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
Change heading to "Heart Of Gold"
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
|
||||
commaSeparatedList.lastDivider
|
||||
|
||||
changeset.contributors.committedBy
|
||||
|
||||
<a
|
||||
href="mailto:zaphod.beeblebrox@hitchhiker.cm"
|
||||
title="changeset.contributors.mailto zaphod.beeblebrox@hitchhiker.cm"
|
||||
>
|
||||
Zaphod Beeblebrox
|
||||
</a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets With Committer and Co-Author 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
The starship Heart of Gold was the first spacecraft to make use of the Infinite Improbability Drive. The craft was stolen by then-President Zaphod Beeblebrox at the official launch of the ship, as he was supposed to be officiating the launch. Later, during the use of the Infinite Improbability Drive, the ship picked up Arthur Dent and Ford Prefect, who were floating unprotected in deep space in the same star sector, having just escaped the destruction of the same planet.
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
,
|
||||
changeset.contributors.committedBy
|
||||
|
||||
<a
|
||||
href="mailto:zaphod.beeblebrox@hitchhiker.cm"
|
||||
title="changeset.contributors.mailto zaphod.beeblebrox@hitchhiker.cm"
|
||||
>
|
||||
Zaphod Beeblebrox
|
||||
</a>
|
||||
|
||||
commaSeparatedList.lastDivider
|
||||
|
||||
changeset.contributors.coAuthoredBy
|
||||
|
||||
<a
|
||||
href="mailto:ford.prefect@hitchhiker.com"
|
||||
title="changeset.contributors.mailto ford.prefect@hitchhiker.com"
|
||||
>
|
||||
Ford Prefect
|
||||
</a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets With avatar 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<figure
|
||||
className="ChangesetRow__AvatarFigure-tkpti5-1 cqLirB media-left"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__FixedSizedAvatar-tkpti5-2 fqLFVn image"
|
||||
>
|
||||
<img
|
||||
alt="SCM Administrator"
|
||||
className="has-rounded-border"
|
||||
src="test-file-stub"
|
||||
/>
|
||||
</div>
|
||||
</figure>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
initialize repository
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots Changesets With multiple Co-Authors 1`] = `
|
||||
<div
|
||||
className="Changesetsstories__Wrapper-sc-122npan-0 iszpMD box box-link-shadow"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Wrapper-tkpti5-0 iLSrdy"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless is-mobile"
|
||||
>
|
||||
<div
|
||||
className="column is-three-fifths"
|
||||
>
|
||||
<div
|
||||
className="columns is-gapless"
|
||||
>
|
||||
<div
|
||||
className="column is-four-fifths"
|
||||
>
|
||||
<div
|
||||
className="media"
|
||||
>
|
||||
<div
|
||||
className="ChangesetRow__Metadata-tkpti5-3 ibQmQZ media-right"
|
||||
>
|
||||
<h4
|
||||
className="has-text-weight-bold is-ellipsis-overflow"
|
||||
>
|
||||
Added design docs
|
||||
</h4>
|
||||
<p
|
||||
className="is-hidden-touch"
|
||||
/>
|
||||
<p
|
||||
className="is-hidden-desktop"
|
||||
/>
|
||||
<p
|
||||
className="ChangesetRow__AuthorWrapper-tkpti5-4 cVHztK is-size-7 is-ellipsis-overflow"
|
||||
>
|
||||
changeset.contributors.authoredBy
|
||||
|
||||
<a
|
||||
href="mailto:scm-admin@scm-manager.org"
|
||||
title="changeset.contributors.mailto scm-admin@scm-manager.org"
|
||||
>
|
||||
SCM Administrator
|
||||
</a>
|
||||
|
||||
commaSeparatedList.lastDivider
|
||||
|
||||
changeset.contributors.coAuthoredBy
|
||||
|
||||
<a
|
||||
title="- Ford Prefect
|
||||
- Zaphod Beeblebrox
|
||||
- Tricia Marie McMillan"
|
||||
>
|
||||
changeset.contributors.more
|
||||
</a>
|
||||
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredColumn-tkpti5-5 idGTGx column"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="ChangesetRow__VCenteredChildColumn-tkpti5-6 gmuCVd column is-flex"
|
||||
>
|
||||
<div
|
||||
className="ButtonAddons__Flex-sc-182golj-0 cicrPZ field has-addons is-marginless"
|
||||
>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-exchange-alt has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.details
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
<p
|
||||
className="control"
|
||||
>
|
||||
<button
|
||||
className="button is-default is-reduced-mobile"
|
||||
onClick={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
className="icon is-medium"
|
||||
>
|
||||
<i
|
||||
className="fas fa-code has-text-inherit"
|
||||
/>
|
||||
</span>
|
||||
<span>
|
||||
changeset.buttons.sources
|
||||
|
||||
</span>
|
||||
</button>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`Storyshots DateFromNow Default 1`] = `
|
||||
<div>
|
||||
<p>
|
||||
|
||||
@@ -22,9 +22,10 @@
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
export type Person = {
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
import { Person } from "@scm-manager/ui-types";
|
||||
|
||||
// re export type to avoid breaking changes,
|
||||
// after the type was moved to ui-types
|
||||
export { Person };
|
||||
|
||||
export const EXTENSION_POINT = "avatar.factory";
|
||||
|
||||
@@ -76,6 +76,7 @@ export { default as OverviewPageActions } from "./OverviewPageActions";
|
||||
export { default as CardColumnGroup } from "./CardColumnGroup";
|
||||
export { default as CardColumn } from "./CardColumn";
|
||||
export { default as CardColumnSmall } from "./CardColumnSmall";
|
||||
export { default as CommaSeparatedList } from "./CommaSeparatedList";
|
||||
|
||||
export { default as comparators } from "./comparators";
|
||||
|
||||
|
||||
@@ -21,53 +21,157 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Changeset } from "@scm-manager/ui-types";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
import { WithTranslation, withTranslation } from "react-i18next";
|
||||
import React, { FC } from "react";
|
||||
import { Changeset, Person } from "@scm-manager/ui-types";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useBinder } from "@scm-manager/ui-extensions";
|
||||
import { EXTENSION_POINT } from "../../avatar/Avatar";
|
||||
import styled from "styled-components";
|
||||
import CommaSeparatedList from "../../CommaSeparatedList";
|
||||
import ContributorAvatar from "./ContributorAvatar";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
type Props = {
|
||||
changeset: Changeset;
|
||||
};
|
||||
|
||||
class ChangesetAuthor extends React.Component<Props> {
|
||||
render() {
|
||||
const { changeset } = this.props;
|
||||
if (!changeset.author) {
|
||||
return null;
|
||||
}
|
||||
type PersonProps = {
|
||||
person: Person;
|
||||
displayTextOnly?: boolean;
|
||||
};
|
||||
|
||||
const { name, mail } = changeset.author;
|
||||
if (mail) {
|
||||
return this.withExtensionPoint(this.renderWithMail(name, mail));
|
||||
}
|
||||
return this.withExtensionPoint(<>{name}</>);
|
||||
const useAvatar = (person: Person): string | undefined => {
|
||||
const binder = useBinder();
|
||||
const factory: (person: Person) => string | undefined = binder.getExtension(EXTENSION_POINT);
|
||||
if (factory) {
|
||||
return factory(person);
|
||||
}
|
||||
};
|
||||
|
||||
renderWithMail(name: string, mail: string) {
|
||||
const { t } = this.props;
|
||||
const AvatarList = styled.span`
|
||||
& > :not(:last-child) {
|
||||
margin-right: 0.25em;
|
||||
}
|
||||
`;
|
||||
|
||||
type PersonAvatarProps = {
|
||||
person: Person;
|
||||
avatar: string;
|
||||
};
|
||||
|
||||
const ContributorWithAvatar: FC<PersonAvatarProps> = ({ person, avatar }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
if (person.mail) {
|
||||
return (
|
||||
<a href={"mailto:" + mail} title={t("changeset.author.mailto") + " " + mail}>
|
||||
{name}
|
||||
<a href={"mailto:" + person.mail} title={t("changeset.contributors.mailto") + " " + person.mail}>
|
||||
<ContributorAvatar src={avatar} alt={person.name} />
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <ContributorAvatar src={avatar} alt={person.name} title={person.name} />;
|
||||
};
|
||||
|
||||
withExtensionPoint(child: any) {
|
||||
const { t } = this.props;
|
||||
const SingleContributor: FC<PersonProps> = ({ person, displayTextOnly }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const avatar = useAvatar(person);
|
||||
if (!displayTextOnly && avatar) {
|
||||
return <ContributorWithAvatar person={person} avatar={avatar} />;
|
||||
}
|
||||
if (person.mail) {
|
||||
return (
|
||||
<a href={"mailto:" + person.mail} title={t("changeset.contributors.mailto") + " " + person.mail}>
|
||||
{person.name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <>{person.name}</>;
|
||||
};
|
||||
|
||||
type PersonsProps = {
|
||||
persons: Person[];
|
||||
label: string;
|
||||
displayTextOnly?: boolean;
|
||||
};
|
||||
|
||||
const Contributors: FC<PersonsProps> = ({ persons, label, displayTextOnly }) => {
|
||||
const binder = useBinder();
|
||||
|
||||
const [t] = useTranslation("repos");
|
||||
if (persons.length === 1) {
|
||||
return (
|
||||
<>
|
||||
{t("changeset.author.prefix")} {child}
|
||||
<ExtensionPoint
|
||||
name="changesets.author.suffix"
|
||||
props={{
|
||||
changeset: this.props.changeset
|
||||
}}
|
||||
renderAll={true}
|
||||
/>
|
||||
{t(label)} <SingleContributor person={persons[0]} displayTextOnly={displayTextOnly} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default withTranslation("repos")(ChangesetAuthor);
|
||||
const avatarFactory = binder.getExtension(EXTENSION_POINT);
|
||||
if (avatarFactory) {
|
||||
return (
|
||||
<>
|
||||
{t(label)}{" "}
|
||||
<AvatarList>
|
||||
{persons.map(p => (
|
||||
<ContributorWithAvatar key={p.name} person={p} avatar={avatarFactory(p)} />
|
||||
))}
|
||||
</AvatarList>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return (
|
||||
<>
|
||||
{t(label)}{" "}
|
||||
<a title={persons.map(person => "- " + person.name).join("\n")}>
|
||||
{t("changeset.contributors.more", { count: persons.length })}
|
||||
</a>
|
||||
</>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
const emptyListOfContributors: Person[] = [];
|
||||
|
||||
const ChangesetAuthor: FC<Props> = ({ changeset }) => {
|
||||
const binder = useBinder();
|
||||
|
||||
const getCoAuthors = () => {
|
||||
return filterContributorsByType("Co-authored-by");
|
||||
};
|
||||
|
||||
const getCommitters = () => {
|
||||
return filterContributorsByType("Committed-by");
|
||||
};
|
||||
|
||||
const filterContributorsByType = (type: string) => {
|
||||
if (changeset.contributors) {
|
||||
return changeset.contributors.filter(p => p.type === type).map(contributor => contributor.person);
|
||||
}
|
||||
return emptyListOfContributors;
|
||||
};
|
||||
|
||||
const authorLine = [];
|
||||
if (changeset.author) {
|
||||
authorLine.push(
|
||||
<Contributors persons={[changeset.author]} label={"changeset.contributors.authoredBy"} displayTextOnly={true} />
|
||||
);
|
||||
}
|
||||
|
||||
const commiters = getCommitters();
|
||||
if (commiters.length > 0) {
|
||||
authorLine.push(<Contributors persons={commiters} label={"changeset.contributors.committedBy"} />);
|
||||
}
|
||||
|
||||
const coAuthors = getCoAuthors();
|
||||
if (coAuthors.length > 0) {
|
||||
authorLine.push(<Contributors persons={coAuthors} label={"changeset.contributors.coAuthoredBy"} />);
|
||||
}
|
||||
|
||||
// extensions
|
||||
const extensions = binder.getExtensions("changesets.author.suffix", { changeset });
|
||||
if (extensions) {
|
||||
coAuthors.push(...extensions);
|
||||
}
|
||||
|
||||
return <CommaSeparatedList>{authorLine}</CommaSeparatedList>;
|
||||
};
|
||||
|
||||
export default ChangesetAuthor;
|
||||
|
||||
@@ -123,7 +123,7 @@ class ChangesetRow extends React.Component<Props> {
|
||||
<p className="is-hidden-desktop">
|
||||
<Trans i18nKey="repos:changeset.shortSummary" components={[changesetId, dateFromNow]} />
|
||||
</p>
|
||||
<AuthorWrapper className="is-size-7">
|
||||
<AuthorWrapper className="is-size-7 is-ellipsis-overflow">
|
||||
<ChangesetAuthor changeset={changeset} />
|
||||
</AuthorWrapper>
|
||||
</Metadata>
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import { storiesOf } from "@storybook/react";
|
||||
import * as React from "react";
|
||||
import styled from "styled-components";
|
||||
import { MemoryRouter } from "react-router-dom";
|
||||
import repository from "../../__resources__/repository";
|
||||
import ChangesetRow from "./ChangesetRow";
|
||||
import {one, two, three, four} from "../../__resources__/changesets";
|
||||
import {Binder, BinderContext} from "@scm-manager/ui-extensions";
|
||||
// @ts-ignore
|
||||
import hitchhiker from "../../__resources__/hitchhiker.png";
|
||||
import {Person} from "../../avatar/Avatar";
|
||||
import {Changeset} from "@scm-manager/ui-types/src";
|
||||
|
||||
const Wrapper = styled.div`
|
||||
margin: 2rem;
|
||||
`;
|
||||
|
||||
const robohash = (person: Person) => {
|
||||
return `https://robohash.org/${person.mail}`;
|
||||
}
|
||||
|
||||
const withAvatarFactory = (factory: (person: Person) => string, changeset: Changeset) => {
|
||||
const binder = new Binder("changeset stories");
|
||||
binder.bind("avatar.factory", factory);
|
||||
return (
|
||||
<BinderContext.Provider value={binder}>
|
||||
<ChangesetRow repository={repository} changeset={changeset} />
|
||||
</BinderContext.Provider>
|
||||
);
|
||||
};
|
||||
|
||||
storiesOf("Changesets", module)
|
||||
.addDecorator(story => <MemoryRouter initialEntries={["/"]}>{story()}</MemoryRouter>)
|
||||
.addDecorator(storyFn => <Wrapper className="box box-link-shadow">{storyFn()}</Wrapper>)
|
||||
.add("Default", () => (
|
||||
<ChangesetRow repository={repository} changeset={three} />
|
||||
))
|
||||
.add("With Committer", () => (
|
||||
<ChangesetRow repository={repository} changeset={two} />
|
||||
))
|
||||
.add("With Committer and Co-Author", () => (
|
||||
<ChangesetRow repository={repository} changeset={one} />
|
||||
))
|
||||
.add("With multiple Co-Authors", () => (
|
||||
<ChangesetRow repository={repository} changeset={four} />
|
||||
))
|
||||
.add("With avatar", () => {
|
||||
return withAvatarFactory(person => hitchhiker, three);
|
||||
})
|
||||
.add("Commiter and Co-Authors with avatar", () => {
|
||||
return withAvatarFactory(robohash, one);
|
||||
})
|
||||
.add("Co-Authors with avatar", () => {
|
||||
return withAvatarFactory(robohash, four);
|
||||
});
|
||||
@@ -0,0 +1,36 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
import styled from "styled-components";
|
||||
import Image from "../../Image";
|
||||
|
||||
const ContributorAvatar = styled(Image)`
|
||||
width: 1em;
|
||||
height: 1em;
|
||||
vertical-align: middle;
|
||||
border-radius: 0.25em;
|
||||
margin-bottom: 0.2em;
|
||||
`;
|
||||
|
||||
export default ContributorAvatar;
|
||||
@@ -34,3 +34,4 @@ export { default as ChangesetRow } from "./ChangesetRow";
|
||||
export { default as ChangesetTag } from "./ChangesetTag";
|
||||
export { default as ChangesetTags } from "./ChangesetTags";
|
||||
export { default as ChangesetTagsCollapsed } from "./ChangesetTagsCollapsed";
|
||||
export { default as ContributorAvatar } from "./ContributorAvatar";
|
||||
|
||||
@@ -26,14 +26,17 @@ import { Collection, Links } from "./hal";
|
||||
import { Tag } from "./Tags";
|
||||
import { Branch } from "./Branches";
|
||||
|
||||
export type Person = {
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
|
||||
export type Changeset = Collection & {
|
||||
id: string;
|
||||
date: Date;
|
||||
author: {
|
||||
name: string;
|
||||
mail?: string;
|
||||
};
|
||||
author: Person;
|
||||
description: string;
|
||||
contributors?: Contributor[];
|
||||
_links: Links;
|
||||
_embedded: {
|
||||
tags?: Tag[];
|
||||
@@ -42,6 +45,11 @@ export type Changeset = Collection & {
|
||||
};
|
||||
};
|
||||
|
||||
export type Contributor = {
|
||||
person: Person;
|
||||
type: string;
|
||||
};
|
||||
|
||||
export type ParentChangeset = {
|
||||
id: string;
|
||||
_links: Links;
|
||||
|
||||
@@ -29,16 +29,12 @@ export { Me } from "./Me";
|
||||
export { DisplayedUser, User } from "./User";
|
||||
export { Group, Member } from "./Group";
|
||||
|
||||
export {
|
||||
Repository,
|
||||
RepositoryCollection,
|
||||
RepositoryGroup
|
||||
} from "./Repositories";
|
||||
export { Repository, RepositoryCollection, RepositoryGroup } from "./Repositories";
|
||||
export { RepositoryType, RepositoryTypeCollection } from "./RepositoryTypes";
|
||||
|
||||
export { Branch, BranchRequest } from "./Branches";
|
||||
|
||||
export { Changeset } from "./Changesets";
|
||||
export { Changeset, Person, Contributor, ParentChangeset } from "./Changesets";
|
||||
|
||||
export { Tag } from "./Tags";
|
||||
|
||||
@@ -46,22 +42,13 @@ export { Config } from "./Config";
|
||||
|
||||
export { IndexResources } from "./IndexResources";
|
||||
|
||||
export {
|
||||
Permission,
|
||||
PermissionCreateEntry,
|
||||
PermissionCollection
|
||||
} from "./RepositoryPermissions";
|
||||
export { Permission, PermissionCreateEntry, PermissionCollection } from "./RepositoryPermissions";
|
||||
|
||||
export { SubRepository, File } from "./Sources";
|
||||
|
||||
export { SelectValue, AutocompleteObject } from "./Autocomplete";
|
||||
|
||||
export {
|
||||
Plugin,
|
||||
PluginCollection,
|
||||
PluginGroup,
|
||||
PendingPlugins
|
||||
} from "./Plugin";
|
||||
export { Plugin, PluginCollection, PluginGroup, PendingPlugins } from "./Plugin";
|
||||
|
||||
export { RepositoryRole } from "./RepositoryRole";
|
||||
|
||||
|
||||
@@ -104,5 +104,8 @@
|
||||
"community": "Community",
|
||||
"enterprise": "Enterprise"
|
||||
}
|
||||
},
|
||||
"commaSeparatedList": {
|
||||
"lastDivider": "und"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,9 +87,15 @@
|
||||
"shortSummary": "Committet <0/> <1/>",
|
||||
"tags": "Tags",
|
||||
"diffNotSupported": "Diff des Changesets wird von diesem Repositorytyp nicht unterstützt",
|
||||
"author": {
|
||||
"prefix": "Verfasst von",
|
||||
"mailto": "Mail senden an"
|
||||
"contributors": {
|
||||
"mailto": "Mail senden an",
|
||||
"list": "Liste der Mitwirkenden",
|
||||
"authoredBy": "Verfasst von",
|
||||
"committedBy": "Committed von",
|
||||
"coAuthoredBy": "Co-Autoren",
|
||||
"more": "{{count}} mehr",
|
||||
"count": "{{count}} Mitwirkender",
|
||||
"count_plural": "{{count}} Mitwirkende"
|
||||
},
|
||||
"buttons": {
|
||||
"details": "Details",
|
||||
|
||||
@@ -105,5 +105,8 @@
|
||||
"community": "Community",
|
||||
"enterprise": "Enterprise"
|
||||
}
|
||||
},
|
||||
"commaSeparatedList": {
|
||||
"lastDivider": "and"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -87,13 +87,19 @@
|
||||
"shortSummary": "Committed <0/> <1/>",
|
||||
"tags": "Tags",
|
||||
"diffNotSupported": "Diff of changesets is not supported by the type of repository",
|
||||
"author": {
|
||||
"prefix": "Authored by",
|
||||
"mailto": "Send mail to"
|
||||
},
|
||||
"buttons": {
|
||||
"details": "Details",
|
||||
"sources": "Sources"
|
||||
},
|
||||
"contributors": {
|
||||
"mailto": "Send mail to",
|
||||
"list": "List of contributors",
|
||||
"authoredBy": "Authored by",
|
||||
"committedBy": "committed by",
|
||||
"coAuthoredBy": "co authored by",
|
||||
"more": "{{count}} more",
|
||||
"count": "{{count}} Contributor",
|
||||
"count_plural": "{{count}} Contributors"
|
||||
}
|
||||
},
|
||||
"repositoryForm": {
|
||||
|
||||
@@ -89,5 +89,8 @@
|
||||
"passwordConfirmFailed": "Las contraseñas deben ser identicas",
|
||||
"submit": "Guardar",
|
||||
"changedSuccessfully": "Contraseña cambiada correctamente"
|
||||
},
|
||||
"commaSeparatedList": {
|
||||
"lastDivider": "y"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,8 @@
|
||||
"errorSubtitle": "No se han podido recuperar los changesets",
|
||||
"noChangesets": "No se han encontrado changesets para esta rama branch. Los commits podrían haber sido eliminados.",
|
||||
"branchSelectorLabel": "Ramas",
|
||||
"collapseDiffs": "Colapso"
|
||||
"collapseDiffs": "Colapso",
|
||||
"contributors": "Lista de contribuyentes"
|
||||
},
|
||||
"changeset": {
|
||||
"description": "Descripción",
|
||||
|
||||
@@ -21,8 +21,8 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React from "react";
|
||||
import { Trans, WithTranslation, withTranslation } from "react-i18next";
|
||||
import React, { FC, useState } from "react";
|
||||
import { Trans, useTranslation, WithTranslation, withTranslation } from "react-i18next";
|
||||
import classNames from "classnames";
|
||||
import styled from "styled-components";
|
||||
import { ExtensionPoint } from "@scm-manager/ui-extensions";
|
||||
@@ -37,8 +37,10 @@ import {
|
||||
changesets,
|
||||
ChangesetTag,
|
||||
DateFromNow,
|
||||
Level
|
||||
Level,
|
||||
Icon
|
||||
} from "@scm-manager/ui-components";
|
||||
import ContributorTable from "./ContributorTable";
|
||||
|
||||
type Props = WithTranslation & {
|
||||
changeset: Changeset;
|
||||
@@ -63,6 +65,74 @@ const BottomMarginLevel = styled(Level)`
|
||||
margin-bottom: 1rem !important;
|
||||
`;
|
||||
|
||||
const countContributors = (changeset: Changeset) => {
|
||||
if (changeset.contributors) {
|
||||
return changeset.contributors.length + 1;
|
||||
}
|
||||
return 1;
|
||||
};
|
||||
|
||||
const ContributorLine = styled.div`
|
||||
display: flex;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const ContributorColumn = styled.p`
|
||||
flex-grow: 1;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
const CountColumn = styled.p`
|
||||
text-align: right;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const ContributorDetails = styled.div`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-bottom: 1rem;
|
||||
`;
|
||||
|
||||
const ContributorToggleLine = styled.p`
|
||||
cursor: pointer;
|
||||
/** maring-bottom is inherit from content p **/
|
||||
margin-bottom: 0.5rem !important;
|
||||
`;
|
||||
|
||||
const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const [open, setOpen] = useState(false);
|
||||
if (open) {
|
||||
return (
|
||||
<ContributorDetails>
|
||||
<ContributorToggleLine onClick={e => setOpen(!open)}>
|
||||
<Icon name="angle-down" /> {t("changeset.contributors.list")}
|
||||
</ContributorToggleLine>
|
||||
<ContributorTable changeset={changeset} />
|
||||
</ContributorDetails>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
<ContributorLine onClick={e => setOpen(!open)}>
|
||||
<ContributorColumn>
|
||||
<Icon name="angle-right" /> <ChangesetAuthor changeset={changeset} />
|
||||
</ContributorColumn>
|
||||
<CountColumn>
|
||||
(
|
||||
<span className="has-text-link">
|
||||
{t("changeset.contributors.count", { count: countContributors(changeset) })}
|
||||
</span>
|
||||
)
|
||||
</CountColumn>
|
||||
</ContributorLine>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
class ChangesetDetails extends React.Component<Props, State> {
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -100,17 +170,14 @@ class ChangesetDetails extends React.Component<Props, State> {
|
||||
<AvatarImage person={changeset.author} />
|
||||
</RightMarginP>
|
||||
</AvatarWrapper>
|
||||
<div className="media-content">
|
||||
<p>
|
||||
<ChangesetAuthor changeset={changeset} />
|
||||
</p>
|
||||
<div className="media-content is-ellipsis-overflow">
|
||||
<Contributors changeset={changeset} />
|
||||
<p>
|
||||
<Trans i18nKey="repos:changeset.summary" components={[id, date]} />
|
||||
</p>
|
||||
</div>
|
||||
<div className="media-right">{this.renderTags()}</div>
|
||||
</article>
|
||||
|
||||
<p>
|
||||
{description.message.split("\n").map((item, key) => {
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
import React, { FC } from "react";
|
||||
import { Changeset, Person } from "@scm-manager/ui-types";
|
||||
import styled from "styled-components";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useBinder } from "@scm-manager/ui-extensions";
|
||||
import { ContributorAvatar, CommaSeparatedList } from "@scm-manager/ui-components";
|
||||
|
||||
type Props = {
|
||||
changeset: Changeset;
|
||||
};
|
||||
|
||||
const SizedTd = styled.td`
|
||||
width: 10rem;
|
||||
`;
|
||||
|
||||
const Contributor: FC<{ person: Person }> = ({ person }) => {
|
||||
const [t] = useTranslation("repos");
|
||||
const binder = useBinder();
|
||||
const avatarFactory = binder.getExtension("avatar.factory");
|
||||
let prefix = null;
|
||||
if (avatarFactory) {
|
||||
const avatar = avatarFactory(person);
|
||||
if (avatar) {
|
||||
prefix = (
|
||||
<>
|
||||
<ContributorAvatar src={avatar} alt={person.name} />{" "}
|
||||
</>
|
||||
);
|
||||
}
|
||||
}
|
||||
if (person.mail) {
|
||||
return (
|
||||
<a href={"mailto:" + person.mail} title={t("changeset.contributors.mailto") + " " + person.mail}>
|
||||
{prefix}
|
||||
{person.name}
|
||||
</a>
|
||||
);
|
||||
}
|
||||
return <>{person.name}</>;
|
||||
};
|
||||
|
||||
const ContributorTable: FC<Props> = ({ changeset }) => {
|
||||
const [t] = useTranslation("plugins");
|
||||
|
||||
const collectAvailableContributorTypes = () => {
|
||||
if (!changeset.contributors) {
|
||||
return [];
|
||||
}
|
||||
// @ts-ignore
|
||||
return [...new Set(changeset.contributors.map(contributor => contributor.type))];
|
||||
};
|
||||
|
||||
const getPersonsByContributorType = (type: string) => {
|
||||
return changeset.contributors?.filter(contributor => contributor.type === type).map(t => t.person);
|
||||
};
|
||||
|
||||
const getContributorsByType = () => {
|
||||
const availableContributorTypes: string[] = collectAvailableContributorTypes();
|
||||
|
||||
const personsByContributorType = [];
|
||||
for (const type of availableContributorTypes) {
|
||||
personsByContributorType.push({ type, persons: getPersonsByContributorType(type) });
|
||||
}
|
||||
return personsByContributorType;
|
||||
};
|
||||
|
||||
return (
|
||||
<table>
|
||||
<tr>
|
||||
<SizedTd>{t("changeset.contributor.type.author")}:</SizedTd>
|
||||
<td>
|
||||
<Contributor person={changeset.author} />
|
||||
</td>
|
||||
</tr>
|
||||
{getContributorsByType().map(contributor => (
|
||||
<tr key={contributor.type}>
|
||||
<SizedTd>{t("changeset.contributor.type." + contributor.type)}:</SizedTd>
|
||||
<td className="is-ellipsis-overflow is-marginless">
|
||||
<CommaSeparatedList>
|
||||
{contributor.persons!.map(person => (
|
||||
<Contributor key={person.name} person={person} />
|
||||
))}
|
||||
</CommaSeparatedList>
|
||||
</td>
|
||||
</tr>
|
||||
))}
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
export default ContributorTable;
|
||||
@@ -21,19 +21,20 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import de.otto.edison.hal.Embedded;
|
||||
import de.otto.edison.hal.Links;
|
||||
import org.mapstruct.Context;
|
||||
import org.mapstruct.Mapper;
|
||||
import org.mapstruct.Mapping;
|
||||
import org.mapstruct.ObjectFactory;
|
||||
import sonia.scm.repository.Branch;
|
||||
import sonia.scm.repository.Changeset;
|
||||
import sonia.scm.repository.Person;
|
||||
import sonia.scm.repository.Repository;
|
||||
import sonia.scm.repository.Tag;
|
||||
import sonia.scm.repository.Contributor;
|
||||
import sonia.scm.repository.api.Command;
|
||||
import sonia.scm.repository.api.RepositoryService;
|
||||
import sonia.scm.repository.api.RepositoryServiceFactory;
|
||||
@@ -49,7 +50,7 @@ import static de.otto.edison.hal.Link.link;
|
||||
import static de.otto.edison.hal.Links.linkingTo;
|
||||
|
||||
@Mapper
|
||||
public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper{
|
||||
public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMapper implements InstantAttributeMapper, ChangesetToChangesetDtoMapper {
|
||||
|
||||
@Inject
|
||||
private RepositoryServiceFactory serviceFactory;
|
||||
@@ -66,10 +67,9 @@ public abstract class DefaultChangesetToChangesetDtoMapper extends HalAppenderMa
|
||||
@Inject
|
||||
private TagCollectionToDtoMapper tagCollectionToDtoMapper;
|
||||
|
||||
abstract ContributorDto map(Contributor contributor);
|
||||
|
||||
@Mapping(target = "attributes", ignore = true) // We do not map HAL attributes
|
||||
public abstract ChangesetDto map(Changeset changeset, @Context Repository repository);
|
||||
|
||||
abstract PersonDto map(Person person);
|
||||
|
||||
@ObjectFactory
|
||||
ChangesetDto createDto(@Context Repository repository, Changeset source) {
|
||||
|
||||
@@ -111,7 +111,6 @@ import sonia.scm.web.security.DefaultAdministrationContext;
|
||||
import javax.net.ssl.SSLContext;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Sebastian Sdorra
|
||||
*/
|
||||
class ScmServletModule extends ServletModule {
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import sonia.scm.plugin.Extension;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Scanner;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
@Extension
|
||||
public class ChangesetDescriptionContributorProvider implements ChangesetPreProcessorFactory {
|
||||
|
||||
private static final Collection<String> SUPPORTED_CONTRIBUTOR_TYPES = ImmutableSet.of("Co-authored-by", "Reviewed-by", "Signed-off-by", "Committed-by");
|
||||
private static final Pattern CONTRIBUTOR_PATTERN = Pattern.compile("^([\\w-]*):\\W*(.*)\\W+<(.*)>\\W*$");
|
||||
|
||||
@Override
|
||||
public ChangesetPreProcessor createPreProcessor(Repository repository) {
|
||||
return new ContributorChangesetPreProcessor();
|
||||
}
|
||||
|
||||
private static class ContributorChangesetPreProcessor implements ChangesetPreProcessor {
|
||||
@Override
|
||||
public void process(Changeset changeset) {
|
||||
new Worker(changeset).process();
|
||||
}
|
||||
}
|
||||
|
||||
private static class Worker {
|
||||
private final StringBuilder newDescription = new StringBuilder();
|
||||
|
||||
private final Changeset changeset;
|
||||
|
||||
boolean foundEmptyLine;
|
||||
|
||||
private Worker(Changeset changeset) {
|
||||
this.changeset = changeset;
|
||||
}
|
||||
private void process() {
|
||||
try (Scanner scanner = new Scanner(changeset.getDescription())) {
|
||||
while (scanner.hasNextLine()) {
|
||||
handleLine(scanner, scanner.nextLine());
|
||||
}
|
||||
}
|
||||
changeset.setDescription(newDescription.toString());
|
||||
}
|
||||
|
||||
private void handleLine(Scanner scanner, String line) {
|
||||
if (line.trim().isEmpty()) {
|
||||
handleEmptyLine(scanner, line);
|
||||
return;
|
||||
}
|
||||
|
||||
if (foundEmptyLine && checkForContributor(line)) {
|
||||
return;
|
||||
}
|
||||
appendLine(scanner, line);
|
||||
}
|
||||
|
||||
private boolean checkForContributor(String line) {
|
||||
Matcher matcher = CONTRIBUTOR_PATTERN.matcher(line);
|
||||
if (matcher.matches()) {
|
||||
String type = matcher.group(1);
|
||||
String name = matcher.group(2);
|
||||
String mail = matcher.group(3);
|
||||
if (SUPPORTED_CONTRIBUTOR_TYPES.contains(type)) {
|
||||
createContributor(type, name, mail);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void handleEmptyLine(Scanner scanner, String line) {
|
||||
foundEmptyLine = true;
|
||||
appendLine(scanner, line);
|
||||
}
|
||||
|
||||
private void appendLine(Scanner scanner, String line) {
|
||||
newDescription.append(line);
|
||||
if (scanner.hasNextLine()) {
|
||||
newDescription.append('\n');
|
||||
}
|
||||
}
|
||||
|
||||
private void createContributor(String type, String name, String mail) {
|
||||
changeset.addContributor(new Contributor(type, new Person(name, mail)));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,15 @@
|
||||
{
|
||||
"changeset": {
|
||||
"contributor": {
|
||||
"type": {
|
||||
"author": "Autor",
|
||||
"Reviewed-by": "Reviewer",
|
||||
"Co-authored-by": "Co-Autoren",
|
||||
"Committed-by": "Committed von",
|
||||
"Signed-off-by": "Signiert von"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"*": {
|
||||
"displayName": "Globaler Administrator",
|
||||
|
||||
@@ -1,4 +1,15 @@
|
||||
{
|
||||
"changeset": {
|
||||
"contributor": {
|
||||
"type": {
|
||||
"author": "Author",
|
||||
"Reviewed-by": "Reviewers",
|
||||
"Co-authored-by": "Co-Authors",
|
||||
"Committed-by": "Commit by",
|
||||
"Signed-off-by": "Signed-off"
|
||||
}
|
||||
}
|
||||
},
|
||||
"permissions": {
|
||||
"*": {
|
||||
"displayName": "Global administrator",
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
@@ -30,7 +30,6 @@ import org.apache.shiro.subject.Subject;
|
||||
import org.apache.shiro.subject.support.SubjectThreadState;
|
||||
import org.apache.shiro.util.ThreadContext;
|
||||
import org.apache.shiro.util.ThreadState;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.util.Lists;
|
||||
import org.jboss.resteasy.mock.MockHttpRequest;
|
||||
import org.jboss.resteasy.mock.MockHttpResponse;
|
||||
@@ -61,7 +60,9 @@ import java.net.URI;
|
||||
import java.time.Instant;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
@@ -113,7 +114,6 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
@Mock
|
||||
private TagCollectionToDtoMapper tagCollectionToDtoMapper;
|
||||
|
||||
|
||||
@InjectMocks
|
||||
private DefaultChangesetToChangesetDtoMapperImpl changesetToChangesetDtoMapper;
|
||||
|
||||
@@ -156,7 +156,7 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
assertEquals(404, response.getStatus());
|
||||
MediaType contentType = (MediaType) response.getOutputHeaders().getFirst("Content-Type");
|
||||
Assertions.assertThat(response.getContentAsString()).contains("branch", "master", "space/repo");
|
||||
assertThat(response.getContentAsString()).contains("branch", "master", "space/repo");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -196,10 +196,10 @@ public class BranchRootResourceTest extends RepositoryTestBase {
|
||||
dispatcher.invoke(request, response);
|
||||
assertEquals(200, response.getStatus());
|
||||
log.info("Response :{}", response.getContentAsString());
|
||||
assertTrue(response.getContentAsString().contains(String.format("\"id\":\"%s\"", REVISION)));
|
||||
assertTrue(response.getContentAsString().contains(String.format("\"name\":\"%s\"", authorName)));
|
||||
assertTrue(response.getContentAsString().contains(String.format("\"mail\":\"%s\"", authorEmail)));
|
||||
assertTrue(response.getContentAsString().contains(String.format("\"description\":\"%s\"", commit)));
|
||||
assertThat(response.getContentAsString()).contains(String.format("\"id\":\"%s\"", REVISION));
|
||||
assertThat(response.getContentAsString()).contains(String.format("\"name\":\"%s\"", authorName));
|
||||
assertThat(response.getContentAsString()).contains(String.format("\"mail\":\"%s\"", authorEmail));
|
||||
assertThat(response.getContentAsString()).contains(String.format("\"description\":\"%s\"", commit));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
|
||||
@@ -68,7 +68,6 @@ import static org.mockito.Mockito.when;
|
||||
@Slf4j
|
||||
public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
public static final String CHANGESET_PATH = "space/repo/changesets/";
|
||||
public static final String CHANGESET_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + CHANGESET_PATH;
|
||||
|
||||
@@ -86,6 +85,7 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
@Mock
|
||||
private LogCommandBuilder logCommandBuilder;
|
||||
|
||||
@InjectMocks
|
||||
private ChangesetCollectionToDtoMapper changesetCollectionToDtoMapper;
|
||||
|
||||
@InjectMocks
|
||||
@@ -93,11 +93,9 @@ public class ChangesetRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
private ChangesetRootResource changesetRootResource;
|
||||
|
||||
|
||||
private final Subject subject = mock(Subject.class);
|
||||
private final ThreadState subjectThreadState = new SubjectThreadState(subject);
|
||||
|
||||
|
||||
@Before
|
||||
public void prepareEnvironment() {
|
||||
changesetCollectionToDtoMapper = new ChangesetCollectionToDtoMapper(changesetToChangesetDtoMapper, resourceLinks);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
import com.google.inject.util.Providers;
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
|
||||
package sonia.scm.api.v2.resources;
|
||||
|
||||
|
||||
@@ -78,7 +78,6 @@ import static sonia.scm.repository.api.DiffFormat.NATIVE;
|
||||
@Slf4j
|
||||
public class IncomingRootResourceTest extends RepositoryTestBase {
|
||||
|
||||
|
||||
public static final String INCOMING_PATH = "space/repo/incoming/";
|
||||
public static final String INCOMING_CHANGESETS_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
|
||||
public static final String INCOMING_DIFF_URL = "/" + RepositoryRootResource.REPOSITORIES_PATH_V2 + INCOMING_PATH;
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
/*
|
||||
* MIT License
|
||||
*
|
||||
* Copyright (c) 2020-present Cloudogu GmbH and Contributors
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
* of this software and associated documentation files (the "Software"), to deal
|
||||
* in the Software without restriction, including without limitation the rights
|
||||
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
* copies of the Software, and to permit persons to whom the Software is
|
||||
* furnished to do so, subject to the following conditions:
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in all
|
||||
* copies or substantial portions of the Software.
|
||||
*
|
||||
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
* SOFTWARE.
|
||||
*/
|
||||
|
||||
package sonia.scm.repository;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Iterator;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class ChangesetDescriptionContributorProviderTest {
|
||||
|
||||
private static final Repository REPOSITORY = RepositoryTestData.createHeartOfGold();
|
||||
|
||||
private final ChangesetDescriptionContributorProvider changesetDescriptionContributors = new ChangesetDescriptionContributorProvider();
|
||||
|
||||
@Test
|
||||
void shouldReturnEmptyList() {
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
assertThat(contributors).isNullOrEmpty();
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertTrailerWithCoAuthors() {
|
||||
Person person = createPerson("Arthur Dent", "dent@hitchhiker.org");
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox\n\nCo-authored-by: Arthur Dent <dent@hitchhiker.org>");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
Contributor contributor = contributors.iterator().next();
|
||||
assertThat(contributor.getType()).isEqualTo("Co-authored-by");
|
||||
assertThat(contributor.getPerson()).isEqualTo(person);
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertTrailerWithReviewers() {
|
||||
Person person = createPerson("Tricia McMillan", "trillian@hitchhiker.org");
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox\n\nReviewed-by: Tricia McMillan <trillian@hitchhiker.org>");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
Contributor contributor = contributors.iterator().next();
|
||||
|
||||
assertThat(contributor.getType()).isEqualTo("Reviewed-by");
|
||||
assertThat(contributor.getPerson()).isEqualTo(person);
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertTrailerWithSigner() {
|
||||
Person person = createPerson("Tricia McMillan", "trillian@hitchhiker.org");
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox\n\n\nSigned-off-by: Tricia McMillan <trillian@hitchhiker.org>");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
Contributor contributor = contributors.iterator().next();
|
||||
|
||||
assertThat(contributor.getType()).isEqualTo("Signed-off-by");
|
||||
assertThat(contributor.getPerson()).isEqualTo(person);
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertTrailerWithCommitter() {
|
||||
Person person = createPerson("Tricia McMillan", "trillian@hitchhiker.org");
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox\n\nCommitted-by: Tricia McMillan <trillian@hitchhiker.org>");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
Contributor contributor = contributors.iterator().next();
|
||||
|
||||
assertThat(contributor.getType()).isEqualTo("Committed-by");
|
||||
assertThat(contributor.getPerson()).isEqualTo(person);
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertMixedTrailers() {
|
||||
Changeset changeset = createChangeset("zaphod beeblebrox\n\n" +
|
||||
"Committed-by: Tricia McMillan <trillian@hitchhiker.org>\n" +
|
||||
"Signed-off-by: Artur Dent <dent@hitchhiker.org>");
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
Iterator<Contributor> contributorIterator = contributors.iterator();
|
||||
Contributor firstContributor = contributorIterator.next();
|
||||
Contributor secondContributor = contributorIterator.next();
|
||||
|
||||
assertThat(firstContributor.getType()).isEqualTo("Committed-by");
|
||||
assertThat(firstContributor.getPerson())
|
||||
.isEqualTo(createPerson("Tricia McMillan", "trillian@hitchhiker.org"));
|
||||
|
||||
assertThat(secondContributor.getType()).isEqualTo("Signed-off-by");
|
||||
assertThat(secondContributor.getPerson())
|
||||
.isEqualTo(createPerson("Artur Dent", "dent@hitchhiker.org"));
|
||||
|
||||
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotTouchUnknownTrailers() {
|
||||
String originalCommitMessage = "zaphod beeblebrox\n\n" +
|
||||
"Some-strange-tag: Tricia McMillan <trillian@hitchhiker.org>";
|
||||
Changeset changeset = createChangeset(originalCommitMessage);
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
assertThat(contributors).isNullOrEmpty();
|
||||
assertThat(changeset.getDescription()).isEqualTo(originalCommitMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreKnownTrailersWithIllegalNameFormat() {
|
||||
String originalCommitMessage = "zaphod beeblebrox\n\n" +
|
||||
"Committed-by: Tricia McMillan";
|
||||
Changeset changeset = createChangeset(originalCommitMessage);
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
assertThat(contributors).isNullOrEmpty();
|
||||
assertThat(changeset.getDescription()).isEqualTo(originalCommitMessage);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldIgnoreWhitespacesInEmptyLines() {
|
||||
String originalCommitMessage = "zaphod beeblebrox\n \n" +
|
||||
"Committed-by: Tricia McMillan <trillian@hitchhiker.org>";
|
||||
Changeset changeset = createChangeset(originalCommitMessage);
|
||||
|
||||
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
|
||||
Collection<Contributor> contributors = changeset.getContributors();
|
||||
|
||||
assertThat(contributors).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldProcessChangesetsSeparately() {
|
||||
Changeset changeset1 = createChangeset("message one\n\n" +
|
||||
"Committed-by: Tricia McMillan <trillian@hitchhiker.org>");
|
||||
Changeset changeset2 = createChangeset("message two");
|
||||
|
||||
ChangesetPreProcessor preProcessor = changesetDescriptionContributors.createPreProcessor(REPOSITORY);
|
||||
preProcessor.process(changeset1);
|
||||
preProcessor.process(changeset2);
|
||||
|
||||
assertThat(changeset1.getDescription()).isEqualTo("message one\n\n");
|
||||
assertThat(changeset1.getContributors()).isNotEmpty();
|
||||
|
||||
assertThat(changeset2.getDescription()).isEqualTo("message two");
|
||||
assertThat(changeset2.getContributors()).isNullOrEmpty();
|
||||
}
|
||||
|
||||
private Changeset createChangeset(String commitMessage) {
|
||||
Changeset changeset = new Changeset();
|
||||
changeset.setDescription(commitMessage);
|
||||
return changeset;
|
||||
}
|
||||
|
||||
private Person createPerson(String name, String mail) {
|
||||
return new Person(name, mail);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user