| /* |
| * Copyright 2013 gitblit.com. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| package com.gitblit.git; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| import java.util.Set; |
| import java.util.TreeSet; |
| |
| import org.eclipse.jgit.lib.ObjectId; |
| import org.eclipse.jgit.revwalk.RevCommit; |
| import org.eclipse.jgit.transport.ReceiveCommand; |
| |
| import com.gitblit.Constants; |
| import com.gitblit.models.TicketModel; |
| import com.gitblit.models.TicketModel.Change; |
| import com.gitblit.models.TicketModel.Field; |
| import com.gitblit.models.TicketModel.Patchset; |
| import com.gitblit.models.TicketModel.PatchsetType; |
| import com.gitblit.models.TicketModel.Status; |
| import com.gitblit.utils.ArrayUtils; |
| import com.gitblit.utils.StringUtils; |
| |
| /** |
| * |
| * A subclass of ReceiveCommand which constructs a ticket change based on a |
| * patchset and data derived from the push ref. |
| * |
| * @author James Moger |
| * |
| */ |
| public class PatchsetCommand extends ReceiveCommand { |
| |
| public static final String TOPIC = "t="; |
| |
| public static final String RESPONSIBLE = "r="; |
| |
| public static final String WATCH = "cc="; |
| |
| public static final String MILESTONE = "m="; |
| |
| protected final Change change; |
| |
| protected boolean isNew; |
| |
| protected long ticketId; |
| |
| public static String getBasePatchsetBranch(long ticketNumber) { |
| StringBuilder sb = new StringBuilder(); |
| sb.append(Constants.R_TICKETS_PATCHSETS); |
| long m = ticketNumber % 100L; |
| if (m < 10) { |
| sb.append('0'); |
| } |
| sb.append(m); |
| sb.append('/'); |
| sb.append(ticketNumber); |
| sb.append('/'); |
| return sb.toString(); |
| } |
| |
| public static String getTicketBranch(long ticketNumber) { |
| return Constants.R_TICKET + ticketNumber; |
| } |
| |
| public static String getReviewBranch(long ticketNumber) { |
| return "ticket-" + ticketNumber; |
| } |
| |
| public static String getPatchsetBranch(long ticketId, long patchset) { |
| return getBasePatchsetBranch(ticketId) + patchset; |
| } |
| |
| public static long getTicketNumber(String ref) { |
| if (ref.startsWith(Constants.R_TICKETS_PATCHSETS)) { |
| // patchset revision |
| |
| // strip changes ref |
| String p = ref.substring(Constants.R_TICKETS_PATCHSETS.length()); |
| // strip shard id |
| p = p.substring(p.indexOf('/') + 1); |
| // strip revision |
| p = p.substring(0, p.indexOf('/')); |
| // parse ticket number |
| return Long.parseLong(p); |
| } else if (ref.startsWith(Constants.R_TICKET)) { |
| String p = ref.substring(Constants.R_TICKET.length()); |
| // parse ticket number |
| return Long.parseLong(p); |
| } |
| return 0L; |
| } |
| |
| public PatchsetCommand(String username, Patchset patchset) { |
| super(patchset.isFF() ? ObjectId.fromString(patchset.parent) : ObjectId.zeroId(), |
| ObjectId.fromString(patchset.tip), null); |
| this.change = new Change(username); |
| this.change.patchset = patchset; |
| } |
| |
| public PatchsetType getPatchsetType() { |
| return change.patchset.type; |
| } |
| |
| public boolean isNewTicket() { |
| return isNew; |
| } |
| |
| public long getTicketId() { |
| return ticketId; |
| } |
| |
| public Change getChange() { |
| return change; |
| } |
| |
| /** |
| * Creates a "new ticket" change for the proposal. |
| * |
| * @param commit |
| * @param mergeTo |
| * @param ticketId |
| * @parem pushRef |
| */ |
| public void newTicket(RevCommit commit, String mergeTo, long ticketId, String pushRef) { |
| this.ticketId = ticketId; |
| isNew = true; |
| change.setField(Field.title, getTitle(commit)); |
| change.setField(Field.body, getBody(commit)); |
| change.setField(Field.status, Status.New); |
| change.setField(Field.mergeTo, mergeTo); |
| change.setField(Field.type, TicketModel.Type.Proposal); |
| |
| Set<String> watchSet = new TreeSet<String>(); |
| watchSet.add(change.author); |
| |
| // identify parameters passed in the push ref |
| if (!StringUtils.isEmpty(pushRef)) { |
| List<String> watchers = getOptions(pushRef, WATCH); |
| if (!ArrayUtils.isEmpty(watchers)) { |
| for (String cc : watchers) { |
| watchSet.add(cc.toLowerCase()); |
| } |
| } |
| |
| String milestone = getSingleOption(pushRef, MILESTONE); |
| if (!StringUtils.isEmpty(milestone)) { |
| // user provided milestone |
| change.setField(Field.milestone, milestone); |
| } |
| |
| String responsible = getSingleOption(pushRef, RESPONSIBLE); |
| if (!StringUtils.isEmpty(responsible)) { |
| // user provided responsible |
| change.setField(Field.responsible, responsible); |
| watchSet.add(responsible); |
| } |
| |
| String topic = getSingleOption(pushRef, TOPIC); |
| if (!StringUtils.isEmpty(topic)) { |
| // user provided topic |
| change.setField(Field.topic, topic); |
| } |
| } |
| |
| // set the watchers |
| change.watch(watchSet.toArray(new String[watchSet.size()])); |
| } |
| |
| /** |
| * |
| * @param commit |
| * @param mergeTo |
| * @param ticket |
| * @param pushRef |
| */ |
| public void updateTicket(RevCommit commit, String mergeTo, TicketModel ticket, String pushRef) { |
| |
| this.ticketId = ticket.number; |
| |
| if (ticket.isClosed()) { |
| // re-opening a closed ticket |
| change.setField(Field.status, Status.Open); |
| } |
| |
| // ticket may or may not already have an integration branch |
| if (StringUtils.isEmpty(ticket.mergeTo) || !ticket.mergeTo.equals(mergeTo)) { |
| change.setField(Field.mergeTo, mergeTo); |
| } |
| |
| if (ticket.isProposal() && change.patchset.commits == 1 && change.patchset.type.isRewrite()) { |
| |
| // Gerrit-style title and description updates from the commit |
| // message |
| String title = getTitle(commit); |
| String body = getBody(commit); |
| |
| if (!ticket.title.equals(title)) { |
| // title changed |
| change.setField(Field.title, title); |
| } |
| |
| if (!ticket.body.equals(body)) { |
| // description changed |
| change.setField(Field.body, body); |
| } |
| } |
| |
| Set<String> watchSet = new TreeSet<String>(); |
| watchSet.add(change.author); |
| |
| // update the patchset command metadata |
| if (!StringUtils.isEmpty(pushRef)) { |
| List<String> watchers = getOptions(pushRef, WATCH); |
| if (!ArrayUtils.isEmpty(watchers)) { |
| for (String cc : watchers) { |
| watchSet.add(cc.toLowerCase()); |
| } |
| } |
| |
| String milestone = getSingleOption(pushRef, MILESTONE); |
| if (!StringUtils.isEmpty(milestone) && !milestone.equals(ticket.milestone)) { |
| // user specified a (different) milestone |
| change.setField(Field.milestone, milestone); |
| } |
| |
| String responsible = getSingleOption(pushRef, RESPONSIBLE); |
| if (!StringUtils.isEmpty(responsible) && !responsible.equals(ticket.responsible)) { |
| // user specified a (different) responsible |
| change.setField(Field.responsible, responsible); |
| watchSet.add(responsible); |
| } |
| |
| String topic = getSingleOption(pushRef, TOPIC); |
| if (!StringUtils.isEmpty(topic) && !topic.equals(ticket.topic)) { |
| // user specified a (different) topic |
| change.setField(Field.topic, topic); |
| } |
| } |
| |
| // update the watchers |
| watchSet.removeAll(ticket.getWatchers()); |
| if (!watchSet.isEmpty()) { |
| change.watch(watchSet.toArray(new String[watchSet.size()])); |
| } |
| } |
| |
| @Override |
| public String getRefName() { |
| return getPatchsetBranch(); |
| } |
| |
| public String getPatchsetBranch() { |
| return getBasePatchsetBranch(ticketId) + change.patchset.number; |
| } |
| |
| public String getTicketBranch() { |
| return getTicketBranch(ticketId); |
| } |
| |
| private String getTitle(RevCommit commit) { |
| String title = commit.getShortMessage(); |
| return title; |
| } |
| |
| /** |
| * Returns the body of the commit message |
| * |
| * @return |
| */ |
| private String getBody(RevCommit commit) { |
| String body = commit.getFullMessage().substring(commit.getShortMessage().length()).trim(); |
| return body; |
| } |
| |
| /** Extracts a ticket field from the ref name */ |
| private static List<String> getOptions(String refName, String token) { |
| if (refName.indexOf('%') > -1) { |
| List<String> list = new ArrayList<String>(); |
| String [] strings = refName.substring(refName.indexOf('%') + 1).split(","); |
| for (String str : strings) { |
| if (str.toLowerCase().startsWith(token)) { |
| String val = str.substring(token.length()); |
| list.add(val); |
| } |
| } |
| return list; |
| } |
| return null; |
| } |
| |
| /** Extracts a ticket field from the ref name */ |
| private static String getSingleOption(String refName, String token) { |
| List<String> list = getOptions(refName, token); |
| if (list != null && list.size() > 0) { |
| return list.get(0); |
| } |
| return null; |
| } |
| |
| /** Extracts a ticket field from the ref name */ |
| public static String getSingleOption(ReceiveCommand cmd, String token) { |
| return getSingleOption(cmd.getRefName(), token); |
| } |
| |
| /** Extracts a ticket field from the ref name */ |
| public static List<String> getOptions(ReceiveCommand cmd, String token) { |
| return getOptions(cmd.getRefName(), token); |
| } |
| |
| } |