Merge "Add configuration file for PEP-8 checker tool"
diff --git a/Documentation/dev-buck.txt b/Documentation/dev-buck.txt
index 7b7512f..c228bb9 100644
--- a/Documentation/dev-buck.txt
+++ b/Documentation/dev-buck.txt
@@ -17,20 +17,21 @@
   ant
 ----
 
-Make sure you have a bin/ directory in your home directory and that it is included in your path:
+Make sure you have a `bin/` directory in your home directory and that
+it is included in your path:
 
 ----
   mkdir ~/bin
   PATH=~/bin:$PATH
 ----
 
-Add a symbolic link in ~/bin to the buck executable:
+Add a symbolic link in `~/bin` to the buck executable:
 
 ----
   ln -s `pwd`/bin/buck ~/bin/
 ----
 
-Verify that buck is accessible:
+Verify that `buck` is accessible:
 
 ----
   which buck
@@ -58,6 +59,10 @@
 Expand the `gerrit` project, right-click on the `buck-out` folder, select
 'Properties', and then under 'Attributes' check 'Derived'.
 
+Note that if you make any changes in the project configuration that get
+saved to the `.project` file, for example adding Resource Filters on a
+folder, they will be overwritten the next time you run `buck build eclipse`.
+
 
 Refreshing the Classpath
 ~~~~~~~~~~~~~~~~~~~~~~~~
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index 0692454..58c836a 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -146,51 +146,6 @@
 ----
 
 
-[[test-rest-api]]
-Testing the REST API
-~~~~~~~~~~~~~~~~~~~~
-
-Basic testing of REST API functionality can be done with `curl`:
-
-----
-  curl http://localhost:8080/path/to/api/
-----
-
-By default, `curl` sends `GET` requests.  To test APIs with `PUT` or `POST`,
-an additional argument is required:
-
-----
- curl -X PUT http://localhost:8080/path/to/api/
- curl -X POST http://localhost:8080/path/to/api/
-----
-
-Some REST APIs accept data in the request body of `PUT` and `POST` requests.
-
-Test data can be included from a local file:
-
-----
-  curl -X PUT -d@testdata.txt --header "Content-Type: application/json" http://localhost:8080/path/to/api/
-----
-
-To test APIs that require authentication, the username and password must be specified on
-the command line:
-
-----
- curl --digest --user username:password http://localhost:8080/path/to/api
-----
-
-This makes it easy to switch users for testing of permissions.
-
-It is also possible to test with a username and password from the `.netrc`
-file (on Windows, `_netrc`):
-
-----
- curl --digest -n http://localhost:8080/a/path/to/api/
-----
-
-In both cases, the password should be the user's link:user-upload.html#http[HTTP password].
-
-
 Release Builds
 --------------
 
diff --git a/Documentation/dev-rest-api.txt b/Documentation/dev-rest-api.txt
new file mode 100644
index 0000000..869ac2b
--- /dev/null
+++ b/Documentation/dev-rest-api.txt
@@ -0,0 +1,81 @@
+Gerrit Code Review - REST API Developers' Notes
+===============================================
+
+This document is about developing the REST API.  For details of the
+actual APIs available in Gerrit, please see the
+link:rest-api.html[REST API interface reference].
+
+
+Testing REST API Functionality
+------------------------------
+
+
+Basic Testing
+~~~~~~~~~~~~~
+
+Basic testing of REST API functionality can be done with `curl`:
+
+----
+  curl http://localhost:8080/path/to/api/
+----
+
+By default, `curl` sends `GET` requests.  To test APIs with `PUT`, `POST`,
+or `DELETE`, an additional argument is required:
+
+----
+ curl -X PUT http://localhost:8080/path/to/api/
+ curl -X POST http://localhost:8080/path/to/api/
+ curl -X DELETE http://localhost:8080/path/to/api/
+----
+
+
+Sending Data in the Request
+~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Some REST APIs accept data in the request body of `PUT` and `POST` requests.
+
+Test data can be included from a local file:
+
+----
+  curl -X PUT -d@testdata.txt --header "Content-Type: application/json" http://localhost:8080/path/to/api/
+----
+
+
+Authentication
+~~~~~~~~~~~~~~
+
+To test APIs that require authentication, the username and password must be specified on
+the command line:
+
+----
+ curl --digest --user username:password http://localhost:8080/a/path/to/api/
+----
+
+This makes it easy to switch users for testing of permissions.
+
+It is also possible to test with a username and password from the `.netrc`
+file (on Windows, `_netrc`):
+
+----
+ curl --digest -n http://localhost:8080/a/path/to/api/
+----
+
+In both cases, the password should be the user's link:user-upload.html#http[HTTP password].
+
+
+Verifying Header Content
+~~~~~~~~~~~~~~~~~~~~~~~~
+
+To verify the headers returned from a REST API call, use `curl` in verbose mode:
+
+----
+  curl -v -n --digest -X DELETE http://localhost:8080/a/path/to/api/
+----
+
+The headers on both the request and the response will be printed.
+
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
+
diff --git a/Documentation/rest-api.txt b/Documentation/rest-api.txt
index 195ca9b..ccc8604 100644
--- a/Documentation/rest-api.txt
+++ b/Documentation/rest-api.txt
@@ -5,7 +5,7 @@
 The API is suitable for automated tools to build upon, as well as
 supporting some ad-hoc scripting use cases.
 
