Merge "Tweak Lucene analyzer's definition of a whole word"
diff --git a/.buckversion b/.buckversion
index cff4f0f..4965cea 100644
--- a/.buckversion
+++ b/.buckversion
@@ -1 +1 @@
-b5a92acf079ced6f80dd3e826ef1fec96eb2c1c0
+a0298c530b41eade38c703a3f528e390d619c5b4
diff --git a/.watchmanconfig b/.watchmanconfig
new file mode 100644
index 0000000..b1869ba
--- /dev/null
+++ b/.watchmanconfig
@@ -0,0 +1,8 @@
+{
+  "ignore_dirs": [
+    "buck-out"
+  ],
+  "ignore_vcs": [
+    ".git"
+  ]
+}
diff --git a/Documentation/BUCK b/Documentation/BUCK
index f070d7e..5ea58fd 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -19,7 +19,7 @@
   srcs = glob([
       'images/*.jpg',
       'images/*.png',
-    ]) + ['doc.css'],
+    ]) + [':doc.css'],
   out = 'html.zip',
   visibility = ['PUBLIC'],
 )
@@ -39,6 +39,13 @@
   out = 'licenses.txt',
 )
 
+genrule(
+  name = 'doc.css',
+  srcs = ['doc.css.in'],
+  cmd = 'cp $SRCS $OUT',
+  out = 'doc.css',
+)
+
 python_binary(
   name = 'gen_licenses',
   main = 'gen_licenses.py',
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 7adf265..6581dd7 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -24,6 +24,7 @@
   asciidoc = [
       '$(exe //lib/asciidoctor:asciidoc)',
       '-z', '$OUT',
+      '--base-dir', '$SRCDIR',
       '--tmp', '$TMP',
       '--in-ext', '".txt%s"' % EXPN,
       '--out-ext', '".html"',
@@ -33,7 +34,7 @@
   for attribute in attributes:
     asciidoc.extend(['-a', attribute])
   asciidoc.append('$SRCS')
-  newsrcs = ["doc.css"]
+  newsrcs = [":doc.css"]
   for src in srcs:
     fn = src
     # We have two cases: regular source files and generated files.
@@ -57,7 +58,9 @@
       out = ex,
     )
 
-    asciidoc.append('$(location :%s)' % ex)
+    # The new AsciiDoctor requires both the css file and include files are under
+    # the same directory. Luckily Buck allows us to use :target as SRCS now.
+    newsrcs.append(':%s' % ex)
 
   genrule(
     name = name,
diff --git a/Documentation/cmd-review.txt b/Documentation/cmd-review.txt
index 5eea379..12d91ae 100644
--- a/Documentation/cmd-review.txt
+++ b/Documentation/cmd-review.txt
@@ -56,12 +56,15 @@
 -m::
 	Optional cover letter to include as part of the message
 	sent to reviewers when the approval states are updated.
+	(option is mutually exclusive with --json)
 
 --json::
 -j::
 	Read review input from JSON file. See
 	link:rest-api-changes.html#review-input[ReviewInput] entity for the
 	format.
+	(option is mutually exclusive with --submit, --restore, --publish, --delete,
+	--abandon and --message)
 
 --notify::
 -n::
@@ -82,24 +85,27 @@
 
 --abandon::
 	Abandon the specified change(s).
-	(option is mutually exclusive with --submit and --restore)
+	(option is mutually exclusive with --submit, --restore, --publish, --delete
+	and --json)
 
 --restore::
 	Restore the specified abandoned change(s).
-	(option is mutually exclusive with --abandon)
+	(option is mutually exclusive with --abandon and --json)
 
 --submit::
 -s::
 	Submit the specified patch set(s) for merging.
-	(option is mutually exclusive with --abandon)
+	(option is mutually exclusive with --abandon, --publish --delete and --json)
 
 --publish::
 	Publish the specified draft patch set(s).
-	(option is mutually exclusive with --submit, --restore, --abandon, and --delete)
+	(option is mutually exclusive with --submit, --restore, --abandon, --delete
+	and --json)
 
 --delete::
 	Delete the specified draft patch set(s).
-	(option is mutually exclusive with --submit, --restore, --abandon, and --publish)
+	(option is mutually exclusive with --submit, --restore, --abandon, --publish
+	and --json)
 
 --code-review::
 --verified::
diff --git a/Documentation/cmd-set-account.txt b/Documentation/cmd-set-account.txt
index 6a413c6..40f2378 100644
--- a/Documentation/cmd-set-account.txt
+++ b/Documentation/cmd-set-account.txt
@@ -41,7 +41,7 @@
     Required; Full name, email-address, SSH username or account id.
 
 --full-name::
-    Display name of the user account.
+    Set the display name for the user account.
 +
 Names containing spaces should be quoted in single quotes (').
 This most likely requires double quoting the value, for example
diff --git a/Documentation/config-gerrit.txt b/Documentation/config-gerrit.txt
index 02bed57..01b41d5 100644
--- a/Documentation/config-gerrit.txt
+++ b/Documentation/config-gerrit.txt
@@ -2602,6 +2602,17 @@
 +
 Common unit suffixes of 'k', 'm', or 'g' are supported.
 
+[[receive.maxBatchChanges]]receive.maxBatchChanges::
++
+The maximum number of changes that Gerrit allows to be pushed
+in a batch for review. When this number is exceeded Gerrit rejects
+the push with an error message.
+
+This setting can be used to prevent users from uploading large
+number of changes for review by mistake.
+
+Default is zero, no limit.
+
 [[receive.threadPoolSize]]receive.threadPoolSize::
 +
 Maximum size of the thread pool in which the change data in received packs is
diff --git a/Documentation/config.defs b/Documentation/config.defs
index 642b915..8d67173 100644
--- a/Documentation/config.defs
+++ b/Documentation/config.defs
@@ -16,5 +16,6 @@
     'last-update-label!',
     'source-highlighter=prettify',
     'stylesheet=doc.css',
+    'linkcss=true',
     'revnumber="%s"' % revision,
   ]
diff --git a/Documentation/doc.css b/Documentation/doc.css.in
similarity index 100%
rename from Documentation/doc.css
rename to Documentation/doc.css.in
diff --git a/Documentation/rest-api-changes.txt b/Documentation/rest-api-changes.txt
index 61221a0..662bbfe 100644
--- a/Documentation/rest-api-changes.txt
+++ b/Documentation/rest-api-changes.txt
@@ -1236,8 +1236,7 @@
   Content-Type: application/json;charset=UTF-8
 
   {
-    "path": "foo",
-    "restore": true
+    "restore_path": "foo"
   }
 ----
 
@@ -1878,6 +1877,15 @@
         {
           "line": 49,
           "message": "[nit] s/conrtol/control"
+        },
+        {
+          "range": {
+            "start_line": 50,
+            "start_character": 0,
+            "end_line": 55,
+            "end_character": 20
+          },
+          "message": "Incorrect indentation"
         }
       ]
     }
