final ReplaceResult result = new ReplaceResult();
final Set<Account.Id> oldReviewers = new HashSet<Account.Id>();
final Set<Account.Id> oldCC = new HashSet<Account.Id>();
Change change = db.changes().get(request.ontoChange);
if (change == null) {
reject(request.cmd, "change " + request.ontoChange + " not found");
return null;
}
if (change.getStatus().isClosed()) {
reject(request.cmd, "change " + request.ontoChange + " closed");
return null;
}
final ChangeControl changeCtl = projectControl.controlFor(change);
if (!changeCtl.canAddPatchSet()) {
reject(request.cmd, "cannot replace " + request.ontoChange);
return null;
}
if (!validCommit(changeCtl.getRefControl(), request.cmd, c)) {
return null;
}
final PatchSet.Id priorPatchSet = change.currentPatchSetId();
for (final PatchSet ps : db.patchSets().byChange(request.ontoChange)) {
if (ps.getRevision() == null) {
log.warn("Patch set " + ps.getId() + " has no revision");
reject(request.cmd, "change state corrupt");
return null;
}
final String revIdStr = ps.getRevision().get();
final ObjectId commitId;
try {
commitId = ObjectId.fromString(revIdStr);
} catch (IllegalArgumentException e) {
log.warn("Invalid revision in " + ps.getId() + ": " + revIdStr);
reject(request.cmd, "change state corrupt");
return null;
}
try {
final RevCommit prior = rp.getRevWalk().parseCommit(commitId);
// Don't allow a change to directly depend upon itself. This is a
// very common error due to users making a new commit rather than
// amending when trying to address review comments.
//
if (rp.getRevWalk().isMergedInto(prior, c)) {
reject(request.cmd, "squash commits first");
return null;
}
// Don't allow the same commit to appear twice on the same change
//
if (c == prior) {
reject(request.cmd, "commit already exists");
return null;
}
// Don't allow the same tree if the commit message is unmodified
// or no parents were updated (rebase), else warn that only part
// of the commit was modified.
//
if (priorPatchSet.equals(ps.getId()) && c.getTree() == prior.getTree()) {
rp.getRevWalk().parseBody(prior);
final boolean messageEq =
eq(c.getFullMessage(), prior.getFullMessage());
final boolean parentsEq = parentsEqual(c, prior);
final boolean authorEq = authorEqual(c, prior);
if (messageEq && parentsEq && authorEq && !ignoreNoChanges) {
reject(request.cmd, "no changes made");
return null;
} else {
ObjectReader reader = rp.getRevWalk().getObjectReader();
StringBuilder msg = new StringBuilder();
msg.append("(W) ");
msg.append(reader.abbreviate(c).name());
msg.append(":");
msg.append(" no files changed");
if (!authorEq) {
msg.append(", author changed");
}
if (!messageEq) {
msg.append(", message updated");
}
if (!parentsEq) {
msg.append(", was rebased");
}
addMessage(msg.toString());
}
}
} catch (IOException e) {
log.error("Change " + change.getId() + " missing " + revIdStr, e);
reject(request.cmd, "change state corrupt");
return null;
}
}
final PatchSet ps;
final ChangeMessage msg;
db.changes().beginTransaction(change.getId());
try {
change =
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
change.nextPatchSetId();
change.setLastSha1MergeTested(null);
return change;
} else {
return null;
}
}
});
if (change == null) {
reject(request.cmd, "change is closed");
return null;
}
ps = new PatchSet(change.currPatchSetId());
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
ps.setUploader(currentUser.getAccountId());
ps.setRevision(toRevId(c));
if (MagicBranch.isDraft(request.cmd.getRefName())) {
ps.setDraft(true);
}
insertAncestors(ps.getId(), c);
db.patchSets().insert(Collections.singleton(ps));
if (request.checkMergedInto) {
final Ref mergedInto = findMergedInto(change.getDest().get(), c);
result.mergedIntoRef = mergedInto != null ? mergedInto.getName() : null;
}
final PatchSetInfo info = patchSetInfoFactory.get(c, ps.getId());
change.setCurrentPatchSet(info);
result.change = change;
result.patchSet = ps;
result.info = info;
List<PatchSetApproval> patchSetApprovals = approvalsUtil.copyVetosToLatestPatchSet(change);
final Set<Account.Id> haveApprovals = new HashSet<Account.Id>();
oldReviewers.clear();
oldCC.clear();
for (PatchSetApproval a : patchSetApprovals) {
haveApprovals.add(a.getAccountId());
if (a.getValue() != 0) {
oldReviewers.add(a.getAccountId());
} else {
oldCC.add(a.getAccountId());
}
}
approvalsUtil.addReviewers(change, ps, info, reviewers, haveApprovals);
msg =
new ChangeMessage(new ChangeMessage.Key(change.getId(), ChangeUtil
.messageUUID(db)), me, ps.getCreatedOn(), ps.getId());
msg.setMessage("Uploaded patch set " + ps.getPatchSetId() + ".");
db.changeMessages().insert(Collections.singleton(msg));
ChangeUtil.updateTrackingIds(db, change, trackingFooters, footerLines);
result.msg = msg;
if (result.mergedIntoRef == null) {
// Change should be new, so it can go through review again.
//
change =
db.changes().atomicUpdate(change.getId(), new AtomicUpdate<Change>() {
@Override
public Change update(Change change) {
if (change.getStatus().isOpen()) {
if (destTopicName != null) {
change.setTopic(destTopicName);
}
if (change.getStatus() == Change.Status.DRAFT && ps.isDraft()) {
// Leave in draft status.
} else {
change.setStatus(Change.Status.NEW);
}
change.setCurrentPatchSet(result.info);
ChangeUtil.updated(change);
return change;
} else {
return null;
}