-See also: link:dev-readme.html#test-rest-api[Developer setup: Testing the REST API].
+See also: link:dev-rest-api.html[REST API Developers' Notes].
 
 Endpoints
 ---------
diff --git a/gerrit-acceptance-tests/BUCK b/gerrit-acceptance-tests/BUCK
index 9e31d66..6b4c2bf 100644
--- a/gerrit-acceptance-tests/BUCK
+++ b/gerrit-acceptance-tests/BUCK
@@ -24,6 +24,8 @@
     '//lib:junit',
     '//lib:servlet-api-3_0',
 
+    '//lib/log:impl_log4j',
+    '//lib/log:log4j',
     '//lib/guice:guice',
     '//lib/jgit:jgit',
     '//lib/jgit:junit',
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
index fecbb76..914e69f 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/PatchScript.java
@@ -48,6 +48,8 @@
   protected List<Edit> edits;
   protected DisplayMethod displayMethodA;
   protected DisplayMethod displayMethodB;
+  protected transient String mimeTypeA;
+  protected transient String mimeTypeB;
   protected CommentDetail comments;
   protected List<Patch> history;
   protected boolean hugeFile;
@@ -60,8 +62,9 @@
       final List<String> h, final AccountDiffPreference dp,
       final SparseFileContent ca, final SparseFileContent cb,
       final List<Edit> e, final DisplayMethod ma, final DisplayMethod mb,
-      final CommentDetail cd, final List<Patch> hist, final boolean hf,
-      final boolean id, final boolean idf, final boolean idt) {
+      final String mta, final String mtb, final CommentDetail cd,
+      final List<Patch> hist, final boolean hf, final boolean id,
+      final boolean idf, final boolean idt) {
     changeId = ck;
     changeType = ct;
     oldName = on;
@@ -75,6 +78,8 @@
     edits = e;
     displayMethodA = ma;
     displayMethodB = mb;
+    mimeTypeA = mta;
+    mimeTypeB = mtb;
     comments = cd;
     history = hist;
     hugeFile = hf;
@@ -170,6 +175,14 @@
     return b;
   }
 
+  public String getMimeTypeA() {
+    return mimeTypeA;
+  }
+
+  public String getMimeTypeB() {
+    return mimeTypeB;
+  }
+
   public List<Edit> getEdits() {
     return edits;
   }
diff --git a/gerrit-gwtexpui/BUCK b/gerrit-gwtexpui/BUCK
index 4e6774d..41625ff 100644
--- a/gerrit-gwtexpui/BUCK
+++ b/gerrit-gwtexpui/BUCK
@@ -90,6 +90,7 @@
   deps = [
     ':SafeHtml',
     '//lib:junit',
+    '//lib/gwt:user',
     '//lib/gwt:dev',
   ],
   source_under_test = [':SafeHtml'],
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
index 639a1cf..3d1d30e 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/account/MyPreferencesScreen.java
@@ -218,7 +218,7 @@
         p.getTimeFormat());
     relativeDateInChangeTable.setValue(p.isRelativeDateInChangeTable());
     setListBox(commentVisibilityStrategy,
-        AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_MOST_RECENT,
+        AccountGeneralPreferences.CommentVisibilityStrategy.EXPAND_RECENT,
         p.getCommentVisibilityStrategy());
   }
 
@@ -285,7 +285,7 @@
         AccountGeneralPreferences.TimeFormat.values()));
     p.setRelativeDateInChangeTable(relativeDateInChangeTable.getValue());
     p.setCommentVisibilityStrategy(getListBox(commentVisibilityStrategy,
-        CommentVisibilityStrategy.EXPAND_MOST_RECENT,
+        CommentVisibilityStrategy.EXPAND_RECENT,
         CommentVisibilityStrategy.values()));
 
     enable(false);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
index 7214906..6819086 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/changes/ChangeScreen.java
@@ -408,7 +408,7 @@
     final Timestamp aged = new Timestamp(System.currentTimeMillis() - AGE);
 
     CommentVisibilityStrategy commentVisibilityStrategy =
