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