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 public static ObjectId computeChangeId(final ObjectId treeId,
94 final ObjectId firstParentId, final PersonIdent author,
95 final PersonIdent committer, final String message) {
96 String cleanMessage = clean(message);
97 if (cleanMessage.length() == 0)
98 return null;
99 StringBuilder b = new StringBuilder();
100 b.append("tree ");
101 b.append(ObjectId.toString(treeId));
102 b.append("\n");
103 if (firstParentId != null) {
104 b.append("parent ");
105 b.append(ObjectId.toString(firstParentId));
106 b.append("\n");
107 }
108 b.append("author ");
109 b.append(author.toExternalString());
110 b.append("\n");
111 b.append("committer ");
112 b.append(committer.toExternalString());
113 b.append("\n\n");
114 b.append(cleanMessage);
115 try (ObjectInserter f = new ObjectInserter.Formatter()) {
116 return f.idFor(Constants.OBJ_COMMIT, Constants.encode(b.toString()));
117 }
118 }
119
120 private static final Pattern issuePattern = Pattern
121 .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$");
122
123 private static final Pattern footerPattern = Pattern
124 .compile("(^[a-zA-Z0-9-]+:(?!//).*$)"); //$NON-NLS-1$
125
126 private static final Pattern changeIdPattern = Pattern
127 .compile("(^" + CHANGE_ID + " *I[a-f0-9]{40}$)");
128
129 private static final Pattern includeInFooterPattern = Pattern
130 .compile("^[ \\[].*$");
131
132 private static final Pattern trailingWhitespace = Pattern.compile("\\s+$");
133
134
135
136
137
138
139
140
141
142
143
144 public static String insertId(String message, ObjectId changeId) {
145 return insertId(message, changeId, false);
146 }
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165 public static String insertId(String message, ObjectId changeId,
166 boolean replaceExisting) {
167 int indexOfChangeId = indexOfChangeId(message, "\n");
168 if (indexOfChangeId > 0) {
169 if (!replaceExisting)
170 return message;
171 else {
172 StringBuilder ret = new StringBuilder(message.substring(0,
173 indexOfChangeId));
174 ret.append(CHANGE_ID);
175 ret.append(" I");
176 ret.append(ObjectId.toString(changeId));
177 int indexOfNextLineBreak = message.indexOf("\n",
178 indexOfChangeId);
179 if (indexOfNextLineBreak > 0)
180 ret.append(message.substring(indexOfNextLineBreak));
181 return ret.toString();
182 }
183 }
184
185 String[] lines = message.split("\n");
186 int footerFirstLine = indexOfFirstFooterLine(lines);
187 int insertAfter = footerFirstLine;
188 for (int i = footerFirstLine; i < lines.length; ++i) {
189 if (issuePattern.matcher(lines[i]).matches()) {
190 insertAfter = i + 1;
191 continue;
192 }
193 break;
194 }
195 StringBuilder ret = new StringBuilder();
196 int i = 0;
197 for (; i < insertAfter; ++i) {
198 ret.append(lines[i]);
199 ret.append("\n");
200 }
201 if (insertAfter == lines.length && insertAfter == footerFirstLine)
202 ret.append("\n");
203 ret.append(CHANGE_ID);
204 ret.append(" I");
205 ret.append(ObjectId.toString(changeId));
206 ret.append("\n");
207 for (; i < lines.length; ++i) {
208 ret.append(lines[i]);
209 ret.append("\n");
210 }
211 return ret.toString();
212 }
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228 public static int indexOfChangeId(String message, String delimiter) {
229 String[] lines = message.split(delimiter);
230 if (lines.length == 0)
231 return -1;
232 int indexOfChangeIdLine = 0;
233 boolean inFooter = false;
234 for (int i = lines.length - 1; i >= 0; --i) {
235 if (!inFooter && isEmptyLine(lines[i]))
236 continue;
237 inFooter = true;
238 if (changeIdPattern.matcher(trimRight(lines[i])).matches()) {
239 indexOfChangeIdLine = i;
240 break;
241 } else if (isEmptyLine(lines[i]) || i == 0)
242 return -1;
243 }
244 int indexOfChangeIdLineinString = 0;
245 for (int i = 0; i < indexOfChangeIdLine; ++i)
246 indexOfChangeIdLineinString += lines[i].length()
247 + delimiter.length();
248 return indexOfChangeIdLineinString
249 + lines[indexOfChangeIdLine].indexOf(CHANGE_ID);
250 }
251
252 private static boolean isEmptyLine(String line) {
253 return line.trim().length() == 0;
254 }
255
256 private static String trimRight(String s) {
257 return trailingWhitespace.matcher(s).replaceAll("");
258 }
259
260
261
262
263
264
265
266
267
268
269
270 public static int indexOfFirstFooterLine(String[] lines) {
271 int footerFirstLine = lines.length;
272 for (int i = lines.length - 1; i > 1; --i) {
273 if (footerPattern.matcher(lines[i]).matches()) {
274 footerFirstLine = i;
275 continue;
276 }
277 if (footerFirstLine != lines.length && lines[i].length() == 0)
278 break;
279 if (footerFirstLine != lines.length
280 && includeInFooterPattern.matcher(lines[i]).matches()) {
281 footerFirstLine = i + 1;
282 continue;
283 }
284 footerFirstLine = lines.length;
285 break;
286 }
287 return footerFirstLine;
288 }
289 }