@@ -3237,7 +3245,7 @@
 If range is set, this equals the end line of the range. +
 If neither line nor range is set, it's a file comment.
 |`range`       |optional|
-The range of the comment as a link:rest-api.html#comment-range[CommentRange]
+The range of the comment as a link:#comment-range[CommentRange]
 entity.
 |`in_reply_to` |optional|
 The URL encoded UUID of the comment to which this comment is a reply.
@@ -3274,9 +3282,9 @@
 The number of the line for which the comment should be added. +
 `0` if it is a file comment. +
 If neither line nor range is set, a file comment is added. +
-If range is set, this should equal the end line of the range.
+If range is set, this value is ignored in favor of the `end_line` of the range.
 |`range`       |optional|
-The range of the comment as a link:rest-api.html#comment-range[CommentRange]
+The range of the comment as a link:#comment-range[CommentRange]
 entity.
 |`in_reply_to` |optional|
 The URL encoded UUID of the comment to which this comment is a reply.
@@ -3824,9 +3832,9 @@
 link:#file-info[FileInfo] entities.
 |===========================
 
-[[restore-path-input]]
-=== RestorePathInput
-The `RestorePathInput` entity contains information for restoring a
+[[change-edit-input]]
+=== ChangeEditInput
+The `ChangeEditInput` entity contains information for restoring a
 path within change edit.
 
 [options="header",width="50%",cols="1,^1,5"]
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
index 0734d12..481e535 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/MergeabilityChecker.java
@@ -333,6 +333,11 @@
       ReviewDb db = context.getReviewDbProvider().get();
       try {
         PatchSet ps = db.patchSets().get(change.currentPatchSetId());
+        if (ps == null) {
+          // Cannot compute mergeability if current patch set is missing.
+          return false;
+        }
+
         Mergeable m = mergeable.get();
         m.setForce(force);
 
@@ -345,10 +350,10 @@
         // change is closed
         return false;
       } catch (Exception e) {
-        String msg = "Failed to update mergeability flags for project "
-            + change.getDest().getParentKey() + " on update of "
-            + change.getDest().get();
-        log.error(msg, e);
+        log.error(String.format(
+            "cannot update mergeability flag of change %d in project %s after update of %s",
+            change.getId().get(),
+            change.getDest().getParentKey(), change.getDest().get()), e);
         throw e;
       } finally {
         tl.setContext(old);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
index bf8474f..4b6e72c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/PostReview.java
@@ -371,6 +371,7 @@
               c.range.startCharacter,
               c.range.endLine,
               c.range.endCharacter));
+          e.setLine(c.range.endLine);
         }
         ups.add(e);
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
index 74b5871..c49ed30 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeOp.java
@@ -655,14 +655,17 @@
 
   private void fireRefUpdated(RefUpdate branchUpdate) {
     gitRefUpdated.fire(destBranch.getParentKey(), branchUpdate);
+    hooks.doRefUpdatedHook(destBranch, branchUpdate, getAccount(mergeTip));
+  }
 
+  private Account getAccount(CodeReviewCommit codeReviewCommit) {
     Account account = null;
     PatchSetApproval submitter = approvalsUtil.getSubmitter(
-        db, mergeTip.notes(), mergeTip.getPatchsetId());
+        db, codeReviewCommit.notes(), codeReviewCommit.getPatchsetId());
     if (submitter != null) {
       account = accountCache.get(submitter.getAccountId()).getAccount();
     }
-    hooks.doRefUpdatedHook(destBranch, branchUpdate, account);
+    return account;
   }
 
   private void updateChangeStatus(final List<Change> submitted) throws NoSuchChangeException {
@@ -726,7 +729,8 @@
     if (mergeTip != null && (branchTip == null || branchTip != mergeTip)) {
       SubmoduleOp subOp =
           subOpFactory.create(destBranch, mergeTip, rw, repo,
-              destProject.getProject(), submitted, commits);
+              destProject.getProject(), submitted, commits,
+              getAccount(mergeTip));
       try {
         subOp.update();
       } catch (SubmoduleException e) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
index 4e12e25..18ec7c6e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MergeUtil.java
@@ -52,6 +52,7 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.MergeStrategy;
 import org.eclipse.jgit.merge.Merger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.merge.ThreeWayMerger;
 import org.eclipse.jgit.revwalk.FooterKey;
 import org.eclipse.jgit.revwalk.FooterLine;
@@ -89,6 +90,12 @@
     return cfg.getBoolean("core", null, "useRecursiveMerge", true);
   }
 
