Skip to content

Commit 1de86dd

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 1de86dd

File tree

1 file changed

+54
-41
lines changed

1 file changed

+54
-41
lines changed

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

Lines changed: 54 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -56,14 +56,15 @@
5656
*/
5757
public abstract class GitRatchet<Project> implements AutoCloseable {
5858

59+
private final Map<Repository, DirCache> dirCaches = new HashMap<>();
60+
5961
public boolean isClean(Project project, ObjectId treeSha, File file) throws IOException {
60-
Repository repo = repositoryFor(project);
61-
String relativePath = FileSignature.pathNativeToUnix(repo.getWorkTree().toPath().relativize(file.toPath()).toString());
62-
return isClean(project, treeSha, relativePath);
62+
return isClean(
63+
project,
64+
treeSha,
65+
FileSignature.pathNativeToUnix(repositoryFor(project).getWorkTree().toPath().relativize(file.toPath()).toString()));
6366
}
6467

65-
private final Map<Repository, DirCache> dirCaches = new HashMap<>();
66-
6768
/**
6869
* This is the highest-level method, which all the others serve. Given the sha
6970
* of a git tree (not a commit!), and the file in question, this method returns
@@ -73,16 +74,22 @@ public boolean isClean(Project project, ObjectId treeSha, File file) throws IOEx
7374
public boolean isClean(Project project, ObjectId treeSha, String relativePathUnix) throws IOException {
7475
Repository repo = repositoryFor(project);
7576

76-
DirCacheIterator dirCacheIteratorInit;
77+
DirCacheIterator dirCacheIterator;
7778
synchronized (this) {
7879
// each DirCache is thread-safe, and we compute them one-to-one based on `repositoryFor`
7980
DirCache dirCache = dirCaches.computeIfAbsent(repo, Errors.rethrow().wrap(Repository::readDirCache));
80-
dirCacheIteratorInit = new DirCacheIterator(dirCache);
81+
dirCacheIterator = new DirCacheIterator(dirCache);
8182
}
83+
84+
return checkFileCleanliness(treeSha, relativePathUnix, repo, dirCacheIterator);
85+
}
86+
87+
private static boolean checkFileCleanliness(final ObjectId treeSha, final String relativePathUnix,
88+
final Repository repo, final DirCacheIterator dirCacheIterator) throws IOException {
8289
try (TreeWalk treeWalk = new TreeWalk(repo)) {
8390
treeWalk.setRecursive(true);
8491
treeWalk.addTree(treeSha);
85-
treeWalk.addTree(dirCacheIteratorInit);
92+
treeWalk.addTree(dirCacheIterator);
8693
treeWalk.addTree(new FileTreeIterator(repo));
8794
treeWalk.setFilter(AndTreeFilter.create(
8895
PathFilter.create(relativePathUnix),
@@ -93,31 +100,17 @@ public boolean isClean(Project project, ObjectId treeSha, String relativePathUni
93100
return true;
94101
} else {
95102
AbstractTreeIterator treeIterator = treeWalk.getTree(TREE, AbstractTreeIterator.class);
96-
DirCacheIterator dirCacheIterator = treeWalk.getTree(INDEX, DirCacheIterator.class);
97-
WorkingTreeIterator workingTreeIterator = treeWalk.getTree(WORKDIR, WorkingTreeIterator.class);
98-
99-
boolean hasTree = treeIterator != null;
100-
boolean hasDirCache = dirCacheIterator != null;
101-
102-
if (!hasTree) {
103+
if (treeIterator == null) {
103104
// it's not in the tree, so it was added
104105
return false;
105106
} else {
106-
if (hasDirCache) {
107-
boolean treeEqualsIndex = treeIterator.idEqual(dirCacheIterator) && treeIterator.getEntryRawMode() == dirCacheIterator.getEntryRawMode();
108-
boolean indexEqualsWC = !workingTreeIterator.isModified(dirCacheIterator.getDirCacheEntry(), true, treeWalk.getObjectReader());
109-
if (treeEqualsIndex != indexEqualsWC) {
110-
// if one is equal and the other isn't, then it has definitely changed
111-
return false;
112-
} else if (treeEqualsIndex) {
113-
// this means they are all equal to each other, which should never happen
114-
// the IndexDiffFilter should keep those out of the TreeWalk entirely
115-
throw new IllegalStateException("Index status for " + relativePathUnix + " against treeSha " + treeSha + " is invalid.");
116-
} else {
117-
// they are all unique
118-
// we have to check manually
119-
return worktreeIsCleanCheckout(treeWalk);
120-
}
107+
DirCacheIterator indexIterator = treeWalk.getTree(INDEX, DirCacheIterator.class);
108+
if (indexIterator != null) {
109+
boolean treeEqualsIndex = treeIterator.idEqual(indexIterator) &&
110+
treeIterator.getEntryRawMode() == indexIterator.getEntryRawMode();
111+
WorkingTreeIterator workingTreeIterator = treeWalk.getTree(WORKDIR, WorkingTreeIterator.class);
112+
return compareTreeIndexAndWorktreeStates(treeSha, relativePathUnix, treeEqualsIndex,
113+
workingTreeIterator, indexIterator, treeWalk);
121114
} else {
122115
// no dirCache, so we will compare the tree to the workdir manually
123116
return worktreeIsCleanCheckout(treeWalk);
@@ -127,6 +120,27 @@ public boolean isClean(Project project, ObjectId treeSha, String relativePathUni
127120
}
128121
}
129122

123+
/**
124+
* Compares the three states (tree, index, worktree) to determine file cleanliness.
125+
* Handles gitignored files by treating them as not clean.
126+
*/
127+
private static boolean compareTreeIndexAndWorktreeStates(final ObjectId treeSha, final String relativePathUnix,
128+
final boolean treeEqualsIndex, final WorkingTreeIterator workingTreeIterator,
129+
final DirCacheIterator indexIterator, final TreeWalk treeWalk) throws IOException {
130+
if (treeEqualsIndex == workingTreeIterator.isModified(indexIterator.getDirCacheEntry(), true, treeWalk.getObjectReader())) {
131+
// if one is equal and the other isn't, then it has definitely changed
132+
return false;
133+
} else if (treeEqualsIndex) {
134+
// this means they are all equal to each other, which should never happen
135+
// the IndexDiffFilter should keep those out of the TreeWalk entirely
136+
throw new IllegalStateException("Index status for " + relativePathUnix + " against treeSha " + treeSha + " is invalid.");
137+
} else {
138+
// they are all unique
139+
// we have to check manually
140+
return worktreeIsCleanCheckout(treeWalk);
141+
}
142+
}
143+
130144
/** Returns true if the worktree file is a clean checkout of head (possibly smudged). */
131145
private static boolean worktreeIsCleanCheckout(TreeWalk treeWalk) {
132146
return treeWalk.idEqual(TREE, WORKDIR);
@@ -178,16 +192,7 @@ public synchronized ObjectId rootTreeShaOf(Project project, String reference) {
178192
if (commitSha == null) {
179193
throw new IllegalArgumentException("No such reference '" + reference + "'");
180194
}
181-
182-
RevCommit ratchetFrom = revWalk.parseCommit(commitSha);
183-
RevCommit head = revWalk.parseCommit(repo.resolve(Constants.HEAD));
184-
185-
revWalk.setRevFilter(RevFilter.MERGE_BASE);
186-
revWalk.markStart(ratchetFrom);
187-
revWalk.markStart(head);
188-
189-
RevCommit mergeBase = revWalk.next();
190-
treeSha = Optional.ofNullable(mergeBase).orElse(ratchetFrom).getTree();
195+
treeSha = getMergeBaseTreeSha(revWalk, repo, revWalk.parseCommit(commitSha));
191196
}
192197
rootTreeShaCache.put(repo, reference, treeSha.copy());
193198
}
@@ -197,6 +202,13 @@ public synchronized ObjectId rootTreeShaOf(Project project, String reference) {
197202
}
198203
}
199204

205+
private static ObjectId getMergeBaseTreeSha(final RevWalk revWalk, final Repository repo, final RevCommit ratchetFrom) throws IOException {
206+
revWalk.setRevFilter(RevFilter.MERGE_BASE);
207+
revWalk.markStart(ratchetFrom);
208+
revWalk.markStart(revWalk.parseCommit(repo.resolve(Constants.HEAD)));
209+
return Optional.ofNullable(revWalk.next()).orElse(ratchetFrom).getTree();
210+
}
211+
200212
/**
201213
* Returns the sha of the git subtree which represents the root of the given project, or {@link ObjectId#zeroId()}
202214
* if there is no git subtree at the project root.
@@ -224,7 +236,8 @@ public synchronized ObjectId subtreeShaOf(Project project, ObjectId rootTreeSha)
224236

225237
@Override
226238
public void close() {
227-
gitRoots.values().stream()
239+
gitRoots.values()
240+
.stream()
228241
.distinct()
229242
.forEach(Repository::close);
230243
}

0 commit comments

Comments
 (0)