Skip to content

Commit 9b008bc

Browse files
committed
fix: add tests for GitUtils
1 parent 286e021 commit 9b008bc

4 files changed

Lines changed: 241 additions & 7 deletions

File tree

pom.xml

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,6 +268,12 @@
268268
<version>4.11.0</version>
269269
<scope>test</scope>
270270
</dependency>
271+
<dependency>
272+
<groupId>org.apache.commons</groupId>
273+
<artifactId>commons-exec</artifactId>
274+
<version>1.6.0</version>
275+
<scope>test</scope>
276+
</dependency>
271277
<!-- A bit of jar-hell requires this to come last. -->
272278
<dependency>
273279
<groupId>org.apache.maven</groupId>
@@ -518,6 +524,20 @@
518524
</execution>
519525
</executions>
520526
</plugin>
527+
528+
<!-- Copy nested `.git` folders excluded by default -->
529+
<plugin>
530+
<groupId>org.apache.maven.plugins</groupId>
531+
<artifactId>maven-resources-plugin</artifactId>
532+
<executions>
533+
<execution>
534+
<id>default-testResources</id>
535+
<configuration>
536+
<addDefaultExcludes>false</addDefaultExcludes>
537+
</configuration>
538+
</execution>
539+
</executions>
540+
</plugin>
521541
</plugins>
522542
</build>
523543
<reporting>
@@ -631,7 +651,7 @@
631651
<plugin>
632652
<groupId>com.github.spotbugs</groupId>
633653
<artifactId>spotbugs-maven-plugin</artifactId>
634-
</plugin>
654+
</plugin>
635655
</plugins>
636656
</reporting>
637657

src/main/java/org/apache/commons/release/plugin/internal/GitUtils.java

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@
2020
import java.nio.charset.StandardCharsets;
2121
import java.nio.file.Files;
2222
import java.nio.file.Path;
23-
import java.nio.file.Paths;
2423
import java.security.MessageDigest;
2524