+  public static ThreeWayMergeStrategy getMergeStrategy(Config cfg) {
+    return useRecursiveMerge(cfg)
+        ? MergeStrategy.RECURSIVE
+        : MergeStrategy.RESOLVE;
+  }
+
   public static interface Factory {
     MergeUtil create(ProjectState project);
     MergeUtil create(ProjectState project, boolean useContentMerge);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
index 8d9fdf2..548d89f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveCommits.java
@@ -1444,6 +1444,7 @@
 
       List<ChangeLookup> pending = Lists.newArrayList();
       final Set<Change.Key> newChangeIds = new HashSet<>();
+      final int maxBatchChanges = receiveConfig.maxBatchChanges;
       for (;;) {
         final RevCommit c = walk.next();
         if (c == null) {
@@ -1477,6 +1478,12 @@
 
         changeKey = new Change.Key(idStr);
         pending.add(new ChangeLookup(c, changeKey));
+        if (maxBatchChanges != 0 && pending.size() > maxBatchChanges) {
+          reject(magicBranch.cmd,
+              "the number of pushed changes in a batch exceeds the max limit "
+                  + maxBatchChanges);
+          return Collections.emptyList();
+        }
       }
 
       for (ChangeLookup p : pending) {
@@ -2353,7 +2360,8 @@
           subOpFactory.create(
               new Branch.NameKey(project.getNameKey(), cmd.getRefName()),
               codeReviewCommit, rw, repo, project, new ArrayList<Change>(),
-              new HashMap<Change.Id, CodeReviewCommit>());
+              new HashMap<Change.Id, CodeReviewCommit>(),
+              currentUser.getAccount());
       subOp.update();
     } catch (InsertException e) {
       log.error("Can't insert patchset", e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
index 2efc94c..6d37ae0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ReceiveConfig.java
@@ -25,6 +25,7 @@
   final boolean checkMagicRefs;
   final boolean checkReferencedObjectsAreReachable;
   final boolean allowDrafts;
+  final int maxBatchChanges;
 
   @Inject
   ReceiveConfig(@GerritServerConfig Config config) {
@@ -37,5 +38,6 @@
     allowDrafts = config.getBoolean(
         "change", null, "allowDrafts",
         true);
+    maxBatchChanges = config.getInt("receive", "maxBatchChanges", 0);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
index 927d20c..5a1e50d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/SubmoduleOp.java
@@ -15,7 +15,9 @@
 package com.google.gerrit.server.git;
 
 import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.Nullable;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
 import com.google.gerrit.reviewdb.client.Project;
@@ -68,7 +70,7 @@
   public interface Factory {
     SubmoduleOp create(Branch.NameKey destBranch, RevCommit mergeTip,
         RevWalk rw, Repository db, Project destProject, List<Change> submitted,
-        Map<Change.Id, CodeReviewCommit> commits);
+        Map<Change.Id, CodeReviewCommit> commits, Account account);
   }
 
   private static final Logger log = LoggerFactory.getLogger(SubmoduleOp.class);
@@ -88,6 +90,8 @@
   private final GitReferenceUpdated gitRefUpdated;
   private final SchemaFactory<ReviewDb> schemaFactory;
   private final Set<Branch.NameKey> updatedSubscribers;
+  private final Account account;
+  private final ChangeHooks changeHooks;
 
   @Inject
   public SubmoduleOp(@Assisted final Branch.NameKey destBranch,
@@ -97,7 +101,8 @@
       @Assisted Project destProject, @Assisted List<Change> submitted,
       @Assisted final Map<Change.Id, CodeReviewCommit> commits,
       @GerritPersonIdent final PersonIdent myIdent,
-      GitRepositoryManager repoManager, GitReferenceUpdated gitRefUpdated) {
+      GitRepositoryManager repoManager, GitReferenceUpdated gitRefUpdated,
+      @Nullable @Assisted Account account, ChangeHooks changeHooks) {
     this.destBranch = destBranch;
     this.mergeTip = mergeTip;
     this.rw = rw;
@@ -110,6 +115,8 @@
     this.myIdent = myIdent;
     this.repoManager = repoManager;
     this.gitRefUpdated = gitRefUpdated;
+    this.account = account;
+    this.changeHooks = changeHooks;
 
     updatedSubscribers = new HashSet<>();
   }
@@ -344,6 +351,7 @@
         case NEW:
         case FAST_FORWARD:
           gitRefUpdated.fire(subscriber.getParentKey(), rfu);
+          changeHooks.doRefUpdatedHook(subscriber, rfu, account);
           // TODO since this is performed "in the background" no mail will be
           // sent to inform users about the updated branch
           break;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
index 8568b1e..c1ae440 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/CherryPick.java
@@ -108,7 +108,7 @@
               mergeTip = n;
             } else {
               mergeTip =
-                  args.mergeUtil.mergeOneCommit(args.myIdent, args.repo,
+                  args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo,
                       args.rw, args.inserter, args.canMergeFlag,
                       args.destBranch, mergeTip, n);
            }
@@ -147,10 +147,10 @@
       cherryPickUser =
           args.identifiedUserFactory.create(submitAudit.getAccountId());
       cherryPickCommitterIdent = cherryPickUser.newCommitterIdent(
-          submitAudit.getGranted(), args.myIdent.getTimeZone());
+          submitAudit.getGranted(), args.serverIdent.get().getTimeZone());
     } else {
       cherryPickUser = args.identifiedUserFactory.create(n.change().getOwner());
-      cherryPickCommitterIdent = args.myIdent;
+      cherryPickCommitterIdent = args.serverIdent.get();
     }
 
     final String cherryPickCmtMsg = args.mergeUtil.createCherryPickCommitMessage(n);
