From 6b2774d8745081c1478cea208c1c45b3f45f4282 Mon Sep 17 00:00:00 2001 From: Moritz Halbritter Date: Wed, 9 Oct 2024 11:00:42 +0200 Subject: [PATCH] Generate .gitattributes for Maven and Gradle Closes gh-1569 --- .../spring/scm/git/GitAttributes.java | 65 +++++++++++++++++++ .../scm/git/GitAttributesContributor.java | 51 +++++++++++++++ .../scm/git/GitAttributesCustomizer.java | 38 +++++++++++ .../spring/scm/git/GitIgnoreContributor.java | 4 +- .../GitProjectGenerationConfiguration.java | 32 +++++++++ .../ProjectGeneratorIntegrationTests.java | 5 +- .../spring/scm/git/GitAttributesTests.java | 55 ++++++++++++++++ ...itProjectGenerationConfigurationTests.java | 47 +++++++++++++- 8 files changed, 291 insertions(+), 6 deletions(-) create mode 100644 initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributes.java create mode 100644 initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesContributor.java create mode 100644 initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesCustomizer.java create mode 100644 initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitAttributesTests.java diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributes.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributes.java new file mode 100644 index 0000000000..47f32f2157 --- /dev/null +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributes.java @@ -0,0 +1,65 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.scm.git; + +import java.io.PrintWriter; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Project's {@code .gitattributes}. + * + * @author Moritz Halbritter + */ +public class GitAttributes { + + private final List lines = new ArrayList<>(); + + /** + * Adds a new pattern with attributes. + * @param pattern the pattern + * @param attribute the first attribute + * @param remainingAttributes the remaining attributes + */ + public void add(String pattern, String attribute, String... remainingAttributes) { + List attributes = new ArrayList<>(); + attributes.add(attribute); + attributes.addAll(Arrays.asList(remainingAttributes)); + this.lines.add(new Line(pattern, attributes)); + } + + void write(PrintWriter writer) { + for (Line line : this.lines) { + line.write(writer); + } + } + + boolean isEmpty() { + return this.lines.isEmpty(); + } + + private record Line(String pattern, List attributes) { + void write(PrintWriter writer) { + writer.print(this.pattern); + writer.print(' '); + writer.print(String.join(" ", this.attributes)); + writer.println(); + } + } + +} diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesContributor.java new file mode 100644 index 0000000000..a0a51421ed --- /dev/null +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesContributor.java @@ -0,0 +1,51 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.scm.git; + +import java.io.IOException; +import java.io.PrintWriter; +import java.nio.file.Files; +import java.nio.file.Path; + +import io.spring.initializr.generator.project.contributor.ProjectContributor; + +/** + * A {@link ProjectContributor} that contributes a {@code .gitattributes} file to a + * project. + * + * @author Moritz Halbritter + */ +public class GitAttributesContributor implements ProjectContributor { + + private final GitAttributes gitAttributes; + + public GitAttributesContributor(GitAttributes gitAttributes) { + this.gitAttributes = gitAttributes; + } + + @Override + public void contribute(Path projectRoot) throws IOException { + if (this.gitAttributes.isEmpty()) { + return; + } + Path file = Files.createFile(projectRoot.resolve(".gitattributes")); + try (PrintWriter writer = new PrintWriter(Files.newBufferedWriter(file))) { + this.gitAttributes.write(writer); + } + } + +} diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesCustomizer.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesCustomizer.java new file mode 100644 index 0000000000..44906d3452 --- /dev/null +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitAttributesCustomizer.java @@ -0,0 +1,38 @@ +/* + * Copyright 2012-2019 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.scm.git; + +import org.springframework.core.Ordered; + +/** + * Callback for customizing a project's {@link GitAttributes}. Invoked with an + * {@link Ordered order} of {@code 0} by default, considering overriding + * {@link #getOrder()} to customize this behaviour. + * + * @author Moritz Halbritter + */ +@FunctionalInterface +public interface GitAttributesCustomizer extends Ordered { + + void customize(GitAttributes gitAttributes); + + @Override + default int getOrder() { + return 0; + } + +} diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitIgnoreContributor.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitIgnoreContributor.java index b1a625908c..6c23dead99 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitIgnoreContributor.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitIgnoreContributor.java @@ -22,11 +22,9 @@ import java.nio.file.Path; import io.spring.initializr.generator.project.contributor.ProjectContributor; -import io.spring.initializr.generator.project.contributor.SingleResourceProjectContributor; /** - * A {@link SingleResourceProjectContributor} that contributes a {@code .gitignore} file - * to a project. + * A {@link ProjectContributor} that contributes a {@code .gitignore} file to a project. * * @author Andy Wilkinson * @author Stephane Nicoll diff --git a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfiguration.java b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfiguration.java index 9d35792fe8..d7b8e19b82 100644 --- a/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfiguration.java +++ b/initializr-generator-spring/src/main/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfiguration.java @@ -29,6 +29,7 @@ * * @author Andy Wilkinson * @author Stephane Nicoll + * @author Moritz Halbritter */ @ProjectGenerationConfiguration public class GitProjectGenerationConfiguration { @@ -45,6 +46,18 @@ public GitIgnore gitIgnore(ObjectProvider gitIgnoreCustomiz return gitIgnore; } + @Bean + GitAttributesContributor gitAttributesContributor(GitAttributes gitAttributes) { + return new GitAttributesContributor(gitAttributes); + } + + @Bean + GitAttributes gitAttributes(ObjectProvider gitAttributesCustomizers) { + GitAttributes gitAttributes = new GitAttributes(); + gitAttributesCustomizers.orderedStream().forEach((customizer) -> customizer.customize(gitAttributes)); + return gitAttributes; + } + @Bean @ConditionalOnBuildSystem(MavenBuildSystem.ID) public GitIgnoreCustomizer mavenGitIgnoreCustomizer() { @@ -68,6 +81,25 @@ public GitIgnoreCustomizer gradleGitIgnoreCustomizer() { }; } + @Bean + @ConditionalOnBuildSystem(MavenBuildSystem.ID) + public GitAttributesCustomizer mavenGitAttributesCustomizer() { + return (gitAttributes) -> { + gitAttributes.add("/mvnw", "text", "eol=lf"); + gitAttributes.add("*.cmd", "text", "eol=crlf"); + }; + } + + @Bean + @ConditionalOnBuildSystem(GradleBuildSystem.ID) + public GitAttributesCustomizer gradleGitAttributesCustomizer() { + return (gitAttributes) -> { + gitAttributes.add("/gradlew", "text", "eol=lf"); + gitAttributes.add("*.bat", "text", "eol=crlf"); + gitAttributes.add("*.jar", "binary"); + }; + } + private GitIgnore createGitIgnore() { GitIgnore gitIgnore = new GitIgnore(); gitIgnore.getSts() diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/ProjectGeneratorIntegrationTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/ProjectGeneratorIntegrationTests.java index 6d6ac40a64..ca63602ec7 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/ProjectGeneratorIntegrationTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/ProjectGeneratorIntegrationTests.java @@ -61,8 +61,9 @@ void customBaseDirectoryIsUsedWhenGeneratingProject() { description.setBaseDirectory("test/demo-app"); ProjectStructure project = this.projectTester.generate(description); assertThat(project).filePaths() - .containsOnly("test/demo-app/.gitignore", "test/demo-app/pom.xml", "test/demo-app/mvnw", - "test/demo-app/mvnw.cmd", "test/demo-app/.mvn/wrapper/maven-wrapper.properties", + .containsOnly("test/demo-app/.gitignore", "test/demo-app/.gitattributes", "test/demo-app/pom.xml", + "test/demo-app/mvnw", "test/demo-app/mvnw.cmd", + "test/demo-app/.mvn/wrapper/maven-wrapper.properties", "test/demo-app/src/main/java/com/example/demo/DemoApplication.java", "test/demo-app/src/main/resources/application.properties", "test/demo-app/src/test/java/com/example/demo/DemoApplicationTests.java", "test/demo-app/HELP.md"); diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitAttributesTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitAttributesTests.java new file mode 100644 index 0000000000..7693973167 --- /dev/null +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitAttributesTests.java @@ -0,0 +1,55 @@ +/* + * Copyright 2012-2024 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.spring.initializr.generator.spring.scm.git; + +import java.io.PrintWriter; +import java.io.StringWriter; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Tests for {@link GitAttributes}. + * + * @author Moritz Halbritter + */ +class GitAttributesTests { + + @Test + void shouldWriteGitAttributes() { + GitAttributes attributes = new GitAttributes(); + attributes.add("/gradlew", "text", "eof=lf"); + attributes.add("*.bat", "text", "eol=crlf"); + attributes.add("*.jar", "binary"); + String written = writeToString(attributes); + assertThat(written).isEqualToNormalizingNewlines(""" + /gradlew text eof=lf + *.bat text eol=crlf + *.jar binary + """); + } + + private String writeToString(GitAttributes attributes) { + StringWriter stringWriter = new StringWriter(); + try (PrintWriter printWriter = new PrintWriter(stringWriter)) { + attributes.write(printWriter); + } + return stringWriter.toString(); + } + +} diff --git a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfigurationTests.java b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfigurationTests.java index 57d3fb82a3..14a4856721 100644 --- a/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfigurationTests.java +++ b/initializr-generator-spring/src/test/java/io/spring/initializr/generator/spring/scm/git/GitProjectGenerationConfigurationTests.java @@ -36,6 +36,7 @@ * Tests for {@link GitProjectGenerationConfiguration}. * * @author Stephane Nicoll + * @author Moritz Halbritter */ class GitProjectGenerationConfigurationTests { @@ -85,11 +86,55 @@ void gitIgnoreMaven() { .doesNotContain(".gradle", "!gradle/wrapper/gradle-wrapper.jar", "/out/"); } + @Test + void gitAttributesIsContributedToProject(@TempDir Path directory) { + MutableProjectDescription description = new MutableProjectDescription(); + description.setBuildSystem(new GradleBuildSystem()); + Path projectDirectory = this.projectTester.withDirectory(directory).generate(description, (context) -> { + GitAttributesContributor contributor = context.getBean(GitAttributesContributor.class); + contributor.contribute(directory); + return directory; + }); + assertThat(projectDirectory.resolve(".gitattributes")).isRegularFile(); + } + + @Test + void gitAttributesGradle() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setBuildSystem(new GradleBuildSystem()); + description.setPlatformVersion(Version.parse("3.4.0")); + assertThat(generateGitAttributes(description)) + .contains("/gradlew text eol=lf", "*.bat text eol=crlf", "*.jar binary") + .doesNotContain("/mvnw text eol=lf", "*.cmd text eol=crlf"); + } + + @Test + void gitAttributesMaven() { + MutableProjectDescription description = new MutableProjectDescription(); + description.setBuildSystem(new MavenBuildSystem()); + description.setPlatformVersion(Version.parse("3.3.0")); + assertThat(generateGitAttributes(description)).contains("/mvnw text eol=lf", "*.cmd text eol=crlf") + .doesNotContain("/gradlew text eol=lf", "*.bat text eol=crlf", "*.jar binary"); + } + private List generateGitIgnore(MutableProjectDescription description) { return this.projectTester.generate(description, (context) -> { GitIgnore gitIgnore = context.getBean(GitIgnore.class); StringWriter out = new StringWriter(); - gitIgnore.write(new PrintWriter(out)); + try (PrintWriter printWriter = new PrintWriter(out)) { + gitIgnore.write(printWriter); + } + return TextTestUtils.readAllLines(out.toString()); + }); + } + + private List generateGitAttributes(MutableProjectDescription description) { + return this.projectTester.generate(description, (context) -> { + GitAttributes gitAttributes = context.getBean(GitAttributes.class); + StringWriter out = new StringWriter(); + try (PrintWriter printWriter = new PrintWriter(out)) { + gitAttributes.write(printWriter); + } return TextTestUtils.readAllLines(out.toString()); }); }