Skip to content

Commit b88ce14

Browse files
author
Vincent Potucek
committed
[fix] NPE due to workingTreeIterator being null for git ignored files. #911
- #911 Signed-off-by: Vincent Potucek <[email protected]>
1 parent bf74d6f commit b88ce14

File tree

2 files changed

+204
-1
lines changed

2 files changed

+204
-1
lines changed

lib-extra/src/main/java/com/diffplug/spotless/extra/GitRatchet.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public boolean isClean(Project project, ObjectId treeSha, String relativePathUni
9696
DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX, DirCacheIterator.class);
9797
WorkingTreeIterator workingTreeIterator = treeWalk.getTree(WORKDIR, WorkingTreeIterator.class);
9898

99-
boolean hasTree = treeIterator != null;
99+
boolean hasTree = treeIterator != null && workingTreeIterator != null;
100100
boolean hasDirCache = dirCacheIterator != null;
101101

102102
if (!hasTree) {

lib-extra/src/test/java/com/diffplug/spotless/extra/GitRachetMergeBaseTest.java

Lines changed: 203 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
*/
1616
package com.diffplug.spotless.extra;
1717

18+
import static org.junit.jupiter.api.Assertions.assertThrows;
19+
1820
import java.io.File;
1921
import java.io.IOException;
2022
import java.util.Arrays;
@@ -57,6 +59,207 @@ void test() throws IllegalStateException, GitAPIException, IOException {
5759
}
5860
}
5961