-        CommentVisibilityStrategy.EXPAND_MOST_RECENT;
+        CommentVisibilityStrategy.EXPAND_RECENT;
     if (Gerrit.isSignedIn()) {
       commentVisibilityStrategy = Gerrit.getUserAccount()
           .getGeneralPreferences().getCommentVisibilityStrategy();
@@ -443,16 +443,16 @@
       switch (commentVisibilityStrategy) {
         case COLLAPSE_ALL:
           break;
-        case EXPAND_RECENT:
-          isOpen = isRecent;
-          break;
         case EXPAND_ALL:
           isOpen = true;
           break;
         case EXPAND_MOST_RECENT:
-        default:
           isOpen = i == msgList.size() - 1;
           break;
+        case EXPAND_RECENT:
+        default:
+          isOpen = isRecent;
+          break;
       }
       cp.setOpen(isOpen);
       comments.add(cp);
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
index 3174757..12289aa 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchDetailServiceImpl.java
@@ -20,6 +20,7 @@
 import com.google.gerrit.common.data.ReviewResult;
 import com.google.gerrit.common.errors.NoSuchEntityException;
 import com.google.gerrit.httpd.rpc.BaseServiceImplementation;
+import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.httpd.rpc.changedetail.ChangeDetailFactory;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
@@ -29,7 +30,9 @@
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.CurrentUser;
 import com.google.gerrit.server.changedetail.DeleteDraftPatchSet;
+import com.google.gerrit.server.patch.PatchScriptFactory;
 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.project.NoSuchProjectException;
 import com.google.gwtjsonrpc.common.AsyncCallback;
@@ -49,6 +52,7 @@
   private final PatchScriptFactory.Factory patchScriptFactoryFactory;
   private final SaveDraft.Factory saveDraftFactory;
   private final ChangeDetailFactory.Factory changeDetailFactory;
+  private final ChangeControl.Factory changeControlFactory;
 
   @Inject
   PatchDetailServiceImpl(final Provider<ReviewDb> schema,
@@ -56,13 +60,15 @@
       final DeleteDraftPatchSet.Factory deleteDraftPatchSetFactory,
       final PatchScriptFactory.Factory patchScriptFactoryFactory,
       final SaveDraft.Factory saveDraftFactory,
-      final ChangeDetailFactory.Factory changeDetailFactory) {
+      final ChangeDetailFactory.Factory changeDetailFactory,
+      final ChangeControl.Factory changeControlFactory) {
     super(schema, currentUser);
 
     this.deleteDraftPatchSetFactory = deleteDraftPatchSetFactory;
     this.patchScriptFactoryFactory = patchScriptFactoryFactory;
     this.saveDraftFactory = saveDraftFactory;
     this.changeDetailFactory = changeDetailFactory;
+    this.changeControlFactory = changeControlFactory;
   }
 
   public void patchScript(final Patch.Key patchKey, final PatchSet.Id psa,
@@ -72,7 +78,16 @@
       callback.onFailure(new NoSuchEntityException());
       return;
     }
-    patchScriptFactoryFactory.create(patchKey, psa, psb, dp).to(callback);
+
+    new Handler<PatchScript>() {
+      @Override
+      public PatchScript call() throws Exception {
+        Change.Id changeId = patchKey.getParentKey().getParentKey();
+        ChangeControl control = changeControlFactory.validateFor(changeId);
+        return patchScriptFactoryFactory.create(
+            control, patchKey.getFileName(), psa, psb, dp).call();
+      }
+    }.to(callback);
   }
 
   public void saveDraft(final PatchLineComment comment,
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
index d1f5b24..4e69b14 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchModule.java
@@ -28,11 +28,9 @@
     install(new FactoryModule() {
       @Override
       protected void configure() {
-        factory(PatchScriptFactory.Factory.class);
         factory(SaveDraft.Factory.class);
       }
     });
-    bind(PatchScriptBuilder.class);
     rpc(PatchDetailServiceImpl.class);
   }
 }
diff --git a/gerrit-pgm/BUCK b/gerrit-pgm/BUCK
index ef0b532..0746bdf 100644
--- a/gerrit-pgm/BUCK
+++ b/gerrit-pgm/BUCK
@@ -6,6 +6,7 @@
     '//gerrit-cache-h2:cache-h2',
     '//gerrit-common:server',
     '//gerrit-extension-api:api',
+    '//gerrit-gwtexpui:server',
     '//gerrit-httpd:httpd',
     '//gerrit-openid:openid',
     '//gerrit-server:common_rules',
@@ -46,8 +47,11 @@
   srcs = glob(['src/test/java/**/*.java']),
   deps = [
     ':pgm',
+    '//gerrit-server:server',
     '//lib:junit',
     '//lib:easymock',
+    '//lib/guice:guice',
+    '//lib/jgit:jgit',
     '//lib/jgit:junit',
   ],
   source_under_test = [':pgm'],
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
new file mode 100644
index 0000000..b792f68
--- /dev/null
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/HiddenErrorHandler.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2013 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.http.jetty;
+
+import static javax.servlet.http.HttpServletResponse.SC_INTERNAL_SERVER_ERROR;
+
+import com.google.common.base.Charsets;
+import com.google.common.base.Strings;
+import com.google.gwtexpui.server.CacheHeaders;
+
+import org.eclipse.jetty.http.HttpHeaders;
+import org.eclipse.jetty.server.AbstractHttpConnection;
+import org.eclipse.jetty.server.Request;
+import org.eclipse.jetty.server.handler.ErrorHandler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import javax.servlet.ServletOutputStream;
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+class HiddenErrorHandler extends ErrorHandler {
+  private static final Logger log = LoggerFactory.getLogger(HiddenErrorHandler.class);
+  private static final byte[] MSG = "Internal server error\n".getBytes(Charsets.ISO_8859_1);
+
+  public void handle(String target, Request baseRequest,
+      HttpServletRequest req, HttpServletResponse res) throws IOException {
+    AbstractHttpConnection.getCurrentConnection().getRequest().setHandled(true);
+    try {
+      log(req);
+    } finally {
+      replyGenericError(res);
+    }
+  }
+
+  private void replyGenericError(HttpServletResponse res) throws IOException {
+    if (!res.isCommitted()) {
+      res.reset();
+      res.setStatus(SC_INTERNAL_SERVER_ERROR);
+      res.setHeader(HttpHeaders.CONTENT_TYPE, "text/plain; charset=ISO-8859-1");
+      res.setContentLength(MSG.length);
+      try {
+        CacheHeaders.setNotCacheable(res);
+      } finally {
+        ServletOutputStream out = res.getOutputStream();
+        try {
+          out.write(MSG);
+        } finally {
+          out.close();
+        }
+      }
+    }
+  }
+
+  private static void log(HttpServletRequest req) {
+    Throwable err = (Throwable)req.getAttribute("javax.servlet.error.exception");
+    if (err != null) {
+      String uri = req.getRequestURI();
+      if (!Strings.isNullOrEmpty(req.getQueryString())) {
+        uri += "?" + req.getQueryString();
+      }
+      log.error(String.format("Error in %s %s", req.getMethod(), uri), err);
+    }
+  }
+}
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
index dd71175..37637d5 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/http/jetty/JettyServer.java
@@ -332,6 +332,7 @@
     // for Gerrit plug-ins to enable user-level sessions.
     //
     app.setSessionHandler(new SessionHandler());