@@ -164,7 +164,7 @@
         ChangeUtil.nextPatchSetId(args.repo, n.change().currentPatchSetId());
     final PatchSet ps = new PatchSet(id);
     ps.setCreatedOn(TimeUtil.nowTs());
-    ps.setUploader(submitAudit.getAccountId());
+    ps.setUploader(cherryPickUser.getAccountId());
     ps.setRevision(new RevId(newCommit.getId().getName()));
 
     final RefUpdate ru;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
index dc0d721..9023623 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeAlways.java
@@ -38,7 +38,7 @@
     }
     while (!toMerge.isEmpty()) {
       mergeTip =
-          args.mergeUtil.mergeOneCommit(args.myIdent, args.repo, args.rw,
+          args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo, args.rw,
               args.inserter, args.canMergeFlag, args.destBranch, mergeTip,
               toMerge.remove(0));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
index 601c6f8..b84586f6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/MergeIfNecessary.java
@@ -43,7 +43,7 @@
     // For every other commit do a pair-wise merge.
     while (!toMerge.isEmpty()) {
       mergeTip =
-          args.mergeUtil.mergeOneCommit(args.myIdent, args.repo, args.rw,
+          args.mergeUtil.mergeOneCommit(args.serverIdent.get(), args.repo, args.rw,
               args.inserter, args.canMergeFlag, args.destBranch, mergeTip,
               toMerge.remove(0));
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
index c38f5f21..130d170 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/RebaseIfNecessary.java
@@ -32,7 +32,6 @@
 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;
@@ -43,17 +42,14 @@
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final RebaseChange rebaseChange;
   private final Map<Change.Id, CodeReviewCommit> newCommits;
-  private final PersonIdent committerIdent;
 
   RebaseIfNecessary(SubmitStrategy.Arguments args,
       PatchSetInfoFactory patchSetInfoFactory,
-      RebaseChange rebaseChange,
-      PersonIdent committerIdent) {
+      RebaseChange rebaseChange) {
     super(args);
     this.patchSetInfoFactory = patchSetInfoFactory;
     this.rebaseChange = rebaseChange;
     this.newCommits = new HashMap<>();
-    this.committerIdent = committerIdent;
   }
 
   @Override
@@ -91,7 +87,7 @@
             final PatchSet newPatchSet =
                 rebaseChange.rebase(args.repo, args.rw, args.inserter,
                     n.getPatchsetId(), n.change(), uploader,
-                    newMergeTip, args.mergeUtil, committerIdent,
+                    newMergeTip, args.mergeUtil, args.serverIdent.get(),
                     false, false, ValidatePolicy.NONE);
 
             List<PatchSetApproval> approvals = Lists.newArrayList();
@@ -133,7 +129,7 @@
             newMergeTip = n;
           } else {
             newMergeTip = args.mergeUtil.mergeOneCommit(
-                args.myIdent, args.repo, args.rw, args.inserter,
+                args.serverIdent.get(), args.repo, args.rw, args.inserter,
                 args.canMergeFlag, args.destBranch, newMergeTip, n);
           }
           final PatchSetApproval submitApproval = args.mergeUtil.markCleanMerges(
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
index aa08085..a864b6c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategy.java
@@ -27,6 +27,7 @@
 import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.index.ChangeIndexer;
 import com.google.gerrit.server.project.ChangeControl;
+import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
@@ -52,7 +53,7 @@
 
   static class Arguments {
     protected final IdentifiedUser.GenericFactory identifiedUserFactory;
-    protected final PersonIdent myIdent;
+    protected final Provider<PersonIdent> serverIdent;
     protected final ReviewDb db;
     protected final ChangeControl.GenericFactory changeControlFactory;
 
@@ -68,14 +69,14 @@
     protected final MergeSorter mergeSorter;
 
     Arguments(final IdentifiedUser.GenericFactory identifiedUserFactory,
-        final PersonIdent myIdent, final ReviewDb db,
+        final Provider<PersonIdent> serverIdent, final ReviewDb db,
         final ChangeControl.GenericFactory changeControlFactory,
         final Repository repo, final RevWalk rw, final ObjectInserter inserter,
         final RevFlag canMergeFlag, final Set<RevCommit> alreadyAccepted,
         final Branch.NameKey destBranch, final ApprovalsUtil approvalsUtil,
         final MergeUtil mergeUtil, final ChangeIndexer indexer) {
       this.identifiedUserFactory = identifiedUserFactory;
-      this.myIdent = myIdent;
+      this.serverIdent = serverIdent;
       this.db = db;
       this.changeControlFactory = changeControlFactory;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
index d427b8d..091523b 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyFactory.java
@@ -31,6 +31,7 @@
 import com.google.gerrit.server.project.ProjectCache;
 import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
+import com.google.inject.Provider;
 import com.google.inject.Singleton;
 
 import org.eclipse.jgit.lib.ObjectInserter;
@@ -51,7 +52,7 @@
       .getLogger(SubmitStrategyFactory.class);
 
   private final IdentifiedUser.GenericFactory identifiedUserFactory;
-  private final PersonIdent myIdent;
+  private final Provider<PersonIdent> myIdent;
   private final ChangeControl.GenericFactory changeControlFactory;
   private final PatchSetInfoFactory patchSetInfoFactory;
   private final GitReferenceUpdated gitRefUpdated;
@@ -64,7 +65,7 @@
   @Inject
   SubmitStrategyFactory(
       final IdentifiedUser.GenericFactory identifiedUserFactory,
-      @GerritPersonIdent final PersonIdent myIdent,
+      @GerritPersonIdent Provider<PersonIdent> myIdent,
       final ChangeControl.GenericFactory changeControlFactory,
       final PatchSetInfoFactory patchSetInfoFactory,
       final GitReferenceUpdated gitRefUpdated, final RebaseChange rebaseChange,
@@ -105,8 +106,7 @@
       case MERGE_IF_NECESSARY:
         return new MergeIfNecessary(args);
       case REBASE_IF_NECESSARY:
-        return new RebaseIfNecessary(
-            args, patchSetInfoFactory, rebaseChange, myIdent);
+        return new RebaseIfNecessary(args, patchSetInfoFactory, rebaseChange);
       default:
         final String errorMsg = "No submit strategy for: " + submitType;
         log.error(errorMsg);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
index cf5625d..aaeffd9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/group/AddMembers.java
@@ -164,7 +164,10 @@
         case LDAP:
           if (accountResolver.find(nameOrEmail) == null) {
             // account does not exist, try to create it
-            return createAccountByLdap(nameOrEmail);
+            Account a = createAccountByLdap(nameOrEmail);
+            if (a != null) {
+              return a;
+            }
           }
           break;
         default:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
index 03653d2..a82527f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/index/ChangeBatchIndexer.java
@@ -32,7 +32,9 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.change.MergeabilityChecker;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.gerrit.server.git.MultiProgressMonitor;
 import com.google.gerrit.server.git.MultiProgressMonitor.Task;
 import com.google.gerrit.server.patch.PatchListLoader;
@@ -43,12 +45,14 @@
 import org.eclipse.jgit.diff.DiffEntry;
 import org.eclipse.jgit.diff.DiffFormatter;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.ProgressMonitor;
 import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevObject;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -111,6 +115,7 @@
   private final ListeningExecutorService executor;
   private final ChangeIndexer.Factory indexerFactory;
   private final MergeabilityChecker mergeabilityChecker;
+  private final ThreeWayMergeStrategy mergeStrategy;
 
   @Inject
   ChangeBatchIndexer(SchemaFactory<ReviewDb> schemaFactory,
@@ -118,6 +123,7 @@
       GitRepositoryManager repoManager,
       @IndexExecutor ListeningExecutorService executor,
       ChangeIndexer.Factory indexerFactory,
+      @GerritServerConfig Config config,
       @Nullable MergeabilityChecker mergeabilityChecker) {
     this.schemaFactory = schemaFactory;
     this.changeDataFactory = changeDataFactory;
@@ -125,6 +131,7 @@
     this.executor = executor;
     this.indexerFactory = indexerFactory;
     this.mergeabilityChecker = mergeabilityChecker;
+    this.mergeStrategy = MergeUtil.getMergeStrategy(config);
   }
 
   public Result indexAll(ChangeIndex index, Iterable<Project.NameKey> projects,
@@ -238,8 +245,13 @@
               byId.put(r.getObjectId(), changeDataFactory.create(db, c));
             }
           }
-          new ProjectIndexer(indexer, byId, repo, done, failed, verboseWriter)
-              .call();
+          new ProjectIndexer(indexer,
+              mergeStrategy,
+              byId,
+              repo,
+              done,
+              failed,
+              verboseWriter).call();
         } catch (RepositoryNotFoundException rnfe) {
           log.error(rnfe.getMessage());
         } finally {
@@ -260,6 +272,7 @@
 
   private static class ProjectIndexer implements Callable<Void> {
     private final ChangeIndexer indexer;
+    private final ThreeWayMergeStrategy mergeStrategy;
     private final Multimap<ObjectId, ChangeData> byId;
     private final ProgressMonitor done;
     private final ProgressMonitor failed;
@@ -268,9 +281,14 @@
     private RevWalk walk;
 
     private ProjectIndexer(ChangeIndexer indexer,
-        Multimap<ObjectId, ChangeData> changesByCommitId, Repository repo,
-        ProgressMonitor done, ProgressMonitor failed, PrintWriter verboseWriter) {
+        ThreeWayMergeStrategy mergeStrategy,
+        Multimap<ObjectId, ChangeData> changesByCommitId,
+        Repository repo,
+        ProgressMonitor done,
+        ProgressMonitor failed,
+        PrintWriter verboseWriter) {
       this.indexer = indexer;
+      this.mergeStrategy = mergeStrategy;
       this.byId = changesByCommitId;
       this.repo = repo;
       this.done = done;
@@ -369,7 +387,7 @@
           walk.parseBody(a);
           return walk.parseTree(a.getTree());
         case 2:
-          return PatchListLoader.automerge(repo, walk, b);
+          return PatchListLoader.automerge(repo, walk, b, mergeStrategy);
         default:
           return null;
       }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
index 4eede00..de36020 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/PatchListLoader.java
@@ -21,7 +21,9 @@
 import com.google.gerrit.reviewdb.client.AccountDiffPreference.Whitespace;
 import com.google.gerrit.reviewdb.client.Patch;
 import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MergeUtil;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.diff.DiffEntry;
@@ -35,6 +37,7 @@
 import org.eclipse.jgit.dircache.DirCache;
 import org.eclipse.jgit.dircache.DirCacheBuilder;
 import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.Constants;
 import org.eclipse.jgit.lib.FileMode;
 import org.eclipse.jgit.lib.ObjectId;
@@ -45,8 +48,8 @@
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.merge.MergeFormatter;
 import org.eclipse.jgit.merge.MergeResult;
-import org.eclipse.jgit.merge.MergeStrategy;
 import org.eclipse.jgit.merge.ResolveMerger;
+import org.eclipse.jgit.merge.ThreeWayMergeStrategy;
 import org.eclipse.jgit.patch.FileHeader;
 import org.eclipse.jgit.patch.FileHeader.PatchType;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -72,11 +75,14 @@
 
   private final GitRepositoryManager repoManager;
   private final PatchListCache patchListCache;
+  private final ThreeWayMergeStrategy mergeStrategy;
 
   @Inject
-  PatchListLoader(GitRepositoryManager mgr, PatchListCache plc) {
+  PatchListLoader(GitRepositoryManager mgr, PatchListCache plc,
+      @GerritServerConfig Config cfg) {
     repoManager = mgr;
     patchListCache = plc;
+    mergeStrategy = MergeUtil.getMergeStrategy(cfg);
   }
 
   @Override
@@ -224,7 +230,7 @@
     }
   }
 
-  private static RevObject aFor(final PatchListKey key,
+  private RevObject aFor(final PatchListKey key,
       final Repository repo, final RevWalk rw, final RevCommit b)
       throws IOException {
     if (key.getOldId() != null) {
@@ -240,20 +246,20 @@
         return r;
       }
       case 2:
-        return automerge(repo, rw, b);
+        return automerge(repo, rw, b, mergeStrategy);
       default:
         // TODO(sop) handle an octopus merge.
         return null;
     }
   }
 
-  public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b)
-      throws IOException {
-    return automerge(repo, rw, b, true);
+  public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
+      ThreeWayMergeStrategy mergeStrategy) throws IOException {
+    return automerge(repo, rw, b, mergeStrategy, true);
   }
 
   public static RevTree automerge(Repository repo, RevWalk rw, RevCommit b,
-      boolean save) throws IOException {
+      ThreeWayMergeStrategy mergeStrategy, boolean save) throws IOException {
     String hash = b.name();
     String refName = RefNames.REFS_CACHE_AUTOMERGE
         + hash.substring(0, 2)
@@ -264,8 +270,7 @@
       return rw.parseTree(ref.getObjectId());
     }
 
-    ObjectId treeId;
-    ResolveMerger m = (ResolveMerger) MergeStrategy.RESOLVE.newMerger(repo, true);
+    ResolveMerger m = (ResolveMerger) mergeStrategy.newMerger(repo, true);
     final ObjectInserter ins = repo.newObjectInserter();
     try {
       DirCache dc = DirCache.newInCore();
@@ -297,6 +302,7 @@
         return null;
       }
 
+      ObjectId treeId;
       if (couldMerge) {
         treeId = m.getResultTreeId();
 
@@ -381,17 +387,18 @@
         treeId = dc.writeTree(ins);
       }
       ins.flush();
+
+      if (save) {
+        RefUpdate update = repo.updateRef(refName);
+        update.setNewObjectId(treeId);
+        update.disableRefLog();
+        update.forceUpdate();
+      }
+
+      return rw.lookupTree(treeId);
     } finally {
       ins.release();
     }
-
-    if (save) {
-      RefUpdate update = repo.updateRef(refName);
-      update.setNewObjectId(treeId);
-      update.disableRefLog();
-      update.forceUpdate();
-    }
-    return rw.parseTree(treeId);
   }
 
   private static ObjectId emptyTree(final Repository repo) throws IOException {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
index 1910a5e..8d1e95f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ListChildProjects.java
@@ -26,6 +26,7 @@
 
 import org.kohsuke.args4j.Option;
 
+import java.util.Collections;
 import java.util.List;
 import java.util.Map;
 
@@ -96,7 +97,13 @@
         node.addChild(key);
       }
     }
-    return getChildProjectsRecursively(projects.get(parent));
+
+    ProjectNode n = projects.get(parent);
+    if (n != null) {
+      return getChildProjectsRecursively(n);
+    } else {
+      return Collections.emptyList();
+    }
   }
 
   private List<ProjectInfo> getChildProjectsRecursively(ProjectNode p) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
