You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

PatchsetCommand.java 8.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324
  1. /*
  2. * Copyright 2013 gitblit.com.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package com.gitblit.git;
  17. import java.util.ArrayList;
  18. import java.util.List;
  19. import java.util.Set;
  20. import java.util.TreeSet;
  21. import org.eclipse.jgit.lib.ObjectId;
  22. import org.eclipse.jgit.revwalk.RevCommit;
  23. import org.eclipse.jgit.transport.ReceiveCommand;
  24. import com.gitblit.Constants;
  25. import com.gitblit.models.TicketModel;
  26. import com.gitblit.models.TicketModel.Change;
  27. import com.gitblit.models.TicketModel.Field;
  28. import com.gitblit.models.TicketModel.Patchset;
  29. import com.gitblit.models.TicketModel.PatchsetType;
  30. import com.gitblit.models.TicketModel.Status;
  31. import com.gitblit.utils.ArrayUtils;
  32. import com.gitblit.utils.StringUtils;
  33. /**
  34. *
  35. * A subclass of ReceiveCommand which constructs a ticket change based on a
  36. * patchset and data derived from the push ref.
  37. *
  38. * @author James Moger
  39. *
  40. */
  41. public class PatchsetCommand extends ReceiveCommand {
  42. public static final String TOPIC = "t=";
  43. public static final String RESPONSIBLE = "r=";
  44. public static final String WATCH = "cc=";
  45. public static final String MILESTONE = "m=";
  46. protected final Change change;
  47. protected boolean isNew;
  48. protected long ticketId;
  49. public static String getBasePatchsetBranch(long ticketNumber) {
  50. StringBuilder sb = new StringBuilder();
  51. sb.append(Constants.R_TICKETS_PATCHSETS);
  52. long m = ticketNumber % 100L;
  53. if (m < 10) {
  54. sb.append('0');
  55. }
  56. sb.append(m);
  57. sb.append('/');
  58. sb.append(ticketNumber);
  59. sb.append('/');
  60. return sb.toString();
  61. }
  62. public static String getTicketBranch(long ticketNumber) {
  63. return Constants.R_TICKET + ticketNumber;
  64. }
  65. public static String getReviewBranch(long ticketNumber) {
  66. return "ticket-" + ticketNumber;
  67. }
  68. public static String getPatchsetBranch(long ticketId, long patchset) {
  69. return getBasePatchsetBranch(ticketId) + patchset;
  70. }
  71. public static long getTicketNumber(String ref) {
  72. if (ref.startsWith(Constants.R_TICKETS_PATCHSETS)) {
  73. // patchset revision
  74. // strip changes ref
  75. String p = ref.substring(Constants.R_TICKETS_PATCHSETS.length());
  76. // strip shard id
  77. p = p.substring(p.indexOf('/') + 1);
  78. // strip revision
  79. p = p.substring(0, p.indexOf('/'));
  80. // parse ticket number
  81. return Long.parseLong(p);
  82. } else if (ref.startsWith(Constants.R_TICKET)) {
  83. String p = ref.substring(Constants.R_TICKET.length());
  84. // parse ticket number
  85. return Long.parseLong(p);
  86. }
  87. return 0L;
  88. }
  89. public PatchsetCommand(String username, Patchset patchset) {
  90. super(patchset.isFF() ? ObjectId.fromString(patchset.parent) : ObjectId.zeroId(),
  91. ObjectId.fromString(patchset.tip), null);
  92. this.change = new Change(username);
  93. this.change.patchset = patchset;
  94. }
  95. public PatchsetType getPatchsetType() {
  96. return change.patchset.type;
  97. }
  98. public boolean isNewTicket() {
  99. return isNew;
  100. }
  101. public long getTicketId() {
  102. return ticketId;
  103. }
  104. public Change getChange() {
  105. return change;
  106. }
  107. /**
  108. * Creates a "new ticket" change for the proposal.
  109. *
  110. * @param commit
  111. * @param mergeTo
  112. * @param ticketId
  113. * @parem pushRef
  114. */
  115. public void newTicket(RevCommit commit, String mergeTo, long ticketId, String pushRef) {
  116. this.ticketId = ticketId;
  117. isNew = true;
  118. change.setField(Field.title, getTitle(commit));
  119. change.setField(Field.body, getBody(commit));
  120. change.setField(Field.status, Status.New);
  121. change.setField(Field.mergeTo, mergeTo);
  122. change.setField(Field.type, TicketModel.Type.Proposal);
  123. Set<String> watchSet = new TreeSet<String>();
  124. watchSet.add(change.author);
  125. // identify parameters passed in the push ref
  126. if (!StringUtils.isEmpty(pushRef)) {
  127. List<String> watchers = getOptions(pushRef, WATCH);
  128. if (!ArrayUtils.isEmpty(watchers)) {
  129. for (String cc : watchers) {
  130. watchSet.add(cc.toLowerCase());
  131. }
  132. }
  133. String milestone = getSingleOption(pushRef, MILESTONE);
  134. if (!StringUtils.isEmpty(milestone)) {
  135. // user provided milestone
  136. change.setField(Field.milestone, milestone);
  137. }
  138. String responsible = getSingleOption(pushRef, RESPONSIBLE);
  139. if (!StringUtils.isEmpty(responsible)) {
  140. // user provided responsible
  141. change.setField(Field.responsible, responsible);
  142. watchSet.add(responsible);
  143. }
  144. String topic = getSingleOption(pushRef, TOPIC);
  145. if (!StringUtils.isEmpty(topic)) {
  146. // user provided topic
  147. change.setField(Field.topic, topic);
  148. }
  149. }
  150. // set the watchers
  151. change.watch(watchSet.toArray(new String[watchSet.size()]));
  152. }
  153. /**
  154. *
  155. * @param commit
  156. * @param mergeTo
  157. * @param ticket
  158. * @param pushRef
  159. */
  160. public void updateTicket(RevCommit commit, String mergeTo, TicketModel ticket, String pushRef) {
  161. this.ticketId = ticket.number;
  162. if (ticket.isClosed()) {
  163. // re-opening a closed ticket
  164. change.setField(Field.status, Status.Open);
  165. }
  166. // ticket may or may not already have an integration branch
  167. if (StringUtils.isEmpty(ticket.mergeTo) || !ticket.mergeTo.equals(mergeTo)) {
  168. change.setField(Field.mergeTo, mergeTo);
  169. }
  170. if (ticket.isProposal() && change.patchset.commits == 1 && change.patchset.type.isRewrite()) {
  171. // Gerrit-style title and description updates from the commit
  172. // message
  173. String title = getTitle(commit);
  174. String body = getBody(commit);
  175. if (!ticket.title.equals(title)) {
  176. // title changed
  177. change.setField(Field.title, title);
  178. }
  179. if (!ticket.body.equals(body)) {
  180. // description changed
  181. change.setField(Field.body, body);
  182. }
  183. }
  184. Set<String> watchSet = new TreeSet<String>();
  185. watchSet.add(change.author);
  186. // update the patchset command metadata
  187. if (!StringUtils.isEmpty(pushRef)) {
  188. List<String> watchers = getOptions(pushRef, WATCH);
  189. if (!ArrayUtils.isEmpty(watchers)) {
  190. for (String cc : watchers) {
  191. watchSet.add(cc.toLowerCase());
  192. }
  193. }
  194. String milestone = getSingleOption(pushRef, MILESTONE);
  195. if (!StringUtils.isEmpty(milestone) && !milestone.equals(ticket.milestone)) {
  196. // user specified a (different) milestone
  197. change.setField(Field.milestone, milestone);
  198. }
  199. String responsible = getSingleOption(pushRef, RESPONSIBLE);
  200. if (!StringUtils.isEmpty(responsible) && !responsible.equals(ticket.responsible)) {
  201. // user specified a (different) responsible
  202. change.setField(Field.responsible, responsible);
  203. watchSet.add(responsible);
  204. }
  205. String topic = getSingleOption(pushRef, TOPIC);
  206. if (!StringUtils.isEmpty(topic) && !topic.equals(ticket.topic)) {
  207. // user specified a (different) topic
  208. change.setField(Field.topic, topic);
  209. }
  210. }
  211. // update the watchers
  212. watchSet.removeAll(ticket.getWatchers());
  213. if (!watchSet.isEmpty()) {
  214. change.watch(watchSet.toArray(new String[watchSet.size()]));
  215. }
  216. }
  217. @Override
  218. public String getRefName() {
  219. return getPatchsetBranch();
  220. }
  221. public String getPatchsetBranch() {
  222. return getBasePatchsetBranch(ticketId) + change.patchset.number;
  223. }
  224. public String getTicketBranch() {
  225. return getTicketBranch(ticketId);
  226. }
  227. private String getTitle(RevCommit commit) {
  228. String title = commit.getShortMessage();
  229. return title;
  230. }
  231. /**
  232. * Returns the body of the commit message
  233. *
  234. * @return
  235. */
  236. private String getBody(RevCommit commit) {
  237. String body = commit.getFullMessage().substring(commit.getShortMessage().length()).trim();
  238. return body;
  239. }
  240. /** Extracts a ticket field from the ref name */
  241. private static List<String> getOptions(String refName, String token) {
  242. if (refName.indexOf('%') > -1) {
  243. List<String> list = new ArrayList<String>();
  244. String [] strings = refName.substring(refName.indexOf('%') + 1).split(",");
  245. for (String str : strings) {
  246. if (str.toLowerCase().startsWith(token)) {
  247. String val = str.substring(token.length());
  248. list.add(val);
  249. }
  250. }
  251. return list;
  252. }
  253. return null;
  254. }
  255. /** Extracts a ticket field from the ref name */
  256. private static String getSingleOption(String refName, String token) {
  257. List<String> list = getOptions(refName, token);
  258. if (list != null && list.size() > 0) {
  259. return list.get(0);
  260. }
  261. return null;
  262. }
  263. /** Extracts a ticket field from the ref name */
  264. public static String getSingleOption(ReceiveCommand cmd, String token) {
  265. return getSingleOption(cmd.getRefName(), token);
  266. }
  267. /** Extracts a ticket field from the ref name */
  268. public static List<String> getOptions(ReceiveCommand cmd, String token) {
  269. return getOptions(cmd.getRefName(), token);
  270. }
  271. }