*/
private PatchsetCommand preparePatchset(ReceiveCommand cmd) {
String branch = getIntegrationBranch(cmd.getRefName());
long number = getTicketId(cmd.getRefName());
TicketModel ticket = null;
if (number > 0 && ticketService.hasTicket(repository, number)) {
ticket = ticketService.getTicket(repository, number);
}
if (ticket == null) {
if (number > 0) {
// requested ticket does not exist
sendError("Sorry, {0} does not have ticket {1,number,0}!", repository.name, number);
sendRejection(cmd, "Invalid ticket number");
return null;
}
} else {
if (ticket.isMerged()) {
// ticket already merged & resolved
Change mergeChange = null;
for (Change change : ticket.changes) {
if (change.isMerge()) {
mergeChange = change;
break;
}
}
if (mergeChange != null) {
sendError("Sorry, {0} already merged {1} from ticket {2,number,0} to {3}!",
mergeChange.author, mergeChange.patchset, number, ticket.mergeTo);
}
sendRejection(cmd, "Ticket {0,number,0} already resolved", number);
return null;
} else if (!StringUtils.isEmpty(ticket.mergeTo)) {
// ticket specifies integration branch
branch = ticket.mergeTo;
}
}
final int shortCommitIdLen = settings.getInteger(Keys.web.shortCommitIdLength, 6);
final String shortTipId = cmd.getNewId().getName().substring(0, shortCommitIdLen);
final RevCommit tipCommit = JGitUtils.getCommit(getRepository(), cmd.getNewId().getName());
final String forBranch = branch;
RevCommit mergeBase = null;
Ref forBranchRef = getAdvertisedRefs().get(Constants.R_HEADS + forBranch);
if (forBranchRef == null || forBranchRef.getObjectId() == null) {
// unknown integration branch
sendError("Sorry, there is no integration branch named ''{0}''.", forBranch);
sendRejection(cmd, "Invalid integration branch specified");
return null;
} else {
// determine the merge base for the patchset on the integration branch
String base = JGitUtils.getMergeBase(getRepository(), forBranchRef.getObjectId(), tipCommit.getId());
if (StringUtils.isEmpty(base)) {
sendError("");
sendError("There is no common ancestry between {0} and {1}.", forBranch, shortTipId);
sendError("Please reconsider your proposed integration branch, {0}.", forBranch);
sendError("");
sendRejection(cmd, "no merge base for patchset and {0}", forBranch);
return null;
}
mergeBase = JGitUtils.getCommit(getRepository(), base);
}
// ensure that the patchset can be cleanly merged right now
MergeStatus status = JGitUtils.canMerge(getRepository(), tipCommit.getName(), forBranch);
switch (status) {
case ALREADY_MERGED:
sendError("");
sendError("You have already merged this patchset.", forBranch);
sendError("");
sendRejection(cmd, "everything up-to-date");
return null;
case MERGEABLE:
break;
default:
if (ticket == null || requireMergeablePatchset) {
sendError("");
sendError("Your patchset can not be cleanly merged into {0}.", forBranch);
sendError("Please rebase your patchset and push again.");
sendError("NOTE:", number);
sendError("You should push your rebase to refs/for/{0,number,0}", number);
sendError("");
sendError(" git push origin HEAD:refs/for/{0,number,0}", number);
sendError("");
sendRejection(cmd, "patchset not mergeable");
return null;
}
}
// check to see if this commit is already linked to a ticket
long id = identifyTicket(tipCommit, false);
if (id > 0) {
sendError("{0} has already been pushed to ticket {1,number,0}.", shortTipId, id);
sendRejection(cmd, "everything up-to-date");
return null;
}
PatchsetCommand psCmd;
if (ticket == null) {
/*
* NEW TICKET
*/
Patchset patchset = newPatchset(null, mergeBase.getName(), tipCommit.getName());
int minLength = 10;
int maxLength = 100;
String minTitle = MessageFormat.format(" minimum length of a title is {0} characters.", minLength);
String maxTitle = MessageFormat.format(" maximum length of a title is {0} characters.", maxLength);
if (patchset.commits > 1) {
sendError("");
sendError("You may not create a ''{0}'' branch proposal ticket from {1} commits!",
forBranch, patchset.commits);
sendError("");
// display an ellipsized log of the commits being pushed
RevWalk walk = getRevWalk();
walk.reset();
walk.sort(RevSort.TOPO);
int boundary = 3;
int count = 0;
try {
walk.markStart(tipCommit);
walk.markUninteresting(mergeBase);
for (;;) {
RevCommit c = walk.next();
if (c == null) {
break;
}
if (count < boundary || count >= (patchset.commits - boundary)) {
walk.parseBody(c);
sendError(" {0} {1}", c.getName().substring(0, shortCommitIdLen),
StringUtils.trimString(c.getShortMessage(), 60));
} else if (count == boundary) {
sendError(" ... more commits ...");
}
count++;
}
} catch (IOException e) {
// Should never happen, the core receive process would have
// identified the missing object earlier before we got control.
LOGGER.error("failed to get commit count", e);
} finally {
walk.release();
}
sendError("");
sendError("Possible Solutions:");
sendError("");
int solution = 1;
String forSpec = cmd.getRefName().substring(Constants.R_FOR.length());
if (forSpec.equals("default") || forSpec.equals("new")) {
try {
// determine other possible integration targets
List<String> bases = Lists.newArrayList();
for (Ref ref : getRepository().getRefDatabase().getRefs(Constants.R_HEADS).values()) {
if (!ref.getName().startsWith(Constants.R_TICKET)
&& !ref.getName().equals(forBranchRef.getName())) {
if (JGitUtils.isMergedInto(getRepository(), ref.getObjectId(), tipCommit)) {
bases.add(Repository.shortenRefName(ref.getName()));
}
}
}
if (!bases.isEmpty()) {
if (bases.size() == 1) {
// suggest possible integration targets
String base = bases.get(0);
sendError("{0}. Propose this change for the ''{1}'' branch.", solution++, base);
sendError("");
sendError(" git push origin HEAD:refs/for/{0}", base);
sendError(" pt propose {0}", base);
sendError("");
} else {
// suggest possible integration targets
sendError("{0}. Propose this change for a different branch.", solution++);
sendError("");
for (String base : bases) {
sendError(" git push origin HEAD:refs/for/{0}", base);
sendError(" pt propose {0}", base);
sendError("");
}
}
}
} catch (IOException e) {
LOGGER.error(null, e);
}
}
sendError("{0}. Squash your changes into a single commit with a meaningful message.", solution++);
sendError("");
sendError("{0}. Open a ticket for your changes and then push your {1} commits to the ticket.",
solution++, patchset.commits);
sendError("");
sendError(" git push origin HEAD:refs/for/{id}");
sendError(" pt propose {id}");
sendError("");
sendRejection(cmd, "too many commits");
return null;
}
// require a reasonable title/subject
String title = tipCommit.getFullMessage().trim().split("\n")[0];
if (title.length() < minLength) {
// reject, title too short
sendError("");
sendError("Please supply a longer title in your commit message!");
sendError("");
sendError(minTitle);
sendError(maxTitle);
sendError("");
sendRejection(cmd, "ticket title is too short [{0}/{1}]", title.length(), maxLength);
return null;
}
if (title.length() > maxLength) {
// reject, title too long
sendError("");
sendError("Please supply a more concise title in your commit message!");
sendError("");
sendError(minTitle);
sendError(maxTitle);
sendError("");
sendRejection(cmd, "ticket title is too long [{0}/{1}]", title.length(), maxLength);
return null;
}
// assign new id
long ticketId = ticketService.assignNewId(repository);
// create the patchset command
psCmd = new PatchsetCommand(user.username, patchset);
psCmd.newTicket(tipCommit, forBranch, ticketId, cmd.getRefName());
} else {
/*
* EXISTING TICKET
*/
Patchset patchset = newPatchset(ticket, mergeBase.getName(), tipCommit.getName());
psCmd = new PatchsetCommand(user.username, patchset);
psCmd.updateTicket(tipCommit, forBranch, ticket, cmd.getRefName());
}
// confirm user can push the patchset
boolean pushPermitted = ticket == null
|| !ticket.hasPatchsets()
|| ticket.isAuthor(user.username)
|| ticket.isPatchsetAuthor(user.username)
|| ticket.isResponsible(user.username)
|| user.canPush(repository);
switch (psCmd.getPatchsetType()) {
case Proposal:
// proposals (first patchset) are always acceptable