index 20e7b04..ba31f56 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/util/SystemLog.java
@@ -25,7 +25,6 @@
 import org.apache.log4j.AsyncAppender;
 import org.apache.log4j.DailyRollingFileAppender;
 import org.apache.log4j.Layout;
-import org.apache.log4j.Level;
 import org.apache.log4j.LogManager;
 import org.apache.log4j.Logger;
 import org.apache.log4j.helpers.OnlyOnceErrorHandler;
@@ -64,7 +63,6 @@
     dst.setFile(new File(resolve(logdir), name).getPath());
     dst.setImmediateFlush(true);
     dst.setAppend(true);
-    dst.setThreshold(Level.INFO);
     dst.setErrorHandler(new DieErrorHandler());
     dst.activateOptions();
     dst.setErrorHandler(new OnlyOnceErrorHandler());
diff --git a/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties b/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties
index d6062c3..8960ff9 100644
--- a/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties
+++ b/gerrit-server/src/main/resources/com/google/gerrit/server/mime-types.properties
@@ -4,6 +4,7 @@
 clj = text/x-clojure
 cl = text/x-common-lisp
 coffee = text/x-coffeescript
+cs = text/x-csharp
 cxx = text/x-c++src
 d = text/x-d
 defs = text/x-python
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
index f931dbf..0fe246e 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/git/SubmoduleOpTest.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.git;
 
 import static org.easymock.EasyMock.capture;
