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.util.regex.Pattern;
46
47 import org.eclipse.jgit.lib.Constants;
48 import org.eclipse.jgit.lib.ObjectId;
49 import org.eclipse.jgit.lib.ObjectInserter;
50 import org.eclipse.jgit.lib.PersonIdent;
51
52
53
54
55
56
57
58
59
60
61 public class ChangeIdUtil {
62
63 static final String CHANGE_ID = "Change-Id:";
64
65
66 @SuppressWarnings("nls")
67 static String clean(String msg) {
68 return msg.
69 replaceAll("(?i)(?m)^Signed-off-by:.*$\n?", "").
70 replaceAll("(?m)^#.*$\n?", "").
71 replaceAll("(?m)\n\n\n+", "\\\n").
72 replaceAll("\\n*$", "").
73 replaceAll("(?s)\ndiff --git.*", "").
74 trim();
75 }
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 String cleanMessage = clean(message);
99 if (cleanMessage.length() == 0)
100 return null;
101 StringBuilder b = new StringBuilder();
102 b.append("tree ");
103 b.append(ObjectId.toString(treeId));
104 b.append("\n");
105 if (firstParentId != null) {
106 b.append("parent ");
107 b.append(ObjectId.toString(firstParentId));
108 b.append("\n");
109 }
110 b.append("author ");
111 b.append(author.toExternalString());
112 b.append("\n");
113 b.append("committer ");
114 b.append(committer.toExternalString());
115 b.append("\n\n");
116 b.append(cleanMessage);
117 try (ObjectInserter f = new ObjectInserter.Formatter()) {
118 return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString()));
119 }
120 }
121
122 private static final Pattern issuePattern = Pattern
123 .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$");
124
125 private static final Pattern footerPattern = Pattern
126 .compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$
127
128 private static final Pattern changeIdPattern = Pattern
129 .compile("(^" + CHANGE_ID + " *I[a-f0-9]{40}$)");
130
131 private static final Pattern includeInFooterPattern = Pattern
132 .compile("^[ \\[].*$");
133
134 private static final Pattern trailingWhitespace = Pattern.compile("\\s+$");
135
136
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
170
171
172 public static String insertId(String message, ObjectId changeId,
173 boolean replaceExisting) {
174 int indexOfChangeId = indexOfChangeId(message, "\n");
175 if (indexOfChangeId > 0) {
176 if (!replaceExisting)
177 return message;
178 else {
179 StringBuilder ret = new StringBuilder(message.substring(0,
180 indexOfChangeId));
181 ret.append(CHANGE_ID);
182 ret.append(" I");
183 ret.append(ObjectId.toString(changeId));
184 int indexOfNextLineBreak = message.indexOf("\n",
185 indexOfChangeId);
186 if (indexOfNextLineBreak > 0)
187 ret.append(message.substring(indexOfNextLineBreak));
188 return ret.toString();
189 }
190 }
191
192 String[] lines = message.split("\n");
193 int footerFirstLine = indexOfFirstFooterLine(lines);
194 int insertAfter = footerFirstLine;
195 for (int i = footerFirstLine; i < lines.length; ++i) {
196 if (issuePattern.matcher(lines[i]).matches()) {
197 insertAfter = i + 1;
198 continue;
199 }
200 break;
201 }
202 StringBuilder ret = new StringBuilder();
203 int i = 0;
204 for (; i < insertAfter; ++i) {
205 ret.append(lines[i]);
206 ret.append("\n");
207 }
208 if (insertAfter == lines.length && insertAfter == footerFirstLine)
209 ret.append("\n");
210 ret.append(CHANGE_ID);
211 ret.append(" I");
212 ret.append(ObjectId.toString(changeId));
213 ret.append("\n");
214 for (; i < lines.length; ++i) {
215 ret.append(lines[i]);
216 ret.append("\n");
217 }
218 return ret.toString();
219 }
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236 public static int indexOfChangeId(String message, String delimiter) {
237 String[] lines = message.split(delimiter);
238 if (lines.length == 0)
239 return -1;
240 int indexOfChangeIdLine = 0;
241 boolean inFooter = false;
242 for (int i = lines.length - 1; i >= 0; --i) {
243 if (!inFooter && isEmptyLine(lines[i]))
244 continue;
245 inFooter = true;
246 if (changeIdPattern.matcher(trimRight(lines[i])).matches()) {
247 indexOfChangeIdLine = i;
248 break;
249 } else if (isEmptyLine(lines[i]) || i == 0)
250 return -1;
251 }
252 int indexOfChangeIdLineinString = 0;
253 for (int i = 0; i < indexOfChangeIdLine; ++i)
254 indexOfChangeIdLineinString += lines[i].length()
255 + delimiter.length();
256 return indexOfChangeIdLineinString
257 + lines[indexOfChangeIdLine].indexOf(CHANGE_ID);
258 }
259
260 private static boolean isEmptyLine(String line) {
261 return line.trim().length() == 0;
262 }
263
264 private static String trimRight(String s) {
265 return trailingWhitespace.matcher(s).replaceAll("");
266 }
267
268
269
270
271
272
273
274
275
276
277
278 public static int indexOfFirstFooterLine(String[] lines) {
279 int footerFirstLine = lines.length;
280 for (int i = lines.length - 1; i > 1; --i) {
281 if (footerPattern.matcher(lines[i]).matches()) {
282 footerFirstLine = i;
283 continue;
284 }
285 if (footerFirstLine != lines.length && lines[i].length() == 0)
286 break;
287 if (footerFirstLine != lines.length
288 && includeInFooterPattern.matcher(lines[i]).matches()) {
289 footerFirstLine = i + 1;
290 continue;
291 }
292 footerFirstLine = lines.length;
293 break;
294 }
295 return footerFirstLine;
296 }
297 }