summaryrefslogtreecommitdiffstats
path: root/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
diff options
context:
space:
mode:
Diffstat (limited to 'gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java')
-rw-r--r--gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java200
1 files changed, 200 insertions, 0 deletions
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
new file mode 100644
index 0000000000..71342ff6f4
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListCacheImpl.java
@@ -0,0 +1,200 @@
+// 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.common.data.PatchScriptSettings.Whitespace.IGNORE_NONE;
+
+import com.google.gerrit.common.data.PatchScriptSettings.Whitespace;
+import com.google.gerrit.reviewdb.Change;
+import com.google.gerrit.reviewdb.PatchSet;
+import com.google.gerrit.reviewdb.Project;
+import com.google.gerrit.server.cache.Cache;
+import com.google.gerrit.server.cache.CacheModule;
+import com.google.gerrit.server.cache.EvictionPolicy;
+import com.google.gerrit.server.cache.SelfPopulatingCache;
+import com.google.gerrit.server.git.GitRepositoryManager;
+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.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectWriter;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.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 PatchListCacheImpl implements 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) //
+ .memoryLimit(128) // very large items, cache only a few
+ .evictionPolicy(EvictionPolicy.LRU) // prefer most recent
+ ;
+ bind(PatchListCacheImpl.class);
+ bind(PatchListCache.class).to(PatchListCacheImpl.class);
+ }
+ };
+ }
+
+ private final GitRepositoryManager repoManager;
+ private final SelfPopulatingCache<PatchListKey, PatchList> self;
+
+ @Inject
+ PatchListCacheImpl(final GitRepositoryManager grm,
+ @Named(CACHE_NAME) final Cache<PatchListKey, PatchList> raw) {
+ repoManager = grm;
+ 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 = repoManager.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.eclipse.jgit.patch.Patch p = new org.eclipse.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]);
+ }
+}