1
2
3
4
5
6
7
8
9
10
11 package org.eclipse.jgit.lib;
12
13 import java.io.File;
14 import java.io.FileNotFoundException;
15 import java.io.IOException;
16 import java.nio.charset.Charset;
17 import java.nio.charset.IllegalCharsetNameException;
18 import java.nio.charset.StandardCharsets;
19 import java.nio.charset.UnsupportedCharsetException;
20 import java.text.MessageFormat;
21 import java.util.Locale;
22
23 import org.eclipse.jgit.annotations.NonNull;
24 import org.eclipse.jgit.annotations.Nullable;
25 import org.eclipse.jgit.errors.ConfigInvalidException;
26 import org.eclipse.jgit.internal.JGitText;
27 import org.eclipse.jgit.lib.Config.ConfigEnum;
28 import org.eclipse.jgit.lib.Config.SectionParser;
29 import org.eclipse.jgit.util.FS;
30 import org.eclipse.jgit.util.IO;
31 import org.eclipse.jgit.util.RawParseUtils;
32 import org.eclipse.jgit.util.StringUtils;
33
34
35
36
37
38
39 public class CommitConfig {
40
41
42
43
44 public static final Config.SectionParser<CommitConfig> KEY = CommitConfig::new;
45
46 private static final String CUT = " ------------------------ >8 ------------------------\n";
47
48 private static final char[] COMMENT_CHARS = { '#', ';', '@', '!', '$', '%',
49 '^', '&', '|', ':' };
50
51
52
53
54
55
56 public enum CleanupMode implements ConfigEnum {
57
58
59
60
61 STRIP,
62
63
64
65
66
67 WHITESPACE,
68
69
70
71
72 VERBATIM,
73
74
75
76
77
78 SCISSORS,
79
80
81
82
83
84
85 DEFAULT;
86
87 @Override
88 public String toConfigValue() {
89 return name().toLowerCase(Locale.ROOT);
90 }
91
92 @Override
93 public boolean matchConfigValue(String in) {
94 return toConfigValue().equals(in);
95 }
96 }
97
98 private final static Charset DEFAULT_COMMIT_MESSAGE_ENCODING = StandardCharsets.UTF_8;
99
100 private String i18nCommitEncoding;
101
102 private String commitTemplatePath;
103
104 private CleanupMode cleanupMode;
105
106 private char commentCharacter = '#';
107
108 private boolean autoCommentChar = false;
109
110 private CommitConfig(Config rc) {
111 commitTemplatePath = rc.getString(ConfigConstants.CONFIG_COMMIT_SECTION,
112 null, ConfigConstants.CONFIG_KEY_COMMIT_TEMPLATE);
113 i18nCommitEncoding = rc.getString(ConfigConstants.CONFIG_SECTION_I18N,
114 null, ConfigConstants.CONFIG_KEY_COMMIT_ENCODING);
115 cleanupMode = rc.getEnum(ConfigConstants.CONFIG_COMMIT_SECTION, null,
116 ConfigConstants.CONFIG_KEY_CLEANUP, CleanupMode.DEFAULT);
117 String comment = rc.getString(ConfigConstants.CONFIG_CORE_SECTION, null,
118 ConfigConstants.CONFIG_KEY_COMMENT_CHAR);
119 if (!StringUtils.isEmptyOrNull(comment)) {
120 if ("auto".equalsIgnoreCase(comment)) {
121 autoCommentChar = true;
122 } else {
123 char first = comment.charAt(0);
124 if (first > ' ' && first < 127) {
125 commentCharacter = first;
126 }
127 }
128 }
129 }
130
131
132
133
134
135
136
137 @Nullable
138 public String getCommitTemplatePath() {
139 return commitTemplatePath;
140 }
141
142
143
144
145
146
147
148 @Nullable
149 public String getCommitEncoding() {
150 return i18nCommitEncoding;
151 }
152
153
154
155
156
157
158
159
160 public char getCommentChar() {
161 return commentCharacter;
162 }
163
164
165
166
167
168
169
170
171
172
173
174
175
176 public char getCommentChar(String text) {
177 if (isAutoCommentChar()) {
178 char toUse = determineCommentChar(text);
179 if (toUse > 0) {
180 return toUse;
181 }
182 return '#';
183 }
184 return getCommentChar();
185 }
186
187
188
189
190
191
192
193
194 public boolean isAutoCommentChar() {
195 return autoCommentChar;
196 }
197
198
199
200
201
202
203
204
205
206 @NonNull
207 public CleanupMode getCleanupMode() {
208 return cleanupMode;
209 }
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226 @NonNull
227 public CleanupMode resolve(@NonNull CleanupMode mode,
228 boolean defaultStrip) {
229 if (CleanupMode.DEFAULT == mode) {
230 CleanupMode defaultMode = getCleanupMode();
231 if (CleanupMode.DEFAULT == defaultMode) {
232 return defaultStrip ? CleanupMode.STRIP
233 : CleanupMode.WHITESPACE;
234 }
235 return defaultMode;
236 }
237 return mode;
238 }
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257 @Nullable
258 public String getCommitTemplateContent(@NonNull Repository repository)
259 throws FileNotFoundException, IOException, ConfigInvalidException {
260
261 if (commitTemplatePath == null) {
262 return null;
263 }
264
265 File commitTemplateFile;
266 FS fileSystem = repository.getFS();
267 if (commitTemplatePath.startsWith("~/")) {
268 commitTemplateFile = fileSystem.resolve(fileSystem.userHome(),
269 commitTemplatePath.substring(2));
270 } else {
271 commitTemplateFile = fileSystem.resolve(null, commitTemplatePath);
272 }
273 if (!commitTemplateFile.isAbsolute()) {
274 commitTemplateFile = fileSystem.resolve(
275 repository.getWorkTree().getAbsoluteFile(),
276 commitTemplatePath);
277 }
278
279 Charset commitMessageEncoding = getEncoding();
280 return RawParseUtils.decode(commitMessageEncoding,
281 IO.readFully(commitTemplateFile));
282
283 }
284
285 private Charset getEncoding() throws ConfigInvalidException {
286 Charset commitMessageEncoding = DEFAULT_COMMIT_MESSAGE_ENCODING;
287
288 if (i18nCommitEncoding == null) {
289 return null;
290 }
291
292 try {
293 commitMessageEncoding = Charset.forName(i18nCommitEncoding);
294 } catch (IllegalCharsetNameException | UnsupportedCharsetException e) {
295 throw new ConfigInvalidException(MessageFormat.format(
296 JGitText.get().invalidEncoding, i18nCommitEncoding), e);
297 }
298
299 return commitMessageEncoding;
300 }
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318 public static String cleanText(@NonNull String text,
319 @NonNull CleanupMode mode, char commentChar) {
320 String toProcess = text;
321 boolean strip = false;
322 switch (mode) {
323 case VERBATIM:
324 return text;
325 case SCISSORS:
326 String cut = commentChar + CUT;
327 if (text.startsWith(cut)) {
328 return "";
329 }
330 int cutPos = text.indexOf('\n' + cut);
331 if (cutPos >= 0) {
332 toProcess = text.substring(0, cutPos + 1);
333 }
334 break;
335 case STRIP:
336 strip = true;
337 break;
338 case WHITESPACE:
339 break;
340 case DEFAULT:
341 default:
342
343 throw new IllegalArgumentException("Invalid clean-up mode " + mode);
344 }
345
346 StringBuilder result = new StringBuilder();
347 boolean lastWasEmpty = true;
348 for (String line : toProcess.split("\n")) {
349 line = line.stripTrailing();
350 if (line.isEmpty()) {
351 if (!lastWasEmpty) {
352 result.append('\n');
353 lastWasEmpty = true;
354 }
355 } else if (!strip || !isComment(line, commentChar)) {
356 lastWasEmpty = false;
357 result.append(line).append('\n');
358 }
359 }
360 int bufferSize = result.length();
361 if (lastWasEmpty && bufferSize > 0) {
362 bufferSize--;
363 result.setLength(bufferSize);
364 }
365 if (bufferSize > 0 && !toProcess.endsWith("\n")) {
366 if (result.charAt(bufferSize - 1) == '\n') {
367 result.setLength(bufferSize - 1);
368 }
369 }
370 return result.toString();
371 }
372
373 private static boolean isComment(String text, char commentChar) {
374 int len = text.length();
375 for (int i = 0; i < len; i++) {
376 char ch = text.charAt(i);
377 if (!Character.isWhitespace(ch)) {
378 return ch == commentChar;
379 }
380 }
381 return false;
382 }
383
384
385
386
387
388
389
390
391
392
393
394
395
396 public static char determineCommentChar(String text) {
397 if (StringUtils.isEmptyOrNull(text)) {
398 return '#';
399 }
400 final boolean[] inUse = new boolean[127];
401 for (String line : text.split("\n")) {
402 int len = line.length();
403 for (int i = 0; i < len; i++) {
404 char ch = line.charAt(i);
405 if (!Character.isWhitespace(ch)) {
406 if (ch >= 0 && ch < inUse.length) {
407 inUse[ch] = true;
408 }
409 break;
410 }
411 }
412 }
413 for (char candidate : COMMENT_CHARS) {
414 if (!inUse[candidate]) {
415 return candidate;
416 }
417 }
418 return (char) 0;
419 }
420 }