diff options
Diffstat (limited to 'src/main/java/com/gitblit/git/PatchsetCommand.java')
-rw-r--r-- | src/main/java/com/gitblit/git/PatchsetCommand.java | 324 |
1 files changed, 324 insertions, 0 deletions
diff --git a/src/main/java/com/gitblit/git/PatchsetCommand.java b/src/main/java/com/gitblit/git/PatchsetCommand.java new file mode 100644 index 00000000..21d2ac45 --- /dev/null +++ b/src/main/java/com/gitblit/git/PatchsetCommand.java @@ -0,0 +1,324 @@ +/* + * 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); + } + +}
\ No newline at end of file |