| // Copyright (C) 2008 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 com.google.gerrit.client.data.AccountInfoCacheFactory; |
| import com.google.gerrit.client.data.LineWithComments; |
| import com.google.gerrit.client.reviewdb.Account; |
| import com.google.gerrit.client.reviewdb.Change; |
| import com.google.gerrit.client.reviewdb.Patch; |
| import com.google.gerrit.client.reviewdb.PatchContent; |
| import com.google.gerrit.client.reviewdb.PatchLineComment; |
| import com.google.gerrit.client.reviewdb.PatchSet; |
| import com.google.gerrit.client.reviewdb.ReviewDb; |
| import com.google.gerrit.client.rpc.BaseServiceImplementation; |
| import com.google.gerrit.client.rpc.Common; |
| 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.gwtorm.client.OrmException; |
| import com.google.gwtorm.client.ResultSet; |
| |
| import org.spearce.jgit.errors.IncorrectObjectTypeException; |
| import org.spearce.jgit.lib.AnyObjectId; |
| import org.spearce.jgit.lib.Constants; |
| import org.spearce.jgit.lib.ObjectId; |
| import org.spearce.jgit.lib.ObjectLoader; |
| import org.spearce.jgit.lib.Repository; |
| import org.spearce.jgit.patch.CombinedFileHeader; |
| import org.spearce.jgit.patch.FileHeader; |
| import org.spearce.jgit.patch.FormatError; |
| |
| import java.io.IOException; |
| import java.util.ArrayList; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| |
| abstract class PatchDetailAction<T> implements Action<T> { |
| protected static final byte[] EMPTY_FILE = {}; |
| |
| protected final Patch.Key patchKey; |
| protected final List<PatchSet.Id> requestedVersions; |
| protected final boolean direct; |
| private final RepositoryCache repoCache; |
| private Repository repo; |
| protected Change change; |
| protected Patch patch; |
| protected FileHeader file; |
| protected int fileCnt; |
| protected AccountInfoCacheFactory accountInfo; |
| protected Account.Id me; |
| |
| protected HashMap<Integer, List<PatchLineComment>> published[]; |
| protected HashMap<Integer, List<PatchLineComment>> drafted[]; |
| |
| PatchDetailAction(final RepositoryCache rc, final Patch.Key key, |
| final List<PatchSet.Id> fileVersions) { |
| this.repoCache = rc; |
| this.patchKey = key; |
| this.requestedVersions = fileVersions; |
| this.direct = isBaseAndPatchOnly(key, fileVersions); |
| } |
| |
| private static boolean isBaseAndPatchOnly(final Patch.Key key, |
| final List<PatchSet.Id> v) { |
| if (v == null || v.size() < 2) { |
| return true; |
| } |
| for (int i = 0; i < v.size() - 1; i++) { |
| if (!PatchSet.BASE.equals(v.get(i))) { |
| return false; |
| } |
| } |
| return v.equals(key.getParentKey()); |
| } |
| |
| protected void init(final ReviewDb db) throws OrmException, Failure { |
| patch = db.patches().get(patchKey); |
| if (patch == null) { |
| throw new Failure(new NoSuchEntityException()); |
| } |
| change = db.changes().get(patch.getKey().getParentKey().getParentKey()); |
| BaseServiceImplementation.assertCanRead(change); |
| |
| file = readFileHeader(db); |
| if (file instanceof CombinedFileHeader) { |
| fileCnt = ((CombinedFileHeader) file).getParentCount() + 1; |
| } else { |
| fileCnt = 2; |
| } |
| |
| accountInfo = new AccountInfoCacheFactory(db); |
| me = Common.getAccountId(); |
| |
| published = new HashMap[fileCnt]; |
| for (int n = 0; n < fileCnt; n++) { |
| published[n] = new HashMap<Integer, List<PatchLineComment>>(); |
| } |
| indexComments(published, direct ? db.patchComments().published(patchKey) |
| : db.patchComments().published(change.getId(), patchKey.get())); |
| |
| if (me != null) { |
| drafted = new HashMap[fileCnt]; |
| for (int n = 0; n < fileCnt; n++) { |
| drafted[n] = new HashMap<Integer, List<PatchLineComment>>(); |
| } |
| indexComments(drafted, direct ? db.patchComments().draft(patchKey, me) |
| : db.patchComments().draft(change.getId(), patchKey.get(), me)); |
| } |
| } |
| |
| protected FileHeader readFileHeader(final ReviewDb db) throws Failure, |
| OrmException { |
| if (direct) { |
| return readCachedFileHeader(db); |
| } |
| |
| // TODO Fix this gross hack so we aren't running git diff-tree for |
| // an interdiff file side by side view. |
| // |
| final Map<PatchSet.Id, PatchSet> psMap = |
| db.patchSets().toMap(db.patchSets().byChange(change.getId())); |
| final Repository repo = openRepository(); |
| final List<String> args = new ArrayList<String>(); |
| args.add("git"); |
| args.add("--git-dir=."); |
| args.add("diff-tree"); |
| args.add("--full-index"); |
| if (requestedVersions.size() > 2) { |
| args.add("--cc"); |
| } else { |
| args.add("--unified=5"); |
| } |
| for (int i = 0; i < requestedVersions.size(); i++) { |
| final PatchSet.Id psi = requestedVersions.get(i); |
| if (psi == null) { |
| throw new Failure(new NoSuchEntityException()); |
| |
| } else if (psi.equals(PatchSet.BASE)) { |
| final PatchSet p = psMap.get(patchKey.getParentKey()); |
| if (p == null || p.getRevision() == null |
| || p.getRevision().get() == null) { |
| throw new Failure(new NoSuchEntityException()); |
| } |
| args.add(p.getRevision().get() + "^" + (i + 1)); |
| |
| } else { |
| final PatchSet p = psMap.get(psi); |
| if (p == null || p.getRevision() == null |
| || p.getRevision().get() == null) { |
| throw new Failure(new NoSuchEntityException()); |
| } |
| args.add(p.getRevision().get()); |
| } |
| } |
| args.add("--"); |
| args.add(patch.getFileName()); |
| |
| try { |
| final Process proc = |
| Runtime.getRuntime().exec(args.toArray(new String[args.size()]), |
| null, repo.getDirectory()); |
| try { |
| final org.spearce.jgit.patch.Patch p = |
| new org.spearce.jgit.patch.Patch(); |
| proc.getOutputStream().close(); |
| proc.getErrorStream().close(); |
| p.parse(proc.getInputStream()); |
| proc.getInputStream().close(); |
| if (p.getFiles().isEmpty()) |
| throw new Failure(new NoDifferencesException()); |
| if (p.getFiles().size() != 1) |
| throw new IOException("unexpected file count back"); |
| return p.getFiles().get(0); |
| } finally { |
| try { |
| if (proc.waitFor() != 0) { |
| throw new IOException("git diff-tree exited abnormally"); |
| } |
| } catch (InterruptedException ie) { |
| } |
| } |
| } catch (IOException e) { |
| throw new Failure(e); |
| } |
| } |
| |
| protected FileHeader readCachedFileHeader(final ReviewDb db) throws Failure, |
| OrmException { |
| return parse(patch, read(db, patch)); |
| } |
| |
| protected List<Patch> history(final ReviewDb db) throws OrmException { |
| return db.patches().history(change.getId(), patch.getFileName()).toList(); |
| } |
| |
| protected void indexComments( |
| final HashMap<Integer, List<PatchLineComment>>[] out, |
| final ResultSet<PatchLineComment> comments) { |
| for (final PatchLineComment c : comments) { |
| short fileId; |
| if (direct) { |
| fileId = c.getSide(); |
| } else { |
| for (fileId = 0; fileId < requestedVersions.size(); fileId++) { |
| final PatchSet.Id i = requestedVersions.get(fileId); |
| if (PatchSet.BASE.equals(i) && c.getSide() == fileId |
| && patchKey.equals(c.getKey().getParentKey())) { |
| break; |
| } |
| if (c.getSide() == requestedVersions.size() - 1 |
| && i.equals(c.getKey().getParentKey().getParentKey())) { |
| break; |
| } |
| } |
| } |
| |
| if (0 <= fileId && fileId < out.length) { |
| final HashMap<Integer, List<PatchLineComment>> m = out[fileId]; |
| List<PatchLineComment> l = m.get(c.getLine()); |
| if (l == null) { |
| l = new ArrayList<PatchLineComment>(4); |
| m.put(c.getLine(), l); |
| } |
| l.add(c); |
| } |
| } |
| } |
| |
| protected void addComments(final LineWithComments pLine, |
| final HashMap<Integer, List<PatchLineComment>>[] cache, final int side, |
| final int line) { |
| List<PatchLineComment> l = cache[side].remove(line); |
| if (l != null) { |
| for (final PatchLineComment c : l) { |
| pLine.addComment(c); |
| accountInfo.want(c.getAuthor()); |
| } |
| } |
| } |
| |
| protected Repository openRepository() throws Failure { |
| if (repo == null) { |
| if (change.getDest() == null) { |
| throw new Failure(new CorruptEntityException(change.getId())); |
| } |
| try { |
| repo = repoCache.get(change.getDest().getParentKey().get()); |
| } catch (InvalidRepositoryException err) { |
| throw new Failure(err); |
| } |
| } |
| return repo; |
| } |
| |
| protected byte[] read(final Repository repo, final AnyObjectId id) |
| throws Failure { |
| if (id == null || ObjectId.zeroId().equals(id)) { |
| return EMPTY_FILE; |
| } |
| try { |
| final ObjectLoader ldr = repo.openObject(id); |
| if (ldr == null) { |
| throw new Failure(new CorruptEntityException(patch.getKey())); |
| } |
| final byte[] content = ldr.getCachedBytes(); |
| if (ldr.getType() != Constants.OBJ_BLOB) { |
| throw new Failure(new IncorrectObjectTypeException(id.toObjectId(), |
| Constants.TYPE_BLOB)); |
| } |
| return content; |
| } catch (IOException err) { |
| throw new Failure(err); |
| } |
| } |
| |
| protected static String read(final ReviewDb db, final Patch patch) |
| throws Failure, OrmException { |
| final PatchContent.Key key = patch.getContent(); |
| if (key == null) { |
| throw new Failure(new CorruptEntityException(patch.getKey())); |
| } |
| |
| final PatchContent pc = db.patchContents().get(key); |
| if (pc == null || pc.getContent() == null) { |
| throw new Failure(new CorruptEntityException(patch.getKey())); |
| } |
| |
| return pc.getContent(); |
| } |
| |
| protected static FileHeader parse(final Patch patch, final String content) |
| throws Failure { |
| final byte[] buf = Constants.encode(content); |
| final org.spearce.jgit.patch.Patch p = new org.spearce.jgit.patch.Patch(); |
| p.parse(buf, 0, buf.length); |
| for (final FormatError err : p.getErrors()) { |
| if (err.getSeverity() == FormatError.Severity.ERROR) { |
| throw new Failure(new CorruptEntityException(patch.getKey())); |
| } |
| } |
| if (p.getFiles().size() != 1) { |
| throw new Failure(new CorruptEntityException(patch.getKey())); |
| } |
| return p.getFiles().get(0); |
| } |
| } |