+import static org.easymock.EasyMock.createNiceMock;
 import static org.easymock.EasyMock.createStrictMock;
 import static org.easymock.EasyMock.eq;
 import static org.easymock.EasyMock.expect;
@@ -22,6 +23,7 @@
 import static org.easymock.EasyMock.verify;
 import static org.junit.Assert.assertEquals;
 
+import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Change;
@@ -79,6 +81,7 @@
   private Provider<String> urlProvider;
   private GitRepositoryManager repoManager;
   private GitReferenceUpdated gitRefUpdated;
+  private ChangeHooks changeHooks;
 
   @SuppressWarnings("unchecked")
   @Override
@@ -92,6 +95,7 @@
     urlProvider = createStrictMock(Provider.class);
     repoManager = createStrictMock(GitRepositoryManager.class);
     gitRefUpdated = createStrictMock(GitReferenceUpdated.class);
+    changeHooks = createNiceMock(ChangeHooks.class);
   }
 
   private void doReplay() {
@@ -138,7 +142,7 @@
     final SubmoduleOp submoduleOp =
         new SubmoduleOp(branchNameKey, mergeTip, new RevWalk(realDb), urlProvider,
             schemaFactory, realDb, null, new ArrayList<Change>(), null, null,
-            null, null);
+            null, null, null, null);
 
     submoduleOp.update();
 