2625
import org.apache.commons.codec.binary.Hex;
@@ -32,7 +31,15 @@
3231
*/
3332
public final class GitUtils {
3433

35-
/** The SCM URI prefix for Git repositories. */
34+
/**
35+
* Prefix used in a {@code gitfile} to point to the Git directory.
36+
*
37+
* <p>See <a href="https://git-scm.com/docs/gitrepository-layout">gitrepository-layout</a>.</p>
38+
*/
39+
private static final String GITDIR_PREFIX = "gitdir: ";
40+
/**
41+
* The SCM URI prefix for Git repositories.
42+
*/
3643
private static final String SCM_GIT_PREFIX = "scm:git:";
3744

3845
/**
@@ -52,8 +59,8 @@ private static Path findGitDir(final Path path) throws IOException {
5259
if (Files.isRegularFile(candidate)) {
5360
// git worktree: .git is a file containing "gitdir: /path/to/real/.git"
5461
final String content = new String(Files.readAllBytes(candidate), StandardCharsets.UTF_8).trim();
55-
if (content.startsWith("gitdir: ")) {
56-
return Paths.get(content.substring("gitdir: ".length()));
62+
if (content.startsWith(GITDIR_PREFIX)) {
63+
return current.resolve(content.substring(GITDIR_PREFIX.length()));
5764
}
5865
}
5966
current = current.getParent();
@@ -98,7 +105,7 @@ public static String gitTree(final Path path) throws IOException {
98105
/**
99106
* Converts an SCM URI to a download URI suffixed with the current branch name.
100107
*
101-
* @param scmUri A Maven SCM URI starting with {@code scm:git}.
108+
* @param scmUri A Maven SCM URI starting with {@code scm:git}.
102109
* @param repositoryPath A path inside the Git repository.
103110
* @return A download URI of the form {@code git+<url>@<branch>}.
104111
* @throws IOException If the current branch cannot be determined.
@@ -111,7 +118,9 @@ public static String scmToDownloadUri(final String scmUri, final Path repository
111118
return "git+" + scmUri.substring(SCM_GIT_PREFIX.length()) + "@" + currentBranch;
112119
}
113120

114-
/** No instances. */
121+
/**
122+
* No instances.
123+
*/
115124
private GitUtils() {
116125
// no instantiation
117126
}
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.release.plugin.internal;
18+
19+
import java.io.IOException;
20+
import java.nio.charset.StandardCharsets;
21+
import java.nio.file.Files;
22+
import java.nio.file.Path;
23+
import java.util.Map;
24+
25+
import org.apache.commons.exec.CommandLine;
26+
import org.apache.commons.exec.DefaultExecutor;
27+
import org.apache.commons.exec.Executor;
28+
import org.apache.commons.exec.PumpStreamHandler;
29+
import org.apache.commons.exec.environment.EnvironmentUtils;
30+
import org.apache.commons.io.output.NullOutputStream;
31+
32+
/**
33+
* Builds real Git fixtures on disk by invoking the {@code git} CLI via Commons Exec
34+
*/
35+
final class GitFixture {
36+
37+
static final String REPO_BRANCH = "foo";
38+
static final String WORKTREE_BRANCH = "bar";
39+
static final String SUBDIR = "subdir";
40+
/**
41+
* SHA-1 of the single commit produced by {@link #createRepoAndWorktree}; deterministic thanks to {@link #ENV}.
42+
*/
43+
static final String INITIAL_COMMIT_SHA = "a2782b3461d2ed2a81193da1139f65bf9d2befc2";
44+
45+
/**
46+
* Process environment with fixed author/committer dates so commit SHAs are stable across runs.
47+
*/
48+
private static final Map<String, String> ENV;
49+
50+
static {
51+
try {
52+
final Map<String, String> env = EnvironmentUtils.getProcEnvironment();
53+
env.put("GIT_AUTHOR_DATE", "2026-01-01T00:00:00Z");
54+
env.put("GIT_COMMITTER_DATE", "2026-01-01T00:00:00Z");
55+
ENV = env;
56+
} catch (final IOException e) {
57+
throw new ExceptionInInitializerError(e);
58+
}
59+
}
60+
61+
/**
62+
* Creates a Git repo for testing.
63+
*
64+
* @param repo Path to the repository to create
65+
* @param worktree Path to a separate worktree to create
66+
*/
67+
static void createRepoAndWorktree(final Path repo, final Path worktree) throws IOException {
68+
final Path subdir = repo.resolve(SUBDIR);
69+
Files.createDirectories(subdir);
70+
git(repo, "init", "-q", ".");
71+
// Put HEAD on 'foo' before the first commit (portable to older git without --initial-branch).
72+
git(repo, "symbolic-ref", "HEAD", "refs/heads/" + REPO_BRANCH);
73+
git(repo, "config", "user.email", "test@example.invalid");
74+
git(repo, "config", "user.name", "Test");
75+
git(repo, "config", "commit.gpgsign", "false");
76+
final Path readme = subdir.resolve("README");
77+
Files.write(readme, "hi\n".getBytes(StandardCharsets.UTF_8));
78+
git(repo, "add", repo.relativize(readme).toString());
79+
git(repo, "commit", "-q", "-m", "init");
80+
git(repo, "branch", WORKTREE_BRANCH);
81+
git(repo, "worktree", "add", "-q", repo.relativize(worktree).toString(), "bar");
82+
}
83+
84+
/**
85+
* Runs {@code git} with the given args; stdout is discarded, stderr is forwarded to {@link System#err}.
86+
*/
87+
static void git(final Path workingDir, final String... args) throws IOException {
88+
final CommandLine cmd = new CommandLine("git");
89+
for (final String a : args) {
90+
cmd.addArgument(a, false);
91+
}
92+
final Executor exec = DefaultExecutor.builder().setWorkingDirectory(workingDir.toFile()).get();
93+
exec.setStreamHandler(new PumpStreamHandler(NullOutputStream.INSTANCE, System.err));
94+
exec.execute(cmd, ENV);
95+
}
96+
97+
private GitFixture() {
98+
}
99+
}
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* https://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
package org.apache.commons.release.plugin.internal;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
import static org.junit.jupiter.api.Assertions.assertThrows;
21+
22+
import java.io.IOException;
23+
import java.nio.file.Files;
24+
import java.nio.file.Path;
25+
import java.util.stream.Stream;
26+
27+
import org.junit.jupiter.api.BeforeAll;
28+
import org.junit.jupiter.api.Test;
29+
import org.junit.jupiter.api.io.TempDir;
30+
import org.junit.jupiter.params.ParameterizedTest;
31+
import org.junit.jupiter.params.provider.Arguments;
32+
import org.junit.jupiter.params.provider.MethodSource;
33+
import org.junit.jupiter.params.provider.ValueSource;
34+
35+
class GitUtilsTest {
36+
37+
private static Path repo;
38+
@TempDir
39+
static Path tempDir;
40+
private static Path worktree;
41+
42+
@BeforeAll
43+
static void setUp() throws IOException {
44+
repo = tempDir.resolve("repo");
45+
worktree = tempDir.resolve("worktree");
46+
GitFixture.createRepoAndWorktree(repo, worktree);
47+
}
48+
49+
static Stream<Arguments> testGetCurrentBranch() {
50+
return Stream.of(Arguments.of(repo, GitFixture.REPO_BRANCH), Arguments.of(repo.resolve(GitFixture.SUBDIR), GitFixture.REPO_BRANCH),
51+
Arguments.of(worktree, GitFixture.WORKTREE_BRANCH), Arguments.of(worktree.resolve(GitFixture.SUBDIR), GitFixture.WORKTREE_BRANCH));
52+
}
53+
54+
static Stream<Arguments> testScmToDownloadUri() {
55+
return Stream.of(
56+
Arguments.of("scm:git:https://gitbox.apache.org/repos/asf/commons-release-plugin.git",
57+
repo,
58+
"git+https://gitbox.apache.org/repos/asf/commons-release-plugin.git@" + GitFixture.REPO_BRANCH),
59+
Arguments.of("scm:git:git@github.com:apache/commons-release-plugin.git",
60+
repo,
61+
"git+git@github.com:apache/commons-release-plugin.git@" + GitFixture.REPO_BRANCH),
62+
Arguments.of("scm:git:ssh://git@github.com/apache/commons-release-plugin.git",
63+
worktree,
64+
"git+ssh://git@github.com/apache/commons-release-plugin.git@" + GitFixture.WORKTREE_BRANCH));
65+
}
66+
67+
@ParameterizedTest
68+
@MethodSource
69+
void testGetCurrentBranch(final Path repo, final String expectedBranchName) throws Exception {
70+
assertEquals(expectedBranchName, GitUtils.getCurrentBranch(repo));
71+
}
72+
73+
@Test
74+
void testGetCurrentBranchDetachedHead() throws IOException {
75+
// Build a fresh repo so we don't mutate HEAD shared with the parameterized tests.
76+
final Path detachedRepo = tempDir.resolve("detached-repo");
77+
final Path detachedWorktree = tempDir.resolve("detached-worktree");
78+
GitFixture.createRepoAndWorktree(detachedRepo, detachedWorktree);
79+
GitFixture.git(detachedRepo, "checkout", "-q", "--detach", "HEAD");
80+
assertEquals(GitFixture.INITIAL_COMMIT_SHA, GitUtils.getCurrentBranch(detachedRepo));
81+
}
82+
83+
@ParameterizedTest
84+
@MethodSource
85+
void testScmToDownloadUri(final String scmUri, final Path repositoryPath, final String expectedDownloadUri) throws IOException {
86+
assertEquals(expectedDownloadUri, GitUtils.scmToDownloadUri(scmUri, repositoryPath));
87+
}
88+
89+
@ParameterizedTest
90+
@ValueSource(strings = {
91+
"scm:svn:https://svn.apache.org/repos/asf/commons-release-plugin",
92+
"scm:hg:https://example.com/repo",
93+
"https://github.com/apache/commons-release-plugin.git",
94+
"git:https://github.com/apache/commons-release-plugin.git",
95+
""
96+
})
97+
void testScmToDownloadUriRejectsNonGit(final String scmUri) {
98+
assertThrows(IllegalArgumentException.class, () -> GitUtils.scmToDownloadUri(scmUri, repo));
99+
}
100+
101+
@Test
102+
void throwsWhenNoGitDirectoryFound() throws IOException {
103+
final Path plain = Files.createDirectories(tempDir.resolve("plain"));
104+
assertThrows(IOException.class, () -> GitUtils.getCurrentBranch(plain));
105+
}
106+
}

0 commit comments

Comments
 (0)