Move Patch entity out of database and store only in cache
Rather than storing every Patch entity for all time we can store the
active Patch entities in a cache, and only keep the most recent ones
we need to load to present to the user. This dramatically shrinks
the database by removing redundant information, information that
we can quickly pull from Git when necessary, but that we can pull
out of Ehcache even faster.
Removing the patches table kills one of the largest sources of
bloat in our database, e.g. on one server:
changes | patch_sets | patches | comments
---------+------------+---------+----------
13,431 | 22,060 | 212,604 | 15,645
This also reduces the amount of information which might need to be
moved into a Git based datastore, as Gerrit can just as easily get
the data from the surrounding project repository on demand.
We no longer prime the cache when the commit is pushed and first
stored into the database, but instead wait for the first rendering
from a web client. This allows us to avoid unnecessary production
of diff entries, such as when we replace a patch set on a change
during a direct git push.
Change-Id: If4cf2f8779ad4c6f31433083849ae78806d3831c
Signed-off-by: Shawn O. Pearce <sop@google.com>
This commit is contained in:
@@ -15,10 +15,24 @@
|
||||
package com.google.gerrit.client.data;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.AccountGeneralPreferences;
|
||||
import com.google.gerrit.client.rpc.CodedEnum;
|
||||
|
||||
public class PatchScriptSettings {
|
||||
public static enum Whitespace {
|
||||
IGNORE_NONE, IGNORE_SPACE_AT_EOL, IGNORE_SPACE_CHANGE, IGNORE_ALL_SPACE;
|
||||
public static enum Whitespace implements CodedEnum {
|
||||
IGNORE_NONE('N'), //
|
||||
IGNORE_SPACE_AT_EOL('E'), //
|
||||
IGNORE_SPACE_CHANGE('S'), //
|
||||
IGNORE_ALL_SPACE('A');
|
||||
|
||||
private final char code;
|
||||
|
||||
private Whitespace(final char c) {
|
||||
code = c;
|
||||
}
|
||||
|
||||
public char getCode() {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
protected int context;
|
||||
|
||||
@@ -37,9 +37,7 @@ import java.sql.Timestamp;
|
||||
* |
|
||||
* +- {@link PatchSetAncestor}: parents of this change's commit.
|
||||
* |
|
||||
* +- {@link Patch}: one file path modified by the change.
|
||||
* |
|
||||
* +- {@link PatchLineComment}: comment about a specific line
|
||||
* +- {@link PatchLineComment}: comment about a specific line
|
||||
* </pre>
|
||||
* <p>
|
||||
* <h5>PatchSets</h5>
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
|
||||
package com.google.gerrit.client.reviewdb;
|
||||
|
||||
import com.google.gerrit.client.rpc.CodedEnum;
|
||||
import com.google.gwtorm.client.Column;
|
||||
import com.google.gwtorm.client.StringKey;
|
||||
|
||||
@@ -65,7 +66,7 @@ public final class Patch {
|
||||
}
|
||||
|
||||
/** Type of modification made to the file path. */
|
||||
public static enum ChangeType {
|
||||
public static enum ChangeType implements CodedEnum {
|
||||
/** Path is being created/introduced by this patch. */
|
||||
ADDED('A'),
|
||||
|
||||
@@ -102,17 +103,17 @@ public final class Patch {
|
||||
}
|
||||
|
||||
/** Type of formatting for this patch. */
|
||||
public static enum PatchType {
|
||||
public static enum PatchType implements CodedEnum {
|
||||
/**
|
||||
* A textual difference between two versions.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* A UNIFIED patch can be rendered in multiple ways. Most commonly, it is
|
||||
* rendered as a side by side display using two columns, left column for the
|
||||
* old version, right column for the new version. A UNIFIED patch can also
|
||||
* be formatted in a number of standard "patch script" styles, but typically
|
||||
* is formatted in the POSIX standard unified diff format.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Usually Gerrit renders a UNIFIED patch in a
|
||||
* {@link com.google.gerrit.client.patches.PatchScreen.SideBySide} view,
|
||||
@@ -124,12 +125,12 @@ public final class Patch {
|
||||
|
||||
/**
|
||||
* Difference of two (or more) binary contents.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* A BINARY patch cannot be viewed in a text display, as it represents a
|
||||
* change in binary content at the associated path, for example, an image
|
||||
* file has been replaced with a different image.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Gerrit can only render a BINARY file in a
|
||||
* {@link com.google.gerrit.client.patches.PatchScreen.Unified} view, as the
|
||||
@@ -139,17 +140,17 @@ public final class Patch {
|
||||
|
||||
/**
|
||||
* Difference of three or more textual contents.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Git can produce an n-way unified diff, showing how a merge conflict was
|
||||
* resolved when two or more conflicting branches were merged together in a
|
||||
* single merge commit.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* This type of patch can only appear if there are two or more
|
||||
* {@link PatchSetAncestor} entities for the same parent {@link PatchSet},
|
||||
* as that denotes that the patch set is a merge commit.
|
||||
*
|
||||
*
|
||||
* <p>
|
||||
* Gerrit can only render an N_WAY file in a
|
||||
* {@link com.google.gerrit.client.patches.PatchScreen.Unified} view, as it
|
||||
@@ -180,19 +181,15 @@ public final class Patch {
|
||||
}
|
||||
}
|
||||
|
||||
@Column(name = Column.NONE)
|
||||
protected Key key;
|
||||
|
||||
/** What sort of change is this to the path; see {@link ChangeType}. */
|
||||
@Column
|
||||
protected char changeType;
|
||||
|
||||
/** What type of patch is this; see {@link PatchType}. */
|
||||
@Column
|
||||
protected char patchType;
|
||||
|
||||
/** Number of published comments on this patch. */
|
||||
@Column
|
||||
protected int nbrComments;
|
||||
|
||||
/** Number of drafts by the current user; not persisted in the datastore. */
|
||||
@@ -202,7 +199,6 @@ public final class Patch {
|
||||
* Original if {@link #changeType} is {@link ChangeType#COPIED} or
|
||||
* {@link ChangeType#RENAMED}.
|
||||
*/
|
||||
@Column(notNull = false)
|
||||
protected String sourceFileName;
|
||||
|
||||
/** True if this patch has been reviewed by the current logged in user */
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
// 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.client.reviewdb;
|
||||
|
||||
import com.google.gwtorm.client.Access;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.PrimaryKey;
|
||||
import com.google.gwtorm.client.Query;
|
||||
import com.google.gwtorm.client.ResultSet;
|
||||
|
||||
public interface PatchAccess extends Access<Patch, Patch.Key> {
|
||||
@PrimaryKey("key")
|
||||
Patch get(Patch.Key id) throws OrmException;
|
||||
|
||||
@Query("WHERE key.patchSetId = ? ORDER BY key.fileName")
|
||||
ResultSet<Patch> byPatchSet(PatchSet.Id ps) throws OrmException;
|
||||
|
||||
@Query("WHERE key.patchSetId.changeId = ?"
|
||||
+ " AND key.fileName = ? ORDER BY key.patchSetId")
|
||||
ResultSet<Patch> history(Change.Id c, String fileName) throws OrmException;
|
||||
}
|
||||
@@ -35,6 +35,11 @@ public interface PatchLineCommentAccess extends
|
||||
ResultSet<PatchLineComment> published(Change.Id id, String file)
|
||||
throws OrmException;
|
||||
|
||||
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
|
||||
+ PatchLineComment.STATUS_PUBLISHED + "'")
|
||||
ResultSet<PatchLineComment> published(PatchSet.Id patchset)
|
||||
throws OrmException;
|
||||
|
||||
@Query("WHERE key.patchKey.patchSetId = ? AND status = '"
|
||||
+ PatchLineComment.STATUS_DRAFT
|
||||
+ "' AND author = ? ORDER BY key.patchKey,lineNbr,writtenOn")
|
||||
|
||||
@@ -31,7 +31,7 @@ import com.google.gwtorm.client.Sequence;
|
||||
* </ul>
|
||||
*/
|
||||
public interface ReviewDb extends Schema {
|
||||
public static final int VERSION = 17;
|
||||
public static final int VERSION = 18;
|
||||
|
||||
@Relation
|
||||
SchemaVersionAccess schemaVersion();
|
||||
@@ -105,9 +105,6 @@ public interface ReviewDb extends Schema {
|
||||
@Relation
|
||||
PatchSetAncestorAccess patchSetAncestors();
|
||||
|
||||
@Relation
|
||||
PatchAccess patches();
|
||||
|
||||
@Relation
|
||||
PatchLineCommentAccess patchComments();
|
||||
|
||||
|
||||
20
src/main/java/com/google/gerrit/client/rpc/CodedEnum.java
Normal file
20
src/main/java/com/google/gerrit/client/rpc/CodedEnum.java
Normal file
@@ -0,0 +1,20 @@
|
||||
// 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.client.rpc;
|
||||
|
||||
/** Extension of Enum which provides distinct character code values. */
|
||||
public interface CodedEnum {
|
||||
char getCode();
|
||||
}
|
||||
@@ -14,18 +14,11 @@
|
||||
|
||||
package com.google.gerrit.git;
|
||||
|
||||
import static com.google.gerrit.client.data.PatchScriptSettings.Whitespace.IGNORE_NONE;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.client.reviewdb.PatchSetAncestor;
|
||||
import com.google.gerrit.client.reviewdb.PatchSetInfo;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.client.reviewdb.RevId;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.server.patch.DiffCache;
|
||||
import com.google.gerrit.server.patch.DiffCacheContent;
|
||||
import com.google.gerrit.server.patch.DiffCacheKey;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.Transaction;
|
||||
@@ -33,14 +26,8 @@ import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.spearce.jgit.lib.Commit;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.ObjectWriter;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
import org.spearce.jgit.patch.CombinedFileHeader;
|
||||
import org.spearce.jgit.patch.FileHeader;
|
||||
import org.spearce.jgit.revwalk.RevCommit;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -50,28 +37,19 @@ import java.util.Map;
|
||||
/** Imports a {@link PatchSet} from a {@link Commit}. */
|
||||
public class PatchSetImporter {
|
||||
public interface Factory {
|
||||
PatchSetImporter create(ReviewDb dstDb, Project.NameKey proj,
|
||||
Repository srcRepo, RevCommit srcCommit, PatchSet dstPatchSet,
|
||||
boolean isNewPatchSet);
|
||||
PatchSetImporter create(ReviewDb dstDb, RevCommit srcCommit,
|
||||
PatchSet dstPatchSet, boolean isNewPatchSet);
|
||||
}
|
||||
|
||||
private final DiffCache diffCache;
|
||||
private final PatchSetInfoFactory patchSetInfoFactory;
|
||||
private final ReviewDb db;
|
||||
private final Project.NameKey projectKey;
|
||||
private final Repository repo;
|
||||
private final RevCommit src;
|
||||
private final PatchSet dst;
|
||||
private final boolean isNew;
|
||||
private Transaction txn;
|
||||
private org.spearce.jgit.patch.Patch gitpatch;
|
||||
|
||||
private PatchSetInfo info;
|
||||
|
||||
private final Map<String, Patch> patchExisting = new HashMap<String, Patch>();
|
||||
private final List<Patch> patchInsert = new ArrayList<Patch>();
|
||||
private final List<Patch> patchUpdate = new ArrayList<Patch>();
|
||||
|
||||
private final Map<Integer, PatchSetAncestor> ancestorExisting =
|
||||
new HashMap<Integer, PatchSetAncestor>();
|
||||
private final List<PatchSetAncestor> ancestorInsert =
|
||||
@@ -80,16 +58,12 @@ public class PatchSetImporter {
|
||||
new ArrayList<PatchSetAncestor>();
|
||||
|
||||
@Inject
|
||||
PatchSetImporter(final DiffCache dc, final PatchSetInfoFactory psif,
|
||||
@Assisted final ReviewDb dstDb, @Assisted final Project.NameKey proj,
|
||||
@Assisted final Repository srcRepo, @Assisted final RevCommit srcCommit,
|
||||
PatchSetImporter(final PatchSetInfoFactory psif,
|
||||
@Assisted final ReviewDb dstDb, @Assisted final RevCommit srcCommit,
|
||||
@Assisted final PatchSet dstPatchSet,
|
||||
@Assisted final boolean isNewPatchSet) {
|
||||
diffCache = dc;
|
||||
patchSetInfoFactory = psif;
|
||||
db = dstDb;
|
||||
projectKey = proj;
|
||||
repo = srcRepo;
|
||||
src = srcCommit;
|
||||
dst = dstPatchSet;
|
||||
isNew = isNewPatchSet;
|
||||
@@ -103,18 +77,10 @@ public class PatchSetImporter {
|
||||
return info;
|
||||
}
|
||||
|
||||
public void run() throws IOException, OrmException {
|
||||
gitpatch = readGitPatch();
|
||||
|
||||
public void run() throws OrmException {
|
||||
dst.setRevision(toRevId(src));
|
||||
|
||||
if (!isNew) {
|
||||
// If we aren't a new patch set then we need to load the existing
|
||||
// files so we can update or delete them if there are corrections.
|
||||
//
|
||||
for (final Patch p : db.patches().byPatchSet(dst.getId())) {
|
||||
patchExisting.put(p.getFileName(), p);
|
||||
}
|
||||
for (final PatchSetAncestor a : db.patchSetAncestors().ancestorsOf(
|
||||
dst.getId())) {
|
||||
ancestorExisting.put(a.getPosition(), a);
|
||||
@@ -123,9 +89,6 @@ public class PatchSetImporter {
|
||||
|
||||
info = patchSetInfoFactory.get(src, dst.getId());
|
||||
importAncestors();
|
||||
for (final FileHeader fh : gitpatch.getFiles()) {
|
||||
importFile(fh);
|
||||
}
|
||||
|
||||
final boolean auto = txn == null;
|
||||
if (auto) {
|
||||
@@ -134,12 +97,8 @@ public class PatchSetImporter {
|
||||
if (isNew) {
|
||||
db.patchSets().insert(Collections.singleton(dst), txn);
|
||||
}
|
||||
db.patches().insert(patchInsert, txn);
|
||||
db.patchSetAncestors().insert(ancestorInsert, txn);
|
||||
if (!isNew) {
|
||||
db.patches().update(patchUpdate, txn);
|
||||
db.patches().delete(patchExisting.values(), txn);
|
||||
|
||||
db.patchSetAncestors().update(ancestorUpdate, txn);
|
||||
db.patchSetAncestors().delete(ancestorExisting.values(), txn);
|
||||
}
|
||||
@@ -162,145 +121,7 @@ public class PatchSetImporter {
|
||||
}
|
||||
}
|
||||
|
||||
private void importFile(final FileHeader fh) {
|
||||
final String path;
|
||||
if (fh.getChangeType() == FileHeader.ChangeType.DELETE) {
|
||||
path = fh.getOldName();
|
||||
} else {
|
||||
path = fh.getNewName();
|
||||
}
|
||||
|
||||
Patch p = patchExisting.remove(path);
|
||||
if (p == null) {
|
||||
p = new Patch(new Patch.Key(dst.getId(), path));
|
||||
patchInsert.add(p);
|
||||
} else {
|
||||
p.setSourceFileName(null);
|
||||
patchUpdate.add(p);
|
||||
}
|
||||
|
||||
// Convert the ChangeType
|
||||
//
|
||||
if (fh.getChangeType() == FileHeader.ChangeType.ADD) {
|
||||
p.setChangeType(Patch.ChangeType.ADDED);
|
||||
|
||||
} else if (fh.getChangeType() == FileHeader.ChangeType.MODIFY) {
|
||||
p.setChangeType(Patch.ChangeType.MODIFIED);
|
||||
|
||||
} else if (fh.getChangeType() == FileHeader.ChangeType.DELETE) {
|
||||
p.setChangeType(Patch.ChangeType.DELETED);
|
||||
|
||||
} else if (fh.getChangeType() == FileHeader.ChangeType.RENAME) {
|
||||
p.setChangeType(Patch.ChangeType.RENAMED);
|
||||
p.setSourceFileName(fh.getOldName());
|
||||
|
||||
} else if (fh.getChangeType() == FileHeader.ChangeType.COPY) {
|
||||
p.setChangeType(Patch.ChangeType.COPIED);
|
||||
p.setSourceFileName(fh.getOldName());
|
||||
}
|
||||
|
||||
// Convert the PatchType
|
||||
//
|
||||
if (fh instanceof CombinedFileHeader) {
|
||||
p.setPatchType(Patch.PatchType.N_WAY);
|
||||
|
||||
} else if (fh.getPatchType() == FileHeader.PatchType.GIT_BINARY) {
|
||||
p.setPatchType(Patch.PatchType.BINARY);
|
||||
|
||||
} else if (fh.getPatchType() == FileHeader.PatchType.BINARY) {
|
||||
p.setPatchType(Patch.PatchType.BINARY);
|
||||
}
|
||||
|
||||
if (p.getPatchType() != Patch.PatchType.BINARY) {
|
||||
final byte[] buf = fh.getBuffer();
|
||||
for (int ptr = fh.getStartOffset(); ptr < fh.getEndOffset(); ptr++) {
|
||||
if (buf[ptr] == '\0') {
|
||||
// Its really binary, but Git couldn't see the nul early enough
|
||||
// to realize its binary, and instead produced the diff.
|
||||
//
|
||||
// Force it to be a binary; it really should have been that.
|
||||
//
|
||||
p.setPatchType(Patch.PatchType.BINARY);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static RevId toRevId(final RevCommit src) {
|
||||
return new RevId(src.getId().name());
|
||||
}
|
||||
|
||||
private org.spearce.jgit.patch.Patch readGitPatch() throws IOException {
|
||||
final List<String> args = new ArrayList<String>();
|
||||
args.add("git");
|
||||
args.add("--git-dir=.");
|
||||
args.add("diff-tree");
|
||||
args.add("-M");
|
||||
args.add("--full-index");
|
||||
|
||||
final ObjectId a, b;
|
||||
switch (src.getParentCount()) {
|
||||
case 0:
|
||||
args.add("--unified=1");
|
||||
a = emptyTree();
|
||||
b = src.getId();
|
||||
break;
|
||||
case 1:
|
||||
args.add("--unified=1");
|
||||
a = src.getParent(0).getId();
|
||||
b = src.getId();
|
||||
break;
|
||||
default:
|
||||
args.add("--cc");
|
||||
a = null;
|
||||
b = src.getId();
|
||||
break;
|
||||
}
|
||||
if (a != null) {
|
||||
args.add(a.name());
|
||||
}
|
||||
args.add(b.name());
|
||||
|
||||
final Process proc =
|
||||
Runtime.getRuntime().exec(args.toArray(new String[args.size()]), null,
|
||||
repo.getDirectory());
|
||||
final org.spearce.jgit.patch.Patch p;
|
||||
try {
|
||||
p = new org.spearce.jgit.patch.Patch();
|
||||
proc.getOutputStream().close();
|
||||
proc.getErrorStream().close();
|
||||
p.parse(proc.getInputStream());
|
||||
proc.getInputStream().close();
|
||||
} finally {
|
||||
try {
|
||||
if (proc.waitFor() != 0) {
|
||||
throw new IOException("git diff-tree exited abnormally");
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
for (final FileHeader fh : p.getFiles()) {
|
||||
final DiffCacheKey k;
|
||||
|
||||
String srcName = null;
|
||||
switch (fh.getChangeType()) {
|
||||
case RENAME:
|
||||
case COPY:
|
||||
srcName = fh.getOldName();
|
||||
break;
|
||||
}
|
||||
|
||||
final String newName = fh.getNewName();
|
||||
k = new DiffCacheKey(projectKey, a, b, newName, srcName, IGNORE_NONE);
|
||||
diffCache.put(k, DiffCacheContent.create(fh));
|
||||
}
|
||||
|
||||
return p;
|
||||
}
|
||||
|
||||
private ObjectId emptyTree() throws IOException {
|
||||
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,138 +0,0 @@
|
||||
// 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.pgm;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
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.git.PatchSetImporter;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.cache.CachePool;
|
||||
import com.google.gerrit.server.config.GerritGlobalModule;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.SchemaFactory;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Injector;
|
||||
|
||||
import org.spearce.jgit.errors.RepositoryNotFoundException;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.ProgressMonitor;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
import org.spearce.jgit.lib.TextProgressMonitor;
|
||||
import org.spearce.jgit.revwalk.RevCommit;
|
||||
import org.spearce.jgit.revwalk.RevWalk;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
|
||||
/**
|
||||
* Recreates PatchSet and Patch entities for the changes supplied.
|
||||
* <p>
|
||||
* Takes on input strings of the form <code>change_id|patch_set_id</code> or
|
||||
* <code>change_id,patch_set_id</code>, such as might be created by the
|
||||
* following PostgreSQL database dump:
|
||||
*
|
||||
* <pre>
|
||||
* psql reviewdb -tAc 'select change_id,patch_set_id from patch_sets'
|
||||
* </pre>
|
||||
* <p>
|
||||
* For each supplied PatchSet the info and patch entities are completely updated
|
||||
* based on the data stored in Git.
|
||||
*/
|
||||
public class ReimportPatchSets extends AbstractProgram {
|
||||
@Inject
|
||||
private SchemaFactory<ReviewDb> schema;
|
||||
|
||||
@Inject
|
||||
private GerritServer gs;
|
||||
|
||||
@Inject
|
||||
private PatchSetImporter.Factory factory;
|
||||
|
||||
@Override
|
||||
public int run() throws Exception {
|
||||
final Injector injector = GerritGlobalModule.createInjector();
|
||||
injector.getInstance(CachePool.class).start();
|
||||
injector.injectMembers(this);
|
||||
|
||||
final ArrayList<PatchSet.Id> todo = new ArrayList<PatchSet.Id>();
|
||||
final BufferedReader br =
|
||||
new BufferedReader(new InputStreamReader(System.in));
|
||||
String line;
|
||||
while ((line = br.readLine()) != null) {
|
||||
todo.add(PatchSet.Id.parse(line.replace('|', ',')));
|
||||
}
|
||||
|
||||
int exitStatus = 0;
|
||||
final ReviewDb db = schema.open();
|
||||
final ProgressMonitor pm = new TextProgressMonitor();
|
||||
try {
|
||||
pm.start(1);
|
||||
pm.beginTask("Import patch sets", todo.size());
|
||||
for (int i = 0; i < todo.size(); i++) {
|
||||
final PatchSet.Id psid = todo.get(i);
|
||||
final PatchSet ps = db.patchSets().get(psid);
|
||||
if (ps == null) {
|
||||
System.err.println();
|
||||
System.err.println("NotFound " + psid);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Change c = db.changes().get(ps.getId().getParentKey());
|
||||
if (c == null) {
|
||||
System.err.println();
|
||||
System.err.println("Orphan " + psid);
|
||||
continue;
|
||||
}
|
||||
|
||||
final Project.NameKey projectKey = c.getProject();
|
||||
final String projectName = projectKey.get();
|
||||
final Repository repo;
|
||||
try {
|
||||
repo = gs.openRepository(projectName);
|
||||
} catch (RepositoryNotFoundException ie) {
|
||||
System.err.println();
|
||||
System.err.println("NoProject " + psid);
|
||||
System.err.println("NoProject " + ie.getMessage());
|
||||
continue;
|
||||
}
|
||||
|
||||
final RevWalk rw = new RevWalk(repo);
|
||||
final RevCommit src =
|
||||
rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
|
||||
factory.create(db, projectKey, repo, src, ps, false).run();
|
||||
pm.update(1);
|
||||
repo.close();
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
System.err.println();
|
||||
e.printStackTrace();
|
||||
if (e.getCause() instanceof SQLException) {
|
||||
final SQLException e2 = (SQLException) e.getCause();
|
||||
if (e2.getNextException() != null) {
|
||||
e2.getNextException().printStackTrace();
|
||||
}
|
||||
}
|
||||
exitStatus = 1;
|
||||
} finally {
|
||||
pm.endTask();
|
||||
db.close();
|
||||
}
|
||||
return exitStatus;
|
||||
}
|
||||
}
|
||||
@@ -52,7 +52,7 @@ import com.google.gerrit.server.mail.MergedSender;
|
||||
import com.google.gerrit.server.mail.RegisterNewEmailSender;
|
||||
import com.google.gerrit.server.mail.ReplacePatchSetSender;
|
||||
import com.google.gerrit.server.mail.SmtpEmailSender;
|
||||
import com.google.gerrit.server.patch.DiffCache;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
import com.google.gerrit.server.ssh.SshKeyCache;
|
||||
@@ -121,8 +121,8 @@ public class GerritGlobalModule extends FactoryModule {
|
||||
bind(CachePool.class);
|
||||
install(AccountByEmailCache.module());
|
||||
install(AccountCache.module());
|
||||
install(DiffCache.module());
|
||||
install(GroupCache.module());
|
||||
install(PatchListCache.module());
|
||||
install(ProjectCache.module());
|
||||
install(SshKeyCache.module());
|
||||
|
||||
|
||||
@@ -0,0 +1,161 @@
|
||||
// Copyright 2008 Google Inc. All rights reserved.
|
||||
// http://code.google.com/p/protobuf/
|
||||
//
|
||||
// Redistribution and use in source and binary forms, with or without
|
||||
// modification, are permitted provided that the following conditions are
|
||||
// met:
|
||||
//
|
||||
// * Redistributions of source code must retain the above copyright
|
||||
// notice, this list of conditions and the following disclaimer.
|
||||
// * Redistributions in binary form must reproduce the above
|
||||
// copyright notice, this list of conditions and the following disclaimer
|
||||
// in the documentation and/or other materials provided with the
|
||||
// distribution.
|
||||
// * Neither the name of Google Inc. nor the names of its
|
||||
// contributors may be used to endorse or promote products derived from
|
||||
// this software without specific prior written permission.
|
||||
//
|
||||
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
package com.google.gerrit.server.ioutil;
|
||||
|
||||
import com.google.gerrit.client.rpc.CodedEnum;
|
||||
|
||||
import org.spearce.jgit.util.NB;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class BasicSerialization {
|
||||
private static final byte[] NO_BYTES = {};
|
||||
|
||||
private static int safeRead(final InputStream input) throws IOException {
|
||||
final int b = input.read();
|
||||
if (b == -1) {
|
||||
throw new EOFException();
|
||||
}
|
||||
return b;
|
||||
}
|
||||
|
||||
/** Read a fixed-width 32 bit integer in network byte order (big-endian). */
|
||||
public static int readFixInt32(final InputStream input) throws IOException {
|
||||
final int b1 = safeRead(input);
|
||||
final int b2 = safeRead(input);
|
||||
final int b3 = safeRead(input);
|
||||
final int b4 = safeRead(input);
|
||||
return (b1 << 24) | (b2 << 16) | (b3 << 8) | b4;
|
||||
}
|
||||
|
||||
/** Write a fixed-width 32 bit integer in network byte order (big-endian). */
|
||||
public static void writeFixInt32(final OutputStream output, final int val)
|
||||
throws IOException {
|
||||
output.write((val >>> 24) & 0xFF);
|
||||
output.write((val >>> 16) & 0xFF);
|
||||
output.write((val >>> 8) & 0xFF);
|
||||
output.write(val & 0xFF);
|
||||
}
|
||||
|
||||
/** Read a varint from the input, one byte at a time. */
|
||||
public static int readVarInt32(final InputStream input) throws IOException {
|
||||
int result = 0;
|
||||
int offset = 0;
|
||||
for (; offset < 32; offset += 7) {
|
||||
final int b = safeRead(input);
|
||||
result |= (b & 0x7f) << offset;
|
||||
if ((b & 0x80) == 0) {
|
||||
return result;
|
||||
}
|
||||
}
|
||||
throw new EOFException();
|
||||
}
|
||||
|
||||
/** Write a varint; value is treated as an unsigned value. */
|
||||
public static void writeVarInt32(final OutputStream output, int value)
|
||||
throws IOException {
|
||||
while (true) {
|
||||
if ((value & ~0x7F) == 0) {
|
||||
output.write(value);
|
||||
return;
|
||||
} else {
|
||||
output.write((value & 0x7F) | 0x80);
|
||||
value >>>= 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Read a fixed length byte array whose length is specified as a varint. */
|
||||
public static byte[] readBytes(final InputStream input) throws IOException {
|
||||
final int len = readVarInt32(input);
|
||||
if (len == 0) {
|
||||
return NO_BYTES;
|
||||
}
|
||||
final byte[] buf = new byte[len];
|
||||
NB.readFully(input, buf, 0, len);
|
||||
return buf;
|
||||
}
|
||||
|
||||
/** Write a byte array prefixed by its length in a varint. */
|
||||
public static void writeBytes(final OutputStream output, final byte[] data)
|
||||
throws IOException {
|
||||
writeBytes(output, data, 0, data.length);
|
||||
}
|
||||
|
||||
/** Write a byte array prefixed by its length in a varint. */
|
||||
public static void writeBytes(final OutputStream output, final byte[] data,
|
||||
final int offset, final int len) throws IOException {
|
||||
writeVarInt32(output, len);
|
||||
output.write(data, offset, len);
|
||||
}
|
||||
|
||||
/** Read a UTF-8 string, prefixed by its byte length in a varint. */
|
||||
public static String readString(final InputStream input) throws IOException {
|
||||
final byte[] bin = readBytes(input);
|
||||
if (bin.length == 0) {
|
||||
return null;
|
||||
}
|
||||
return new String(bin, 0, bin.length, "UTF-8");
|
||||
}
|
||||
|
||||
/** Write a UTF-8 string, prefixed by its byte length in a varint. */
|
||||
public static void writeString(final OutputStream output, final String s)
|
||||
throws IOException {
|
||||
if (s == null) {
|
||||
writeVarInt32(output, 0);
|
||||
} else {
|
||||
writeBytes(output, s.getBytes("UTF-8"));
|
||||
}
|
||||
}
|
||||
|
||||
/** Read an enum whose code is stored as a varint. */
|
||||
public static <T extends CodedEnum> T readEnum(final InputStream input,
|
||||
final T[] all) throws IOException {
|
||||
final int val = readVarInt32(input);
|
||||
for (T t : all) {
|
||||
if (t.getCode() == val) {
|
||||
return t;
|
||||
}
|
||||
}
|
||||
throw new IOException("Invalid enum " + val + " for " + all[0].getClass());
|
||||
}
|
||||
|
||||
/** Write an enum whose code is stored as a varint. */
|
||||
public static <T extends CodedEnum> void writeEnum(final OutputStream output,
|
||||
final T e) throws IOException {
|
||||
writeVarInt32(output, e.getCode());
|
||||
}
|
||||
|
||||
private BasicSerialization() {
|
||||
}
|
||||
}
|
||||
@@ -17,18 +17,17 @@ package com.google.gerrit.server.mail;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchLineComment;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.server.patch.PatchFile;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import org.spearce.jgit.errors.RepositoryNotFoundException;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/** Send comments, after the author of them hit used Publish Comments in the UI. */
|
||||
public class CommentSender extends ReplyToChangeSender {
|
||||
@@ -71,64 +70,55 @@ public class CommentSender extends ReplyToChangeSender {
|
||||
}
|
||||
|
||||
private void formatInlineComments() {
|
||||
final Map<Patch.Key, Patch> patches = getPatchMap();
|
||||
final Repository repo = getRepository();
|
||||
|
||||
Patch.Key currentFileKey = null;
|
||||
PatchFile currentFileData = null;
|
||||
for (final PatchLineComment c : inlineComments) {
|
||||
final Patch.Key pk = c.getKey().getParentKey();
|
||||
final int lineNbr = c.getLine();
|
||||
final short side = c.getSide();
|
||||
|
||||
if (!pk.equals(currentFileKey)) {
|
||||
appendText("....................................................\n");
|
||||
appendText("File ");
|
||||
appendText(pk.get());
|
||||
appendText("\n");
|
||||
currentFileKey = pk;
|
||||
|
||||
final Patch p = patches.get(pk);
|
||||
if (p != null && repo != null) {
|
||||
try {
|
||||
currentFileData = new PatchFile(repo, patchSet.getRevision(), p);
|
||||
} catch (Throwable e) {
|
||||
// Don't quote the line if we can't load it.
|
||||
}
|
||||
} else {
|
||||
currentFileData = null;
|
||||
}
|
||||
}
|
||||
|
||||
appendText("Line " + lineNbr);
|
||||
if (currentFileData != null) {
|
||||
try {
|
||||
final String lineStr = currentFileData.getLine(side, lineNbr);
|
||||
appendText(": ");
|
||||
appendText(lineStr);
|
||||
} catch (Throwable cce) {
|
||||
// Don't quote the line if we can't safely convert it.
|
||||
}
|
||||
}
|
||||
appendText("\n");
|
||||
|
||||
appendText(c.getMessage().trim());
|
||||
appendText("\n\n");
|
||||
}
|
||||
|
||||
if (repo != null) {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
|
||||
private Map<Patch.Key, Patch> getPatchMap() {
|
||||
try {
|
||||
final PatchSet.Id psId = patchSet.getId();
|
||||
return db.patches().toMap(db.patches().byPatchSet(psId));
|
||||
} catch (OrmException e) {
|
||||
// Can't read the patch table? Don't quote file lines.
|
||||
//
|
||||
return Collections.emptyMap();
|
||||
final PatchList patchList = repo != null ? getPatchList() : null;
|
||||
|
||||
Patch.Key currentFileKey = null;
|
||||
PatchFile currentFileData = null;
|
||||
for (final PatchLineComment c : inlineComments) {
|
||||
final Patch.Key pk = c.getKey().getParentKey();
|
||||
final int lineNbr = c.getLine();
|
||||
final short side = c.getSide();
|
||||
|
||||
if (!pk.equals(currentFileKey)) {
|
||||
appendText("....................................................\n");
|
||||
appendText("File ");
|
||||
appendText(pk.get());
|
||||
appendText("\n");
|
||||
currentFileKey = pk;
|
||||
|
||||
if (patchList != null) {
|
||||
try {
|
||||
currentFileData =
|
||||
new PatchFile(repo, patchList, pk.getFileName());
|
||||
} catch (IOException e) {
|
||||
// Don't quote the line if we can't load it.
|
||||
}
|
||||
} else {
|
||||
currentFileData = null;
|
||||
}
|
||||
}
|
||||
|
||||
appendText("Line " + lineNbr);
|
||||
if (currentFileData != null) {
|
||||
try {
|
||||
final String lineStr = currentFileData.getLine(side, lineNbr);
|
||||
appendText(": ");
|
||||
appendText(lineStr);
|
||||
} catch (Throwable cce) {
|
||||
// Don't quote the line if we can't safely convert it.
|
||||
}
|
||||
}
|
||||
appendText("\n");
|
||||
|
||||
appendText(c.getMessage().trim());
|
||||
appendText("\n\n");
|
||||
}
|
||||
} finally {
|
||||
if (repo != null) {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,10 +18,9 @@ import com.google.gerrit.client.reviewdb.Account;
|
||||
import com.google.gerrit.client.reviewdb.AccountGroup;
|
||||
import com.google.gerrit.client.reviewdb.AccountProjectWatch;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
import com.google.gerrit.client.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.client.reviewdb.ChangeMessage;
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.client.reviewdb.PatchSetApproval;
|
||||
import com.google.gerrit.client.reviewdb.PatchSetInfo;
|
||||
import com.google.gerrit.client.reviewdb.ReviewDb;
|
||||
import com.google.gerrit.client.reviewdb.StarredChange;
|
||||
@@ -31,6 +30,9 @@ import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountCache;
|
||||
import com.google.gerrit.server.config.CanonicalWebUrl;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.patch.PatchListEntry;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ProjectCache;
|
||||
@@ -86,6 +88,9 @@ public abstract class OutgoingEmail {
|
||||
@Inject
|
||||
private AccountCache accountCache;
|
||||
|
||||
@Inject
|
||||
private PatchListCache patchListCache;
|
||||
|
||||
@Inject
|
||||
private EmailSender emailSender;
|
||||
|
||||
@@ -418,20 +423,23 @@ public abstract class OutgoingEmail {
|
||||
appendText("\n");
|
||||
}
|
||||
|
||||
if (db != null && patchSet != null) {
|
||||
if (patchSet != null) {
|
||||
appendText("---\n");
|
||||
try {
|
||||
for (Patch p : db.patches().byPatchSet(patchSet.getId())) {
|
||||
appendText(p.getChangeType().getCode() + " " + p.getFileName() + "\n");
|
||||
}
|
||||
} catch (OrmException e) {
|
||||
// Don't bother including the files if we get a failure,
|
||||
// ensure we at least send the notification message.
|
||||
for (PatchListEntry p : getPatchList().getPatches()) {
|
||||
appendText(p.getChangeType().getCode() + " " + p.getNewName() + "\n");
|
||||
}
|
||||
appendText("\n");
|
||||
}
|
||||
}
|
||||
|
||||
/** Get the patch list corresponding to this patch set. */
|
||||
protected PatchList getPatchList() {
|
||||
if (patchSet != null) {
|
||||
return patchListCache.get(change, patchSet);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/** Lookup a human readable name for an account, usually the "full name". */
|
||||
protected String getNameFor(final Account.Id accountId) {
|
||||
if (accountId == null) {
|
||||
|
||||
@@ -1,67 +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.server.GerritServer;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
|
||||
/** Provides the {@link DiffCacheContent}. */
|
||||
@Singleton
|
||||
public class DiffCache {
|
||||
private static final String CACHE_NAME = "diff";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<DiffCacheKey, DiffCacheContent>> type =
|
||||
new TypeLiteral<Cache<DiffCacheKey, DiffCacheContent>>() {};
|
||||
disk(type, CACHE_NAME);
|
||||
bind(DiffCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final SelfPopulatingCache<DiffCacheKey, DiffCacheContent> self;
|
||||
|
||||
@Inject
|
||||
DiffCache(final GerritServer gs,
|
||||
@Named(CACHE_NAME) final Cache<DiffCacheKey, DiffCacheContent> raw) {
|
||||
final DiffCacheEntryFactory f = new DiffCacheEntryFactory(gs);
|
||||
self = new SelfPopulatingCache<DiffCacheKey, DiffCacheContent>(raw) {
|
||||
@Override
|
||||
protected DiffCacheContent createEntry(final DiffCacheKey key)
|
||||
throws Exception {
|
||||
return f.createEntry(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public DiffCacheContent get(final DiffCacheKey key) {
|
||||
return self.get(key);
|
||||
}
|
||||
|
||||
public void put(final DiffCacheKey k, final DiffCacheContent c) {
|
||||
self.put(k, c);
|
||||
}
|
||||
}
|
||||
@@ -1,144 +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 org.spearce.jgit.diff.Edit;
|
||||
import org.spearce.jgit.lib.FileMode;
|
||||
import org.spearce.jgit.patch.CombinedFileHeader;
|
||||
import org.spearce.jgit.patch.FileHeader;
|
||||
import org.spearce.jgit.patch.Patch;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public final class DiffCacheContent implements Serializable {
|
||||
// Note: If we modify our version, also modify DiffCacheKey, so
|
||||
// the on disk cache is fully destroyed and recreated when the
|
||||
// schema has changed.
|
||||
//
|
||||
private static final long serialVersionUID = DiffCacheKey.serialVersionUID;
|
||||
|
||||
public static DiffCacheContent create(final FileHeader file) {
|
||||
return new DiffCacheContent(file);
|
||||
}
|
||||
|
||||
public static DiffCacheContent createEmpty() {
|
||||
return new DiffCacheContent();
|
||||
}
|
||||
|
||||
private transient FileHeader header;
|
||||
private transient List<Edit> edits;
|
||||
|
||||
private DiffCacheContent() {
|
||||
header = null;
|
||||
edits = Collections.emptyList();
|
||||
}
|
||||
|
||||
private DiffCacheContent(final FileHeader h) {
|
||||
header = compact(h);
|
||||
|
||||
if (h == null || h instanceof CombinedFileHeader || h.getHunks().isEmpty()
|
||||
|| h.getOldMode() == FileMode.GITLINK
|
||||
|| h.getNewMode() == FileMode.GITLINK) {
|
||||
edits = Collections.emptyList();
|
||||
} else {
|
||||
edits = Collections.unmodifiableList(h.toEditList());
|
||||
}
|
||||
}
|
||||
|
||||
public FileHeader getFileHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public List<Edit> getEdits() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
if (header != null) {
|
||||
final int hdrLen = end(header) - header.getStartOffset();
|
||||
out.writeInt(hdrLen);
|
||||
out.write(header.getBuffer(), header.getStartOffset(), hdrLen);
|
||||
} else {
|
||||
out.writeInt(0);
|
||||
}
|
||||
|
||||
out.writeInt(edits.size());
|
||||
for (final Edit e : edits) {
|
||||
out.writeInt(e.getBeginA());
|
||||
out.writeInt(e.getEndA());
|
||||
out.writeInt(e.getBeginB());
|
||||
out.writeInt(e.getEndB());
|
||||
}
|
||||
}
|
||||
|
||||
private void readObject(final ObjectInputStream in) throws IOException {
|
||||
final int hdrLen = in.readInt();
|
||||
if (hdrLen > 0) {
|
||||
final byte[] buf = new byte[hdrLen];
|
||||
in.readFully(buf);
|
||||
header = parse(buf);
|
||||
} else {
|
||||
header = null;
|
||||
}
|
||||
|
||||
final int editCount = in.readInt();
|
||||
if (editCount > 0) {
|
||||
final Edit[] editArray = new Edit[editCount];
|
||||
for (int i = 0; i < editCount; i++) {
|
||||
final int beginA = in.readInt();
|
||||
final int endA = in.readInt();
|
||||
final int beginB = in.readInt();
|
||||
final int endB = in.readInt();
|
||||
editArray[i] = new Edit(beginA, endA, beginB, endB);
|
||||
}
|
||||
edits = Collections.unmodifiableList(Arrays.asList(editArray));
|
||||
} else {
|
||||
edits = Collections.emptyList();
|
||||
}
|
||||
}
|
||||
|
||||
private static FileHeader parse(final byte[] buf) {
|
||||
final Patch p = new Patch();
|
||||
p.parse(buf, 0, buf.length);
|
||||
return p.getFiles().get(0);
|
||||
}
|
||||
|
||||
private static FileHeader compact(final FileHeader h) {
|
||||
final int end = end(h);
|
||||
if (h.getStartOffset() == 0 && end == h.getBuffer().length) {
|
||||
return h;
|
||||
}
|
||||
|
||||
final byte[] buf = new byte[end - h.getStartOffset()];
|
||||
System.arraycopy(h.getBuffer(), h.getStartOffset(), buf, 0, buf.length);
|
||||
return parse(buf);
|
||||
}
|
||||
|
||||
private static int end(final FileHeader h) {
|
||||
if (h instanceof CombinedFileHeader) {
|
||||
return h.getEndOffset();
|
||||
}
|
||||
if (!h.getHunks().isEmpty()) {
|
||||
return h.getHunks().get(0).getStartOffset();
|
||||
}
|
||||
return h.getEndOffset();
|
||||
}
|
||||
}
|
||||
@@ -1,113 +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.server.GerritServer;
|
||||
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
import org.spearce.jgit.patch.FileHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
final class DiffCacheEntryFactory {
|
||||
private final GerritServer server;
|
||||
|
||||
DiffCacheEntryFactory(final GerritServer gs) {
|
||||
server = gs;
|
||||
}
|
||||
|
||||
DiffCacheContent createEntry(final DiffCacheKey key) throws Exception {
|
||||
final Repository db = server.openRepository(key.getProjectKey().get());
|
||||
try {
|
||||
return createEntry(key, db);
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
private DiffCacheContent createEntry(final DiffCacheKey key,
|
||||
final Repository db) throws Exception {
|
||||
final ObjectId newId = key.getNewId();
|
||||
final List<String> args = new ArrayList<String>();
|
||||
args.add("git");
|
||||
args.add("--git-dir=.");
|
||||
args.add("diff-tree");
|
||||
if (key.getSourceFileName() != null) {
|
||||
args.add("-M");
|
||||
}
|
||||
switch (key.getWhitespace()) {
|
||||
case IGNORE_NONE:
|
||||
break;
|
||||
case IGNORE_SPACE_AT_EOL:
|
||||
args.add("--ignore-space-at-eol");
|
||||
break;
|
||||
case IGNORE_SPACE_CHANGE:
|
||||
args.add("--ignore-space-change");
|
||||
break;
|
||||
case IGNORE_ALL_SPACE:
|
||||
args.add("--ignore-all-space");
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported whitespace " + key.getWhitespace());
|
||||
}
|
||||
args.add("--full-index");
|
||||
|
||||
if (key.getOldId() == null) {
|
||||
args.add("--cc");
|
||||
} else {
|
||||
args.add("--unified=1");
|
||||
args.add(key.getOldId().name());
|
||||
}
|
||||
args.add(newId.name());
|
||||
args.add("--");
|
||||
args.add(key.getFileName());
|
||||
if (key.getSourceFileName() != null) {
|
||||
args.add(key.getSourceFileName());
|
||||
}
|
||||
|
||||
final Process proc =
|
||||
Runtime.getRuntime().exec(args.toArray(new String[args.size()]), null,
|
||||
db.getDirectory());
|
||||
final FileHeader file;
|
||||
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()) {
|
||||
return DiffCacheContent.createEmpty();
|
||||
|
||||
} else if (p.getFiles().size() != 1) {
|
||||
throw new IOException("unexpected file count: " + key);
|
||||
}
|
||||
|
||||
file = p.getFiles().get(0);
|
||||
} finally {
|
||||
try {
|
||||
if (proc.waitFor() != 0) {
|
||||
throw new IOException("git diff-tree exited abnormally: " + key);
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
return DiffCacheContent.create(file);
|
||||
}
|
||||
}
|
||||
@@ -1,185 +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.client.data.PatchScriptSettings;
|
||||
import com.google.gerrit.client.data.PatchScriptSettings.Whitespace;
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
|
||||
import org.spearce.jgit.lib.AnyObjectId;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.ObjectIdSerialization;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
public final class DiffCacheKey implements Serializable {
|
||||
static final long serialVersionUID = 5L;
|
||||
|
||||
private transient Project.NameKey projectKey;
|
||||
private transient ObjectId oldId;
|
||||
private transient ObjectId newId;
|
||||
private transient String fileName;
|
||||
private transient String sourceFileName;
|
||||
private transient Whitespace whitespace;
|
||||
|
||||
public DiffCacheKey(final Project.NameKey pnk, final AnyObjectId a,
|
||||
final AnyObjectId b, final Patch p, final PatchScriptSettings s) {
|
||||
this(pnk, a, b, p.getFileName(), p.getSourceFileName(), s.getWhitespace());
|
||||
}
|
||||
|
||||
public DiffCacheKey(final Project.NameKey p, final AnyObjectId a,
|
||||
final AnyObjectId b, final String dname, final String sname,
|
||||
final Whitespace ws) {
|
||||
projectKey = p;
|
||||
oldId = a != null ? a.copy() : null;
|
||||
newId = b.copy();
|
||||
fileName = dname;
|
||||
sourceFileName = sname;
|
||||
whitespace = ws;
|
||||
}
|
||||
|
||||
public Project.NameKey getProjectKey() {
|
||||
return projectKey;
|
||||
}
|
||||
|
||||
/** Commit name, or the empty tree name if this is for an initial commit. */
|
||||
public ObjectId getOldId() {
|
||||
return oldId;
|
||||
}
|
||||
|
||||
/** Commit name. */
|
||||
public ObjectId getNewId() {
|
||||
return newId;
|
||||
}
|
||||
|
||||
public String getFileName() {
|
||||
return fileName;
|
||||
}
|
||||
|
||||
public String getSourceFileName() {
|
||||
return sourceFileName;
|
||||
}
|
||||
|
||||
public Whitespace getWhitespace() {
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = projectKey.hashCode();
|
||||
|
||||
if (oldId != null) {
|
||||
h *= 31;
|
||||
h += oldId.hashCode();
|
||||
}
|
||||
|
||||
h *= 31;
|
||||
h += newId.hashCode();
|
||||
|
||||
h *= 31;
|
||||
h += fileName.hashCode();
|
||||
|
||||
if (sourceFileName != null) {
|
||||
h *= 31;
|
||||
h += sourceFileName.hashCode();
|
||||
}
|
||||
|
||||
h *= 31;
|
||||
h += whitespace.ordinal();
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof DiffCacheKey) {
|
||||
final DiffCacheKey k = (DiffCacheKey) o;
|
||||
return projectKey.equals(k.projectKey) && eq(oldId, k.oldId)
|
||||
&& eq(newId, k.newId) && eq(fileName, k.fileName)
|
||||
&& eq(sourceFileName, k.sourceFileName) && whitespace == k.whitespace;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean eq(final ObjectId a, final ObjectId b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
return a != null && b != null && AnyObjectId.equals(a, b);
|
||||
}
|
||||
|
||||
private static boolean eq(final String a, final String b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
return a != null && a.equals(b);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
final StringBuilder r = new StringBuilder();
|
||||
r.append("DiffCache[");
|
||||
r.append(whitespace.name());
|
||||
r.append(" ");
|
||||
r.append(projectKey.toString());
|
||||
r.append(" ");
|
||||
if (oldId != null) {
|
||||
r.append(oldId.name());
|
||||
r.append("..");
|
||||
}
|
||||
r.append(newId.name());
|
||||
r.append(" -- ");
|
||||
r.append(fileName);
|
||||
if (sourceFileName != null) {
|
||||
r.append(" ");
|
||||
r.append(sourceFileName);
|
||||
}
|
||||
r.append("]");
|
||||
return r.toString();
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
out.writeUTF(projectKey.get());
|
||||
ObjectIdSerialization.write(out, oldId);
|
||||
ObjectIdSerialization.write(out, newId);
|
||||
writeString(out, fileName);
|
||||
writeString(out, sourceFileName);
|
||||
writeString(out, whitespace.name());
|
||||
}
|
||||
|
||||
private void readObject(final ObjectInputStream in) throws IOException {
|
||||
projectKey = new Project.NameKey(in.readUTF());
|
||||
oldId = ObjectIdSerialization.read(in);
|
||||
newId = ObjectIdSerialization.read(in);
|
||||
fileName = readString(in);
|
||||
sourceFileName = readString(in);
|
||||
whitespace = Whitespace.valueOf(readString(in));
|
||||
}
|
||||
|
||||
private static void writeString(final ObjectOutputStream out, final String s)
|
||||
throws IOException {
|
||||
out.writeUTF(s != null ? s : "");
|
||||
}
|
||||
|
||||
private static String readString(final ObjectInputStream in)
|
||||
throws IOException {
|
||||
final String s = in.readUTF();
|
||||
return s.length() > 0 ? s : null;
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,6 @@
|
||||
package com.google.gerrit.server.patch;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.RevId;
|
||||
import com.google.gerrit.client.rpc.CorruptEntityException;
|
||||
import com.google.gerrit.client.rpc.NoSuchEntityException;
|
||||
|
||||
@@ -25,9 +24,9 @@ import org.spearce.jgit.errors.MissingObjectException;
|
||||
import org.spearce.jgit.lib.Constants;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
import org.spearce.jgit.lib.ObjectLoader;
|
||||
import org.spearce.jgit.lib.ObjectWriter;
|
||||
import org.spearce.jgit.lib.Repository;
|
||||
import org.spearce.jgit.revwalk.RevCommit;
|
||||
import org.spearce.jgit.revwalk.RevTree;
|
||||
import org.spearce.jgit.revwalk.RevWalk;
|
||||
import org.spearce.jgit.treewalk.TreeWalk;
|
||||
|
||||
@@ -37,32 +36,34 @@ import java.nio.charset.CharacterCodingException;
|
||||
/** State supporting processing of a single {@link Patch} instance. */
|
||||
public class PatchFile {
|
||||
private final Repository repo;
|
||||
private final Patch patch;
|
||||
private final ObjectId aTree;
|
||||
private final ObjectId bTree;
|
||||
private final PatchListEntry entry;
|
||||
private final RevTree aTree;
|
||||
private final RevTree bTree;
|
||||
|
||||
private Text a;
|
||||
private Text b;
|
||||
|
||||
public PatchFile(final Repository repo, final RevId id, final Patch patch)
|
||||
throws MissingObjectException, IncorrectObjectTypeException, IOException {
|
||||
public PatchFile(final Repository repo, final PatchList patchList,
|
||||
final String fileName) throws MissingObjectException,
|
||||
IncorrectObjectTypeException, IOException {
|
||||
this.repo = repo;
|
||||
this.patch = patch;
|
||||
this.entry = patchList.get(fileName);
|
||||
|
||||
final RevWalk rw = new RevWalk(repo);
|
||||
final RevCommit bCommit = rw.parseCommit(ObjectId.fromString(id.get()));
|
||||
if (bCommit.getParentCount() > 0) {
|
||||
rw.parseHeaders(bCommit.getParent(0));
|
||||
aTree = bCommit.getParent(0).getTree();
|
||||
final RevCommit bCommit = rw.parseCommit(patchList.getNewId());
|
||||
if (patchList.getOldId() != null) {
|
||||
aTree = rw.parseTree(patchList.getOldId());
|
||||
} else {
|
||||
aTree = emptyTree();
|
||||
final RevCommit p = bCommit.getParent(0);
|
||||
rw.parseHeaders(p);
|
||||
aTree = p.getTree();
|
||||
}
|
||||
bTree = bCommit.getTree();
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract a line from the file, as a string.
|
||||
*
|
||||
*
|
||||
* @param file the file index to extract.
|
||||
* @param line the line number to extract (1 based; 1 is the first line).
|
||||
* @return the string version of the file line.
|
||||
@@ -76,17 +77,13 @@ public class PatchFile {
|
||||
switch (file) {
|
||||
case 0:
|
||||
if (a == null) {
|
||||
String p = patch.getSourceFileName();
|
||||
if (p == null) {
|
||||
p = patch.getFileName();
|
||||
}
|
||||
a = load(aTree, p);
|
||||
a = load(aTree, entry.getOldName());
|
||||
}
|
||||
return a.getLine(line - 1);
|
||||
|
||||
case 1:
|
||||
if (b == null) {
|
||||
b = load(bTree, patch.getFileName());
|
||||
b = load(bTree, entry.getNewName());
|
||||
}
|
||||
return b.getLine(line - 1);
|
||||
|
||||
@@ -98,6 +95,9 @@ public class PatchFile {
|
||||
private Text load(final ObjectId tree, final String path)
|
||||
throws MissingObjectException, IncorrectObjectTypeException,
|
||||
CorruptObjectException, IOException {
|
||||
if (path == null) {
|
||||
return Text.EMPTY;
|
||||
}
|
||||
final TreeWalk tw = TreeWalk.forPath(repo, path, tree);
|
||||
if (tw == null) {
|
||||
return Text.EMPTY;
|
||||
@@ -112,8 +112,4 @@ public class PatchFile {
|
||||
}
|
||||
return new Text(ldr.getCachedBytes());
|
||||
}
|
||||
|
||||
private ObjectId emptyTree() throws IOException {
|
||||
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
|
||||
}
|
||||
}
|
||||
|
||||
164
src/main/java/com/google/gerrit/server/patch/PatchList.java
Normal file
164
src/main/java/com/google/gerrit/server/patch/PatchList.java
Normal file
@@ -0,0 +1,164 @@
|
||||
// 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.server.ioutil.BasicSerialization.readBytes;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.readCanBeNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.readNotNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.writeCanBeNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.writeNotNull;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
|
||||
import org.spearce.jgit.lib.AnyObjectId;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.zip.DeflaterOutputStream;
|
||||
import java.util.zip.InflaterInputStream;
|
||||
|
||||
public class PatchList implements Serializable {
|
||||
private static final long serialVersionUID = PatchListKey.serialVersionUID;
|
||||
private static final Comparator<PatchListEntry> PATCH_CMP =
|
||||
new Comparator<PatchListEntry>() {
|
||||
@Override
|
||||
public int compare(final PatchListEntry a, final PatchListEntry b) {
|
||||
return a.getNewName().compareTo(b.getNewName());
|
||||
}
|
||||
};
|
||||
|
||||
@Nullable
|
||||
private transient ObjectId oldId;
|
||||
private transient ObjectId newId;
|
||||
private transient PatchListEntry[] patches;
|
||||
|
||||
PatchList(@Nullable final AnyObjectId oldId, final AnyObjectId newId,
|
||||
final PatchListEntry[] patches) {
|
||||
this.oldId = oldId != null ? oldId.copy() : null;
|
||||
this.newId = newId.copy();
|
||||
|
||||
Arrays.sort(patches, PATCH_CMP);
|
||||
this.patches = patches;
|
||||
}
|
||||
|
||||
/** Old side tree or commit; null only if this is a combined diff. */
|
||||
@Nullable
|
||||
public ObjectId getOldId() {
|
||||
return oldId;
|
||||
}
|
||||
|
||||
/** New side commit. */
|
||||
public ObjectId getNewId() {
|
||||
return newId;
|
||||
}
|
||||
|
||||
/** Get a sorted, unmodifiable list of all files in this list. */
|
||||
public List<PatchListEntry> getPatches() {
|
||||
return Collections.unmodifiableList(Arrays.asList(patches));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a sorted, modifiable list of all files in this list.
|
||||
* <p>
|
||||
* The returned list items do not populate:
|
||||
* <ul>
|
||||
* <li>{@link Patch#getCommentCount()}
|
||||
* <li>{@link Patch#getDraftCount()}
|
||||
* <li>{@link Patch#isReviewedByCurrentUser()}
|
||||
* </ul>
|
||||
*
|
||||
* @param setId the patch set identity these patches belong to. This really
|
||||
* should not need to be specified, but is a current legacy artifact of
|
||||
* how the cache is keyed versus how the database is keyed.
|
||||
*/
|
||||
public List<Patch> toPatchList(final PatchSet.Id setId) {
|
||||
final ArrayList<Patch> r = new ArrayList<Patch>(patches.length);
|
||||
for (final PatchListEntry e : patches) {
|
||||
r.add(e.toPatch(setId));
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Find an entry by name, returning an empty entry if not present. */
|
||||
public PatchListEntry get(final String fileName) {
|
||||
final int index = search(fileName);
|
||||
return 0 <= index ? patches[index] : PatchListEntry.empty(fileName);
|
||||
}
|
||||
|
||||
private int search(final String fileName) {
|
||||
int high = patches.length;
|
||||
int low = 0;
|
||||
while (low < high) {
|
||||
final int mid = (low + high) >>> 1;
|
||||
final int cmp = patches[mid].getNewName().compareTo(fileName);
|
||||
if (cmp < 0)
|
||||
low = mid + 1;
|
||||
else if (cmp == 0)
|
||||
return mid;
|
||||
else
|
||||
high = mid;
|
||||
}
|
||||
return -(low + 1);
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream output) throws IOException {
|
||||
final ByteArrayOutputStream buf = new ByteArrayOutputStream();
|
||||
final DeflaterOutputStream out = new DeflaterOutputStream(buf);
|
||||
try {
|
||||
writeCanBeNull(out, oldId);
|
||||
writeNotNull(out, newId);
|
||||
writeVarInt32(out, patches.length);
|
||||
for (PatchListEntry p : patches) {
|
||||
p.writeTo(out);
|
||||
}
|
||||
} finally {
|
||||
out.close();
|
||||
}
|
||||
writeBytes(output, buf.toByteArray());
|
||||
}
|
||||
|
||||
private void readObject(final ObjectInputStream input) throws IOException {
|
||||
final ByteArrayInputStream buf = new ByteArrayInputStream(readBytes(input));
|
||||
final InflaterInputStream in = new InflaterInputStream(buf);
|
||||
try {
|
||||
oldId = readCanBeNull(in);
|
||||
newId = readNotNull(in);
|
||||
final int cnt = readVarInt32(in);
|
||||
final PatchListEntry[] all = new PatchListEntry[cnt];
|
||||
for (int i = 0; i < all.length; i++) {
|
||||
all[i] = PatchListEntry.readFrom(in);
|
||||
}
|
||||
patches = all;
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
195
src/main/java/com/google/gerrit/server/patch/PatchListCache.java
Normal file
195
src/main/java/com/google/gerrit/server/patch/PatchListCache.java
Normal file
@@ -0,0 +1,195 @@
|
||||
// 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.data.PatchScriptSettings.Whitespace.IGNORE_NONE;
|
||||
|
||||
import com.google.gerrit.client.data.PatchScriptSettings.Whitespace;
|
||||
import com.google.gerrit.client.reviewdb.Change;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.cache.Cache;
|
||||
import com.google.gerrit.server.cache.CacheModule;
|
||||
import com.google.gerrit.server.cache.SelfPopulatingCache;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Module;
|
||||
import com.google.inject.Singleton;
|
||||
import com.google.inject.TypeLiteral;
|
||||
import com.google.inject.name.Named;
|
||||
|
||||
import org.spearce.jgit.errors.IncorrectObjectTypeException;
|
||||
import org.spearce.jgit.errors.MissingObjectException;
|
||||
import org.spearce.jgit.lib.AnyObjectId;
|
||||
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;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/** Provides a cached list of {@link PatchListEntry}. */
|
||||
@Singleton
|
||||
public class PatchListCache {
|
||||
private static final String CACHE_NAME = "diff";
|
||||
|
||||
public static Module module() {
|
||||
return new CacheModule() {
|
||||
@Override
|
||||
protected void configure() {
|
||||
final TypeLiteral<Cache<PatchListKey, PatchList>> type =
|
||||
new TypeLiteral<Cache<PatchListKey, PatchList>>() {};
|
||||
disk(type, CACHE_NAME);
|
||||
bind(PatchListCache.class);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private final GerritServer server;
|
||||
private final SelfPopulatingCache<PatchListKey, PatchList> self;
|
||||
|
||||
@Inject
|
||||
PatchListCache(final GerritServer gs,
|
||||
@Named(CACHE_NAME) final Cache<PatchListKey, PatchList> raw) {
|
||||
server = gs;
|
||||
self = new SelfPopulatingCache<PatchListKey, PatchList>(raw) {
|
||||
@Override
|
||||
protected PatchList createEntry(final PatchListKey key) throws Exception {
|
||||
return compute(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public PatchList get(final PatchListKey key) {
|
||||
return self.get(key);
|
||||
}
|
||||
|
||||
public PatchList get(final Change change, final PatchSet patchSet) {
|
||||
return get(change, patchSet, IGNORE_NONE);
|
||||
}
|
||||
|
||||
public PatchList get(final Change change, final PatchSet patchSet,
|
||||
final Whitespace whitespace) {
|
||||
final Project.NameKey projectKey = change.getProject();
|
||||
final ObjectId a = null;
|
||||
final ObjectId b = ObjectId.fromString(patchSet.getRevision().get());
|
||||
return get(new PatchListKey(projectKey, a, b, whitespace));
|
||||
}
|
||||
|
||||
private PatchList compute(final PatchListKey key)
|
||||
throws MissingObjectException, IncorrectObjectTypeException, IOException {
|
||||
final Repository repo = server.openRepository(key.projectKey.get());
|
||||
try {
|
||||
return readPatchList(key, repo);
|
||||
} finally {
|
||||
repo.close();
|
||||
}
|
||||
}
|
||||
|
||||
private PatchList readPatchList(final PatchListKey key, final Repository repo)
|
||||
throws IOException {
|
||||
final RevCommit b = new RevWalk(repo).parseCommit(key.getNewId());
|
||||
final AnyObjectId a = aFor(key, repo, b);
|
||||
|
||||
final List<String> args = new ArrayList<String>();
|
||||
args.add("git");
|
||||
args.add("--git-dir=.");
|
||||
args.add("diff-tree");
|
||||
args.add("-M");
|
||||
switch (key.getWhitespace()) {
|
||||
case IGNORE_NONE:
|
||||
break;
|
||||
case IGNORE_SPACE_AT_EOL:
|
||||
args.add("--ignore-space-at-eol");
|
||||
break;
|
||||
case IGNORE_SPACE_CHANGE:
|
||||
args.add("--ignore-space-change");
|
||||
break;
|
||||
case IGNORE_ALL_SPACE:
|
||||
args.add("--ignore-all-space");
|
||||
break;
|
||||
default:
|
||||
throw new IOException("Unsupported whitespace " + key.getWhitespace());
|
||||
}
|
||||
if (a == null /* want combined diff */) {
|
||||
args.add("--cc");
|
||||
args.add(b.name());
|
||||
} else {
|
||||
args.add("--unified=1");
|
||||
args.add(a.name());
|
||||
args.add(b.name());
|
||||
}
|
||||
|
||||
final org.spearce.jgit.patch.Patch p = new org.spearce.jgit.patch.Patch();
|
||||
final Process diffProcess = exec(repo, args);
|
||||
try {
|
||||
diffProcess.getOutputStream().close();
|
||||
diffProcess.getErrorStream().close();
|
||||
|
||||
final InputStream in = diffProcess.getInputStream();
|
||||
try {
|
||||
p.parse(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
final int rc = diffProcess.waitFor();
|
||||
if (rc != 0) {
|
||||
throw new IOException("git diff-tree exited abnormally: " + rc);
|
||||
}
|
||||
} catch (InterruptedException ie) {
|
||||
}
|
||||
}
|
||||
|
||||
final int cnt = p.getFiles().size();
|
||||
final PatchListEntry[] entries = new PatchListEntry[cnt];
|
||||
for (int i = 0; i < cnt; i++) {
|
||||
entries[i] = new PatchListEntry(p.getFiles().get(i));
|
||||
}
|
||||
return new PatchList(a, b, entries);
|
||||
}
|
||||
|
||||
private static AnyObjectId aFor(final PatchListKey key,
|
||||
final Repository repo, final RevCommit b) throws IOException {
|
||||
if (key.getOldId() != null) {
|
||||
return key.getOldId();
|
||||
}
|
||||
|
||||
switch (b.getParentCount()) {
|
||||
case 0:
|
||||
return emptyTree(repo);
|
||||
case 1:
|
||||
return b.getParent(0);
|
||||
default:
|
||||
// merge commit, return null to force combined diff behavior
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Process exec(final Repository repo, final List<String> args)
|
||||
throws IOException {
|
||||
final String[] argv = args.toArray(new String[args.size()]);
|
||||
return Runtime.getRuntime().exec(argv, null, repo.getDirectory());
|
||||
}
|
||||
|
||||
private static ObjectId emptyTree(final Repository repo) throws IOException {
|
||||
return new ObjectWriter(repo).writeCanonicalTree(new byte[0]);
|
||||
}
|
||||
}
|
||||
264
src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
Normal file
264
src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
Normal file
@@ -0,0 +1,264 @@
|
||||
// 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.server.ioutil.BasicSerialization.readBytes;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readEnum;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readString;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeBytes;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeString;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
|
||||
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchSet;
|
||||
import com.google.gerrit.client.reviewdb.Patch.ChangeType;
|
||||
import com.google.gerrit.client.reviewdb.Patch.PatchType;
|
||||
|
||||
import org.spearce.jgit.diff.Edit;
|
||||
import org.spearce.jgit.lib.Constants;
|
||||
import org.spearce.jgit.lib.FileMode;
|
||||
import org.spearce.jgit.patch.CombinedFileHeader;
|
||||
import org.spearce.jgit.patch.FileHeader;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
public class PatchListEntry {
|
||||
static PatchListEntry empty(final String fileName) {
|
||||
final StringBuilder buf = new StringBuilder();
|
||||
buf.append("diff --git a/");
|
||||
buf.append(fileName);
|
||||
buf.append(" b/");
|
||||
buf.append(fileName);
|
||||
buf.append("\n\n");
|
||||
return new PatchListEntry(parse(Constants.encode(buf.toString())));
|
||||
}
|
||||
|
||||
private final ChangeType changeType;
|
||||
private final PatchType patchType;
|
||||
private final String oldName;
|
||||
private final String newName;
|
||||
private final FileHeader header;
|
||||
private final List<Edit> edits;
|
||||
|
||||
PatchListEntry(final FileHeader hdr) {
|
||||
changeType = toChangeType(hdr);
|
||||
patchType = toPatchType(hdr);
|
||||
|
||||
switch (changeType) {
|
||||
case DELETED:
|
||||
oldName = null;
|
||||
newName = hdr.getOldName();
|
||||
break;
|
||||
|
||||
case ADDED:
|
||||
case MODIFIED:
|
||||
oldName = null;
|
||||
newName = hdr.getNewName();
|
||||
break;
|
||||
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
oldName = hdr.getOldName();
|
||||
newName = hdr.getNewName();
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type " + changeType);
|
||||
}
|
||||
|
||||
header = compact(hdr);
|
||||
|
||||
if (hdr instanceof CombinedFileHeader
|
||||
|| hdr.getHunks().isEmpty() //
|
||||
|| hdr.getOldMode() == FileMode.GITLINK
|
||||
|| hdr.getNewMode() == FileMode.GITLINK) {
|
||||
edits = Collections.emptyList();
|
||||
} else {
|
||||
edits = Collections.unmodifiableList(hdr.toEditList());
|
||||
}
|
||||
}
|
||||
|
||||
private PatchListEntry(final ChangeType changeType,
|
||||
final PatchType patchType, final String oldName, final String newName,
|
||||
final FileHeader header, final List<Edit> edits) {
|
||||
this.changeType = changeType;
|
||||
this.patchType = patchType;
|
||||
this.oldName = oldName;
|
||||
this.newName = newName;
|
||||
this.header = header;
|
||||
this.edits = edits;
|
||||
}
|
||||
|
||||
public ChangeType getChangeType() {
|
||||
return changeType;
|
||||
}
|
||||
|
||||
public PatchType getPatchType() {
|
||||
return patchType;
|
||||
}
|
||||
|
||||
public String getOldName() {
|
||||
return oldName;
|
||||
}
|
||||
|
||||
public String getNewName() {
|
||||
return newName;
|
||||
}
|
||||
|
||||
public FileHeader getFileHeader() {
|
||||
return header;
|
||||
}
|
||||
|
||||
public List<Edit> getEdits() {
|
||||
return edits;
|
||||
}
|
||||
|
||||
Patch toPatch(final PatchSet.Id setId) {
|
||||
final Patch p = new Patch(new Patch.Key(setId, getNewName()));
|
||||
p.setChangeType(getChangeType());
|
||||
p.setPatchType(getPatchType());
|
||||
p.setSourceFileName(getOldName());
|
||||
return p;
|
||||
}
|
||||
|
||||
void writeTo(final OutputStream out) throws IOException {
|
||||
writeEnum(out, changeType);
|
||||
writeEnum(out, patchType);
|
||||
writeString(out, oldName);
|
||||
writeString(out, newName);
|
||||
|
||||
final int hdrLen = end(header) - header.getStartOffset();
|
||||
writeBytes(out, header.getBuffer(), header.getStartOffset(), hdrLen);
|
||||
|
||||
writeVarInt32(out, edits.size());
|
||||
for (final Edit e : edits) {
|
||||
writeVarInt32(out, e.getBeginA());
|
||||
writeVarInt32(out, e.getEndA());
|
||||
writeVarInt32(out, e.getBeginB());
|
||||
writeVarInt32(out, e.getEndB());
|
||||
}
|
||||
}
|
||||
|
||||
static PatchListEntry readFrom(final InputStream in) throws IOException {
|
||||
final ChangeType changeType = readEnum(in, ChangeType.values());
|
||||
final PatchType patchType = readEnum(in, PatchType.values());
|
||||
final String oldName = readString(in);
|
||||
final String newName = readString(in);
|
||||
final FileHeader hdr = parse(readBytes(in));
|
||||
|
||||
final int editCount = readVarInt32(in);
|
||||
final Edit[] editArray = new Edit[editCount];
|
||||
for (int i = 0; i < editCount; i++) {
|
||||
final int beginA = readVarInt32(in);
|
||||
final int endA = readVarInt32(in);
|
||||
final int beginB = readVarInt32(in);
|
||||
final int endB = readVarInt32(in);
|
||||
editArray[i] = new Edit(beginA, endA, beginB, endB);
|
||||
}
|
||||
|
||||
return new PatchListEntry(changeType, patchType, oldName, newName, hdr,
|
||||
Collections.unmodifiableList(Arrays.asList(editArray)));
|
||||
}
|
||||
|
||||
private static FileHeader parse(final byte[] buf) {
|
||||
final org.spearce.jgit.patch.Patch p = new org.spearce.jgit.patch.Patch();
|
||||
p.parse(buf, 0, buf.length);
|
||||
return p.getFiles().get(0);
|
||||
}
|
||||
|
||||
private static FileHeader compact(final FileHeader h) {
|
||||
final int end = end(h);
|
||||
if (h.getStartOffset() == 0 && end == h.getBuffer().length) {
|
||||
return h;
|
||||
}
|
||||
|
||||
final byte[] buf = new byte[end - h.getStartOffset()];
|
||||
System.arraycopy(h.getBuffer(), h.getStartOffset(), buf, 0, buf.length);
|
||||
return parse(buf);
|
||||
}
|
||||
|
||||
private static int end(final FileHeader h) {
|
||||
if (h instanceof CombinedFileHeader) {
|
||||
return h.getEndOffset();
|
||||
}
|
||||
if (!h.getHunks().isEmpty()) {
|
||||
return h.getHunks().get(0).getStartOffset();
|
||||
}
|
||||
return h.getEndOffset();
|
||||
}
|
||||
|
||||
private static ChangeType toChangeType(final FileHeader hdr) {
|
||||
switch (hdr.getChangeType()) {
|
||||
case ADD:
|
||||
return Patch.ChangeType.ADDED;
|
||||
case MODIFY:
|
||||
return Patch.ChangeType.MODIFIED;
|
||||
case DELETE:
|
||||
return Patch.ChangeType.DELETED;
|
||||
case RENAME:
|
||||
return Patch.ChangeType.RENAMED;
|
||||
case COPY:
|
||||
return Patch.ChangeType.COPIED;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type "
|
||||
+ hdr.getChangeType());
|
||||
}
|
||||
}
|
||||
|
||||
private static PatchType toPatchType(final FileHeader hdr) {
|
||||
PatchType pt;
|
||||
|
||||
if (hdr instanceof CombinedFileHeader) {
|
||||
pt = Patch.PatchType.N_WAY;
|
||||
} else {
|
||||
switch (hdr.getPatchType()) {
|
||||
case UNIFIED:
|
||||
pt = Patch.PatchType.UNIFIED;
|
||||
break;
|
||||
case GIT_BINARY:
|
||||
case BINARY:
|
||||
pt = Patch.PatchType.BINARY;
|
||||
break;
|
||||
default:
|
||||
throw new IllegalArgumentException("Unsupported type "
|
||||
+ hdr.getPatchType());
|
||||
}
|
||||
}
|
||||
|
||||
if (pt != PatchType.BINARY) {
|
||||
final byte[] buf = hdr.getBuffer();
|
||||
for (int ptr = hdr.getStartOffset(); ptr < hdr.getEndOffset(); ptr++) {
|
||||
if (buf[ptr] == '\0') {
|
||||
// Its really binary, but Git couldn't see the nul early enough
|
||||
// to realize its binary, and instead produced the diff.
|
||||
//
|
||||
// Force it to be a binary; it really should have been that.
|
||||
//
|
||||
pt = PatchType.BINARY;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return pt;
|
||||
}
|
||||
}
|
||||
111
src/main/java/com/google/gerrit/server/patch/PatchListKey.java
Normal file
111
src/main/java/com/google/gerrit/server/patch/PatchListKey.java
Normal file
@@ -0,0 +1,111 @@
|
||||
// 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.server.ioutil.BasicSerialization.readEnum;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeEnum;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.readCanBeNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.readNotNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.writeCanBeNull;
|
||||
import static org.spearce.jgit.lib.ObjectIdSerialization.writeNotNull;
|
||||
|
||||
import com.google.gerrit.client.data.PatchScriptSettings.Whitespace;
|
||||
import com.google.gerrit.client.reviewdb.Project;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
|
||||
import org.spearce.jgit.lib.AnyObjectId;
|
||||
import org.spearce.jgit.lib.ObjectId;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class PatchListKey implements Serializable {
|
||||
static final long serialVersionUID = 9L;
|
||||
|
||||
private transient ObjectId oldId;
|
||||
private transient ObjectId newId;
|
||||
private transient Whitespace whitespace;
|
||||
|
||||
transient Project.NameKey projectKey; // not required to form the key
|
||||
|
||||
public PatchListKey(final Project.NameKey pk, final AnyObjectId a,
|
||||
final AnyObjectId b, final Whitespace ws) {
|
||||
projectKey = pk;
|
||||
oldId = a != null ? a.copy() : null;
|
||||
newId = b.copy();
|
||||
whitespace = ws;
|
||||
}
|
||||
|
||||
/** Old side commit, or null to assume ancestor or combined merge. */
|
||||
@Nullable
|
||||
public ObjectId getOldId() {
|
||||
return oldId;
|
||||
}
|
||||
|
||||
/** New side commit name. */
|
||||
public ObjectId getNewId() {
|
||||
return newId;
|
||||
}
|
||||
|
||||
public Whitespace getWhitespace() {
|
||||
return whitespace;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int h = 0;
|
||||
|
||||
if (oldId != null) {
|
||||
h = h * 31 + oldId.hashCode();
|
||||
}
|
||||
|
||||
h = h * 31 + newId.hashCode();
|
||||
h = h * 31 + whitespace.name().hashCode();
|
||||
|
||||
return h;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
if (o instanceof PatchListKey) {
|
||||
final PatchListKey k = (PatchListKey) o;
|
||||
return eq(oldId, k.oldId) //
|
||||
&& eq(newId, k.newId) //
|
||||
&& whitespace == k.whitespace;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static boolean eq(final ObjectId a, final ObjectId b) {
|
||||
if (a == null && b == null) {
|
||||
return true;
|
||||
}
|
||||
return a != null && b != null && AnyObjectId.equals(a, b);
|
||||
}
|
||||
|
||||
private void writeObject(final ObjectOutputStream out) throws IOException {
|
||||
writeCanBeNull(out, oldId);
|
||||
writeNotNull(out, newId);
|
||||
writeEnum(out, whitespace);
|
||||
}
|
||||
|
||||
private void readObject(final ObjectInputStream in) throws IOException {
|
||||
oldId = readCanBeNull(in);
|
||||
newId = readNotNull(in);
|
||||
whitespace = readEnum(in, Whitespace.values());
|
||||
}
|
||||
}
|
||||
@@ -22,18 +22,20 @@ 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.NoSuchEntityException;
|
||||
import com.google.gerrit.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoFactory;
|
||||
import com.google.gerrit.server.patch.PatchSetInfoNotAvailableException;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.rpc.Handler;
|
||||
import com.google.gwtorm.client.OrmException;
|
||||
import com.google.gwtorm.client.ResultSet;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@@ -45,6 +47,7 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
|
||||
|
||||
private final PatchSetInfoFactory infoFactory;
|
||||
private final ReviewDb db;
|
||||
private final PatchListCache patchListCache;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
|
||||
private final PatchSet.Id psId;
|
||||
@@ -55,10 +58,12 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
|
||||
|
||||
@Inject
|
||||
PatchSetDetailFactory(final PatchSetInfoFactory psif, final ReviewDb db,
|
||||
final PatchListCache patchListCache,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
@Assisted final PatchSet.Id id) {
|
||||
this.infoFactory = psif;
|
||||
this.db = db;
|
||||
this.patchListCache = patchListCache;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
|
||||
this.psId = id;
|
||||
@@ -75,43 +80,43 @@ class PatchSetDetailFactory extends Handler<PatchSetDetail> {
|
||||
}
|
||||
}
|
||||
|
||||
final PatchList list = patchListCache.get(control.getChange(), patchSet);
|
||||
final List<Patch> patches = list.toPatchList(patchSet.getId());
|
||||
final Map<Patch.Key, Patch> byKey = new HashMap<Patch.Key, Patch>();
|
||||
for (final Patch p : patches) {
|
||||
byKey.put(p.getKey(), p);
|
||||
}
|
||||
|
||||
for (final PatchLineComment c : db.patchComments().published(psId)) {
|
||||
final Patch p = byKey.get(c.getKey().getParentKey());
|
||||
if (p != null) {
|
||||
p.setCommentCount(p.getCommentCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
detail = new PatchSetDetail();
|
||||
detail.setPatchSet(patchSet);
|
||||
|
||||
detail.setInfo(infoFactory.get(psId));
|
||||
detail.setPatches(db.patches().byPatchSet(psId).toList());
|
||||
detail.setPatches(patches);
|
||||
|
||||
if (control.getCurrentUser() instanceof IdentifiedUser) {
|
||||
final CurrentUser user = control.getCurrentUser();
|
||||
if (user instanceof IdentifiedUser) {
|
||||
// If we are signed in, compute the number of draft comments by the
|
||||
// current user on each of these patch files. This way they can more
|
||||
// quickly locate where they have pending drafts, and review them.
|
||||
//
|
||||
final Account.Id me =
|
||||
((IdentifiedUser) control.getCurrentUser()).getAccountId();
|
||||
final List<PatchLineComment> comments =
|
||||
db.patchComments().draft(psId, me).toList();
|
||||
if (!comments.isEmpty()) {
|
||||
final Map<Patch.Key, Patch> byKey =
|
||||
db.patches().toMap(detail.getPatches());
|
||||
for (final PatchLineComment c : comments) {
|
||||
final Patch p = byKey.get(c.getKey().getParentKey());
|
||||
if (p != null) {
|
||||
p.setDraftCount(p.getDraftCount() + 1);
|
||||
}
|
||||
final Account.Id me = ((IdentifiedUser) user).getAccountId();
|
||||
for (final PatchLineComment c : db.patchComments().draft(psId, me)) {
|
||||
final Patch p = byKey.get(c.getKey().getParentKey());
|
||||
if (p != null) {
|
||||
p.setDraftCount(p.getDraftCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Get all the reviewed patches in one query
|
||||
ResultSet<AccountPatchReview> reviews =
|
||||
db.accountPatchReviews().byReviewer(me, psId);
|
||||
HashSet<Patch.Key> reviewedPatches = new HashSet<Patch.Key>();
|
||||
for (AccountPatchReview review : reviews) {
|
||||
reviewedPatches.add(review.getKey().getPatchKey());
|
||||
}
|
||||
|
||||
// Initialize the reviewed status of each patch
|
||||
for (Patch p : detail.getPatches()) {
|
||||
if (reviewedPatches.contains(p.getKey())) {
|
||||
for (AccountPatchReview r : db.accountPatchReviews().byReviewer(me, psId)) {
|
||||
final Patch p = byKey.get(r.getKey().getPatchKey());
|
||||
if (p != null) {
|
||||
p.setReviewedByCurrentUser(true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.google.gerrit.client.reviewdb.Patch;
|
||||
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.server.CurrentUser;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.account.AccountInfoCacheFactory;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
@@ -31,6 +32,11 @@ import com.google.gwtorm.client.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.assistedinject.Assisted;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
||||
class CommentDetailFactory extends Handler<CommentDetail> {
|
||||
interface Factory {
|
||||
@@ -50,8 +56,6 @@ class CommentDetailFactory extends Handler<CommentDetail> {
|
||||
private final PatchSet.Id patchSetId;
|
||||
private final Change.Id changeId;
|
||||
|
||||
private Patch patch;
|
||||
|
||||
@Inject
|
||||
CommentDetailFactory(final ReviewDb db,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
@@ -77,31 +81,44 @@ class CommentDetailFactory extends Handler<CommentDetail> {
|
||||
validatePatchSetId(psb);
|
||||
|
||||
final ChangeControl control = changeControlFactory.validateFor(changeId);
|
||||
patch = db.patches().get(patchKey);
|
||||
if (patch == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
final String pn = patchKey.getFileName();
|
||||
final CommentDetail r = new CommentDetail(psa, psb);
|
||||
|
||||
final List<Patch> historyList = new ArrayList<Patch>();
|
||||
final Map<PatchSet.Id, Patch> bySet = new HashMap<PatchSet.Id, Patch>();
|
||||
for (final PatchSet ps : db.patchSets().byChange(changeId)) {
|
||||
final Patch p = new Patch(new Patch.Key(ps.getId(), pn));
|
||||
historyList.add(p);
|
||||
bySet.put(ps.getId(), p);
|
||||
}
|
||||
|
||||
final String pn = patch.getFileName();
|
||||
final CommentDetail r;
|
||||
|
||||
r = new CommentDetail(psa, psb != null ? psb : patchSetId);
|
||||
for (PatchLineComment p : db.patchComments().published(changeId, pn)) {
|
||||
if (r.include(p)) {
|
||||
aic.want(p.getAuthor());
|
||||
for (PatchLineComment c : db.patchComments().published(changeId, pn)) {
|
||||
if (r.include(c)) {
|
||||
aic.want(c.getAuthor());
|
||||
}
|
||||
final PatchSet.Id psId = c.getKey().getParentKey().getParentKey();
|
||||
final Patch patch = bySet.get(psId);
|
||||
if (patch != null) {
|
||||
patch.setCommentCount(patch.getCommentCount() + 1);
|
||||
}
|
||||
}
|
||||
|
||||
if (control.getCurrentUser() instanceof IdentifiedUser) {
|
||||
final Account.Id me =
|
||||
((IdentifiedUser) control.getCurrentUser()).getAccountId();
|
||||
aic.want(me);
|
||||
for (PatchLineComment p : db.patchComments().draft(changeId, pn, me)) {
|
||||
r.include(p);
|
||||
final CurrentUser user = control.getCurrentUser();
|
||||
if (user instanceof IdentifiedUser) {
|
||||
final Account.Id me = ((IdentifiedUser) user).getAccountId();
|
||||
for (PatchLineComment c : db.patchComments().draft(changeId, pn, me)) {
|
||||
if (r.include(c)) {
|
||||
aic.want(me);
|
||||
}
|
||||
final PatchSet.Id psId = c.getKey().getParentKey().getParentKey();
|
||||
final Patch patch = bySet.get(psId);
|
||||
if (patch != null) {
|
||||
patch.setDraftCount(patch.getDraftCount() + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
r.setHistory(db.patches().history(changeId, pn).toList());
|
||||
r.setHistory(historyList);
|
||||
r.setAccountInfoCache(aic.create());
|
||||
return r;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,6 @@ import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
@@ -217,21 +216,10 @@ class PatchDetailServiceImpl extends BaseServiceImplementation implements
|
||||
|
||||
final boolean iscurrent = psid.equals(r.change.currentPatchSetId());
|
||||
r.comments = db.patchComments().draft(psid, me).toList();
|
||||
final Set<Patch.Key> patchKeys = new HashSet<Patch.Key>();
|
||||
for (final PatchLineComment c : r.comments) {
|
||||
patchKeys.add(c.getKey().getParentKey());
|
||||
}
|
||||
final Map<Patch.Key, Patch> patches =
|
||||
db.patches().toMap(db.patches().get(patchKeys));
|
||||
for (final PatchLineComment c : r.comments) {
|
||||
final Patch p = patches.get(c.getKey().getParentKey());
|
||||
if (p != null) {
|
||||
p.setCommentCount(p.getCommentCount() + 1);
|
||||
}
|
||||
c.setStatus(PatchLineComment.Status.PUBLISHED);
|
||||
c.updated();
|
||||
}
|
||||
db.patches().update(patches.values(), txn);
|
||||
db.patchComments().update(r.comments, txn);
|
||||
|
||||
final StringBuilder msgbuf = new StringBuilder();
|
||||
|
||||
@@ -20,19 +20,14 @@ import com.google.gerrit.client.data.PatchScriptSettings;
|
||||
import com.google.gerrit.client.data.SparseFileContent;
|
||||
import com.google.gerrit.client.data.PatchScript.DisplayMethod;
|
||||
import com.google.gerrit.client.patches.CommentDetail;
|
||||
import com.google.gerrit.client.reviewdb.Patch;
|
||||
import com.google.gerrit.client.reviewdb.PatchLineComment;
|
||||
import com.google.gerrit.client.rpc.CorruptEntityException;
|
||||
import com.google.gerrit.server.FileTypeRegistry;
|
||||
import com.google.gerrit.server.patch.DiffCacheContent;
|
||||
import com.google.gerrit.server.patch.DiffCacheKey;
|
||||
import com.google.gerrit.server.patch.PatchListEntry;
|
||||
import com.google.gerrit.server.patch.Text;
|
||||
|
||||
import eu.medsea.mimeutil.MimeType;
|
||||
import eu.medsea.mimeutil.MimeUtil2;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spearce.jgit.diff.Edit;
|
||||
import org.spearce.jgit.errors.CorruptObjectException;
|
||||
import org.spearce.jgit.errors.IncorrectObjectTypeException;
|
||||
@@ -60,9 +55,6 @@ class PatchScriptBuilder {
|
||||
static final int MAX_CONTEXT = 5000000;
|
||||
static final int BIG_FILE = 9000;
|
||||
|
||||
private static final Logger log =
|
||||
LoggerFactory.getLogger(PatchScriptBuilder.class);
|
||||
|
||||
private static final Comparator<Edit> EDIT_SORT = new Comparator<Edit>() {
|
||||
@Override
|
||||
public int compare(final Edit o1, final Edit o2) {
|
||||
@@ -72,9 +64,9 @@ class PatchScriptBuilder {
|
||||
|
||||
private final List<String> header;
|
||||
private Repository db;
|
||||
private Patch patch;
|
||||
private Patch.Key patchKey;
|
||||
private PatchScriptSettings settings;
|
||||
private ObjectId aId;
|
||||
private ObjectId bId;
|
||||
|
||||
private final Side a;
|
||||
private final Side b;
|
||||
@@ -93,22 +85,22 @@ class PatchScriptBuilder {
|
||||
db = r;
|
||||
}
|
||||
|
||||
void setPatch(final Patch p) {
|
||||
patch = p;
|
||||
patchKey = patch.getKey();
|
||||
}
|
||||
|
||||
void setSettings(final PatchScriptSettings s) {
|
||||
settings = s;
|
||||
}
|
||||
|
||||
void setTrees(final ObjectId a, final ObjectId b) {
|
||||
aId = a;
|
||||
bId = b;
|
||||
}
|
||||
|
||||
private int context() {
|
||||
return settings.getContext();
|
||||
}
|
||||
|
||||
PatchScript toPatchScript(final DiffCacheKey key,
|
||||
final DiffCacheContent contentWS, final CommentDetail comments,
|
||||
final DiffCacheContent contentAct) throws CorruptEntityException {
|
||||
PatchScript toPatchScript(final PatchListEntry contentWS,
|
||||
final CommentDetail comments, final PatchListEntry contentAct)
|
||||
throws IOException {
|
||||
if (contentAct.getFileHeader() instanceof CombinedFileHeader) {
|
||||
// For a diff --cc format we don't support converting it into
|
||||
// a patch script. Instead treat everything as a file header.
|
||||
@@ -119,18 +111,15 @@ class PatchScriptBuilder {
|
||||
a.displayMethod, b.displayMethod);
|
||||
}
|
||||
|
||||
a.path = oldFileName(key, contentAct.getFileHeader());
|
||||
b.path = newFileName(key, contentAct.getFileHeader());
|
||||
a.path = oldName(contentAct);
|
||||
b.path = newName(contentAct);
|
||||
|
||||
a.resolve(null, key.getOldId());
|
||||
b.resolve(a, key.getNewId());
|
||||
a.resolve(null, aId);
|
||||
b.resolve(a, bId);
|
||||
|
||||
edits = new ArrayList<Edit>(contentAct.getEdits());
|
||||
ensureCommentsVisible(comments);
|
||||
|
||||
if (contentAct.getFileHeader() != null) {
|
||||
packHeader(contentAct.getFileHeader());
|
||||
}
|
||||
packHeader(contentAct.getFileHeader());
|
||||
|
||||
if (a.mode == FileMode.GITLINK || b.mode == FileMode.GITLINK) {
|
||||
|
||||
@@ -165,33 +154,31 @@ class PatchScriptBuilder {
|
||||
a.displayMethod, b.displayMethod);
|
||||
}
|
||||
|
||||
private static String oldFileName(final DiffCacheKey key, final FileHeader fh) {
|
||||
if (fh != null) {
|
||||
if (FileMode.MISSING == fh.getOldMode()) {
|
||||
private static String oldName(final PatchListEntry entry) {
|
||||
switch (entry.getChangeType()) {
|
||||
case ADDED:
|
||||
return null;
|
||||
}
|
||||
if (FileHeader.DEV_NULL.equals(fh.getOldName())) {
|
||||
return null;
|
||||
}
|
||||
return fh.getOldName();
|
||||
case DELETED:
|
||||
case MODIFIED:
|
||||
return entry.getNewName();
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
default:
|
||||
return entry.getOldName();
|
||||
}
|
||||
if (key.getSourceFileName() != null) {
|
||||
return key.getSourceFileName();
|
||||
}
|
||||
return key.getFileName();
|
||||
}
|
||||
|
||||
private static String newFileName(final DiffCacheKey key, final FileHeader fh) {
|
||||
if (fh != null) {
|
||||
if (FileMode.MISSING == fh.getNewMode()) {
|
||||
private static String newName(final PatchListEntry entry) {
|
||||
switch (entry.getChangeType()) {
|
||||
case DELETED:
|
||||
return null;
|
||||
}
|
||||
if (FileHeader.DEV_NULL.equals(fh.getNewName())) {
|
||||
return null;
|
||||
}
|
||||
return fh.getNewName();
|
||||
case ADDED:
|
||||
case MODIFIED:
|
||||
case COPIED:
|
||||
case RENAMED:
|
||||
default:
|
||||
return entry.getNewName();
|
||||
}
|
||||
return key.getFileName();
|
||||
}
|
||||
|
||||
private void ensureCommentsVisible(final CommentDetail comments) {
|
||||
@@ -357,8 +344,7 @@ class PatchScriptBuilder {
|
||||
DisplayMethod displayMethod = DisplayMethod.DIFF;
|
||||
final SparseFileContent dst = new SparseFileContent();
|
||||
|
||||
void resolve(final Side other, final ObjectId within)
|
||||
throws CorruptEntityException {
|
||||
void resolve(final Side other, final ObjectId within) throws IOException {
|
||||
try {
|
||||
final TreeWalk tw = find(within);
|
||||
|
||||
@@ -405,8 +391,7 @@ class PatchScriptBuilder {
|
||||
dst.setMissingNewlineAtEnd(src.isMissingNewlineAtEnd());
|
||||
dst.setSize(src.size());
|
||||
} catch (IOException err) {
|
||||
log.error("Cannot read " + within.name() + ":" + path, err);
|
||||
throw new CorruptEntityException(patchKey);
|
||||
throw new IOException("Cannot read " + within.name() + ":" + path, err);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,14 +25,14 @@ import com.google.gerrit.client.reviewdb.PatchLineComment;
|
||||
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.server.FileTypeRegistry;
|
||||
import com.google.gerrit.server.GerritServer;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.Nullable;
|
||||
import com.google.gerrit.server.patch.DiffCache;
|
||||
import com.google.gerrit.server.patch.DiffCacheContent;
|
||||
import com.google.gerrit.server.patch.DiffCacheKey;
|
||||
import com.google.gerrit.server.patch.PatchList;
|
||||
import com.google.gerrit.server.patch.PatchListCache;
|
||||
import com.google.gerrit.server.patch.PatchListEntry;
|
||||
import com.google.gerrit.server.patch.PatchListKey;
|
||||
import com.google.gerrit.server.project.ChangeControl;
|
||||
import com.google.gerrit.server.project.NoSuchChangeException;
|
||||
import com.google.gerrit.server.rpc.Handler;
|
||||
@@ -44,10 +44,7 @@ import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.spearce.jgit.errors.RepositoryNotFoundException;
|
||||
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;
|
||||
|
||||
@@ -65,7 +62,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
|
||||
private final GerritServer server;
|
||||
private final FileTypeRegistry registry;
|
||||
private final DiffCache diffCache;
|
||||
private final PatchListCache patchListCache;
|
||||
private final ReviewDb db;
|
||||
private final ChangeControl.Factory changeControlFactory;
|
||||
|
||||
@@ -79,15 +76,19 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
private final Change.Id changeId;
|
||||
|
||||
private Change change;
|
||||
private Patch patch;
|
||||
private PatchSet patchSet;
|
||||
private Project.NameKey projectKey;
|
||||
private Repository git;
|
||||
|
||||
private ChangeControl control;
|
||||
|
||||
private ObjectId aId;
|
||||
|
||||
private ObjectId bId;
|
||||
|
||||
@Inject
|
||||
PatchScriptFactory(final GerritServer gs, final FileTypeRegistry ftr,
|
||||
final DiffCache dc, final ReviewDb db,
|
||||
final PatchListCache patchListCache, final ReviewDb db,
|
||||
final ChangeControl.Factory changeControlFactory,
|
||||
@Assisted final Patch.Key patchKey,
|
||||
@Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
|
||||
@@ -95,7 +96,7 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
@Assisted final PatchScriptSettings settings) {
|
||||
this.server = gs;
|
||||
this.registry = ftr;
|
||||
this.diffCache = dc;
|
||||
this.patchListCache = patchListCache;
|
||||
this.db = db;
|
||||
this.changeControlFactory = changeControlFactory;
|
||||
|
||||
@@ -115,13 +116,15 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
|
||||
control = changeControlFactory.validateFor(changeId);
|
||||
change = control.getChange();
|
||||
patch = db.patches().get(patchKey);
|
||||
|
||||
if (patch == null) {
|
||||
patchSet = db.patchSets().get(patchSetId);
|
||||
if (patchSet == null) {
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
|
||||
projectKey = change.getProject();
|
||||
aId = psa != null ? toObjectId(db, psa) : null;
|
||||
bId = toObjectId(db, psb);
|
||||
|
||||
try {
|
||||
git = server.openRepository(projectKey.get());
|
||||
} catch (RepositoryNotFoundException e) {
|
||||
@@ -129,32 +132,28 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
}
|
||||
|
||||
final String fileName = patchKey.getFileName();
|
||||
try {
|
||||
final PatchScriptBuilder b = newBuilder();
|
||||
final ObjectId bId = toObjectId(db, psb);
|
||||
final ObjectId aId = psa == null ? ancestor(bId) : toObjectId(db, psa);
|
||||
|
||||
final DiffCacheKey key = keyFor(bId, aId);
|
||||
final DiffCacheContent contentWS = get(key);
|
||||
final PatchList list = listFor(keyFor(settings.getWhitespace()));
|
||||
final PatchScriptBuilder b = newBuilder(list);
|
||||
final PatchListEntry contentWS = list.get(fileName);
|
||||
final CommentDetail comments = allComments(db);
|
||||
|
||||
final DiffCacheContent contentActual;
|
||||
if (settings.getWhitespace() != Whitespace.IGNORE_NONE) {
|
||||
final PatchListEntry contentActual;
|
||||
if (settings.getWhitespace() == Whitespace.IGNORE_NONE) {
|
||||
contentActual = contentWS;
|
||||
} else {
|
||||
// If we are ignoring whitespace in some form, we still need to know
|
||||
// where the post-image differs so we can ensure the post-image lines
|
||||
// are still packed for the client to display.
|
||||
//
|
||||
final PatchScriptSettings s = new PatchScriptSettings(settings);
|
||||
s.setWhitespace(Whitespace.IGNORE_NONE);
|
||||
contentActual = get(new DiffCacheKey(projectKey, aId, bId, patch, s));
|
||||
} else {
|
||||
contentActual = contentWS;
|
||||
contentActual = listFor(keyFor(Whitespace.IGNORE_NONE)).get(fileName);
|
||||
}
|
||||
|
||||
try {
|
||||
return b.toPatchScript(key, contentWS, comments, contentActual);
|
||||
} catch (CorruptEntityException e) {
|
||||
log.error("File content for " + key + " unavailable", e);
|
||||
return b.toPatchScript(contentWS, comments, contentActual);
|
||||
} catch (IOException e) {
|
||||
log.error("File content unavailable", e);
|
||||
throw new NoSuchChangeException(changeId, e);
|
||||
}
|
||||
} finally {
|
||||
@@ -162,21 +161,16 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
}
|
||||
}
|
||||
|
||||
private DiffCacheKey keyFor(final ObjectId bId, final ObjectId aId) {
|
||||
return new DiffCacheKey(projectKey, aId, bId, patch, settings);
|
||||
private PatchListKey keyFor(final Whitespace whitespace) {
|
||||
return new PatchListKey(projectKey, aId, bId, whitespace);
|
||||
}
|
||||
|
||||
private DiffCacheContent get(final DiffCacheKey key)
|
||||
private PatchList listFor(final PatchListKey key) {
|
||||
return patchListCache.get(key);
|
||||
}
|
||||
|
||||
private PatchScriptBuilder newBuilder(final PatchList list)
|
||||
throws NoSuchChangeException {
|
||||
final DiffCacheContent r = diffCache.get(key);
|
||||
if (r == null) {
|
||||
log.error("Cache get failed for " + key);
|
||||
throw new NoSuchChangeException(changeId);
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
private PatchScriptBuilder newBuilder() throws NoSuchChangeException {
|
||||
final PatchScriptSettings s = new PatchScriptSettings(settings);
|
||||
|
||||
final int ctx = settings.getContext();
|
||||
@@ -189,32 +183,11 @@ class PatchScriptFactory extends Handler<PatchScript> {
|
||||
|
||||
final PatchScriptBuilder b = new PatchScriptBuilder(registry);
|
||||
b.setRepository(git);
|
||||
b.setPatch(patch);
|
||||
b.setSettings(s);
|
||||
b.setTrees(list.getOldId(), list.getNewId());
|
||||
return b;
|
||||
}
|
||||
|
||||
private ObjectId ancestor(final ObjectId id) throws NoSuchChangeException {
|
||||
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 NoSuchChangeException(changeId, e);
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectId emptyTree() throws IOException {
|
||||
return new ObjectWriter(git).writeCanonicalTree(new byte[0]);
|
||||
}
|
||||
|
||||
private ObjectId toObjectId(final ReviewDb db, final PatchSet.Id psId)
|
||||
throws OrmException, NoSuchChangeException {
|
||||
if (!changeId.equals(psId.getParentKey())) {
|
||||
|
||||
@@ -786,8 +786,7 @@ final class Receive extends AbstractGitCommand {
|
||||
ps.setCreatedOn(change.getCreatedOn());
|
||||
ps.setUploader(me);
|
||||
|
||||
final PatchSetImporter imp =
|
||||
importFactory.create(db, project.getNameKey(), repo, c, ps, true);
|
||||
final PatchSetImporter imp = importFactory.create(db, c, ps, true);
|
||||
imp.setTransaction(txn);
|
||||
imp.run();
|
||||
|
||||
@@ -966,14 +965,9 @@ final class Receive extends AbstractGitCommand {
|
||||
ps.setCreatedOn(new Timestamp(System.currentTimeMillis()));
|
||||
ps.setUploader(currentUser.getAccountId());
|
||||
|
||||
final PatchSetImporter imp =
|
||||
importFactory.create(db, project.getNameKey(), repo, c, ps, true);
|
||||
final PatchSetImporter imp = importFactory.create(db, c, ps, true);
|
||||
imp.setTransaction(txn);
|
||||
try {
|
||||
imp.run();
|
||||
} catch (IOException e) {
|
||||
throw new OrmException(e);
|
||||
}
|
||||
imp.run();
|
||||
|
||||
final Ref mergedInto = findMergedInto(change.getDest().get(), c);
|
||||
final ReplaceResult result = new ReplaceResult();
|
||||
|
||||
@@ -14,38 +14,55 @@
|
||||
|
||||
package org.spearce.jgit.lib;
|
||||
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readFixInt32;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.readVarInt32;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeFixInt32;
|
||||
import static com.google.gerrit.server.ioutil.BasicSerialization.writeVarInt32;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.ObjectInputStream;
|
||||
import java.io.ObjectOutputStream;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
|
||||
public class ObjectIdSerialization {
|
||||
public static void write(final ObjectOutputStream out, final AnyObjectId id)
|
||||
public static void writeCanBeNull(final OutputStream out, final AnyObjectId id)
|
||||
throws IOException {
|
||||
if (id != null) {
|
||||
out.writeBoolean(true);
|
||||
out.writeInt(id.w1);
|
||||
out.writeInt(id.w2);
|
||||
out.writeInt(id.w3);
|
||||
out.writeInt(id.w4);
|
||||
out.writeInt(id.w5);
|
||||
writeVarInt32(out, 1);
|
||||
writeNotNull(out, id);
|
||||
} else {
|
||||
out.writeBoolean(false);
|
||||
writeVarInt32(out, 0);
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectId read(final ObjectInputStream in) throws IOException {
|
||||
if (in.readBoolean()) {
|
||||
final int w1 = in.readInt();
|
||||
final int w2 = in.readInt();
|
||||
final int w3 = in.readInt();
|
||||
final int w4 = in.readInt();
|
||||
final int w5 = in.readInt();
|
||||
return new ObjectId(w1, w2, w3, w4, w5);
|
||||
} else {
|
||||
return null;
|
||||
public static void writeNotNull(final OutputStream out, final AnyObjectId id)
|
||||
throws IOException {
|
||||
writeFixInt32(out, id.w1);
|
||||
writeFixInt32(out, id.w2);
|
||||
writeFixInt32(out, id.w3);
|
||||
writeFixInt32(out, id.w4);
|
||||
writeFixInt32(out, id.w5);
|
||||
}
|
||||
|
||||
public static ObjectId readCanBeNull(final InputStream in) throws IOException {
|
||||
switch (readVarInt32(in)) {
|
||||
case 0:
|
||||
return null;
|
||||
case 1:
|
||||
return readNotNull(in);
|
||||
default:
|
||||
throw new IOException("Invalid flag before ObjectId");
|
||||
}
|
||||
}
|
||||
|
||||
public static ObjectId readNotNull(final InputStream in) throws IOException {
|
||||
final int w1 = readFixInt32(in);
|
||||
final int w2 = readFixInt32(in);
|
||||
final int w3 = readFixInt32(in);
|
||||
final int w4 = readFixInt32(in);
|
||||
final int w5 = readFixInt32(in);
|
||||
return new ObjectId(w1, w2, w3, w4, w5);
|
||||
}
|
||||
|
||||
private ObjectIdSerialization() {
|
||||
}
|
||||
}
|
||||
|
||||
@@ -136,11 +136,6 @@ CREATE INDEX contributor_agreements_active
|
||||
ON contributor_agreements (active, short_name);
|
||||
|
||||
|
||||
-- *********************************************************************
|
||||
-- PatchAccess
|
||||
-- @PrimaryKey covers: byPatchSet
|
||||
|
||||
|
||||
-- *********************************************************************
|
||||
-- PatchLineCommentAccess
|
||||
-- @PrimaryKey covers: published, draft
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
-- Cluster hot tables by their primary method of access
|
||||
--
|
||||
ALTER TABLE patch_sets CLUSTER ON patch_sets_pkey;
|
||||
ALTER TABLE patches CLUSTER ON patches_pkey;
|
||||
ALTER TABLE change_messages CLUSTER ON change_messages_pkey;
|
||||
ALTER TABLE patch_comments CLUSTER ON patch_comments_pkey;
|
||||
ALTER TABLE patch_set_approvals CLUSTER ON patch_set_approvals_pkey;
|
||||
@@ -183,11 +182,6 @@ CREATE INDEX contributor_agreements_active
|
||||
ON contributor_agreements (active, short_name);
|
||||
|
||||
|
||||
-- *********************************************************************
|
||||
-- PatchAccess
|
||||
-- @PrimaryKey covers: byPatchSet
|
||||
|
||||
|
||||
-- *********************************************************************
|
||||
-- PatchLineCommentAccess
|
||||
-- @PrimaryKey covers: published, draft
|
||||
|
||||
6
src/main/webapp/WEB-INF/sql/upgrade017_018_mysql.sql
Normal file
6
src/main/webapp/WEB-INF/sql/upgrade017_018_mysql.sql
Normal file
@@ -0,0 +1,6 @@
|
||||
-- Upgrade: schema_version 17 to 18 (MySQL)
|
||||
--
|
||||
|
||||
DROP TABLE patches;
|
||||
|
||||
UPDATE schema_version SET version_nbr = 18;
|
||||
12
src/main/webapp/WEB-INF/sql/upgrade017_018_postgres.sql
Normal file
12
src/main/webapp/WEB-INF/sql/upgrade017_018_postgres.sql
Normal file
@@ -0,0 +1,12 @@
|
||||
-- Upgrade: schema_version 17 to 18 (PostgreSQL)
|
||||
--
|
||||
|
||||
BEGIN;
|
||||
|
||||
SELECT check_schema_version(17);
|
||||
|
||||
DROP TABLE patches;
|
||||
|
||||
UPDATE schema_version SET version_nbr = 18;
|
||||
|
||||
COMMIT;
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.client.reviewdb.Patch;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
public class PatchListEntryTest extends TestCase {
|
||||
public void testEmpty1() {
|
||||
final String name = "empty-file";
|
||||
final PatchListEntry e = PatchListEntry.empty(name);
|
||||
assertNull(e.getOldName());
|
||||
assertEquals(name, e.getNewName());
|
||||
|
||||
// I'm not sure why JGit is calling this binary, it may be because
|
||||
// there are no edits on the file, which is fine.
|
||||
//
|
||||
assertSame(Patch.PatchType.BINARY, e.getPatchType());
|
||||
assertSame(Patch.ChangeType.MODIFIED, e.getChangeType());
|
||||
assertNotNull(e.getFileHeader());
|
||||
assertTrue(e.getEdits().isEmpty());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user