Ignore duplicate contributors for single changeset

Committed-by: Konstantin Schaper <konstantin.schaper@cloudogu.com>
This commit is contained in:
Eduard Heimbuch
2023-05-25 19:26:38 +02:00
parent b812922142
commit a50e456969
5 changed files with 80 additions and 13 deletions

View File

@@ -0,0 +1,2 @@
- type: fixed
description: Duplicate contributors for single changeset

View File

@@ -240,6 +240,9 @@ public class Changeset extends BasicPropertiesAware implements ModelObject {
* @since 2.1.0 * @since 2.1.0
*/ */
public Collection<Contributor> getContributors() { public Collection<Contributor> getContributors() {
if (contributors == null) {
return new ArrayList<>();
}
return contributors; return contributors;
} }

View File

@@ -43,7 +43,7 @@ import {
Level, Level,
SignatureIcon, SignatureIcon,
Tooltip, Tooltip,
SubSubtitle SubSubtitle,
} from "@scm-manager/ui-components"; } from "@scm-manager/ui-components";
import ContributorTable from "./ContributorTable"; import ContributorTable from "./ContributorTable";
import { Link as ReactLink } from "react-router-dom"; import { Link as ReactLink } from "react-router-dom";
@@ -57,7 +57,19 @@ type Props = {
const countContributors = (changeset: Changeset) => { const countContributors = (changeset: Changeset) => {
if (changeset.contributors) { if (changeset.contributors) {
return changeset.contributors.length + 1; const uniqueContributors: string[] = [];
changeset.contributors
.map((p) => p.person)
.forEach((c) => {
if (c.mail) {
if (!uniqueContributors.includes(c.mail)) {
uniqueContributors.push(c.mail);
}
} else {
uniqueContributors.push(c.name);
}
});
return uniqueContributors.length + 1;
} }
return 1; return 1;
}; };
@@ -93,7 +105,7 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => {
return ( return (
<div className="is-flex is-flex-direction-column mb-4"> <div className="is-flex is-flex-direction-column mb-4">
<div className="is-flex"> <div className="is-flex">
<p className="is-ellipsis-overflow is-clickable mb-2" onClick={e => setOpen(!open)}> <p className="is-ellipsis-overflow is-clickable mb-2" onClick={(e) => setOpen(!open)}>
<Icon name="angle-down" alt={t("changeset.contributors.hideList")} /> {t("changeset.contributors.list")} <Icon name="angle-down" alt={t("changeset.contributors.hideList")} /> {t("changeset.contributors.list")}
</p> </p>
{signatureIcon} {signatureIcon}
@@ -105,7 +117,7 @@ const Contributors: FC<{ changeset: Changeset }> = ({ changeset }) => {
return ( return (
<> <>
<div className="is-flex is-clickable" onClick={e => setOpen(!open)}> <div className="is-flex is-clickable" onClick={(e) => setOpen(!open)}>
<ContributorColumn className="is-ellipsis-overflow"> <ContributorColumn className="is-ellipsis-overflow">
<Icon name="angle-right" alt={t("changeset.contributors.showList")} />{" "} <Icon name="angle-right" alt={t("changeset.contributors.showList")} />{" "}
<ChangesetAuthor changeset={changeset} /> <ChangesetAuthor changeset={changeset} />
@@ -150,7 +162,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
name="changeset.description" name="changeset.description"
props={{ props={{
changeset, changeset,
value: description.title value: description.title,
}} }}
renderAll={false} renderAll={false}
> >
@@ -210,7 +222,7 @@ const ChangesetDetails: FC<Props> = ({ changeset, repository, fileControlFactory
name="changeset.description" name="changeset.description"
props={{ props={{
changeset, changeset,
value: item value: item,
}} }}
renderAll={false} renderAll={false}
> >

View File

@@ -54,6 +54,7 @@ public class ChangesetDescriptionContributorProvider implements ChangesetPreProc
private Worker(Changeset changeset) { private Worker(Changeset changeset) {
this.changeset = changeset; this.changeset = changeset;
} }
private void process() { private void process() {
try (Scanner scanner = new Scanner(changeset.getDescription())) { try (Scanner scanner = new Scanner(changeset.getDescription())) {
while (scanner.hasNextLine()) { while (scanner.hasNextLine()) {
@@ -76,13 +77,34 @@ public class ChangesetDescriptionContributorProvider implements ChangesetPreProc
} }
private boolean checkForContributor(String line) { private boolean checkForContributor(String line) {
Optional<Contributor> contributor = Contributor.fromCommitLine(line); Optional<Contributor> optionalContributor = Contributor.fromCommitLine(line);
if (contributor.isPresent()) { if (optionalContributor.isPresent()) {
changeset.addContributor(contributor.get()); Contributor contributor = optionalContributor.get();
if (acceptContributor(contributor)) {
changeset.addContributor(contributor);
}
return true; return true;
} else{ }
return false; return false;
} }
private boolean acceptContributor(Contributor contributor) {
if (isCoAuthorEqualToAuthor(contributor)) {
return false;
}
return
changeset.getContributors().stream()
.noneMatch(c ->
c.getType().equals(contributor.getType())
&& c.getPerson().getMail().equals(contributor.getPerson().getMail()
)
);
}
private boolean isCoAuthorEqualToAuthor(Contributor contributor) {
return contributor.getType().equals(Contributor.CO_AUTHORED_BY)
&& contributor.getPerson().getMail().equals(changeset.getAuthor().getMail());
} }
private void handleEmptyLine(Scanner scanner, String line) { private void handleEmptyLine(Scanner scanner, String line) {

View File

@@ -53,15 +53,28 @@ class ChangesetDescriptionContributorProviderTest {
@Test @Test
void shouldConvertTrailerWithCoAuthors() { void shouldConvertTrailerWithCoAuthors() {
Person person = createPerson("Arthur Dent", "dent@hitchhiker.org"); Person person = createPerson("Tricia McMillan", "trillian@hitchhiker.org");
Changeset changeset = createChangeset("zaphod beeblebrox\n\nCo-authored-by: Arthur Dent <dent@hitchhiker.org>"); Changeset changeset = createChangeset("zaphod beeblebrox\n\nCo-authored-by: Arthur Dent <dent@hitchhiker.org>");
changeset.setAuthor(person);
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset); changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
Collection<Contributor> contributors = changeset.getContributors(); Collection<Contributor> contributors = changeset.getContributors();
Contributor contributor = contributors.iterator().next(); Contributor contributor = contributors.iterator().next();
assertThat(contributor.getType()).isEqualTo("Co-authored-by"); assertThat(contributor.getType()).isEqualTo("Co-authored-by");
assertThat(contributor.getPerson()).isEqualTo(person); assertThat(contributor.getPerson()).isEqualTo(createPerson("Arthur Dent", "dent@hitchhiker.org"));
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
}
@Test
void shouldIgnoreCoAuthorIfEqualToAuthor() {
Person person = createPerson("Arthur Dent", "dent@hitchhiker.org");
Changeset changeset = createChangeset("zaphod beeblebrox\n\nCo-authored-by: Arthur Dent <dent@hitchhiker.org>");
changeset.setAuthor(person);
changesetDescriptionContributors.createPreProcessor(REPOSITORY).process(changeset);
Collection<Contributor> contributors = changeset.getContributors();
assertThat(contributors).isEmpty();
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n"); assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
} }
@@ -110,6 +123,21 @@ class ChangesetDescriptionContributorProviderTest {
assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n"); assertThat(changeset.getDescription()).isEqualTo("zaphod beeblebrox\n\n");
} }
@Test
void shouldIgnoreDuplicateContributorWithSameTypeAndMail() {
Person person = createPerson("Tricia McMillan", "trillian@hitchhiker.org");
Changeset changeset = createChangeset("zaphod beeblebrox\n\nCommitted-by: Tricia McMillan <trillian@hitchhiker.org>\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 @Test
void shouldConvertMixedTrailers() { void shouldConvertMixedTrailers() {
Changeset changeset = createChangeset("zaphod beeblebrox\n\n" + Changeset changeset = createChangeset("zaphod beeblebrox\n\n" +