1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43 package org.eclipse.jgit.util;
44
45 import java.io.IOException;
46 import java.util.regex.Pattern;
47
48 import org.eclipse.jgit.lib.Constants;
49 import org.eclipse.jgit.lib.ObjectInserter;
50 import org.eclipse.jgit.lib.ObjectId;
51 import org.eclipse.jgit.lib.PersonIdent;
52
53
54
55
56
57
58
59
60
61
62 public class ChangeIdUtil {
63
64 static final String CHANGE_ID = "Change-Id:";
65
66
67 @SuppressWarnings("nls")
68 static String clean(String msg) {
69 return msg.
70 replaceAll("(?i)(?m)^Signed-off-by:.*$\n?", "").
71 replaceAll("(?m)^#.*$\n?", "").
72 replaceAll("(?m)\n\n\n+", "\\\n").
73 replaceAll("\\n*$", "").
74 replaceAll("(?s)\ndiff --git.*", "").
75 trim();
76 }
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95 public static ObjectId computeChangeId(final ObjectId treeId,
96 final ObjectId firstParentId, final PersonIdent author,
97 final PersonIdent committer, final String message)
98 throws IOException {
99 String cleanMessage = clean(message);
100 if (cleanMessage.length() == 0)
101 return null;
102 StringBuilder b = new StringBuilder();
103 b.append("tree ");
104 b.append(ObjectId.toString(treeId));
105 b.append("\n");
106 if (firstParentId != null) {
107 b.append("parent ");
108 b.append(ObjectId.toString(firstParentId));
109 b.append("\n");
110 }
111 b.append("author ");
112 b.append(author.toExternalString());
113 b.append("\n");
114 b.append("committer ");
115 b.append(committer.toExternalString());
116 b.append("\n\n");
117 b.append(cleanMessage);
118 try (ObjectInserter f = new ObjectInserter.Formatter()) {
119 return f.idFor(Constants.OBJ_COMMIT,
120 b.toString().getBytes(Constants.CHARACTER_ENCODING));
121 }
122 }
123
124 private static final Pattern issuePattern = Pattern
125 .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$");
126
127 private static final Pattern footerPattern = Pattern
128 .compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$
129
130 private static final Pattern changeIdPattern = Pattern
131 .compile("(^" + CHANGE_ID + " *I[a-f0-9]{40}$)");
132
133 private static final Pattern includeInFooterPattern = Pattern
134 .compile("^[ \\[].*$");
135
136 private static final Pattern trailingWhitespace = Pattern.compile("\\s+$");
137
138
139
140
141
142
143
144
145
146
147
148 public static String insertId(String message, ObjectId changeId) {
149 return insertId(message, changeId, false);
150 }
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169 public static String insertId(String message, ObjectId changeId,
170 boolean replaceExisting) {
171 int indexOfChangeId = indexOfChangeId(message, "\n");
172 if (indexOfChangeId > 0) {
173 if (!replaceExisting)
174 return message;
175 else {
176 StringBuilder ret = new StringBuilder(message.substring(0,
177 indexOfChangeId));
178 ret.append(CHANGE_ID);
179 ret.append(" I");
180 ret.append(ObjectId.toString(changeId));
181 int indexOfNextLineBreak = message.indexOf("\n",
182 indexOfChangeId);
183 if (indexOfNextLineBreak > 0)
184 ret.append(message.substring(indexOfNextLineBreak));
185 return ret.toString();
186 }
187 }
188
189 String[] lines = message.split("\n");
190 int footerFirstLine = indexOfFirstFooterLine(lines);
191 int insertAfter = footerFirstLine;
192 for (int i = footerFirstLine; i < lines.length; ++i) {
193 if (issuePattern.matcher(lines[i]).matches()) {
194 insertAfter = i + 1;
195 continue;
196 }
197 break;
198 }
199 StringBuilder ret = new StringBuilder();
200 int i = 0;
201 for (; i < insertAfter; ++i) {
202 ret.append(lines[i]);
203 ret.append("\n");
204 }
205 if (insertAfter == lines.length && insertAfter == footerFirstLine)
206 ret.append("\n");
207 ret.append(CHANGE_ID);
208 ret.append(" I");
209 ret.append(ObjectId.toString(changeId));
210 ret.append("\n");
211 for (; i < lines.length; ++i) {
212 ret.append(lines[i]);
213 ret.append("\n");
214 }
215 return ret.toString();
216 }
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232 public static int indexOfChangeId(String message, String delimiter) {
233 String[] lines = message.split(delimiter);
234 if (lines.length == 0)
235 return -1;
236 int indexOfChangeIdLine = 0;
237 boolean inFooter = false;
238 for (int i = lines.length - 1; i >= 0; --i) {
239 if (!inFooter && isEmptyLine(lines[i]))
240 continue;
241 inFooter = true;
242 if (changeIdPattern.matcher(trimRight(lines[i])).matches()) {
243 indexOfChangeIdLine = i;
244 break;
245 } else if (isEmptyLine(lines[i]) || i == 0)
246 return -1;
247 }
248 int indexOfChangeIdLineinString = 0;
249 for (int i = 0; i < indexOfChangeIdLine; ++i)
250 indexOfChangeIdLineinString += lines[i].length()
251 + delimiter.length();
252 return indexOfChangeIdLineinString
253 + lines[indexOfChangeIdLine].indexOf(CHANGE_ID);
254 }
255
256 private static boolean isEmptyLine(String line) {
257 return line.trim().length() == 0;
258 }
259
260 private static String trimRight(String s) {
261 return trailingWhitespace.matcher(s).replaceAll("");
262 }
263
264
265
266
267
268
269
270
271
272
273
274 public static int indexOfFirstFooterLine(String[] lines) {
275 int footerFirstLine = lines.length;
276 for (int i = lines.length - 1; i > 1; --i) {
277 if (footerPattern.matcher(lines[i]).matches()) {
278 footerFirstLine = i;
279 continue;
280 }
281 if (footerFirstLine != lines.length && lines[i].length() == 0)
282 break;
283 if (footerFirstLine != lines.length
284 && includeInFooterPattern.matcher(lines[i]).matches()) {
285 footerFirstLine = i + 1;
286 continue;
287 }
288 footerFirstLine = lines.length;
289 break;
290 }
291 return footerFirstLine;
292 }
293 }