2 * Copyright (C) 2010, Robin Rosenberg
3 * and other copyright owners as documented in the project's IP log.
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
43 package org
.eclipse
.jgit
.util
;
45 import java
.io
.IOException
;
46 import java
.util
.regex
.Pattern
;
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
;
54 * Utilities for creating and working with Change-Id's, like the one used by
57 * A Change-Id is a SHA-1 computed from the content of a commit, in a similar
58 * fashion to how the commit id is computed. Unlike the commit id a Change-Id is
59 * retained in the commit and subsequent revised commits in the footer of the
62 public class ChangeIdUtil
{
64 static final String CHANGE_ID
= "Change-Id:";
66 // package-private so the unit test can test this part only
67 static String
clean(String 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.*", "").//
78 * Compute a Change-Id.
81 * The id of the tree that would be committed
82 * @param firstParentId
83 * parent id of previous commit or null
85 * the {@link PersonIdent} for the presumed author and time
87 * the {@link PersonIdent} for the presumed committer and time
90 * @return the change id SHA1 string (without the 'I') or null if the
91 * message is not complete enough
94 public static ObjectId
computeChangeId(final ObjectId treeId
,
95 final ObjectId firstParentId
, final PersonIdent author
,
96 final PersonIdent committer
, final String message
)
98 String cleanMessage
= clean(message
);
99 if (cleanMessage
.length() == 0)
101 StringBuilder b
= new StringBuilder();
103 b
.append(ObjectId
.toString(treeId
));
105 if (firstParentId
!= null) {
107 b
.append(ObjectId
.toString(firstParentId
));
111 b
.append(author
.toExternalString());
113 b
.append("committer ");
114 b
.append(committer
.toExternalString());
116 b
.append(cleanMessage
);
117 return new ObjectInserter
.Formatter().idFor(Constants
.OBJ_COMMIT
, //
118 b
.toString().getBytes(Constants
.CHARACTER_ENCODING
));
121 private static final Pattern issuePattern
= Pattern
122 .compile("^(Bug|Issue)[a-zA-Z0-9-]*:.*$");
124 private static final Pattern footerPattern
= Pattern
125 .compile("(^[a-zA-Z0-9-]+:(?!//).*$)");
127 private static final Pattern includeInFooterPattern
= Pattern
128 .compile("^[ \\[].*$");
131 * Find the right place to insert a Change-Id and return it.
133 * The Change-Id is inserted before the first footer line but after a Bug
138 * @return a commit message with an inserted Change-Id line
140 public static String
insertId(String message
, ObjectId changeId
) {
141 return insertId(message
, changeId
, false);
145 * Find the right place to insert a Change-Id and return it.
147 * If no Change-Id is found the Change-Id is inserted before
148 * the first footer line but after a Bug line.
150 * If Change-Id is found and replaceExisting is set to false,
151 * the message is unchanged.
153 * If Change-Id is found and replaceExisting is set to true,
154 * the Change-Id is replaced with {@code changeId}.
158 * @param replaceExisting
159 * @return a commit message with an inserted Change-Id line
161 public static String
insertId(String message
, ObjectId changeId
,
162 boolean replaceExisting
) {
163 if (message
.indexOf(CHANGE_ID
) > 0) {
164 if (replaceExisting
) {
165 int i
= message
.indexOf(CHANGE_ID
) + 10;
166 while (message
.charAt(i
) == ' ')
168 String oldId
= message
.length() == (i
+ 40) ?
169 message
.substring(i
) : message
.substring(i
, i
+ 41);
170 message
= message
.replace(oldId
, "I" + changeId
.getName());
175 String
[] lines
= message
.split("\n");
176 int footerFirstLine
= lines
.length
;
177 for (int i
= lines
.length
- 1; i
> 1; --i
) {
178 if (footerPattern
.matcher(lines
[i
]).matches()) {
182 if (footerFirstLine
!= lines
.length
&& lines
[i
].length() == 0) {
185 if (footerFirstLine
!= lines
.length
186 && includeInFooterPattern
.matcher(lines
[i
]).matches()) {
187 footerFirstLine
= i
+ 1;
190 footerFirstLine
= lines
.length
;
193 int insertAfter
= footerFirstLine
;
194 for (int i
= footerFirstLine
; i
< lines
.length
; ++i
) {
195 if (issuePattern
.matcher(lines
[i
]).matches()) {
201 StringBuilder ret
= new StringBuilder();
203 for (; i
< insertAfter
; ++i
) {
204 ret
.append(lines
[i
]);
207 if (insertAfter
== lines
.length
&& insertAfter
== footerFirstLine
)
209 ret
.append(CHANGE_ID
);
211 ret
.append(ObjectId
.toString(changeId
));
213 for (; i
< lines
.length
; ++i
) {
214 ret
.append(lines
[i
]);
217 return ret
.toString();