1
1
/*
2
- * Copyright 2016 DiffPlug
2
+ * Copyright 2016-2020 DiffPlug
3
3
*
4
4
* Licensed under the Apache License, Version 2.0 (the "License");
5
5
* you may not use this file except in compliance with the License.
22
22
import java .io .IOException ;
23
23
import java .io .InputStream ;
24
24
import java .io .Serializable ;
25
- import java .util .ArrayList ;
26
- import java .util .Collection ;
27
25
import java .util .Collections ;
28
26
import java .util .HashMap ;
29
- import java .util .HashSet ;
30
27
import java .util .List ;
31
28
import java .util .Locale ;
32
29
import java .util .Map ;
33
30
import java .util .Objects ;
34
- import java .util .Set ;
35
31
import java .util .function .Supplier ;
36
- import java .util .stream .Collectors ;
37
- import java .util .stream .Stream ;
38
32
39
33
import javax .annotation .Nullable ;
40
34
51
45
import org .eclipse .jgit .util .SystemReader ;
52
46
53
47
import com .googlecode .concurrenttrees .radix .ConcurrentRadixTree ;
54
- import com .googlecode .concurrenttrees .radix .node .Node ;
55
48
import com .googlecode .concurrenttrees .radix .node .concrete .DefaultCharSequenceNodeFactory ;
56
49
57
50
import com .diffplug .common .base .Errors ;
58
- import com .diffplug .common .tree .TreeStream ;
59
51
import com .diffplug .spotless .FileSignature ;
60
52
import com .diffplug .spotless .LazyForwardingEquality ;
61
53
import com .diffplug .spotless .LineEnding ;
@@ -72,62 +64,82 @@ public final class GitAttributesLineEndings {
72
64
// prevent direct instantiation
73
65
private GitAttributesLineEndings () {}
74
66
75
- public static Policy create (File projectDir , Supplier <Iterable <File >> toFormat ) {
76
- return new Policy (projectDir , toFormat );
67
+ /**
68
+ * Creates a line-endings policy whose serialized state is relativized against projectDir,
69
+ * at the cost of eagerly evaluating the line-ending state of every target file when the
70
+ * policy is checked for equality with another policy.
71
+ */
72
+ public static LineEnding .Policy create (File projectDir , Supplier <Iterable <File >> toFormat ) {
73
+ return new RelocatablePolicy (projectDir , toFormat );
77
74
}
78
75
79
- static class Policy extends LazyForwardingEquality <FileState > implements LineEnding .Policy {
80
- private static final long serialVersionUID = 1L ;
76
+ static class RelocatablePolicy extends LazyForwardingEquality <CachedEndings > implements LineEnding .Policy {
77
+ private static final long serialVersionUID = 5868522122123693015L ;
81
78
82
79
final transient File projectDir ;
83
80
final transient Supplier <Iterable <File >> toFormat ;
84
81
85
- Policy (File projectDir , Supplier <Iterable <File >> toFormat ) {
82
+ RelocatablePolicy (File projectDir , Supplier <Iterable <File >> toFormat ) {
86
83
this .projectDir = Objects .requireNonNull (projectDir , "projectDir" );
87
84
this .toFormat = Objects .requireNonNull (toFormat , "toFormat" );
88
85
}
89
86
90
87
@ Override
91
- protected FileState calculateState () throws Exception {
92
- return new FileState (projectDir , toFormat .get ());
88
+ protected CachedEndings calculateState () throws Exception {
89
+ Runtime runtime = new RuntimeInit (projectDir , toFormat .get ()).atRuntime ();
90
+ return new CachedEndings (projectDir , runtime , toFormat .get ());
93
91
}
94
92
95
- /**
96
- * Initializing the state() for up-to-date checking is faster than the full initialization
97
- * needed to actually do the formatting. We load the Runtime lazily from the state().
98
- */
99
- transient Runtime runtime ;
100
-
101
93
@ Override
102
94
public String getEndingFor (File file ) {
103
- if (runtime == null ) {
104
- runtime = state ().atRuntime ();
105
- }
106
- return runtime .getEndingFor (file );
95
+ return state ().endingFor (file );
107
96
}
108
97
}
109
98
110
99
@ SuppressFBWarnings ("SE_TRANSIENT_FIELD_NOT_RESTORED" )
111
- static class FileState implements Serializable {
112
- private static final long serialVersionUID = 1L ;
100
+ static class CachedEndings implements Serializable {
101
+ private static final long serialVersionUID = -2534772773057900619L ;
102
+
103
+ /** this is transient, to simulate PathSensitive.RELATIVE */
104
+ transient final String rootDir ;
105
+ /** the line ending used for most files */
106
+ final String defaultEnding ;
107
+ /** any exceptions to that default, in terms of relative path from rootDir */
108
+ final ConcurrentRadixTree <String > hasNonDefaultEnding = new ConcurrentRadixTree <>(new DefaultCharSequenceNodeFactory ());
109
+
110
+ CachedEndings (File projectDir , Runtime runtime , Iterable <File > toFormat ) {
111
+ rootDir = FileSignature .pathNativeToUnix (projectDir .getAbsolutePath ()) + "/" ;
112
+ defaultEnding = runtime .defaultEnding ;
113
+ for (File file : toFormat ) {
114
+ String ending = runtime .getEndingFor (file );
115
+ if (!ending .equals (defaultEnding )) {
116
+ String path = FileSignature .pathNativeToUnix (file .getAbsolutePath ());
117
+ hasNonDefaultEnding .put (path , ending );
118
+ }
119
+ }
120
+ }
113
121
122
+ /** Returns the line ending appropriate for the given file. */
123
+ public String endingFor (File file ) {
124
+ String path = FileSignature .pathNativeToUnix (file .getAbsolutePath ());
125
+ String subpath = FileSignature .subpath (rootDir , path );
126
+ String ending = hasNonDefaultEnding .getValueForExactKey (subpath );
127
+ return ending == null ? defaultEnding : ending ;
128
+ }
129
+ }
130
+
131
+ static class RuntimeInit {
114
132
/** /etc/gitconfig (system-global), ~/.gitconfig, project/.git/config (each might-not exist). */
115
- transient final FileBasedConfig systemConfig , userConfig , repoConfig ;
133
+ final FileBasedConfig systemConfig , userConfig , repoConfig ;
116
134
117
135
/** Global .gitattributes file pointed at by systemConfig or userConfig, and the file in the repo. */
118
- transient final @ Nullable File globalAttributesFile , repoAttributesFile ;
136
+ final @ Nullable File globalAttributesFile , repoAttributesFile ;
119
137
120
138
/** git worktree root, might not exist if we're not in a git repo. */
121
- transient final @ Nullable File workTree ;
122
-
123
- /** All the .gitattributes files in the work tree that we're formatting. */
124
- transient final List <File > gitattributes ;
125
-
126
- /** The signature of *all* of the files below. */
127
- final FileSignature signature ;
139
+ final @ Nullable File workTree ;
128
140
129
141
@ SuppressFBWarnings ("SIC_INNER_SHOULD_BE_STATIC_ANON" )
130
- FileState (File projectDir , Iterable <File > toFormat ) throws IOException {
142
+ RuntimeInit (File projectDir , Iterable <File > toFormat ) throws IOException {
131
143
requireElementsNonNull (toFormat );
132
144
/////////////////////////////////
133
145
// USER AND SYSTEM-WIDE VALUES //
@@ -178,56 +190,6 @@ public boolean isOutdated() {
178
190
repoAttributesFile = null ;
179
191
}
180
192
Errors .log ().run (repoConfig ::load );
181
-
182
- // The .gitattributes files which apply to the files we are formatting
183
- gitattributes = gitAttributes (toFormat );
184
-
185
- // find every actual File which exists above
186
- Stream <File > misc = Stream .of (systemConfig .getFile (), userConfig .getFile (), repoConfig .getFile (), globalAttributesFile , repoAttributesFile );
187
- List <File > toSign = Stream .concat (gitattributes .stream (), misc )
188
- .filter (file -> file != null && file .exists () && file .isFile ())
189
- .collect (Collectors .toList ());
190
- // sign it for up-to-date checking
191
- signature = FileSignature .signAsSet (toSign );
192
- }
193
-
194
- /** Returns all of the .gitattributes files which affect the given files. */
195
- static List <File > gitAttributes (Iterable <File > files ) {
196
- // build a radix tree out of all the parent folders in these files
197
- ConcurrentRadixTree <String > tree = new ConcurrentRadixTree <>(new DefaultCharSequenceNodeFactory ());
198
- for (File file : files ) {
199
- String parentPath = file .getParent () + File .separator ;
200
- tree .putIfAbsent (parentPath , parentPath );
201
- }
202
- // traverse the edge nodes to find the outermost folders
203
- List <File > edgeFolders = TreeStream .depthFirst (Node ::getOutgoingEdges , tree .getNode ())
204
- .filter (node -> node .getOutgoingEdges ().isEmpty () && node .getValue () != null )
205
- .map (node -> new File ((String ) node .getValue ()))
206
- .collect (Collectors .toList ());
207
-
208
- List <File > gitAttrFiles = new ArrayList <>();
209
- Set <File > visitedFolders = new HashSet <>();
210
- for (File edgeFolder : edgeFolders ) {
211
- gitAttrAddWithParents (edgeFolder , visitedFolders , gitAttrFiles );
212
- }
213
- return gitAttrFiles ;
214
- }
215
-
216
- /** Searches folder and all its parents for gitattributes files. */
217
- private static void gitAttrAddWithParents (File folder , Set <File > visitedFolders , Collection <File > gitAttrFiles ) {
218
- if (!visitedFolders .add (folder )) {
219
- // bail if we already visited this folder
220
- return ;
221
- }
222
-
223
- File gitAttr = new File (folder , Constants .DOT_GIT_ATTRIBUTES );
224
- if (gitAttr .exists () && gitAttr .isFile ()) {
225
- gitAttrFiles .add (gitAttr );
226
- }
227
- File parentFile = folder .getParentFile ();
228
- if (parentFile != null ) {
229
- gitAttrAddWithParents (folder .getParentFile (), visitedFolders , gitAttrFiles );
230
- }
231
193
}
232
194
233
195
private Runtime atRuntime () {
0 commit comments