/* * 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); } }