James Moger
2014-03-18 41a7e46579d12e36c583aa8c2418e49c3f4c29a4
commit | author | age
5e3521 1 /*
JM 2  * Copyright 2013 gitblit.com.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package com.gitblit.git;
17
18 import java.util.ArrayList;
19 import java.util.List;
20 import java.util.Set;
21 import java.util.TreeSet;
22
23 import org.eclipse.jgit.lib.ObjectId;
24 import org.eclipse.jgit.revwalk.RevCommit;
25 import org.eclipse.jgit.transport.ReceiveCommand;
26
27 import com.gitblit.Constants;
28 import com.gitblit.models.TicketModel;
29 import com.gitblit.models.TicketModel.Change;
30 import com.gitblit.models.TicketModel.Field;
31 import com.gitblit.models.TicketModel.Patchset;
32 import com.gitblit.models.TicketModel.PatchsetType;
33 import com.gitblit.models.TicketModel.Status;
34 import com.gitblit.utils.ArrayUtils;
35 import com.gitblit.utils.StringUtils;
36
37 /**
38  *
39  * A subclass of ReceiveCommand which constructs a ticket change based on a
40  * patchset and data derived from the push ref.
41  *
42  * @author James Moger
43  *
44  */
45 public class PatchsetCommand extends ReceiveCommand {
46
47     public static final String TOPIC = "t=";
48
49     public static final String RESPONSIBLE = "r=";
50
51     public static final String WATCH = "cc=";
52
53     public static final String MILESTONE = "m=";
54
55     protected final Change change;
56
57     protected boolean isNew;
58
59     protected long ticketId;
60
61     public static String getBasePatchsetBranch(long ticketNumber) {
62         StringBuilder sb = new StringBuilder();
63         sb.append(Constants.R_TICKETS_PATCHSETS);
64         long m = ticketNumber % 100L;
65         if (m < 10) {
66             sb.append('0');
67         }
68         sb.append(m);
69         sb.append('/');
70         sb.append(ticketNumber);
71         sb.append('/');
72         return sb.toString();
73     }
74
75     public static String getTicketBranch(long ticketNumber) {
76         return Constants.R_TICKET + ticketNumber;
77     }
78
79     public static String getReviewBranch(long ticketNumber) {
80         return "ticket-" + ticketNumber;
81     }
82
83     public static String getPatchsetBranch(long ticketId, long patchset) {
84         return getBasePatchsetBranch(ticketId) + patchset;
85     }
86
87     public static long getTicketNumber(String ref) {
88         if (ref.startsWith(Constants.R_TICKETS_PATCHSETS)) {
89             // patchset revision
90
91             // strip changes ref
92             String p = ref.substring(Constants.R_TICKETS_PATCHSETS.length());
93             // strip shard id
94             p = p.substring(p.indexOf('/') + 1);
95             // strip revision
96             p = p.substring(0, p.indexOf('/'));
97             // parse ticket number
98             return Long.parseLong(p);
99         } else if (ref.startsWith(Constants.R_TICKET)) {
100             String p = ref.substring(Constants.R_TICKET.length());
101             // parse ticket number
102             return Long.parseLong(p);
103         }
104         return 0L;
105     }
106
107     public PatchsetCommand(String username, Patchset patchset) {
108         super(patchset.isFF() ? ObjectId.fromString(patchset.parent) : ObjectId.zeroId(),
109                 ObjectId.fromString(patchset.tip), null);
110         this.change = new Change(username);
111         this.change.patchset = patchset;
112     }
113
114     public PatchsetType getPatchsetType() {
115         return change.patchset.type;
116     }
117
118     public boolean isNewTicket() {
119         return isNew;
120     }
121
122     public long getTicketId() {
123         return ticketId;
124     }
125
126     public Change getChange() {
127         return change;
128     }
129
130     /**
131      * Creates a "new ticket" change for the proposal.
132      *
133      * @param commit
134      * @param mergeTo
135      * @param ticketId
136      * @parem pushRef
137      */
138     public void newTicket(RevCommit commit, String mergeTo, long ticketId, String pushRef) {
139         this.ticketId = ticketId;
140         isNew = true;
141         change.setField(Field.title, getTitle(commit));
142         change.setField(Field.body, getBody(commit));
143         change.setField(Field.status, Status.New);
144         change.setField(Field.mergeTo, mergeTo);
145         change.setField(Field.type, TicketModel.Type.Proposal);
146
147         Set<String> watchSet = new TreeSet<String>();
148         watchSet.add(change.author);
149
150         // identify parameters passed in the push ref
151         if (!StringUtils.isEmpty(pushRef)) {
152             List<String> watchers = getOptions(pushRef, WATCH);
153             if (!ArrayUtils.isEmpty(watchers)) {
154                 for (String cc : watchers) {
155                     watchSet.add(cc.toLowerCase());
156                 }
157             }
158
159             String milestone = getSingleOption(pushRef, MILESTONE);
160             if (!StringUtils.isEmpty(milestone)) {
161                 // user provided milestone
162                 change.setField(Field.milestone, milestone);
163             }
164
165             String responsible = getSingleOption(pushRef, RESPONSIBLE);
166             if (!StringUtils.isEmpty(responsible)) {
167                 // user provided responsible
168                 change.setField(Field.responsible, responsible);
169                 watchSet.add(responsible);
170             }
171
172             String topic = getSingleOption(pushRef, TOPIC);
173             if (!StringUtils.isEmpty(topic)) {
174                 // user provided topic
175                 change.setField(Field.topic, topic);
176             }
177         }
178
179         // set the watchers
180         change.watch(watchSet.toArray(new String[watchSet.size()]));
181     }
182
183     /**
184      *
185      * @param commit
186      * @param mergeTo
187      * @param ticket
188      * @param pushRef
189      */
190     public void updateTicket(RevCommit commit, String mergeTo, TicketModel ticket, String pushRef) {
191
192         this.ticketId = ticket.number;
193
194         if (ticket.isClosed()) {
195             // re-opening a closed ticket
196             change.setField(Field.status, Status.Open);
197         }
198
199         // ticket may or may not already have an integration branch
200         if (StringUtils.isEmpty(ticket.mergeTo) || !ticket.mergeTo.equals(mergeTo)) {
201             change.setField(Field.mergeTo, mergeTo);
202         }
203
204         if (ticket.isProposal() && change.patchset.commits == 1 && change.patchset.type.isRewrite()) {
205
206             // Gerrit-style title and description updates from the commit
207             // message
208             String title = getTitle(commit);
209             String body = getBody(commit);
210
211             if (!ticket.title.equals(title)) {
212                 // title changed
213                 change.setField(Field.title, title);
214             }
215
216             if (!ticket.body.equals(body)) {
217                 // description changed
218                 change.setField(Field.body, body);
219             }
220         }
221
222         Set<String> watchSet = new TreeSet<String>();
223         watchSet.add(change.author);
224
225         // update the patchset command metadata
226         if (!StringUtils.isEmpty(pushRef)) {
227             List<String> watchers = getOptions(pushRef, WATCH);
228             if (!ArrayUtils.isEmpty(watchers)) {
229                 for (String cc : watchers) {
230                     watchSet.add(cc.toLowerCase());
231                 }
232             }
233
234             String milestone = getSingleOption(pushRef, MILESTONE);
235             if (!StringUtils.isEmpty(milestone) && !milestone.equals(ticket.milestone)) {
236                 // user specified a (different) milestone
237                 change.setField(Field.milestone, milestone);
238             }
239
240             String responsible = getSingleOption(pushRef, RESPONSIBLE);
241             if (!StringUtils.isEmpty(responsible) && !responsible.equals(ticket.responsible)) {
242                 // user specified a (different) responsible
243                 change.setField(Field.responsible, responsible);
244                 watchSet.add(responsible);
245             }
246
247             String topic = getSingleOption(pushRef, TOPIC);
248             if (!StringUtils.isEmpty(topic) && !topic.equals(ticket.topic)) {
249                 // user specified a (different) topic
250                 change.setField(Field.topic, topic);
251             }
252         }
253
254         // update the watchers
255         watchSet.removeAll(ticket.getWatchers());
256         if (!watchSet.isEmpty()) {
257             change.watch(watchSet.toArray(new String[watchSet.size()]));
258         }
259     }
260
261     @Override
262     public String getRefName() {
263         return getPatchsetBranch();
264     }
265
266     public String getPatchsetBranch() {
267         return getBasePatchsetBranch(ticketId) + change.patchset.number;
268     }
269
270     public String getTicketBranch() {
271         return getTicketBranch(ticketId);
272     }
273
274     private String getTitle(RevCommit commit) {
275         String title = commit.getShortMessage();
276         return title;
277     }
278
279     /**
280      * Returns the body of the commit message
281      *
282      * @return
283      */
284     private String getBody(RevCommit commit) {
285         String body = commit.getFullMessage().substring(commit.getShortMessage().length()).trim();
286         return body;
287     }
288
289     /** Extracts a ticket field from the ref name */
290     private static List<String> getOptions(String refName, String token) {
291         if (refName.indexOf('%') > -1) {
292             List<String> list = new ArrayList<String>();
293             String [] strings = refName.substring(refName.indexOf('%') + 1).split(",");
294             for (String str : strings) {
295                 if (str.toLowerCase().startsWith(token)) {
296                     String val = str.substring(token.length());
297                     list.add(val);
298                 }
299             }
300             return list;
301         }
302         return null;
303     }
304
305     /** Extracts a ticket field from the ref name */
306     private static String getSingleOption(String refName, String token) {
307         List<String> list = getOptions(refName, token);
308         if (list != null && list.size() > 0) {
309             return list.get(0);
310         }
311         return null;
312     }
313
314     /** Extracts a ticket field from the ref name */
315     public static String getSingleOption(ReceiveCommand cmd, String token) {
316         return getSingleOption(cmd.getRefName(), token);
317     }
318
319     /** Extracts a ticket field from the ref name */
320     public static List<String> getOptions(ReceiveCommand cmd, String token) {
321         return getOptions(cmd.getRefName(), token);
322     }
323
324 }