62+
@Test // https://github.com/diffplug/spotless/issues/911
63+
void testGitIgnoredDirectory_ShouldNotThrowNPE() throws IllegalStateException, GitAPIException, IOException {
64+
try (Git git = initRepo()) {
65+
// Create a directory with files and commit them
66+
setFile("useless/Wow.java").toContent("class Wow {}");
67+
setFile("useless/Another.java").toContent("class Another {}");
68+
addAndCommit(git, "Add useless package");
69+
70+
// Now ignore the entire directory and commit
71+
setFile(".gitignore").toContent("useless/");
72+
addAndCommit(git, "Ignore useless directory");
73+
74+
// The files in the useless directory are now committed but gitignored
75+
// This should not throw NPE
76+
ratchetFrom("main").onlyDirty("useless/Wow.java", "useless/Another.java");
77+
}
78+
}
79+
80+
@Test
81+
void testNewUntrackedFile_ShouldBeDirty() throws IllegalStateException, GitAPIException, IOException {
82+
try (Git git = initRepo()) {
83+
// Create and commit initial file
84+
setFile("committed.java").toContent("class Committed {}");
85+
addAndCommit(git, "Initial commit");
86+
87+
// Create a new file that's not tracked at all (not in gitignore either)
88+
setFile("new_untracked.java").toContent("class NewUntracked {}");
89+
90+
// The new untracked file should be considered dirty
91+
ratchetFrom("main").onlyDirty("new_untracked.java");
92+
}
93+
}
94+
95+
@Test
96+
void testModifiedTrackedFile_ShouldBeDirty() throws IllegalStateException, GitAPIException, IOException {
97+
try (Git git = initRepo()) {
98+
// Create and commit initial file
99+
setFile("Main.java").toContent("class Main { void old() {} }");
100+
addAndCommit(git, "Initial commit");
101+
102+
// Modify the file
103+
setFile("Main.java").toContent("class Main { void newMethod() {} }");
104+
105+
// The modified file should be considered dirty
106+
ratchetFrom("main").onlyDirty("Main.java");
107+
}
108+
}
109+
110+
@Test
111+
void testDeletedFile_ShouldBeDirty() throws IllegalStateException, GitAPIException, IOException {
112+
try (Git git = initRepo()) {
113+
// Create and commit multiple files
114+
setFile("keep.java").toContent("class Keep {}");
115+
setFile("delete.java").toContent("class Delete {}");
116+
addAndCommit(git, "Initial commit");
117+
118+
// Delete one file
119+
new File(rootFolder(), "delete.java").delete();
120+
121+
// The deleted file should be considered dirty
122+
ratchetFrom("main").onlyDirty("delete.java");
123+
}
124+
}
125+
126+
@Test
127+
void testRenamedFile_ShouldBeDirty() throws IllegalStateException, GitAPIException, IOException {
128+
try (Git git = initRepo()) {
129+
// Create and commit initial file
130+
setFile("OldName.java").toContent("class OldName {}");
131+
addAndCommit(git, "Initial commit");
132+
133+
// Rename the file (Git sees this as delete + add)
134+
File oldFile = new File(rootFolder(), "OldName.java");
135+
File newFile = new File(rootFolder(), "NewName.java");
136+
oldFile.renameTo(newFile);
137+
138+
// Both old and new files should be considered dirty
139+
ratchetFrom("main").onlyDirty("OldName.java", "NewName.java");
140+
}
141+
}
142+
143+
@Test
144+
void testStagedButUncommittedChanges_ShouldBeDirty() throws IllegalStateException, GitAPIException, IOException {
145+
try (Git git = initRepo()) {
146+
// Create and commit initial file
147+
setFile("Test.java").toContent("class Test {}");
148+
addAndCommit(git, "Initial commit");
149+
150+
// Modify and stage the file but don't commit
151+
setFile("Test.java").toContent("class Test { void newMethod() {} }");
152+
git.add().addFilepattern("Test.java").call();
153+
154+
// The staged but uncommitted file should be considered dirty
155+
ratchetFrom("main").onlyDirty("Test.java");
156+
}
157+
}
158+
159+
@Test
160+
void testMultipleBranchesWithDifferentFiles() throws IllegalStateException, GitAPIException, IOException {
161+
try (Git git = initRepo()) {
162+
// Initial commit
163+
setFile("base.txt").toContent("base");
164+
addAndCommit(git, "Initial commit");
165+
166+
// Branch A changes
167+
git.checkout().setCreateBranch(true).setName("branch-a").call();
168+
setFile("a-only.txt").toContent("a content");
169+
addAndCommit(git, "Branch A commit");
170+
171+
// Branch B changes
172+
git.checkout().setName("main").call();
173+
git.checkout().setCreateBranch(true).setName("branch-b").call();
174+
setFile("b-only.txt").toContent("b content");
175+
addAndCommit(git, "Branch B commit");
176+
177+
// Check from both branches - each should only see their own changes as dirty
178+
git.checkout().setName("main").call();
179+
ratchetFrom("branch-a").onlyDirty("a-only.txt");
180+
ratchetFrom("branch-b").onlyDirty("b-only.txt");
181+
}
182+
}
183+
184+
@Test
185+
void testNestedDirectoryStructure() throws IllegalStateException, GitAPIException, IOException {
186+
try (Git git = initRepo()) {
187+
// Create nested directory structure
188+
setFile("src/main/java/com/example/Main.java").toContent("package com.example; class Main {}");
189+
setFile("src/main/java/com/example/Util.java").toContent("package com.example; class Util {}");
190+
setFile("src/test/java/com/example/MainTest.java").toContent("package com.example; class MainTest {}");
191+
addAndCommit(git, "Add nested structure");
192+
193+
// Modify only one nested file
194+
setFile("src/main/java/com/example/Util.java").toContent("package com.example; class Util { void newMethod() {} }");
195+
196+
// Only the modified nested file should be dirty
197+
ratchetFrom("main").onlyDirty("src/main/java/com/example/Util.java");
198+
}
199+
}
200+
201+
@Test
202+
void testNonExistentReference_ShouldThrowException() throws IllegalStateException, GitAPIException, IOException {
203+
try (Git git = initRepo()) {
204+
setFile("test.txt").toContent("test");
205+
addAndCommit(git, "Initial commit");
206+
207+
// Trying to ratchet from non-existent branch should throw
208+
assertThrows(IllegalArgumentException.class, () ->
209+
ratchetFrom("nonexistent-branch"));
210+
}
211+
}
212+
213+
@Test
214+
void testBinaryFile_ShouldBeHandled() throws IllegalStateException, GitAPIException, IOException {
215+
try (Git git = initRepo()) {
216+
// Create and commit binary file
217+
setFile("image.png").toContent("binary content that looks like an image");
218+
addAndCommit(git, "Add binary file");
219+
220+
// Modify binary content
221+
setFile("image.png").toContent("modified binary content");
222+
223+
// Binary file should be detected as dirty
224+
ratchetFrom("main").onlyDirty("image.png");
225+
}
226+
}
227+
228+
@Test
229+
void testSymlink_ShouldBeHandled() throws IllegalStateException, GitAPIException, IOException {
230+
try (Git git = initRepo()) {
231+
// This test would require creating actual symlinks
232+
// For now, we'll test that the code doesn't break with special files
233+
setFile("regular.txt").toContent("regular file");
234+
setFile("special.txt").toContent("special file");
235+
addAndCommit(git, "Add files");
236+
237+
// Modify one file
238+
setFile("special.txt").toContent("modified special file");
239+
240+
// Should correctly identify the modified file
241+
ratchetFrom("main").onlyDirty("special.txt");
242+
}
243+
}
244+
245+
@Test
246+
void testMultipleProjectsInSameRepo() throws IllegalStateException, GitAPIException, IOException {
247+
try (Git git = initRepo()) {
248+
// Simulate multiple projects in same repo
249+
setFile("project1/src/Main.java").toContent("class Main {}");
250+
setFile("project2/src/Other.java").toContent("class Other {}");
251+
setFile("shared/common.txt").toContent("shared");
252+
addAndCommit(git, "Add projects");
253+
254+
// Modify files in different "projects"
255+
setFile("project1/src/Main.java").toContent("class Main { void change() {} }");
256+
setFile("shared/common.txt").toContent("modified shared");
257+
258+
// Should detect all modified files
259+
ratchetFrom("main").onlyDirty("project1/src/Main.java", "shared/common.txt");
260+
}
261+
}
262+
60263
static class GitRatchetSimple extends GitRatchet<File> {
61264
@Override
62265
protected File getDir(File project) {

0 commit comments

Comments
 (0)