@@ -654,7 +658,8 @@
         new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
             sourceRepository), urlProvider, schemaFactory, sourceRepository,
             new Project(sourceBranchNameKey.getParentKey()), submitted,
-            mergedCommits, myIdent, repoManager, gitRefUpdated);
+            mergedCommits, myIdent, repoManager, gitRefUpdated, null,
+            changeHooks);
 
     submoduleOp.update();
 
@@ -758,7 +763,7 @@
         new SubmoduleOp(sourceBranchNameKey, sourceMergeTip, new RevWalk(
             sourceRepository), urlProvider, schemaFactory, sourceRepository,
             new Project(sourceBranchNameKey.getParentKey()), submitted,
-            mergedCommits, myIdent, repoManager, gitRefUpdated);
+            mergedCommits, myIdent, repoManager, gitRefUpdated, null, changeHooks);
 
     submoduleOp.update();
 
@@ -912,7 +917,7 @@
         new SubmoduleOp(mergedBranch, mergeTip, new RevWalk(realDb),
             urlProvider, schemaFactory, realDb, new Project(mergedBranch
                 .getParentKey()), new ArrayList<Change>(), null, null,
-            repoManager, null);
+            repoManager, null, null, null);
 
     submoduleOp.update();
   }
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
index 26507b3..f85b1b3 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/ReviewCommand.java
@@ -186,6 +186,9 @@
         throw error("json and message are mutually exclusive");
       }
     }
+    if (deleteDraftPatchSet && submitChange) {
+      throw error("delete and submit actions are mutually exclusive");
+    }
 
     boolean ok = true;
     ReviewInput input = null;
@@ -242,6 +245,9 @@
     if (changeComment == null) {
       changeComment = "";
     }
+    if (notify == null) {
+      notify = NotifyHandling.ALL;
+    }
 
     ReviewInput review = new ReviewInput();
     review.message = Strings.emptyToNull(changeComment);
diff --git a/gerrit-war/src/main/resources/log4j.properties b/gerrit-war/src/main/resources/log4j.properties
index cb14916..ef64f3b 100644
--- a/gerrit-war/src/main/resources/log4j.properties
+++ b/gerrit-war/src/main/resources/log4j.properties
@@ -12,14 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 #
-log4j.rootCategory=DEBUG, stderr
+log4j.rootCategory=INFO, stderr
 log4j.appender.stderr=org.apache.log4j.ConsoleAppender
 log4j.appender.stderr.target=System.err
 log4j.appender.stderr.layout=org.apache.log4j.PatternLayout
 log4j.appender.stderr.layout.ConversionPattern=[%d] %-5p %c %x: %m%n
 
-log4j.logger.com.google.gerrit=INFO
-
 # Silence non-critical messages from MINA SSHD.
 #
 log4j.logger.org.apache.mina=WARN
@@ -28,10 +26,6 @@
 log4j.logger.org.apache.sshd.common.keyprovider.FileKeyPairProvider=INFO
 log4j.logger.com.google.gerrit.sshd.GerritServerSession=WARN
 
-# Silence non-critical messages from Jetty.
-#
-log4j.logger.org.eclipse.jetty=INFO
-
 # Silence non-critical messages from mime-util.
 #
 log4j.logger.eu.medsea.mimeutil=WARN
diff --git a/lib/asciidoctor/BUCK b/lib/asciidoctor/BUCK
index b1d5933..66a12c1 100644
--- a/lib/asciidoctor/BUCK
+++ b/lib/asciidoctor/BUCK
@@ -15,6 +15,7 @@
     ':jruby',
     '//lib:args4j',
     '//lib:guava',
+    '//lib/log:api',
   ],
   visibility = ['//tools/eclipse:classpath'],
 )
