summaryrefslogtreecommitdiffstats
path: root/src/main/java/com/gitblit/git/PatchsetCommand.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/main/java/com/gitblit/git/PatchsetCommand.java')
-rw-r--r--src/main/java/com/gitblit/git/PatchsetCommand.java324
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