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