Revert "Move patch script class to the server tree."
This reverts commit 2c29e73e6b
.
Appears to have broken the acceptance test suite.
Change-Id: I7404548bce12d9cc60f9dc80fae44c9a9e2ffafe
This commit is contained in:
@@ -1,518 +0,0 @@
|
||||
// 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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,334 +0,0 @@
|
||||
// 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