@@ -42,8 +43,8 @@
 
 maven_jar(
   name = 'asciidoctor',
-  id = 'org.asciidoctor:asciidoctor-java-integration:0.1.4',
-  sha1 = '3596c7142fd30d7b65a0e64ba294f3d9d4bd538f',
+  id = 'org.asciidoctor:asciidoctorj:1.5.0',
+  sha1 = '192df5660f72a0fb76966dcc64193b94fba65f99',
   license = 'Apache2.0',
   visibility = [],
   attach_source = False,
diff --git a/lib/asciidoctor/java/AsciiDoctor.java b/lib/asciidoctor/java/AsciiDoctor.java
index 9e48641..c7562df 100644
--- a/lib/asciidoctor/java/AsciiDoctor.java
+++ b/lib/asciidoctor/java/AsciiDoctor.java
@@ -53,6 +53,9 @@
   @Option(name = "--out-ext", usage = "extension for output files")
   private String outExt = ".html";
 
+  @Option(name = "--base-dir", usage = "base directory")
+  private File basedir;
+
   @Option(name = "--tmp", usage = "temporary output path")
   private File tmpdir;
 
@@ -82,7 +85,7 @@
     OptionsBuilder optionsBuilder = OptionsBuilder.options();
 
     optionsBuilder.backend(backend).docType(DOCTYPE).eruby(ERUBY)
-      .safe(SafeMode.UNSAFE);
+      .safe(SafeMode.UNSAFE).baseDir(basedir);
     // XXX(fishywang): ideally we should just output to a string and add the
     // content into zip. But asciidoctor will actually ignore all attributes if
     // not output to a file. So we *have* to output to a file then read the
diff --git a/plugins/commit-message-length-validator b/plugins/commit-message-length-validator
index 5ea4e66..c882e58 160000
--- a/plugins/commit-message-length-validator
+++ b/plugins/commit-message-length-validator
@@ -1 +1 @@
-Subproject commit 5ea4e667c22bd920ce53201016ccf63f43819c0b
+Subproject commit c882e583226d712053ad02fdee4afcfd1e4df915
diff --git a/plugins/download-commands b/plugins/download-commands
index 4c1ab53..4e978f9 160000
--- a/plugins/download-commands
+++ b/plugins/download-commands
@@ -1 +1 @@
-Subproject commit 4c1ab539a956cb3fddea9c6af1f3c07e4e522cd1
+Subproject commit 4e978f916d429ab22b0ea28d25ac87755513cc56
diff --git a/plugins/replication b/plugins/replication
index 02607c7..52e48e3 160000
--- a/plugins/replication
+++ b/plugins/replication
@@ -1 +1 @@
-Subproject commit 02607c7b31e38ee34399c318aeb1a52211f47a7f
+Subproject commit 52e48e3ce1eea6f24926dd6a2928bb0bb080e3f3
diff --git a/plugins/reviewnotes b/plugins/reviewnotes
index 602c430..de5eccc 160000
--- a/plugins/reviewnotes
+++ b/plugins/reviewnotes
@@ -1 +1 @@
-Subproject commit 602c430749bcbadcaa3adb27fbbfc745fef074b3
+Subproject commit de5eccc9c5477a3c504c523f39dfede35426ec8c
diff --git a/plugins/singleusergroup b/plugins/singleusergroup
index a06c582..3c59757 160000
--- a/plugins/singleusergroup
+++ b/plugins/singleusergroup
@@ -1 +1 @@
-Subproject commit a06c5827222242285737258e823f13414bb5cf97
+Subproject commit 3c5975763148a6ea2bfa867a96ba5498ae5767f2
diff --git a/tools/BUCK b/tools/BUCK
index 08ced89..ee26062 100644
--- a/tools/BUCK
+++ b/tools/BUCK
@@ -21,7 +21,7 @@
   visibility = ['PUBLIC'],
 )
 
-python_library(
+python_test(
   name = 'util_test',
   srcs = ['util_test.py'],
   deps = [':util'],
@@ -44,13 +44,3 @@
   visibility = ['PUBLIC'],
 )
 
-java_test(
-  name = 'python_tests',
-  srcs = glob(['PythonTestCaller.java']),
-  deps = [
-    '//lib:guava',
-    '//lib:junit',
-    ':util',
-    ':util_test',
-  ],
-)
diff --git a/tools/PythonTestCaller.java b/tools/PythonTestCaller.java
deleted file mode 100644
index deabeb4..0000000
--- a/tools/PythonTestCaller.java
+++ /dev/null
@@ -1,67 +0,0 @@
-// 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.
-
-import static org.junit.Assert.assertTrue;
-
-import com.google.common.io.ByteStreams;
-import com.google.common.base.Splitter;
-
-import java.io.File;
-import java.io.IOException;
-import java.io.InputStream;
-import org.junit.Test;
-
-public class PythonTestCaller {
-
-  @Test
-  public void resolveUrl() throws Exception {
-    PythonTestCaller.pythonUnit("tools", "util_test");
-  }
-
-  private static void pythonUnit(String d, String sut) throws Exception {
-    ProcessBuilder b =
-        new ProcessBuilder(Splitter.on(' ').splitToList(
-                "python -m unittest " + sut))
-            .directory(new File(d))
-            .redirectErrorStream(true);
-    Process p = null;
-    InputStream i = null;
-    byte[] out;
-    try {
-      p = b.start();
-      i = p.getInputStream();
-      out = ByteStreams.toByteArray(i);
-    } catch (IOException e) {
-      throw new Exception(e);
-    } finally {
-      if (p != null) {
-        p.getOutputStream().close();
-      }
-      if (i != null) {
-        i.close();
-      }
-    }
-    int value;
-    try {
-      value = p.waitFor();
-    } catch (InterruptedException e) {
-      throw new Exception("interrupted waiting for process");
-    }
-    String err = new String(out, "UTF-8");
-    if (value != 0) {
-      System.err.print(err);
-    }
-    assertTrue(err, value == 0);
-  }
-}