InlineEdit: Add method to publish edits

Change-Id: I7f73c716d71a0cf335d99d90bf96452ed3798ad6
This commit is contained in:
David Ostrovsky
2014-04-25 20:30:49 +02:00
committed by David Ostrovsky
parent 0034fabb30
commit a570dbfb06
2 changed files with 216 additions and 2 deletions

View File

@@ -520,7 +520,7 @@ public class ChangeUtil {
return (IdentifiedUser) userProvider.get();
}
private static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
public static PatchSet.Id nextPatchSetId(PatchSet.Id id) {
return new PatchSet.Id(id.getParentKey(), id.get() + 1);
}

View File

@@ -15,35 +15,63 @@
package com.google.gerrit.server.edit;
import com.google.common.base.Optional;
import com.google.common.collect.Iterables;
import com.google.gerrit.extensions.restapi.AuthException;
import com.google.gerrit.extensions.restapi.ResourceConflictException;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Change;
import com.google.gerrit.reviewdb.client.PatchSet;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.client.RevId;
import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.change.PatchSetInserter;
import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.project.ChangeControl;
import com.google.gerrit.server.project.InvalidChangeOperationException;
import com.google.gerrit.server.project.NoSuchChangeException;
import com.google.gerrit.server.util.TimeUtil;
import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject;
import com.google.inject.Provider;
import com.google.inject.Singleton;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectInserter;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import java.io.IOException;
import java.util.List;
/**
* Utility functions to manipulate change edits.
* <p>
* This class contains method to retrieve edits.
* This class contains methods to retrieve and publish edits.
*/
@Singleton
public class ChangeEditUtil {
private final GitRepositoryManager gitManager;
private final PatchSetInserter.Factory patchSetInserterFactory;
private final ChangeControl.GenericFactory changeControlFactory;
private final Provider<ReviewDb> db;
private final Provider<IdentifiedUser> user;
@Inject
ChangeEditUtil(GitRepositoryManager gitManager,
PatchSetInserter.Factory patchSetInserterFactory,
ChangeControl.GenericFactory changeControlFactory,
Provider<ReviewDb> db,
Provider<IdentifiedUser> user) {
this.gitManager = gitManager;
this.patchSetInserterFactory = patchSetInserterFactory;
this.changeControlFactory = changeControlFactory;
this.db = db;
this.user = user;
}
@@ -75,6 +103,121 @@ public class ChangeEditUtil {
}
}
/**
* Promote change edit to patch set, by squashing the edit into
* its parent.
*
* @param edit change edit to publish
* @throws AuthException
* @throws NoSuchChangeException
* @throws IOException
* @throws InvalidChangeOperationException
* @throws OrmException
* @throws ResourceConflictException
*/
public void publish(ChangeEdit edit) throws AuthException,
NoSuchChangeException, IOException, InvalidChangeOperationException,
OrmException, ResourceConflictException {
Change change = edit.getChange();
Repository repo = gitManager.openRepository(change.getProject());
try {
RevWalk rw = new RevWalk(repo);
ObjectInserter inserter = repo.newObjectInserter();
try {
RevCommit editCommit = rw.parseCommit(edit.getRef().getObjectId());
if (editCommit == null) {
throw new NoSuchChangeException(change.getId());
}
PatchSet basePatchSet = getBasePatchSet(edit, editCommit);
if (!basePatchSet.getId().equals(change.currentPatchSetId())) {
throw new ResourceConflictException(
"only edit for current patch set can be published");
}
insertPatchSet(edit, change, repo, rw, basePatchSet,
squashEdit(repo, rw, inserter, editCommit, basePatchSet));
} finally {
inserter.release();
rw.release();
}
// TODO(davido): This should happen in the same BatchRefUpdate.
deleteRef(repo, edit);
} finally {
repo.close();
}
}
/**
* Retrieve base patch set the edit was created on.
*
* @param edit change edit to retrieve base patch set for
* @return parent patch set of the edit
* @throws IOException
* @throws InvalidChangeOperationException
*/
public PatchSet getBasePatchSet(ChangeEdit edit) throws IOException,
InvalidChangeOperationException {
Change change = edit.getChange();
Repository repo = gitManager.openRepository(change.getProject());
try {
RevWalk rw = new RevWalk(repo);
try {
return getBasePatchSet(edit, rw.parseCommit(
edit.getRef().getObjectId()));
} finally {
rw.release();
}
} finally {
repo.close();
}
}
/**
* Retrieve base patch set the edit was created on.
*
* @param edit change edit to retrieve base patch set for
* @param commit change edit commit
* @return parent patch set of the edit
* @throws IOException
* @throws InvalidChangeOperationException
*/
public PatchSet getBasePatchSet(ChangeEdit edit, RevCommit commit)
throws IOException, InvalidChangeOperationException {
if (commit.getParentCount() != 1) {
throw new InvalidChangeOperationException(
"change edit commit has multiple parents");
}
RevCommit parentCommit = commit.getParent(0);
ObjectId rev = parentCommit.getId();
RevId parentRev = new RevId(ObjectId.toString(rev));
try {
List<PatchSet> r = db.get().patchSets().byRevision(parentRev).toList();
if (r.isEmpty()) {
throw new InvalidChangeOperationException(String.format(
"patch set %s change edit is based on doesn't exist",
rev.abbreviate(8)));
}
if (r.size() > 1) {
throw new InvalidChangeOperationException(String.format(
"multiple patch sets for change edit parent %s",
rev.abbreviate(8)));
}
PatchSet parentPatchSet = Iterables.getOnlyElement(r);
if (!edit.getChange().getId().equals(
parentPatchSet.getId().getParentKey())) {
throw new InvalidChangeOperationException(String.format(
"different change edit ID %d and its parent patch set %d",
edit.getChange().getId().get(),
parentPatchSet.getId().getParentKey().get()));
}
return parentPatchSet;
} catch (OrmException e) {
throw new IOException(e);
}
}
/**
* Returns reference for this change edit with sharded user and change number:
* refs/users/UU/UUUU/edit-CCCC.
@@ -88,4 +231,75 @@ public class ChangeEditUtil {
RefNames.refsUsers(accountId),
changeId.get());
}
private RevCommit squashEdit(Repository repo, RevWalk rw,
ObjectInserter inserter, RevCommit edit, PatchSet basePatchSet)
throws IOException, ResourceConflictException {
RevCommit parent = rw.parseCommit(ObjectId.fromString(
basePatchSet.getRevision().get()));
if (parent.getTree().equals(edit.getTree())) {
throw new ResourceConflictException("identical tree");
}
return writeSquashedCommit(rw, inserter, parent, edit);
}
private void insertPatchSet(ChangeEdit edit, Change change,
Repository repo, RevWalk rw, PatchSet basePatchSet, RevCommit squashed)
throws NoSuchChangeException, InvalidChangeOperationException,
OrmException, IOException {
PatchSet ps = new PatchSet(
ChangeUtil.nextPatchSetId(change.currentPatchSetId()));
ps.setRevision(new RevId(ObjectId.toString(squashed)));
ps.setUploader(edit.getUser().getAccountId());
ps.setCreatedOn(TimeUtil.nowTs());
PatchSetInserter insr =
patchSetInserterFactory.create(repo, rw,
changeControlFactory.controlFor(change, edit.getUser()),
squashed);
insr.setPatchSet(ps)
.setMessage(
String.format("Patch Set %d: Published edit on patch set %d",
ps.getPatchSetId(),
basePatchSet.getPatchSetId()))
.insert();
}
private static void deleteRef(Repository repo, ChangeEdit edit)
throws IOException {
String refName = edit.getRefName();
RefUpdate ru = repo.updateRef(refName, true);
ru.setExpectedOldObjectId(edit.getRef().getObjectId());
ru.setForceUpdate(true);
RefUpdate.Result result = ru.delete();
switch (result) {
case FORCED:
case NEW:
case NO_CHANGE:
break;
default:
throw new IOException(String.format("Failed to delete ref %s: %s",
refName, result));
}
}
private static RevCommit writeSquashedCommit(RevWalk rw,
ObjectInserter inserter, RevCommit parent, RevCommit edit)
throws IOException {
CommitBuilder mergeCommit = new CommitBuilder();
mergeCommit.setParentIds(parent.getParent(0));
mergeCommit.setAuthor(parent.getAuthorIdent());
mergeCommit.setMessage(parent.getFullMessage());
mergeCommit.setCommitter(edit.getCommitterIdent());
mergeCommit.setTreeId(edit.getTree());
return rw.parseCommit(commit(inserter, mergeCommit));
}
private static ObjectId commit(ObjectInserter inserter,
CommitBuilder mergeCommit) throws IOException {
ObjectId id = inserter.insert(mergeCommit);
inserter.flush();
return id;
}
}