+    app.setErrorHandler(new HiddenErrorHandler());
 
     // This is the path we are accessed by clients within our domain.
     //
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
index ad0f130..abdf879 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/AccountGeneralPreferences.java
@@ -249,7 +249,7 @@
 
   public CommentVisibilityStrategy getCommentVisibilityStrategy() {
     if (commentVisibilityStrategy == null) {
-      return CommentVisibilityStrategy.EXPAND_MOST_RECENT;
+      return CommentVisibilityStrategy.EXPAND_RECENT;
     }
     return CommentVisibilityStrategy.valueOf(commentVisibilityStrategy);
   }
diff --git a/gerrit-server/BUCK b/gerrit-server/BUCK
index 3132075..1d676a0 100644
--- a/gerrit-server/BUCK
+++ b/gerrit-server/BUCK
@@ -12,6 +12,7 @@
     '//gerrit-extension-api:api',
     '//gerrit-patch-commonsnet:commons-net',
     '//gerrit-patch-jgit:server',
+    '//gerrit-prettify:server',
     '//gerrit-reviewdb:server',
     '//gerrit-util-cli:cli',
     '//gerrit-util-ssl:ssl',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
new file mode 100644
index 0000000..ac2d8ba
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/GetDiff.java
@@ -0,0 +1,335 @@
+// Copyright (C) 2013 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.change;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import com.google.common.base.Objects;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
+import com.google.gerrit.common.data.PatchScript;
+import com.google.gerrit.common.data.PatchScript.DisplayMethod;
+import com.google.gerrit.common.data.PatchScript.FileMode;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.prettify.common.SparseFileContent;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountDiffPreference;
+import com.google.gerrit.reviewdb.client.Patch.ChangeType;
+import com.google.gerrit.reviewdb.client.PatchSet;
+import com.google.gerrit.server.patch.PatchScriptFactory;
+import com.google.gerrit.server.project.NoSuchChangeException;
+import com.google.gerrit.server.git.LargeObjectException;
+import com.google.gwtorm.server.OrmException;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import org.eclipse.jgit.diff.Edit;
+import org.eclipse.jgit.diff.ReplaceEdit;
+import org.kohsuke.args4j.CmdLineException;
+import org.kohsuke.args4j.CmdLineParser;
+import org.kohsuke.args4j.NamedOptionDef;
+import org.kohsuke.args4j.Option;
+import org.kohsuke.args4j.OptionDef;
+import org.kohsuke.args4j.spi.OptionHandler;
+import org.kohsuke.args4j.spi.Parameters;
+import org.kohsuke.args4j.spi.Setter;
+
+import java.util.List;
+
+public class GetDiff implements RestReadView<FileResource> {
+  private final PatchScriptFactory.Factory patchScriptFactoryFactory;
+  private final Provider<Revisions> revisions;
+
+  @Option(name = "--base", metaVar = "REVISION")
+  String base;
+
+  @Option(name = "--ignore-whitespace")
+  IgnoreWhitespace ignoreWhitespace = IgnoreWhitespace.NONE;
+
+  @Option(name = "--context", handler = ContextOptionHandler.class)
+  short context = AccountDiffPreference.DEFAULT_CONTEXT;
+
+  @Option(name = "--intraline")
+  boolean intraline;
+
+  @Inject
+  GetDiff(PatchScriptFactory.Factory patchScriptFactoryFactory,
+      Provider<Revisions> revisions) {
+    this.patchScriptFactoryFactory = patchScriptFactoryFactory;
+    this.revisions = revisions;
+  }
+
+  @Override
+  public Object apply(FileResource resource)
+      throws OrmException, NoSuchChangeException, LargeObjectException, ResourceNotFoundException {
+    PatchSet.Id basePatchSet = null;
+    if (base != null) {
+      RevisionResource baseResource = revisions.get().parse(
+          resource.getRevision().getChangeResource(), IdString.fromDecoded(base));
+      basePatchSet = baseResource.getPatchSet().getId();
+    }
+    AccountDiffPreference prefs = new AccountDiffPreference(new Account.Id(0));
+    prefs.setIgnoreWhitespace(ignoreWhitespace.whitespace);
+    prefs.setContext(context);
+    prefs.setIntralineDifference(intraline);
+
+    PatchScript ps = patchScriptFactoryFactory.create(
+        resource.getRevision().getControl(),
+        resource.getPatchKey().getFileName(),
+        basePatchSet,
+        resource.getPatchKey().getParentKey(),
+        prefs)
+          .call();
+
+    Content content = new Content(ps);
+    for (Edit edit : ps.getEdits()) {
+      if (edit.getType() == Edit.Type.EMPTY) {
+        continue;
+      }
+      content.addCommon(edit.getBeginA());
+
+      checkState(content.nextA == edit.getBeginA(),
+          "nextA = %d; want %d", content.nextA, edit.getBeginA());
+      checkState(content.nextB == edit.getBeginB(),
+          "nextB = %d; want %d", content.nextB, edit.getBeginB());
+      switch (edit.getType()) {
+        case DELETE:
+        case INSERT:
+        case REPLACE:
+          List<Edit> internalEdit = edit instanceof ReplaceEdit
+            ? ((ReplaceEdit) edit).getInternalEdits()
+            : null;
+          content.addDiff(edit.getEndA(), edit.getEndB(), internalEdit);
+          break;
+        case EMPTY:
+        default:
+          throw new IllegalStateException();
+      }
+    }
+    content.addCommon(ps.getA().size());
+
+    Result result = new Result();
+    if (ps.getDisplayMethodA() != DisplayMethod.NONE) {
+      result.metaA = new FileMeta();
+      result.metaA.name = Objects.firstNonNull(ps.getOldName(), ps.getNewName());
+      result.metaA.setContentType(ps.getFileModeA(), ps.getMimeTypeA());
+    }
+
+    if (ps.getDisplayMethodB() != DisplayMethod.NONE) {
+      result.metaB = new FileMeta();
+      result.metaB.name = ps.getNewName();
+      result.metaB.setContentType(ps.getFileModeB(), ps.getMimeTypeB());
+    }
+
+    if (intraline) {
+      if (ps.hasIntralineTimeout()) {
+        result.intralineStatus = IntraLineStatus.TIMEOUT;
+      } else if (ps.hasIntralineFailure()) {
+        result.intralineStatus = IntraLineStatus.FAILURE;
+      } else {
+        result.intralineStatus = IntraLineStatus.OK;
+      }
+    }
+
+    result.changeType = ps.getChangeType();
+    if (ps.getPatchHeader().size() > 0) {
+      result.diffHeader = ps.getPatchHeader();
+    }
+    result.content = content.lines;
+    return result;
+  }
+
+  static class Result {
+    FileMeta metaA;
+    FileMeta metaB;
+    IntraLineStatus intralineStatus;
+    ChangeType changeType;
+    List<String> diffHeader;
+    List<ContentEntry> content;
+  }
+
+  static class FileMeta {
+    String name;
+    String contentType;
+    String url;
+
+    void setContentType(FileMode fileMode, String mimeType) {
+      switch (fileMode) {
+        case FILE:
+          contentType = mimeType;
+          break;
+        case GITLINK:
+          contentType = "x-git/gitlink";
+          break;
+        case SYMLINK:
+          contentType = "x-git/symlink";
+          break;
+        default:
+          throw new IllegalStateException("file mode: " + fileMode);
+      }
+    }
+  }
+
+  enum IntraLineStatus {
+    OK,
+    TIMEOUT,
+    FAILURE;
+  }
+
+  private static class Content {
+    final List<ContentEntry> lines;
+    final SparseFileContent fileA;
+    final SparseFileContent fileB;
+
+    int nextA;
+    int nextB;
+
+    Content(PatchScript ps) {
+      lines = Lists.newArrayListWithExpectedSize(ps.getEdits().size() + 2);
+      fileA = ps.getA();
+      fileB = ps.getB();
+    }
+
+    void addCommon(int end) {
+      end = Math.min(end, fileA.size());
+      if (nextA >= end) {
+        return;
+      }
+      nextB += end - nextA;
+
+      while (nextA < end) {
+        if (fileA.contains(nextA)) {
+          ContentEntry e = entry();
+          e.ab = Lists.newArrayListWithCapacity(end - nextA);
+          for (int i = nextA; i == nextA && i < end; i = fileA.next(i), nextA++) {
+            e.ab.add(fileA.get(i));
+          }
+        } else {
+          int endRegion = Math.min(end,
+              (nextA == 0) ? fileA.first() : fileA.next(nextA - 1));
+          ContentEntry e = entry();
+          e.skip = endRegion - nextA;
+          nextA = endRegion;
+        }
+      }
+    }
+
+    void addDiff(int endA, int endB, List<Edit> internalEdit) {
+      int lenA = endA - nextA;
+      int lenB = endB - nextB;
+      checkState(lenA > 0 || lenB > 0);
+
+      ContentEntry e = entry();
+      if (lenA > 0) {
+        e.a = Lists.newArrayListWithCapacity(lenA);
+        for (; nextA < endA; nextA++) {
+          e.a.add(fileA.get(nextA));
+        }
+      }
+      if (lenB > 0) {
+        e.b = Lists.newArrayListWithCapacity(lenB);
+        for (; nextB < endB; nextB++) {
+          e.b.add(fileB.get(nextB));
+        }
+      }
+      if (internalEdit != null && !internalEdit.isEmpty()) {
+        e.editA = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        e.editB = Lists.newArrayListWithCapacity(internalEdit.size() * 2);
+        for (Edit edit : internalEdit) {
+          if (edit.getBeginA() != edit.getEndA()) {
+            e.editA.add(ImmutableList.of(edit.getBeginA(), edit.getEndA() - edit.getBeginA()));
+          }
+          if (edit.getBeginB() != edit.getEndB()) {
+            e.editB.add(ImmutableList.of(edit.getBeginB(), edit.getEndB() - edit.getBeginB()));
+          }
+        }
+      }
+    }
+
+    private ContentEntry entry() {
+      ContentEntry e = new ContentEntry();
+      lines.add(e);
+      return e;
+    }
+  }
+
+  enum IgnoreWhitespace {
+    NONE(AccountDiffPreference.Whitespace.IGNORE_NONE),
+    TRAILING(AccountDiffPreference.Whitespace.IGNORE_SPACE_AT_EOL),
+    CHANGED(AccountDiffPreference.Whitespace.IGNORE_SPACE_CHANGE),
+    ALL(AccountDiffPreference.Whitespace.IGNORE_ALL_SPACE);
+
+    private final AccountDiffPreference.Whitespace whitespace;
+
+    private IgnoreWhitespace(AccountDiffPreference.Whitespace whitespace) {
+      this.whitespace = whitespace;
+    }
+  }
+
+  static final class ContentEntry {
+    // Common lines to both sides.
+    List<String> ab;
+    // Lines of a.
+    List<String> a;
+    // Lines of b.
+    List<String> b;
+
+    // A list of changed sections of the of the corresponding line list.
+    // Each entry is a character <offset, length> pair. The offset is from the
+    // beginning of the first line in the list. Also, the offset includes an
+    // implied trailing newline character for each line.
+    List<List<Integer>> editA;
+    List<List<Integer>> editB;
+
+    // Number of lines to skip on both sides.
+    Integer skip;
+  }
+
+  public static class ContextOptionHandler extends OptionHandler<Short> {
+    public ContextOptionHandler(
+        CmdLineParser parser, OptionDef option, Setter<Short> setter) {
+      super(parser, option, setter);
+    }
+
+    @Override
+    public final int parseArguments(final Parameters params)
+        throws CmdLineException {
+      final String value = params.getParameter(0);
+      short context;
+      if ("all".equalsIgnoreCase(value)) {
+        context = AccountDiffPreference.WHOLE_FILE_CONTEXT;
+      } else {
+        try {
+          context = Short.parseShort(value, 10);
+          if (context < 0) {
+            throw new NumberFormatException();
+          }
+        } catch (NumberFormatException e) {
+          throw new CmdLineException(owner,
+              String.format("\"%s\" is not a valid value for \"%s\"",
+                  value, ((NamedOptionDef) option).name()));
+        }
+      }
+      setter.addValue(context);
+      return 1;
+    }
+
+    @Override
+    public final String getDefaultMetaVariable() {
+      return "ALL|# LINES";
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
index e088e4b..4c87d98 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Module.java
@@ -85,6 +85,7 @@
     put(FILE_KIND, "reviewed").to(PutReviewed.class);
     delete(FILE_KIND, "reviewed").to(DeleteReviewed.class);
     get(FILE_KIND, "content").to(GetContent.class);
+    get(FILE_KIND, "diff").to(GetDiff.class);
 
     install(new FactoryModule() {
       @Override
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
index ec6b122..358e5fc 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/Rebase.java
@@ -39,11 +39,11 @@
   public static class Input {
   }
 
-  private final RebaseChange rebaseChange;
+  private final Provider<RebaseChange> rebaseChange;
   private final ChangeJson json;
 
   @Inject
-  public Rebase(RebaseChange rebaseChange, ChangeJson json) {
+  public Rebase(Provider<RebaseChange> rebaseChange, ChangeJson json) {
     this.rebaseChange = rebaseChange;
     this.json = json;
   }
@@ -62,7 +62,7 @@
     }
 
     try {
-      rebaseChange.rebase(rsrc.getPatchSet().getId(), rsrc.getUser());
+      rebaseChange.get().rebase(rsrc.getPatchSet().getId(), rsrc.getUser());
     } catch (InvalidChangeOperationException e) {
       throw new ResourceConflictException(e.getMessage());
     } catch (IOException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
index cb4c2c3..37cbdf7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/changedetail/RebaseChange.java
@@ -75,6 +75,7 @@
   private final ChangeHookRunner hooks;
   private final MergeUtil.Factory mergeUtilFactory;
   private final ProjectCache projectCache;
+  private final IdentifiedUser currentUser;
 
   @Inject
   RebaseChange(final ChangeControl.GenericFactory changeControlFactory,
@@ -85,7 +86,8 @@
       final RebasedPatchSetSender.Factory rebasedPatchSetSenderFactory,
       final ChangeHookRunner hooks,
       final MergeUtil.Factory mergeUtilFactory,
-      final ProjectCache projectCache) {
+      final ProjectCache projectCache,
+      final IdentifiedUser currentUser) {
     this.changeControlFactory = changeControlFactory;
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.db = db;
@@ -96,6 +98,7 @@
     this.hooks = hooks;
     this.mergeUtilFactory = mergeUtilFactory;
     this.projectCache = projectCache;
+    this.currentUser = currentUser;
   }
 
   /**
@@ -152,10 +155,14 @@
       final RevCommit baseCommit =
           rw.parseCommit(ObjectId.fromString(baseRev));
 
+      PersonIdent committerIdent =
+          currentUser.newCommitterIdent(myIdent.getWhen(),
+              myIdent.getTimeZone());
+
       final PatchSet newPatchSet =
           rebase(git, rw, inserter, patchSetId, change, uploader.getAccountId(), baseCommit,
               mergeUtilFactory.create(
-                  changeControl.getProjectControl().getProjectState(), true));
+                  changeControl.getProjectControl().getProjectState(), true), committerIdent);
 
       final Set<Account.Id> oldReviewers = Sets.newHashSet();
       final Set<Account.Id> oldCC = Sets.newHashSet();
@@ -307,7 +314,7 @@
   public PatchSet rebase(final Repository git, final RevWalk revWalk,
       final ObjectInserter inserter, final PatchSet.Id patchSetId,
       final Change chg, final Account.Id uploader, final RevCommit baseCommit,
-      final MergeUtil mergeUtil) throws NoSuchChangeException,
+      final MergeUtil mergeUtil, PersonIdent committerIdent) throws NoSuchChangeException,
       OrmException, IOException, InvalidChangeOperationException,
       PathConflictException {
     Change change = chg;
@@ -316,7 +323,7 @@
     final RevCommit rebasedCommit;
     ObjectId oldId = ObjectId.fromString(originalPatchSet.getRevision().get());
     ObjectId newId = rebaseCommit(git, inserter, revWalk.parseCommit(oldId),
-        baseCommit, mergeUtil, myIdent);
+        baseCommit, mergeUtil, committerIdent);
 
     rebasedCommit = revWalk.parseCommit(newId);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
index 62c6863..a7574c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GerritGlobalModule.java
@@ -92,6 +92,7 @@
 import com.google.gerrit.server.mail.ReplacePatchSetSender;
 import com.google.gerrit.server.mail.VelocityRuntimeProvider;
 import com.google.gerrit.server.patch.PatchListCacheImpl;
+import com.google.gerrit.server.patch.PatchScriptFactory;
 import com.google.gerrit.server.patch.PatchSetInfoFactory;
 import com.google.gerrit.server.project.AccessControlModule;
 import com.google.gerrit.server.project.ChangeControl;
@@ -186,6 +187,7 @@
     factory(MergedSender.Factory.class);
     factory(MergeFailSender.Factory.class);
     factory(MergeUtil.Factory.class);
+    factory(PatchScriptFactory.Factory.class);
     factory(PerformCreateGroup.Factory.class);
     factory(PerformRenameGroup.Factory.class);
     factory(PluginUser.Factory.class);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
index 8490ea1..77c4863 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/RebaseIfNecessary.java
@@ -25,6 +25,7 @@
 import com.google.gwtorm.server.OrmException;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.PersonIdent;
 
 import java.io.IOException;
 import java.util.HashMap;
@@ -35,12 +36,14 @@
 
   private final RebaseChange rebaseChange;
   private final Map<Change.Id, CodeReviewCommit> newCommits;
+  private final PersonIdent committerIdent;
 
   RebaseIfNecessary(final SubmitStrategy.Arguments args,
-      final RebaseChange rebaseChange) {
+      final RebaseChange rebaseChange, PersonIdent committerIdent) {
     super(args);
     this.rebaseChange = rebaseChange;
     this.newCommits = new HashMap<Change.Id, CodeReviewCommit>();
+    this.committerIdent = committerIdent;
   }
 
   @Override
@@ -77,7 +80,7 @@
                 rebaseChange.rebase(args.repo, args.rw, args.inserter,
                     n.patchsetId, n.change,
                     args.mergeUtil.getSubmitter(n.patchsetId).getAccountId(),
-                    newMergeTip, args.mergeUtil);
+                    newMergeTip, args.mergeUtil, committerIdent);
             List<PatchSetApproval> approvals = Lists.newArrayList();
             for (PatchSetApproval a : args.mergeUtil.getApprovalsForCommit(n)) {
               approvals.add(new PatchSetApproval(newPatchSet.getId(), a));
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
index 8bf831c..d43a756 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmitStrategyFactory.java
@@ -93,7 +93,7 @@
       case MERGE_IF_NECESSARY:
         return new MergeIfNecessary(args);
       case REBASE_IF_NECESSARY:
-        return new RebaseIfNecessary(args, rebaseChange);
+        return new RebaseIfNecessary(args, rebaseChange, myIdent);
       default:
         final String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
index ff9e6cf..852165c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListEntry.java
@@ -171,7 +171,10 @@
     final List<String> headerLines = new ArrayList<String>(m.size() - 1);
     for (int i = 1; i < m.size() - 1; i++) {
       final int b = m.get(i);
-      final int e = m.get(i + 1);
+      int e = m.get(i + 1);
+      if (header[e - 1] == '\n') {
+        e--;
+      }
       headerLines.add(RawParseUtils.decode(Constants.CHARSET, header, b, e));
     }
     return headerLines;
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
similarity index 97%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
index 5019403..e74d5a1 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptBuilder.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptBuilder.java
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd.rpc.patch;
+package com.google.gerrit.server.patch;
 
 import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
@@ -26,11 +26,6 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.server.FileTypeRegistry;
-import com.google.gerrit.server.patch.IntraLineDiff;
-import com.google.gerrit.server.patch.IntraLineDiffKey;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.patch.PatchListEntry;
-import com.google.gerrit.server.patch.Text;
 import com.google.inject.Inject;
 
 import eu.medsea.mimeutil.MimeType;
@@ -216,7 +211,8 @@
     return new PatchScript(change.getKey(), content.getChangeType(),
         content.getOldName(), content.getNewName(), a.fileMode, b.fileMode,
         content.getHeaderLines(), diffPrefs, a.dst, b.dst, edits,
-        a.displayMethod, b.displayMethod, comments, history, hugeFile,
+        a.displayMethod, b.displayMethod, a.mimeType.toString(),
+        b.mimeType.toString(), comments, history, hugeFile,
         intralineDifferenceIsPossible, intralineFailure, intralineTimeout);
   }
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
similarity index 88%
rename from gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
rename to gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
index e0ec465..b46f1e7 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/patch/PatchScriptFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchScriptFactory.java
@@ -12,11 +12,10 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package com.google.gerrit.httpd.rpc.patch;
+package com.google.gerrit.server.patch;
 
 import com.google.gerrit.common.data.CommentDetail;
 import com.google.gerrit.common.data.PatchScript;
-import com.google.gerrit.httpd.rpc.Handler;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.AccountDiffPreference;
 import com.google.gerrit.reviewdb.client.Change;
@@ -32,11 +31,6 @@
 import com.google.gerrit.server.account.AccountInfoCacheFactory;
 import com.google.gerrit.server.git.GitRepositoryManager;
 import com.google.gerrit.server.git.LargeObjectException;
-import com.google.gerrit.server.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.patch.PatchListNotAvailableException;
 import com.google.gerrit.server.project.ChangeControl;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gwtorm.server.OrmException;
@@ -55,13 +49,16 @@
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.concurrent.Callable;
 
 import javax.annotation.Nullable;
 
 
-class PatchScriptFactory extends Handler<PatchScript> {
-  interface Factory {
-    PatchScriptFactory create(Patch.Key patchKey,
+public class PatchScriptFactory implements Callable<PatchScript> {
+  public interface Factory {
+    PatchScriptFactory create(
+        ChangeControl control,
+        String fileName,
         @Assisted("patchSetA") PatchSet.Id patchSetA,
         @Assisted("patchSetB") PatchSet.Id patchSetB,
         AccountDiffPreference diffPrefs);
@@ -74,20 +71,17 @@
   private final Provider<PatchScriptBuilder> builderFactory;
   private final PatchListCache patchListCache;
   private final ReviewDb db;
-  private final ChangeControl.Factory changeControlFactory;
   private final AccountInfoCacheFactory.Factory aicFactory;
 
-  private final Patch.Key patchKey;
+  private final String fileName;
   @Nullable
   private final PatchSet.Id psa;
   private final PatchSet.Id psb;
   private final AccountDiffPreference diffPrefs;
 
-  private final PatchSet.Id patchSetId;
   private final Change.Id changeId;
 
   private Change change;
-  private PatchSet patchSet;
   private Project.NameKey projectKey;
   private ChangeControl control;
   private ObjectId aId;
@@ -99,9 +93,9 @@
   PatchScriptFactory(final GitRepositoryManager grm,
       Provider<PatchScriptBuilder> builderFactory,
       final PatchListCache patchListCache, final ReviewDb db,
-      final ChangeControl.Factory changeControlFactory,
       final AccountInfoCacheFactory.Factory aicFactory,
-      @Assisted final Patch.Key patchKey,
+      @Assisted ChangeControl control,
+      @Assisted final String fileName,
       @Assisted("patchSetA") @Nullable final PatchSet.Id patchSetA,
       @Assisted("patchSetB") final PatchSet.Id patchSetB,
       @Assisted final AccountDiffPreference diffPrefs) {
@@ -109,16 +103,15 @@
     this.builderFactory = builderFactory;
     this.patchListCache = patchListCache;
     this.db = db;
-    this.changeControlFactory = changeControlFactory;
+    this.control = control;
     this.aicFactory = aicFactory;
 
-    this.patchKey = patchKey;
+    this.fileName = fileName;
     this.psa = patchSetA;
     this.psb = patchSetB;
     this.diffPrefs = diffPrefs;
 
-    patchSetId = patchKey.getParentKey();
-    changeId = patchSetId.getParentKey();
+    changeId = patchSetB.getParentKey();
   }
 
   @Override
@@ -127,13 +120,8 @@
     validatePatchSetId(psa);
     validatePatchSetId(psb);
 
-    control = changeControlFactory.validateFor(changeId);
     change = control.getChange();
     projectKey = change.getProject();
-    patchSet = db.patchSets().get(patchSetId);
-    if (patchSet == null) {
-      throw new NoSuchChangeException(changeId);
-    }
 
     aId = psa != null ? toObjectId(db, psa) : null;
     bId = toObjectId(db, psb);
@@ -151,7 +139,7 @@
     try {
       final PatchList list = listFor(keyFor(diffPrefs.getIgnoreWhitespace()));
       final PatchScriptBuilder b = newBuilder(list, git);
-      final PatchListEntry content = list.get(patchKey.getFileName());
+      final PatchListEntry content = list.get(fileName);
 
       loadCommentsAndHistory(content.getChangeType(), //
           content.getOldName(), //
@@ -232,7 +220,7 @@
     // proper rename detection between the patch sets.
     //
     for (final PatchSet ps : db.patchSets().byChange(changeId)) {
-      String name = patchKey.get();
+      String name = fileName;
       if (psa != null) {
         switch (changeType) {
           case COPIED:
diff --git a/lib/prolog/java/BuckPrologCompiler.java b/lib/prolog/java/BuckPrologCompiler.java
index d23e15d..b731ea7 100644
--- a/lib/prolog/java/BuckPrologCompiler.java
+++ b/lib/prolog/java/BuckPrologCompiler.java
@@ -12,11 +12,15 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
+import com.googlecode.prolog_cafe.compiler.CompileException;
+import com.googlecode.prolog_cafe.compiler.Compiler;
+
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.List;
 import java.util.Locale;
 import java.util.jar.JarEntry;
@@ -29,9 +33,6 @@
 import javax.tools.StandardJavaFileManager;
 import javax.tools.ToolProvider;
 
-import com.googlecode.prolog_cafe.compiler.CompileException;
-import com.googlecode.prolog_cafe.compiler.Compiler;
-
 public class BuckPrologCompiler {
   public static void main(String[] argv) throws IOException, CompileException {
     List<File> srcs = new ArrayList<File>();
@@ -82,14 +83,16 @@
         classpath.append(jar.getPath());
       }
       ArrayList<String> args = new ArrayList<String>();
-      args.add("-g:none");
-      args.add("-nowarn");
+      args.addAll(Arrays.asList(new String[]{
+          "-source", "6",
+          "-target", "6",
+          "-g:none",
+          "-nowarn",
+          "-d", classes.getPath()}));
       if (classpath.length() > 0) {
         args.add("-classpath");
         args.add(classpath.toString());
       }
-      args.add("-d");
-      args.add(classes.getPath());
       if (!javac.getTask(null, fm, d, args, null,
           fm.getJavaFileObjectsFromFiles(find(java, ".java"))).call()) {
         StringBuilder msg = new StringBuilder();
diff --git a/plugins/replication b/plugins/replication
index e7d3274..6b5ca01 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit e7d327447da27f060d0f9c0db6b52dfbe25ec48f
+Subproject commit 6b5ca0107992973f5d8b3b9b35bd68653d8c2219