blob: 56a85cfc96a2c9b1fce28ff6a8415a48fc63ca18 [file] [log] [blame]
// Copyright (C) 2009 The Android Open Source Project
//
// 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.google.gerrit.server.patch;
import static com.google.gerrit.client.rpc.BaseServiceImplementation.canRead;
import com.google.gerrit.client.data.PatchScript;
import com.google.gerrit.client.reviewdb.AccountGeneralPreferences;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.Patch;
import com.google.gerrit.client.reviewdb.PatchSet;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.rpc.CorruptEntityException;
import com.google.gerrit.client.rpc.NoDifferencesException;
import com.google.gerrit.client.rpc.NoSuchEntityException;
import com.google.gerrit.client.rpc.BaseServiceImplementation.Action;
import com.google.gerrit.client.rpc.BaseServiceImplementation.Failure;
import com.google.gerrit.git.InvalidRepositoryException;
import com.google.gerrit.git.RepositoryCache;
import com.google.gerrit.server.GerritServer;
import com.google.gwtorm.client.OrmException;
import net.sf.ehcache.CacheException;
import net.sf.ehcache.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ObjectWriter;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.IOException;
class PatchScriptAction implements Action<PatchScript> {
private static final Logger log =
LoggerFactory.getLogger(PatchScriptAction.class);
private final GerritServer server;
private final RepositoryCache rc;
private final Patch.Key patchKey;
private final PatchSet.Id psa;
private final PatchSet.Id psb;
private final int context;
private final PatchSet.Id patchSetId;
private final Change.Id changeId;
private Change change;
private Patch patch;
private Project.NameKey projectKey;
private Repository git;
PatchScriptAction(final GerritServer gs, final RepositoryCache rc,
final Patch.Key patchKey, final PatchSet.Id psa, final PatchSet.Id psb,
final int context) {
this.server = gs;
this.rc = rc;
this.patchKey = patchKey;
this.psa = psa;
this.psb = psb;
this.context = context;
patchSetId = patchKey.getParentKey();
changeId = patchSetId.getParentKey();
}
public PatchScript run(final ReviewDb db) throws OrmException, Failure {
validatePatchSetId(psa);
validatePatchSetId(psb);
change = db.changes().get(changeId);
patch = db.patches().get(patchKey);
if (change == null || patch == null || !canRead(change)) {
throw new Failure(new NoSuchEntityException());
}
projectKey = change.getDest().getParentKey();
try {
git = rc.get(projectKey.get());
} catch (InvalidRepositoryException e) {
log.error("Repository " + projectKey + " not found", e);
throw new Failure(new NoSuchEntityException());
}
final PatchScriptBuilder b = newBuilder();
final ObjectId bId = toObjectId(db, psb);
final ObjectId aId = psa == null ? ancestor(bId) : toObjectId(db, psa);
final DiffCacheKey key = new DiffCacheKey(projectKey, aId, bId, patch);
final Element cacheElem;
try {
cacheElem = server.getDiffCache().get(key);
} catch (IllegalStateException e) {
log.error("Cache get failed for " + key, e);
throw new Failure(new NoSuchEntityException());
} catch (CacheException e) {
log.error("Cache get failed for " + key, e);
throw new Failure(new NoSuchEntityException());
}
if (cacheElem == null || cacheElem.getObjectValue() == null) {
log.error("Cache get failed for " + key);
throw new Failure(new NoSuchEntityException());
}
final DiffCacheContent dcc = (DiffCacheContent) cacheElem.getObjectValue();
if (dcc.isNoDifference()) {
throw new Failure(new NoDifferencesException());
}
try {
return b.toPatchScript(dcc);
} catch (CorruptEntityException e) {
log.error("File content for " + key + " unavailable", e);
throw new Failure(new NoSuchEntityException());
}
}
private PatchScriptBuilder newBuilder() throws Failure {
final PatchScriptBuilder b = new PatchScriptBuilder();
b.setRepository(git);
b.setPatch(patch);
if (context == AccountGeneralPreferences.WHOLE_FILE_CONTEXT)
b.setContext(PatchScriptBuilder.MAX_CONTEXT);
else if (0 <= context && context <= PatchScriptBuilder.MAX_CONTEXT)
b.setContext(context);
else
throw new Failure(new NoSuchEntityException());
return b;
}
private ObjectId ancestor(final ObjectId id) throws Failure {
try {
final RevCommit c = new RevWalk(git).parseCommit(id);
switch (c.getParentCount()) {
case 0:
return emptyTree();
case 1:
return c.getParent(0).getId();
default:
return null;
}
} catch (IOException e) {
log.error("Commit information for " + id.name() + " unavailable", e);
throw new Failure(new NoSuchEntityException());
}
}
private ObjectId emptyTree() throws IOException {
return new ObjectWriter(git).writeCanonicalTree(new byte[0]);
}
private ObjectId toObjectId(final ReviewDb db, final PatchSet.Id psId)
throws Failure, OrmException {
if (!changeId.equals(psId.getParentKey())) {
throw new Failure(new NoSuchEntityException());
}
final PatchSet ps = db.patchSets().get(psId);
if (ps == null || ps.getRevision() == null
|| ps.getRevision().get() == null) {
throw new Failure(new NoSuchEntityException());
}
try {
return ObjectId.fromString(ps.getRevision().get());
} catch (IllegalArgumentException e) {
log.error("Patch set " + psId + " has invalid revision");
throw new Failure(new NoSuchEntityException());
}
}
private void validatePatchSetId(final PatchSet.Id psId) throws Failure {
if (psId == null) { // OK, means use base;
} else if (changeId.equals(psId.getParentKey())) { // OK, same change;
} else {
throw new Failure(new NoSuchEntityException());
}
}
}