Move patch script class to the server tree.
The PatchScriptFactory and PatchScriptBuilder were previously in the
httpd tree. Move them to the server tree so the implementation of the
revision diff REST API can share the logic. Also, update
PatchScriptFactory to implement Callable instead of Handler, so
there is not a circular dependency.
Prior-Id: Ic20fbf81a2da5ad5e2fd5c3542e6caa47df33cea
(cherry picked from commit 2c29e73e6b
)
Change-Id: I4a24fa75a3f83ab8ec1129fe8fef06cd64e730a3
This commit is contained in:

committed by
Shawn Pearce

parent
2f2466a05a
commit
010daed12d
@@ -0,0 +1,518 @@
|
||||
// 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 com.google.gerrit.common.data.CommentDetail;
|
||||
import com.google.gerrit.common.data.PatchScript;
|
||||
import com.google.gerrit.common.data.PatchScript.DisplayMethod;
|
||||
import com.google.gerrit.prettify.common.EditList;
|
||||
import com.google.gerrit.prettify.common.SparseFileContent;
|
||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
|
||||
import com.google.gerrit.server.FileTypeRegistry;
|
||||
import com.google.inject.Inject;
|
||||
|
||||
import eu.medsea.mimeutil.MimeType;
|
||||
import eu.medsea.mimeutil.MimeUtil2;
|
||||
|
||||
import org.eclipse.jgit.diff.Edit;
|
||||
import org.eclipse.jgit.errors.CorruptObjectException;
|
||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.eclipse.jgit.errors.MissingObjectException;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
class PatchScriptBuilder {
|
||||
static final int MAX_CONTEXT = 5000000;
|
||||
static final int BIG_FILE = 9000;
|
||||
|
||||
private static final Comparator<Edit> EDIT_SORT = new Comparator<Edit>() {
|
||||
@Override
|
||||
public int compare(final Edit o1, final Edit o2) {
|
||||
return o1.getBeginA() - o2.getBeginA();
|
||||
}
|
||||
};
|
||||
|
||||
private Repository db;
|
||||
private Project.NameKey projectKey;
|
||||
private ObjectReader reader;
|
||||
private Change change;
|
||||
private AccountDiffPreference diffPrefs;
|
||||
private boolean againstParent;
|
||||
private ObjectId aId;
|
||||
private ObjectId bId;
|
||||
|
||||
private final Side a;
|
||||
private final Side b;
|
||||
|
||||
private List<Edit> edits;
|
||||
private final FileTypeRegistry registry;
|
||||
private final PatchListCache patchListCache;
|
||||
private int context;
|
||||
|
||||
@Inject
|
||||
PatchScriptBuilder(final FileTypeRegistry ftr, final PatchListCache plc) {
|
||||
a = new Side();
|
||||
b = new Side();
|
||||
registry = ftr;
|
||||
patchListCache = plc;
|
||||
}
|
||||
|
||||
void setRepository(Repository r, Project.NameKey projectKey) {
|
||||
this.db = r;
|
||||
this.projectKey = projectKey;
|
||||
}
|
||||
|
||||
void setChange(final Change c) {
|
||||
this.change = c;
|
||||
}
|
||||
|
||||
void setDiffPrefs(final AccountDiffPreference dp) {
|
||||
diffPrefs = dp;
|
||||
|
||||
context = diffPrefs.getContext();
|
||||
if (context == AccountDiffPreference.WHOLE_FILE_CONTEXT) {
|
||||
context = MAX_CONTEXT;
|
||||
} else if (context > MAX_CONTEXT) {
|
||||
context = MAX_CONTEXT;
|
||||
}
|
||||
}
|
||||
|
||||
void setTrees(final boolean ap, final ObjectId a, final ObjectId b) {
|
||||
againstParent = ap;
|
||||
aId = a;
|
||||
bId = b;
|
||||
}
|
||||
|
||||
PatchScript toPatchScript(final PatchListEntry content,
|
||||
final CommentDetail comments, final List<Patch> history)
|
||||
throws IOException {
|
||||
reader = db.newObjectReader();
|
||||
try {
|
||||
return build(content, comments, history);
|
||||
} finally {
|
||||
reader.release();
|
||||
}
|
||||
}
|
||||
|
||||
private PatchScript build(final PatchListEntry content,
|
||||
final CommentDetail comments, final List<Patch> history)
|
||||
throws IOException {
|
||||
boolean intralineDifferenceIsPossible = true;
|
||||
boolean intralineFailure = false;
|
||||
boolean intralineTimeout = false;
|
||||
|
||||
a.path = oldName(content);
|
||||
b.path = newName(content);
|
||||
|
||||
a.resolve(null, aId);
|
||||
b.resolve(a, bId);
|
||||
|
||||
edits = new ArrayList<Edit>(content.getEdits());
|
||||
|
||||
if (!isModify(content)) {
|
||||
intralineDifferenceIsPossible = false;
|
||||
} else if (diffPrefs.isIntralineDifference()) {
|
||||
IntraLineDiff d =
|
||||
patchListCache.getIntraLineDiff(new IntraLineDiffKey(a.id, a.src,
|
||||
b.id, b.src, edits, projectKey, bId, b.path,
|
||||
diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE));
|
||||
if (d != null) {
|
||||
switch (d.getStatus()) {
|
||||
case EDIT_LIST:
|
||||
edits = new ArrayList<Edit>(d.getEdits());
|
||||
break;
|
||||
|
||||
case DISABLED:
|
||||
intralineDifferenceIsPossible = false;
|
||||
break;
|
||||
|
||||
case ERROR:
|
||||
intralineDifferenceIsPossible = false;
|
||||
intralineFailure = true;
|
||||
break;
|
||||
|
||||
case TIMEOUT:
|
||||
intralineDifferenceIsPossible = false;
|
||||
intralineTimeout = true;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
intralineDifferenceIsPossible = false;
|
||||
intralineFailure = true;
|
||||
}
|
||||
}
|
||||
|
||||
ensureCommentsVisible(comments);
|
||||
|
||||
boolean hugeFile = false;
|
||||
if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) {
|
||||
|
||||
} else if (a.src == b.src && a.size() <= context
|
||||
&& content.getEdits().isEmpty()) {
|
||||
// Odd special case; the files are identical (100% rename or copy)
|
||||
// and the user has asked for context that is larger than the file.
|
||||
// Send them the entire file, with an empty edit after the last line.
|
||||
//
|
||||
for (int i = 0; i < a.size(); i++) {
|
||||
a.addLine(i);
|
||||
}
|
||||
edits = new ArrayList<Edit>(1);
|
||||
edits.add(new Edit(a.size(), a.size()));
|
||||
|
||||
} else {
|
||||
if (BIG_FILE < Math.max(a.size(), b.size())) {
|
||||
// IF the file is really large, we disable things to avoid choking
|
||||
// the browser client.
|
||||
//
|
||||
diffPrefs.setContext((short) Math.min(25, context));
|
||||
diffPrefs.setSyntaxHighlighting(false);
|
||||
context = diffPrefs.getContext();
|
||||
hugeFile = true;
|
||||
|
||||
} else {
|
||||
// In order to expand the skipped common lines or syntax highlight the
|
||||
// file properly we need to give the client the complete file contents.
|
||||
// So force our context temporarily to the complete file size.
|
||||
//
|
||||
context = MAX_CONTEXT;
|
||||
}
|
||||
packContent(diffPrefs.getIgnoreWhitespace() != Whitespace.IGNORE_NONE);
|
||||
}
|
||||
|
||||
return new PatchScript(change.getKey(), content.getChangeType(),
|
||||
content.getOldName(), content.getNewName(), a.fileMode, b.fileMode,
|
||||
content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
|
||||
a.displayMethod, b.displayMethod, comments, history, hugeFile,
|
||||
intralineDifferenceIsPossible, intralineFailure, intralineTimeout);
|
||||
}
|
||||
|
||||
private static boolean isModify(PatchListEntry content) {
|
||||
switch (content.getChangeType()) {
|
||||
case MODIFIED:
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
return true;
|
||||
|
||||
case ADDED:
|
||||
case DELETED:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static String oldName(final PatchListEntry entry) {
|
||||
switch (entry.getChangeType()) {
|
||||
case ADDED:
|
||||
return null;
|
||||
case DELETED:
|
||||
case MODIFIED:
|
||||
return entry.getNewName();
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
default:
|
||||
return entry.getOldName();
|
||||
}
|
||||
}
|
||||
|
||||
private static String newName(final PatchListEntry entry) {
|
||||
switch (entry.getChangeType()) {
|
||||
case DELETED:
|
||||
return null;
|
||||
case ADDED:
|
||||
case MODIFIED:
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
default:
|
||||
return entry.getNewName();
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureCommentsVisible(final CommentDetail comments) {
|
||||
if (comments.getCommentsA().isEmpty() && comments.getCommentsB().isEmpty()) {
|
||||
// No comments, no additional dummy edits are required.
|
||||
//
|
||||
return;
|
||||
}
|
||||
|
||||
// Construct empty Edit blocks around each location where a comment is.
|
||||
// This will force the later packContent method to include the regions
|
||||
// containing comments, potentially combining those regions together if
|
||||
// they have overlapping contexts. UI renders will also be able to make
|
||||
// correct hunks from this, but because the Edit is empty they will not
|
||||
// style it specially.
|
||||
//
|
||||
final List<Edit> empty = new ArrayList<Edit>();
|
||||
int lastLine;
|
||||
|
||||
lastLine = -1;
|
||||
for (PatchLineComment plc : comments.getCommentsA()) {
|
||||
final int a = plc.getLine();
|
||||
if (lastLine != a) {
|
||||
final int b = mapA2B(a - 1);
|
||||
if (0 <= b) {
|
||||
safeAdd(empty, new Edit(a - 1, b));
|
||||
}
|
||||
lastLine = a;
|
||||
}
|
||||
}
|
||||
|
||||
lastLine = -1;
|
||||
for (PatchLineComment plc : comments.getCommentsB()) {
|
||||
final int b = plc.getLine();
|
||||
if (lastLine != b) {
|
||||
final int a = mapB2A(b - 1);
|
||||
if (0 <= a) {
|
||||
safeAdd(empty, new Edit(a, b - 1));
|
||||
}
|
||||
lastLine = b;
|
||||
}
|
||||
}
|
||||
|
||||
// Sort the final list by the index in A, so packContent can combine
|
||||
// them correctly later.
|
||||
//
|
||||
edits.addAll(empty);
|
||||
Collections.sort(edits, EDIT_SORT);
|
||||
}
|
||||
|
||||
private void safeAdd(final List<Edit> empty, final Edit toAdd) {
|
||||
final int a = toAdd.getBeginA();
|
||||
final int b = toAdd.getBeginB();
|
||||
for (final Edit e : edits) {
|
||||
if (e.getBeginA() <= a && a <= e.getEndA()) {
|
||||
return;
|
||||
}
|
||||
if (e.getBeginB() <= b && b <= e.getEndB()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
empty.add(toAdd);
|
||||
}
|
||||
|
||||
private int mapA2B(final int a) {
|
||||
if (edits.isEmpty()) {
|
||||
// Magic special case of an unmodified file.
|
||||
//
|
||||
return a;
|
||||
}
|
||||
|
||||
for (int i = 0; i < edits.size(); i++) {
|
||||
final Edit e = edits.get(i);
|
||||
if (a < e.getBeginA()) {
|
||||
if (i == 0) {
|
||||
// Special case of context at start of file.
|
||||
//
|
||||
return a;
|
||||
}
|
||||
return e.getBeginB() - (e.getBeginA() - a);
|
||||
}
|
||||
if (e.getBeginA() <= a && a <= e.getEndA()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
final Edit last = edits.get(edits.size() - 1);
|
||||
return last.getBeginB() + (a - last.getEndA());
|
||||
}
|
||||
|
||||
private int mapB2A(final int b) {
|
||||
if (edits.isEmpty()) {
|
||||
// Magic special case of an unmodified file.
|
||||
//
|
||||
return b;
|
||||
}
|
||||
|
||||
for (int i = 0; i < edits.size(); i++) {
|
||||
final Edit e = edits.get(i);
|
||||
if (b < e.getBeginB()) {
|
||||
if (i == 0) {
|
||||
// Special case of context at start of file.
|
||||
//
|
||||
return b;
|
||||
}
|
||||
return e.getBeginA() - (e.getBeginB() - b);
|
||||
}
|
||||
if (e.getBeginB() <= b && b <= e.getEndB()) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
final Edit last = edits.get(edits.size() - 1);
|
||||
return last.getBeginA() + (b - last.getEndB());
|
||||
}
|
||||
|
||||
private void packContent(boolean ignoredWhitespace) {
|
||||
EditList list = new EditList(edits, context, a.size(), b.size());
|
||||
for (final EditList.Hunk hunk : list.getHunks()) {
|
||||
while (hunk.next()) {
|
||||
if (hunk.isContextLine()) {
|
||||
final String lineA = a.src.getString(hunk.getCurA());
|
||||
a.dst.addLine(hunk.getCurA(), lineA);
|
||||
|
||||
if (ignoredWhitespace) {
|
||||
// If we ignored whitespace in some form, also get the line
|
||||
// from b when it does not exactly match the line from a.
|
||||
//
|
||||
final String lineB = b.src.getString(hunk.getCurB());
|
||||
if (!lineA.equals(lineB)) {
|
||||
b.dst.addLine(hunk.getCurB(), lineB);
|
||||
}
|
||||
}
|
||||
hunk.incBoth();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (hunk.isDeletedA()) {
|
||||
a.addLine(hunk.getCurA());
|
||||
hunk.incA();
|
||||
}
|
||||
|
||||
if (hunk.isInsertedB()) {
|
||||
b.addLine(hunk.getCurB());
|
||||
hunk.incB();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class Side {
|
||||
String path;
|
||||
ObjectId id;
|
||||
FileMode mode;
|
||||
byte[] srcContent;
|
||||
Text src;
|
||||
MimeType mimeType = MimeUtil2.UNKNOWN_MIME_TYPE;
|
||||
DisplayMethod displayMethod = DisplayMethod.DIFF;
|
||||
PatchScript.FileMode fileMode = PatchScript.FileMode.FILE;
|
||||
final SparseFileContent dst = new SparseFileContent();
|
||||
|
||||
int size() {
|
||||
return src != null ? src.size() : 0;
|
||||
}
|
||||
|
||||
void addLine(int line) {
|
||||
dst.addLine(line, src.getString(line));
|
||||
}
|
||||
|
||||
void resolve(final Side other, final ObjectId within) throws IOException {
|
||||
try {
|
||||
final boolean reuse;
|
||||
if (Patch.COMMIT_MSG.equals(path)) {
|
||||
if (againstParent && (aId == within || within.equals(aId))) {
|
||||
id = ObjectId.zeroId();
|
||||
src = Text.EMPTY;
|
||||
srcContent = Text.NO_BYTES;
|
||||
mode = FileMode.MISSING;
|
||||
displayMethod = DisplayMethod.NONE;
|
||||
} else {
|
||||
id = within;
|
||||
src = Text.forCommit(db, reader, within);
|
||||
srcContent = src.getContent();
|
||||
if (src == Text.EMPTY) {
|
||||
mode = FileMode.MISSING;
|
||||
displayMethod = DisplayMethod.NONE;
|
||||
} else {
|
||||
mode = FileMode.REGULAR_FILE;
|
||||
}
|
||||
}
|
||||
reuse = false;
|
||||
|
||||
} else {
|
||||
final TreeWalk tw = find(within);
|
||||
|
||||
id = tw != null ? tw.getObjectId(0) : ObjectId.zeroId();
|
||||
mode = tw != null ? tw.getFileMode(0) : FileMode.MISSING;
|
||||
reuse = other != null && other.id.equals(id) && other.mode == mode;
|
||||
|
||||
if (reuse) {
|
||||
srcContent = other.srcContent;
|
||||
|
||||
} else if (mode.getObjectType() == Constants.OBJ_BLOB) {
|
||||
srcContent = Text.asByteArray(db.open(id, Constants.OBJ_BLOB));
|
||||
|
||||
} else {
|
||||
srcContent = Text.NO_BYTES;
|
||||
}
|
||||
|
||||
if (reuse) {
|
||||
mimeType = other.mimeType;
|
||||
displayMethod = other.displayMethod;
|
||||
src = other.src;
|
||||
|
||||
} else if (srcContent.length > 0 && FileMode.SYMLINK != mode) {
|
||||
mimeType = registry.getMimeType(path, srcContent);
|
||||
if ("image".equals(mimeType.getMediaType())
|
||||
&& registry.isSafeInline(mimeType)) {
|
||||
displayMethod = DisplayMethod.IMG;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mode == FileMode.MISSING) {
|
||||
displayMethod = DisplayMethod.NONE;
|
||||
}
|
||||
|
||||
if (!reuse) {
|
||||
if (srcContent == Text.NO_BYTES) {
|
||||
src = Text.EMPTY;
|
||||
} else {
|
||||
src = new Text(srcContent);
|
||||
}
|
||||
}
|
||||
|
||||
if (srcContent.length > 0 && srcContent[srcContent.length - 1] != '\n') {
|
||||
dst.setMissingNewlineAtEnd(true);
|
||||
}
|
||||
dst.setSize(size());
|
||||
dst.setPath(path);
|
||||
|
||||
if (mode == FileMode.SYMLINK) {
|
||||
fileMode = PatchScript.FileMode.SYMLINK;
|
||||
} else if (mode == FileMode.GITLINK) {
|
||||
fileMode = PatchScript.FileMode.GITLINK;
|
||||
}
|
||||
} catch (IOException err) {
|
||||
throw new IOException("Cannot read " + within.name() + ":" + path, err);
|
||||
}
|
||||
}
|
||||
|
||||
private TreeWalk find(final ObjectId within) throws MissingObjectException,
|
||||
IncorrectObjectTypeException, CorruptObjectException, IOException {
|
||||
if (path == null || within == null) {
|
||||
return null;
|
||||
}
|
||||
final RevWalk rw = new RevWalk(reader);
|
||||
final RevTree tree = rw.parseTree(within);
|
||||
return TreeWalk.forPath(reader, path, tree);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,334 @@
|
||||
// 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 com.google.gerrit.common.data.CommentDetail;
|
||||
import com.google.gerrit.common.data.PatchScript;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference;
|
||||
import com.google.gerrit.reviewdb.client.Change;
|
||||
import com.google.gerrit.reviewdb.client.Patch;
|
||||
import com.google.gerrit.reviewdb.client.PatchLineComment;
|
||||
import com.google.gerrit.reviewdb.client.PatchSet;
|
||||
import com.google.gerrit.reviewdb.client.Project;
|
||||
import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
|
||||
import com.google.gerrit.reviewdb.client.Patch.ChangeType;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountInfoCacheFactory;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gerrit.server.git.LargeObjectException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
|
||||
public class PatchScriptFactory implements Callable<PatchScript> {
|
||||
public interface Factory {
|
||||
PatchScriptFactory create(Patch.Key patchKey,
|
||||
@Assisted("patchSetA") PatchSet.Id patchSetA,
|
||||
@Assisted("patchSetB") PatchSet.Id patchSetB,
|
||||
AccountDiffPreference diffPrefs);
|
||||
}
|
||||
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(PatchScriptFactory.class);
|
||||
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final Provider<PatchScriptBuilder> builderFactory;
|
||||
private final PatchListCache patchListCache;
|
||||
private final ReviewDb db;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
private final AccountInfoCacheFactory.Factory aicFactory;
|
||||
|
||||
private final Patch.Key patchKey;
|
||||
@Nullable
|
||||
private final PatchSet.Id psa;
|
||||
private final PatchSet.Id psb;
|
||||
private final AccountDiffPreference diffPrefs;
|
||||
|
||||
private final PatchSet.Id patchSetId;
|
||||
private final Change.Id changeId;
|
||||
|
||||
private Change change;
|
||||
private PatchSet patchSet;
|
||||
private Project.NameKey projectKey;
|
||||
private ChangeControl control;
|
||||
private ObjectId aId;
|
||||
private ObjectId bId;
|
||||
private List<Patch> history;
|
||||
private CommentDetail comments;
|
||||
|
||||
@Inject
|
||||
PatchScriptFactory(final GitRepositoryManager grm,
|
||||
Provider<PatchScriptBuilder> builderFactory,
|
||||
final PatchListCache patchListCache, final ReviewDb db,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
final AccountInfoCacheFactory.Factory aicFactory,
|
||||
@Assisted final Patch.Key patchKey,
|
||||
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
|
||||
@Assisted("patchSetB") final PatchSet.Id patchSetB,
|
||||
@Assisted final AccountDiffPreference diffPrefs) {
|
||||
this.repoManager = grm;
|
||||
this.builderFactory = builderFactory;
|
||||
this.patchListCache = patchListCache;
|
||||
this.db = db;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
this.aicFactory = aicFactory;
|
||||
|
||||
this.patchKey = patchKey;
|
||||
this.psa = patchSetA;
|
||||
this.psb = patchSetB;
|
||||
this.diffPrefs = diffPrefs;
|
||||
|
||||
patchSetId = patchKey.getParentKey();
|
||||
changeId = patchSetId.getParentKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PatchScript call() throws OrmException, NoSuchChangeException,
|
||||
LargeObjectException {
|
||||
validatePatchSetId(psa);
|
||||
validatePatchSetId(psb);
|
||||
|
||||
control = changeControlFactory.validateFor(changeId);
|
||||
change = control.getChange();
|
||||
projectKey = change.getProject();
|
||||
patchSet = db.patchSets().get(patchSetId);
|
||||
if (patchSet == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
aId = psa != null ? toObjectId(db, psa) : null;
|
||||
bId = toObjectId(db, psb);
|
||||
|
||||
final Repository git;
|
||||
try {
|
||||
git = repoManager.openRepository(projectKey);
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
log.error("Repository " + projectKey + " not found", e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
} catch (IOException e) {
|
||||
log.error("Cannot open repository " + projectKey, e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
}
|
||||
try {
|
||||
final PatchList list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
|
||||
final PatchScriptBuilder b = newBuilder(list, git);
|
||||
final PatchListEntry content = list.get(patchKey.getFileName());
|
||||
|
||||
loadCommentsAndHistory(content.getChangeType(), //
|
||||
content.getOldName(), //
|
||||
content.getNewName());
|
||||
|
||||
return b.toPatchScript(content, comments, history);
|
||||
} catch (PatchListNotAvailableException e) {
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
} catch (IOException e) {
|
||||
log.error("File content unavailable", e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
} catch (org.eclipse.jgit.errors.LargeObjectException err) {
|
||||
throw new LargeObjectException("File content is too large", err);
|
||||
} finally {
|
||||
git.close();
|
||||
}
|
||||
}
|
||||
|
||||
private PatchListKey keyFor(final Whitespace whitespace) {
|
||||
return new PatchListKey(projectKey, aId, bId, whitespace);
|
||||
}
|
||||
|
||||
private PatchList listFor(final PatchListKey key)
|
||||
throws PatchListNotAvailableException {
|
||||
return patchListCache.get(key);
|
||||
}
|
||||
|
||||
private PatchScriptBuilder newBuilder(final PatchList list, Repository git) {
|
||||
final AccountDiffPreference dp = new AccountDiffPreference(diffPrefs);
|
||||
final PatchScriptBuilder b = builderFactory.get();
|
||||
b.setRepository(git, projectKey);
|
||||
b.setChange(change);
|
||||
b.setDiffPrefs(dp);
|
||||
b.setTrees(list.isAgainstParent(), list.getOldId(), list.getNewId());
|
||||
return b;
|
||||
}
|
||||
|
||||
private ObjectId toObjectId(final ReviewDb db, final PatchSet.Id psId)
|
||||
throws OrmException, NoSuchChangeException {
|
||||
if (!changeId.equals(psId.getParentKey())) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
final PatchSet ps = db.patchSets().get(psId);
|
||||
if (ps == null || ps.getRevision() == null
|
||||
|| ps.getRevision().get() == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
try {
|
||||
return ObjectId.fromString(ps.getRevision().get());
|
||||
} catch (IllegalArgumentException e) {
|
||||
log.error("Patch set " + psId + " has invalid revision");
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private void validatePatchSetId(final PatchSet.Id psId)
|
||||
throws NoSuchChangeException {
|
||||
if (psId == null) { // OK, means use base;
|
||||
} else if (changeId.equals(psId.getParentKey())) { // OK, same change;
|
||||
} else {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
}
|
||||
|
||||
private void loadCommentsAndHistory(final ChangeType changeType,
|
||||
final String oldName, final String newName) throws OrmException {
|
||||
history = new ArrayList<Patch>();
|
||||
comments = new CommentDetail(psa, psb);
|
||||
|
||||
final Map<Patch.Key, Patch> byKey = new HashMap<Patch.Key, Patch>();
|
||||
final AccountInfoCacheFactory aic = aicFactory.create();
|
||||
|
||||
// This seems like a cheap trick. It doesn't properly account for a
|
||||
// file that gets renamed between patch set 1 and patch set 2. We
|
||||
// will wind up packing the wrong Patch object because we didn't do
|
||||
// proper rename detection between the patch sets.
|
||||
//
|
||||
for (final PatchSet ps : db.patchSets().byChange(changeId)) {
|
||||
String name = patchKey.get();
|
||||
if (psa != null) {
|
||||
switch (changeType) {
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
if (ps.getId().equals(psa)) {
|
||||
name = oldName;
|
||||
}
|
||||
break;
|
||||
|
||||
case MODIFIED:
|
||||
case DELETED:
|
||||
case ADDED:
|
||||
case REWRITE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
final Patch p = new Patch(new Patch.Key(ps.getId(), name));
|
||||
history.add(p);
|
||||
byKey.put(p.getKey(), p);
|
||||
}
|
||||
|
||||
switch (changeType) {
|
||||
case ADDED:
|
||||
case MODIFIED:
|
||||
loadPublished(byKey, aic, newName);
|
||||
break;
|
||||
|
||||
case DELETED:
|
||||
loadPublished(byKey, aic, newName);
|
||||
break;
|
||||
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
if (psa != null) {
|
||||
loadPublished(byKey, aic, oldName);
|
||||
}
|
||||
loadPublished(byKey, aic, newName);
|
||||
break;
|
||||
|
||||
case REWRITE:
|
||||
break;
|
||||
}
|
||||
|
||||
final CurrentUser user = control.getCurrentUser();
|
||||
if (user instanceof IdentifiedUser) {
|
||||
final Account.Id me = ((IdentifiedUser) user).getAccountId();
|
||||
switch (changeType) {
|
||||
case ADDED:
|
||||
case MODIFIED:
|
||||
loadDrafts(byKey, aic, me, newName);
|
||||
break;
|
||||
|
||||
case DELETED:
|
||||
loadDrafts(byKey, aic, me, newName);
|
||||
break;
|
||||
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
if (psa != null) {
|
||||
loadDrafts(byKey, aic, me, oldName);
|
||||
}
|
||||
loadDrafts(byKey, aic, me, newName);
|
||||
break;
|
||||
|
||||
case REWRITE:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
comments.setAccountInfoCache(aic.create());
|
||||
}
|
||||
|
||||
private void loadPublished(final Map<Patch.Key, Patch> byKey,
|
||||
final AccountInfoCacheFactory aic, final String file) throws OrmException {
|
||||
for (PatchLineComment c : db.patchComments().publishedByChangeFile(changeId, file)) {
|
||||
if (comments.include(c)) {
|
||||
aic.want(c.getAuthor());
|
||||
}
|
||||
|
||||
final Patch.Key pKey = c.getKey().getParentKey();
|
||||
final Patch p = byKey.get(pKey);
|
||||
if (p != null) {
|
||||
p.setCommentCount(p.getCommentCount() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void loadDrafts(final Map<Patch.Key, Patch> byKey,
|
||||
final AccountInfoCacheFactory aic, final Account.Id me, final String file)
|
||||
throws OrmException {
|
||||
for (PatchLineComment c : db.patchComments().draftByChangeFileAuthor(changeId, file, me)) {
|
||||
if (comments.include(c)) {
|
||||
aic.want(me);
|
||||
}
|
||||
|
||||
final Patch.Key pKey = c.getKey().getParentKey();
|
||||
final Patch p = byKey.get(pKey);
|
||||
if (p != null) {
|
||||
p.setDraftCount(p.getDraftCount() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user