Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 7 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,17 +191,22 @@ Either use add a ```<skip>true</skip>``` configuration in the inheriting project
On the `initialize` maven phase, `git-code-format:install-hooks` installs a git `pre-commit` hook that looks like this :
```bash
#!/bin/bash
"./.git/hooks/${project.artifactId}.git-code-format.pre-commit.sh"
"$(git rev-parse --git-common-dir)/hooks/${project.artifactId}.git-code-format.pre-commit.sh"
```
and `.git/hooks/${project.artifactId}.git-code-format.pre-commit.sh` has the following content:
```bash
#!/bin/bash
set -e
"${env.M2_HOME}/bin/mvn" -f "${project.basedir}/pom.xml" git-code-format:on-pre-commit
"${env.M2_HOME}/bin/mvn" -f "$(git rev-parse --show-toplevel)/<pom.xml path relative to the repository root>" git-code-format:on-pre-commit
```

On `pre-commit` git phase, the hook triggers the `git-code-format:on-pre-commit` which formats the code of the modified files.

The hooks live in the directory shared by all the [linked worktrees](https://git-scm.com/docs/git-worktree)
of the repository, and the paths inside the repository are resolved against the worktree being
committed. A commit from a linked worktree therefore formats the staged files of that worktree,
with the pom and the maven wrapper of that worktree.

# Maven wrapper

When the repository holds a [maven wrapper](https://maven.apache.org/wrapper/), the hook runs that
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@ protected final boolean isExecutionRoot() {
* @return The git hooks directory
*/
protected final Path getOrCreateHooksDirectory() {
Path hooksDirectory = gitRepository().getDirectory().toPath().resolve(HOOKS_DIR);
// Linked worktrees share the hooks of the common directory, which is where git runs them from.
Path hooksDirectory = gitRepository().getCommonDirectory().toPath().resolve(HOOKS_DIR);
if (!Files.exists(hooksDirectory)) {
getLog().debug("Creating directory " + hooksDirectory);
try {
Expand All @@ -134,6 +135,6 @@ protected final Path getOrCreateHooksDirectory() {
}

protected final Path gitBaseDir() {
return gitRepository().getDirectory().getParentFile().toPath();
return gitRepository().getWorkTree().toPath();
}
}
26 changes: 23 additions & 3 deletions core/src/main/java/com/cosium/code/format/InstallHooksMojo.java
Original file line number Diff line number Diff line change
Expand Up @@ -133,8 +133,8 @@ private void writePluginHooks(Path hooksDirectory) throws IOException {
.truncateWithTemplate(
() -> getClass().getResourceAsStream(BASE_PLUGIN_PRE_COMMIT_HOOK),
StandardCharsets.UTF_8.toString(),
mavenExecutables.select(debug, preferMavenWrapper).toAbsolutePath(),
pomFile().toAbsolutePath(),
resolveFromWorkTreeRoot(mavenExecutables.select(debug, preferMavenWrapper)),
resolveFromWorkTreeRoot(pomFile()),
mavenCliArguments());
getLog().debug("Written plugin pre commit hook file");
}
Expand Down Expand Up @@ -179,8 +179,27 @@ private Path prepareHooksDirectory() {
return hooksDirectory;
}

/**
* The plugin hook is shared by all the worktrees of the repository. A path inside the worktree
* that installed it is therefore resolved against the worktree being committed.
*/
private String resolveFromWorkTreeRoot(Path path) {
Path absolutePath = path.toAbsolutePath().normalize();
Path workTree = gitBaseDir().toAbsolutePath().normalize();
if (!absolutePath.startsWith(workTree)) {
return "\"" + replaceBackslashes(absolutePath) + "\"";
}
return "\"$(git rev-parse --show-toplevel)/"
+ replaceBackslashes(workTree.relativize(absolutePath))
+ "\"";
}

private String replaceBackslashes(Path path) {
return path.toString().replace('\\', '/');
}

private String preCommitHookBaseScriptCall() {
return "$(git rev-parse --git-dir)/" + HOOKS_DIR + "/" + pluginPreCommitHookFileName();
return "$(git rev-parse --git-common-dir)/" + HOOKS_DIR + "/" + pluginPreCommitHookFileName();
}

private List<String> legacyPreCommitHookBaseScriptCalls() {
Expand All @@ -195,6 +214,7 @@ private List<String> legacyPreCommitHookBaseScriptCalls() {
+ gitBaseDir().relativize(getOrCreateHooksDirectory())
+ "/"
+ pluginPreCommitHookFileName());
calls.add("$(git rev-parse --git-dir)/" + HOOKS_DIR + "/" + pluginPreCommitHookFileName());
return calls;
}

Expand Down
14 changes: 7 additions & 7 deletions core/src/test/java/com/cosium/code/format/MavenWrapperTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@
public class MavenWrapperTest extends AbstractTest {

private static final String MAVEN_WRAPPER = "mvnw";
private static final String WORK_TREE_MAVEN_WRAPPER =
"\"$(git rev-parse --show-toplevel)/" + MAVEN_WRAPPER + "\"";
private static final String BAD_FORMAT_JAVA = "src/main/java/BadFormat.java";
private static final Path MAVEN_WRAPPER_PROPERTIES =
Paths.get(".mvn/wrapper/maven-wrapper.properties");
Expand All @@ -37,11 +39,11 @@ public MavenWrapperTest(MavenRuntime.MavenRuntimeBuilder mavenBuilder) throws Ex
@MavenPluginTest
@DisplayName("GIVEN a maven wrapper WHEN installing the hooks THEN the hook runs the wrapper")
public void test1() throws Exception {
Path wrapper = installMavenWrapper(projectRoot());
installMavenWrapper(projectRoot());

installHooks();

assertThat(readHookScript()).contains(wrapper.toAbsolutePath().toString());
assertThat(readHookScript()).contains(WORK_TREE_MAVEN_WRAPPER);
}

@MavenPluginTest
Expand All @@ -50,13 +52,13 @@ public void test1() throws Exception {
+ " wrapper")
public void test2() throws Exception {
// The build is run from the module, while the wrapper sits at the root of the repository.
Path wrapper = installMavenWrapper(projectRoot());
installMavenWrapper(projectRoot());
Path module = Files.createDirectories(projectRoot().resolve("module"));
Files.copy(projectRoot().resolve("pom.xml"), module.resolve("pom.xml"));

buildMavenExecution(module).execute("initialize").assertErrorFreeLog();

assertThat(readHookScript()).contains(wrapper.toAbsolutePath().toString());
assertThat(readHookScript()).contains(WORK_TREE_MAVEN_WRAPPER);
}

@MavenPluginTest
Expand Down Expand Up @@ -142,7 +144,7 @@ private void installHooks() throws Exception {
* pins the maven version the build already runs with, hence no distribution left to download. The
* executable bit has to be set back, as a plain copy drops it.
*/
private Path installMavenWrapper(Path directory) throws IOException {
private void installMavenWrapper(Path directory) throws IOException {
Path repositoryRoot = Paths.get("..");

Path wrapper = directory.resolve(MAVEN_WRAPPER);
Expand All @@ -152,8 +154,6 @@ private Path installMavenWrapper(Path directory) throws IOException {
Path properties = directory.resolve(MAVEN_WRAPPER_PROPERTIES);
Files.createDirectories(properties.getParent());
Files.copy(repositoryRoot.resolve(MAVEN_WRAPPER_PROPERTIES), properties);

return wrapper;
}

private String readHookScript() throws IOException {
Expand Down
102 changes: 102 additions & 0 deletions core/src/test/java/com/cosium/code/format/WorktreeTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package com.cosium.code.format;

import static org.assertj.core.api.Assertions.assertThat;

import io.takari.maven.testing.executor.MavenRuntime;
import io.takari.maven.testing.executor.MavenVersions;
import io.takari.maven.testing.executor.junit.MavenPluginTest;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.io.TempDir;

/**
* Covers <a href="https://github.com/Cosium/git-code-format-maven-plugin/issues/390">issue 390</a>.
*
* <p>JGit cannot add a linked worktree, hence the git command line.
*
* @author Réda Housni Alaoui
*/
@MavenVersions({"3.5.0"})
public class WorktreeTest extends AbstractTest {

private static final String BAD_FORMAT_JAVA = "src/main/java/BadFormat.java";
private static final String FORMATTED_CONTENT =
"public class BadFormat {\n" + "\n" + " void a() {}\n" + "}\n";

@TempDir private Path worktreeParent;
private Path worktree;

public WorktreeTest(MavenRuntime.MavenRuntimeBuilder mavenBuilder) throws Exception {
super(mavenBuilder, "single-module");
}

@BeforeEach
void addWorktree() throws Exception {
worktree = worktreeParent.resolve("wt");
runGit(projectRoot(), "worktree", "add", "-b", "wt", worktree.toString());
}

@MavenPluginTest
@DisplayName(
"GIVEN hooks installed from the main checkout WHEN committing a bad formatted file from a"
+ " linked worktree THEN the file of the worktree has the correct format")
public void test1() throws Exception {
buildMavenExecution(projectRoot()).execute("initialize").assertErrorFreeLog();

commitBadFormattedFileFromWorktree();

assertThat(worktree.resolve(BAD_FORMAT_JAVA))
.content(StandardCharsets.UTF_8)
.isEqualTo(FORMATTED_CONTENT);
}

@MavenPluginTest
@DisplayName(
"GIVEN hooks installed from a linked worktree WHEN committing a bad formatted file from the"
+ " worktree THEN the file of the worktree has the correct format")
public void test2() throws Exception {
buildMavenExecution(worktree).execute("initialize").assertErrorFreeLog();

commitBadFormattedFileFromWorktree();

assertThat(worktree.resolve(BAD_FORMAT_JAVA))
.content(StandardCharsets.UTF_8)
.isEqualTo(FORMATTED_CONTENT);
}

private void commitBadFormattedFileFromWorktree() throws Exception {
Files.write(
worktree.resolve(BAD_FORMAT_JAVA),
("public class BadFormat {\n" + "\n" + " void a( ){}\n" + "}\n")
.getBytes(StandardCharsets.UTF_8));
runGit(worktree, "add", BAD_FORMAT_JAVA);
runGit(
worktree,
"-c",
"user.name=" + gitIdentity().getName(),
"-c",
"user.email=" + gitIdentity().getEmailAddress(),
"commit",
"-m",
"Committing a badly formatted file");
}

private void runGit(Path directory, String... arguments)
throws IOException, InterruptedException {
List<String> command = new ArrayList<>();
command.add("git");
command.addAll(Arrays.asList(arguments));
Process process =
new ProcessBuilder(command).directory(directory.toFile()).redirectErrorStream(true).start();
String output = IOUtils.toString(process.getInputStream(), StandardCharsets.UTF_8);
assertThat(process.waitFor()).as(output).isZero();
}
}
Loading