Merge "Remove unused dbProvider field from IdentifiedUser"
diff --git a/.buckconfig b/.buckconfig
index c697fc8..3d5e9d8 100644
--- a/.buckconfig
+++ b/.buckconfig
@@ -8,6 +8,7 @@
   headless = //:headless
   polygerrit = //:polygerrit
   release = //:release
+  releasenotes = //ReleaseNotes:html
   safari = //:safari
   soyc = //gerrit-gwtui:ui_soyc
   soyc_r = //gerrit-gwtui:ui_soyc_r
diff --git a/Documentation/BUCK b/Documentation/BUCK
index ea95063..48ca579 100644
--- a/Documentation/BUCK
+++ b/Documentation/BUCK
@@ -14,7 +14,7 @@
 genasciidoc(
   name = 'html',
   out = 'html.zip',
-  docdir = DOC_DIR,
+  directory = DOC_DIR,
   srcs = SRCS + [':licenses.txt'],
   attributes = documentation_attributes(git_describe()),
   backend = 'html5',
@@ -24,7 +24,7 @@
 genasciidoc(
   name = 'searchfree',
   out = 'searchfree.zip',
-  docdir = DOC_DIR,
+  directory = DOC_DIR,
   srcs = SRCS + [':licenses.txt'],
   attributes = documentation_attributes(git_describe()),
   backend = 'html5',
@@ -57,6 +57,7 @@
 python_binary(
   name = 'replace_macros',
   main = 'replace_macros.py',
+  visibility = ['//ReleaseNotes:'],
 )
 
 genrule(
diff --git a/Documentation/asciidoc.defs b/Documentation/asciidoc.defs
index 1cf0790..4b17071 100644
--- a/Documentation/asciidoc.defs
+++ b/Documentation/asciidoc.defs
@@ -52,7 +52,7 @@
 
     genrule(
       name = ex,
-      cmd = '$(exe :replace_macros) --suffix="%s"' % EXPN +
+      cmd = '$(exe //Documentation:replace_macros) --suffix="%s"' % EXPN +
         ' -s ' + passed_src + ' -o $OUT' +
         (' --searchbox' if searchbox else ' --no-searchbox'),
       srcs = srcs,
@@ -72,40 +72,42 @@
 def genasciidoc(
     name,
     out,
-    docdir,
+    directory,
     srcs = [],
     attributes = [],
     backend = None,
     searchbox = True,
+    resources = True,
     visibility = []):
   SUFFIX = '_htmlonly'
 
   genasciidoc_htmlonly(
-    name = name + SUFFIX,
+    name = name + SUFFIX if resources else name,
     srcs = srcs,
     attributes = attributes,
     backend = backend,
     searchbox = searchbox,
-    out = name + SUFFIX + '.zip',
+    out = (name + SUFFIX + '.zip') if resources else (name + '.zip'),
   )
 
-  genrule(
-    name = name,
-    cmd = 'cd $TMP;' +
-      'mkdir -p %s/images;' % docdir +
-      'unzip -q $(location %s) -d %s/;'
-      % (':' + name + SUFFIX, docdir) +
-      'for s in $SRCS;do ln -s $s %s;done;' % docdir +
-      'mv %s/*.{jpg,png} %s/images;' % (docdir, docdir) +
-      'cp $(location %s) LICENSES.txt;' % ':licenses.txt' +
-      'zip -qr $OUT *',
-    srcs = glob([
-        'images/*.jpg',
-        'images/*.png',
-      ]) + [
-        '//gerrit-prettify:prettify.min.css',
-        '//gerrit-prettify:prettify.min.js',
-      ],
-    out = out,
-    visibility = visibility,
-  )
+  if resources:
+    genrule(
+      name = name,
+      cmd = 'cd $TMP;' +
+        'mkdir -p %s/images;' % directory +
+        'unzip -q $(location %s) -d %s/;'
+        % (':' + name + SUFFIX, directory) +
+        'for s in $SRCS;do ln -s $s %s/;done;' % directory +
+        'mv %s/*.{jpg,png} %s/images;' % (directory, directory) +
+        'cp $(location %s) LICENSES.txt;' % ':licenses.txt' +
+        'zip -qr $OUT *',
+      srcs = glob([
+          'images/*.jpg',
+          'images/*.png',
+        ]) + [
+          '//gerrit-prettify:prettify.min.css',
+          '//gerrit-prettify:prettify.min.js',
+        ],
+      out = out,
+      visibility = visibility,
+    )
diff --git a/Documentation/config-plugins.txt b/Documentation/config-plugins.txt
index 415cb76..5dec21d 100644
--- a/Documentation/config-plugins.txt
+++ b/Documentation/config-plugins.txt
@@ -189,16 +189,6 @@
 link:https://gerrit.googlesource.com/plugins/changemessage/+doc/master/src/main/resources/Documentation/config.md[
 Configuration]
 
-[[codenvy]]
-=== codenvy
-
-Plugin to allow to edit code on-line on either an existing branch or an
-active change using the link:http://codenvy.com[Codenvy] cloud
-development platform.
-
-link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/codenvy[
-Project]
-
 [[delete-project]]
 === delete-project
 
@@ -557,6 +547,18 @@
 link:https://gerrit.googlesource.com/plugins/scripting/scala-provider/+doc/master/src/main/resources/Documentation/about.md[
 Documentation]
 
+[[scripts]]
+=== scripts
+
+Repository containing a collection of Gerrit scripting plugins that are intended
+to provide simple and useful extensions.
+
+Groovy and Scala scripts require the installation of the corresponding
+scripting/*-provider plugin in order to be loaded into Gerrit.
+
+link:https://gerrit-review.googlesource.com/#/admin/projects/plugins/scripts[Project]
+link:https://gerrit.googlesource.com/plugins/scripts/+doc/master/README.md[Documentation]
+
 [[server-config]]
 === server-config
 
diff --git a/Documentation/dev-plugins.txt b/Documentation/dev-plugins.txt
index 8315776..dbaa3c1 100644
--- a/Documentation/dev-plugins.txt
+++ b/Documentation/dev-plugins.txt
@@ -94,7 +94,6 @@
   Implementation-Title: Example plugin showing examples
   Implementation-Version: 1.0
   Implementation-Vendor: Example, Inc.
-  Implementation-URL: http://example.com/opensource/plugin-foo/
 ====
 
 === ApiType
diff --git a/Documentation/dev-release.txt b/Documentation/dev-release.txt
index fc1ad10..5ede117 100644
--- a/Documentation/dev-release.txt
+++ b/Documentation/dev-release.txt
@@ -335,9 +335,12 @@
 * Build the release notes:
 +
 ----
-  make -C ReleaseNotes
+  buck build releasenotes
 ----
 
+* Extract the release notes files from the zip file generated from the previous
+step: `buck-out/gen/ReleaseNotes/html/html.zip`.
+
 * Extract the documentation files from the zip file generated from
 `buck build docs`: `buck-out/gen/Documentation/searchfree/searchfree.zip`.
 
diff --git a/Documentation/images/link.png b/Documentation/images/link.png
deleted file mode 100644
index 25eacb7..0000000
--- a/Documentation/images/link.png
+++ /dev/null
Binary files differ
diff --git a/Documentation/replace_macros.py b/Documentation/replace_macros.py
index 0930572..baf08e7 100755
--- a/Documentation/replace_macros.py
+++ b/Documentation/replace_macros.py
@@ -1,4 +1,5 @@
 #!/usr/bin/env python
+# coding=utf-8
 # Copyright (C) 2013 The Android Open Source Project
 #
 # Licensed under the Apache License, Version 2.0 (the "License");
@@ -171,11 +172,14 @@
           a.setAttribute('href', '#' + id);
           a.setAttribute('style', 'position: absolute;'
               + ' left: ' + (element.offsetLeft - 16 - 2 * 4) + 'px;'
-              + ' padding-left: 4px; padding-right: 4px; padding-top:4px;');
-          var img = document.createElement('img');
-          img.setAttribute('src', 'images/link.png');
-          img.setAttribute('style', 'background-color: #FFFFFF;');
-          a.appendChild(img);
+              + ' padding-left: 4px; padding-right: 4px;');
+          var span = document.createElement('span');
+          span.setAttribute('style', 'height: ' + element.offsetHeight + 'px;'
+              + ' display: inline-block; vertical-align: baseline;'
+              + ' font-size: 16px; text-decoration: none; color: grey;');
+          a.appendChild(span);
+          var link = document.createTextNode('🔗');
+          span.appendChild(link);
           element.insertBefore(a, element.firstChild);
 
           // remove the link icon when the mouse is moved away,
@@ -183,14 +187,16 @@
           hide = function(evt) {
             if (document.elementFromPoint(evt.clientX, evt.clientY) != element
                 && document.elementFromPoint(evt.clientX, evt.clientY) != a
-                && document.elementFromPoint(evt.clientX, evt.clientY) != img
+                && document.elementFromPoint(evt.clientX, evt.clientY) != span
+                && document.elementFromPoint(evt.clientX, evt.clientY) != link
                 && element.contains(a)) {
               element.removeChild(a);
             }
           }
           element.onmouseout = hide;
           a.onmouseout = hide;
-          img.onmouseout = hide;
+          span.onmouseout = hide;
+          link.onmouseout = hide;
         }
       }
     }
diff --git a/Documentation/rest-api-accounts.txt b/Documentation/rest-api-accounts.txt
index 8e2dae9..bb61c3d 100644
--- a/Documentation/rest-api-accounts.txt
+++ b/Documentation/rest-api-accounts.txt
@@ -1300,7 +1300,7 @@
 
 .Request
 ----
-  GET /a/accounts/self/preferences.diff HTTP/1.0
+  PUT /a/accounts/self/preferences.diff HTTP/1.0
   Content-Type: application/json; charset=UTF-8
 
   {
diff --git a/Documentation/rest-api-config.txt b/Documentation/rest-api-config.txt
index 661abb0..345f759 100644
--- a/Documentation/rest-api-config.txt
+++ b/Documentation/rest-api-config.txt
@@ -936,6 +936,103 @@
   ]
 ----
 
+[[get-diff-preferences]]
+=== Get diff preferences
+
+--
+'GET /config/server/preferences.diff'
+--
+
+Returns the default diff preferences for the server.
+
+.Request
+----
+  GET /a/config/server/preferences.diff HTTP/1.0
+----
+
+As response a link:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] is returned.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "context": 10,
+    "tab_size": 8,
+    "line_length": 100,
+    "cursor_blink_rate": 0,
+    "intraline_difference": true,
+    "show_line_endings": true,
+    "show_tabs": true,
+    "show_whitespace_errors": true,
+    "syntax_highlighting": true,
+    "auto_hide_diff_table_header": true,
+    "theme": "DEFAULT",
+    "ignore_whitespace": "IGNORE_NONE"
+  }
+----
+
+[[set-diff-preferences]]
+=== Set Diff Preferences
+
+--
+'PUT /config/server/preferences.diff'
+--
+
+Sets the default diff preferences for the server. Default diff preferences can
+only be set by a Gerrit link:access-control.html#administrators[administrator].
+At least one field of alink:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] must be provided in the request body.
+
+.Request
+----
+  PUT /a/config/server/preferences.diff HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "context": 10,
+    "tab_size": 8,
+    "line_length": 80,
+    "cursor_blink_rate": 0,
+    "intraline_difference": true,
+    "show_line_endings": true,
+    "show_tabs": true,
+    "show_whitespace_errors": true,
+    "syntax_highlighting": true,
+    "auto_hide_diff_table_header": true,
+    "theme": "DEFAULT",
+    "ignore_whitespace": "IGNORE_NONE"
+  }
+----
+
+As response a link:rest-api-accounts.html#diff-preferences-info[
+DiffPreferencesInfo] is returned.
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "context": 10,
+    "tab_size": 8,
+    "line_length": 80,
+    "cursor_blink_rate": 0,
+    "intraline_difference": true,
+    "show_line_endings": true,
+    "show_tabs": true,
+    "show_whitespace_errors": true,
+    "syntax_highlighting": true,
+    "auto_hide_diff_table_header": true,
+    "theme": "DEFAULT",
+    "ignore_whitespace": "IGNORE_NONE"
+  }
+----
+
 
 [[ids]]
 == IDs
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index cbba2f2..36e7489 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -923,6 +923,143 @@
   }
 ----
 
+[[get-access]]
+=== List Access Rights for Project
+--
+'GET /projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
+--
+
+Lists the access rights for a single project.
+
+As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+
+.Request
+----
+  GET /projects/MyProject/access HTTP/1.0
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
+    "inherits_from": {
+      "id": "All-Projects",
+      "name": "All-Projects",
+      "description": "Access inherited by all other projects."
+    },
+    "local": {
+        "refs/*": {
+          "permissions": {
+            "read": {
+              "rules": {
+                "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+                  "action": "ALLOW",
+                  "force": false
+                },
+                "global:Anonymous-Users": {
+                  "action": "ALLOW",
+                  "force": false
+                }
+              }
+            }
+          }
+        }
+    },
+    "is_owner": true,
+    "owner_of": [
+      "refs/*"
+    ],
+    "can_upload": true,
+    "can_add": true,
+    "config_visible": true
+  }
+----
+
+[[set-access]]
+=== Add, Update and Delete Access Rights for Project
+--
+'POST /projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
+--
+
+Sets access rights for the project using the diff schema provided by
+link:#project-access-input[ProjectAccessInput]. Deductions are used to
+remove access sections, permissions or permission rules. The backend will remove
+the entity with the finest granularity in the request, meaning that if an
+access section without permissions is posted, the access section will be
+removed; if an access section with a permission but no permission rules is
+posted, the permission will be removed; if an access section with a permission
+and a permission rule is posted, the permission rule will be removed.
+
+Additionally, access sections and permissions will be cleaned up after applying
+the deductions by removing items that have no child elements.
+
+After removals have been applied, additions will be applied.
+
+As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
+
+.Request
+----
+  POST /projects/MyProject/access HTTP/1.0
+  Content-Type: application/json; charset=UTF-8
+
+  {
+    "remove": [
+      "refs/*": {
+        "permissions": {
+          "read": {
+            "rules": {
+              "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
+                "action": "ALLOW"
+              }
+            }
+          }
+        }
+      }
+    ]
+  }
+----
+
+.Response
+----
+  HTTP/1.1 200 OK
+  Content-Type: application/json; charset=UTF-8
+
+  )]}'
+  {
+    "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
+    "inherits_from": {
+      "id": "All-Projects",
+      "name": "All-Projects",
+      "description": "Access inherited by all other projects."
+    },
+    "local": {
+        "refs/*": {
+          "permissions": {
+            "read": {
+              "rules": {
+                "global:Anonymous-Users": {
+                  "action": "ALLOW",
+                  "force": false
+                }
+              }
+            }
+          }
+        }
+    },
+    "is_owner": true,
+    "owner_of": [
+      "refs/*"
+    ],
+    "can_upload": true,
+    "can_add": true,
+    "config_visible": true
+  }
+----
+
 [[branch-endpoints]]
 == Branch Endpoints
 
@@ -1651,7 +1788,7 @@
   }
 ----
 
-[[get-content]]
+[[get-content-from-commit]]
 === Get Content
 --
 'GET /projects/link:#project-name[\{project-name\}]/commits/link:#commit-id[\{commit-id\}]/files/link:rest-api-changes.html#file-id[\{file-id\}]/content'
@@ -2293,6 +2430,27 @@
 Not set if there is no global limit for the object size.
 |===============================
 
+[[project-access-input]]
+=== ProjectAccessInput
+The `ProjectAccessInput` describes changes that should be applied to a project
+access config.
+
+[options="header",cols="1,^2,4"]
+|=============================
+|Field Name          |        |Description
+|`remove`            |optional|
+A list of deductions to be applied to the project access as
+link:rest-api-access.html#project-access-info[ProjectAccessInfo] entities.
+|`add`               |optional|
+A list of additions to be applied to the project access as
+link:rest-api-access.html#project-access-info[ProjectAccessInfo] entities.
+|`message`           |optional|
+A commit message for this change.
+|`parent`            |optional|
+A new parent for the project to inherit from. Changing the parent project
+requires administrative privileges.
+|=============================
+
 [[project-description-input]]
 === ProjectDescriptionInput
 The `ProjectDescriptionInput` entity contains information for setting a
@@ -2472,60 +2630,6 @@
 The path to the `GerritSiteFooter.html` file.
 |=============================
 
-[[get-access]]
-=== List Access Rights for Project
---
-'GET //projects/link:rest-api-projects.html#project-name[\{project-name\}]/access'
---
-
-Lists the access rights for a single project.
-
-As result a link:#project-access-info[ProjectAccessInfo] entity is returned.
-
-.Request
-----
-  GET /projects/MyProject/access HTTP/1.0
-----
-
-.Response
-----
-  HTTP/1.1 200 OK
-  Content-Type: application/json; charset=UTF-8
-
-  )]}'
-  {
-    "revision": "61157ed63e14d261b6dca40650472a9b0bd88474",
-    "inherits_from": {
-      "id": "All-Projects",
-      "name": "All-Projects",
-      "description": "Access inherited by all other projects."
-    },
-    "local": {
-        "refs/*": {
-          "permissions": {
-            "read": {
-              "rules": {
-                "c2ce4749a32ceb82cd6adcce65b8216e12afb41c": {
-                  "action": "ALLOW",
-                  "force": false
-                },
-                "global:Anonymous-Users": {
-                  "action": "ALLOW",
-                  "force": false
-                }
-              }
-            }
-          }
-        }
-    },
-    "is_owner": true,
-    "owner_of": [
-      "refs/*"
-    ],
-    "can_upload": true,
-    "can_add": true,
-    "config_visible": true
-  }
 ----
 
 GERRIT
diff --git a/Documentation/user-submodules.txt b/Documentation/user-submodules.txt
index 8f75bf1..af2b344 100644
--- a/Documentation/user-submodules.txt
+++ b/Documentation/user-submodules.txt
@@ -85,7 +85,7 @@
 ====
 and add the following lines:
 ====
-  [subscribe "<superproject>"]
+  [allowSuperproject "<superproject>"]
     refs = <refspec>
 ====
 where the 'superproject' should be the exact project name of the superproject.
@@ -100,6 +100,15 @@
 ====
 After the change is integrated a superproject subscription is possible.
 
+The configuration is inherited from parent projects, such that you can have
+a configuration in the "All-Projects" project like:
+====
+    [allowSuperproject "my-only-superproject"]
+        refs = refs/heads/*:refs/heads/*
+====
+and then you don't have to worry about configuring the individual projects
+any more. Child projects cannot negate the parent's configuration.
+
 === Defining the submodule branch
 
 Since Gerrit manages subscriptions in the branch scope, we could have
@@ -153,14 +162,14 @@
 'stable':
 ====
   [allowSuperproject "<superproject>"]
-    refs/heads/*:refs/heads/*
+    refs = refs/heads/*:refs/heads/*
 ====
 
 If you want to enable a branch to be subscribed to any other branch of
 the superproject, omit the second part of the RefSpec:
 ====
   [allowSuperproject "<superproject>"]
-    refs/heads/<submodule-branch>
+    refs = refs/heads/<submodule-branch>
 ====
 
 === Subscription Limitations
diff --git a/ReleaseNotes/BUCK b/ReleaseNotes/BUCK
new file mode 100644
index 0000000..0f47808
--- /dev/null
+++ b/ReleaseNotes/BUCK
@@ -0,0 +1,19 @@
+include_defs('//Documentation/asciidoc.defs')
+include_defs('//ReleaseNotes/config.defs')
+
+DIR = 'ReleaseNotes'
+
+SRCS = glob(['*.txt'])
+
+
+genasciidoc(
+  name = 'html',
+  out = 'html.zip',
+  directory = DIR,
+  srcs = SRCS,
+  attributes = release_notes_attributes(),
+  backend = 'html5',
+  searchbox = False,
+  resources = False,
+  visibility = ['PUBLIC'],
+)
diff --git a/ReleaseNotes/Makefile b/ReleaseNotes/Makefile
deleted file mode 100644
index 3081600..0000000
--- a/ReleaseNotes/Makefile
+++ /dev/null
@@ -1,47 +0,0 @@
-# Copyright (C) 2010 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.
-
-ASCIIDOC       ?= asciidoc
-ASCIIDOC_EXTRA ?=
-
-DOC_HTML      := $(patsubst %.txt,%.html,$(wildcard ReleaseNotes*.txt))
-
-all: html
-
-html: index.html $(DOC_HTML)
-
-clean:
-	rm -f *.html
-
-index.html: index.txt
-	@echo FORMAT $@
-	@rm -f $@+ $@
-	@$(ASCIIDOC) --unsafe \
-		-a toc \
-		-b xhtml11 -f asciidoc.conf \
-		$(ASCIIDOC_EXTRA) -o $@+ $<
-	@mv $@+ $@
-
-$(DOC_HTML): %.html : %.txt
-	@echo FORMAT $@
-	@rm -f $@+ $@
-	@v=$$(echo $< | sed 's/^ReleaseNotes-//;s/.txt$$//;') && \
-	 n=$$(git describe HEAD) && \
-	 if ! git diff-index --quiet v$$v -- $< 2>/dev/null; then v="$$v (from $$n)"; fi && \
-	 $(ASCIIDOC) --unsafe \
-		-a toc \
-		-a "revision=$$v" \
-		-b xhtml11 -f asciidoc.conf \
-		$(ASCIIDOC_EXTRA) -o $@+ $<
-	@mv $@+ $@
diff --git a/ReleaseNotes/ReleaseNotes-2.0.10.txt b/ReleaseNotes/ReleaseNotes-2.0.10.txt
index 695be4f..33078d9 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.10.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.10.txt
@@ -1,13 +1,11 @@
-Release notes for Gerrit 2.0.10
-===============================
+= Release notes for Gerrit 2.0.10
 
 Gerrit 2.0.10 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
 
-New Features
-------------
+== New Features
 
 * GERRIT-129  Make the browser window title reflect the current scre...
 +
@@ -25,8 +23,7 @@
 +
 Minor enhancement to the way submitted emails are formatted.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-91   Delay updating the UI until a Screen instance is fully...
 +
@@ -46,8 +43,7 @@
 * GERRIT-135  Enable Save button after paste in a comment editor
 * GERRIT-137  Error out if a user forgets to squash when replacing a...
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.10 development
 * Add missing super.onSign{In,Out} calls to ChangeScreen
 * Remove the now pointless sign in callback support
diff --git a/ReleaseNotes/ReleaseNotes-2.0.11.txt b/ReleaseNotes/ReleaseNotes-2.0.11.txt
index 62f2a18..5bd6ca0 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.11.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.11.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.11
-===============================
+= Release notes for Gerrit 2.0.11
 
 Gerrit 2.0.11 is now available in the usual location:
 
@@ -12,11 +11,9 @@
   java -jar gerrit.war --cat sql/upgrade009_010.sql | psql reviewdb
 ----
 
-Important Notes
----------------
+== Important Notes
 
-Cache directory
-~~~~~~~~~~~~~~~
+=== Cache directory
 
 Gerrit now prefers having a temporary directory to store a disk-based content cache.  This cache used to be in the PostgreSQL database, and was the primary reason for the rather large size of the Gerrit schema.  In 2.0.11 the cache has been moved to the local filesystem, and now has automatic expiration management to prevent it from growing too large.  As this is only a cache, making backups of this directory is not required.
 
@@ -30,13 +27,11 @@
 
 link:http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html[http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html]
 
-Protocol change
-~~~~~~~~~~~~~~~
+=== Protocol change
 
 The protocol between the browser based JavaScript and the server has changed.  After installing 2.0.11 users need to load the site page again to ensure they are running 2.0.11 or later.  Users can verify they have the new version by checking the version number in the footer in the lower right.  Users who don't load the new version (e.g. are using a stale tab from a week ago) will see errors when trying to view patches.
 
-New Features
-------------
+== New Features
 
 * GERRIT-8    Add 'Whole File' as a context preference in the user s...
 * GERRIT-9    Honor user's "Default Context" preference
@@ -65,8 +60,7 @@
 +
 Simple DWIMery: users can now do `repo upload --reviewer=who` to have the reviewer email automatically expand according to the email_format column in system_config, e.g. by expanding `who` to `who@example.com`.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-81   Can't repack a repository while Gerrit is running
 +
@@ -80,8 +74,7 @@
 +
 Service users created by manually inserting into the accounts table didn't permit using their preferred_email in commits or tags; administrators had to also insert a dummy record into the account_external_ids table.  The dummy account_external_ids record is no longer necessary.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.11 development
 * Include the 'Google Format' style we selected in our p...
 * Upgrade JGit to v0.4.0-310-g3da8761
diff --git a/ReleaseNotes/ReleaseNotes-2.0.12.txt b/ReleaseNotes/ReleaseNotes-2.0.12.txt
index eb28e2e..0e1df04 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.12.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.12.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.12
-===============================
+= Release notes for Gerrit 2.0.12
 
 Gerrit 2.0.12 is now available in the usual location:
 
@@ -12,21 +11,17 @@
   java -jar gerrit.war --cat sql/upgrade010_011.sql | psql reviewdb
 ----
 
-Important Notes
----------------
+== Important Notes
 
-Java 6 Required
-~~~~~~~~~~~~~~~
+=== Java 6 Required
 
 Gerrit now requires running within a Java 6 (or later) JVM.
 
-Protocol change
-~~~~~~~~~~~~~~~
+=== Protocol change
 
 The protocol between the browser based JavaScript and the server has changed.  After installing 2.0.12 users need to load the site page again to ensure they are running 2.0.12 or later.  Users can verify they have the new version by checking the version number in the footer in the lower right.  Users who don't load the new version (e.g. are using a stale tab from a week ago) will see errors when trying to view patches.
 
-New Features
-------------
+== New Features
 * Honor --reviewer=not.preferred.email during upload
 * Also scan by preferred email for --reviewers and --cc ...
 +
@@ -73,8 +68,7 @@
 +
 Keyboard bindings have been completely overhauled in this release, and should now work on every browser.  Press '?' in any context to see the available actions.  Please note that this help is context sensitive, so you will only see keys that make sense in the current context.  Actions in a user dashboard screen differ from actions in a patch (for example), but where possible the same key is used when the logical meaning is unchanged.
 
-Bug Fixes
----------
+== Bug Fixes
 * Ignore "SshException: Already closed" errors
 +
 Hides some non-errors from the log file.
@@ -83,8 +77,7 @@
 +
 Should be a minor improvement for MSIE 6 users.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.12 development
 * Report what version we want on a schema version mismat...
 * Remove unused imports in SshServlet
diff --git a/ReleaseNotes/ReleaseNotes-2.0.13.txt b/ReleaseNotes/ReleaseNotes-2.0.13.txt
index 8ec13a8..7589568 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.13.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.13.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.13, 2.0.13.1
-=========================================
+= Release notes for Gerrit 2.0.13, 2.0.13.1
 
 Gerrit 2.0.13.1 is now available in the usual location:
 
@@ -23,8 +22,7 @@
   java -jar gerrit.war --cat sql/upgrade011_012_part2.sql | psql reviewdb
 ----
 
-Configuration Mapping
----------------------
+== Configuration Mapping
 || *system_config*                || *$site_path/gerrit.config*     ||
 || max_session_age                || auth.maxSessionAge             ||
 || canonical_url                  || gerrit.canonicalWebUrl         ||
@@ -45,8 +43,7 @@
 
 See also [http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html Gerrit2 Configuration].
 
-New Features
-------------
+== New Features
 * GERRIT-180  Rewrite outgoing email to be more user friendly
 +
 A whole slew of feature improvements on outgoing email formatting was closed by this one (massive) rewrite of the outgoing email implementation.
@@ -81,8 +78,7 @@
 +
 The new `sendemail` section of `$site_path/gerrit.config` now controls the configuration of the outgoing SMTP server, rather than relying upon a JNDI resource.  See [http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html configuration] section sendemail for more details.
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix file browser in patch that is taller than the wind...
 * GERRIT-184  Make 'f' toggle the file browser popup closed
 * GERRIT-188  Fix key bindings in patch when changing the old or new...
@@ -123,8 +119,7 @@
 +
 Bug fixes identified after release of 2.0.13, rolled into 2.0.13.1.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.13 development
 * Use gwtexpui 1.1.1-SNAPSHOT
 * Document the Patch.PatchType and Patch.ChangeType enum
diff --git a/ReleaseNotes/ReleaseNotes-2.0.14.txt b/ReleaseNotes/ReleaseNotes-2.0.14.txt
index de58035..128036d 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.14.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.14.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.14, 2.0.14.1
-=========================================
+= Release notes for Gerrit 2.0.14, 2.0.14.1
 
 Gerrit 2.0.14.1 is now available in the usual location:
 
@@ -13,8 +12,7 @@
   java -jar gerrit.war --cat sql/upgrade012_013_mysql.sql | mysql reviewdb
 ----
 
-New Features
-------------
+== New Features
 * GERRIT-177  Display branch name next to project in change list
 +
 Now its easier to see from your Mine>Changes what branch each change goes to.  For some users this may help prioritize reviews.
@@ -53,8 +51,7 @@
 +
 This is really for the server admin, the Git reflogs are now more likely to contain actual user information in them, rather than generic "gerrit2@localhost" identities.  This may help if you are mining "WTF happened to this branch" data from Git directly.
 
-Bug Fixes
----------
+== Bug Fixes
 * GERRIT-213  Fix n/p on a file with only one edit
 * GERRIT-66   Always show comments in patch views, even if no edit e...
 * Correctly handle comments after last hunk of patch
@@ -81,8 +78,7 @@
 +
 Fixed run-on addresses when more than one user was listed in To/CC headers.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.14 development (again)
 * Small doc updates.
 * Merge change 10282
diff --git a/ReleaseNotes/ReleaseNotes-2.0.15.txt b/ReleaseNotes/ReleaseNotes-2.0.15.txt
index a87cba1..a8d60a4 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.15.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.15.txt
@@ -1,23 +1,19 @@
-Release notes for Gerrit 2.0.15
-===============================
+= Release notes for Gerrit 2.0.15
 
 Gerrit 2.0.15 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 None.  For a change.  :-)
 
-New Features
-------------
+== New Features
 * Allow other ignore whitespace settings beyond IGNORE_S...
 +
 Now you can ignore whitespace inside the middle of a line, in addition to on the ends.
 
-Bug Fixes
----------
+== Bug Fixes
 * Update SSHD to include SSHD-28 (deadlock on close) bug...
 +
 Fixes a major stability problem with the internal SSHD.  Without this patch the daemon can become unresponsive, requiring a complete JVM restart to recover the daemon.  The symptom is connections appear to work sporadically... some connections are fine while others freeze during setup, or during data transfer.
@@ -31,8 +27,7 @@
 +
 Stupid bugs in the patch viewing code.  Random server errors and/or client UI crashes.
 
-Other Changes
--------------
+== Other Changes
 * Restart 2.0.15 development
 * Update JGit to 0.4.0-411-g8076bdb
 * Remove dead isGerrit method from AbstractGitCommand
diff --git a/ReleaseNotes/ReleaseNotes-2.0.16.txt b/ReleaseNotes/ReleaseNotes-2.0.16.txt
index 4d0252d..4f5a5ba 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.16.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.16.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.16
-===============================
+= Release notes for Gerrit 2.0.16
 
 Gerrit 2.0.16 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.14)
 
@@ -16,8 +14,7 @@
   java -jar gerrit.war --cat sql/upgrade013_014_mysql.sql | mysql reviewdb
 ----
 
-New Features
-------------
+== New Features
 * Search for changes created or reviewed by a user
 +
 The search box in the upper right corner now accepts "owner:email" and "reviewer:email", in addition to change numbers and commit SHA-1s.  Using owner: and reviewer: is not the most efficient query plan, as potentially the entire database is scanned.  We hope to improve on that as we move to a pure git based backend.
@@ -42,8 +39,7 @@
 +
 The "/Gerrit" suffix is no longer necessary in the URL.  Gerrit now favors just "/" as its path location.  This drops one redirection during initial page loading, slightly improving page loading performance, and making all URLs 6 characters shorter.  :-)
 
-Bug Fixes
----------
+== Bug Fixes
 * Don't create reflogs for patch set refs
 +
 Previously Gerrit created pointless 1 record reflogs for each change ref under refs/changes/.  These waste an inode on the local filesystem and provide no metadata value, as the same information is also stored in the metadata database.  These reflogs are no longer created.
@@ -64,8 +60,7 @@
 +
 If the hostname is "localhost" or "127.0.0.1", such as might happen when a user tries to proxy through an SSH tunnel, we honor the hostname anyway if OpenID is not being used.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.16 development
 * Update JGit to 0.4.9-18-g393ad45
 * Name replication threads by their remote name
diff --git a/ReleaseNotes/ReleaseNotes-2.0.17.txt b/ReleaseNotes/ReleaseNotes-2.0.17.txt
index 493a64b..8a24b22 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.17.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.17.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.17
-===============================
+= Release notes for Gerrit 2.0.17
 
 Gerrit 2.0.17 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.16)
 
@@ -22,8 +20,7 @@
   java -jar gerrit.war --cat sql/upgrade014_015_part2.sql | mysql reviewdb
 ----
 
-New Features
-------------
+== New Features
 * Add '[' and ']' shortcuts to PatchScreen.
 +
 The keys '[' and ']' can be used to navigate to previous and next file in a patch set.
@@ -56,8 +53,7 @@
 +
 The owner of a project was moved from the General tab to the Access Rights tab, under a new category called Owner.  This permits multiple groups to be designated the Owner of the project (simply grant Owner status to each group).
 
-Bug Fixes
----------
+== Bug Fixes
 * Permit author Signed-off-by to be optional
 +
 If a project requires Signed-off-by tags to appear the author tag is now optional, only the committer/uploader must provide a Signed-off-by tag.
@@ -83,8 +79,7 @@
 +
 Instead of crashing on a criss-cross merge case, Gerrit unsubmits the change and attaches a message, like it does when it encounters a path conflict.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.17 development
 * Move '[' and ']' key bindings to Navigation category
 * Use gwtexpui 1.1.2-SNAPSHOT to fix navigation keys
diff --git a/ReleaseNotes/ReleaseNotes-2.0.18.txt b/ReleaseNotes/ReleaseNotes-2.0.18.txt
index df635d9..1028185 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.18.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.18.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.18
-===============================
+= Release notes for Gerrit 2.0.18
 
 Gerrit 2.0.18 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Important Notices
------------------
+== Important Notices
 
 Please ensure you read the following important notices about this release; .18 is a much larger release than usual.
 
@@ -41,8 +39,7 @@
 the SSH authentication system.  More details can be found in the
 [http://android.git.kernel.org/?p=tools/gerrit.git;a=commit;h=080b40f7bbe00ac5fc6f2b10a861b63ce63e8add commit message].
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.17)
 
@@ -71,15 +68,13 @@
   java -jar gerrit.war --cat sql/upgrade015_016_part2.sql | mysql reviewdb
 ----
 
-New Bugs
---------
+== New Bugs
 * Memory leaks during warm restarts
 
 2.0.18 includes [http://code.google.com/p/google-guice/ Google Guice], which leaves a finalizer thread dangling when the Gerrit web application is halted by the servlet container.  As this thread does not terminate, the web context stays loaded in memory indefinitely, creating a memory leak.  Cold restarting the container in order to restart Gerrit is highly recommended.
 
 
-New Features
-------------
+== New Features
 * GERRIT-104  Allow end-users to select their own SSH username
 +
 End users may now select their own SSH username through the web interface.  The username must be unique within a Gerrit server installation.  During upgrades from 2.0.17 duplicate users are resolved by giving the username to the user who most recently logged in under it; other users will need to login through the web interface and select a unique username.  This change was necessary to fix a very minor security bug (see above).
@@ -113,8 +108,7 @@
 +
 As noted above in the section about cache changes, the disk cache is now completely optional.
 
-Bug Fixes
----------
+== Bug Fixes
 * GERRIT-5    Remove PatchSetInfo from database and get it always fr...
 +
 A very, very old bug.  We no longer mirror the commit data into the SQL database, but instead pull it directly from Git when needed.  Removing duplicated data simplifies the data store model, something that is important as we shift from an SQL database to a Git backed database.
@@ -139,8 +133,7 @@
 +
 The database schema changed, adding `patch_set_id` to the approval object, and renaming the approval table to `patch_set_approvals`.  If you have external code writing to this table, uh, sorry, its broken with this release, you'll have to update that code first.  :-\
 
-Other Changes
--------------
+== Other Changes
 
 This release is really massive because the internal code moved from some really ugly static data variables to doing almost everything through Guice injection.  Nothing user visible, but code cleanup that needed to occur before we started making additional changes to the system.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.0.19.txt b/ReleaseNotes/ReleaseNotes-2.0.19.txt
index 0e114c8..c9d9c56 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.19.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.19.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.19, 2.0.19.1, 2.0.19.2
-===================================================
+= Release notes for Gerrit 2.0.19, 2.0.19.1, 2.0.19.2
 
 Gerrit 2.0.19.2 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Important Notices
------------------
+== Important Notices
 
 * Prior User Sessions
 +
@@ -25,8 +23,7 @@
 set [http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#cache.directory cache.directory] in gerrit.config.  This allows Gerrit to flush the set
 of active sessions to disk during shutdown, and load them back during startup.
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.18)
 
@@ -44,8 +41,7 @@
 ----
 
 
-New Features
-------------
+== New Features
 * New ssh create-project command
 +
 Thanks to Ulrik Sjölin we now have `gerrit create-project`
@@ -171,8 +167,7 @@
 For more details, please see the docs:
 link:http://gerrit.googlecode.com/svn/documentation/2.0/user-changeid.html[http://gerrit.googlecode.com/svn/documentation/2.0/user-changeid.html]
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix yet another ArrayIndexOutOfBounds during side-by-s...
 +
 We found yet another bug with the side-by-side view failing
@@ -230,8 +225,7 @@
 wrong project, e.g. uploading a replacement commit to project
 B while picking a change number from project A.  Fixed.
 
-=Fixes in 2.0.19.1=
--------------------
+== =Fixes in 2.0.19.1=
 
 * Fix NPE during direct push to branch closing a change
 +
@@ -258,8 +252,7 @@
 +
 HTTP_LDAP broke using local usernames to match an account.  Fixed.
 
-=Fixes in 2.0.19.2=
--------------------
+== =Fixes in 2.0.19.2=
 * Don't line wrap project or group names in admin panels
 +
 Line wrapping group names like "All Users" when the description column
@@ -283,8 +276,7 @@
 As reported on repo-discuss, recursive search is sometimes necessary,
 and is now the default.
 
-Removed Features
-----------------
+== Removed Features
 
 * Remove support for /user/email style URLs
 +
@@ -292,8 +284,7 @@
 discoverable.  Its unlikely anyone is really using it, but if
 they are, they could try using "#q,owner:email,n,z" instead.
 
-Other Changes
--------------
+== Other Changes
 
 * Start 2.0.19 development
 * Document the Failure and UnloggedFailure classes in Ba...
diff --git a/ReleaseNotes/ReleaseNotes-2.0.2.txt b/ReleaseNotes/ReleaseNotes-2.0.2.txt
index b2d5b98..eb8546c 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.2
-==============================
+= Release notes for Gerrit 2.0.2
 
 Gerrit 2.0.2 is now available for download:
 
 link:https://www.gerritcodereview.com/[https://www.gerritcodereview.com/]
 
-Important Notes
----------------
+== Important Notes
 
 Starting with this version, Gerrit is now packaged as a single WAR file.
 Just download and drop into your webapps directory for easier deployment.
@@ -31,16 +29,14 @@
 and insert it into your container's CLASSPATH.  But I think all known
 instances are on PostgreSQL, so this is probably not a concern to anyone.
 
-New Features
-------------
+== New Features
 
 * Trailing whitespace is highlighted in diff views
 * SSHD upgraded with "faster connection" patch discussed on list
 * Git reflogs now contain the Gerrit account information of who did the push
 * Insanely long change subjects are now clipped at 80 characters
 
-All Changes
------------
+== All Changes
 
 * Switch back to -SNAPSHOT builds
 * Overhaul our build system to only create a WAR file
diff --git a/ReleaseNotes/ReleaseNotes-2.0.20.txt b/ReleaseNotes/ReleaseNotes-2.0.20.txt
index 527de8e..4f15bb0 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.20.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.20.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.20
-===============================
+= Release notes for Gerrit 2.0.20
 
 Gerrit 2.0.20 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 A prior bug (GERRIT-262) permitted some invalid data to enter into some databases.  Administrators should consider running the following update statement as part of their upgrade to .20 to make any comments which were created with this bug visible:
 ----
@@ -14,8 +12,7 @@
 ----
 Unfortunately the correct position of the comment has been lost, and the statement above will simply position them on the first line of the file.  Fortunately the lost comments were only on the wrong side of an insertion or deletion, and are generally rare.  (On my servers only 0.33% of the comments were created like this.)
 
-New Features
-------------
+== New Features
 * New ssh command approve
 +
 Patch sets can now be approved remotely via SSH.  For more
@@ -31,8 +28,7 @@
 administrators may permit automatically updating an existing
 account with a new identity by matching on the email address.
 
-Bug Fixes
----------
+== Bug Fixes
 * GERRIT-262  Disallow creating comments on line 0
 +
 Users were able to create comments in dead regions of a file.
@@ -59,8 +55,7 @@
 +
 MySQL schema upgrade scripts had a few bugs, fixed.
 
-Other Changes
--------------
+== Other Changes
 * Restart 2.0.20
 * Update MINA SSHD to 0.2.0 release
 * Update args4j to snapshot built from current CVS
diff --git a/ReleaseNotes/ReleaseNotes-2.0.21.txt b/ReleaseNotes/ReleaseNotes-2.0.21.txt
index 34ab581..5de84ff 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.21.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.21.txt
@@ -1,13 +1,11 @@
-Release notes for Gerrit 2.0.21
-===============================
+= Release notes for Gerrit 2.0.21
 
 Gerrit 2.0.21 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.19)
 
@@ -48,8 +46,7 @@
 ----
 
 
-Important Notices
------------------
+== Important Notices
 
 * Prior User Sessions
 +
@@ -106,8 +103,7 @@
 a supported provider.  I just want to make it clear that I no
 longer recommend it in production.
 
-New Features
-------------
+== New Features
 
 * GERRIT-189  Show approval status in account dashboards
 +
@@ -203,8 +199,7 @@
 to `USER` in gerrit.config.  For more details see
 link:http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.from[sendemail.from]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Fix ReviewDb to actually be per-request scoped
 +
@@ -276,8 +271,7 @@
 the same email address, or to have the same OpenID auth token.
 Fixed by asserting a unique constraint on the column.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.21 development
 * Support cleaning up a Commons DBCP connection pool
 * Clarify which Factory we are importing in ApproveComma...
diff --git a/ReleaseNotes/ReleaseNotes-2.0.22.txt b/ReleaseNotes/ReleaseNotes-2.0.22.txt
index faaca81..5e2f8b5 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.22.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.22.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.22
-===============================
+= Release notes for Gerrit 2.0.22
 
 Gerrit 2.0.22 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 There is no schema change in this release.
 
@@ -32,8 +30,7 @@
    login over SSH until they return and select a new name.
 
 
-New Features
-------------
+== New Features
 * GERRIT-280  create-project: Add --branch and cleanup arguments
 +
 The --branch option to create-project can be used to setup the
@@ -94,8 +91,7 @@
 +
 Sample git pull lines are now included in email notifications.
 
-Bug Fixes
----------
+== Bug Fixes
 * create-project: Document needing to double quote descr...
 +
 The --description flag to create-project require two levels
@@ -141,8 +137,7 @@
 Merge commits created by Gerrit were still using the older style
 integer change number; changed to use the abbreviated Change-Id.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.22 development
 * Configure Maven to build with UTF-8 encoding
 * Document minimum build requirement for Mac OS X
diff --git a/ReleaseNotes/ReleaseNotes-2.0.23.txt b/ReleaseNotes/ReleaseNotes-2.0.23.txt
index 16488d4..a3f28a7 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.23.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.23.txt
@@ -1,18 +1,15 @@
-Release notes for Gerrit 2.0.23
-===============================
+= Release notes for Gerrit 2.0.23
 
 Gerrit 2.0.23 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 There is no schema change in this release.
 
 
-New Features
-------------
+== New Features
 
 * Adding support to list merged and abandoned changes
 +
@@ -22,8 +19,7 @@
 changes in the same project while merged changes link to all merged
 changes in the same project.  These links are bookmarkable.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Fix new change email to always have SSH pull URL
 * Move git pull URL to bottom of email notifications
@@ -39,8 +35,7 @@
 
 * Fix MySQL CREATE USER example in install documentation
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.23 development
 * Move Jetty 6.x resources into a jetty6 directory
 * Move the Jetty 6.x start script to our extra directory
diff --git a/ReleaseNotes/ReleaseNotes-2.0.24.txt b/ReleaseNotes/ReleaseNotes-2.0.24.txt
index 1f08582..7da1693 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.24.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.24.txt
@@ -1,13 +1,11 @@
-Release notes for Gerrit 2.0.24, 2.0.24.1, 2.0.24.2
-===================================================
+= Release notes for Gerrit 2.0.24, 2.0.24.1, 2.0.24.2
 
 Gerrit 2.0.24 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING: This version contains a schema change* (since 2.0.21)
 
@@ -18,8 +16,7 @@
 ----
 
 
-LDAP Change
------------
+== LDAP Change
 
 LDAP groups are now bound via their full distinguished name, and not
 by their common name.  Sites using LDAP groups will need to have the
@@ -34,8 +31,7 @@
 create identically named groups.
 
 
-New Features
-------------
+== New Features
 * Check if the user has permission to upload changes
 +
 The new READ +2 permission is required to upload a change to a
@@ -69,8 +65,7 @@
 Encrypted SMTP is now supported natively within Gerrit, see
 link:http://gerrit.googlecode.com/svn/documentation/2.0/config-gerrit.html#sendemail.smtpEncryption[sendemail.smtpEncryption]
 
-Bug Fixes
----------
+== Bug Fixes
 * issue 290    Fix invalid drop index in upgrade017_018_mysql
 +
 Minor syntax error in SQL script.
@@ -142,8 +137,7 @@
 identities when the claimed identity is just a delegate to the
 delegate provider.  We now store both in the account.
 
-Fixes in 2.0.24.1
------------------
+== Fixes in 2.0.24.1
 * Fix unused import in OpenIdServiceImpl
 * dev-readme: Fix formatting of initdb command
 +
@@ -159,8 +153,7 @@
 Fixes sendemail configuration to use the documented smtppass
 variable and not the undocumented smtpuserpass variable.
 
-Fixes in 2.0.24.2
------------------
+== Fixes in 2.0.24.2
 * Fix CreateSchema to create Administrators group
 * Fix CreateSchema to set type of Registered Users group
 * Default AccountGroup instances to type INTERNAL
@@ -180,8 +173,7 @@
 Added unit tests to validate CreateSchema works properly, so we
 don't have a repeat of breakage here.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.24 development
 * Merge change Ie16b8ca2
 * Switch to the new org.eclipse.jgit package
diff --git a/ReleaseNotes/ReleaseNotes-2.0.3.txt b/ReleaseNotes/ReleaseNotes-2.0.3.txt
index 6bf3510..d319b35 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.3.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.3
-==============================
+= Release notes for Gerrit 2.0.3
 
 Gerrit 2.0.3 is now available in the usual location:
 
@@ -10,8 +9,7 @@
 existing change".  This has been an open issue in the bug tracker for a
 while, and its finally closed thanks to his work.
 
-New Features
-------------
+== New Features
 
 * GERRIT-37  Add additional reviewers to an existing change
 * Display old and new image line numbers in unified diff
@@ -19,8 +17,7 @@
 * Allow up/down arrow keys to scroll the page in patch view
 * Use a Java applet to help users load public SSH keys
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-72  Make review comments standout more from the surrounding text
 * GERRIT-7   Restart the merge queue when Gerrit starts up
@@ -36,8 +33,7 @@
 Gerrit UI.  Such a display might be able to convince a user they are
 clicking on one thing, while doing something else entirely.
 
-Other Changes
--------------
+== Other Changes
 
 * Restore -SNAPSHOT suffix after 2.0.2
 * Add a document describing Gerrit's high level design
diff --git a/ReleaseNotes/ReleaseNotes-2.0.4.txt b/ReleaseNotes/ReleaseNotes-2.0.4.txt
index fec2425..0b10756 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.4.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.4
-==============================
+= Release notes for Gerrit 2.0.4
 
 Gerrit 2.0.4 is now available in the usual location:
 
@@ -31,8 +30,7 @@
 individual user's privacy by strongly encrypting their contact
 information, and storing it "off site".
 
-Other Changes
--------------
+== Other Changes
 * Change to 2.0.3-SNAPSHOT
 * Correct grammar in the patch conflict messages
 * Document how to create branches through SSH and web
diff --git a/ReleaseNotes/ReleaseNotes-2.0.5.txt b/ReleaseNotes/ReleaseNotes-2.0.5.txt
index 70116d3..8006e12 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.5.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.5
-==============================
+= Release notes for Gerrit 2.0.5
 
 Gerrit 2.0.5 is now available in the usual location:
 
@@ -15,8 +14,7 @@
 
 link:http://gerrit.googlecode.com/svn/documentation/2.0/config-sso.html[http://gerrit.googlecode.com/svn/documentation/2.0/config-sso.html]
 
-New Features
-------------
+== New Features
 
 * GERRIT-62  Work around IE6's inability to set innerHTML on a tbody ...
 * GERRIT-62  Upgrade to gwtjsonrpc 1.0.2 for ie6 support
@@ -35,14 +33,12 @@
 +
 These features make it easier to copy patch download commands.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-79  Error out with more useful message on "push :refs/change...
 * Invalidate all SSH keys when otherwise flushing all cach...
 
-Other Changes
--------------
+== Other Changes
 
 * Set version 2.0.4-SNAPSHOT
 * Correct note in developer setup about building SSHD
diff --git a/ReleaseNotes/ReleaseNotes-2.0.6.txt b/ReleaseNotes/ReleaseNotes-2.0.6.txt
index 9d0af33..1e28da8 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.6.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.0.6
-==============================
+= Release notes for Gerrit 2.0.6
 
 Gerrit 2.0.6 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-New Features
-------------
+== New Features
 
 * GERRIT-41  Add support for abandoning a dead change
 +
@@ -14,8 +12,7 @@
 
 * Bold substrings which match query when showing completi...
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-43  Work around Safari 3.2.1 OpenID login problems
 * GERRIT-43  Suggest boosting the headerBufferSize when deploying un...
@@ -24,8 +21,7 @@
 * GERRIT-76  Upgrade to JGit 0.4.0-209-g9c26a41
 * Ensure branches modified through web UI replicate
 
-Other Changes
--------------
+== Other Changes
 
 * Start 2.0.6 development
 * Generate the id for the iframe used during OpenID login
diff --git a/ReleaseNotes/ReleaseNotes-2.0.7.txt b/ReleaseNotes/ReleaseNotes-2.0.7.txt
index afc7784..d1bc38f 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.7.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.7
-==============================
+= Release notes for Gerrit 2.0.7
 
 Gerrit 2.0.7 is now available in the usual location:
 
@@ -11,15 +10,13 @@
 
 Gerrit is still Apache 2/MIT/BSD licensed, despite the switch of a dependency.
 
-New Features
-------------
+== New Features
 
 * GERRIT-103  Display our server host keys for the client to copy an...
 +
 For the paranoid user, they can check the key fingerprint, or even copy the complete host key line for ~/.ssh/known_hosts, directly from Settings > SSH Keys.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * GERRIT-98   Require that a change be open in order to abandon it
 * GERRIT-101  Switch OpenID relying party to openid4java
@@ -34,8 +31,7 @@
 
 * Fix a NullPointerException in OpenIdServiceImpl on res...
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.7 development
 * Upgrade JGit to 0.4.0-212-g9057f1b
 * Make the sign in dialog a bit taller to avoid clipping...
diff --git a/ReleaseNotes/ReleaseNotes-2.0.8.txt b/ReleaseNotes/ReleaseNotes-2.0.8.txt
index 4b2d10a5..89e7fdd 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.8.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.8
-==============================
+= Release notes for Gerrit 2.0.8
 
 Gerrit 2.0.8 is now available in the usual location:
 
@@ -14,8 +13,7 @@
 This version has some major bug fixes for JGit.  I strongly encourage people to upgrade, we had a number of JGit bugs identified last week, all of them should be fixed in this release.
 
 
-New Features
-------------
+== New Features
 * Allow users to subscribe to submitted change events
 +
 Someone asked me on an IRC channel to have Gerrit send emails when changes are actually merged into a project.  This is what triggered the schema change; there is a new checkbox on the Watched Projects list under Settings to subscribe to these email notifications.
@@ -33,15 +31,13 @@
 +
 The reflogs now contain the remote user's IP address when Gerrit makes edits, resulting in slightly more detail than was there before.
 
-Bug Fixes
----------
+== Bug Fixes
 * Make sure only valid ObjectIds can be passed into git d...
 * GERRIT-92  Upgrade JGit to 0.4.0-262-g3c268c8
 +
 The JGit bug fixes are rather major.  I would strongly encourage upgrading.
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.8 development
 * Upgrade MINA SSHD to SVN trunk 755651
 * Fix a minor whitespace error in ChangeMail
diff --git a/ReleaseNotes/ReleaseNotes-2.0.9.txt b/ReleaseNotes/ReleaseNotes-2.0.9.txt
index d2a9196..1f683cf 100644
--- a/ReleaseNotes/ReleaseNotes-2.0.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.0.9.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.0.9
-==============================
+= Release notes for Gerrit 2.0.9
 
 Gerrit 2.0.9 is now available in the usual location:
 
@@ -20,8 +19,7 @@
 
 The SQL statement to insert a new project into the database has been changed.  Please see [http://gerrit.googlecode.com/svn/documentation/2.0/project-setup.html Project Setup] for the modified statement.
 
-New Features
-------------
+== New Features
 * GERRIT-69   Make the merge commit message more detailed when mergi...
 * Show the user's starred/not-starred icon in the change...
 * Modify Push Annotated Tag to require signed tags, or r...
@@ -34,8 +32,7 @@
 These last two changes move the hidden gerrit.fastforwardonly feature to the database and the user interface, so project owners can make use of it (or not).  Please see the new 'Change Submit Action' section in the user documentation:
 link:http://gerrit.googlecode.com/svn/documentation/2.0/project-setup.html[http://gerrit.googlecode.com/svn/documentation/2.0/project-setup.html]
 
-Bug Fixes
----------
+== Bug Fixes
 * Work around focus bugs in WebKit based browsers
 * Include our license list in the WAR file
 * Whack any prior submit approvals by myself when replac...
@@ -43,8 +40,7 @@
 * GERRIT-85   ie6: Correct rendering of commit messages
 * GERRIT-89   ie6: Fix date line wrapping in messages
 
-Other Changes
--------------
+== Other Changes
 * Start 2.0.9 development
 * Always show the commit SHA-1 next to the patch set hea...
 * Silence more non-critical log messages from openid4java
diff --git a/ReleaseNotes/ReleaseNotes-2.1.1.txt b/ReleaseNotes/ReleaseNotes-2.1.1.txt
index 9d795b6..38b6caf 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.1.txt
@@ -1,20 +1,17 @@
-Release notes for Gerrit 2.1.1, 2.1.1.1
-=======================================
+= Release notes for Gerrit 2.1.1, 2.1.1.1
 
 Gerrit 2.1.1.1 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING* This release contains a schema change.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
 ----
 
-Patch 2.1.1.1
--------------
+== Patch 2.1.1.1
 
 * Update MINA SSHD to SVN 897374
 +
@@ -28,8 +25,7 @@
 Discarding a comment from the publish comments screen caused
 a ConcurrentModificationException.  Fixed.
 
-New Features
-------------
+== New Features
 
 * issue 322    Update to GWT 2.0.0
 +
@@ -106,8 +102,7 @@
 by an accidental click.  This is especially useful when there
 is a merge error during submit.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 359    Allow updates of commits where only the parent changes
 +
@@ -158,8 +153,7 @@
 to leak file descriptors, as pipes to the external CGI were not
 always closed.  Fixed.
 
-Other
------
+== Other
 * Switch to ClientBundle
 * Update to gwtexpui-1.2.0-SNAPSHOT
 * Merge branch 'master' into gwt-2.0
diff --git a/ReleaseNotes/ReleaseNotes-2.1.10.txt b/ReleaseNotes/ReleaseNotes-2.1.10.txt
index 5464267..5c5bcc6 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.10.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.10.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.10
-===============================
+= Release notes for Gerrit 2.1.10
 
 There are no schema changes from link:ReleaseNotes-2.1.9.html[2.1.9].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.10.war[https://www.gerritcodereview.com/download/gerrit-2.1.10.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix clone for modern Git clients
 +
 The security fix in 2.1.9 broke clone for recent Git clients,
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.1.txt b/ReleaseNotes/ReleaseNotes-2.1.2.1.txt
index ae5d912..b181fee 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.1.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2.1
-================================
+= Release notes for Gerrit 2.1.2.1
 
 Gerrit 2.1.2.1 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Include smart http:// URLs in gitweb
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.2.txt b/ReleaseNotes/ReleaseNotes-2.1.2.2.txt
index 6565833..305e3e1 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2.2
-================================
+= Release notes for Gerrit 2.1.2.2
 
 Gerrit 2.1.2.2 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Add ',' to be encoded in email headers.
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.3.txt b/ReleaseNotes/ReleaseNotes-2.1.2.3.txt
index 3cfbdd1..f81092c 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2.3
-================================
+= Release notes for Gerrit 2.1.2.3
 
 Gerrit 2.1.2.3 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 528 gsql: Fix escaping of quotes in JSON
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.4.txt b/ReleaseNotes/ReleaseNotes-2.1.2.4.txt
index 5e863f7..45fcb40 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.4.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2.4
-================================
+= Release notes for Gerrit 2.1.2.4
 
 Gerrit 2.1.2.4 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-New Features
-------------
+== New Features
 
 * Add 'checkout' download command to patch sets
 +
@@ -14,8 +12,7 @@
 and checkout the patch set on a detached HEAD.  This is more suitable
 for building and testing the change locally.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 545 Fallback to ISO-8859-1 if charset isn't supported
 +
@@ -45,8 +42,7 @@
 in the directory, Gerrit crashed during sign-in while trying to
 clear out the user name.  Fixed.
 
-Documentation Corrections
-~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Documentation Corrections
 
 * documentation: Elaborate on branch level Owner
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.5.txt b/ReleaseNotes/ReleaseNotes-2.1.2.5.txt
index 6e9a49e..eece1e7 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.5.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2.5
-================================
+= Release notes for Gerrit 2.1.2.5
 
 Gerrit 2.1.2.5 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 390 Resolve objects going missing
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.1.2.txt b/ReleaseNotes/ReleaseNotes-2.1.2.txt
index e0d8c12..8e7cd5c 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.2
-==============================
+= Release notes for Gerrit 2.1.2
 
 Gerrit 2.1.2 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING* This release contains multiple schema changes.  To upgrade:
 ----
@@ -14,8 +12,7 @@
 ----
 
 
-Breakages
----------
+== Breakages
 
 * issue 421 Force validation of the author and committer lines
 +
@@ -33,11 +30,9 @@
 exists, and Forge Identity +2 where Push Branch >= +1 exists.
 
 
-New Features
-------------
+== New Features
 
-UI - Diff Viewer
-~~~~~~~~~~~~~~~~
+=== UI - Diff Viewer
 
 * issue 169 Highlight line-level (aka word) differences in files
 +
@@ -110,8 +105,7 @@
 * Use RIGHT-POINTING DOUBLE ANGLE QUOTATION MARK for tabs
 * Use a tooltip to explain whitespace errors
 
-UI - Other
-~~~~~~~~~~
+=== UI - Other
 
 * issue 408 Show summary of code review, verified on all open changes
 +
@@ -153,8 +147,7 @@
 Site administrators can now theme the UI with local site colors
 by setting theme variables in gerrit.config.
 
-Permissions
-~~~~~~~~~~~
+=== Permissions
 
 * issue 60 Change permissions to be branch based
 +
@@ -172,8 +165,7 @@
 See link:http://gerrit.googlecode.com/svn/documentation/2.1.2/access-control.html#function_MaxNoBlock[access control]
 for more details on this function.
 
-Remote Access
-~~~~~~~~~~~~~
+=== Remote Access
 
 * Enable smart HTTP under /p/ URLs
 +
@@ -210,8 +202,7 @@
 addition to single quotes.  This can make it easier to intermix
 quoting styles with the shell that is calling the SSH client .
 
-Server Administration
-~~~~~~~~~~~~~~~~~~~~~
+=== Server Administration
 
 * issue 383 Add event hook support
 +
@@ -272,8 +263,7 @@
 software driven queries over SSH easier.  The -c option accepts
 one query, executes it, and returns.
 
-Other
-~~~~~
+=== Other
 
 * Warn when a commit message isn't wrapped
 +
@@ -290,11 +280,9 @@
 rather than from the user's preferred account information.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-UI
-~~
+=== UI
 
 * Change "Publish Comments" to "Review"
 +
@@ -380,8 +368,7 @@
 * Update URL for GitHub's SSH key guide
 * issue 314 Hide group type choice if LDAP is not enabled
 
-Email
-~~~~~
+=== Email
 
 * Send missing dependencies to owners if they are the only reviewer
 +
@@ -401,8 +388,7 @@
 An additional from line is now injected at the start of the email
 body to indicate the actual user.
 
-Remote Access
-~~~~~~~~~~~~~
+=== Remote Access
 
 * issue 385 Delete session cookie when session is expired
 +
@@ -443,8 +429,7 @@
 whitespace, removing a common source of typos that lead to users
 being automatically assigned more than one Gerrit user account.
 
-Server Administration
-~~~~~~~~~~~~~~~~~~~~~
+=== Server Administration
 
 * daemon: Really allow httpd.listenUrl to end with /
 +
@@ -532,8 +517,7 @@
 sometimes failed.  Fixed by executing an implicit reload in these
 cases, reducing the number of times a user sees a failure.
 
-Development
-~~~~~~~~~~~
+=== Development
 
 * issue 427 Adjust SocketUtilTest to be more likely to pass
 +
@@ -569,8 +553,7 @@
 removed from the license file.
 
 
-Schema Changes in Detail
-------------------------
+== Schema Changes in Detail
 
 * Remove Project.Id and use only Project.NameKey
 +
@@ -594,8 +577,7 @@
 aren't possible, or to run on MySQL MyISAM tables.
 
 
-Other Changes
--------------
+== Other Changes
 * Update gwtorm to 1.1.4-SNAPSHOT
 * Add unique column ids to every column
 * Remove unused byName @SecondaryKey from ApprovalCategory
diff --git a/ReleaseNotes/ReleaseNotes-2.1.3.txt b/ReleaseNotes/ReleaseNotes-2.1.3.txt
index f4faf32..6226b93 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.3
-==============================
+= Release notes for Gerrit 2.1.3
 
 Gerrit 2.1.3 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING* This release contains multiple schema changes.  To upgrade:
 ----
@@ -14,11 +12,9 @@
 ----
 
 
-New Features
-------------
+== New Features
 
-Web UI
-~~~~~~
+=== Web UI
 
 * issue 289 Remove reviewers (or self) from a change
 +
@@ -63,8 +59,7 @@
 Access tabs.  Editing is obviously disabled, unless the user has
 owner level access to the project, or one of its branches.
 
-Access Controls
-~~~~~~~~~~~~~~~
+=== Access Controls
 
 * Branch-level read access is now supported
 +
@@ -97,8 +92,7 @@
 inherited by default, but the old exclusive behavior can be obtained
 by prefixing the reference with '-'.
 
-SSH Commands
-~~~~~~~~~~~~
+=== SSH Commands
 
 * create-account: Permit creation of batch user accounts over SSH
 * issue 269 Enable create-project for non-Administrators
@@ -125,8 +119,7 @@
 The old `gerrit approve` name will be kept around as an alias to
 provide time to migrate hooks/scripts/etc.
 
-Hooks / Stream Events
-~~~~~~~~~~~~~~~~~~~~~
+=== Hooks / Stream Events
 
 * \--change-url parameter passed to hooks
 +
@@ -147,13 +140,11 @@
 set is now included in the stream-events record, making it possible
 for a monitor to easily pull down a patch set and compile it.
 
-Contrib
-~~~~~~~
+=== Contrib
 
 * Example hook to auto-re-approve a trivial rebase
 
-Misc.
-~~~~~
+=== Misc.
 
 * transfer.timeout: Support configurable timeouts for dead clients
 +
@@ -195,11 +186,9 @@
 Apache Commons DBCP to 1.4.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 
 * issue 396 Prevent 'no-score' approvals from being recorded
 +
@@ -237,8 +226,7 @@
 'Register New Email...' button.  A cancel button was added to
 close the dialog.
 
-Server Programs
-~~~~~~~~~~~~~~~
+=== Server Programs
 
 * init: Import non-standardly named Git repositories
 +
@@ -265,8 +253,7 @@
 were not properly logged by `gerrit approve` (now gerrit review).
 Fixed by logging the root cause of the failure.
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * Display error when HTTP authentication isn't configured
 +
@@ -282,8 +269,7 @@
 during sign-in.  Administrators can enable following by adding
 `ldap.referral = follow` to their gerrit.config file.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
 * documentation: Clarified the ownership of '\-- All Projects \--'
 +
@@ -308,7 +294,6 @@
 the current stable version of the Maven plugin.
 
 
-Version
--------
+== Version
 
 e8fd49f5f7481e2f916cb0d8cfbada79309562b4
diff --git a/ReleaseNotes/ReleaseNotes-2.1.4.txt b/ReleaseNotes/ReleaseNotes-2.1.4.txt
index 3e25163..72eec55 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.4.txt
@@ -1,23 +1,19 @@
-Release notes for Gerrit 2.1.4
-==============================
+= Release notes for Gerrit 2.1.4
 
 Gerrit 2.1.4 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING* This release contains multiple schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
 ----
 
-New Features
-------------
+== New Features
 
-Change Management
-~~~~~~~~~~~~~~~~~
+=== Change Management
 
 * issue 504 Implement full query operators
 +
@@ -51,8 +47,7 @@
 watched project list, and a new menu item was added under the My menu
 to select open changes matching these watched projects.
 
-Web UI
-~~~~~~
+=== Web UI
 
 * issue 579 Remember diff formatting preferences
 +
@@ -82,8 +77,7 @@
 
 * issue 509 Make branch columns link to changes on that branch
 
-Email Notifications
-~~~~~~~~~~~~~~~~~~~
+=== Email Notifications
 
 * issue 311 No longer CC a user by default
 +
@@ -116,8 +110,7 @@
 New fields in the email footer provide additional detail, enabling
 better filtering and classification of messages.
 
-Access Control
-~~~~~~~~~~~~~~
+=== Access Control
 
 * Support regular expressions for ref access rules
 +
@@ -136,8 +129,7 @@
 Groups can now be created over SSH by administrators using the
 `gerrit create-group` command.
 
-Authentication
-~~~~~~~~~~~~~~
+=== Authentication
 
 * Remove password authentication over SSH
 +
@@ -157,8 +149,7 @@
 setting, rather than expiring when the browser closes.  (Previously
 sessions expired when the browser exited.)
 
-Misc.
-~~~~~
+=== Misc.
 
 * Add topic, lastUpdated, sortKey to ChangeAttribute
 +
@@ -184,11 +175,9 @@
 Updated JGit to 0.8.4.89-ge2f5716, log4j to 1.2.16, GWT to 2.0.4,
 sfl4j to 1.6.1, easymock to 3.0, JUnit to 4.8.1.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 
 * issue 352 Confirm branch deletion in web UI
 +
@@ -211,15 +200,13 @@
 Keyboard navigation to standard links like 'Google Accounts'
 wasn't supported.  Fixed.
 
-Misc.
-~~~~~
+=== Misc.
 
 * issue 614 Fix 503 error when Jetty cancels a request
 +
 A bug was introduced in 2.1.3 that caused a server 503 error
 when a fetch/pull/clone or push request timed out.  Fixed.
 
-Version
--------
+== Version
 
 ae59d1bf232bba16d4d03ca924884234c68be0f2
diff --git a/ReleaseNotes/ReleaseNotes-2.1.5.txt b/ReleaseNotes/ReleaseNotes-2.1.5.txt
index 4934223..88288e2 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.5.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.1.5
-==============================
+= Release notes for Gerrit 2.1.5
 
 Gerrit 2.1.5 is now available:
 
@@ -8,8 +7,7 @@
 This is primarily a bug fix release to 2.1.4, but some additional
 new features were included so its named 2.1.5 rather than 2.1.4.1.
 
-Upgrade Instructions
---------------------
+== Upgrade Instructions
 
 If upgrading from version 2.1.4, simply replace the WAR file in
 `'site_path'/bin/gerrit.war` and restart Gerrit.
@@ -18,11 +16,9 @@
 `java -jar gerrit.war init -d 'site_path'` to upgrade the schema,
 and restart Gerrit.
 
-New Features
-------------
+== New Features
 
-Web UI
-~~~~~~
+=== Web UI
 * issue 361 Enable commenting on commit messages
 +
 The commit message of a change can now be commented on inline, and
@@ -49,8 +45,7 @@
 A 'diffstat' is shown for each file, summarizing the size of the
 change on that file in terms of number of lines added or deleted.
 
-Email Notifications
-~~~~~~~~~~~~~~~~~~~
+=== Email Notifications
 * issue 452 Include a quick summary of the size of a change in email
 +
 After the file listing, a summary totaling the number of files
@@ -58,11 +53,9 @@
 help reviewers to get a quick estimation on the time required for
 them to review the change.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 * issue 639 Fix keyboard shortcuts under Chrome/Safari
 +
 Keyboard shortcuts didn't work properly on modern WebKit browsers
@@ -91,8 +84,7 @@
 is present and will toggle the user's starred flag for that change.
 Fixed.
 
-Access Control
-~~~~~~~~~~~~~~
+=== Access Control
 * issue 672 Fix branch owner adding exclusive ACL
 +
 Branch owners could not add exclusive ACLs within their branch
@@ -118,8 +110,7 @@
 bug which failed to consider the project inheritance if any branch
 (not just the one being uploaded to) denied upload access.
 
-Misc.
-~~~~~
+=== Misc.
 * issue 641 Don't pass null arguments to hooks
 +
 Some hooks crashed inside of the server during invocation because the
@@ -162,12 +153,10 @@
 Gerrit Code Review was only accepting the ';' syntax.  Fixed
 to support both.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 * Fixed example for gerrit create-account.
 * gerrit.sh: Correct /etc/default path in error message
 
-Version
--------
+== Version
 
 2765ff9e5f821100e9ca671f4d502b5c938457a5
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.1.txt b/ReleaseNotes/ReleaseNotes-2.1.6.1.txt
index a490c0a..4626c7b 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.6.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.1.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.6.1
-================================
+= Release notes for Gerrit 2.1.6.1
 
 Gerrit 2.1.6.1 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.6.1.war[https://www.gerritcodereview.com/download/gerrit-2.1.6.1.war]
 
-Schema Change
--------------
+== Schema Change
 
 If upgrading from 2.1.6, there are no schema changes.  Replace the
 WAR and restart the daemon.
@@ -17,8 +15,7 @@
   java -jar gerrit.war init -d site_path
 ----
 
-New Features
-------------
+== New Features
 * Display the originator of each access rule
 +
 The project access panel now shows which project each rule inherits
@@ -37,8 +34,7 @@
 project, provided that the parent project is not the root level
 \-- All Projects \--.
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix disabled intraline difference checkbox
 +
 Intraline difference couldn't be enabled once it was disabled by
diff --git a/ReleaseNotes/ReleaseNotes-2.1.6.txt b/ReleaseNotes/ReleaseNotes-2.1.6.txt
index 520b2a6..83689e7 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.6.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.6
-==============================
+= Release notes for Gerrit 2.1.6
 
 Gerrit 2.1.6 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.6.war[https://www.gerritcodereview.com/download/gerrit-2.1.6.war]
 
-Schema Change
--------------
+== Schema Change
 
 *WARNING* This release contains multiple schema changes.  To upgrade:
 ----
@@ -14,11 +12,9 @@
 ----
 
 
-New Features
-------------
+== New Features
 
-Web UI
-~~~~~~
+=== Web UI
 * issue 312 Abandoned changes can now be restored.
 * issue 698 Make date and time fields customizable
 * issue 556 Preference to display patch sets in reverse order
@@ -39,8 +35,7 @@
 This is built upon experimental merge code inherited from JGit,
 and is therefore still experimental in Gerrit.
 
-Change Query
-~~~~~~~~~~~~
+=== Change Query
 * issue 688 Match branch, topic, project, ref by regular expressions
 +
 Similar to other features in Gerrit Code Review, starting any of these
@@ -80,8 +75,7 @@
 Gerrit change submission, Gerrit will now send a new "ref-updated"
 event to the event stream.
 
-User Management
-~~~~~~~~~~~~~~~
+=== User Management
 * SSO via client SSL certificates
 +
 A new auth.type of CLIENT_SSL_CERT_LDAP supports authenticating users
@@ -123,8 +117,7 @@
 The internal SSH daemon now supports additional configuration
 settings to reduce the risk of abuse.
 
-Administration
-~~~~~~~~~~~~~~
+=== Administration
 * issue 558 Allow Access rights to be edited by clicking on them.
 
 * New 'Project Owner' system group to define default rights
@@ -186,11 +179,9 @@
 prevent the older (pre-filter-branch) history from being reintroduced
 into the repository.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 * issue 498 Enable Keyboard navigation after change submit
 * issue 691 Make ']' on last file go up to change
 * issue 741 Make ENTER work for 'Create Group'
@@ -224,13 +215,11 @@
 are not project owners may now only view access rights for branches
 they have at least READ +1 permission on.
 
-Change Query
-~~~~~~~~~~~~
+=== Change Query
 * issue 689 Fix age:4days to parse correctly
 * Make branch: operator slightly less ambiguous
 
-Push Support
-~~~~~~~~~~~~
+=== Push Support
 * issue 695 Permit changing only the author of a commit
 +
 Correcting only the author of a change failed to upload the new patch
@@ -257,8 +246,7 @@
 create changes for review were unable to push to a project.  Fixed.
 This (finally) makes Gerrit a replacement for Gitosis or Gitolite.
 
-Replication
-~~~~~~~~~~~
+=== Replication
 * issue 683 Don't assume authGroup = "Registered Users" in replication
 +
 Previously a misconfigured authGroup in replication.config may have
@@ -281,8 +269,7 @@
 
 * issue 658 Allow refspec shortcuts (push = master) for replication
 
-User Management
-~~~~~~~~~~~~~~~
+=== User Management
 * Ensure proper escaping of LDAP group names
 +
 Some special characters may appear in LDAP group names, these must be
@@ -295,8 +282,7 @@
 but cannot because it is already in use by another user on this
 server, the new account won't be created.
 
-Administration
-~~~~~~~~~~~~~~
+=== Administration
 * gerrit.sh: actually verify running processes
 +
 Previously `gerrit.sh check` claimed a server was running if the
@@ -317,6 +303,5 @@
 child project.  Permissions can now be overidden if the category,
 group name and reference name all match.
 
-Version
--------
+== Version
 ef16a1816f293d00c33de9f90470021e2468a709
diff --git a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
index fdd7725..9c9e6e1 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.7.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.7.2
-================================
+= Release notes for Gerrit 2.1.7.2
 
 Gerrit 2.1.7.2 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.7.2.war[https://www.gerritcodereview.com/download/gerrit-2.1.7.2.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * issue 997 Resolve Project Owners when checking access rights
 +
 Members of the 'Project Owners' magical group did not always have
diff --git a/ReleaseNotes/ReleaseNotes-2.1.7.txt b/ReleaseNotes/ReleaseNotes-2.1.7.txt
index 5123279..ad440b5 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.7.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.7
-==============================
+= Release notes for Gerrit 2.1.7
 
 Gerrit 2.1.7 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.7.war[https://www.gerritcodereview.com/download/gerrit-2.1.7.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING* This release contains multiple schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -18,8 +16,7 @@
   java -jar gerrit.war ExportReviewNotes -d site_path
 ----
 
-Memory Usage Increase
----------------------
+== Memory Usage Increase
 *WARNING* The JGit delta base cache, whose size is controlled by
 `core.deltaBaseCacheLimit`, has changed in this release from being a
 JVM-wide singleton to per-thread. This alters the memory usage, going
@@ -27,17 +24,14 @@
 change improves performance on big repositories, but may need a larger
 `container.heapLimit` if the number of concurrent operations is high.
 
-New Features
-------------
+== New Features
 
-Change Data
-~~~~~~~~~~~
+=== Change Data
 * issue 64 Create Git notes for submitted changes
 +
 Git notes are automatically added to the `refs/notes/review`.
 
-Query
-~~~~~
+=== Query
 * Search project names by substring
 +
 Entering a word with no operator (for example `gerrit`) will be
@@ -49,8 +43,7 @@
 search for changes whose owner or that has a reviewer in (or not
 in if prefixed with `-`) the specified group.
 
-Web UI
-~~~~~~
+=== Web UI
 * Add reviewer/verifier name beside check/plus/minus
 +
 Change lists (such as from a search result, or in a user's dashboard)
@@ -90,17 +83,14 @@
 SSH public key files by hand.
 
 
-SSH Commands
-~~~~~~~~~~~~
+=== SSH Commands
 * issue 674 Add abandon/restore to `gerrit review`
 * Add `gerrit version` command
 
-Change Upload
-~~~~~~~~~~~~~
+=== Change Upload
 * Display a more verbose "you are not author/committer" message
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 * Detailed error message explanations
 +
 Most common error messages are now described in detail in the
@@ -111,8 +101,7 @@
 * issue 905 Document reverse proxy using Nginx
 * Updated system scaling data in 'System Design'
 
-Outgoing Mail
-~~~~~~~~~~~~~
+=== Outgoing Mail
 * Optionally add Importance and Expiry-Days headers
 +
 New gerrit.config variable `sendemail.importance` can be set to `high`
@@ -122,8 +111,7 @@
 
 * Add support for SMTP AUTH LOGIN
 
-Administration
-~~~~~~~~~~~~~~
+=== Administration
 * Group option to make group visible to all users
 +
 A new group option permits the group to be visible to all users,
@@ -181,8 +169,7 @@
 path used for the authentication cookie, which may be necessary if
 a reverse proxy maps requests to the managed gitweb.
 
-Replication
-~~~~~~~~~~~
+=== Replication
 * Add adminUrl to replication for repository creation
 +
 Replication remotes can be configured with `remote.name.adminUrl` to
@@ -196,8 +183,7 @@
 Replication can now be performed over an authenticated smart HTTP
 transport, in addition to anonymous Git and authenticated SSH.
 
-Misc.
-~~~~~
+=== Misc.
 * Alternative URL for Gerrit's managed Gitweb
 +
 The internal gitweb served from `/gitweb` can now appear to be from a
@@ -210,11 +196,9 @@
 to 1.6, Apache Commons Net to 2.2, Apache Commons Pool to 1.5.5, JGit
 to 0.12.1.53-g5ec4977, MINA SSHD to 0.5.1-r1095809.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 * issue 853 Incorrect side-by-side display of modified lines
 +
 A bug in JGit lead to the side-by-side view displaying wrong and
@@ -261,12 +245,10 @@
 * Always display button text in black
 * Always disable content merge option if user can't change project
 
-commit-msg Hook
-~~~~~~~~~~~~~~~
+=== commit-msg Hook
 * issue 922 Fix commit-msg hook to run on Solaris
 
-Outgoing Mail
-~~~~~~~~~~~~~
+=== Outgoing Mail
 * issue 780 E-mail about failed merge should not use Anonymous Coward
 +
 Some email was sent as Anonymous Coward, even when the user had a
@@ -281,8 +263,7 @@
 * Do not email reviewers adding themselves as reviewers
 * Fix comma/space separation in email templates
 
-Pushing Changes
-~~~~~~~~~~~~~~~
+=== Pushing Changes
 * Avoid huge pushes during refs/for/BRANCH push
 +
 With Gerrit 2.1.6, clients started to push possibly hundreds of
@@ -356,8 +337,7 @@
 no mention if it on the server error log. Now it is reported so the
 site administrator also knows about it.
 
-SSH Commands
-~~~~~~~~~~~~
+=== SSH Commands
 * issue 755 Send new patchset event after its available
 * issue 814 Evict initial members of group created by SSH
 * issue 879 Fix replication of initial empty commit in new project
@@ -365,8 +345,7 @@
 * Automatically create user account(s) as necessary
 * Move SSH command creation off NioProcessor threads
 
-Administration
-~~~~~~~~~~~~~~
+=== Administration
 * Enable git reflog for all newly created projects
 +
 Previously branch updates were not being recorded in the native Git
@@ -388,8 +367,7 @@
 * gerrit.sh: Fix issues on Solaris
 * gerrit.sh: Support spaces in JAVA_HOME
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 * issue 800 documentation: Show example of review -m
 * issue 896 Clarify that $\{name\} is required for replication.
 * Fix spelling mistake in 'Searching Changes' documentation
diff --git a/ReleaseNotes/ReleaseNotes-2.1.8.txt b/ReleaseNotes/ReleaseNotes-2.1.8.txt
index 476e312..e1ed11c 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.8.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.8
-==============================
+= Release notes for Gerrit 2.1.8
 
 Gerrit 2.1.8 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.8.war[https://www.gerritcodereview.com/download/gerrit-2.1.8.war]
 
-New Features
-------------
+== New Features
 * Add cache for tag advertisements
 +
 When READ level access controls are used on references/branches, this
@@ -39,8 +37,7 @@
 MS-DOS compatibility may have permitted access to special device
 files in any directory, rather than just the "\\.\" device namespace.
 
-Bug Fixes
----------
+== Bug Fixes
 * issue 518 Fix MySQL counter resets
 +
 MySQL databases lost their change_id, account_id counters after
diff --git a/ReleaseNotes/ReleaseNotes-2.1.9.txt b/ReleaseNotes/ReleaseNotes-2.1.9.txt
index 2efc5b6..63bcb20 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.9.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.1.9
-==============================
+= Release notes for Gerrit 2.1.9
 
 There are no schema changes from link:ReleaseNotes-2.1.8.html[2.1.8].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.1.9.war[https://www.gerritcodereview.com/download/gerrit-2.1.9.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Patch JGit security hole
 +
 The security hole may permit a modified Git client to gain access
diff --git a/ReleaseNotes/ReleaseNotes-2.1.txt b/ReleaseNotes/ReleaseNotes-2.1.txt
index 127ab09..28cc90d 100644
--- a/ReleaseNotes/ReleaseNotes-2.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.1.txt
@@ -1,20 +1,17 @@
-Release notes for Gerrit 2.1
-============================
+= Release notes for Gerrit 2.1
 
 Gerrit 2.1 is now available in the usual location:
 
 link:https://www.gerritcodereview.com/download/index.html[https://www.gerritcodereview.com/download/index.html]
 
 
-New site_path Layout
---------------------
+== New site_path Layout
 
 The layout of the `$site_path` directory has been changed in 2.1.
 Configuration files are now stored within the `etc/` subdirectory
 and will be automatically moved there by the init subcommand.
 
-Upgrading From 2.0.x
---------------------
+== Upgrading From 2.0.x
 
   If the server is running a version older than 2.0.24, upgrade the
   database schema to the current schema version of 19.  Download
@@ -37,8 +34,7 @@
 sendemail.smtpPass and ldap.password out of gerrit.config into a
 read-protected secure.config file.
 
-New Daemon Mode
----------------
+== New Daemon Mode
 
 Gerrit 2.1 and later embeds the Jetty servlet container, and
 runs it automatically as part of `java -jar gerrit.war daemon`.
@@ -57,8 +53,7 @@
 link:http://gerrit.googlecode.com/svn/documentation/2.1/index.html[http://gerrit.googlecode.com/svn/documentation/2.1/index.html]
 
 
-New Features
-------------
+== New Features
 
 * issue 19     Link to issue tracker systems from commits
 +
@@ -184,8 +179,7 @@
 +
 Most dependencies were updated to their current stable versions.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 259    Improve search hint to include owner:email
 +
@@ -268,8 +262,7 @@
 `$HOME/.gerritcodereview/tmp`, which should be isolated from
 the host system's /tmp cleaner.
 
-Other=
-------
+== Other=
 
 * Pick up gwtexpui 1.1.4-SNAPSHOT
 * Merge change Ia64286d3
diff --git a/ReleaseNotes/ReleaseNotes-2.10.1.txt b/ReleaseNotes/ReleaseNotes-2.10.1.txt
index 3065492..72d26d1 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.1
-===============================
+= Release notes for Gerrit 2.10.1
 
 There are no schema changes from link:ReleaseNotes-2.10.html[2.10].
 
@@ -7,8 +6,7 @@
 link:https://www.gerritcodereview.com/download/gerrit-2.10.1.war[
 https://www.gerritcodereview.com/download/gerrit-2.10.1.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2260[Issue 2260]:
 LDAP horrendous login time due to recursive lookup.
@@ -19,8 +17,7 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=3211[Issue 3211]:
 Intermittent Null Pointer Exception when showing process queue.
 
-LDAP
-----
+== LDAP
 
 * Several performance improvements when using LDAP, both in the number of LDAP
 requests and in the amount of data transferred.
@@ -28,13 +25,11 @@
 * Sites using LDAP for authentication but otherwise rely on local Gerrit groups
 should set the new `ldap.fetchMemberOfEagerly` option to `false`.
 
-OAuth
------
+== OAuth
 
 * Expose extension point for generic OAuth providers.
 
-OpenID
-------
+== OpenID
 
 * Add support for Launchpad on the login form.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.10.2.txt b/ReleaseNotes/ReleaseNotes-2.10.2.txt
index ac7c866..49be04e 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.2
-===============================
+= Release notes for Gerrit 2.10.2
 
 There are no schema changes from link:ReleaseNotes-2.10.1.html[2.10.1].
 
@@ -7,8 +6,7 @@
 link:https://www.gerritcodereview.com/download/gerrit-2.10.2.war[
 https://www.gerritcodereview.com/download/gerrit-2.10.2.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Work around MyersDiff infinite loop in PatchListLoader. If the MyersDiff diff
 doesn't finish within 5 seconds, interrupt it and fall back to a different diff
@@ -16,15 +14,13 @@
 loop is detected is that the files in the commit will not be compared in-depth,
 which will result in bigger edit regions.
 
-Secondary Index
----------------
+== Secondary Index
 
 * Online reindexing: log the number of done/failed changes in the error_log.
 Administrators can use the logged information to decide whether to activate the
 new index version or not.
 
-Gitweb
-------
+== Gitweb
 
 * Do not return `Forbidden` when clicking on Gitweb breadcrumb. Now when the
 user clicks on the parent folder, redirect to Gerrit projects list screen with
diff --git a/ReleaseNotes/ReleaseNotes-2.10.3.1.txt b/ReleaseNotes/ReleaseNotes-2.10.3.1.txt
index 39312eb..7777bd8 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.3.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.3.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.3.1
-=================================
+= Release notes for Gerrit 2.10.3.1
 
 There are no schema changes from link:ReleaseNotes-2.10.3.html[2.10.3].
 
diff --git a/ReleaseNotes/ReleaseNotes-2.10.3.txt b/ReleaseNotes/ReleaseNotes-2.10.3.txt
index f7a69c3..1dd96e7 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.10.3
-===============================
+= Release notes for Gerrit 2.10.3
 
 Download:
 link:https://www.gerritcodereview.com/download/gerrit-2.10.3.war[
 https://www.gerritcodereview.com/download/gerrit-2.10.3.war]
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* There are no schema changes from
 link:ReleaseNotes-2.10.2.html[2.10.2], but Bouncycastle was upgraded to 1.51.
@@ -26,8 +24,7 @@
   java -jar gerrit.war init -d site_path
 ----
 
-New Features
-------------
+== New Features
 
 * Support hybrid OpenID and OAuth2 authentication
 +
@@ -36,15 +33,13 @@
 Particularly, linking of user identities across protocol boundaries and even from
 one OAuth2 identity to another OAuth2 identity wasn't implemented yet.
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * Allow to configure
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10.3/config-gerrit.html#sshd.rekeyBytesLimit[
 SSHD rekey parameters].
 
-SSH
----
+== SSH
 
 * Update SSHD to 0.14.0.
 +
@@ -56,8 +51,7 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=2797[Issue 2797]:
 Add support for ECDSA based public key authentication.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Prevent wrong content type for CSS files.
 +
@@ -69,8 +63,7 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=3289[Issue 3289]:
 Prevent NullPointerException in Gitweb servlet.
 
-Replication plugin
-~~~~~~~~~~~~~~~~~~
+=== Replication plugin
 
 * Set connection timeout to 120 seconds for SSH remote operations.
 +
@@ -78,8 +71,7 @@
 operation. By setting a timeout, we ensure the operation does not get stuck
 forever, essentially blocking all future remote git creation operations.
 
-OAuth extension point
-~~~~~~~~~~~~~~~~~~~~~
+=== OAuth extension point
 
 * Respect servlet context path in URL for login token
 +
@@ -90,34 +82,29 @@
 +
 After web session cache expiration there is no way to re-sign-in into Gerrit.
 
-Daemon
-~~~~~~
+=== Daemon
 
 * Print proper names for tasks in output of `show-queue` command.
 +
 Some tasks were not displayed with the proper name.
 
-Web UI
-~~~~~~
+=== Web UI
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3044[Issue 3044]:
 Remove stripping `#` in login redirect.
 
-SSH
-~~~
+=== SSH
 
 * Prevent double authentication for the same public key.
 
 
-Performance
------------
+== Performance
 
 * Improved performance when creating a new branch on a repository with a large
 number of changes.
 
 
-Upgrades
---------
+== Upgrades
 
 * Update Bouncycastle to 1.51.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.10.4.txt b/ReleaseNotes/ReleaseNotes-2.10.4.txt
index c16e7e9..c69a946 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.4.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.4
-===============================
+= Release notes for Gerrit 2.10.4
 
 There are no schema changes from link:ReleaseNotes-2.10.3.1.html[2.10.3.1].
 
@@ -7,8 +6,7 @@
 link:https://www.gerritcodereview.com/download/gerrit-2.10.4.war[
 https://www.gerritcodereview.com/download/gerrit-2.10.4.war]
 
-New Features
-------------
+== New Features
 
 * Support identity linking in hybrid OpenID and OAuth2 authentication.
 +
@@ -20,8 +18,7 @@
 Linking of user identities from one OAuth2 identity to another OAuth2
 identity is supported.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3300[Issue 3300]:
 Fix >10x performance degradation for Git push and replication operations.
@@ -35,15 +32,13 @@
 The padding was not flushed, which caused the downloaded patch to not be
 valid base64.
 
-OAuth extension point
-~~~~~~~~~~~~~~~~~~~~~
+=== OAuth extension point
 
 * Check for session validity during logout.
 +
 When user was trying to log out, after Gerrit restart, the session was
 invalidated and IllegalStateException was recorded in the error_log.
 
-Updates
--------
+== Updates
 
 * Update jgit to 4.0.0.201505050340-m2.
diff --git a/ReleaseNotes/ReleaseNotes-2.10.5.txt b/ReleaseNotes/ReleaseNotes-2.10.5.txt
index eb48c31..a221b58 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.5.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.5
-===============================
+= Release notes for Gerrit 2.10.5
 
 There are no schema changes from link:ReleaseNotes-2.10.4.html[2.10.4].
 
@@ -7,8 +6,7 @@
 link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.5.war[
 https://gerrit-releases.storage.googleapis.com/gerrit-2.10.5.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Update JGit to include a memory leak fix as discussed
 link:https://groups.google.com/forum/#!topic/repo-discuss/RRQT_xCqz4o[here]
@@ -21,7 +19,6 @@
 * Fixed a regression caused by the defaultValue feature which broke the ability
 to remove labels in subprojects
 
-Updates
--------
+== Updates
 
 * Update JGit to v4.0.0.201506090130-r
diff --git a/ReleaseNotes/ReleaseNotes-2.10.6.txt b/ReleaseNotes/ReleaseNotes-2.10.6.txt
index 94a95bd..7c12d11 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.6.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.6
-===============================
+= Release notes for Gerrit 2.10.6
 
 There are no schema changes from link:ReleaseNotes-2.10.5.html[2.10.5].
 
@@ -7,8 +6,7 @@
 link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.6.war[
 https://gerrit-releases.storage.googleapis.com/gerrit-2.10.6.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Fix generation of licenses in documentation.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.10.7.txt b/ReleaseNotes/ReleaseNotes-2.10.7.txt
index 28cf37b..f369999 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.7.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10.7
-===============================
+= Release notes for Gerrit 2.10.7
 
 There are no schema changes from link:ReleaseNotes-2.10.6.html[2.10.6].
 
@@ -7,8 +6,7 @@
 link:https://gerrit-releases.storage.googleapis.com/gerrit-2.10.7.war[
 https://gerrit-releases.storage.googleapis.com/gerrit-2.10.7.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3361[Issue 3361]:
 Synchronize Myers diff and Histogram diff invocations to prevent pack file
diff --git a/ReleaseNotes/ReleaseNotes-2.10.txt b/ReleaseNotes/ReleaseNotes-2.10.txt
index f6bd951..4f068cc 100644
--- a/ReleaseNotes/ReleaseNotes-2.10.txt
+++ b/ReleaseNotes/ReleaseNotes-2.10.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.10
-=============================
+= Release notes for Gerrit 2.10
 
 
 Gerrit 2.10 is now available:
@@ -14,8 +13,7 @@
 link:ReleaseNotes-2.9.4.html[Gerrit 2.9.4].
 These bug fixes are *not* listed in these release notes.
 
-Important Notes
----------------
+== Important Notes
 
 
 *WARNING:* This release contains schema changes.  To upgrade:
@@ -44,8 +42,7 @@
 link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 
 * Support for externally loaded plugins.
@@ -62,16 +59,13 @@
 can configure the default contents of the menu.
 
 
-New Features
-------------
+== New Features
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 
-Global
-^^^^^^
+==== Global
 
 * Add 'All-Users' project to store meta data for all users.
 
@@ -82,8 +76,7 @@
 * Allow UiActions to perform redirects without JavaScript.
 
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 
 * Display avatar for author, committer, and change owner.
@@ -104,8 +97,7 @@
 Allow to customize Submit button label and tooltip.
 
 
-Side-by-Side Diff Screen
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== Side-by-Side Diff Screen
 
 * Allow the user to select the syntax highlighter.
 
@@ -116,8 +108,7 @@
 * Add syntax highlighting of the commit message.
 
 
-Change List / Dashboards
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== Change List / Dashboards
 
 * Remove age operator when drilling down from a dashboard to a query.
 
@@ -132,8 +123,7 @@
 when 'R' is pressed.  The same binding is added for custom dashboards.
 
 
-Project Screens
-^^^^^^^^^^^^^^^
+==== Project Screens
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2751[Issue 2751]:
 Add support for filtering by regex in project list screen.
@@ -142,8 +132,7 @@
 
 * Add branch actions to 'Projects > Branches' view.
 
-User Preferences
-^^^^^^^^^^^^^^^^
+==== User Preferences
 
 
 * Users can customize the contents of the 'My' menu from the preferences
@@ -156,8 +145,7 @@
 names and 'Show Username' to show usernames in the change list.
 
 
-Secondary Index / Search
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Secondary Index / Search
 
 
 * Allow to search projects by prefix.
@@ -177,8 +165,7 @@
 rather than just the project name.
 
 
-ssh
-~~~
+=== ssh
 
 
 * Expose SSHD backend in
@@ -187,12 +174,10 @@
 
 * Add support for JCE (Java Cryptography Extension) ciphers.
 
-REST API
-~~~~~~~~
+=== REST API
 
 
-General
-^^^^^^^
+==== General
 
 
 * Remove `kind` attribute from REST containers.
@@ -201,8 +186,7 @@
 
 * Accept `HEAD` in RestApiServlet.
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-accounts.html#get-user-preferences[
@@ -211,8 +195,7 @@
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-accounts.html#set-user-preferences[
 Set user preferences].
 
-Changes
-^^^^^^^
+==== Changes
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2338[Issue 2338]:
@@ -226,8 +209,7 @@
 If the `other-branches` option is specified, the mergeability will also be
 checked for all other branches.
 
-Config
-^^^^^^
+==== Config
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-config.html#list-tasks[
@@ -254,8 +236,7 @@
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-config.html#get-summary[
 Get server summary].
 
-Projects
-^^^^^^^^
+==== Projects
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-projects.html#ban-commit[
@@ -276,8 +257,7 @@
 list projects endpoint] to support query offset.
 
 
-Daemon
-~~~~~~
+=== Daemon
 
 
 * Add change subject to output of change URL on push.
@@ -292,8 +272,7 @@
 Add change kind to PatchSetCreatedEvent.
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * Use
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/config-gerrit.html#core.useRecursiveMerge[
@@ -344,8 +323,7 @@
 configure Tomcat] to allow embedded slashes.
 
 
-Misc
-~~~~
+=== Misc
 
 * Don't allow empty user name and passwords in InternalAuthBackend.
 
@@ -353,8 +331,7 @@
 Add change-owner parameter to gerrit hooks.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
 * Support for externally loaded plugins.
 +
@@ -428,11 +405,9 @@
 ** Star/unstar changes
 ** Check if revision needs rebase
 
-Bug Fixes
----------
+== Bug Fixes
 
-General
-~~~~~~~
+=== General
 
 * Use fixed rate instead of fixed delay for log file compression.
 +
@@ -453,8 +428,7 @@
 made, but one was not being closed. This eventually caused resource exhaustion
 and LDAP authentications failed.
 
-Access Permissions
-~~~~~~~~~~~~~~~~~~
+=== Access Permissions
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2995[Issue 2995]:
 Fix faulty behaviour in `BLOCK` permission.
@@ -464,11 +438,9 @@
 case the `BLOCK` would always win for the child, even though the `BLOCK` was
 overruled in the parent.
 
-Web UI
-~~~~~~
+=== Web UI
 
-General
-^^^^^^^
+==== General
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2595[Issue 2595]:
 Make gitweb redirect to login.
@@ -486,8 +458,7 @@
 if a site administrator ran `java -war gerrit.war init -d /some/existing/site --batch`.
 
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 * Don't linkify trailing dot or comma in messages.
 +
@@ -525,8 +496,7 @@
 * Fix exception when clicking on a binary file without being signed in.
 
 
-Side-By-Side Diff
-^^^^^^^^^^^^^^^^^
+==== Side-By-Side Diff
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2970[Issue 2970]:
 Fix misalignment of side A and side B for long insertion/deletion blocks.
@@ -556,30 +526,25 @@
 * Include content on identical files with mode change.
 
 
-User Settings
-^^^^^^^^^^^^^
+==== User Settings
 
 * Avoid loading all SSH keys when adding a new one.
 
 
-Secondary Index / Search
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Secondary Index / Search
 
 
 * Omit corrupt changes from search results.
 
 * Allow illegal label names from default search predicate.
 
-REST
-~~~~
+=== REST
 
-General
-^^^^^^^
+==== General
 
 * Fix REST API responses for 3xx and 4xx classes.
 
-Changes
-^^^^^^^
+==== Changes
 
 * Fix inconsistent behaviour in the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-changes.html#add-reviewer[
@@ -589,8 +554,7 @@
 to add a user who had no visibility to the change or whose account was invalid.
 
 
-Changes
-^^^^^^^
+==== Changes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2583[Issue 2583]:
 Reject inline comments on files that do not exist in the patch set.
@@ -620,8 +584,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.10/rest-api-changes.html#list-comments[
 List Comments] endpoint.
 
-SSH
-~~~
+=== SSH
 
 
 * Prevent double authentication for the same public key.
@@ -641,8 +604,7 @@
 from being logged when `git receive-pack` was executed instead of `git-receive-pack`.
 
 
-Daemon
-~~~~~~
+=== Daemon
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2284[Issue 2284]:
@@ -661,11 +623,9 @@
 directly pushed changes.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
-General
-^^^^^^^
+==== General
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2895[Issue 2895]:
@@ -680,8 +640,7 @@
 
 * Fix ChangeListener auto-registered implementations.
 
-Replication
-^^^^^^^^^^^
+==== Replication
 
 
 * Move replication logs into a separate file.
@@ -691,8 +650,7 @@
 * Show replication ID in the log and in show-queue command.
 
 
-Upgrades
---------
+== Upgrades
 
 
 * Update Guava to 17.0
diff --git a/ReleaseNotes/ReleaseNotes-2.11.1.txt b/ReleaseNotes/ReleaseNotes-2.11.1.txt
index be19fc5..3583421 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.1
-===============================
+= Release notes for Gerrit 2.11.1
 
 Gerrit 2.11.1 is now available:
 
@@ -14,8 +13,7 @@
 There are no schema changes from link:ReleaseNotes-2.11.html[2.11].
 
 
-New Features
-------------
+== New Features
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=321[Issue 321]:
 Use in-memory Lucene index for a better reviewer suggestion.
@@ -27,11 +25,9 @@
 suggest.fullTextSearchRefresh] parameter.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-Performance
-~~~~~~~~~~~
+=== Performance
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3363[Issue 3363]:
 Fix performance degrade in background mergeability checks.
@@ -57,8 +53,7 @@
 +
 The change edit information was being loaded twice.
 
-Index
-~~~~~
+=== Index
 
 * Fix `PatchLineCommentsUtil.draftByChangeAuthor`.
 +
@@ -67,8 +62,7 @@
 
 * Don't show stack trace when failing to build BloomFilter during reindex.
 
-Permissions
-~~~~~~~~~~~
+=== Permissions
 
 * Require 'View Plugins' capability to list plugins through SSH.
 +
@@ -83,8 +77,7 @@
 edit the `project.config` file.
 
 
-Change Screen / Diff / Inline Edit
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Change Screen / Diff / Inline Edit
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3191[Issue 3191]:
 Always show 'Not Current' as state when looking at old patch set.
@@ -103,8 +96,7 @@
 In the side-by-side diff, the cursor is placed on the first column of the diff,
 rather than at the end.
 
-Web Container
-~~~~~~~~~~~~~
+=== Web Container
 
 * Fix `gc_log` when running in a web container.
 +
@@ -117,8 +109,7 @@
 web container with the site path configured using the `gerrit.site_path`
 property.
 
-Plugins
-~~~~~~~
+=== Plugins
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3310[Issue 3310]:
 Fix disabling plugins when Gerrit is running on Windows.
@@ -137,8 +128,7 @@
 When `replicateOnStartup` is enabled, the plugin was not emitting the status
 events after the initial sync.
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3328[Issue 3328]:
 Allow to push a tag that points to a non-commit object.
@@ -168,8 +158,7 @@
 
 * Assume change kind is 'rework' if `LargeObjectException` occurs.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3325[Issue 3325]:
 Add missing `--newrev` parameter to the
@@ -185,8 +174,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11.1/config-gerrit.html#auth.registerUrl[
 auth types].
 
-Updates
--------
+== Updates
 
 * Update CodeMirror to 5.0.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.11.2.txt b/ReleaseNotes/ReleaseNotes-2.11.2.txt
index 07f99ae..98e66b0 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.2
-===============================
+= Release notes for Gerrit 2.11.2
 
 Gerrit 2.11.2 is now available:
 
@@ -12,8 +11,7 @@
 
 There are no schema changes from link:ReleaseNotes-2.11.1.html[2.11.1].
 
-New Features
-------------
+== New Features
 
 New SSH commands:
 
@@ -29,8 +27,7 @@
 problems.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2761[Issue 2761]:
 Fix incorrect file list when comparing patchsets.
@@ -96,8 +93,7 @@
 
 * Print proper name for reindex after update tasks in `show-queue` command.
 
-Updates
--------
+== Updates
 
 * Update JGit to 4.0.1.201506240215-r.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.11.3.txt b/ReleaseNotes/ReleaseNotes-2.11.3.txt
index 0df3a29..f705d1e 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.3.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.3
-===============================
+= Release notes for Gerrit 2.11.3
 
 Gerrit 2.11.3 is now available:
 
@@ -9,8 +8,7 @@
 There are no schema changes from link:ReleaseNotes-2.11.2.html[2.11.2].
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Do not suggest inactive accounts.
 +
@@ -86,8 +84,7 @@
 caused by plugins not loading that an admin should pay attention to and try to
 resolve.
 
-Updates
--------
+== Updates
 
 * Update Guice to 4.0.
 * Replace parboiled 1.1.7 with grappa 1.0.4.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.4.txt b/ReleaseNotes/ReleaseNotes-2.11.4.txt
index 6037edd..cfa8576 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.4.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.4
-===============================
+= Release notes for Gerrit 2.11.4
 
 Gerrit 2.11.4 is now available:
 
@@ -13,8 +12,7 @@
 There are no schema changes from link:ReleaseNotes-2.11.3.html[2.11.3].
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Fix NullPointerException in `ls-project` command with `--has-acl-for` option.
 +
@@ -131,8 +129,7 @@
 was a merge commit, or if the change being viewed conflicted with an open merge
 commit.
 
-Plugin Bugfixes
----------------
+== Plugin Bugfixes
 
 * singleusergroup: Allow to add a user to a project's ACL using `user/username`.
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.11.5.txt b/ReleaseNotes/ReleaseNotes-2.11.5.txt
index d7758cb..6957827 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.5.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.5
-===============================
+= Release notes for Gerrit 2.11.5
 
 Gerrit 2.11.5 is now available:
 
@@ -9,8 +8,7 @@
 There are no schema changes from link:ReleaseNotes-2.11.4.html[2.11.4].
 
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* This release uses a forked version of buck.
 
@@ -25,8 +23,7 @@
 ----
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3442[Issue 3442]:
 Handle commit validation errors when creating/editing changes via REST.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.6.txt b/ReleaseNotes/ReleaseNotes-2.11.6.txt
index d6f939f..977ea14 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.6.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.6
-===============================
+= Release notes for Gerrit 2.11.6
 
 Gerrit 2.11.6 is now available:
 
@@ -8,11 +7,9 @@
 
 There are no schema changes from link:ReleaseNotes-2.11.5.html[2.11.5].
 
-Bug Fixes
----------
+== Bug Fixes
 
-General
-~~~~~~~
+=== General
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3742[Issue 3742]:
 Use merge strategy for mergeability testing on 'Rebase if Necessary' strategy.
@@ -51,8 +48,7 @@
 couldn't be added as a reviewer by selecting it from the suggested list of
 accounts.
 
-Authentication
-~~~~~~~~~~~~~~
+=== Authentication
 
 * Fix handling of lowercase HTTP username.
 +
@@ -69,8 +65,7 @@
 of the user for the claimed identity would fail, causing a new account to be
 created.
 
-UI
-~~
+=== UI
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3714[Issue 3714]:
 Improve visibility of comments on dark themes.
@@ -78,8 +73,7 @@
 * Fix highlighting of search results and trailing whitespaces in intraline
 diff chunks.
 
-Plugins
-~~~~~~~
+=== Plugins
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3768[Issue 3768]:
 Fix usage of `EqualsFilePredicate` in plugins.
@@ -115,8 +109,7 @@
 
 ** Allow to use GWTORM `Key` classes.
 
-Documentation Updates
----------------------
+== Documentation Updates
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=412[Issue 412]:
 Update documentation of `commentlink.match` regular expression to clarify
diff --git a/ReleaseNotes/ReleaseNotes-2.11.7.txt b/ReleaseNotes/ReleaseNotes-2.11.7.txt
index 7a0de2d..6742279 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.7.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.7
-===============================
+= Release notes for Gerrit 2.11.7
 
 Gerrit 2.11.7 is now available:
 
@@ -8,8 +7,7 @@
 
 There are no schema changes from link:ReleaseNotes-2.11.6.html[2.11.6].
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3882[Issue 3882]:
 Fix 'No user on email thread' exception when label with group parameter is
diff --git a/ReleaseNotes/ReleaseNotes-2.11.8.txt b/ReleaseNotes/ReleaseNotes-2.11.8.txt
index 0f9dc21..0aa8dfc 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.8.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11.8
-===============================
+= Release notes for Gerrit 2.11.8
 
 Gerrit 2.11.8 is now available:
 
@@ -8,8 +7,7 @@
 
 There are no schema changes from link:ReleaseNotes-2.11.7.html[2.11.7].
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Upgrade Apache commons-collections to version 3.2.2.
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.11.9.txt b/ReleaseNotes/ReleaseNotes-2.11.9.txt
new file mode 100644
index 0000000..52ee3fe
--- /dev/null
+++ b/ReleaseNotes/ReleaseNotes-2.11.9.txt
@@ -0,0 +1,49 @@
+= Release notes for Gerrit 2.11.9
+
+Gerrit 2.11.9 is now available:
+
+link:https://gerrit-releases.storage.googleapis.com/gerrit-2.11.9.war[
+https://gerrit-releases.storage.googleapis.com/gerrit-2.11.9.war]
+
+There are no schema changes from link:ReleaseNotes-2.11.8.html[2.11.8].
+
+== Bug Fixes
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=4070[Issue 4070]:
+Don't return current patch set in queries if the current patch set is not
+visible.
++
+When querying changes with the `gerrit query` ssh command, and passing the
+`--current-patch-set` option, the current patch set was included even when
+it is not visible to the caller (for example when the patch set is a draft,
+and the caller cannot see drafts).
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=3970[Issue 3970]:
+Fix keyboard shortcuts for special processing of CTRL and META.
++
+The processing of CTRL and META was incorrectly removed in Gerrit version
+2.11.8, resulting in shortcuts like 'STRG+T' being interpreted as 'T'.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=4056[Issue 4056]:
+Fix download URLs for BouncyCastle libraries.
++
+The location of the libraries was moved, so the download URLs are updated
+accordingly.
+
+* link:https://code.google.com/p/gerrit/issues/detail?id=4055[Issue 4055]:
+Fix subject for 'Updated Changes' lines on push.
++
+When a change was updated it showed the subject from the previous patch set
+instead of the subject from the new current patch set.
+
+* Fix incorrect loading of access sections in `project.config` files.
+
+* Fix internal server error when `auth.userNameToLowerCase` is enabled
+and the auth backend does not provide the username.
+
+* Fix error reindexing changes when a change no longer exists.
+
+* Fix internal server error when loading submit rules.
+
+* Fix internal server error when parsing tracking footers from commit
+messages.
diff --git a/ReleaseNotes/ReleaseNotes-2.11.txt b/ReleaseNotes/ReleaseNotes-2.11.txt
index 44c5398..1ca6825 100644
--- a/ReleaseNotes/ReleaseNotes-2.11.txt
+++ b/ReleaseNotes/ReleaseNotes-2.11.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.11
-=============================
+= Release notes for Gerrit 2.11
 
 
 Gerrit 2.11 is now available:
@@ -14,8 +13,7 @@
 These bug fixes are *not* listed in these release notes.
 
 
-Important Notes
----------------
+== Important Notes
 
 
 *WARNING:* This release contains schema changes.  To upgrade:
@@ -82,8 +80,7 @@
 
 *WARNING:* The deprecated '/query' URL is removed and will now return `Not Found`.
 
-Release Highlights
-------------------
+== Release Highlights
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=505[Issue 505]:
@@ -95,16 +92,13 @@
 * The old change screen is removed.
 
 
-New Features
-------------
+== New Features
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 [[inline-editing]]
-Inline Editing
-^^^^^^^^^^^^^^
+==== Inline Editing
 
 Refer to the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/user-inline-edit.html[
@@ -133,8 +127,7 @@
 
 * Files can be added, deleted, restored and modified directly in browser.
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 * Remove the 'Edit Message' button from the change screen.
 +
@@ -175,8 +168,7 @@
 * Show changes across all projects and branches in the `Same Topic` tab.
 
 
-Side-By-Side Diff
-^^^^^^^^^^^^^^^^^
+==== Side-By-Side Diff
 
 * New button to switch between side-by-side diff and unified diff.
 
@@ -206,8 +198,7 @@
 ** Soy
 
 
-Projects Screen
-^^^^^^^^^^^^^^^
+==== Projects Screen
 
 * Add pagination and filtering on the branch list page.
 
@@ -220,17 +211,14 @@
 browser, which is useful since it is possible that not all configuration options
 are available in the UI.
 
-REST
-~~~~
+=== REST
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 * Add new link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-accounts.html#suggest-account[
 Suggest Account endpoint].
 
-Changes
-^^^^^^^
+==== Changes
 
 * The link:https://gerrit-review.googlesource.com/Documentation/2.10/rest-api-changes.html#message[
 Edit Commit Message] endpoint is removed in favor of the new
@@ -260,8 +248,7 @@
 Get Change Detail] endpoint.
 
 
-Change Edits
-^^^^^^^^^^^^
+==== Change Edits
 
 Several new endpoints are added to support the inline edit feature.
 
@@ -296,8 +283,7 @@
 Delete Change Edit].
 
 
-Projects
-^^^^^^^^
+==== Projects
 
 * Add new
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-projects.html#delete-branches[
@@ -316,8 +302,7 @@
 Get Tag] endpoint.
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * Add support for
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#auth.httpExternalIdHeader[
@@ -395,8 +380,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/config-gerrit.html#container.daemonOpt[
 options to pass to the daemon].
 
-Daemon
-~~~~~~
+=== Daemon
 
 * Allow to enable the http daemon when running in slave mode.
 +
@@ -416,8 +400,7 @@
 * Don't send 'new patch set' notification emails for trivial rebases.
 
 
-SSH
-~~~
+=== SSH
 
 * Add new commands
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-logging-ls-level.html[
@@ -448,8 +431,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/cmd-set-account.html[
 `set-account` SSH command].
 
-Email
-~~~~~
+=== Email
 
 * Add `$change.originalSubject` field for email templates.
 +
@@ -464,11 +446,9 @@
 field during first use.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
-General
-^^^^^^^
+==== General
 
 * Plugins can listen to account group membership changes.
 +
@@ -518,17 +498,14 @@
 ** Get comments and drafts.
 ** Get change edit.
 
-Replication
-^^^^^^^^^^^
+==== Replication
 
 * Projects can be specified with wildcard in the `start` command.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-Daemon
-~~~~~~
+=== Daemon
 
 * Change 'Merge topic' to 'Merge changes from topic'.
 +
@@ -564,8 +541,7 @@
 of a repository with a space in its name was impossible.
 
 
-Secondary Index / Search
-~~~~~~~~~~~~~~~~~~~~~~~~
+=== Secondary Index / Search
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2822[Issue 2822]:
 Improve Lucene analysis of words linked with underscore or dot.
@@ -576,8 +552,7 @@
 * Fix support for `change~branch~id` in query syntax.
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 [[remove-generate-http-password-capability]]
 * Remove the 'Generate HTTP Password' capability.
@@ -608,17 +583,14 @@
 read from `hooks.changeMerged`. Fix to use `hooks.changeMergedHook` as
 documented.
 
-Web UI
-~~~~~~
+=== Web UI
 
-Change List
-^^^^^^^^^^^
+==== Change List
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3304[Issue 3304]:
 Always show a tooltip on the label column entries.
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3147[Issue 3147]:
 Allow to disable muting of common path prefixes in the file list.
@@ -742,8 +714,7 @@
 Align parent weblinks with parent commits in the commit box.
 
 
-Side-By-Side Diff
-^^^^^^^^^^^^^^^^^
+==== Side-By-Side Diff
 
 * Return to normal mode after editing a draft comment.
 +
@@ -756,18 +727,15 @@
 highlighter.
 
 
-Project Screen
-^^^^^^^^^^^^^^
+==== Project Screen
 
 * Fix alignment of checkboxes on project access screen.
 +
 The 'Exclusive' checkbox was not aligned with the other checkboxes.
 
-REST API
-~~~~~~~~
+=== REST API
 
-Changes
-^^^^^^^
+==== Changes
 
 * Remove the administrator restriction on the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.11/rest-api-changes.html#index-change[
@@ -800,8 +768,7 @@
 `409 Conflict`.
 
 
-Projects
-^^^^^^^^
+==== Projects
 
 * Make it mandatory to specify at least one of the `--prefix`, `--match` or `--regex`
 options in the
@@ -826,11 +793,9 @@
 others.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
-Replication
-^^^^^^^^^^^
+==== Replication
 
 * Create missing repositories on the remote when replicating with the git
 protocol.
@@ -843,8 +808,7 @@
 create a project on the remote.
 
 
-Upgrades
---------
+== Upgrades
 
 * Update Antlr to 3.5.2.
 
diff --git a/ReleaseNotes/ReleaseNotes-2.12.1.txt b/ReleaseNotes/ReleaseNotes-2.12.1.txt
index f49de7d..e746d6e 100644
--- a/ReleaseNotes/ReleaseNotes-2.12.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.12.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.12.1
-===============================
+= Release notes for Gerrit 2.12.1
 
 Gerrit 2.12.1 is now available:
 
@@ -11,8 +10,7 @@
 link:ReleaseNotes-2.11.7.html[Gerrit 2.11.7]. These bug fixes are *not*
 listed in these release notes.
 
-Schema Upgrade
---------------
+== Schema Upgrade
 
 *WARNING:* This version includes a manual schema upgrade when upgrading
 from 2.12.
@@ -48,11 +46,9 @@
 necessary and should be omitted.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-General
-~~~~~~~
+=== General
 
 * Fix column type for signed push certificates.
 +
@@ -160,8 +156,7 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=3883[Issue 3883]:
 Respect the `core.commentchar` setting from `.gitconfig` in `commit-msg` hook.
 
-UI
-~~
+=== UI
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3894[Issue 3894]:
 Fix display of 'Related changes' after change is rebased in web UI:
@@ -194,8 +189,7 @@
 * Improve tooltip on 'Submit' button when 'Submit whole topic' is enabled
 and the topic can't be submitted due to some changes not being ready.
 
-Plugins
-~~~~~~~
+=== Plugins
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=3821[Issue 3821]:
 Fix repeated reloading of plugins when running on OpenJDK 8.
@@ -223,14 +217,12 @@
 Allow plugins to suggest reviewers based on either change or project
 resources.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
 * Update documentation of `commentlink` to reflect changed search URL.
 
 * Add missing documentation of valid `database.type` values.
 
-Upgrades
---------
+== Upgrades
 
 * Upgrade JGit to 4.1.2.201602141800-r.
diff --git a/ReleaseNotes/ReleaseNotes-2.12.2.txt b/ReleaseNotes/ReleaseNotes-2.12.2.txt
index 500b015..8292eb5 100644
--- a/ReleaseNotes/ReleaseNotes-2.12.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.12.2.txt
@@ -1,13 +1,11 @@
-Release notes for Gerrit 2.12.2
-===============================
+= Release notes for Gerrit 2.12.2
 
 Gerrit 2.12.2 is now available:
 
 link:https://gerrit-releases.storage.googleapis.com/gerrit-2.12.2.war[
 https://gerrit-releases.storage.googleapis.com/gerrit-2.12.2.war]
 
-Schema Upgrade
---------------
+== Schema Upgrade
 
 *WARNING:* There are no schema changes from link:ReleaseNotes-2.12.1.html[
 2.12.1] but a manual schema upgrade is necessary when upgrading from 2.12.
@@ -43,8 +41,7 @@
 done the migration, this manual step is not necessary and should be omitted.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Upgrade Apache commons-collections to version 3.2.2.
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.12.txt b/ReleaseNotes/ReleaseNotes-2.12.txt
index 797a138..65a4484 100644
--- a/ReleaseNotes/ReleaseNotes-2.12.txt
+++ b/ReleaseNotes/ReleaseNotes-2.12.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.12
-=============================
+= Release notes for Gerrit 2.12
 
 
 Gerrit 2.12 is now available:
@@ -7,8 +6,7 @@
 link:https://www.gerritcodereview.com/download/gerrit-2.12.war[
 https://www.gerritcodereview.com/download/gerrit-2.12.war]
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
@@ -39,8 +37,7 @@
 `refs/*/master` instead of `Plain` and `master`.
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 This release includes the following new features. See the sections below for
 further details.
@@ -50,11 +47,9 @@
 * Support for GPG Keys and signed pushes.
 
 
-New Features
-------------
+== New Features
 
-New Change Submission Workflows
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== New Change Submission Workflows
 
 * New 'Submit Whole Topic' setting.
 +
@@ -76,8 +71,7 @@
 enter the 'Submitted, Merge Pending' state.
 
 
-GPG Keys and Signed Pushes
-~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== GPG Keys and Signed Pushes
 
 * Signed push can be enabled by setting
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/config-gerrit.html#receive.enableSignedPush[
@@ -102,8 +96,7 @@
 `receive.certNonceSlop`].
 
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3333[Issue 3333]:
 Support searching for changes by author and committer.
@@ -141,11 +134,9 @@
 sense to enforce all changes to be written to disk ASAP.
 
 
-UI
-~~
+=== UI
 
-General
-^^^^^^^
+==== General
 
 * Edit and diff preferences can be modified from the user preferences screen.
 +
@@ -165,14 +156,12 @@
 users.
 
 
-Project Screen
-^^^^^^^^^^^^^^
+==== Project Screen
 
 * New tab to list the project's tags, similar to the branch list.
 
 
-Inline Editor
-^^^^^^^^^^^^^
+==== Inline Editor
 
 * Store and load edit preferences in git.
 +
@@ -187,8 +176,7 @@
 * Add support for Emacs and Vim key maps.
 
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3318[Issue 3318]:
 Highlight 'Reply' button if there are draft comments on any patch set.
@@ -216,8 +204,7 @@
 This helps to identify changes when the subject is truncated in the list.
 
 
-Side-By-Side Diff
-^^^^^^^^^^^^^^^^^
+==== Side-By-Side Diff
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3293[Issue 3293]:
 Add syntax highlighting for Puppet.
@@ -226,40 +213,34 @@
 Add syntax highlighting for VHDL.
 
 
-Group Screen
-^^^^^^^^^^^^
+==== Group Screen
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=1479[Issue 1479]:
 The group screen now includes an 'Audit Log' panel showing member additions,
 removals, and the user who made the change.
 
 
-API
-~~~
+=== API
 
 Several new APIs are added.
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 * Suggest accounts.
 
-Tags
-^^^^
+==== Tags
 
 * List tags.
 
 * Get tag.
 
 
-REST API
-~~~~~~~~
+=== REST API
 
 New REST API endpoints and new options on existing endpoints.
 
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-accounts.html#set-username[
 Set Username]: Set the username of an account.
@@ -276,8 +257,7 @@
 account.
 
 
-Changes
-^^^^^^^
+==== Changes
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-changes.html#set-review[
 Set Review]: Add an option to omit duplicate comments.
@@ -294,8 +274,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-changes.html#set-review[
 Set Review]: Add an option to publish draft comments on all revisions.
 
-Config
-^^^^^^
+==== Config
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-config.html#get-info[
 Get Server Info]: Return information about the Gerrit server configuration.
@@ -304,8 +283,7 @@
 Confirm Email]: Confirm that the user owns an email address.
 
 
-Groups
-^^^^^^
+==== Groups
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-groups.html#list-group[
 List Groups]: Add option to suggest groups.
@@ -317,8 +295,7 @@
 additions, removals, and the user who made the change.
 
 
-Projects
-^^^^^^^^
+==== Projects
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.12/rest-api-projects.html#run-gc[
 Run GC]: Add `aggressive` option to specify whether or not to run an aggressive
@@ -329,8 +306,7 @@
 `--start` and `--end`.
 
 
-SSH
-~~~
+=== SSH
 
 * Add support for ZLib Compression.
 +
@@ -340,11 +316,9 @@
 
 * Add support for hmac-sha2-256 and hmac-sha2-512 as MACs.
 
-Plugins
-~~~~~~~
+=== Plugins
 
-General
-^^^^^^^
+==== General
 
 * Gerrit client can now pass JavaScriptObjects to extension panels.
 
@@ -387,8 +361,7 @@
 ** Allow to use GWTORM `Key` classes.
 
 
-Other
-~~~~~
+=== Other
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3401[Issue 3401]:
 Add option to
@@ -433,8 +406,7 @@
 and queues, and invoke the index REST API on changes.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=3499[Issue 3499]:
 Fix syntax highlighting of raw string literals in go.
@@ -536,8 +508,7 @@
 +
 Under some circumstances it was possible to fail with an IO error.
 
-Documentation Updates
----------------------
+== Documentation Updates
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=412[Issue 412]:
 Update documentation of `commentlink.match` regular expression to clarify
@@ -549,8 +520,7 @@
 
 * Document that `ldap.groupBase` and `ldap.accountBase` are repeatable.
 
-Upgrades
---------
+== Upgrades
 
 * Upgrade Asciidoctor to 1.5.2
 
diff --git a/ReleaseNotes/ReleaseNotes-2.13.txt b/ReleaseNotes/ReleaseNotes-2.13.txt
index 071dc30..07ceb4d 100644
--- a/ReleaseNotes/ReleaseNotes-2.13.txt
+++ b/ReleaseNotes/ReleaseNotes-2.13.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.13
-=============================
+= Release notes for Gerrit 2.13
 
 
 Gerrit 2.13 is now available:
@@ -8,23 +7,19 @@
 https://www.gerritcodereview.com/download/gerrit-2.13.war]
 
 
-Important Notes
----------------
+== Important Notes
 
 TODO
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 * Metrics interface.
 
 
-New Features
-------------
+== New Features
 
-Metrics
-~~~~~~~
+=== Metrics
 
 Metrics about Gerrit's internal state can be sent to external
 monitoring systems.
@@ -66,21 +61,18 @@
 
 * TODO add more
 
-Changes
-~~~~~~~
+=== Changes
 
 In order to avoid potentially confusing behavior, when submitting changes in a
 batch, submit type rules may not be used to mix submit types on a single branch,
 and trying to submit such a batch will fail.
 
-Bug Fixes
----------
+== Bug Fixes
 
 TODO
 
 
-Upgrades
---------
+== Upgrades
 
 * Upgrade CodeMirror to 5.14.2
 
diff --git a/ReleaseNotes/ReleaseNotes-2.2.0.txt b/ReleaseNotes/ReleaseNotes-2.2.0.txt
index 5938f66..5cc54f9 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.0.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.0.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.2.0
-==============================
+= Release notes for Gerrit 2.2.0
 
 Gerrit 2.2.0 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.2.0.war[https://www.gerritcodereview.com/download/gerrit-2.2.0.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* Upgrading to 2.2.0 requires the server be first upgraded
 to 2.1.7, and then to 2.2.0.
 
@@ -21,11 +19,9 @@
 Git repository. The init based upgrade tool will automatically
 export the current table contents and create the Git data.
 
-New Features
-------------
+== New Features
 
-Project Administration
-~~~~~~~~~~~~~~~~~~~~~~
+=== Project Administration
 * issue 436 List projects by scanning the managed Git directory
 +
 Instead of generating the list of projects from SQL database, the
@@ -52,11 +48,9 @@
 The Access panel of the project administration has been rewritten
 with a new UI that reflects the new Git based storage format.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Project Administration
-~~~~~~~~~~~~~~~~~~~~~~
+=== Project Administration
 * Avoid unnecessary updates to $GIT_DIR/description
 +
 Gerrit always tried to rewrite the gitweb "description" file when the
diff --git a/ReleaseNotes/ReleaseNotes-2.2.1.txt b/ReleaseNotes/ReleaseNotes-2.2.1.txt
index 6a4829e..26aa8db 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.1.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.2.1
-==============================
+= Release notes for Gerrit 2.2.1
 
 Gerrit 2.2.1 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.2.1.war[https://www.gerritcodereview.com/download/gerrit-2.2.1.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -15,8 +13,7 @@
 *WARNING:* Upgrading to 2.2.x requires the server be first upgraded
 to 2.1.7, and then to 2.2.x.
 
-New Features
-------------
+== New Features
 * Add 'Expand All Comments' checkbox in PatchScreen
 +
 Allows users to save a user preference that automatically expands
@@ -28,8 +25,7 @@
 usage adds a new column of output per project line listing the
 current value of that branch.
 
-Bug Fixes
----------
+== Bug Fixes
 * issue 994 Rename "-- All Projects --" to "All-Projects"
 +
 The name "-- All Projects --.git" is difficult to work with on
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.1.txt b/ReleaseNotes/ReleaseNotes-2.2.2.1.txt
index aabe03a..37f5a76 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.2.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.2.2.1
-================================
+= Release notes for Gerrit 2.2.2.1
 
 Gerrit 2.2.2.1 is now available:
 
@@ -11,8 +10,7 @@
 link:ReleaseNotes-2.2.2.html[ReleaseNotes].
 
 
-Bug Fixes
----------
+== Bug Fixes
 * issue 1139 Fix change state in patch set approval if reviewer is added to
 closed change
 +
@@ -35,8 +33,7 @@
 commit 14246de3c0f81c06bba8d4530e6bf00e918c11b0
 
 
-Documentation
--------------
+== Documentation
 * Update top level SUBMITTING_PATCHES
 +
 This document is out of date, the URLs are from last August.
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
index 2747ab0..f50c4e7 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.2.2.2
-================================
+= Release notes for Gerrit 2.2.2.2
 
 Gerrit 2.2.2.2 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from anything earlier, follow the upgrade
 procedure in the 2.2.2 link:ReleaseNotes-2.2.2.html[ReleaseNotes].
 
-Security Fixes
---------------
+== Security Fixes
 * Some access control sections may be ignored
 +
 Gerrit sometimes ignored an access control section in a project
diff --git a/ReleaseNotes/ReleaseNotes-2.2.2.txt b/ReleaseNotes/ReleaseNotes-2.2.2.txt
index 3889dcc..276714c 100644
--- a/ReleaseNotes/ReleaseNotes-2.2.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.2.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.2.2
-==============================
+= Release notes for Gerrit 2.2.2
 
 Gerrit 2.2.2 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.2.2.war[https://www.gerritcodereview.com/download/gerrit-2.2.2.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -15,11 +13,9 @@
 *WARNING:* Upgrading to 2.2.x requires the server be first upgraded
 to 2.1.7 (or a later 2.1.x version), and then to 2.2.x.
 
-New Features
-------------
+== New Features
 
-Prolog
-~~~~~~
+=== Prolog
 * issue 971 Use Prolog Cafe for ChangeControl.canSubmit()
 
 *  Add per-project prolog submit rule files
@@ -59,8 +55,7 @@
 administrators can play around with by downloading the Gerrit WAR
 file and executing: java -jar gerrit.war prolog-shell
 
-Prolog Predicates
-^^^^^^^^^^^^^^^^^
+==== Prolog Predicates
 *  Add Prolog Predicates to check commit messages and edits
 +
 commit_message returns the commit message as a symbol.
@@ -104,8 +99,7 @@
 it exportable for now until we can come back and clean up the legacy
 approval data code.
 
-Web
-~~~
+=== Web
 
 * Support in Firefox delete key in NpIntTextBox
 +
@@ -118,8 +112,7 @@
 There is a bug in gwt 2.1.0 that prevents pressing special keys like
 Enter, Backspace etc. from being properly recognized and so they have no effect.
 
-ChangeScreen
-^^^^^^^^^^^^
+==== ChangeScreen
 * issue 855 Indicate outdated dependencies on the ChangeScreen
 +
 If a change dependency is no longer the latest patchSet for that
@@ -142,8 +135,7 @@
 permits the submit_rule to make an ApprovalCategory optional, or to
 make a new label required.
 
-Diff Screen
-^^^^^^^^^^^
+==== Diff Screen
 * Add top level menus for a new PatchScreen header
 +
 Modify the PatchScreen so that the header contents is selectable
@@ -183,8 +175,7 @@
 automatic result that Git would create, and the actual result that
 was uploaded by the author/committer of the merge.
 
-Groups
-^^^^^^
+==== Groups
 * Add menu to AccountGroupScreen
 +
 This change introduces a menu in the AccountGroupScreen and
@@ -199,8 +190,7 @@
 'groups' file in the 'refs/meta/config' branch which requires
 the UUID of the group to be known.
 
-Project Access
-^^^^^^^^^^^^^^
+==== Project Access
 * Automatically add new rule when adding new permission
 +
 If a new permission was added to a block, immediately create the new
@@ -218,8 +208,7 @@
 switch back to the "read-only" view where the widgets are all
 disabled and the Edit button is enabled.
 
-Project Branches
-^^^^^^^^^^^^^^^^
+==== Project Branches
 * Display refs/meta/config branch on ProjectBranchesScreen
 +
 The new refs/meta/config branch was not shown in the ProjectBranchesScreen.
@@ -231,8 +220,7 @@
 Since HEAD and refs/meta/config do not represent ordinary branches,
 highlight their rows with a special style in the ProjectBranchesScreen.
 
-URLs
-^^^^
+==== URLs
 * Modernize URLs to be shorter and consistent
 +
 Instead of http://site/#change,1234 we now use a slightly more
@@ -250,8 +238,7 @@
 
 * issue 1018 Accept ~ in linkify() URLs
 
-SSH
-~~~
+=== SSH
 * Added a set-reviewers ssh command
 
 * Support removing more than one reviewer at once
@@ -290,8 +277,7 @@
 will report additional data about the JVM, and tell the caller
 where it is running.
 
-Queries
-^^^^^^^
+==== Queries
 * Output patchset creation date for 'query' command.
 
 * issue 1053 Support comments option in query command
@@ -300,8 +286,7 @@
 used. If --comments is used together with --patch-sets all inline
 comments are included in the output.
 
-Config
-~~~~~~
+=== Config
 * Move batch user priority to a capability
 +
 Instead of using a magical group, use a special capability to
@@ -351,8 +336,7 @@
 This allows aliases which redirect to gerrit's ssh port (say
 from port 22) to be setup and advertised to users.
 
-Dev
-~~~
+=== Dev
 * Updated eclipse settings for 3.7 and m2e 1.0
 
 * Fix build in m2eclipse 1.0
@@ -375,8 +359,7 @@
 switching between different users when using the
 DEVELOPMENT_BECOME_ANY_ACCOUNT authentication type.
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 * Permit adding reviewers to closed changes
 +
 Permit adding a reviewer to closed changes to support post-submit
@@ -412,8 +395,7 @@
 gerrit page load.
 
 
-Performance
------------
+== Performance
 * Bumped Brics version to 1.11.8
 +
 This Brics version fixes a performance issue in some larger Gerrit systems.
@@ -453,8 +435,7 @@
 during the construction of ProjectState is a waste of resources.
 
 
-Upgrades
---------
+== Upgrades
 * Upgrade to GWT 2.3.0
 * Upgrade to Gson to 1.7.1
 * Upgrade to gwtjsonrpc 1.2.4
@@ -463,8 +444,7 @@
 * Upgrade to Brics 1.11.8
 
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix: Issue where Gerrit could not linkify certain URLs
 
 * issue 1015 Fix handling of regex ref patterns in Access panel
@@ -563,11 +543,9 @@
 duplicate account_ids later.
 
 
-Documentation
--------------
+== Documentation
 
-New Documents
-~~~~~~~~~~~~~
+=== New Documents
 * First Cut of Gerrit Walkthrough Introduction documentation.
 +
 Add a new document intended to be a complement for the existing
@@ -580,8 +558,7 @@
 The new document covers quick installation, new project and first
 upload.  It contains lots of quoted output, with a demo style to it.
 
-Access Control
-~~~~~~~~~~~~~~
+=== Access Control
 * Code review
 
 * Conversion table between 2.1 and 2.2
@@ -614,8 +591,7 @@
 +
 Access categories are now sorted to match drop down box in UI
 
-Other Documentation
-~~~~~~~~~~~~~~~~~~~
+=== Other Documentation
 * Added additional information on the install instructions.
 +
 The installation instructions presumes much prior knowledge,
diff --git a/ReleaseNotes/ReleaseNotes-2.3.1.txt b/ReleaseNotes/ReleaseNotes-2.3.1.txt
index 8914c69..627fba5 100644
--- a/ReleaseNotes/ReleaseNotes-2.3.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.3.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.3.1
-==============================
+= Release notes for Gerrit 2.3.1
 
 Gerrit 2.3.1 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from anything earlier, follow the upgrade
 procedure in the 2.3 link:ReleaseNotes-2.3.html[ReleaseNotes].
 
-Security Fixes
---------------
+== Security Fixes
 * Some access control sections may be ignored
 +
 Gerrit sometimes ignored an access control section in a project
diff --git a/ReleaseNotes/ReleaseNotes-2.3.txt b/ReleaseNotes/ReleaseNotes-2.3.txt
index 9cdc886..7a29d0e 100644
--- a/ReleaseNotes/ReleaseNotes-2.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.3
-============================
+= Release notes for Gerrit 2.3
 
 Gerrit 2.3 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.3.war[https://www.gerritcodereview.com/download/gerrit-2.3.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -19,10 +17,8 @@
 upgrade directly to 2.3.x.
 
 
-New Features
-------------
-Drafts
-~~~~~~
+== New Features
+=== Drafts
 * New draft statuses and magic branches
 +
 Adds draft status to Change. DRAFT status in change occurs before NEW
@@ -65,8 +61,7 @@
 * When pushing changes as drafts, output [DRAFT] next to the change link
 
 
-Web
-~~~
+=== Web
 * issue 203 Create project through web interface
 +
 Add a new panel in the Admin->Projects Screen.  It
@@ -106,8 +101,7 @@
 * Disable SSH Keys in the web UI if SSHD is disabled
 
 
-SSH
-~~~
+=== SSH
 * Adds --description (-d) option to ls-projects
 +
 Allows listing of projects together with their respective
@@ -167,8 +161,7 @@
 labels could not be applied due to change being closed.
 
 
-Config
-~~~~~~
+=== Config
 * issue 349 Apply states for projects (active, readonly and hidden)
 +
 Active state indicates the project is regular and is the default value.
@@ -250,8 +243,7 @@
 logic associated with the site header, footer and CSS.
 
 
-Dev
-~~~
+=== Dev
 * Fix 'No source code is available for type org.eclipse.jgit.lib.Constants'
 
 * Fix miscellaneous compiler warnings
@@ -270,8 +262,7 @@
 Fixes java.lang.NoClassDefFoundError: com/google/gwt/dev/DevMode
 
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 * Allow superprojects to subscribe to submodules updates
 +
 The feature introduced in this release allows superprojects to
@@ -370,8 +361,7 @@
 * Improve validation of email registration tokens
 
 
-Upgrades
---------
+== Upgrades
 * Upgrade to gwtorm 1.2
 
 * Upgrade to JGit 1.1.0.201109151100-r.119-gb4495d1
@@ -382,8 +372,7 @@
 * Support Velocity 1.5 (as well as previous 1.6.4)
 
 
-Bug Fixes
----------
+== Bug Fixes
 * Avoid NPE when group is missing
 
 * Do not fail with NPE if context path of request is null
@@ -437,8 +426,7 @@
 * Update top level SUBMITTING_PATCHES URLs
 
 
-Documentation
--------------
+== Documentation
 * Some updates to the design docs
 
 * cmd-index: Fix link to documentation of rename-group command
diff --git a/ReleaseNotes/ReleaseNotes-2.4.1.txt b/ReleaseNotes/ReleaseNotes-2.4.1.txt
index dbe6c4b..f3c4765 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.4.1
-==============================
+= Release notes for Gerrit 2.4.1
 
 Gerrit 2.4.1 is now available:
 
@@ -11,8 +10,7 @@
 link:ReleaseNotes-2.4.html[ReleaseNotes].
 
 
-Bug Fixes
----------
+== Bug Fixes
 * Catch all exceptions when async emailing
 +
 This fixes email notification issues reported
diff --git a/ReleaseNotes/ReleaseNotes-2.4.2.txt b/ReleaseNotes/ReleaseNotes-2.4.2.txt
index 5652d15..d5c2a11 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.4.2
-==============================
+= Release notes for Gerrit 2.4.2
 
 Gerrit 2.4.2 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from anything earlier, follow the upgrade
 procedure in the 2.4 link:ReleaseNotes-2.4.html[ReleaseNotes].
 
-Security Fixes
---------------
+== Security Fixes
 * Some access control sections may be ignored
 +
 Gerrit sometimes ignored an access control section in a project
diff --git a/ReleaseNotes/ReleaseNotes-2.4.3.txt b/ReleaseNotes/ReleaseNotes-2.4.3.txt
index 6745564..ece0bda 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.4.3
-==============================
+= Release notes for Gerrit 2.4.3
 
 There are no schema changes from link:ReleaseNotes-2.4.2.html[2.4.2].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.4.3.war[https://www.gerritcodereview.com/download/gerrit-2.4.3.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Patch JGit security hole
 +
 The security hole may permit a modified Git client to gain access
diff --git a/ReleaseNotes/ReleaseNotes-2.4.4.txt b/ReleaseNotes/ReleaseNotes-2.4.4.txt
index 5570271..f9ea6b5 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.4.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.4.4
-==============================
+= Release notes for Gerrit 2.4.4
 
 There are no schema changes from link:ReleaseNotes-2.4.4.html[2.4.4].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.4.4.war[https://www.gerritcodereview.com/download/gerrit-2.4.4.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix clone for modern Git clients
 +
 The security fix in 2.4.3 broke clone for recent Git clients,
diff --git a/ReleaseNotes/ReleaseNotes-2.4.txt b/ReleaseNotes/ReleaseNotes-2.4.txt
index 0e11550..1db4ba3 100644
--- a/ReleaseNotes/ReleaseNotes-2.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.4.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.4
-============================
+= Release notes for Gerrit 2.4
 
 Gerrit 2.4 is now available:
 
 link:https://www.gerritcodereview.com/download/gerrit-2.4.war[https://www.gerritcodereview.com/download/gerrit-2.4.war]
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -16,11 +14,9 @@
 a later 2.1.x version), and then to 2.4.x.  If you are upgrading from 2.2.x.x or
 newer, you may ignore this warning and upgrade directly to 2.4.x.
 
-New Features
-------------
+== New Features
 
-Security
-~~~~~~~~
+=== Security
 
 * Restrict visibility to arbitrary user dashboards
 +
@@ -42,8 +38,7 @@
 
 * Indicate that 'not found' may actually be a permission issue
 
-Web
-~~~
+=== Web
 
 * Add user preference to mark files reviewed automatically or manually
 +
@@ -82,14 +77,12 @@
 * Change 'Loading ...' to say 'Working ...' as, often, there is more going on
 than just loading a response.
 
-Performance
-~~~~~~~~~~~
+=== Performance
 
 * Asynchronously send email so it does not block the UI
 * Optimize queries for open/merged changes by project + branch
 
-Git
-~~~
+=== Git
 
 * Implement a multi-sub-task progress monitor for ReceiveCommits
 
@@ -116,8 +109,7 @@
 can be monitored for timeouts and cancelled, and have stalls reported
 to the user from the main thread.
 
-Search
-~~~~~~
+=== Search
 
 * Add the '--dependencies' option to the 'query' command.
 +
@@ -141,15 +133,13 @@
 With this change, we can fetch the comments on a patchset by sending a
 request to 'https://site/query?comments=true'
 
-Access Rights
-~~~~~~~~~~~~~
+=== Access Rights
 
 * Added the 'emailReviewers' as a global capability.
 +
 This replaces the 'emailOnlyAuthors' flag of account groups.
 
-Dev
-~~~
+=== Dev
 
 * issue 1272 Add scripts to create release notes from git log
 +
@@ -164,8 +154,7 @@
 
 * Add '--issues' and '--issue_numbers' options to the 'gitlog2asciidoc.py'
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 
 * Remove perl from 'commit-msg' hook
 +
@@ -174,8 +163,7 @@
 
 * updating contrib 'trivial_rebase.py' for 2.2.2.1
 
-Upgrades
---------
+== Upgrades
 
 * Updated to Guice 3.0.
 * Updated to gwtorm 1.4.
@@ -185,8 +173,7 @@
 The change also shrinks the built WAR from 38M to 23M
 by excluding the now unnecessary GWT server code.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * issue 904 Users who starred a change should receive all the emails about a change.
 
@@ -226,11 +213,9 @@
 * Fix inconsistent behavior when replicating refs/meta/config
 * Fix duplicated results on status:open project:P branch:B
 
-Documentation
--------------
+== Documentation
 
-Access Rights
-~~~~~~~~~~~~~
+=== Access Rights
 * Capabilities introduced
 * Kill and priority capabilities
 * Administrate server capability
@@ -246,8 +231,7 @@
 * Project owner example role
 * Administrator example role
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 * User upload documentation: Replace changes
 * Add visible-to-all flag in the documentation for cmd-create-group
 * Add a contributing guideline for annotations
diff --git a/ReleaseNotes/ReleaseNotes-2.5.1.txt b/ReleaseNotes/ReleaseNotes-2.5.1.txt
index 6e6a481..c2982df 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.5.1
-==============================
+= Release notes for Gerrit 2.5.1
 
 Gerrit 2.5.1 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from a version older than 2.5, follow the upgrade
 procedure in the 2.5 link:ReleaseNotes-2.5.html[Release Notes].
 
-Security Fixes
---------------
+== Security Fixes
 * Correctly identify Git-over-HTTP operations
 +
 Git operations over HTTP should be classified as using AccessPath.GIT
@@ -45,8 +43,7 @@
   project by definition has no parent)
 by pushing changes of the `project.config` file to `refs/meta/config`.
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix RequestCleanup bug with Git over HTTP
 +
 Decide if a continuation is going to be used early, before the filter
diff --git a/ReleaseNotes/ReleaseNotes-2.5.2.txt b/ReleaseNotes/ReleaseNotes-2.5.2.txt
index cc436ac..9bedeac 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.5.2
-==============================
+= Release notes for Gerrit 2.5.2
 
 Gerrit 2.5.2 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from any earlier version, follow the upgrade
 procedure in the 2.5 link:ReleaseNotes-2.5.html[Release Notes].
 
-Bug Fixes
----------
+== Bug Fixes
 * Improve performance of ReceiveCommits for repos with many refs
 +
 When validating the received commits all existing refs were added as
diff --git a/ReleaseNotes/ReleaseNotes-2.5.3.txt b/ReleaseNotes/ReleaseNotes-2.5.3.txt
index 8e9db0c..6448f1c 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.3.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.5.3
-==============================
+= Release notes for Gerrit 2.5.3
 
 Gerrit 2.5.3 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from a version older than 2.5, follow the upgrade
 procedure in the 2.5 link:ReleaseNotes-2.5.html[Release Notes].
 
-Security Fixes
---------------
+== Security Fixes
 * Patch vulnerabilities in OpenID client library
 +
 Installations using OpenID for authentication were vulnerable to a
diff --git a/ReleaseNotes/ReleaseNotes-2.5.4.txt b/ReleaseNotes/ReleaseNotes-2.5.4.txt
index 4d51528..6ea93bb 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.4.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.5.4
-==============================
+= Release notes for Gerrit 2.5.4
 
 Gerrit 2.5.4 is now available:
 
@@ -10,8 +9,7 @@
 However, if upgrading from a version older than 2.5, follow the upgrade
 procedure in the 2.5 link:ReleaseNotes-2.5.html[Release Notes].
 
-Bug Fixes
----------
+== Bug Fixes
 * Require preferred email to be verified
 +
 Some users were able to select a preferred email address that was
diff --git a/ReleaseNotes/ReleaseNotes-2.5.5.txt b/ReleaseNotes/ReleaseNotes-2.5.5.txt
index 146fd40..27dd2b6 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.5.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.5.5
-==============================
+= Release notes for Gerrit 2.5.5
 
 There are no schema changes from link:ReleaseNotes-2.5.4.html[2.5.4].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.5.5.war[https://www.gerritcodereview.com/download/gerrit-2.5.5.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Patch JGit security hole
 +
 The security hole may permit a modified Git client to gain access
diff --git a/ReleaseNotes/ReleaseNotes-2.5.6.txt b/ReleaseNotes/ReleaseNotes-2.5.6.txt
index b1e88f9..393eb93 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.6.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.5.6
-==============================
+= Release notes for Gerrit 2.5.6
 
 There are no schema changes from link:ReleaseNotes-2.5.6.html[2.5.6].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.5.6.war[https://www.gerritcodereview.com/download/gerrit-2.5.6.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Fix clone for modern Git clients
 +
 The security fix in 2.5.4 broke clone for recent Git clients,
diff --git a/ReleaseNotes/ReleaseNotes-2.5.txt b/ReleaseNotes/ReleaseNotes-2.5.txt
index 8519ee9..6bcb87a 100644
--- a/ReleaseNotes/ReleaseNotes-2.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.5.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.5
-============================
+= Release notes for Gerrit 2.5
 
 Gerrit 2.5 is now available:
 
@@ -10,8 +9,7 @@
 link:ReleaseNotes-2.4.2.html[Gerrit 2.4.2]. These bug fixes are *not*
 listed in these release notes.
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -21,8 +19,7 @@
 a later 2.1.x version), and then to 2.5.x.  If you are upgrading from 2.2.x.x or
 newer, you may ignore this warning and upgrade directly to 2.5.x.
 
-Warning on upgrade to schema version 68
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Warning on upgrade to schema version 68
 
 The migration to schema version 68, may result in a warning, which can
 be ignored when running init in the interactive mode.
@@ -48,19 +45,16 @@
 message you can see that the migration failed because the index exists
 already (as in the example above), you can safely ignore this warning.
 
-Upgrade Warnings
-----------------
+== Upgrade Warnings
 
 [[replication]]
-Replication
-~~~~~~~~~~~
+=== Replication
 
 Gerrit 2.5 no longer includes replication support out of the box.
 Servers that reply upon `replication.config` to copy Git repository
 data to other locations must also install the replication plugin.
 
-Cache Configuration
-~~~~~~~~~~~~~~~~~~~
+=== Cache Configuration
 
 Disk caches are now backed by individual H2 databases, rather than
 Ehcache's own private format. Administrators are encouraged to clear
@@ -98,11 +92,9 @@
 updates are often made to the source without telling Gerrit to reload
 the cache.
 
-New Features
-------------
+== New Features
 
-Plugins
-~~~~~~~
+=== Plugins
 
 The Gerrit server functionality can be extended by installing plugins.
 Depending on how tightly the extension code is coupled with the Gerrit
@@ -261,8 +253,7 @@
 +
 This enables plugins to make use of servlet sessions.
 
-REST API
-~~~~~~~~
+=== REST API
 Gerrit now supports a REST like API available over HTTP. The API is
 suitable for automated tools to build upon, as well as supporting some
 ad-hoc scripting use cases.
@@ -302,11 +293,9 @@
 `site.enableDeprecatedQuery`] parameter in the Gerrit config file. This
 allows to enforce tools to move to the new API.
 
-Web
-~~~
+=== Web
 
-Change Screen
-^^^^^^^^^^^^^
+==== Change Screen
 
 * Display commit message in a box
 +
@@ -384,8 +373,7 @@
 
 * Use more gentle shade of red to highlight outdated dependencies
 
-Patch Screens
-^^^^^^^^^^^^^
+==== Patch Screens
 
 * New patch screen header
 +
@@ -447,8 +435,7 @@
 
 * Use download icons instead of the `Download` text links
 
-User Dashboard
-^^^^^^^^^^^^^^
+==== User Dashboard
 * Support for link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/user-custom-dashboards.html[custom
   dashboards]
 
@@ -472,8 +459,7 @@
 and the oldest stale at the bottom. This may help users to identify
 items to take immediate action on, as they appear closer to the top.
 
-Access Rights Screen
-^^^^^^^^^^^^^^^^^^^^
+==== Access Rights Screen
 
 * Display error if modifying access rights for a ref is forbidden
 +
@@ -512,8 +498,7 @@
 For project owners now also groups to which they are not a member are
 suggested when editing the access rights of the project.
 
-Other
-^^^^^
+==== Other
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=1592[issue 1592]:
   Ask user to login if change is not found
@@ -626,8 +611,7 @@
 The URL for the external system can be configured for the
 link:#custom-extension[`CUSTOM_EXTENSION`] auth type.
 
-Access Rights
-~~~~~~~~~~~~~
+=== Access Rights
 
 * Restrict rebasing of a change in the web UI to the change owner and
   the submitter
@@ -657,8 +641,7 @@
 `Anonymous users` were able to access the `refs/meta/config` branch
 which by default should only be accessible by the project owners.
 
-Search
-~~~~~~
+=== Search
 * Offer suggestions for the search operators in the search panel
 +
 There are many search operators and it's difficult to remember all of
@@ -678,8 +661,7 @@
 
 * `/query` API has been link:#query-deprecation[deprecated]
 
-SSH
-~~~
+=== SSH
 * link:http://code.google.com/p/gerrit/issues/detail?id=1095[issue 1095]
   link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/cmd-set-account.html[SSH command to manage
   accounts]
@@ -738,11 +720,9 @@
 command output a tab-separated table containing all available
 information about each group (though not its members).
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
-Commands
-^^^^^^^^
+==== Commands
 
 * document for the link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/cmd-create-group.html[`create-group`]
   command that for unknown users an account is automatically created if
@@ -770,8 +750,7 @@
 
 * Fix and complete synopsis of commands
 
-Access Control
-^^^^^^^^^^^^^^
+==== Access Control
 
 * Clarify the ref format for
   link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/access-control.html#category_push_merge[`Push
@@ -785,8 +764,7 @@
   link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/access-control.html#capability_emailReviewers[
   `emailReviewers`] capability
 
-Error
-^^^^^
+==== Error
 * Improve documentation of link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/error-change-closed.html[
   `change closed` error]
 +
@@ -806,8 +784,7 @@
   a tag fails because the tagger is somebody else and the `Forge
   Committer` access right is not assigned.
 
-Dev
-^^^
+==== Dev
 
 * Update push URL in link:../SUBMITTING_PATCHES[SUBMITTING_PATCHES]
 +
@@ -840,8 +817,7 @@
 Document what it takes to make a Gerrit stable or stable-fix release,
 and how to release Gerrit subprojects.
 
-Other
-^^^^^
+==== Other
 * Add link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/prolog-cookbook.html[Cookbook for Prolog
   submit rules]
 +
@@ -884,8 +860,7 @@
 +
 Correct typos, spelling mistakes, and grammatical errors.
 
-Dev
-~~~
+=== Dev
 * Add link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.5/dev-release.html#plugin-api[script for
   releasing plugin API jars]
 
@@ -948,8 +923,7 @@
 `all` profile so that the plugin modules are always built for release
 builds.
 
-Mail
-~~~~
+=== Mail
 
 * Add unified diff to newchange mail template
 +
@@ -997,8 +971,7 @@
 +
 Show the URL right away in the body.
 
-Miscellaneous
-~~~~~~~~~~~~~
+=== Miscellaneous
 * Back in-memory caches with Guava, disk caches with H2
 +
 Instead of using Ehcache for in-memory caches, use Guava. The Guava
@@ -1264,8 +1237,7 @@
 eventually go away when there is proper support for authentication
 plugins.
 
-Performance
-~~~~~~~~~~~
+=== Performance
 [[performance-issue-on-showing-group-list]]
 * Fix performance issues on showing the list of groups in the Gerrit
   WebUI
@@ -1399,8 +1371,7 @@
 front allows the database to send all requests to the backend as early
 as possible, allowing the network latency to overlap.
 
-Upgrades
---------
+== Upgrades
 * Update Gson to 2.1
 * Update GWT to 2.4.0
 * Update JGit to 2.0.0.201206130900-r.23-gb3dbf19
@@ -1416,11 +1387,9 @@
 inner table did not dynamically resize to handle a larger number
 of cached items, causing O(N) lookup performance for most objects.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Security
-~~~~~~~~
+=== Security
 * Ensure that only administrators can change the global capabilities
 +
 Only Gerrit server administrators (members of the groups that have
@@ -1468,8 +1437,7 @@
 Hence this check is currently not done and these access rights in this
 case have simply no effect.
 
-Web
-~~~
+=== Web
 
 * Do not show "Session cookie not available" on sign in
 +
@@ -1625,8 +1593,7 @@
 and redirect to 'Admin' > 'Projects' to show the projects the caller
 has access to.
 
-Mail
-~~~~
+=== Mail
 
 * Fix: Rebase did not mail all reviewers
 
@@ -1653,8 +1620,7 @@
 `Reverted.vm` were not extracted during the initialization of a new
 site.
 
-SSH
-~~~
+=== SSH
 * Fix reject message if bypassing code review is not allowed
 +
 If a user is not allowed to bypass code review, but tries to push a
@@ -1726,8 +1692,7 @@
 non existing project name this was logged in the `error.log` but
 printing the error out to the user is sufficient.
 
-Authentication
-~~~~~~~~~~~~~~
+=== Authentication
 
 * Fix NPE in LdapRealm caused by non-LDAP users
 +
@@ -1748,8 +1713,7 @@
 is valid. When the URL is missing (e.g. because the provider is
 still broken) rely on the context path of the application instead.
 
-Replication
-~~~~~~~~~~~
+=== Replication
 
 * Fix inconsistent behavior when replicating `refs/meta/config`
 +
@@ -1766,8 +1730,7 @@
 The groupCache was being used before it was set in the class. Fix the
 ordering of the assignment.
 
-Approval Categories
-~~~~~~~~~~~~~~~~~~~
+=== Approval Categories
 
 * Make `NoBlock` and `NoOp` approval category functions work
 +
@@ -1811,8 +1774,7 @@
 collection came out null, which cannot be iterated. Make it always be
 an empty list.
 
-Other
-~~~~~
+=== Other
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=1554[issue 1554]:
   Fix cloning of new projects from slave servers
diff --git a/ReleaseNotes/ReleaseNotes-2.6.1.txt b/ReleaseNotes/ReleaseNotes-2.6.1.txt
index e43b077..94de483 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.1.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.6.1
-==============================
+= Release notes for Gerrit 2.6.1
 
 There are no schema changes from link:ReleaseNotes-2.6.html[2.6].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.6.1.war[https://www.gerritcodereview.com/download/gerrit-2.6.1.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * Patch JGit security hole
 +
 The security hole may permit a modified Git client to gain access
diff --git a/ReleaseNotes/ReleaseNotes-2.6.txt b/ReleaseNotes/ReleaseNotes-2.6.txt
index dfd5d80..26b0b0e 100644
--- a/ReleaseNotes/ReleaseNotes-2.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.6.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.6
-============================
+= Release notes for Gerrit 2.6
 
 Gerrit 2.6 is now available:
 
@@ -12,8 +11,7 @@
 link:ReleaseNotes-2.5.4.html[Gerrit 2.5.4]. These bug fixes are *not*
 listed in these release notes.
 
-Schema Change
--------------
+== Schema Change
 *WARNING:* This release contains schema changes.  To upgrade:
 ----
   java -jar gerrit.war init -d site_path
@@ -23,8 +21,7 @@
 a later 2.1.x version), and then to 2.6.x.  If you are upgrading from 2.2.x.x or
 newer, you may ignore this warning and upgrade directly to 2.6.x.
 
-Reverse Proxy Configuration Changes
------------------------------------
+== Reverse Proxy Configuration Changes
 
 If you are running a reverse proxy in front of Gerrit (e.g. Apache or Nginx),
 make sure to check your configuration, especially if you are encountering
@@ -34,8 +31,7 @@
 
 Gerrit now requires passed URLs to be unchanged by the proxy.
 
-Release Highlights
-------------------
+== Release Highlights
 * 42x improvement on `git clone` and `git fetch`
 +
 Running link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/cmd-gc.html[
@@ -51,14 +47,11 @@
 labels] and link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/prolog-cookbook.html[
 Prolog rules].
 
-New Features
-------------
+== New Features
 
-Web UI
-~~~~~~
+=== Web UI
 
-Global
-^^^^^^
+==== Global
 
 * New Login Screens
 +
@@ -93,8 +86,7 @@
 
 * Add a link to the REST API documentation in the top menu.
 
-Search
-^^^^^^
+==== Search
 * Suggest projects, groups and users in search panel
 +
 Suggest projects, groups and users in the search panel as parameter for
@@ -107,8 +99,7 @@
 The values that are suggested for the search operators in the search
 panel are now only quoted if they contain a whitespace.
 
-Change Screens
-^^^^^^^^^^^^^^
+==== Change Screens
 
 * A change's commit message can be edited from the change screen.
 
@@ -182,8 +173,7 @@
 
 * Rename "Old Version History" to "Reference Version".
 
-Patch Screens
-^^^^^^^^^^^^^
+==== Patch Screens
 
 * Support for file comments
 +
@@ -205,8 +195,7 @@
 
 * Enable expanding skipped lines even if 'Syntax Coloring' is off.
 
-Project Screens
-^^^^^^^^^^^^^^^
+==== Project Screens
 
 * Support filtering of projects in the project list screen
 +
@@ -260,8 +249,7 @@
 Improve the error messages that are displayed in the WebUI if the
 creation of a branch fails due to invalid user input.
 
-Group Screens
-^^^^^^^^^^^^^
+==== Group Screens
 
 * Support filtering of groups in the group list screen
 +
@@ -276,8 +264,7 @@
 well-known system groups which are of type 'SYSTEM'. The system groups
 are so well-known that there is no need to display the type for them.
 
-Dashboard Screens
-^^^^^^^^^^^^^^^^^
+==== Dashboard Screens
 
 * Link dashboard title to a URL version of itself
 +
@@ -290,8 +277,7 @@
 
 * Increase time span for "Recently Closed" section in user dashboard to 4 weeks.
 
-Account Screens
-^^^^^^^^^^^^^^^
+==== Account Screens
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1740[Issue 1740]:
   Display description how to generate SSH Key in SshPanel
@@ -302,16 +288,14 @@
 
 * Make the text for "Register" customizable
 
-Plugin Screens
-^^^^^^^^^^^^^^
+==== Plugin Screens
 
 * Show status for enabled plugins in the WebUI as 'Enabled'
 +
 Earlier no status was shown for enabled plugins, which was confusing to
 some users.
 
-REST API
-~~~~~~~~
+=== REST API
 
 * A big chunk of the Gerrit functionality is now available via the
   link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/rest-api.html[REST API].
@@ -443,8 +427,7 @@
 HTML thanks to Gson encoding HTML control characters using Unicode
 character escapes within JSON strings.
 
-Project Dashboards
-~~~~~~~~~~~~~~~~~~
+=== Project Dashboards
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/user-dashboards.html#project-dashboards[
   Support for storing custom dashboards for projects]
 +
@@ -472,8 +455,7 @@
 The `foreach` parameter which will get appended to all the queries in
 the dashboard.
 
-Access Controls
-~~~~~~~~~~~~~~~
+=== Access Controls
 * Allow to overrule `BLOCK` permissions on the same project
 +
 It was impossible to block a permission for a group and allow the same
@@ -567,8 +549,7 @@
 Having the `Push` access right for `refs/meta/config` on the
 `All-Projects` project without being administrator has no effect.
 
-Hooks
-~~~~~
+=== Hooks
 * Change topic is passed to hooks as `--topic NAME`.
 * link:https://code.google.com/p/gerrit/issues/detail?id=1200[Issue 1200]:
 New `reviewer-added` hook and stream event when a reviewer is added.
@@ -581,8 +562,7 @@
 
 * Add `--is-draft` parameter to `comment-added` hook
 
-Git
-~~~
+=== Git
 * Add options to `refs/for/` magic branch syntax
 +
 Git doesn't want to modify the network protocol to support passing
@@ -629,8 +609,7 @@
 
 * Add `oldObjectId` and `newObjectId` to the `GitReferenceUpdatedListener.Update`
 
-SSH
-~~~
+=== SSH
 * New SSH command to http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/cmd-gc.html[
   run Git garbage collection]
 +
@@ -659,8 +638,7 @@
 * http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/cmd-test-submit-type.html[
   test-submit type] tests the Prolog submit type with a chosen change.
 
-Query
-~~~~~
+=== Query
 * Allow `{}` to be used for quoting in query expressions
 +
 This makes it a little easier to query for group names that contain
@@ -676,8 +654,7 @@
 * When a file is renamed the old file name is included in the Patch
   attribute
 
-Plugins
-~~~~~~~
+=== Plugins
 * Plugins can contribute Prolog facts/predicates from Java.
 * Plugins can prompt for parameters during `init` with `InitStep`.
 * Plugins can now contribute JavaScript to the web UI. UI plugins can
@@ -697,8 +674,7 @@
 delegate to the plugin servlet's magic handling for static files and
 documentation. Add JAR attributes to configure these prefixes.
 
-Prolog
-~~~~~~
+=== Prolog
 [[submit-type-from-prolog]]
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/prolog-cookbook.html#HowToWriteSubmitType[
   Support controlling the submit type for changes from Prolog]
@@ -723,8 +699,7 @@
 
 * A new `max_with_block` predicate was added for more convenient usage
 
-Email
-~~~~~
+=== Email
 * Notify project watchers if draft change is published
 * Notify users mentioned in commit footer on draft publish
 * Add new notify type that allows watching of new patch sets
@@ -747,8 +722,7 @@
 which review updates should send email, and which categories of users
 on a change should get that email.
 
-Labels
-~~~~~~
+=== Labels
 * Approval categories stored in the database have been replaced with labels
   configured in `project.config`. Existing categories are migrated to
   `project.config` in `All-Projects` as part of the schema upgrade; no user
@@ -765,8 +739,7 @@
 or their own build system they can now trivially add the `Verified`
 category by pasting 5 lines into `project.config`.
 
-Dev
-~~~
+=== Dev
 
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/dev-readme.html#debug-javascript[
   Support loading debug JavaScript]
@@ -815,8 +788,7 @@
 * "Become Any Account" can be used for accounts whose full name is an empty string.
 
 
-Performance
-~~~~~~~~~~~
+=== Performance
 * Bitmap Optimizations
 +
 On running the http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/cmd-gc.html[
@@ -904,8 +876,7 @@
 database traffic a cache for changes was introduced. This cache is
 disabled by default since it can mess up multi-server setups.
 
-Misc
-~~~~
+=== Misc
 * Add config parameter to make new groups by default visible to all
 +
 Add a new http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/config-gerrit.html#groups.newGroupsVisibleToAll[
@@ -1018,8 +989,7 @@
 
 * Show path to gerrit.war in command for upgrade schema
 
-Upgrades
-~~~~~~~~
+=== Upgrades
 * link:https://code.google.com/p/gerrit/issues/detail?id=1619[Issue 1619]:
 Embedded Jetty is now 8.1.7.v20120910.
 
@@ -1033,11 +1003,9 @@
 +
 Fixes some issues with IE9 and IE10.
 
-Bug Fixes
----------
+== Bug Fixes
 
-Web UI
-~~~~~~
+=== Web UI
 * link:https://code.google.com/p/gerrit/issues/detail?id=1662[Issue 1662]:
   Don't show error on ACL modification if empty permissions are added
 +
@@ -1234,8 +1202,7 @@
 Correctly keep patch set ordering after a new patch set is added via
 the Web UI.
 
-REST API
-~~~~~~~~
+=== REST API
 * Fix returning of 'Email Reviewers' capability via REST
 +
 The `/accounts/self/capabilities/` didn't return the 'Email Reviewers'
@@ -1249,8 +1216,7 @@
 * Provide a more descriptive error message for unauthenticated REST
   API access
 
-Git
-~~~
+=== Git
 * The wildcard `.` is now permitted in reference regex rules.
 
 * Checking if a change is mergeable no longer writes to the repository.
@@ -1356,8 +1322,7 @@
 "Only 0 of 0 new change refs created in xxx; aborting"
 could appear in the error log.
 
-SSH
-~~~
+=== SSH
 * `review --restore` allows a review score to be added on the restored change.
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1721[Issue 1721]:
@@ -1379,8 +1344,7 @@
 
 * Fix setting account's full name via ssh.
 
-Query
-~~~~~
+=== Query
 * link:https://code.google.com/p/gerrit/issues/detail?id=1729[Issue 1729]:
   Fix query by 'label:Verified=0'
 
@@ -1389,8 +1353,7 @@
 
 * Fix query cost for "status:merged commit:c0ffee"
 
-Plugins
-~~~~~~~
+=== Plugins
 * Skip disabled plugins on rescan
 +
 In a background thread Gerrit periodically scans for new or changed
@@ -1450,8 +1413,7 @@
   Allow InternalUser (aka plugins) to see any internal group, and run
   plugin startup and shutdown as PluginUser.
 
-Email
-~~~~~
+=== Email
 * Merge failure emails are only sent once per day.
 * Unused macros are removed from the mail templates.
 * Unnecessary ellipses are no longer applied to email subjects.
@@ -1467,8 +1429,7 @@
 If a user is watching 'All Comments' on `All-Projects` this should
 apply to all projects.
 
-Misc
-~~~~
+=== Misc
 * Provide more descriptive message for NoSuchProjectException
 
 * On internal error due to receive timeout include the value of
@@ -1630,15 +1591,13 @@
 standard `Authorization` header unspecified and available for use in
 HTTP reverse proxies.
 
-Documentation
--------------
+== Documentation
 
 The link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/index.html[
 documentation index] is restructured to make it easier to use for different kinds of
 users.
 
-User Documentation
-~~~~~~~~~~~~~~~~~~
+=== User Documentation
 * Split link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/rest-api.html[
   REST API documentation] and have one page per top level resource
 
@@ -1750,8 +1709,7 @@
 * Fix external links in 2.0.21 and 2.0.24 release notes
 * Manual pages can be optionally created/installed for core gerrit ssh commands.
 
-Developer And Maintainer Documentation
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Developer And Maintainer Documentation
 * Updated the link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.6/dev-eclipse.html#maven[
   Maven plugin installation instructions] for Eclipse 3.7 (Indigo).
 
diff --git a/ReleaseNotes/ReleaseNotes-2.7.txt b/ReleaseNotes/ReleaseNotes-2.7.txt
index 9782b08..0870cbf 100644
--- a/ReleaseNotes/ReleaseNotes-2.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.7.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.7
-============================
+= Release notes for Gerrit 2.7
 
 
 Gerrit 2.7 is now available:
@@ -10,8 +9,7 @@
 Gerrit 2.7 includes the bug fixes done with link:ReleaseNotes-2.6.1.html[Gerrit 2.6.1].
 These bug fixes are *not* listed in these release notes.
 
-Schema Change
--------------
+== Schema Change
 
 
 *WARNING:* This release contains schema changes.  To upgrade:
@@ -25,8 +23,7 @@
 
 
 
-Gerrit Trigger Plugin in Jenkins
---------------------------------
+== Gerrit Trigger Plugin in Jenkins
 
 
 *WARNING:* Upgrading to 2.7 may cause the Gerrit Trigger Plugin in Jenkins to
@@ -34,8 +31,7 @@
 below.
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 
 * New `copyMaxScore` setting for labels.
@@ -46,12 +42,10 @@
 * Several new REST APIs.
 
 
-New Features
-------------
+== New Features
 
 
-General
-~~~~~~~
+=== General
 
 * New `copyMaxScore` setting for labels.
 +
@@ -105,12 +99,10 @@
 * Allow administrators to see all groups.
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 
-Global
-^^^^^^
+==== Global
 
 * User avatars are displayed in more places in the Web UI.
 
@@ -120,8 +112,7 @@
 mouse over avatar images.
 
 
-Change Screens
-^^^^^^^^^^^^^^
+==== Change Screens
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=667[Issue 667]:
@@ -140,8 +131,7 @@
 change comments.
 
 
-Diff Screens
-^^^^^^^^^^^^
+==== Diff Screens
 
 * Show images in side-by-side and unified diffs.
 
@@ -150,15 +140,13 @@
 * Harmonize unified diff's styling of images with that of text.
 
 
-REST API
-~~~~~~~~
+=== REST API
 
 
 Several new link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/rest-api.html[
 REST API endpoints] are added.
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/rest-api-accounts.html#get-diff-preferences[
@@ -168,8 +156,7 @@
 Set account diff preferences]
 
 
-Changes
-^^^^^^^
+==== Changes
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1820[Issue 1820]:
@@ -181,16 +168,14 @@
 
 
 
-Projects
-^^^^^^^^
+==== Projects
 
 
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/rest-api-projects.html#get-config[
 Get project configuration]
 
 
-ssh
-~~~
+=== ssh
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1088[Issue 1088]:
@@ -198,11 +183,9 @@
 Kerberos authentication for ssh interaction].
 
 
-Bug Fixes
----------
+== Bug Fixes
 
-General
-~~~~~~~
+=== General
 
 * Postpone check for first account until adding an account.
 
@@ -240,8 +223,7 @@
 draft was already deleted.
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 
 * Properly handle double-click on external group in GroupTable.
@@ -282,8 +264,7 @@
 Fix browser null-pointer exception when ChangeCache is incomplete.
 
 
-REST API
-~~~~~~~~
+=== REST API
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1819[Issue 1819]:
@@ -294,37 +275,32 @@
 * Correct URL encoding in 'GroupInfo'.
 
 
-Email
-~~~~~
+=== Email
 
 * Log failure to access reviewer list for notification emails.
 
 * Log when appropriate if email delivery is skipped.
 
 
-ssh
-~~~
+=== ssh
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2016[Issue 2016]:
 Flush caches after adding or deleting ssh keys via the `set-account` ssh command.
 
-Tools
-~~~~~
+=== Tools
 
 
 * The release build now builds for all browser configurations.
 
 
-Upgrades
---------
+== Upgrades
 
 * `gwtexpui` is now built in the gerrit tree rather than linking a separate module.
 
 
 
-Documentation
--------------
+== Documentation
 
 
 * Update the access control documentation to clarify how to set
diff --git a/ReleaseNotes/ReleaseNotes-2.8.1.txt b/ReleaseNotes/ReleaseNotes-2.8.1.txt
index 26414a1..5e32cf5 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.1.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.8.1
-==============================
+= Release notes for Gerrit 2.8.1
 
 There are no schema changes from link:ReleaseNotes-2.8.html[2.8].
 
 link:https://www.gerritcodereview.com/download/gerrit-2.8.1.war[https://www.gerritcodereview.com/download/gerrit-2.8.1.war]
 
-Bug Fixes
----------
+== Bug Fixes
 * link:https://code.google.com/p/gerrit/issues/detail?id=2073[Issue 2073]:
 Changes that depend on outdated patch sets were missing in the related changes list.
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.8.2.txt b/ReleaseNotes/ReleaseNotes-2.8.2.txt
index 926db02..99cb437 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.2.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8.2
-==============================
+= Release notes for Gerrit 2.8.2
 
 There are no schema changes from link:ReleaseNotes-2.8.1.html[2.8.1].
 
@@ -8,8 +7,7 @@
 https://www.gerritcodereview.com/download/gerrit-2.8.2.war]
 
 
-Lucene Index
-------------
+== Lucene Index
 
 * Support committing Lucene writes within a fixed interval.
 +
@@ -24,8 +22,7 @@
 indexes are committed.
 
 
-General
--------
+== General
 
 * Only add "cherry picked from" when cherry picking a merged change.
 +
@@ -142,8 +139,7 @@
 The joda time library was being unnecessarily packaged in the root of
 the gerrit.war file.
 
-Change Screen / Diff Screen
----------------------------
+== Change Screen / Diff Screen
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2398[Issue 2398]:
@@ -231,8 +227,7 @@
 +
 Now, an error message will be displayed in the UI.
 
-ssh
----
+== ssh
 
 
 * Support for nio2 backend is removed.
@@ -263,8 +258,7 @@
 * link:https://code.google.com/p/gerrit/issues/detail?id=2515[Issue 2515]:
 Fix internal server error when updating an existing label with `gerrit review`.
 
-Replication Plugin
-------------------
+== Replication Plugin
 
 
 * Never replicate automerge-cache commits.
@@ -287,14 +281,12 @@
 * Update documentation to clarify replication of refs/meta/config when
 refspec is 'all refs'.
 
-Upgrades
---------
+== Upgrades
 
 
 * JGit is upgraded to 3.2.0.201312181205-r
 
-Documentation
--------------
+== Documentation
 
 
 * Add missing documentation of the secondary index configuration.
diff --git a/ReleaseNotes/ReleaseNotes-2.8.3.txt b/ReleaseNotes/ReleaseNotes-2.8.3.txt
index 2bd4aa7..f94dce0 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.3.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8.3
-==============================
+= Release notes for Gerrit 2.8.3
 
 There are no schema changes from link:ReleaseNotes-2.8.2.html[2.8.2].
 
@@ -8,8 +7,7 @@
 https://www.gerritcodereview.com/download/gerrit-2.8.3.war]
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Fix for merging multiple changes with "Cherry Pick", "Merge Always" and
 "Merge If Necessary" strategies.
@@ -19,8 +17,7 @@
 them to actually land into the branch.
 
 
-Documentation
--------------
+== Documentation
 
 * Minor fixes in the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8.3/dev-buck.html[
diff --git a/ReleaseNotes/ReleaseNotes-2.8.4.txt b/ReleaseNotes/ReleaseNotes-2.8.4.txt
index b80ac17..8aac71c 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.4.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8.4
-==============================
+= Release notes for Gerrit 2.8.4
 
 There are no schema changes from link:ReleaseNotes-2.8.3.html[2.8.3].
 
@@ -8,12 +7,10 @@
 https://www.gerritcodereview.com/download/gerrit-2.8.4.war]
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 
 * Disable `commitWithin` when running Reindex.
@@ -31,8 +28,7 @@
 `SubIndex.NrtFuture` objects were being added as listeners of `searchManager`
 and never released.
 
-Change Screen
-~~~~~~~~~~~~~
+=== Change Screen
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2456[Issue 2456]:
@@ -84,8 +80,7 @@
 tooltip on the up arrow but did not show the tooltip on the left
 or right arrows.
 
-Plugins
-~~~~~~~
+=== Plugins
 
 
 * Fix ChangeListener auto-registered implementations.
@@ -98,8 +93,7 @@
 Plugins could be built, but not loaded, if they had any manifest entries
 that contained a dollar sign.
 
-Misc
-~~~~
+=== Misc
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2564[Issue 2564],
@@ -156,8 +150,7 @@
 so stream-events consumers can properly detect who uploaded the
 rebased patch set.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1273[Issue 1273]:
diff --git a/ReleaseNotes/ReleaseNotes-2.8.5.txt b/ReleaseNotes/ReleaseNotes-2.8.5.txt
index db18083..ae30530 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.5.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.5.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.8.5
-==============================
+= Release notes for Gerrit 2.8.5
 
 Download:
 link:https://www.gerritcodereview.com/download/gerrit-2.8.5.war[
 https://www.gerritcodereview.com/download/gerrit-2.8.5.war]
 
-Schema Changes and Upgrades
----------------------------
+== Schema Changes and Upgrades
 
 
 * There are no schema changes from link:ReleaseNotes-2.8.4.html[2.8.4].
@@ -24,19 +22,16 @@
   java -jar gerrit.war init -d site_path
 ----
 
-Bug Fixes
----------
+== Bug Fixes
 
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 
 * Fix deadlocks on index shutdown.
 
 
-Change Screen
-~~~~~~~~~~~~~
+=== Change Screen
 
 
 * Only permit current patch set to edit the commit message.
@@ -68,8 +63,7 @@
 * Fix failure to load side-by-side diff due to "ISE EditIterator out of bounds"
 error.
 
-ssh
-~~~
+=== ssh
 
 * Upgrade SSHD to version 0.11.0.
 +
@@ -90,8 +84,7 @@
 link:https://issues.apache.org/jira/browse/SSHD-252[bug in SSHD].  That bug
 was fixed in SSHD version 0.10.0, so now we can re-enable nio2.
 
-Misc
-~~~~
+=== Misc
 
 
 * Keep old timestamps during data migration.
diff --git a/ReleaseNotes/ReleaseNotes-2.8.6.1.txt b/ReleaseNotes/ReleaseNotes-2.8.6.1.txt
index d1ed9e9..81e7297 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.6.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.6.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8.6.1
-================================
+= Release notes for Gerrit 2.8.6.1
 
 There are no schema changes from link:ReleaseNotes-2.8.6.html[2.8.6].
 
@@ -7,8 +6,7 @@
 link:https://www.gerritcodereview.com/download/gerrit-2.8.6.1.war[
 https://www.gerritcodereview.com/download/gerrit-2.8.6.1.war]
 
-Bug Fixes
----------
+== Bug Fixes
 
 * The fix in 2.8.6 for the merge queue race condition caused a regression
 in database transaction handling.
@@ -17,7 +15,6 @@
 database support.
 
 
-Updates
--------
+== Updates
 
 * gwtorm is updated to 1.7.3
diff --git a/ReleaseNotes/ReleaseNotes-2.8.6.txt b/ReleaseNotes/ReleaseNotes-2.8.6.txt
index ab79a20..a810ad0 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.6.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.6.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8.6
-==============================
+= Release notes for Gerrit 2.8.6
 
 There are no schema changes from link:ReleaseNotes-2.8.5.html[2.8.5].
 
@@ -10,8 +9,7 @@
 *Warning*: Support for MySQL's MyISAM storage engine is discontinued.
 Only transactional storage engines are supported.
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2034[Issue 2034],
 link:https://code.google.com/p/gerrit/issues/detail?id=2383[Issue 2383],
@@ -40,8 +38,7 @@
 * Fix sporadic SSHD handshake failures
 (link:https://issues.apache.org/jira/browse/SSHD-330[SSHD-330]).
 
-Updates
--------
+== Updates
 
 * gwtorm is updated to 1.7.1
 * sshd is updated to 0.11.1-atlassian-1
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index 2d1dc7a..472f0dc 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.8
-============================
+= Release notes for Gerrit 2.8
 
 
 Gerrit 2.8 is now available:
@@ -8,8 +7,7 @@
 https://www.gerritcodereview.com/download/gerrit-2.8.war]
 
 
-Schema Change
--------------
+== Schema Change
 
 
 *WARNING:* This release contains schema changes.  To upgrade:
@@ -46,8 +44,7 @@
 site initialization].
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/intro-change-screen.html[
@@ -70,11 +67,9 @@
 * New core plugin: Download Commands.
 
 
-New Features
-------------
+== New Features
 
-Build
-~~~~~
+=== Build
 
 * Gerrit is now built with
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/dev-buck.html[
@@ -83,8 +78,7 @@
 * Documentation is now built with Buck and link:http://asciidoctor.org[Asciidoctor].
 
 
-Indexing and Search
-~~~~~~~~~~~~~~~~~~~
+=== Indexing and Search
 
 Gerrit can be configured to use a
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/config-gerrit.html#__a_id_index_a_section_index[
@@ -111,8 +105,7 @@
 reindex program] before restarting the Gerrit server.
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * Project owners can define `receive.maxObjectSizeLimit` in the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/config-gerrit.html#receive.maxObjectSizeLimit[
@@ -168,17 +161,14 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/config-labels.html#httpd.label_copyAllScoresOnTrivialRebase[
 configured to copy scores forward to new patch sets for trivial rebases].
 
-Web UI
-~~~~~~
+=== Web UI
 
 
-Global
-^^^^^^
+==== Global
 
 * The change status is shown in a separate column on dashboards and search results.
 
-Change Screens
-^^^^^^^^^^^^^^
+==== Change Screens
 
 
 * New change screen with completely redesigned UI, using the REST API.
@@ -220,8 +210,7 @@
 are uploaded.
 
 
-REST API
-~~~~~~~~
+=== REST API
 
 * Several new link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api.html[
 REST API endpoints] are added.
@@ -230,15 +219,13 @@
 
 * REST views can handle 'HTTP 422 Unprocessable Entity' responses.
 
-Access Rights
-^^^^^^^^^^^^^
+==== Access Rights
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-access.html#list-access[
 List access rights for project(s)]
 
-Accounts
-^^^^^^^^
+==== Accounts
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#create-account[
@@ -310,8 +297,7 @@
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-accounts.html#unstar-change[
 Unstar change]
 
-Changes
-^^^^^^^
+==== Changes
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-changes.html#rebase-change[
@@ -345,8 +331,7 @@
 Get included in]
 
 
-Config
-^^^^^^
+==== Config
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-config.html#get-capabilities[
 Get capabilities]
@@ -355,8 +340,7 @@
 Get version] (of the Gerrit server)
 
 
-Projects
-^^^^^^^^
+==== Projects
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/rest-api-projects.html#list-branches[
@@ -381,8 +365,7 @@
 Set configuration]
 
 
-Capabilities
-~~~~~~~~~~~~
+=== Capabilities
 
 
 New global capabilities are added.
@@ -403,8 +386,7 @@
 explicitly.
 
 
-Emails
-~~~~~~
+=== Emails
 
 * The `RebasedPatchSet` template is removed.  Email notifications for rebased
 changes are now sent with the `ReplacePatchSet` template.
@@ -413,12 +395,10 @@
 to, and links to the file(s) in which comments are made.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
 
-Global
-^^^^^^
+==== Global
 
 
 * Plugins may now contribute buttons to various parts of the UI using the
@@ -475,14 +455,12 @@
 
 
 
-Commit Message Length Checker
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== Commit Message Length Checker
 
 
 * Commits whose subject or body length exceeds the limit can be rejected.
 
-Replication
-^^^^^^^^^^^
+==== Replication
 
 * Automatically create missing repositories on the destination.
 +
@@ -528,8 +506,7 @@
 delay.
 
 
-ssh
-~~~
+=== ssh
 
 
 * The `commit-msg` hook installation command is now
@@ -557,8 +534,7 @@
 * The 'CHANGEID,PATCHSET' format for specifying a patch set in the `review` command
 is no longer considered to be a 'legacy' feature that will be removed in future.
 
-Daemon
-~~~~~~
+=== Daemon
 
 
 * Add `--init` option to Daemon to initialize site on daemon start.
@@ -567,12 +543,10 @@
 non-interactive (batch) mode.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 
-General
-~~~~~~~
+=== General
 
 
 * Use the parent change on the same branch for rebases.
@@ -619,8 +593,7 @@
 lots of time can be saved when pushing to complex Gits.
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 
 * Do not persist default project state in `project.config`.
@@ -644,12 +617,10 @@
 
 * Fix JdbcSQLException when numbers are read from cache.
 
-Web UI
-~~~~~~
+=== Web UI
 
 
-Global
-^^^^^^
+==== Global
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1574[Issue 1574]:
@@ -669,8 +640,7 @@
 If a user voted '-1', and then another user voted '+1' for a label, the
 label was shown as a red '1' in the change list instead of red '-1'.
 
-Change Screens
-^^^^^^^^^^^^^^
+==== Change Screens
 
 
 * Default review comment visibility is changed to expand all recent.
@@ -694,8 +664,7 @@
 
 * Prevent duplicate permitted_labels from being shown in labels list.
 
-Diff Screens
-^^^^^^^^^^^^
+==== Diff Screens
 
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=1233[Issue 1233]:
@@ -708,8 +677,7 @@
 that comment was not shown if there was no code changed between
 the two patch sets
 
-Project Screens
-^^^^^^^^^^^^^^^
+==== Project Screens
 
 
 * Only enable the delete branch button when branches are selected.
@@ -717,16 +685,14 @@
 * Disable the delete branch button while branch deletion requests are
 still being processed.
 
-User Profile Screens
-^^^^^^^^^^^^^^^^^^^^
+==== User Profile Screens
 
 
 * The preferred email address field is shown as empty if the user has no
 preferred email address.
 
 
-REST API
-~~~~~~~~
+=== REST API
 
 
 * Support raw input also in POST requests.
@@ -735,8 +701,7 @@
 
 * Return all revisions when `o=ALL_REVISIONS` is set on `/changes/`.
 
-ssh
-~~~
+=== ssh
 
 
 * The `--force-message` option is removed from the
@@ -762,11 +727,9 @@
 * Improve the error message when rejecting upload for review to a read-only project.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
-Global
-^^^^^^
+==== Global
 
 * Better error message when a Javascript plugin cannot be loaded.
 
@@ -784,8 +747,7 @@
 * Make plugin servlet's context path authorization aware.
 
 
-Review Notes
-^^^^^^^^^^^^
+==== Review Notes
 
 * Do not try to create review notes for ref deletion events.
 
@@ -800,22 +762,19 @@
 
 * Correct documentation of the export command.
 
-Emails
-~~~~~~
+=== Emails
 
 * Email notifications are sent for new changes created via actions in the
 Web UI such as cherry-picking or reverting a change.
 
 
-Tools
-~~~~~
+=== Tools
 
 
 * git-exproll.sh: return non-zero on errors
 
 
-Documentation
--------------
+== Documentation
 
 
 * The link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.8/index.html[
@@ -832,8 +791,7 @@
 Documentation of the query operator is fixed.
 
 
-Upgrades
---------
+== Upgrades
 
 * Update JGit to 3.1.0.201310021548-r
 * Update gwtorm to 1.7
diff --git a/ReleaseNotes/ReleaseNotes-2.9.1.txt b/ReleaseNotes/ReleaseNotes-2.9.1.txt
index 3377df4..b584193 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.1.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.1.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.9.1
-==============================
+= Release notes for Gerrit 2.9.1
 
 There are no schema changes from link:ReleaseNotes-2.9.html[2.9].
 
@@ -13,8 +12,7 @@
 startup failure described in
 link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
 
-Bug Fixes
----------
+== Bug Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2801[Issue 2801]:
 Set default for review SSH command to `notify=ALL`.
diff --git a/ReleaseNotes/ReleaseNotes-2.9.2.txt b/ReleaseNotes/ReleaseNotes-2.9.2.txt
index 4e5de01..ec5b77e 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.2.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.2.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.9.2
-==============================
+= Release notes for Gerrit 2.9.2
 
 Download:
 link:https://www.gerritcodereview.com/download/gerrit-2.9.2.war[
 https://www.gerritcodereview.com/download/gerrit-2.9.2.war]
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* There are no schema changes from
 link:ReleaseNotes-2.9.1.html[2.9.1], but when upgrading from an existing site
@@ -28,11 +26,9 @@
 startup failure described in
 link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
 
-Bug Fixes
----------
+== Bug Fixes
 
-ssh
-~~~
+=== ssh
 
 * Update SSHD to 0.13.0.
 +
@@ -41,8 +37,7 @@
 +
 Also update SSHD Mina to 2.0.8 and Bouncycastle to 1.51.
 
-Database
-~~~~~~~~
+=== Database
 
 * Update gwtorm to 1.14.
 +
@@ -54,8 +49,7 @@
 were initialized with Gerrit version 2.6 or later, the primary key column
 order will be fixed during initialization when upgrading to 2.9.2.
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 * Fix "400 cannot create query for index" error in "Conflicts With" list.
 +
@@ -79,8 +73,7 @@
 the database. If a user clicks on search result from a stale change, they will
 get a 404 page and the change will be removed from the index.
 
-Change Screen
-~~~~~~~~~~~~~
+=== Change Screen
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2964[Issue 2964]:
 Fix comment box font colors of dark CodeMirror themes.
@@ -107,8 +100,7 @@
 
 * Remove 'send email' checkbox from reply box on change screen.
 
-Plugins
-~~~~~~~
+=== Plugins
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=543[Issue 543]
 Replication plugin: Prevent creating repos on extra servers.
@@ -120,8 +112,7 @@
 By ensuring the authGroup can see the project first, the repository is
 not created if it's not needed.
 
-Security
-~~~~~~~~
+=== Security
 
 * Do not throw away bytes from the CNSPRG when generating HTTP passwords.
 +
@@ -141,8 +132,7 @@
 with BLOCK or DENY action were considered as project owners.
 
 
-Miscellaneous Fixes
-~~~~~~~~~~~~~~~~~~~
+=== Miscellaneous Fixes
 
 * link:https://code.google.com/p/gerrit/issues/detail?id=2911[Issue 2911]:
 Fix Null Pointer Exception after a MergeValidationListener throws
diff --git a/ReleaseNotes/ReleaseNotes-2.9.3.txt b/ReleaseNotes/ReleaseNotes-2.9.3.txt
index e6c8573..1b732cb 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.3.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.3.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.9.3
-==============================
+= Release notes for Gerrit 2.9.3
 
 Download:
 link:https://www.gerritcodereview.com/download/gerrit-2.9.3.war[
 https://www.gerritcodereview.com/download/gerrit-2.9.3.war]
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* There are no schema changes from
 link:ReleaseNotes-2.9.2.html[2.9.2], but when upgrading from an existing site
@@ -28,8 +26,7 @@
 startup failure described in
 link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
 
-Bug Fixes
----------
+== Bug Fixes
 
 *Downgrade SSHD to 0.9.0-4-g5967cfd*
 
diff --git a/ReleaseNotes/ReleaseNotes-2.9.4.txt b/ReleaseNotes/ReleaseNotes-2.9.4.txt
index 5063489..e2ad6ac 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.4.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.4.txt
@@ -1,12 +1,10 @@
-Release notes for Gerrit 2.9.4
-==============================
+= Release notes for Gerrit 2.9.4
 
 Download:
 link:https://www.gerritcodereview.com/download/gerrit-2.9.4.war[
 https://www.gerritcodereview.com/download/gerrit-2.9.4.war]
 
-Important Notes
----------------
+== Important Notes
 
 *WARNING:* There are no schema changes from
 link:ReleaseNotes-2.9.3.html[2.9.3], but when upgrading from an existing site
@@ -28,8 +26,7 @@
 startup failure described in
 link:https://code.google.com/p/gerrit/issues/detail?id=3084[Issue 3084].
 
-Bug Fixes
----------
+== Bug Fixes
 
 * Update JGit to 3.4.2.201412180340-r
 +
diff --git a/ReleaseNotes/ReleaseNotes-2.9.txt b/ReleaseNotes/ReleaseNotes-2.9.txt
index 3387f98..c026914 100644
--- a/ReleaseNotes/ReleaseNotes-2.9.txt
+++ b/ReleaseNotes/ReleaseNotes-2.9.txt
@@ -1,5 +1,4 @@
-Release notes for Gerrit 2.9
-============================
+= Release notes for Gerrit 2.9
 
 
 Gerrit 2.9 is now available:
@@ -20,8 +19,7 @@
 link:ReleaseNotes-2.8.6.1.html[Gerrit 2.8.6.1].
 These bug fixes are *not* listed in these release notes.
 
-Important Notes
----------------
+== Important Notes
 
 
 *WARNING:* This release contains schema changes.  To upgrade:
@@ -98,8 +96,7 @@
 the site's `plugins` folder.
 
 
-Release Highlights
-------------------
+== Release Highlights
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2065[Issue 2065]:
@@ -116,22 +113,18 @@
 to the old change screen.
 
 
-New Features
-------------
+== New Features
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 
-Global
-^^^^^^
+==== Global
 
 * Project links by default link to the project dashboard.
 
 
-New Change Screen
-^^^^^^^^^^^^^^^^^
+==== New Change Screen
 
 
 * The new change screen is now the default change screen.
@@ -197,8 +190,7 @@
 New copy-to-clipboard button for commit ID.
 
 
-New Side-by-Side Diff Screen
-^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+==== New Side-by-Side Diff Screen
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=348[Issue 348]:
 The lines of a patch file are linkable.
@@ -220,8 +212,7 @@
 The full file path is shown.
 
 
-Change List / Dashboards
-^^^^^^^^^^^^^^^^^^^^^^^^
+==== Change List / Dashboards
 
 * The `Status` column shows `Merge Conflict` for changes that are not
 mergeable.
@@ -240,8 +231,7 @@
 without the `limit` operator.
 
 
-Project Screens
-^^^^^^^^^^^^^^^
+==== Project Screens
 
 * The general project screen provides a copyable clone command that
 automatically installs the `commit-msg` hook.
@@ -263,15 +253,13 @@
 Run Garbage Collection] global capability.
 
 
-User Preferences
-^^^^^^^^^^^^^^^^
+==== User Preferences
 
 * Users can choose the UK date format to render dates and timestamps in
 the UI.
 
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 * Support for query via the SQL index is removed. The usage of
 a secondary index is now mandatory.
@@ -280,8 +268,7 @@
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/pgm-reindex.html[
 reindex] program.
 
-ssh
-~~~
+=== ssh
 
 * New `--notify` option on the
 link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/cmd-review.html[
@@ -305,12 +292,10 @@
 New link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/cmd-create-branch.html[
 create-branch] command.
 
-REST API
-~~~~~~~~
+=== REST API
 
 
-Changes
-^^^^^^^
+==== Changes
 
 
 [[sortkey-deprecation]]
@@ -325,22 +310,19 @@
 Queries with sortkeys are still supported against old index versions, to enable
 online reindexing while clients have an older JS version.
 
-Projects
-^^^^^^^^
+==== Projects
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/rest-api-projects.html#get-content[
 Get content of a file from HEAD of a branch].
 
-Documentation
-^^^^^^^^^^^^^
+==== Documentation
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/rest-api-documentation.html#search-documentation.html[
 Search documentation].
 
-Access Rights
-~~~~~~~~~~~~~
+=== Access Rights
 
 
 * New link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/access-control.html#capability_viewAllAccounts[
@@ -374,8 +356,7 @@
 Gerrit but not in LDAP are authenticated with their HTTP password from
 the Gerrit database.
 
-Search
-~~~~~~
+=== Search
 
 * New link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/user-search.html#mergeable[
 is:mergeable] search operator.
@@ -411,8 +392,7 @@
 ** `p` = `project`
 ** `f` = `file`
 
-Daemon
-~~~~~~
+=== Daemon
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/dev-inspector.html[
@@ -421,8 +401,7 @@
 New `-s` option is added to the Daemon to start an interactive Jython shell for inspection and
 troubleshooting of live data of the Gerrit instance.
 
-Documentation
-~~~~~~~~~~~~~
+=== Documentation
 
 
 * The documentation is now
@@ -442,8 +421,7 @@
 Newly structured documentation index].
 
 
-Configuration
-~~~~~~~~~~~~~
+=== Configuration
 
 * New init step for installing the `Verified` label.
 
@@ -463,8 +441,7 @@
 Allow the text of the "Report Bug" link to be configured.
 
 
-Misc
-~~~~
+=== Misc
 
 * The removal of reviewers and their votes is recorded as a change
 message.
@@ -478,8 +455,7 @@
 * Stable CSS class names.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
 
 * Plugin API to invoke the REST API.
@@ -499,8 +475,7 @@
 Remote plugin administration is by default disabled].
 
 
-Extension Points
-^^^^^^^^^^^^^^^^
+==== Extension Points
 
 
 * Extension point to provide a "Message Of The Day".
@@ -531,8 +506,7 @@
 DataSource Interception].
 
 
-JavaScript Plugins
-^^^^^^^^^^^^^^^^^^
+==== JavaScript Plugins
 
 
 * link:https://gerrit-documentation.storage.googleapis.com/Documentation/2.9/js-api.html#self_on[
@@ -545,19 +519,16 @@
 insert arbitrary HTML fragments from plugins.
 
 
-Bug Fixes
----------
+== Bug Fixes
 
 
-Access Rights
-~~~~~~~~~~~~~
+=== Access Rights
 
 
 * Fix possibility to overcome BLOCK permissions.
 
 
-Web UI
-~~~~~~
+=== Web UI
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2652[Issue 2652]:
@@ -622,8 +593,7 @@
 Fix copying from copyable label in Safari.
 
 
-Secondary Index
-~~~~~~~~~~~~~~~
+=== Secondary Index
 
 * Fix Online Reindexing.
 
@@ -640,16 +610,14 @@
 Reindex change after updating commit message.
 
 
-REST
-~~~~
+=== REST
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2568[Issue 2568]:
 Update description file during `PUT /projects/{name}/config`.
 
 
-SSH
-~~~
+=== SSH
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2516[Issue 2516]:
@@ -659,8 +627,7 @@
 Clarify for review command when `--verified` can be used.
 
 
-Plugins
-~~~~~~~
+=== Plugins
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2551[Issue 2551]:
@@ -670,16 +637,14 @@
 Respect servlet context path in URL for top menu items.
 
 
-Other
-~~~~~
+=== Other
 
 
 * link:http://code.google.com/p/gerrit/issues/detail?id=2382[Issue 2382]:
 Clean left over data migration after removal of TrackingIds table.
 
 
-Upgrades
---------
+== Upgrades
 
 * Update JGit to 3.4.0.201405051725-m7
 +
@@ -704,11 +669,9 @@
 * Update GWT to 2.6.0
 
 
-Plugins
--------
+== Plugins
 
-Replication
-~~~~~~~~~~~
+=== Replication
 
 * Default push refSpec is changed to `refs/*:refs/*` (non-forced push).
 +
@@ -728,8 +691,7 @@
 * Configuration changes can be detected and replication is
 automatically restarted.
 
-Issue Tracker System plugins
-~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+=== Issue Tracker System plugins
 
 *WARNING:* The `hooks-*` plugins (`plugins/hooks-bugzilla`,
 `plugins/hooks-jira` and `plugins/hooks-rtc`) are deprecated with
diff --git a/ReleaseNotes/asciidoc.conf b/ReleaseNotes/asciidoc.conf
deleted file mode 100644
index 527f58f..0000000
--- a/ReleaseNotes/asciidoc.conf
+++ /dev/null
@@ -1,19 +0,0 @@
-[attributes]
-asterisk=&#42;
-plus=&#43;
-caret=&#94;
-startsb=&#91;
-endsb=&#93;
-tilde=&#126;
-max-width=55em
-
-[specialsections]
-GERRIT=gerrituplink
-
-[gerrituplink]
-<hr style="
-  height: 2px;
-  color: silver;
-  margin-top: 1.2em;
-  margin-bottom: 0.5em;
-">
diff --git a/ReleaseNotes/config.defs b/ReleaseNotes/config.defs
new file mode 100644
index 0000000..86b7603
--- /dev/null
+++ b/ReleaseNotes/config.defs
@@ -0,0 +1,14 @@
+def release_notes_attributes():
+  return [
+    'toc',
+    'newline="\\n"',
+    'asterisk="&#42;"',
+    'plus="&#43;"',
+    'caret="&#94;"',
+    'startsb="&#91;"',
+    'endsb="&#93;"',
+    'tilde="&#126;"',
+    'last-update-label!',
+    'stylesheet=DEFAULT',
+    'linkcss=true',
+  ]
diff --git a/ReleaseNotes/index.txt b/ReleaseNotes/index.txt
index 4cab151..49491ac 100644
--- a/ReleaseNotes/index.txt
+++ b/ReleaseNotes/index.txt
@@ -1,21 +1,18 @@
-Gerrit Code Review - Release Notes
-==================================
+= Gerrit Code Review - Release Notes
 
-[[2_13]]
-Version 2.13.x
---------------
+[[s2_13]]
+== Version 2.13.x
 * link:ReleaseNotes-2.13.html[2.13]
 
-[[2_12]]
-Version 2.12.x
---------------
+[[s2_12]]
+== Version 2.12.x
 * link:ReleaseNotes-2.12.2.html[2.12.2]
 * link:ReleaseNotes-2.12.1.html[2.12.1]
 * link:ReleaseNotes-2.12.html[2.12]
 
-[[2_11]]
-Version 2.11.x
---------------
+[[s2_11]]
+== Version 2.11.x
+* link:ReleaseNotes-2.11.9.html[2.11.9]
 * link:ReleaseNotes-2.11.8.html[2.11.8]
 * link:ReleaseNotes-2.11.7.html[2.11.7]
 * link:ReleaseNotes-2.11.6.html[2.11.6]
@@ -26,9 +23,8 @@
 * link:ReleaseNotes-2.11.1.html[2.11.1]
 * link:ReleaseNotes-2.11.html[2.11]
 
-[[2_10]]
-Version 2.10.x
---------------
+[[s2_10]]
+== Version 2.10.x
 * link:ReleaseNotes-2.10.7.html[2.10.7]
 * link:ReleaseNotes-2.10.6.html[2.10.6]
 * link:ReleaseNotes-2.10.5.html[2.10.5]
@@ -39,18 +35,16 @@
 * link:ReleaseNotes-2.10.1.html[2.10.1]
 * link:ReleaseNotes-2.10.html[2.10]
 
-[[2_9]]
-Version 2.9.x
--------------
+[[s2_9]]
+== Version 2.9.x
 * link:ReleaseNotes-2.9.4.html[2.9.4]
 * link:ReleaseNotes-2.9.3.html[2.9.3]
 * link:ReleaseNotes-2.9.2.html[2.9.2]
 * link:ReleaseNotes-2.9.1.html[2.9.1]
 * link:ReleaseNotes-2.9.html[2.9]
 
-[[2_8]]
-Version 2.8.x
--------------
+[[s2_8]]
+== Version 2.8.x
 * link:ReleaseNotes-2.8.6.1.html[2.8.6.1]
 * link:ReleaseNotes-2.8.6.html[2.8.6]
 * link:ReleaseNotes-2.8.5.html[2.8.5]
@@ -60,20 +54,17 @@
 * link:ReleaseNotes-2.8.1.html[2.8.1]
 * link:ReleaseNotes-2.8.html[2.8]
 
-[[2_7]]
-Version 2.7.x
--------------
+[[s2_7]]
+== Version 2.7.x
 * link:ReleaseNotes-2.7.html[2.7]
 
-[[2_6]]
-Version 2.6.x
--------------
+[[s2_6]]
+== Version 2.6.x
 * link:ReleaseNotes-2.6.1.html[2.6.1]
 * link:ReleaseNotes-2.6.html[2.6]
 
-[[2_5]]
-Version 2.5.x
--------------
+[[s2_5]]
+== Version 2.5.x
 * link:ReleaseNotes-2.5.6.html[2.5.6]
 * link:ReleaseNotes-2.5.5.html[2.5.5]
 * link:ReleaseNotes-2.5.4.html[2.5.4]
@@ -82,33 +73,29 @@
 * link:ReleaseNotes-2.5.1.html[2.5.1]
 * link:ReleaseNotes-2.5.html[2.5]
 
-[[2_4]]
-Version 2.4.x
--------------
+[[s2_4]]
+== Version 2.4.x
 * link:ReleaseNotes-2.4.4.html[2.4.4]
 * link:ReleaseNotes-2.4.3.html[2.4.3]
 * link:ReleaseNotes-2.4.2.html[2.4.2]
 * link:ReleaseNotes-2.4.1.html[2.4.1]
 * link:ReleaseNotes-2.4.html[2.4]
 
-[[2_3]]
-Version 2.3.x
--------------
+[[s2_3]]
+== Version 2.3.x
 * link:ReleaseNotes-2.3.1.html[2.3.1]
 * link:ReleaseNotes-2.3.html[2.3]
 
-[[2_2]]
-Version 2.2.x
--------------
+[[s2_2]]
+== Version 2.2.x
 * link:ReleaseNotes-2.2.2.2.html[2.2.2.2]
 * link:ReleaseNotes-2.2.2.1.html[2.2.2.1]
 * link:ReleaseNotes-2.2.2.html[2.2.2]
 * link:ReleaseNotes-2.2.1.html[2.2.1]
 * link:ReleaseNotes-2.2.0.html[2.2.0]
 
-[[2_1]]
-Version 2.1.x
--------------
+[[s2_1]]
+== Version 2.1.x
 * link:ReleaseNotes-2.1.10.html[2.1.10]
 * link:ReleaseNotes-2.1.9.html[2.1.9]
 * link:ReleaseNotes-2.1.8.html[2.1.8]
@@ -129,9 +116,8 @@
 * link:ReleaseNotes-2.1.1.html[2.1.1]
 * link:ReleaseNotes-2.1.html[2.1]
 
-[[2_0]]
-Version 2.0.x
--------------
+[[s2_0]]
+== Version 2.0.x
 * link:ReleaseNotes-2.0.24.html[2.0.24.2]
 * link:ReleaseNotes-2.0.24.html[2.0.24.1]
 * link:ReleaseNotes-2.0.24.html[2.0.24]
diff --git a/contrib/populate-fixture-data.py b/contrib/populate-fixture-data.py
new file mode 100644
index 0000000..c35f82c
--- /dev/null
+++ b/contrib/populate-fixture-data.py
@@ -0,0 +1,298 @@
+#!/usr/bin/env python
+# Copyright (C) 2016 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.
+
+"""
+This script will populate an empty standard Gerrit instance with some
+data for local testing.
+
+This script requires 'requests'. If you do not have this module, run
+'pip3 install requests' to install it.
+
+TODO(hiesel): Make real git commits instead of empty changes
+TODO(hiesel): Add comments
+"""
+
+import atexit
+import json
+import os
+import random
+import shutil
+import subprocess
+import tempfile
+
+import requests
+import requests.auth
+
+DEFAULT_TMP_PATH = "/tmp"
+TMP_PATH = ""
+BASE_URL = "http://localhost:8080/a/"
+ACCESS_URL = BASE_URL + "access/"
+ACCOUNTS_URL = BASE_URL + "accounts/"
+CHANGES_URL = BASE_URL + "changes/"
+CONFIG_URL = BASE_URL + "config/"
+GROUPS_URL = BASE_URL + "groups/"
+PLUGINS_URL = BASE_URL + "plugins/"
+PROJECTS_URL = BASE_URL + "projects/"
+
+ADMIN_DIGEST = requests.auth.HTTPDigestAuth("admin", "secret")
+
+# GROUP_ADMIN stores a GroupInfo for the admin group (see Gerrit rest docs)
+# In addition, GROUP_ADMIN["name"] stores the admin group"s name.
+GROUP_ADMIN = {}
+
+HEADERS = {"Content-Type": "application/json", "charset": "UTF-8"}
+
+# Random names from US Census Data
+FIRST_NAMES = [
+  "Casey", "Yesenia", "Shirley", "Tara", "Wanda", "Sheryl", "Jaime", "Elaine",
+  "Charlotte", "Carly", "Bonnie", "Kirsten", "Kathryn", "Carla", "Katrina",
+  "Melody", "Suzanne", "Sandy", "Joann", "Kristie", "Sally", "Emma", "Susan",
+  "Amanda", "Alyssa", "Patty", "Angie", "Dominique", "Cynthia", "Jennifer",
+  "Theresa", "Desiree", "Kaylee", "Maureen", "Jeanne", "Kellie", "Valerie",
+  "Nina", "Judy", "Diamond", "Anita", "Rebekah", "Stefanie", "Kendra", "Erin",
+  "Tammie", "Tracey", "Bridget", "Krystal", "Jasmin", "Sonia", "Meghan",
+  "Rebecca", "Jeanette", "Meredith", "Beverly", "Natasha", "Chloe", "Selena",
+  "Teresa", "Sheena", "Cassandra", "Rhonda", "Tami", "Jodi", "Shelly", "Angela",
+  "Kimberly", "Terry", "Joanna", "Isabella", "Lindsey", "Loretta", "Dana",
+  "Veronica", "Carolyn", "Laura", "Karen", "Dawn", "Alejandra", "Cassie",
+  "Lorraine", "Yolanda", "Kerry", "Stephanie", "Caitlin", "Melanie", "Kerri",
+  "Doris", "Sandra", "Beth", "Carol", "Vicki", "Shelia", "Bethany", "Rachael",
+  "Donna", "Alexandra", "Barbara", "Ana", "Jillian", "Ann", "Rachel", "Lauren",
+  "Hayley", "Misty", "Brianna", "Tanya", "Danielle", "Courtney", "Jacqueline",
+  "Becky", "Christy", "Alisha", "Phyllis", "Faith", "Jocelyn", "Nancy",
+  "Gloria", "Kristen", "Evelyn", "Julie", "Julia", "Kara", "Chelsey", "Cassidy",
+  "Jean", "Chelsea", "Jenny", "Diana", "Haley", "Kristine", "Kristina", "Erika",
+  "Jenna", "Alison", "Deanna", "Abigail", "Melissa", "Sierra", "Linda",
+  "Monica", "Tasha", "Traci", "Yvonne", "Tracy", "Marie", "Maria", "Michaela",
+  "Stacie", "April", "Morgan", "Cathy", "Darlene", "Cristina", "Emily"
+  "Ian", "Russell", "Phillip", "Jay", "Barry", "Brad", "Frederick", "Fernando",
+  "Timothy", "Ricardo", "Bernard", "Daniel", "Ruben", "Alexis", "Kyle", "Malik",
+  "Norman", "Kent", "Melvin", "Stephen", "Daryl", "Kurt", "Greg", "Alex",
+  "Mario", "Riley", "Marvin", "Dan", "Steven", "Roberto", "Lucas", "Leroy",
+  "Preston", "Drew", "Fred", "Casey", "Wesley", "Elijah", "Reginald", "Joel",
+  "Christopher", "Jacob", "Luis", "Philip", "Mark", "Rickey", "Todd", "Scott",
+  "Terrence", "Jim", "Stanley", "Bobby", "Thomas", "Gabriel", "Tracy", "Marcus",
+  "Peter", "Michael", "Calvin", "Herbert", "Darryl", "Billy", "Ross", "Dustin",
+  "Jaime", "Adam", "Henry", "Xavier", "Dominic", "Lonnie", "Danny", "Victor",
+  "Glen", "Perry", "Jackson", "Grant", "Gerald", "Garrett", "Alejandro",
+  "Eddie", "Alan", "Ronnie", "Mathew", "Dave", "Wayne", "Joe", "Craig",
+  "Terry", "Chris", "Randall", "Parker", "Francis", "Keith", "Neil", "Caleb",
+  "Jon", "Earl", "Taylor", "Bryce", "Brady", "Max", "Sergio", "Leon", "Gene",
+  "Darin", "Bill", "Edgar", "Antonio", "Dalton", "Arthur", "Austin", "Cristian",
+  "Kevin", "Omar", "Kelly", "Aaron", "Ethan", "Tom", "Isaac", "Maurice",
+  "Gilbert", "Hunter", "Willie", "Harry", "Dale", "Darius", "Jerome", "Jason",
+  "Harold", "Kerry", "Clarence", "Gregg", "Shane", "Eduardo", "Micheal",
+  "Howard", "Vernon", "Rodney", "Anthony", "Levi", "Larry", "Franklin", "Jimmy",
+  "Jonathon", "Carl",
+]
+
+LAST_NAMES = [
+  "Savage", "Hendrix", "Moon", "Larsen", "Rocha", "Burgess", "Bailey", "Farley",
+  "Moses", "Schmidt", "Brown", "Hoover", "Klein", "Jennings", "Braun", "Rangel",
+  "Casey", "Dougherty", "Hancock", "Wolf", "Henry", "Thomas", "Bentley",
+  "Barnett", "Kline", "Pitts", "Rojas", "Sosa", "Paul", "Hess", "Chase",
+  "Mckay", "Bender", "Colins", "Montoya", "Townsend", "Potts", "Ayala", "Avery",
+  "Sherman", "Tapia", "Hamilton", "Ferguson", "Huang", "Hooper", "Zamora",
+  "Logan", "Lloyd", "Quinn", "Monroe", "Brock", "Ibarra", "Fowler", "Weiss",
+  "Montgomery", "Diaz", "Dixon", "Olson", "Robertson", "Arias", "Benjamin",
+  "Abbott", "Stein", "Schroeder", "Beck", "Velasquez", "Barber", "Nichols",
+  "Ortiz", "Burns", "Moody", "Stokes", "Wilcox", "Rush", "Michael", "Kidd",
+  "Rowland", "Mclean", "Saunders", "Chung", "Newton", "Potter", "Hickman",
+  "Ray", "Larson", "Figueroa", "Duncan", "Sparks", "Rose", "Hodge", "Huynh",
+  "Joseph", "Morales", "Beasley", "Mora", "Fry", "Ross", "Novak", "Hahn",
+  "Wise", "Knight", "Frederick", "Heath", "Pollard", "Vega", "Mcclain",
+  "Buckley", "Conrad", "Cantrell", "Bond", "Mejia", "Wang", "Lewis", "Johns",
+  "Mcknight", "Callahan", "Reynolds", "Norris", "Burnett", "Carey", "Jacobson",
+  "Oneill", "Oconnor", "Leonard", "Mckenzie", "Hale", "Delgado", "Spence",
+  "Brandt", "Obrien", "Bowman", "James", "Avila", "Roberts", "Barker", "Cohen",
+  "Bradley", "Prince", "Warren", "Summers", "Little", "Caldwell", "Garrett",
+  "Hughes", "Norton", "Burke", "Holden", "Merritt", "Lee", "Frank", "Wiley",
+  "Ho", "Weber", "Keith", "Winters", "Gray", "Watts", "Brady", "Aguilar",
+  "Nicholson", "David", "Pace", "Cervantes", "Davis", "Baxter", "Sanchez",
+  "Singleton", "Taylor", "Strickland", "Glenn", "Valentine", "Roy", "Cameron",
+  "Beard", "Norman", "Fritz", "Anthony", "Koch", "Parrish", "Herman", "Hines",
+  "Sutton", "Gallegos", "Stephenson", "Lozano", "Franklin", "Howe", "Bauer",
+  "Love", "Ali", "Ellison", "Lester", "Guzman", "Jarvis", "Espinoza",
+  "Fletcher", "Burton", "Woodard", "Peterson", "Barajas", "Richard", "Bryan",
+  "Goodman", "Cline", "Rowe", "Faulkner", "Crawford", "Mueller", "Patterson",
+  "Hull", "Walton", "Wu", "Flores", "York", "Dickson", "Barnes", "Fisher",
+  "Strong", "Juarez", "Fitzgerald", "Schmitt", "Blevins", "Villa", "Sullivan",
+  "Velazquez", "Horton", "Meadows", "Riley", "Barrera", "Neal", "Mendez",
+  "Mcdonald", "Floyd", "Lynch", "Mcdowell", "Benson", "Hebert", "Livingston",
+  "Davies", "Richardson", "Vincent", "Davenport", "Osborn", "Mckee", "Marshall",
+  "Ferrell", "Martinez", "Melton", "Mercer", "Yoder", "Jacobs", "Mcdaniel",
+  "Mcmillan", "Peters", "Atkinson", "Wood", "Briggs", "Valencia", "Chandler",
+  "Rios", "Hunter", "Bean", "Hicks", "Hays", "Lucero", "Malone", "Waller",
+  "Banks", "Myers", "Mitchell", "Grimes", "Houston", "Hampton", "Trujillo",
+  "Perkins", "Moran", "Welch", "Contreras", "Montes", "Ayers", "Hayden",
+  "Daniel", "Weeks", "Porter", "Gill", "Mullen", "Nolan", "Dorsey", "Crane",
+  "Estes", "Lam", "Wells", "Cisneros", "Giles", "Watson", "Vang", "Scott",
+  "Knox", "Hanna", "Fields",
+]
+
+
+def clean(json_string):
+  # Strip JSON XSS Tag
+  json_string = json_string.strip()
+  if json_string.startswith(")]}'"):
+    return json_string[5:]
+  return json_string
+
+
+def digest_auth(user):
+  return requests.auth.HTTPDigestAuth(user["username"], user["http_password"])
+
+
+def fetch_admin_group():
+  global GROUP_ADMIN
+  # Get admin group
+  r = json.loads(clean(requests.get(GROUPS_URL + "?suggest=ad&p=All-Projects",
+                                    headers=HEADERS,
+                                    auth=ADMIN_DIGEST).text))
+  admin_group_name = r.keys()[0]
+  GROUP_ADMIN = r[admin_group_name]
+  GROUP_ADMIN["name"] = admin_group_name
+
+
+def generate_random_text():
+  return " ".join([random.choice("lorem ipsum "
+                                 "doleret delendam "
+                                 "\n esse".split(" ")) for _ in xrange(1, 100)])
+
+
+def set_up():
+  global TMP_PATH
+  TMP_PATH = tempfile.mkdtemp()
+  atexit.register(clean_up)
+  os.makedirs(TMP_PATH + "/ssh")
+  os.makedirs(TMP_PATH + "/repos")
+  fetch_admin_group()
+
+
+def get_random_users(num_users):
+  users = [(f, l) for f in FIRST_NAMES for l in LAST_NAMES][:num_users]
+  names = []
+  for u in users:
+    names.append({"firstname": u[0],
+                  "lastname": u[1],
+                  "name": u[0] + " " + u[1],
+                  "username": u[0] + u[1],
+                  "email": u[0] + "." + u[1] + "@gmail.com",
+                  "http_password": "secret",
+                  "groups": []})
+  return names
+
+
+def generate_ssh_keys(gerrit_users):
+  for user in gerrit_users:
+    key_file = TMP_PATH + "/ssh/" + user["username"] + ".key"
+    subprocess.check_output(["ssh-keygen", "-f", key_file, "-N", ""])
+    with open(key_file + ".pub", "r") as f:
+      user["ssh_key"] = f.read()
+
+
+def create_gerrit_groups():
+  groups = [
+    {"name": "iOS-Maintainers", "description": "iOS Maintainers",
+     "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+     "owner_id": GROUP_ADMIN["id"]},
+    {"name": "Android-Maintainers", "description": "Android Maintainers",
+     "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+     "owner_id": GROUP_ADMIN["id"]},
+    {"name": "Backend-Maintainers", "description": "Backend Maintainers",
+     "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+     "owner_id": GROUP_ADMIN["id"]},
+    {"name": "Script-Maintainers", "description": "Script Maintainers",
+     "visible_to_all": True, "owner": GROUP_ADMIN["name"],
+     "owner_id": GROUP_ADMIN["id"]},
+    {"name": "Security-Team", "description": "Sec Team",
+     "visible_to_all": False, "owner": GROUP_ADMIN["name"],
+     "owner_id": GROUP_ADMIN["id"]}]
+  for g in groups:
+    requests.put(GROUPS_URL + g["name"],
+                 json.dumps(g),
+                 headers=HEADERS,
+                 auth=ADMIN_DIGEST)
+  return [g["name"] for g in groups]
+
+
+def create_gerrit_projects(owner_groups):
+  projects = [
+    {"id": "android", "name": "Android", "parent": "All-Projects",
+     "branches": ["master"], "description": "Our android app.",
+     "owners": [owner_groups[0]], "create_empty_commit": True},
+    {"id": "ios", "name": "iOS", "parent": "All-Projects",
+     "branches": ["master"], "description": "Our ios app.",
+     "owners": [owner_groups[1]], "create_empty_commit": True},
+    {"id": "backend", "name": "Backend", "parent": "All-Projects",
+     "branches": ["master"], "description": "Our awesome backend.",
+     "owners": [owner_groups[2]], "create_empty_commit": True},
+    {"id": "scripts", "name": "Scripts", "parent": "All-Projects",
+     "branches": ["master"], "description": "some small scripts.",
+     "owners": [owner_groups[3]], "create_empty_commit": True}]
+  for p in projects:
+    requests.put(PROJECTS_URL + p["name"],
+                 json.dumps(p),
+                 headers=HEADERS,
+                 auth=ADMIN_DIGEST)
+  return [p["name"] for p in projects]
+
+
+def create_gerrit_users(gerrit_users):
+  for user in gerrit_users:
+    requests.put(ACCOUNTS_URL + user["username"],
+                 json.dumps(user),
+                 headers=HEADERS,
+                 auth=ADMIN_DIGEST)
+
+
+def create_change(user, project_name):
+  random_commit_message = generate_random_text()
+  change = {
+    "project": project_name,
+    "subject": random_commit_message.split("\n")[0],
+    "branch": "master",
+    "status": "NEW",
+  }
+  requests.post(CHANGES_URL,
+                json.dumps(change),
+                headers=HEADERS,
+                auth=digest_auth(user))
+
+
+def clean_up():
+  shutil.rmtree(TMP_PATH)
+
+
+def main():
+  set_up()
+  gerrit_users = get_random_users(100)
+
+  group_names = create_gerrit_groups()
+  for idx, u in enumerate(gerrit_users):
+    u["groups"].append(group_names[idx % len(group_names)])
+    if idx % 5 == 0:
+      # Also add to security group
+      u["groups"].append(group_names[4])
+
+  generate_ssh_keys(gerrit_users)
+  create_gerrit_users(gerrit_users)
+
+  project_names = create_gerrit_projects(group_names)
+
+  for idx, u in enumerate(gerrit_users):
+    create_change(u, project_names[4 * idx / len(gerrit_users)])
+
+main()
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
index 7dc4cda..dfa0336c 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/api/change/ChangeIT.java
@@ -1090,7 +1090,7 @@
 
   @Test
   public void noteDbCommitsOnPatchSetCreation() throws Exception {
-    assume().that(notesMigration.enabled()).isTrue();
+    assume().that(notesMigration.readChanges()).isTrue();
 
     PushOneCommit.Result r = createChange();
     pushFactory.create(db, admin.getIdent(), testRepo, PushOneCommit.SUBJECT,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
index fd7b359..e63b28ba 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractPushForReview.java
@@ -456,8 +456,8 @@
 
   @Test
   public void testPushForMasterWithHashtagsNoteDbDisabled() throws Exception {
-    // push with hashtags should fail when noteDb is disabled
-    assume().that(notesMigration.enabled()).isFalse();
+    // Push with hashtags should fail when reading from NoteDb is disabled.
+    assume().that(notesMigration.readChanges()).isFalse();
     PushOneCommit.Result r = pushTo("refs/for/master%hashtag=tag1");
     r.assertErrorStatus("cannot add hashtags; noteDb is disabled");
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
index bb1a656..b504d25 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/AbstractSubmoduleSubscription.java
@@ -17,6 +17,7 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.SubscribeSection;
 import com.google.gerrit.reviewdb.client.Project;
@@ -35,14 +36,19 @@
 import java.util.concurrent.atomic.AtomicInteger;
 
 public abstract class AbstractSubmoduleSubscription extends AbstractDaemonTest {
-  protected TestRepository<?> createProjectWithPush(String name)
-      throws Exception {
-    Project.NameKey project = createProject(name);
+  protected TestRepository<?> createProjectWithPush(String name,
+      @Nullable Project.NameKey parent) throws Exception {
+    Project.NameKey project = createProject(name, parent);
     grant(Permission.PUSH, project, "refs/heads/*");
     grant(Permission.SUBMIT, project, "refs/for/refs/heads/*");
     return cloneProject(project);
   }
 
+  protected TestRepository<?> createProjectWithPush(String name)
+      throws Exception {
+    return createProjectWithPush(name, null);
+  }
+
   private static AtomicInteger contentCounter = new AtomicInteger(0);
 
   protected ObjectId pushChangeTo(TestRepository<?> repo, String ref,
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
index e69a647..4af4a41 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsIT.java
@@ -17,8 +17,12 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.GerritConfig;
+import com.google.gerrit.acceptance.NoHttpd;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.testutil.ConfigSuite;
 
 import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lib.Config;
 import org.eclipse.jgit.lib.ObjectId;
 import org.eclipse.jgit.revwalk.RevCommit;
 import org.eclipse.jgit.revwalk.RevTree;
@@ -26,7 +30,12 @@
 import org.eclipse.jgit.transport.RefSpec;
 import org.junit.Test;
 
+@NoHttpd
 public class SubmoduleSubscriptionsIT extends AbstractSubmoduleSubscription {
+  @ConfigSuite.Config
+  public static Config submitWholeTopicEnabled() {
+    return submitWholeTopicEnabledConfig();
+  }
 
   @Test
   @GerritConfig(name = "submodule.enableSuperProjectSubscriptions", value = "false")
@@ -34,9 +43,11 @@
     TestRepository<?> superRepo = createProjectWithPush("super-project");
     TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
 
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     pushChangeTo(subRepo, "master");
-    assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
   }
 
   @Test
@@ -46,7 +57,8 @@
     allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
         "super-project", "refs/heads/master");
 
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     pushChangeTo(subRepo, "master");
     ObjectId subHEAD = pushChangeTo(subRepo, "master");
     expectToHaveSubmoduleState(superRepo, "master",
@@ -61,7 +73,8 @@
         "super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     ObjectId subHEAD = pushChangeTo(subRepo, "master");
     expectToHaveSubmoduleState(superRepo, "master",
         "subscribed-to-project", subHEAD);
@@ -78,8 +91,10 @@
     pushChangeTo(superRepo, "branch");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
-    createSubmoduleSubscription(superRepo, "branch", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "branch",
+        "subscribed-to-project", "master");
 
     ObjectId subHEAD = pushChangeTo(subRepo, "master");
 
@@ -102,8 +117,10 @@
     pushChangeTo(subRepo, "branch");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
-    createSubmoduleSubscription(superRepo, "branch", "subscribed-to-project", "branch");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "branch",
+        "subscribed-to-project", "branch");
 
     ObjectId subHEAD1 = pushChangeTo(subRepo, "master");
     ObjectId subHEAD2 = pushChangeTo(subRepo, "branch");
@@ -114,7 +131,8 @@
         "subscribed-to-project", subHEAD2);
 
     // Now test that cross subscriptions do not work:
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "branch");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "branch");
     ObjectId subHEAD3 = pushChangeTo(subRepo, "branch");
 
     expectToHaveSubmoduleState(superRepo, "master",
@@ -132,7 +150,8 @@
         "super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
 
     // The first update doesn't include any commit messages
     ObjectId subRepoId = pushChangeTo(subRepo, "master");
@@ -157,7 +176,8 @@
         "super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     ObjectId subHEAD = pushChangeTo(subRepo, "master");
 
     // The first update doesn't include the rev log
@@ -187,7 +207,8 @@
         "super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
 
     pushChangeTo(subRepo, "master");
     ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -205,14 +226,16 @@
   }
 
   @Test
-  public void testSubscriptionUnsubscribeByDeletingGitModules() throws Exception {
+  public void testSubscriptionUnsubscribeByDeletingGitModules()
+      throws Exception {
     TestRepository<?> superRepo = createProjectWithPush("super-project");
     TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
     allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
         "super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
 
     pushChangeTo(subRepo, "master");
     ObjectId subHEADbeforeUnsubscribing = pushChangeTo(subRepo, "master");
@@ -236,7 +259,8 @@
     allowSubmoduleSubscription("subscribed-to-project", "refs/heads/foo",
         "super-project", "refs/heads/master");
 
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "foo");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "foo");
     ObjectId subFoo = pushChangeTo(subRepo, "foo");
     pushChangeTo(subRepo, "master");
 
@@ -254,16 +278,18 @@
         "subscribed-to-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    pushChangeTo(superRepo, "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     createSubmoduleSubscription(subRepo, "master", "super-project", "master");
 
-    ObjectId subHEAD = pushChangeTo(subRepo, "master");
+    pushChangeTo(subRepo, "master");
     pushChangeTo(superRepo, "master");
 
-    expectToHaveSubmoduleState(superRepo, "master",
-        "subscribed-to-project", subHEAD);
-
-    assertThat(hasSubmodule(subRepo, "master", "super-project")).isFalse();
+    assertThat(hasSubmodule(subRepo, "master",
+        "super-project")).isFalse();
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
   }
 
 
@@ -273,9 +299,11 @@
     TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     pushChangeTo(subRepo, "master");
-    assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
   }
 
   @Test
@@ -286,9 +314,11 @@
         "wrong-super-project", "refs/heads/master");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     pushChangeTo(subRepo, "master");
-    assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
   }
 
   @Test
@@ -299,9 +329,28 @@
         "super-project", "refs/heads/wrong-branch");
 
     pushChangeTo(subRepo, "master");
-    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
     pushChangeTo(subRepo, "master");
-    assertThat(hasSubmodule(superRepo, "master", "subscribed-to-project")).isFalse();
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
+  }
+
+  @Test
+  public void testSubscriptionInheritACL() throws Exception {
+    createProjectWithPush("config-repo");
+    TestRepository<?> superRepo = createProjectWithPush("super-project",
+        new Project.NameKey(name("config-repo")));
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+    allowSubmoduleSubscription("config-repo", "refs/heads/master",
+        "super-project", "refs/heads/wrong-branch");
+
+    pushChangeTo(subRepo, "master");
+    createSubmoduleSubscription(superRepo, "master",
+        "subscribed-to-project", "master");
+    pushChangeTo(subRepo, "master");
+    assertThat(hasSubmodule(superRepo, "master",
+        "subscribed-to-project")).isFalse();
   }
 
   private void deleteAllSubscriptions(TestRepository<?> repo, String branch)
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
index 30482dd..0a067e9 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/git/SubmoduleSubscriptionsWholeTopicMergeIT.java
@@ -93,6 +93,71 @@
   }
 
   @Test
+  public void testSubscriptionUpdateIncludingChangeInSuperproject() throws Exception {
+    TestRepository<?> superRepo = createProjectWithPush("super-project");
+    TestRepository<?> subRepo = createProjectWithPush("subscribed-to-project");
+    allowSubmoduleSubscription("subscribed-to-project", "refs/heads/master",
+        "super-project", "refs/heads/master");
+
+    createSubmoduleSubscription(superRepo, "master", "subscribed-to-project", "master");
+
+    ObjectId subHEAD = subRepo.branch("HEAD").commit().insertChangeId()
+        .message("some change")
+        .add("a.txt", "a contents ")
+        .create();
+    subRepo.git().push().setRemote("origin").setRefSpecs(
+          new RefSpec("HEAD:refs/heads/master")).call();
+
+    RevCommit c = subRepo.getRevWalk().parseCommit(subHEAD);
+
+    RevCommit c1 = subRepo.branch("HEAD").commit().insertChangeId()
+      .message("first change")
+      .add("asdf", "asdf\n")
+      .create();
+    subRepo.git().push().setRemote("origin")
+      .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+      .call();
+
+    subRepo.reset(c.getId());
+    RevCommit c2 = subRepo.branch("HEAD").commit().insertChangeId()
+      .message("qwerty")
+      .add("qwerty", "qwerty")
+      .create();
+
+    RevCommit c3 = subRepo.branch("HEAD").commit().insertChangeId()
+      .message("qwerty followup")
+      .add("qwerty", "qwerty\nqwerty\n")
+      .create();
+    subRepo.git().push().setRemote("origin")
+      .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+      .call();
+
+    RevCommit c4 = superRepo.branch("HEAD").commit().insertChangeId()
+      .message("new change on superproject")
+      .add("foo", "bar")
+      .create();
+    superRepo.git().push().setRemote("origin")
+      .setRefSpecs(new RefSpec("HEAD:refs/for/master/" + name("topic-foo")))
+      .call();
+
+    String id1 = getChangeId(subRepo, c1).get();
+    String id2 = getChangeId(subRepo, c2).get();
+    String id3 = getChangeId(subRepo, c3).get();
+    String id4 = getChangeId(superRepo, c4).get();
+    gApi.changes().id(id1).current().review(ReviewInput.approve());
+    gApi.changes().id(id2).current().review(ReviewInput.approve());
+    gApi.changes().id(id3).current().review(ReviewInput.approve());
+    gApi.changes().id(id4).current().review(ReviewInput.approve());
+
+    gApi.changes().id(id1).current().submit();
+    ObjectId subRepoId = subRepo.git().fetch().setRemote("origin").call()
+        .getAdvertisedRef("refs/heads/master").getObjectId();
+
+    expectToHaveSubmoduleState(superRepo, "master",
+        "subscribed-to-project", subRepoId);
+  }
+
+  @Test
   public void testUpdateManySubmodules() throws Exception {
     TestRepository<?> superRepo = createProjectWithPush("super-project");
     TestRepository<?> sub1 = createProjectWithPush("sub1");
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
index 5403e0d..7fb91f1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/AbstractSubmit.java
@@ -95,7 +95,7 @@
     return submitWholeTopicEnabledConfig();
   }
 
-  private Map<String, String> mergeResults;
+  private Map<String, String> changeMergedEvents;
 
   @Inject
   private ApprovalsUtil approvalsUtil;
@@ -127,7 +127,7 @@
 
   @Before
   public void setUp() throws Exception {
-    mergeResults = new HashMap<>();
+    changeMergedEvents = new HashMap<>();
     eventListenerRegistration =
         eventListeners.add(new UserScopedEventListener() {
           @Override
@@ -139,7 +139,7 @@
             ChangeAttribute c = e.change.get();
             PatchSetAttribute ps = e.patchSet.get();
             log.debug("Merged {},{} as {}", ps.number, c.number, e.newRev);
-            mergeResults.put(e.change.get().number, e.newRev);
+            changeMergedEvents.put(e.change.get().number, e.newRev);
           }
 
           @Override
@@ -305,8 +305,8 @@
     // newRev of the ChangeMergedEvent.
     BranchInfo branch = gApi.projects().name(change.project)
         .branch(change.branch).get();
-    assertThat(mergeResults).isNotEmpty();
-    String newRev = mergeResults.get(Integer.toString(change._number));
+    assertThat(changeMergedEvents).isNotEmpty();
+    String newRev = changeMergedEvents.get(Integer.toString(change._number));
     assertThat(newRev).isNotNull();
     assertThat(branch.revision).isEqualTo(newRev);
   }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
index b8f0ec9..6781ef1 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ConfigChangeIT.java
@@ -26,6 +26,7 @@
 import com.google.gerrit.extensions.api.changes.ReviewInput;
 import com.google.gerrit.extensions.api.projects.ProjectInput;
 import com.google.gerrit.extensions.client.ChangeStatus;
+import com.google.gerrit.extensions.client.SubmitType;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
 import com.google.gerrit.server.git.ProjectConfig;
 import com.google.gerrit.server.project.Util;
@@ -56,6 +57,18 @@
   @Test
   @TestProjectInput(cloneAs = "user")
   public void updateProjectConfig() throws Exception {
+    String id = testUpdateProjectConfig();
+    assertThat(gApi.changes().id(id).get().revisions).hasSize(1);
+  }
+
+  @Test
+  @TestProjectInput(cloneAs = "user", submitType = SubmitType.CHERRY_PICK)
+  public void updateProjectConfigWithCherryPick() throws Exception {
+    String id = testUpdateProjectConfig();
+    assertThat(gApi.changes().id(id).get().revisions).hasSize(2);
+  }
+
+  private String testUpdateProjectConfig() throws Exception {
     Config cfg = readProjectConfig();
     assertThat(cfg.getString("project", null, "description")).isNull();
     String desc = "new project description";
@@ -74,6 +87,11 @@
     fetchRefsMetaConfig();
     assertThat(readProjectConfig().getString("project", null, "description"))
         .isEqualTo(desc);
+    String changeRev = gApi.changes().id(id).get().currentRevision;
+    String branchRev = gApi.projects().name(project.get())
+        .branch("refs/meta/config").get().revision;
+    assertThat(changeRev).isEqualTo(branchRev);
+    return id;
   }
 
   @Test
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
index 924eb4d..f8640bd 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/CreateChangeIT.java
@@ -116,7 +116,7 @@
 
   @Test
   public void noteDbCommit() throws Exception {
-    assume().that(notesMigration.enabled()).isTrue();
+    assume().that(notesMigration.readChanges()).isTrue();
 
     ChangeInfo c = assertCreateSucceeds(newChangeInput(ChangeStatus.NEW));
     try (Repository repo = repoManager.openRepository(project);
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java
new file mode 100644
index 0000000..e68b4af
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/config/DiffPreferencesIT.java
@@ -0,0 +1,79 @@
+// Copyright (C) 2016 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.acceptance.rest.config;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+
+import org.junit.Test;
+
+import java.lang.reflect.Field;
+
+public class DiffPreferencesIT extends AbstractDaemonTest {
+
+  @Test
+  public void GetDiffPreferences() throws Exception {
+    DiffPreferencesInfo result = get();
+    assertPrefsEqual(result, DiffPreferencesInfo.defaults());
+  }
+
+  @Test
+  public void SetDiffPreferences() throws Exception {
+    int newLineLength = DiffPreferencesInfo.defaults().lineLength + 10;
+    DiffPreferencesInfo update = new DiffPreferencesInfo();
+    update.lineLength = newLineLength;
+    DiffPreferencesInfo result = put(update);
+    assertThat(result.lineLength).named("lineLength").isEqualTo(newLineLength);
+
+    result = get();
+    DiffPreferencesInfo expected = DiffPreferencesInfo.defaults();
+    expected.lineLength = newLineLength;
+    assertPrefsEqual(result, expected);
+  }
+
+  private DiffPreferencesInfo get() throws Exception {
+    RestResponse r = adminRestSession.get("/config/server/preferences.diff");
+    r.assertOK();
+    return newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
+  }
+
+  private DiffPreferencesInfo put(DiffPreferencesInfo input) throws Exception {
+    RestResponse r = adminRestSession.put(
+        "/config/server/preferences.diff", input);
+    r.assertOK();
+    return newGson().fromJson(r.getReader(), DiffPreferencesInfo.class);
+  }
+
+  private void assertPrefsEqual(DiffPreferencesInfo actual,
+      DiffPreferencesInfo expected) throws Exception {
+    for (Field field : actual.getClass().getDeclaredFields()) {
+      if (skipField(field)) {
+        continue;
+      }
+      Object actualField = field.get(actual);
+      Object expectedField = field.get(expected);
+      Class<?> type = field.getType();
+      if ((type == boolean.class || type == Boolean.class)
+          && actualField == null) {
+        continue;
+      }
+      assertThat(actualField).named(field.getName()).isEqualTo(expectedField);
+    }
+  }
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
index 9a5dfeb..255fe57 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/project/AccessIT.java
@@ -16,16 +16,407 @@
 import static com.google.common.truth.Truth.assertThat;
 
 import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.extensions.api.access.AccessSectionInfo;
+import com.google.gerrit.extensions.api.access.PermissionInfo;
+import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
+import com.google.gerrit.extensions.api.projects.ProjectApi;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.group.SystemGroupBackend;
 
+import org.eclipse.jgit.lib.Constants;
+import org.junit.Before;
 import org.junit.Test;
 
+import java.util.HashMap;
+
 public class AccessIT extends AbstractDaemonTest {
+
+  private final String PROJECT_NAME = "newProject";
+
+  private final String REFS_ALL = Constants.R_REFS + "*";
+  private final String REFS_HEADS = Constants.R_HEADS + "*";
+
+  private final String LABEL_CODE_REVIEW = "Code-Review";
+
+  private String newProjectName;
+  private ProjectApi pApi;
+
+  @Before
+  public void setUp() throws Exception  {
+    newProjectName = createProject(PROJECT_NAME).get();
+    pApi = gApi.projects().name(newProjectName);
+  }
+
   @Test
-  public void testGetDefaultInheritance() throws Exception {
-    String newProjectName = createProject("newProjectAccess").get();
-    String inheritedName = gApi.projects()
-      .name(newProjectName).access().inheritsFrom.name;
+  public void getDefaultInheritance() throws Exception {
+    String inheritedName = pApi.access().inheritsFrom.name;
     assertThat(inheritedName).isEqualTo(AllProjectsNameProvider.DEFAULT);
   }
+
+  @Test
+  public void addAccessSection() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+    accessInput.add.put(REFS_HEADS, accessSectionInfo);
+    pApi.access(accessInput);
+
+    assertThat(pApi.access().local).isEqualTo(accessInput.add);
+  }
+
+  @Test
+  public void removePermission() throws Exception {
+    // Add initial permission set
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+    accessInput.add.put(REFS_HEADS, accessSectionInfo);
+    pApi.access(accessInput);
+
+    // Remove specific permission
+    AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+    accessSectionToRemove.permissions
+        .put(Permission.LABEL + LABEL_CODE_REVIEW, newPermissionInfo());
+    ProjectAccessInput removal = newProjectAccessInput();
+    removal.remove.put(REFS_HEADS, accessSectionToRemove);
+    pApi.access(removal);
+
+    // Remove locally
+    accessInput.add.get(REFS_HEADS).permissions
+        .remove(Permission.LABEL + LABEL_CODE_REVIEW);
+
+    // Check
+    assertThat(pApi.access().local).isEqualTo(accessInput.add);
+  }
+
+  @Test
+  public void removePermissionRule() throws Exception {
+    // Add initial permission set
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+    accessInput.add.put(REFS_HEADS, accessSectionInfo);
+    pApi.access(accessInput);
+
+    // Remove specific permission rule
+    AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+    PermissionInfo codeReview = newPermissionInfo();
+    codeReview.label = LABEL_CODE_REVIEW;
+    PermissionRuleInfo pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.DENY, false);
+    codeReview.rules.put(
+        SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSectionToRemove.permissions
+        .put(Permission.LABEL +LABEL_CODE_REVIEW, codeReview);
+    ProjectAccessInput removal = newProjectAccessInput();
+    removal.remove.put(REFS_HEADS, accessSectionToRemove);
+    pApi.access(removal);
+
+    // Remove locally
+    accessInput.add.get(REFS_HEADS).permissions
+        .get(Permission.LABEL + LABEL_CODE_REVIEW)
+        .rules.remove(SystemGroupBackend.REGISTERED_USERS.get());
+
+    // Check
+    assertThat(pApi.access().local).isEqualTo(accessInput.add);
+  }
+
+  @Test
+  public void removePermissionRulesAndCleanupEmptyEntries() throws Exception {
+    // Add initial permission set
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createDefaultAccessSectionInfo();
+
+    accessInput.add.put(REFS_HEADS, accessSectionInfo);
+    pApi.access(accessInput);
+
+    // Remove specific permission rules
+    AccessSectionInfo accessSectionToRemove = newAccessSectionInfo();
+    PermissionInfo codeReview = newPermissionInfo();
+    codeReview.label = LABEL_CODE_REVIEW;
+    PermissionRuleInfo pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.DENY, false);
+    codeReview.rules.put(
+        SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.DENY, false);
+    codeReview.rules.put(
+        SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+    accessSectionToRemove.permissions
+        .put(Permission.LABEL +LABEL_CODE_REVIEW, codeReview);
+    ProjectAccessInput removal = newProjectAccessInput();
+    removal.remove.put(REFS_HEADS, accessSectionToRemove);
+    pApi.access(removal);
+
+    // Remove locally
+    accessInput.add.get(REFS_HEADS)
+        .permissions.remove(Permission.LABEL + LABEL_CODE_REVIEW);
+
+    // Check
+    assertThat(pApi.access().local).isEqualTo(accessInput.add);
+  }
+
+  @Test
+  public void getPermissionsWithDisallowedUser() throws Exception {
+    // Add initial permission set
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+    // Disallow READ
+    accessInput.add.put(REFS_ALL, accessSectionInfo);
+    pApi.access(accessInput);
+
+    setApiUser(user);
+    exception.expect(ResourceNotFoundException.class);
+    gApi.projects().name(newProjectName).access();
+  }
+
+  @Test
+  public void setPermissionsWithDisallowedUser() throws Exception {
+    // Add initial permission set
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = createAccessSectionInfoDenyAll();
+
+    // Disallow READ
+    accessInput.add.put(REFS_ALL, accessSectionInfo);
+    pApi.access(accessInput);
+
+    // Create a change to apply
+    ProjectAccessInput accessInfoToApply = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfoToApply =
+        createDefaultAccessSectionInfo();
+    accessInfoToApply.add.put(REFS_HEADS, accessSectionInfoToApply);
+
+    setApiUser(user);
+    exception.expect(ResourceNotFoundException.class);
+    gApi.projects().name(newProjectName).access();
+  }
+
+  @Test
+  public void updateParentAsUser() throws Exception {
+    // Create child
+    String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+
+    // Set new parent
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    accessInput.parent = newParentProjectName;
+
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    exception.expectMessage("not administrator");
+    gApi.projects().name(newProjectName).access(accessInput);
+  }
+
+  @Test
+  public void updateParentAsAdministrator() throws Exception {
+    // Create parent
+    String newParentProjectName = createProject(PROJECT_NAME + "PA").get();
+
+    // Set new parent
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    accessInput.parent = newParentProjectName;
+
+    gApi.projects().name(newProjectName).access(accessInput);
+
+    assertThat(pApi.access().inheritsFrom.name).isEqualTo(newParentProjectName);
+  }
+
+  @Test
+  public void addGlobalCapabilityAsUser() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo =
+        createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    gApi.projects().name(allProjects.get()).access(accessInput);
+  }
+
+  @Test
+  public void addGlobalCapabilityAsAdmin() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo =
+        createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    ProjectAccessInfo updatedAccessSectionInfo =
+        gApi.projects().name(allProjects.get()).access(accessInput);
+    assertThat(updatedAccessSectionInfo.local.get(
+        AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+        .containsAllIn(accessSectionInfo.permissions.keySet());
+  }
+
+  @Test
+  public void addGlobalCapabilityForNonRootProject() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo =
+        createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    exception.expect(BadRequestException.class);
+    pApi.access(accessInput);
+  }
+
+  @Test
+  public void addNonGlobalCapabilityToGlobalCapabilities() throws Exception {
+    AccountGroup adminGroup =
+        groupCache.get(new AccountGroup.NameKey("Administrators"));
+
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+    PermissionInfo permissionInfo = newPermissionInfo();
+    permissionInfo.rules.put(
+        adminGroup.getGroupUUID().get(), null);
+    accessSectionInfo.permissions.put(Permission.PUSH,
+        permissionInfo);
+
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    exception.expect(BadRequestException.class);
+    gApi.projects().name(allProjects.get()).access(accessInput);
+  }
+
+  @Test
+  public void removeGlobalCapabilityAsUser() throws Exception {
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo =
+        createDefaultGlobalCapabilitiesAccessSectionInfo();
+
+    accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    setApiUser(user);
+    exception.expect(AuthException.class);
+    gApi.projects().name(allProjects.get()).access(accessInput);
+  }
+
+  @Test
+  public void removeGlobalCapabilityAsAdmin() throws Exception {
+    AccountGroup adminGroup =
+        groupCache.get(new AccountGroup.NameKey("Administrators"));
+
+    ProjectAccessInput accessInput = newProjectAccessInput();
+    AccessSectionInfo accessSectionInfo = newAccessSectionInfo();
+
+    PermissionInfo permissionInfo = newPermissionInfo();
+    permissionInfo.rules.put(
+        adminGroup.getGroupUUID().get(), null);
+    accessSectionInfo.permissions.put(GlobalCapability.ACCESS_DATABASE,
+        permissionInfo);
+
+    // Add and validate first as removing existing privileges such as
+    // administrateServer would break upcoming tests
+    accessInput.add.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    ProjectAccessInfo updatedProjectAccessInfo =
+        gApi.projects().name(allProjects.get()).access(accessInput);
+    assertThat(updatedProjectAccessInfo.local.get(
+        AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+        .containsAllIn(accessSectionInfo.permissions.keySet());
+
+    // Remove
+    accessInput.add.clear();
+    accessInput.remove.put(AccessSection.GLOBAL_CAPABILITIES,
+        accessSectionInfo);
+
+    updatedProjectAccessInfo =
+        gApi.projects().name(allProjects.get()).access(accessInput);
+    assertThat(updatedProjectAccessInfo.local.get(
+        AccessSection.GLOBAL_CAPABILITIES).permissions.keySet())
+        .containsNoneIn(accessSectionInfo.permissions.keySet());
+  }
+
+  private ProjectAccessInput newProjectAccessInput() {
+    ProjectAccessInput p = new ProjectAccessInput();
+    p.add = new HashMap<>();
+    p.remove = new HashMap<>();
+    return p;
+  }
+
+  private PermissionInfo newPermissionInfo() {
+    PermissionInfo p = new PermissionInfo(null, null);
+    p.rules = new HashMap<>();
+    return p;
+  }
+
+  private AccessSectionInfo newAccessSectionInfo() {
+    AccessSectionInfo a = new AccessSectionInfo();
+    a.permissions = new HashMap<>();
+    return a;
+  }
+
+  private AccessSectionInfo createDefaultAccessSectionInfo() {
+    AccessSectionInfo accessSection = newAccessSectionInfo();
+
+    PermissionInfo push = newPermissionInfo();
+    PermissionRuleInfo pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.ALLOW, false);
+    push.rules.put(
+        SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSection.permissions.put(Permission.PUSH, push);
+
+    PermissionInfo codeReview = newPermissionInfo();
+    codeReview.label = LABEL_CODE_REVIEW;
+    pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.DENY, false);
+    codeReview.rules.put(
+        SystemGroupBackend.REGISTERED_USERS.get(), pri);
+
+    pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.ALLOW, false);
+    pri.max = 1;
+    pri.min = -1;
+    codeReview.rules.put(
+        SystemGroupBackend.PROJECT_OWNERS.get(), pri);
+    accessSection.permissions.put(Permission.LABEL
+        + LABEL_CODE_REVIEW, codeReview);
+
+    return accessSection;
+  }
+
+
+  private AccessSectionInfo createDefaultGlobalCapabilitiesAccessSectionInfo() {
+    AccessSectionInfo accessSection = newAccessSectionInfo();
+
+    PermissionInfo email = newPermissionInfo();
+    PermissionRuleInfo pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.ALLOW, false);
+    email.rules.put(
+        SystemGroupBackend.REGISTERED_USERS.get(), pri);
+    accessSection.permissions.put(GlobalCapability.EMAIL_REVIEWERS, email);
+
+    return accessSection;
+  }
+
+  private AccessSectionInfo createAccessSectionInfoDenyAll() {
+    AccessSectionInfo accessSection = newAccessSectionInfo();
+
+    PermissionInfo read = newPermissionInfo();
+    PermissionRuleInfo pri = new PermissionRuleInfo(
+        PermissionRuleInfo.Action.DENY, false);
+    read.rules.put(
+        SystemGroupBackend.ANONYMOUS_USERS.get(), pri);
+    accessSection.permissions.put(Permission.READ, read);
+
+    return accessSection;
+  }
 }
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
index 1227a03..ea4909f 100644
--- a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/server/notedb/ChangeRebuilderIT.java
@@ -17,6 +17,9 @@
 import static com.google.common.truth.Truth.assertThat;
 import static com.google.common.truth.TruthJUnit.assume;
 import static com.google.gerrit.reviewdb.client.RefNames.changeMetaRef;
+import static com.google.gerrit.reviewdb.client.RefNames.refsDraftComments;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
 
 import com.google.common.collect.ImmutableList;
 import com.google.gerrit.acceptance.AbstractDaemonTest;
@@ -35,7 +38,6 @@
 import com.google.gerrit.reviewdb.client.PatchLineComment;
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.RevId;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.ChangeUtil;
@@ -57,7 +59,9 @@
 import com.google.inject.Provider;
 
 import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefUpdate;
 import org.eclipse.jgit.lib.Repository;
 import org.junit.After;
 import org.junit.Before;
@@ -298,14 +302,14 @@
 
     putDraft(user, id, 1, "comment by user");
     ObjectId userDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(id, user.getId()));
+        allUsers, refsDraftComments(id, user.getId()));
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
         + "," + user.getId() + "=" + userDraftsId.name());
 
     putDraft(admin, id, 2, "comment by admin");
     ObjectId adminDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(id, admin.getId()));
+        allUsers, refsDraftComments(id, admin.getId()));
     assertThat(admin.getId().get()).isLessThan(user.getId().get());
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
@@ -314,7 +318,7 @@
 
     putDraft(admin, id, 2, "revised comment by admin");
     adminDraftsId = getMetaRef(
-        allUsers, RefNames.refsDraftComments(id, admin.getId()));
+        allUsers, refsDraftComments(id, admin.getId()));
     assertThat(unwrapDb().changes().get(id).getNoteDbState()).isEqualTo(
         changeMetaId.name()
         + "," + admin.getId() + "=" + adminDraftsId.name()
@@ -579,6 +583,56 @@
     assertThat(notes.getChange().getSubject()).isEqualTo(PushOneCommit.SUBJECT);
   }
 
+  @Test
+  public void createWithAutoRebuildingDisabled() throws Exception {
+    ReviewDb oldDb = db;
+    setNotesMigration(true, true);
+
+    PushOneCommit.Result r = createChange();
+    Change.Id id = r.getPatchSetId().getParentKey();
+    ChangeNotes oldNotes = notesFactory.create(db, project, id);
+
+    // Make a ReviewDb change behind NoteDb's back.
+    Change c = oldDb.changes().get(id);
+    assertThat(c.getTopic()).isNull();
+    String topic = name("a-topic");
+    c.setTopic(topic);
+    oldDb.changes().update(Collections.singleton(c));
+
+    c = oldDb.changes().get(c.getId());
+    ChangeNotes newNotes =
+        notesFactory.createWithAutoRebuildingDisabled(c, null);
+    assertThat(newNotes.getChange().getTopic()).isNotEqualTo(topic);
+    assertThat(newNotes.getChange().getTopic())
+        .isEqualTo(oldNotes.getChange().getTopic());
+  }
+
+  @Test
+  public void rebuildDeletesOldDraftRefs() throws Exception {
+    PushOneCommit.Result r = createChange();
+    Change.Id id = r.getPatchSetId().getParentKey();
+    putDraft(user, id, 1, "comment");
+
+    Account.Id otherAccountId = new Account.Id(user.getId().get() + 1234);
+    String otherDraftRef = refsDraftComments(id, otherAccountId);
+
+    try (Repository repo = repoManager.openRepository(allUsers);
+        ObjectInserter ins = repo.newObjectInserter()) {
+      ObjectId sha = ins.insert(OBJ_BLOB, "garbage data".getBytes(UTF_8));
+      ins.flush();
+      RefUpdate ru = repo.updateRef(otherDraftRef);
+      ru.setExpectedOldObjectId(ObjectId.zeroId());
+      ru.setNewObjectId(sha);
+      assertThat(ru.update()).isEqualTo(RefUpdate.Result.NEW);
+    }
+
+    checker.rebuildAndCheckChanges(id);
+
+    try (Repository repo = repoManager.openRepository(allUsers)) {
+      assertThat(repo.exactRef(otherDraftRef)).isNull();
+    }
+  }
+
   private void setInvalidNoteDbState(Change.Id id) throws Exception {
     ReviewDb db = unwrapDb();
     Change c = db.changes().get(id);
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
index f21f894..993820b 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/AccessSection.java
@@ -75,6 +75,10 @@
     }
   }
 
+  public void addPermission(Permission p) {
+    getPermissions().add(p);
+  }
+
   public void remove(Permission permission) {
     if (permission != null) {
       removePermission(permission.getName());
diff --git a/gerrit-extension-api/BUCK b/gerrit-extension-api/BUCK
index 67bfdc1..9b83c5a 100644
--- a/gerrit-extension-api/BUCK
+++ b/gerrit-extension-api/BUCK
@@ -38,6 +38,9 @@
 java_library(
   name = 'api',
   srcs = glob([SRC + '**/*.java']),
+  deps = [
+    '//gerrit-common:annotations',
+  ],
   provided_deps = [
     '//lib/guice:guice',
     '//lib/guice:guice-assistedinject',
@@ -72,6 +75,7 @@
     '//lib/guice:javax-inject',
     '//lib/guice:guice_library',
     '//lib/guice:guice-assistedinject',
+    '//gerrit-common:annotations',
   ],
   visibility = ['PUBLIC'],
 )
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
index b97fd3b..7c47ec5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/AccessSectionInfo.java
@@ -14,7 +14,22 @@
 package com.google.gerrit.extensions.api.access;
 
 import java.util.Map;
+import java.util.Objects;
 
 public class AccessSectionInfo {
+
   public Map<String, PermissionInfo> permissions;
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof AccessSectionInfo) {
+      return Objects.equals(permissions, ((AccessSectionInfo) obj).permissions);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(permissions);
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
index c6d25b3..c4808a5 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionInfo.java
@@ -14,6 +14,7 @@
 package com.google.gerrit.extensions.api.access;
 
 import java.util.Map;
+import java.util.Objects;
 
 public class PermissionInfo {
   public String label;
@@ -24,4 +25,20 @@
     this.label = label;
     this.exclusive = exclusive;
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof PermissionInfo) {
+      PermissionInfo p = (PermissionInfo) obj;
+      return Objects.equals(label, p.label)
+          && Objects.equals(exclusive, p.exclusive)
+          && Objects.equals(rules, p.rules);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(label, exclusive, rules);
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
index 93990dc..f979039 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/PermissionRuleInfo.java
@@ -13,6 +13,8 @@
 // limitations under the License.
 package com.google.gerrit.extensions.api.access;
 
+import java.util.Objects;
+
 public class PermissionRuleInfo {
   public enum Action {
     ALLOW,
@@ -31,4 +33,21 @@
     this.action = action;
     this.force = force;
   }
+
+  @Override
+  public boolean equals(Object obj) {
+    if (obj instanceof PermissionRuleInfo) {
+      PermissionRuleInfo p = (PermissionRuleInfo) obj;
+      return Objects.equals(action, p.action)
+          && Objects.equals(force, p.force)
+          && Objects.equals(min, p.min)
+          && Objects.equals(max, p.max);
+    }
+    return false;
+  }
+
+  @Override
+  public int hashCode() {
+    return Objects.hash(action, force, min, max);
+  }
 }
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java
new file mode 100644
index 0000000..39a5209
--- /dev/null
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/access/ProjectAccessInput.java
@@ -0,0 +1,23 @@
+// Copyright (C) 2016 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.extensions.api.access;
+
+import java.util.Map;
+
+public class ProjectAccessInput {
+  public Map<String, AccessSectionInfo> remove;
+  public Map<String, AccessSectionInfo> add;
+  public String parent;
+  public String message;
+}
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
index 75cebaa..260d7ce 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/api/projects/ProjectApi.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.extensions.api.projects;
 
 import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
 import com.google.gerrit.extensions.common.ProjectInfo;
 import com.google.gerrit.extensions.restapi.NotImplementedException;
 import com.google.gerrit.extensions.restapi.RestApiException;
@@ -30,6 +31,7 @@
   void description(PutDescriptionInput in) throws RestApiException;
 
   ProjectAccessInfo access() throws RestApiException;
+  ProjectAccessInfo access(ProjectAccessInput p) throws RestApiException;
 
   ListRefsRequest<BranchInfo> branches();
   ListRefsRequest<TagInfo> tags();
@@ -138,6 +140,12 @@
     }
 
     @Override
+    public ProjectAccessInfo access(ProjectAccessInput p)
+      throws RestApiException {
+      throw new NotImplementedException();
+    }
+
+    @Override
     public void description(PutDescriptionInput in)
         throws RestApiException {
       throw new NotImplementedException();
diff --git a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
index a838baf..c20c9e0 100644
--- a/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
+++ b/gerrit-extension-api/src/main/java/com/google/gerrit/extensions/events/GitReferenceUpdatedListener.java
@@ -14,7 +14,9 @@
 
 package com.google.gerrit.extensions.events;
 
+import com.google.gerrit.common.Nullable;
 import com.google.gerrit.extensions.annotations.ExtensionPoint;
+import com.google.gerrit.extensions.common.AccountInfo;
 
 /** Notified when one or more references are modified. */
 @ExtensionPoint
@@ -28,6 +30,10 @@
     boolean isCreate();
     boolean isDelete();
     boolean isNonFastForward();
+    /**
+     * The updater, could be null if it's the server.
+     */
+    @Nullable AccountInfo getUpdater();
   }
 
   void onGitReferenceUpdated(Event event);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
index d222942..cc5c9b7 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/change/RelatedChanges.java
@@ -215,6 +215,11 @@
         EnumSet.of(ListChangesOption.CURRENT_REVISION, ListChangesOption.CURRENT_COMMIT),
         new TabChangeListCallback(Tab.CHERRY_PICKS, info.project(), revision));
 
+    // TODO(sbeller): show only on latest revision
+    ChangeApi.change(info.legacyId().get()).view("submitted_together")
+        .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
+            info.project(), revision));
+
     if (!Gerrit.info().change().isSubmitWholeTopicEnabled()
         && info.topic() != null && !"".equals(info.topic())) {
       StringBuilder topicQuery = new StringBuilder();
@@ -226,11 +231,6 @@
                      ListChangesOption.DETAILED_LABELS,
                      ListChangesOption.LABELS),
           new TabChangeListCallback(Tab.SAME_TOPIC, info.project(), revision));
-    } else {
-      // TODO(sbeller): show only on latest revision
-      ChangeApi.change(info.legacyId().get()).view("submitted_together")
-          .get(new TabChangeListCallback(Tab.SUBMITTED_TOGETHER,
-              info.project(), revision));
     }
   }
 
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
index 20dd883..1d198ec 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CommentGroup.java
@@ -31,10 +31,11 @@
  */
 abstract class CommentGroup extends Composite {
 
+  final DisplaySide side;
+  final int line;
+
   private final CommentManager manager;
   private final CodeMirror cm;
-  private final DisplaySide side;
-  private final int line;
   private final FlowPanel comments;
   private LineWidget lineWidget;
   private Timer resizeTimer;
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
index 216fbda..ae6d3c1 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentGroup.java
@@ -21,6 +21,8 @@
 
 import net.codemirror.lib.CodeMirror;
 
+import java.util.PriorityQueue;
+
 /**
  * LineWidget attached to a CodeMirror container.
  *
@@ -28,14 +30,15 @@
  * The group tracks all comment boxes on that same line, and also includes an
  * empty padding element to keep subsequent lines vertically aligned.
  */
-class SideBySideCommentGroup extends CommentGroup {
+class SideBySideCommentGroup extends CommentGroup
+    implements Comparable<SideBySideCommentGroup> {
   static void pair(SideBySideCommentGroup a, SideBySideCommentGroup b) {
-    a.peer = b;
-    b.peer = a;
+    a.peers.add(b);
+    b.peers.add(a);
   }
 
   private final Element padding;
-  private SideBySideCommentGroup peer;
+  private final PriorityQueue<SideBySideCommentGroup> peers;
 
   SideBySideCommentGroup(SideBySideCommentManager manager, CodeMirror cm, DisplaySide side,
       int line) {
@@ -45,29 +48,42 @@
     padding.setClassName(SideBySideTable.style.padding());
     SideBySideChunkManager.focusOnClick(padding, cm.side());
     getElement().appendChild(padding);
+    peers = new PriorityQueue<>();
   }
 
   SideBySideCommentGroup getPeer() {
-    return peer;
+    return peers.peek();
   }
 
   @Override
   void remove(DraftBox box) {
     super.remove(box);
 
-    if (0 < getBoxCount() || 0 < peer.getBoxCount()) {
-      resize();
-    } else {
+    if (getBoxCount() == 0 && peers.size() == 1
+        && peers.peek().peers.size() > 1) {
+      SideBySideCommentGroup peer = peers.peek();
+      peer.peers.remove(this);
       detach();
-      peer.detach();
+      if (peer.getBoxCount() == 0 && peer.peers.size() == 1
+          && peer.peers.peek().getBoxCount() == 0) {
+        peer.detach();
+      } else {
+        peer.resize();
+      }
+    } else {
+      resize();
     }
   }
 
   @Override
   void init(DiffTable parent) {
-    if (getLineWidget() == null && peer.getLineWidget() == null) {
-      this.attach(parent);
-      peer.attach(parent);
+    if (getLineWidget() == null) {
+      attach(parent);
+    }
+    for (CommentGroup peer : peers) {
+      if (peer.getLineWidget() == null) {
+        peer.attach(parent);
+      }
     }
   }
 
@@ -76,20 +92,20 @@
     getLineWidget().onRedraw(new Runnable() {
       @Override
       public void run() {
-        if (canComputeHeight() && peer.canComputeHeight()) {
+        if (canComputeHeight() && peers.peek().canComputeHeight()) {
           if (getResizeTimer() != null) {
             getResizeTimer().cancel();
             setResizeTimer(null);
           }
-          adjustPadding(SideBySideCommentGroup.this, peer);
+          adjustPadding(SideBySideCommentGroup.this, peers.peek());
         } else if (getResizeTimer() == null) {
           setResizeTimer(new Timer() {
             @Override
             public void run() {
-              if (canComputeHeight() && peer.canComputeHeight()) {
+              if (canComputeHeight() && peers.peek().canComputeHeight()) {
                 cancel();
                 setResizeTimer(null);
-                adjustPadding(SideBySideCommentGroup.this, peer);
+                adjustPadding(SideBySideCommentGroup.this, peers.peek());
               }
             }
           });
@@ -102,7 +118,7 @@
   @Override
   void resize() {
     if (getLineWidget() != null) {
-      adjustPadding(this, peer);
+      adjustPadding(this, peers.peek());
     }
   }
 
@@ -117,6 +133,16 @@
   private static void adjustPadding(SideBySideCommentGroup a, SideBySideCommentGroup b) {
     int apx = a.computeHeight();
     int bpx = b.computeHeight();
+    for (SideBySideCommentGroup otherPeer : a.peers) {
+      if (otherPeer != b) {
+        bpx += otherPeer.computeHeight();
+      }
+    }
+    for (SideBySideCommentGroup otherPeer : b.peers) {
+      if (otherPeer != a) {
+        apx += otherPeer.computeHeight();
+      }
+    }
     int h = Math.max(apx, bpx);
     a.padding.getStyle().setHeight(Math.max(0, h - apx), Unit.PX);
     b.padding.getStyle().setHeight(Math.max(0, h - bpx), Unit.PX);
@@ -125,4 +151,14 @@
     a.updateSelection();
     b.updateSelection();
   }
+
+  @Override
+  public int compareTo(SideBySideCommentGroup o) {
+    if (side == o.side) {
+      return line - o.line;
+    } else {
+      throw new IllegalStateException(
+          "Cannot compare SideBySideCommentGroup with different sides");
+    }
+  }
 }
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
index a2af3a1c..2b83b71 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SideBySideCommentManager.java
@@ -23,6 +23,7 @@
 import net.codemirror.lib.TextMarker.FromTo;
 
 import java.util.Collection;
+import java.util.Map;
 import java.util.SortedMap;
 
 /** Tracks comment widgets for {@link SideBySide}. */
@@ -94,36 +95,33 @@
 
   @Override
   CommentGroup group(DisplaySide side, int line) {
-    SideBySideCommentGroup w = (SideBySideCommentGroup) map(side).get(line);
-    if (w != null) {
-      return w;
+    CommentGroup existing = map(side).get(line);
+    if (existing != null) {
+      return existing;
     }
 
-    int lineA;
-    int lineB;
-    if (line == 0) {
-      lineA = lineB = 0;
-    } else if (side == DisplaySide.A) {
-      lineA = line;
-      lineB = host.lineOnOther(side, line - 1).getLine() + 1;
+    SideBySideCommentGroup newGroup = newGroup(side, line);
+    Map<Integer, CommentGroup> map =
+        side == DisplaySide.A ? sideA : sideB;
+    Map<Integer, CommentGroup> otherMap =
+        side == DisplaySide.A ? sideB : sideA;
+    map.put(line, newGroup);
+    int otherLine = host.lineOnOther(side, line - 1).getLine() + 1;
+    existing = map(side.otherSide()).get(otherLine);
+    CommentGroup otherGroup;
+    if (existing != null) {
+      otherGroup = existing;
     } else {
-      lineA = host.lineOnOther(side, line - 1).getLine() + 1;
-      lineB = line;
+      otherGroup = newGroup(side.otherSide(), otherLine);
+      otherMap.put(otherLine, otherGroup);
     }
-
-    SideBySideCommentGroup a = newGroup(DisplaySide.A, lineA);
-    SideBySideCommentGroup b = newGroup(DisplaySide.B, lineB);
-    SideBySideCommentGroup.pair(a, b);
-
-    sideA.put(lineA, a);
-    sideB.put(lineB, b);
+    SideBySideCommentGroup.pair(newGroup, (SideBySideCommentGroup) otherGroup);
 
     if (isAttached()) {
-      a.init(host.getDiffTable());
-      b.handleRedraw();
+      newGroup.init(host.getDiffTable());
+      otherGroup.handleRedraw();
     }
-
-    return side == DisplaySide.A ? a : b;
+    return newGroup;
   }
 
   private SideBySideCommentGroup newGroup(DisplaySide side, int line) {
diff --git a/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
index 5365cc5..7c8b362 100644
--- a/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
+++ b/gerrit-gwtui/src/main/java/net/codemirror/addon/Addons.java
@@ -20,7 +20,7 @@
 import com.google.gwt.resources.client.DataResource.DoNotEmbed;
 
 public interface Addons extends ClientBundle {
-  public static final Addons I = GWT.create(Addons.class);
+  Addons I = GWT.create(Addons.class);
 
   @Source("merge_bundled.js") @DoNotEmbed DataResource merge_bundled();
 }
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
index 45e5615..2c67182 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/UrlModule.java
@@ -67,6 +67,8 @@
       serve("/").with(HostPageServlet.class);
       serve("/Gerrit").with(LegacyGerritServlet.class);
       serve("/Gerrit/*").with(legacyGerritScreen());
+      // Forward PolyGerrit URLs to their respective GWT equivalents.
+      serveRegex("^/(c|q|x|admin|dashboard|settings)/(.*)").with(gerritUrl());
     }
     serve("/cat/*").with(CatServlet.class);
 
@@ -87,9 +89,6 @@
     serve("/watched").with(query("is:watched status:open"));
     serve("/starred").with(query("is:starred"));
 
-    // Forward PolyGerrit URLs to their respective GWT equivalents.
-    serveRegex("^/(c|q|x|admin|dashboard|settings)/(.*)").with(gerritUrl());
-
     serveRegex("^/settings/?$").with(screen(PageLinks.SETTINGS));
     serveRegex("^/register/?$").with(screen(PageLinks.REGISTER + "/"));
     serveRegex("^/([1-9][0-9]*)/?$").with(directChangeById());
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
index beb0139..2190fe0 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/plugins/HttpPluginServlet.java
@@ -488,7 +488,6 @@
       String t = main.getValue(Attributes.Name.IMPLEMENTATION_TITLE);
       String n = main.getValue(Attributes.Name.IMPLEMENTATION_VENDOR);
       String v = main.getValue(Attributes.Name.IMPLEMENTATION_VERSION);
-      String u = main.getValue(Attributes.Name.IMPLEMENTATION_URL);
       String a = main.getValue("Gerrit-ApiVersion");
 
       html.append("<table class=\"plugin_info\">");
@@ -507,11 +506,6 @@
             .append(v)
             .append("</td></tr>\n");
       }
-      if (!Strings.isNullOrEmpty(u)) {
-        html.append("<tr><th>URL</th><td>")
-            .append(String.format("<a href=\"%s\">%s</a>", u, u))
-            .append("</td></tr>\n");
-      }
       if (!Strings.isNullOrEmpty(a)) {
         html.append("<tr><th>API Version</th><td>")
             .append(a)
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
index 32a80fe..e8efd72 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/raw/HostPageServlet.java
@@ -114,7 +114,7 @@
     site = sp;
     refreshHeaderFooter = cfg.getBoolean("site", "refreshHeaderFooter", true);
     staticServlet = ss;
-    isNoteDbEnabled = migration.enabled();
+    isNoteDbEnabled = migration.readChanges();
     pluginsLoadTimeout = getPluginsLoadTimeout(cfg);
     getDiff = diffPref;
 
diff --git a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
index 223629d..da19b5e 100644
--- a/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
+++ b/gerrit-httpd/src/main/java/com/google/gerrit/httpd/rpc/project/ChangeProjectAccess.java
@@ -87,7 +87,7 @@
     RevCommit commit = config.commit(md);
 
     gitRefUpdated.fire(config.getProject().getNameKey(), RefNames.REFS_CONFIG,
-        base, commit.getId());
+        base, commit.getId(), user.asIdentifiedUser().getAccount());
     hooks.doRefUpdatedHook(
       new Branch.NameKey(config.getProject().getNameKey(), RefNames.REFS_CONFIG),
       base, commit.getId(), user.asIdentifiedUser().getAccount());
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
index 9ef6956..1c72600 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/init/VersionedAuthorizedKeysOnInit.java
@@ -49,7 +49,7 @@
 import java.util.List;
 
 public class VersionedAuthorizedKeysOnInit extends VersionedMetaData {
-  public static interface Factory {
+  public interface Factory {
     VersionedAuthorizedKeysOnInit create(Account.Id accountId);
   }
 
diff --git a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
index d701afa..be061c7 100644
--- a/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
+++ b/gerrit-pgm/src/main/java/com/google/gerrit/pgm/util/BatchProgramModule.java
@@ -33,6 +33,7 @@
 import com.google.gerrit.server.account.Realm;
 import com.google.gerrit.server.cache.CacheRemovalListener;
 import com.google.gerrit.server.cache.h2.DefaultCacheFactory;
+import com.google.gerrit.server.change.ChangeJson;
 import com.google.gerrit.server.change.ChangeKindCacheImpl;
 import com.google.gerrit.server.change.MergeabilityCacheImpl;
 import com.google.gerrit.server.change.PatchSetInserter;
@@ -139,5 +140,8 @@
     factory(CapabilityControl.Factory.class);
     factory(ChangeData.Factory.class);
     factory(ProjectState.Factory.class);
+
+    bind(ChangeJson.Factory.class).toProvider(
+        Providers.<ChangeJson.Factory>of(null));
   }
 }
diff --git a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 270e15c..e32a0d6 100644
--- a/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -31,9 +31,6 @@
     <requiredProperty key="Implementation-Vendor">
       <defaultValue>Gerrit Code Review</defaultValue>
     </requiredProperty>
-    <requiredProperty key="Implementation-Url">
-      <defaultValue>https://www.gerritcodereview.com/</defaultValue>
-    </requiredProperty>
 
     <requiredProperty key="gerritApiType">
       <defaultValue>plugin</defaultValue>
diff --git a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
index a6103b1..026e21d 100644
--- a/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-archetype/src/main/resources/archetype-resources/pom.xml
@@ -50,7 +50,6 @@
 #end
 
               <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
-              <Implementation-URL>${Implementation-Url}</Implementation-URL>
 
               <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
               <Implementation-Version>${project.version}</Implementation-Version>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index 3c3508c..32a603b 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -21,9 +21,6 @@
     <requiredProperty key="Implementation-Vendor">
       <defaultValue>Gerrit Code Review</defaultValue>
     </requiredProperty>
-    <requiredProperty key="Implementation-Url">
-      <defaultValue>https://www.gerritcodereview.com/</defaultValue>
-    </requiredProperty>
     <requiredProperty key="Gwt-Version">
       <defaultValue>2.7.0</defaultValue>
     </requiredProperty>
diff --git a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
index d67c7cb..2c7fe88 100644
--- a/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-gwt-archetype/src/main/resources/archetype-resources/pom.xml
@@ -45,7 +45,6 @@
               <Gerrit-Module>${package}.Module</Gerrit-Module>
               <Gerrit-HttpModule>${package}.HttpModule</Gerrit-HttpModule>
               <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
-              <Implementation-URL>${Implementation-Url}</Implementation-URL>
 
               <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
               <Implementation-Version>${project.version}</Implementation-Version>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
index fbf1e46..ef0e96c 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/META-INF/maven/archetype-metadata.xml
@@ -21,9 +21,6 @@
     <requiredProperty key="Implementation-Vendor">
       <defaultValue>Gerrit Code Review</defaultValue>
     </requiredProperty>
-    <requiredProperty key="Implementation-Url">
-      <defaultValue>https://gerrit.googlesource.com/</defaultValue>
-    </requiredProperty>
 
     <requiredProperty key="gerritApiType">
       <defaultValue>js</defaultValue>
diff --git a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
index f24d81e..8f4aadd 100644
--- a/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
+++ b/gerrit-plugin-js-archetype/src/main/resources/archetype-resources/pom.xml
@@ -44,7 +44,6 @@
             <manifestEntries>
               <Gerrit-PluginName>${pluginName}</Gerrit-PluginName>
               <Implementation-Vendor>${Implementation-Vendor}</Implementation-Vendor>
-              <Implementation-URL>${Implementation-Url}</Implementation-URL>
 
               <Implementation-Title>${Gerrit-ApiType} ${project.artifactId}</Implementation-Title>
               <Implementation-Version>${project.version}</Implementation-Version>
diff --git a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
index 88efd50..20f9b82 100644
--- a/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
+++ b/gerrit-reviewdb/src/main/java/com/google/gerrit/reviewdb/client/Change.java
@@ -160,7 +160,10 @@
           RefNames.EDIT_PREFIX.length();
       int endChangeId = nextNonDigit(ref, startChangeId);
       String id = ref.substring(startChangeId, endChangeId);
-      return new Change.Id(Integer.parseInt(id));
+      if (id != null && !id.isEmpty()) {
+        return new Change.Id(Integer.parseInt(id));
+      }
+      return null;
     }
 
     public static Id fromRefPart(String ref) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
index 674cba6..b91378e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/DeleteWatchedProjects.java
@@ -64,9 +64,10 @@
     if (input != null) {
       List<AccountProjectWatch.Key> keysToDelete = new LinkedList<>();
       for (String projectKeyToDelete : input) {
-        if (!watchedProjectsMap.containsKey(projectKeyToDelete))
+        if (!watchedProjectsMap.containsKey(projectKeyToDelete)) {
           throw new UnprocessableEntityException(projectKeyToDelete
               + " is not currently watched by this user.");
+        }
         keysToDelete.add(watchedProjectsMap.get(projectKeyToDelete).getKey());
       }
       dbProvider.get().accountProjectWatches().deleteKeys(keysToDelete);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
index be87ae7..2c4a840 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/GetDiffPreferences.java
@@ -15,6 +15,7 @@
 package com.google.gerrit.server.account;
 
 import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
 
 import com.google.gerrit.extensions.client.DiffPreferencesInfo;
 import com.google.gerrit.extensions.restapi.AuthException;
@@ -31,11 +32,17 @@
 import org.eclipse.jgit.errors.ConfigInvalidException;
 import org.eclipse.jgit.errors.RepositoryNotFoundException;
 import org.eclipse.jgit.lib.Repository;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
 
 import java.io.IOException;
+import java.lang.reflect.Field;
 
 @Singleton
 public class GetDiffPreferences implements RestReadView<AccountResource> {
+  private static final Logger log =
+      LoggerFactory.getLogger(GetDiffPreferences.class);
+
   private final Provider<CurrentUser> self;
   private final Provider<AllUsersName> allUsersName;
   private final GitRepositoryManager gitMgr;
@@ -66,13 +73,41 @@
       DiffPreferencesInfo in)
       throws IOException, ConfigInvalidException, RepositoryNotFoundException {
     try (Repository git = gitMgr.openRepository(allUsersName)) {
+      // Load all users prefs.
+      VersionedAccountPreferences dp =
+          VersionedAccountPreferences.forDefault();
+      dp.load(git);
+      DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+      loadSection(dp.getConfig(), UserConfigSections.DIFF, null, allUserPrefs,
+          DiffPreferencesInfo.defaults(), in);
+
+      // Load user prefs
       VersionedAccountPreferences p =
           VersionedAccountPreferences.forUser(id);
       p.load(git);
       DiffPreferencesInfo prefs = new DiffPreferencesInfo();
       loadSection(p.getConfig(), UserConfigSections.DIFF, null, prefs,
-          DiffPreferencesInfo.defaults(), in);
+          updateDefaults(allUserPrefs), in);
       return prefs;
     }
   }
+
+  private static DiffPreferencesInfo updateDefaults(DiffPreferencesInfo input) {
+    DiffPreferencesInfo result = DiffPreferencesInfo.defaults();
+    try {
+      for (Field field : input.getClass().getDeclaredFields()) {
+        if (skipField(field)) {
+          continue;
+        }
+        Object newVal = field.get(input);
+        if (newVal != null) {
+          field.set(result, newVal);
+        }
+      }
+    } catch (IllegalAccessException e) {
+      log.warn("Cannot get default diff preferences from All-Users", e);
+      return DiffPreferencesInfo.defaults();
+    }
+    return result;
+  }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
index 4662f87..dc96d49 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/account/VersionedAuthorizedKeys.java
@@ -165,7 +165,7 @@
     }
   }
 
-  public static interface Factory {
+  public interface Factory {
     VersionedAuthorizedKeys create(Account.Id accountId);
   }
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
index b8bd905..e8baefe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/api/projects/ProjectApiImpl.java
@@ -17,6 +17,7 @@
 import static com.google.gerrit.server.account.CapabilityUtils.checkRequiresCapability;
 
 import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
 import com.google.gerrit.extensions.api.projects.BranchApi;
 import com.google.gerrit.extensions.api.projects.BranchInfo;
 import com.google.gerrit.extensions.api.projects.ChildProjectApi;
@@ -44,6 +45,7 @@
 import com.google.gerrit.server.project.ProjectResource;
 import com.google.gerrit.server.project.ProjectsCollection;
 import com.google.gerrit.server.project.PutDescription;
+import com.google.gerrit.server.project.SetAccess;
 import com.google.inject.assistedinject.Assisted;
 import com.google.inject.assistedinject.AssistedInject;
 
@@ -74,6 +76,7 @@
   private final GetAccess getAccess;
   private final ListBranches listBranches;
   private final ListTags listTags;
+  private final SetAccess setAccess;
 
   @AssistedInject
   ProjectApiImpl(CurrentUser user,
@@ -88,12 +91,13 @@
       BranchApiImpl.Factory branchApiFactory,
       TagApiImpl.Factory tagApiFactory,
       GetAccess getAccess,
+      SetAccess setAccess,
       ListBranches listBranches,
       ListTags listTags,
       @Assisted ProjectResource project) {
     this(user, createProjectFactory, projectApi, projects, getDescription,
         putDescription, childApi, children, projectJson, branchApiFactory,
-        tagApiFactory, getAccess, listBranches, listTags,
+        tagApiFactory, getAccess, setAccess, listBranches, listTags,
         project, null);
   }
 
@@ -110,12 +114,13 @@
       BranchApiImpl.Factory branchApiFactory,
       TagApiImpl.Factory tagApiFactory,
       GetAccess getAccess,
+      SetAccess setAccess,
       ListBranches listBranches,
       ListTags listTags,
       @Assisted String name) {
     this(user, createProjectFactory, projectApi, projects, getDescription,
         putDescription, childApi, children, projectJson, branchApiFactory,
-        tagApiFactory, getAccess, listBranches, listTags,
+        tagApiFactory, getAccess, setAccess, listBranches, listTags,
         null, name);
   }
 
@@ -131,6 +136,7 @@
       BranchApiImpl.Factory branchApiFactory,
       TagApiImpl.Factory tagApiFactory,
       GetAccess getAccess,
+      SetAccess setAccess,
       ListBranches listBranches,
       ListTags listTags,
       ProjectResource project,
@@ -149,6 +155,7 @@
     this.branchApi = branchApiFactory;
     this.tagApi = tagApiFactory;
     this.getAccess = getAccess;
+    this.setAccess = setAccess;
     this.listBranches = listBranches;
     this.listTags = listTags;
   }
@@ -199,6 +206,16 @@
   }
 
   @Override
+  public ProjectAccessInfo access(ProjectAccessInput p)
+      throws RestApiException {
+    try {
+      return setAccess.apply(checkExists(), p);
+    } catch (IOException e) {
+      throw new RestApiException("Cannot put access rights", e);
+    }
+  }
+
+  @Override
   public void description(PutDescriptionInput in)
       throws RestApiException {
     try {
@@ -260,7 +277,8 @@
   }
 
   @Override
-  public List<ProjectInfo> children(boolean recursive) throws RestApiException {
+  public List<ProjectInfo> children(boolean recursive)
+      throws RestApiException {
     ListChildProjects list = children.list();
     list.setRecursive(recursive);
     return list.apply(checkExists());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
index 5846400..4e6bd2d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/change/SetHashtagsOp.java
@@ -35,6 +35,7 @@
 import com.google.gerrit.server.git.BatchUpdate.Context;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.notedb.ChangeUpdate;
+import com.google.gerrit.server.notedb.NotesMigration;
 import com.google.gerrit.server.validators.HashtagValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
@@ -51,6 +52,7 @@
     SetHashtagsOp create(HashtagsInput input);
   }
 
+  private final NotesMigration notesMigration;
   private final ChangeMessagesUtil cmUtil;
   private final ChangeHooks hooks;
   private final DynamicSet<HashtagValidationListener> validationListeners;
@@ -65,10 +67,12 @@
 
   @AssistedInject
   SetHashtagsOp(
+      NotesMigration notesMigration,
       ChangeMessagesUtil cmUtil,
       ChangeHooks hooks,
       DynamicSet<HashtagValidationListener> validationListeners,
       @Assisted @Nullable HashtagsInput input) {
+    this.notesMigration = notesMigration;
     this.cmUtil = cmUtil;
     this.hooks = hooks;
     this.validationListeners = validationListeners;
@@ -83,6 +87,9 @@
   @Override
   public boolean updateChange(ChangeContext ctx)
       throws AuthException, BadRequestException, OrmException, IOException {
+    if (!notesMigration.readChanges()) {
+      throw new BadRequestException("Cannot add hashtags; NoteDb is disabled");
+    }
     if (input == null
         || (input.add == null && input.remove == null)) {
       updatedHashtags = ImmutableSortedSet.of();
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
index e124e48..eaeb850 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/ConfigUtil.java
@@ -382,16 +382,16 @@
     return s;
   }
 
+  public static boolean skipField(Field field) {
+    int modifiers = field.getModifiers();
+    return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
+  }
+
   private static boolean isCollectionOrMap(Class<?> t) {
     return Collection.class.isAssignableFrom(t)
         || Map.class.isAssignableFrom(t);
   }
 
-  private static boolean skipField(Field field) {
-    int modifiers = field.getModifiers();
-    return Modifier.isFinal(modifiers) || Modifier.isTransient(modifiers);
-  }
-
   private static boolean isString(Class<?> t) {
     return String.class == t;
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java
new file mode 100644
index 0000000..f0bab6e
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/GetDiffPreferences.java
@@ -0,0 +1,67 @@
+// Copyright (C) 2016 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.config;
+
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.eclipse.jgit.lib.Repository;
+
+import java.io.IOException;
+
+@Singleton
+public class GetDiffPreferences implements RestReadView<ConfigResource> {
+
+  private final AllUsersName allUsersName;
+  private final GitRepositoryManager gitManager;
+
+  @Inject
+  GetDiffPreferences(GitRepositoryManager gitManager,
+      AllUsersName allUsersName) {
+    this.allUsersName = allUsersName;
+    this.gitManager = gitManager;
+  }
+
+  @Override
+  public DiffPreferencesInfo apply(ConfigResource configResource)
+      throws BadRequestException, ResourceConflictException, Exception {
+    return readFromGit(gitManager, allUsersName, null);
+  }
+
+  static DiffPreferencesInfo readFromGit(GitRepositoryManager gitMgr,
+             AllUsersName allUsersName, DiffPreferencesInfo in)
+      throws IOException, ConfigInvalidException, RepositoryNotFoundException {
+    try (Repository git = gitMgr.openRepository(allUsersName)) {
+      // Load all users prefs.
+      VersionedAccountPreferences dp =
+          VersionedAccountPreferences.forDefault();
+      dp.load(git);
+      DiffPreferencesInfo allUserPrefs = new DiffPreferencesInfo();
+      loadSection(dp.getConfig(), UserConfigSections.DIFF, null, allUserPrefs,
+          DiffPreferencesInfo.defaults(), in);
+      return allUserPrefs;
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
index e909f17..a05058e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/Module.java
@@ -38,6 +38,8 @@
     get(CONFIG_KIND, "info").to(GetServerInfo.class);
     get(CONFIG_KIND, "preferences").to(GetPreferences.class);
     put(CONFIG_KIND, "preferences").to(SetPreferences.class);
+    get(CONFIG_KIND, "preferences.diff").to(GetDiffPreferences.class);
+    put(CONFIG_KIND, "preferences.diff").to(SetDiffPreferences.class);
     put(CONFIG_KIND, "email.confirm").to(ConfirmEmail.class);
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java
new file mode 100644
index 0000000..155ffc2
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/config/SetDiffPreferences.java
@@ -0,0 +1,106 @@
+// Copyright (C) 2016 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.config;
+
+import static com.google.gerrit.server.config.ConfigUtil.loadSection;
+import static com.google.gerrit.server.config.ConfigUtil.skipField;
+import static com.google.gerrit.server.config.ConfigUtil.storeSection;
+import static com.google.gerrit.server.config.GetDiffPreferences.readFromGit;
+
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.extensions.annotations.RequiresCapability;
+import com.google.gerrit.extensions.client.DiffPreferencesInfo;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.server.account.VersionedAccountPreferences;
+import com.google.gerrit.server.git.GitRepositoryManager;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.UserConfigSections;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.errors.RepositoryNotFoundException;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.lang.reflect.Field;
+
+@RequiresCapability(GlobalCapability.ADMINISTRATE_SERVER)
+@Singleton
+public class SetDiffPreferences implements
+    RestModifyView<ConfigResource, DiffPreferencesInfo> {
+  private static final Logger log =
+      LoggerFactory.getLogger(SetDiffPreferences.class);
+
+  private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+  private final AllUsersName allUsersName;
+  private final GitRepositoryManager gitManager;
+
+  @Inject
+  SetDiffPreferences(GitRepositoryManager gitManager,
+      Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+      AllUsersName allUsersName) {
+    this.gitManager = gitManager;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allUsersName = allUsersName;
+  }
+
+  @Override
+  public Object apply(ConfigResource configResource, DiffPreferencesInfo in)
+      throws BadRequestException, Exception {
+    if (in == null) {
+      throw new BadRequestException("input must be provided");
+    }
+    if (!hasSetFields(in)) {
+      throw new BadRequestException("unsupported option");
+    }
+    return writeToGit(readFromGit(gitManager, allUsersName, in));
+  }
+
+  private DiffPreferencesInfo writeToGit(DiffPreferencesInfo in)
+      throws RepositoryNotFoundException, IOException, ConfigInvalidException {
+    DiffPreferencesInfo out = new DiffPreferencesInfo();
+    try (MetaDataUpdate md = metaDataUpdateFactory.get().create(allUsersName)) {
+      VersionedAccountPreferences prefs =
+          VersionedAccountPreferences.forDefault();
+      prefs.load(md);
+      DiffPreferencesInfo defaults = DiffPreferencesInfo.defaults();
+      storeSection(prefs.getConfig(), UserConfigSections.DIFF, null, in,
+          defaults);
+      prefs.commit(md);
+      loadSection(prefs.getConfig(), UserConfigSections.DIFF, null, out,
+          DiffPreferencesInfo.defaults(), null);
+    }
+    return out;
+  }
+
+  private static boolean hasSetFields(DiffPreferencesInfo in) {
+    try {
+      for (Field field : in.getClass().getDeclaredFields()) {
+        if (skipField(field)) {
+          continue;
+        }
+        if (field.get(in) != null) {
+          return true;
+        }
+      }
+    } catch (IllegalAccessException e) {
+      log.warn("Unable to verify input", e);
+    }
+    return false;
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
new file mode 100644
index 0000000..cb30eea
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/EventUtil.java
@@ -0,0 +1,45 @@
+// Copyright (C) 2015 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.extensions.events;
+
+import com.google.gerrit.extensions.common.AccountInfo;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.inject.Inject;
+
+public class EventUtil {
+
+  private final AccountCache accountCache;
+
+  @Inject
+  EventUtil(AccountCache accountCache) {
+    this.accountCache = accountCache;
+  }
+
+  public AccountInfo accountInfo(Account a) {
+    if (a == null || a.getId() == null) {
+      return null;
+    }
+    AccountInfo ai = new AccountInfo(a.getId().get());
+    ai.email = a.getPreferredEmail();
+    ai.name = a.getFullName();
+    ai.username = a.getUserName();
+    return ai;
+  }
+
+  public AccountInfo accountInfo(Account.Id accountId) {
+    return accountInfo(accountCache.get(accountId).getAccount());
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
index 7eef0ee..6eac07c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/extensions/events/GitReferenceUpdated.java
@@ -14,8 +14,10 @@
 
 package com.google.gerrit.server.extensions.events;
 
+import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.extensions.events.GitReferenceUpdatedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
+import com.google.gerrit.reviewdb.client.Account;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.inject.Inject;
 
@@ -26,42 +28,111 @@
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.util.Collections;
-
 public class GitReferenceUpdated {
   private static final Logger log = LoggerFactory
       .getLogger(GitReferenceUpdated.class);
 
-  public static final GitReferenceUpdated DISABLED = new GitReferenceUpdated(
-      Collections.<GitReferenceUpdatedListener> emptyList());
+  public static final GitReferenceUpdated DISABLED = new GitReferenceUpdated() {
+    @Override
+    public void fire(Project.NameKey project, RefUpdate refUpdate,
+        ReceiveCommand.Type type, Account updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, RefUpdate refUpdate,
+        ReceiveCommand.Type type, Account.Id updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, RefUpdate refUpdate,
+        Account updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, RefUpdate refUpdate,
+        AccountInfo updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
+        ObjectId newObjectId, Account updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, ReceiveCommand cmd,
+        Account updater) {}
+
+    @Override
+    public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate,
+        Account.Id updater) {}
+  };
 
   private final Iterable<GitReferenceUpdatedListener> listeners;
+  private final EventUtil util;
 
   @Inject
-  GitReferenceUpdated(DynamicSet<GitReferenceUpdatedListener> listeners) {
+  GitReferenceUpdated(DynamicSet<GitReferenceUpdatedListener> listeners,
+      EventUtil util) {
     this.listeners = listeners;
+    this.util = util;
   }
 
-  GitReferenceUpdated(Iterable<GitReferenceUpdatedListener> listeners) {
-    this.listeners = listeners;
+  private GitReferenceUpdated() {
+    this.listeners = null;
+    this.util = null;
   }
 
   public void fire(Project.NameKey project, RefUpdate refUpdate,
-      ReceiveCommand.Type type) {
+      ReceiveCommand.Type type, Account updater) {
     fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
-        refUpdate.getNewObjectId(), type);
+        refUpdate.getNewObjectId(), type, util.accountInfo(updater));
   }
 
-  public void fire(Project.NameKey project, RefUpdate refUpdate) {
+  public void fire(Project.NameKey project, RefUpdate refUpdate,
+      ReceiveCommand.Type type, Account.Id updater) {
     fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
-        refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE);
+        refUpdate.getNewObjectId(), type, util.accountInfo(updater));
+  }
+
+  public void fire(Project.NameKey project, RefUpdate refUpdate,
+      Account updater) {
+    fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
+        refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE,
+        util.accountInfo(updater));
+  }
+
+  public void fire(Project.NameKey project, RefUpdate refUpdate,
+      AccountInfo updater) {
+    fire(project, refUpdate.getName(), refUpdate.getOldObjectId(),
+        refUpdate.getNewObjectId(), ReceiveCommand.Type.UPDATE, updater);
   }
 
   public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
-      ObjectId newObjectId, ReceiveCommand.Type type) {
+      ObjectId newObjectId, Account updater) {
+    fire(project, ref, oldObjectId, newObjectId, ReceiveCommand.Type.UPDATE,
+        util.accountInfo(updater));
+  }
+
+  public void fire(Project.NameKey project, ReceiveCommand cmd, Account updater) {
+    fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType(),
+        util.accountInfo(updater));
+  }
+
+  public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate,
+      Account.Id updater) {
+    for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
+      if (cmd.getResult() == ReceiveCommand.Result.OK) {
+        fire(project, cmd, util.accountInfo(updater));
+      }
+    }
+  }
+
+  private void fire(Project.NameKey project, ReceiveCommand cmd,
+      AccountInfo updater) {
+    fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType(),
+        updater);
+  }
+
+  private void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
+      ObjectId newObjectId, ReceiveCommand.Type type, AccountInfo updater) {
     ObjectId o = oldObjectId != null ? oldObjectId : ObjectId.zeroId();
     ObjectId n = newObjectId != null ? newObjectId : ObjectId.zeroId();
-    Event event = new Event(project, ref, o.name(), n.name(), type);
+    Event event = new Event(project, ref, o.name(), n.name(), type, updater);
     for (GitReferenceUpdatedListener l : listeners) {
       try {
         l.onGitReferenceUpdated(event);
@@ -71,38 +142,24 @@
     }
   }
 
-  public void fire(Project.NameKey project, String ref, ObjectId oldObjectId,
-      ObjectId newObjectId) {
-    fire(project, ref, oldObjectId, newObjectId, ReceiveCommand.Type.UPDATE);
-  }
-
-  public void fire(Project.NameKey project, ReceiveCommand cmd) {
-    fire(project, cmd.getRefName(), cmd.getOldId(), cmd.getNewId(), cmd.getType());
-  }
-
-  public void fire(Project.NameKey project, BatchRefUpdate batchRefUpdate) {
-    for (ReceiveCommand cmd : batchRefUpdate.getCommands()) {
-      if (cmd.getResult() == ReceiveCommand.Result.OK) {
-        fire(project, cmd);
-      }
-    }
-  }
-
   private static class Event implements GitReferenceUpdatedListener.Event {
     private final String projectName;
     private final String ref;
     private final String oldObjectId;
     private final String newObjectId;
     private final ReceiveCommand.Type type;
+    private final AccountInfo updater;
 
     Event(Project.NameKey project, String ref,
         String oldObjectId, String newObjectId,
-        ReceiveCommand.Type type) {
+        ReceiveCommand.Type type,
+        AccountInfo updater) {
       this.projectName = project.get();
       this.ref = ref;
       this.oldObjectId = oldObjectId;
       this.newObjectId = newObjectId;
       this.type = type;
+      this.updater = updater;
     }
 
     @Override
@@ -141,6 +198,11 @@
     }
 
     @Override
+    public AccountInfo getUpdater() {
+      return updater;
+    }
+
+    @Override
     public String toString() {
       return String.format("%s[%s,%s: %s -> %s]", getClass().getSimpleName(),
           projectName, ref, oldObjectId, newObjectId);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
index 982777d..3b3c78e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/BatchUpdate.java
@@ -350,7 +350,10 @@
           // callers may assume a patch set ref being created means the change
           // was created, or a branch advancing meaning some changes were
           // closed.
-          u.gitRefUpdated.fire(u.project, u.batchRefUpdate);
+          u.gitRefUpdated.fire(
+              u.project,
+              u.batchRefUpdate,
+              u.getUser().isIdentifiedUser() ? u.getUser().getAccountId() : null);
         }
       }
 
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 26db045..30cf0a9 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
@@ -729,7 +729,7 @@
     logDebug("Updating superprojects");
     SubmoduleOp subOp = subOpProvider.get();
     try {
-      subOp.updateSuperProjects(db, branches, submissionId, orm);
+      subOp.updateSuperProjects(branches, submissionId, orm);
       logDebug("Updating superprojects done");
     } catch (SubmoduleException e) {
       logError("The gitlinks were not updated according to the "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
index 540d479..477da3f 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/MetaDataUpdate.java
@@ -125,8 +125,8 @@
     public MetaDataUpdate create(Project.NameKey name, Repository repository,
         IdentifiedUser user, BatchRefUpdate batch) {
       MetaDataUpdate md = factory.create(name, repository, batch);
-      md.getCommitBuilder().setAuthor(createPersonIdent(user));
       md.getCommitBuilder().setCommitter(serverIdent);
+      md.setAuthor(user);
       return md;
     }
 
@@ -176,6 +176,7 @@
   private final CommitBuilder commit;
   private boolean allowEmpty;
   private boolean insertChangeId;
+  private IdentifiedUser author;
 
   @AssistedInject
   public MetaDataUpdate(GitReferenceUpdated gitRefUpdated,
@@ -198,8 +199,9 @@
     getCommitBuilder().setMessage(message);
   }
 
-  public void setAuthor(IdentifiedUser user) {
-    getCommitBuilder().setAuthor(user.newCommitterIdent(
+  public void setAuthor(IdentifiedUser author) {
+    this.author = author;
+    getCommitBuilder().setAuthor(author.newCommitterIdent(
         getCommitBuilder().getCommitter().getWhen(),
         getCommitBuilder().getCommitter().getTimeZone()));
   }
@@ -244,6 +246,7 @@
   }
 
   void fireGitRefUpdatedEvent(RefUpdate ru) {
-    gitRefUpdated.fire(projectName, ru);
+    gitRefUpdated.fire(
+        projectName, ru, author == null ? null : author.getAccount());
   }
 }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
index 2c5e512..d7424c6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/NotesBranchUtil.java
@@ -14,6 +14,7 @@
 
 package com.google.gerrit.server.git;
 
+import com.google.gerrit.extensions.common.AccountInfo;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
@@ -261,7 +262,7 @@
         throw new IOException("Couldn't update " + notesBranch + ". "
             + result.name());
       } else {
-        gitRefUpdated.fire(project, refUpdate);
+        gitRefUpdated.fire(project, refUpdate, (AccountInfo) null);
         break;
       }
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
index d07dea5..a0bc332 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/ProjectConfig.java
@@ -284,6 +284,41 @@
     }
   }
 
+  public void remove(AccessSection section, Permission permission) {
+    if (permission == null) {
+      remove(section);
+    } else if (section != null) {
+      AccessSection a = accessSections.get(section.getName());
+      a.remove(permission);
+      if (a.getPermissions().isEmpty()) {
+        remove(a);
+      }
+    }
+  }
+
+  public void remove(AccessSection section,
+      Permission permission, PermissionRule rule) {
+    if (rule == null) {
+      remove(section, permission);
+    } else if (section != null && permission != null) {
+      AccessSection a = accessSections.get(section.getName());
+      if (a == null) {
+        return;
+      }
+      Permission p = a.getPermission(permission.getName());
+      if (p == null) {
+        return;
+      }
+      p.remove(rule);
+      if (p.getRules().isEmpty()) {
+        a.remove(permission);
+      }
+      if (a.getPermissions().isEmpty()) {
+        remove(a);
+      }
+    }
+  }
+
   public void replace(AccessSection section) {
     for (Permission permission : section.getPermissions()) {
       for (PermissionRule rule : permission.getRules()) {
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 9e1165f..b52844d 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
@@ -667,7 +667,7 @@
             // We only fire gitRefUpdated for direct refs updates.
             // Events for change refs are fired when they are created.
             //
-            gitRefUpdated.fire(project.getNameKey(), c);
+            gitRefUpdated.fire(project.getNameKey(), c, user.getAccount());
             hooks.doRefUpdatedHook(
                 new Branch.NameKey(project.getNameKey(), c.getRefName()),
                 c.getOldId(),
@@ -680,7 +680,7 @@
     SubmoduleOp op = subOpProvider.get();
     try (MergeOpRepoManager orm = ormProvider.get()) {
       orm.setContext(db, TimeUtil.nowTs(), user);
-      op.updateSuperProjects(db, branches, "receiveID", orm);
+      op.updateSuperProjects(branches, "receiveID", orm);
     } catch (SubmoduleException e) {
       log.error("Can't update the superprojects", e);
     }
@@ -937,7 +937,7 @@
           case UPDATE_NONFASTFORWARD:
             try {
               ProjectConfig cfg = new ProjectConfig(project.getNameKey());
-              cfg.load(repo, cmd.getNewId());
+              cfg.load(rp.getRevWalk(), cmd.getNewId());
               if (!cfg.getValidationErrors().isEmpty()) {
                 addError("Invalid project configuration:");
                 for (ValidationError err : cfg.getValidationErrors()) {
@@ -1208,7 +1208,7 @@
     @Option(name = "--hashtag", aliases = {"-t"}, metaVar = "HASHTAG",
         usage = "add hashtag to changes")
     void addHashtag(String token) throws CmdLineException {
-      if (!notesMigration.enabled()) {
+      if (!notesMigration.readChanges()) {
         throw clp.reject("cannot add hashtags; noteDb is disabled");
       }
       String hashtag = cleanupHashtag(token);
@@ -1787,10 +1787,12 @@
             .setRequestScopePropagator(requestScopePropagator)
             .setSendMail(true)
             .setUpdateRef(true));
-        bu.addOp(
-            changeId,
-            hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
-              .setRunHooks(false));
+        if (!magicBranch.hashtags.isEmpty()) {
+          bu.addOp(
+              changeId,
+              hashtagsFactory.create(new HashtagsInput(magicBranch.hashtags))
+                .setRunHooks(false));
+        }
         if (!Strings.isNullOrEmpty(magicBranch.topic)) {
           bu.addOp(
               changeId,
@@ -2178,7 +2180,7 @@
 
       PatchSet newPatchSet = replaceOp.getPatchSet();
       gitRefUpdated.fire(project.getNameKey(), newPatchSet.getRefName(),
-          ObjectId.zeroId(), newCommit);
+          ObjectId.zeroId(), newCommit, user.getAccount());
 
       if (magicBranch != null && magicBranch.submit) {
         submit(changeCtl, newPatchSet);
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 63c71a1..4e804df 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
@@ -16,7 +16,6 @@
 
 import com.google.common.collect.HashMultimap;
 import com.google.common.collect.Multimap;
-import com.google.common.collect.Sets;
 import com.google.gerrit.common.ChangeHooks;
 import com.google.gerrit.common.Nullable;
 import com.google.gerrit.common.data.SubscribeSection;
@@ -25,13 +24,13 @@
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.client.SubmoduleSubscription;
-import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.server.GerritPersonIdent;
 import com.google.gerrit.server.config.GerritServerConfig;
 import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
 import com.google.gerrit.server.git.MergeOpRepoManager.OpenRepo;
 import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
 import com.google.inject.Inject;
 
 import org.eclipse.jgit.dircache.DirCache;
@@ -71,7 +70,7 @@
   private final PersonIdent myIdent;
   private final GitReferenceUpdated gitRefUpdated;
   private final ProjectCache projectCache;
-  private final Set<Branch.NameKey> updatedSubscribers;
+  private final ProjectState.Factory projectStateFactory;
   private final Account account;
   private final ChangeHooks changeHooks;
   private final boolean verboseSuperProject;
@@ -85,19 +84,20 @@
       @GerritServerConfig Config cfg,
       GitReferenceUpdated gitRefUpdated,
       ProjectCache projectCache,
+      ProjectState.Factory projectStateFactory,
       @Nullable Account account,
       ChangeHooks changeHooks) {
     this.gitmodulesFactory = gitmodulesFactory;
     this.myIdent = myIdent;
     this.gitRefUpdated = gitRefUpdated;
     this.projectCache = projectCache;
+    this.projectStateFactory = projectStateFactory;
     this.account = account;
     this.changeHooks = changeHooks;
     this.verboseSuperProject = cfg.getBoolean("submodule",
         "verboseSuperprojectUpdate", true);
     this.enableSuperProjectSubscriptions = cfg.getBoolean("submodule",
         "enableSuperProjectSubscriptions", true);
-    updatedSubscribers = new HashSet<>();
   }
 
   public Collection<Branch.NameKey> getDestinationBranches(Branch.NameKey src,
@@ -143,7 +143,8 @@
     Collection<SubmoduleSubscription> ret = new ArrayList<>();
     Project.NameKey project = branch.getParentKey();
     ProjectConfig cfg = projectCache.get(project).getConfig();
-    for (SubscribeSection s : cfg.getSubscribeSections(branch)) {
+    for (SubscribeSection s : projectStateFactory.create(cfg)
+        .getSubscribeSections(branch)) {
       Collection<Branch.NameKey> branches =
           getDestinationBranches(branch, s, orm);
       for (Branch.NameKey targetBranch : branches) {
@@ -156,7 +157,7 @@
     return ret;
   }
 
-  protected void updateSuperProjects(ReviewDb db,
+  protected void updateSuperProjects(
       Collection<Branch.NameKey> updatedBranches, String updateId,
       MergeOpRepoManager orm) throws SubmoduleException {
     if (!enableSuperProjectSubscriptions) {
@@ -165,32 +166,50 @@
     }
     this.updateId = updateId;
     logDebug("Updating superprojects");
-    // These (repo/branch) will be updated later with all the given
-    // individual submodule subscriptions
+
     Multimap<Branch.NameKey, SubmoduleSubscription> targets =
         HashMultimap.create();
 
-    try {
-      for (Branch.NameKey updatedBranch : updatedBranches) {
-        for (SubmoduleSubscription sub :
-          superProjectSubscriptionsForSubmoduleBranch(updatedBranch, orm)) {
-          targets.put(sub.getSuperProject(), sub);
+    for (Branch.NameKey updatedBranch : updatedBranches) {
+      logDebug("Now processing " + updatedBranch);
+      Set<Branch.NameKey> checkedTargets = new HashSet<>();
+      Set<Branch.NameKey> targetsToProcess = new HashSet<>();
+      targetsToProcess.add(updatedBranch);
+
+      while (!targetsToProcess.isEmpty()) {
+        Set<Branch.NameKey> newTargets = new HashSet<>();
+        for (Branch.NameKey b : targetsToProcess) {
+          try {
+            Collection<SubmoduleSubscription> subs =
+                superProjectSubscriptionsForSubmoduleBranch(b, orm);
+            for (SubmoduleSubscription sub : subs) {
+              Branch.NameKey dst = sub.getSuperProject();
+              targets.put(dst, sub);
+              newTargets.add(dst);
+            }
+          } catch (IOException e) {
+            throw new SubmoduleException("Cannot find superprojects for " + b, e);
+          }
         }
+        logDebug("adding to done " + targetsToProcess);
+        checkedTargets.addAll(targetsToProcess);
+        logDebug("completely done with " + checkedTargets);
+
+        Set<Branch.NameKey> intersection = new HashSet<>(checkedTargets);
+        intersection.retainAll(newTargets);
+        if (!intersection.isEmpty()) {
+          throw new SubmoduleException("Possible circular subscription involving " + updatedBranch);
+        }
+
+        targetsToProcess = newTargets;
       }
-    } catch (IOException e) {
-      throw new SubmoduleException("Could not calculate all superprojects");
     }
-    updatedSubscribers.addAll(updatedBranches);
-    // Update subscribers.
-    for (Branch.NameKey dest : targets.keySet()) {
+
+    for (Branch.NameKey dst : targets.keySet()) {
       try {
-        if (!updatedSubscribers.add(dest)) {
-          log.error("Possible circular subscription involving " + dest);
-        } else {
-          updateGitlinks(db, dest, targets.get(dest), orm);
-        }
+        updateGitlinks(dst, targets.get(dst), orm);
       } catch (SubmoduleException e) {
-        log.warn("Cannot update gitlinks for " + dest, e);
+        throw new SubmoduleException("Cannot update gitlinks for " + dst, e);
       }
     }
   }
@@ -202,7 +221,7 @@
    * @param updates submodule updates which should be updated to.
    * @throws SubmoduleException
    */
-  private void updateGitlinks(ReviewDb db, Branch.NameKey subscriber,
+  private void updateGitlinks(Branch.NameKey subscriber,
       Collection<SubmoduleSubscription> updates, MergeOpRepoManager orm)
           throws SubmoduleException {
     PersonIdent author = null;
@@ -324,7 +343,7 @@
       switch (rfu.update()) {
         case NEW:
         case FAST_FORWARD:
-          gitRefUpdated.fire(subscriber.getParentKey(), rfu);
+          gitRefUpdated.fire(subscriber.getParentKey(), rfu, account);
           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
@@ -340,8 +359,6 @@
         default:
           throw new IOException(rfu.getResult().name());
       }
-      // Recursive call: update subscribers of the subscriber
-      updateSuperProjects(db, Sets.newHashSet(subscriber), updateId, orm);
     } catch (IOException e) {
       throw new SubmoduleException("Cannot update gitlinks for "
           + subscriber.get(), e);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
index ecba568..dc927a6 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/VersionedMetaData.java
@@ -137,12 +137,36 @@
    */
   public void load(Repository db, ObjectId id) throws IOException,
       ConfigInvalidException {
-    reader = db.newObjectReader();
+    try (RevWalk walk = new RevWalk(db)) {
+      load(walk, id);
+    }
+  }
+
+  /**
+   * Load a specific version from an open walk.
+   * <p>
+   * This method is primarily useful for applying updates to a specific revision
+   * that was shown to an end-user in the user interface. If there are conflicts
+   * with another user's concurrent changes, these will be automatically
+   * detected at commit time.
+   * <p>
+   * The caller retains ownership of the walk and is responsible for closing
+   * it. However, this instance does not hold a reference to the walk or the
+   * repository after the call completes, allowing the application to retain
+   * this object for long periods of time.
+   *
+   * @param walk open walk to access to access.
+   * @param id revision to load.
+   * @throws IOException
+   * @throws ConfigInvalidException
+   */
+  public void load(RevWalk walk, ObjectId id) throws IOException,
+     ConfigInvalidException {
+    this.reader = walk.getObjectReader();
     try {
       revision = id != null ? new RevWalk(reader).parseCommit(id) : null;
       onLoad();
     } finally {
-      reader.close();
       reader = null;
     }
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
index 01ae0b8..5e70b91 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/git/strategy/SubmitStrategyOp.java
@@ -145,7 +145,7 @@
       logDebug("Loading new configuration from {}", RefNames.REFS_CONFIG);
       try {
         ProjectConfig cfg = new ProjectConfig(getProject());
-        cfg.load(ctx.getRepository(), commit);
+        cfg.load(ctx.getRevWalk(), commit);
       } catch (Exception e) {
         throw new IntegrationException("Submit would store invalid"
             + " project configuration " + commit.name() + " for "
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
index 89daa1d..75f9f82 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/mail/DeleteReviewerSender.java
@@ -34,7 +34,7 @@
 public class DeleteReviewerSender extends ReplyToChangeSender {
   private final Set<Account.Id> reviewers = new HashSet<>();
 
-  public static interface Factory extends
+  public interface Factory extends
       ReplyToChangeSender.Factory<DeleteReviewerSender> {
     @Override
     DeleteReviewerSender create(Project.NameKey project, Change.Id change);
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
index 3d5de08..e763e0c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/AbstractChangeNotes.java
@@ -124,7 +124,7 @@
     }
     boolean read = args.migration.readChanges();
     boolean readOrWrite = read || args.migration.writeChanges();
-    if (!readOrWrite || !autoRebuild) {
+    if (!readOrWrite && !autoRebuild) {
       loadDefaults();
       return self();
     }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
index 4d2a719..0dfd8c9 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeBundle.java
@@ -515,7 +515,7 @@
    * messages below.
    */
   @AutoValue
-  static abstract class ChangeMessageCandidate {
+  abstract static class ChangeMessageCandidate {
     static ChangeMessageCandidate create(ChangeMessage cm) {
       return new AutoValue_ChangeBundle_ChangeMessageCandidate(
           cm.getAuthor(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
index 6daf457..9aa69bf 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/notedb/ChangeRebuilderImpl.java
@@ -47,6 +47,7 @@
 import com.google.gerrit.reviewdb.client.PatchSet;
 import com.google.gerrit.reviewdb.client.PatchSetApproval;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.reviewdb.server.ReviewDb;
 import com.google.gerrit.reviewdb.server.ReviewDbUtil;
 import com.google.gerrit.server.GerritPersonIdent;
@@ -54,6 +55,7 @@
 import com.google.gerrit.server.account.AccountCache;
 import com.google.gerrit.server.config.AnonymousCowardName;
 import com.google.gerrit.server.git.ChainedReceiveCommands;
+import com.google.gerrit.server.notedb.NoteDbUpdateManager.OpenRepo;
 import com.google.gerrit.server.patch.PatchListCache;
 import com.google.gerrit.server.project.NoSuchChangeException;
 import com.google.gerrit.server.project.ProjectCache;
@@ -69,6 +71,7 @@
 import org.eclipse.jgit.lib.ObjectInserter;
 import org.eclipse.jgit.lib.PersonIdent;
 import org.eclipse.jgit.lib.ProgressMonitor;
+import org.eclipse.jgit.lib.Ref;
 import org.eclipse.jgit.lib.Repository;
 import org.eclipse.jgit.lib.TextProgressMonitor;
 import org.eclipse.jgit.revwalk.RevCommit;
@@ -240,7 +243,8 @@
     events.addAll(getHashtagsEvents(change, manager));
 
     // Delete ref only after hashtags have been read
-    deleteRef(change, manager.getChangeRepo().cmds);
+    deleteChangeMetaRef(change, manager.getChangeRepo().cmds);
+    deleteDraftRefs(change, manager.getAllUsersRepo());
 
     Integer minPsNum = getMinPatchSetNum(bundle);
     Set<PatchSet.Id> psIds =
@@ -473,7 +477,7 @@
     return new PatchSet.Id(change.getId(), psId);
   }
 
-  private void deleteRef(Change change, ChainedReceiveCommands cmds)
+  private void deleteChangeMetaRef(Change change, ChainedReceiveCommands cmds)
       throws IOException {
     String refName = changeMetaRef(change.getId());
     Optional<ObjectId> old = cmds.get(refName);
@@ -482,6 +486,15 @@
     }
   }
 
+  private void deleteDraftRefs(Change change, OpenRepo allUsersRepo)
+      throws IOException {
+    for (Ref r : allUsersRepo.repo.getRefDatabase()
+        .getRefs(RefNames.refsDraftCommentsPrefix(change.getId())).values()) {
+      allUsersRepo.cmds.add(
+          new ReceiveCommand(r.getObjectId(), ObjectId.zeroId(), r.getName()));
+    }
+  }
+
   private static final Ordering<Event> EVENT_ORDER = new Ordering<Event>() {
     @Override
     public int compare(Event a, Event b) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
index fd02042..d75a553 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/patch/AutoMerger.java
@@ -198,7 +198,6 @@
       builder.finish();
       treeId = dc.writeTree(ins);
     }
-    ins.flush();
 
     return commit(repo, rw, ins, refName, treeId, merge);
   }
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
index bc4d8f3..e41acbe 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateBranch.java
@@ -148,7 +148,9 @@
           case FAST_FORWARD:
           case NEW:
           case NO_CHANGE:
-            referenceUpdated.fire(name.getParentKey(), u, ReceiveCommand.Type.CREATE);
+            referenceUpdated.fire(
+                name.getParentKey(), u, ReceiveCommand.Type.CREATE,
+                identifiedUser.get().getAccount());
             hooks.doRefUpdatedHook(name, u, identifiedUser.get().getAccount());
             break;
           case LOCK_FAILURE:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
index a73ed0e..ecc618e 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/CreateProject.java
@@ -370,7 +370,8 @@
         Result result = ru.update();
         switch (result) {
           case NEW:
-            referenceUpdated.fire(project, ru, ReceiveCommand.Type.CREATE);
+            referenceUpdated.fire(project, ru, ReceiveCommand.Type.CREATE,
+                currentUser.get().getAccountId());
             break;
           case FAST_FORWARD:
           case FORCED:
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
index b4c25b4..43e1422 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranch.java
@@ -108,7 +108,8 @@
         case NO_CHANGE:
         case FAST_FORWARD:
         case FORCED:
-          referenceUpdated.fire(rsrc.getNameKey(), u, ReceiveCommand.Type.DELETE);
+          referenceUpdated.fire(rsrc.getNameKey(), u, ReceiveCommand.Type.DELETE,
+              identifiedUser.get().getAccount());
           hooks.doRefUpdatedHook(rsrc.getBranchKey(), u, identifiedUser.get().getAccount());
           break;
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
index b851f9e..daecc1d 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/DeleteBranches.java
@@ -162,7 +162,8 @@
   }
 
   private void postDeletion(ProjectResource project, ReceiveCommand cmd) {
-    referenceUpdated.fire(project.getNameKey(), cmd);
+    referenceUpdated.fire(project.getNameKey(), cmd,
+        identifiedUser.get().getAccount());
     Branch.NameKey branchKey =
         new Branch.NameKey(project.getNameKey(), cmd.getRefName());
     hooks.doRefUpdatedHook(branchKey, cmd.getOldId(), cmd.getNewId(),
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
index b8c5fd8..be6f892 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetAccess.java
@@ -14,9 +14,9 @@
 
 package com.google.gerrit.server.project;
 
-import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.BiMap;
+import com.google.common.collect.ImmutableBiMap;
 import com.google.common.collect.Iterables;
-import com.google.common.collect.Maps;
 import com.google.gerrit.common.data.AccessSection;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
@@ -53,16 +53,14 @@
 @Singleton
 public class GetAccess implements RestReadView<ProjectResource> {
 
-  private static final ImmutableMap<PermissionRule.Action, PermissionRuleInfo.Action> ACTION_TYPE =
-      Maps.immutableEnumMap(
-          new ImmutableMap.Builder<PermissionRule.Action, PermissionRuleInfo.Action>()
-              .put(PermissionRule.Action.ALLOW, PermissionRuleInfo.Action.ALLOW)
-              .put(PermissionRule.Action.BATCH, PermissionRuleInfo.Action.BATCH)
-              .put(PermissionRule.Action.BLOCK, PermissionRuleInfo.Action.BLOCK)
-              .put(PermissionRule.Action.DENY, PermissionRuleInfo.Action.DENY)
-              .put(PermissionRule.Action.INTERACTIVE,
-                  PermissionRuleInfo.Action.INTERACTIVE)
-              .build());
+  public static final BiMap<PermissionRule.Action,
+      PermissionRuleInfo.Action> ACTION_TYPE = ImmutableBiMap.of(
+          PermissionRule.Action.ALLOW, PermissionRuleInfo.Action.ALLOW,
+          PermissionRule.Action.BATCH, PermissionRuleInfo.Action.BATCH,
+          PermissionRule.Action.BLOCK, PermissionRuleInfo.Action.BLOCK,
+          PermissionRule.Action.DENY, PermissionRuleInfo.Action.DENY,
+          PermissionRule.Action.INTERACTIVE,
+          PermissionRuleInfo.Action.INTERACTIVE);
 
   private final Provider<CurrentUser> self;
   private final GroupControl.Factory groupControlFactory;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
index 469312d..3d0ff05c 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/Module.java
@@ -42,10 +42,12 @@
     put(PROJECT_KIND).to(PutProject.class);
     get(PROJECT_KIND).to(GetProject.class);
     get(PROJECT_KIND, "description").to(GetDescription.class);
-    get(PROJECT_KIND, "access").to(GetAccess.class);
     put(PROJECT_KIND, "description").to(PutDescription.class);
     delete(PROJECT_KIND, "description").to(PutDescription.class);
 
+    get(PROJECT_KIND, "access").to(GetAccess.class);
+    post(PROJECT_KIND, "access").to(SetAccess.class);
+
     get(PROJECT_KIND, "parent").to(GetParent.class);
     put(PROJECT_KIND, "parent").to(SetParent.class);
 
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
index 29f97fb..28cb5b4 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/ProjectState.java
@@ -27,8 +27,10 @@
 import com.google.gerrit.common.data.LabelTypes;
 import com.google.gerrit.common.data.Permission;
 import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.SubscribeSection;
 import com.google.gerrit.extensions.client.InheritableBoolean;
 import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Branch;
 import com.google.gerrit.reviewdb.client.Project;
 import com.google.gerrit.reviewdb.client.RefNames;
 import com.google.gerrit.rules.PrologEnvironment;
@@ -475,6 +477,15 @@
     return null;
   }
 
+  public Collection<SubscribeSection> getSubscribeSections(
+      Branch.NameKey branch) {
+    Collection<SubscribeSection> ret = new ArrayList<>();
+    for (ProjectState s : tree()) {
+      ret.addAll(s.getConfig().getSubscribeSections(branch));
+    }
+    return ret;
+  }
+
   public ThemeInfo getTheme() {
     ThemeInfo theme = this.theme;
     if (theme == null) {
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
index 86f98ff..4b0ecf0 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutConfig.java
@@ -196,7 +196,7 @@
         // Only fire hook if project was actually changed.
         if (!Objects.equals(baseRev, commitRev)) {
           gitRefUpdated.fire(projectName, RefNames.REFS_CONFIG,
-              baseRev, commitRev);
+              baseRev, commitRev, user.get().asIdentifiedUser().getAccount());
           hooks.doRefUpdatedHook(
             new Branch.NameKey(projectName, RefNames.REFS_CONFIG),
             baseRev, commitRev, user.get().asIdentifiedUser().getAccount());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
index 7cf426d..b136821 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/PutDescription.java
@@ -94,7 +94,7 @@
       // Only fire hook if project was actually changed.
       if (!Objects.equals(baseRev, commitRev)) {
         gitRefUpdated.fire(resource.getNameKey(), RefNames.REFS_CONFIG,
-            baseRev, commitRev);
+            baseRev, commitRev, user.getAccount());
         hooks.doRefUpdatedHook(
           new Branch.NameKey(resource.getNameKey(), RefNames.REFS_CONFIG),
           baseRev, commitRev, user.getAccount());
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
new file mode 100644
index 0000000..258b386
--- /dev/null
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/SetAccess.java
@@ -0,0 +1,328 @@
+// Copyright (C) 2016 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.project;
+
+import com.google.common.base.MoreObjects;
+import com.google.common.base.Strings;
+import com.google.gerrit.common.ChangeHooks;
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupDescription;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.extensions.api.access.AccessSectionInfo;
+import com.google.gerrit.extensions.api.access.PermissionInfo;
+import com.google.gerrit.extensions.api.access.PermissionRuleInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInfo;
+import com.google.gerrit.extensions.api.access.ProjectAccessInput;
+import com.google.gerrit.extensions.restapi.AuthException;
+import com.google.gerrit.extensions.restapi.BadRequestException;
+import com.google.gerrit.extensions.restapi.ResourceConflictException;
+import com.google.gerrit.extensions.restapi.ResourceNotFoundException;
+import com.google.gerrit.extensions.restapi.RestModifyView;
+import com.google.gerrit.extensions.restapi.UnprocessableEntityException;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.Branch;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.RefNames;
+import com.google.gerrit.server.CurrentUser;
+import com.google.gerrit.server.IdentifiedUser;
+import com.google.gerrit.server.account.GroupBackend;
+import com.google.gerrit.server.config.AllProjectsName;
+import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+import com.google.inject.Singleton;
+
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.revwalk.RevCommit;
+
+import java.io.IOException;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Map;
+
+@Singleton
+public class SetAccess implements
+    RestModifyView<ProjectResource, ProjectAccessInput> {
+  protected final GroupBackend groupBackend;
+  private final GroupsCollection groupsCollection;
+  private final Provider<MetaDataUpdate.User> metaDataUpdateFactory;
+  private final AllProjectsName allProjects;
+  private final Provider<SetParent> setParent;
+  private final ChangeHooks hooks;
+  private final GitReferenceUpdated gitRefUpdated;
+  private final GetAccess getAccess;
+  private final ProjectCache projectCache;
+  private final Provider<IdentifiedUser> identifiedUser;
+
+  @Inject
+  private SetAccess(GroupBackend groupBackend,
+      Provider<MetaDataUpdate.User> metaDataUpdateFactory,
+      AllProjectsName allProjects,
+      Provider<SetParent> setParent,
+      ChangeHooks hooks,
+      GitReferenceUpdated gitRefUpdated,
+      GroupsCollection groupsCollection,
+      ProjectCache projectCache,
+      GetAccess getAccess,
+      Provider<IdentifiedUser> identifiedUser) {
+    this.groupBackend = groupBackend;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    this.allProjects = allProjects;
+    this.setParent = setParent;
+    this.groupsCollection = groupsCollection;
+    this.hooks = hooks;
+    this.gitRefUpdated = gitRefUpdated;
+    this.getAccess = getAccess;
+    this.projectCache = projectCache;
+    this.identifiedUser = identifiedUser;
+  }
+
+  @Override
+  public ProjectAccessInfo apply(ProjectResource rsrc,
+      ProjectAccessInput input)
+      throws ResourceNotFoundException, ResourceConflictException,
+      IOException, AuthException, BadRequestException,
+      UnprocessableEntityException{
+    List<AccessSection> removals = getAccessSections(input.remove);
+    List<AccessSection> additions = getAccessSections(input.add);
+    MetaDataUpdate.User metaDataUpdateUser = metaDataUpdateFactory.get();
+
+    ProjectControl projectControl = rsrc.getControl();
+    ProjectConfig config;
+    ObjectId base;
+
+    Project.NameKey newParentProjectName = input.parent == null ?
+        null : new Project.NameKey(input.parent);
+
+    try (MetaDataUpdate md = metaDataUpdateUser.create(rsrc.getNameKey())) {
+      config = ProjectConfig.read(md);
+      base = config.getRevision();
+
+      // Perform removal checks
+      for (AccessSection section : removals) {
+        boolean isGlobalCapabilities =
+            AccessSection.GLOBAL_CAPABILITIES.equals(section.getName());
+
+        if (isGlobalCapabilities) {
+          checkGlobalCapabilityPermissions(config.getName());
+        } else if (!projectControl.controlForRef(section.getName()).isOwner()) {
+          throw new AuthException("You are not allowed to edit permissions"
+              + "for ref: " + section.getName());
+        }
+      }
+      // Perform addition checks
+      for (AccessSection section : additions) {
+        String name = section.getName();
+        boolean isGlobalCapabilities =
+            AccessSection.GLOBAL_CAPABILITIES.equals(name);
+
+        if (isGlobalCapabilities) {
+          checkGlobalCapabilityPermissions(config.getName());
+        } else {
+          if (!AccessSection.isValid(name)) {
+            throw new BadRequestException("invalid section name");
+          }
+          if (!projectControl.controlForRef(name).isOwner()) {
+            throw new AuthException("You are not allowed to edit permissions"
+                + "for ref: " + name);
+          }
+          RefControl.validateRefPattern(name);
+        }
+
+        // Check all permissions for soundness
+        for (Permission p : section.getPermissions()) {
+          if (isGlobalCapabilities
+              && !GlobalCapability.isCapability(p.getName())) {
+            throw new BadRequestException("Cannot add non-global capability "
+                + p.getName() + " to global capabilities");
+          }
+        }
+      }
+
+      // Apply removals
+      for (AccessSection section : removals) {
+        if (section.getPermissions().isEmpty()) {
+          // Remove entire section
+          config.remove(config.getAccessSection(section.getName()));
+        }
+        // Remove specific permissions
+        for (Permission p : section.getPermissions()) {
+          if (p.getRules().isEmpty()) {
+            config.remove(config.getAccessSection(section.getName()), p);
+          } else {
+            for (PermissionRule r : p.getRules()) {
+              config.remove(config.getAccessSection(section.getName()), p, r);
+            }
+          }
+        }
+      }
+
+      // Apply additions
+      for (AccessSection section : additions) {
+        AccessSection currentAccessSection =
+            config.getAccessSection(section.getName());
+
+        if (currentAccessSection == null) {
+          // Add AccessSection
+          config.replace(section);
+        } else {
+          for (Permission p : section.getPermissions()) {
+            Permission currentPermission =
+                currentAccessSection.getPermission(p.getName());
+            if (currentPermission == null) {
+              // Add Permission
+              currentAccessSection.addPermission(p);
+            } else {
+              for (PermissionRule r : p.getRules()) {
+                // AddPermissionRule
+                currentPermission.add(r);
+              }
+            }
+          }
+        }
+      }
+
+      if (newParentProjectName != null &&
+          !config.getProject().getNameKey().equals(allProjects) &&
+          !config.getProject().getParent(allProjects)
+              .equals(newParentProjectName)) {
+        try {
+          setParent.get().validateParentUpdate(projectControl,
+              MoreObjects.firstNonNull(newParentProjectName, allProjects).get(),
+              true);
+        } catch (UnprocessableEntityException e) {
+          throw new ResourceConflictException(e.getMessage(), e);
+        }
+        config.getProject().setParentName(newParentProjectName);
+      }
+
+      if (!Strings.isNullOrEmpty(input.message)) {
+        if (!input.message.endsWith("\n")) {
+          input.message += "\n";
+        }
+        md.setMessage(input.message);
+      } else {
+        md.setMessage("Modify access rules\n");
+      }
+
+      updateProjectConfig(projectControl.getUser(), config, md, base);
+    } catch (InvalidNameException e) {
+      throw new BadRequestException(e.toString());
+    } catch (ConfigInvalidException e) {
+      throw new ResourceConflictException(rsrc.getName());
+    }
+
+    return getAccess.apply(rsrc.getNameKey());
+  }
+
+  private List<AccessSection> getAccessSections(
+      Map<String, AccessSectionInfo> sectionInfos)
+      throws UnprocessableEntityException {
+    List<AccessSection> sections = new LinkedList<>();
+    if (sectionInfos == null) {
+      return sections;
+    }
+
+    for (Map.Entry<String, AccessSectionInfo> entry :
+      sectionInfos.entrySet()) {
+      AccessSection accessSection = new AccessSection(entry.getKey());
+
+      if (entry.getValue().permissions == null) {
+        continue;
+      }
+
+      for (Map.Entry<String, PermissionInfo> permissionEntry : entry
+          .getValue().permissions
+          .entrySet()) {
+        Permission p = new Permission(permissionEntry.getKey());
+        if (permissionEntry.getValue().exclusive != null) {
+          p.setExclusiveGroup(permissionEntry.getValue().exclusive);
+        }
+
+        if (permissionEntry.getValue().rules == null) {
+          continue;
+        }
+        for (Map.Entry<String, PermissionRuleInfo> permissionRuleInfoEntry :
+            permissionEntry.getValue().rules.entrySet()) {
+          PermissionRuleInfo pri = permissionRuleInfoEntry.getValue();
+
+          GroupDescription.Basic group = groupsCollection
+              .parseId(permissionRuleInfoEntry.getKey());
+          if (group == null) {
+            throw new UnprocessableEntityException(
+              permissionRuleInfoEntry.getKey() + " is not a valid group ID");
+          }
+          PermissionRule r = new PermissionRule(
+              GroupReference.forGroup(group));
+          if (pri != null) {
+            if (pri.max != null) {
+              r.setMax(pri.max);
+            }
+            if (pri.min != null) {
+              r.setMin(pri.min);
+            }
+            r.setAction(GetAccess.ACTION_TYPE.inverse().get(pri.action));
+            r.setForce(pri.force);
+          }
+          p.add(r);
+        }
+        accessSection.getPermissions().add(p);
+      }
+      sections.add(accessSection);
+    }
+    return sections;
+  }
+
+  private void updateProjectConfig(CurrentUser user,
+      ProjectConfig config, MetaDataUpdate md, ObjectId base)
+      throws IOException {
+    RevCommit commit = config.commit(md);
+
+    Account account = user.isIdentifiedUser()
+        ? user.asIdentifiedUser().getAccount()
+        : null;
+    gitRefUpdated.fire(config.getProject().getNameKey(), RefNames.REFS_CONFIG,
+        base, commit.getId(), account);
+
+    projectCache.evict(config.getProject());
+
+    hooks.doRefUpdatedHook(
+        new Branch.NameKey(config.getProject().getNameKey(),
+            RefNames.REFS_CONFIG),
+        base, commit.getId(), user.asIdentifiedUser().getAccount());
+  }
+
+  private void checkGlobalCapabilityPermissions(Project.NameKey projectName)
+    throws BadRequestException, AuthException {
+
+    if (!allProjects.equals(projectName)) {
+      throw new BadRequestException("Cannot edit global capabilities "
+        + "for projects other than " + allProjects.get());
+    }
+
+    if (!identifiedUser.get().getCapabilities().canAdministrateServer()) {
+      throw new AuthException("Editing global capabilities "
+        + "requires " + GlobalCapability.ADMINISTRATE_SERVER);
+    }
+  }
+}
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
index 5eed391..ed7b134 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/query/change/ChangeData.java
@@ -844,7 +844,7 @@
    * @throws OrmException an error occurred reading the database.
    */
   public Collection<PatchSet> visiblePatchSets() throws OrmException {
-    return FluentIterable.from(patchSets()).filter(new Predicate<PatchSet>() {
+    Predicate<PatchSet> predicate = new Predicate<PatchSet>() {
       @Override
       public boolean apply(PatchSet input) {
         try {
@@ -852,7 +852,9 @@
         } catch (OrmException e) {
           return false;
         }
-      }}).toList();
+      }
+    };
+    return FluentIterable.from(patchSets()).filter(predicate).toList();
   }
 
 public void setPatchSets(Collection<PatchSet> patchSets) {
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
index e9b65c8..ec9b716 100644
--- a/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
+++ b/gerrit-server/src/test/java/com/google/gerrit/server/query/change/AbstractQueryChangesTest.java
@@ -66,6 +66,7 @@
 import com.google.gerrit.server.git.validators.CommitValidators;
 import com.google.gerrit.server.index.change.ChangeField;
 import com.google.gerrit.server.index.change.ChangeIndexCollection;
+import com.google.gerrit.server.index.change.ChangeIndexer;
 import com.google.gerrit.server.notedb.ChangeNotes;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.project.RefControl;
@@ -119,6 +120,7 @@
   @Inject protected GerritApi gApi;
   @Inject protected IdentifiedUser.GenericFactory userFactory;
   @Inject protected ChangeIndexCollection indexes;
+  @Inject protected ChangeIndexer indexer;
   @Inject protected InMemoryDatabase schemaFactory;
   @Inject protected InMemoryRepositoryManager repoManager;
   @Inject protected InternalChangeQuery internalChangeQuery;
@@ -1087,8 +1089,24 @@
 
   @Test
   public void byHashtagWithoutNoteDb() throws Exception {
-    assume().that(notesMigration.enabled()).isFalse();
-    setUpHashtagChanges();
+    assume().that(notesMigration.readChanges()).isFalse();
+
+    notesMigration.setWriteChanges(true);
+    notesMigration.setReadChanges(true);
+    db.close();
+    db = schemaFactory.open();
+    List<Change> changes;
+    try {
+      changes = setUpHashtagChanges();
+      notesMigration.setWriteChanges(false);
+      notesMigration.setReadChanges(false);
+    } finally {
+      db.close();
+    }
+    db = schemaFactory.open();
+    for (Change c : changes) {
+      indexer.index(db, c); // Reindex without hashtag field.
+    }
     assertQuery("hashtag:foo");
     assertQuery("hashtag:bar");
     assertQuery("hashtag:\" bar \"");
@@ -1415,7 +1433,7 @@
 
   @Test
   public void prepopulatedFields() throws Exception {
-    assume().that(notesMigration.enabled()).isFalse();
+    assume().that(notesMigration.readChanges()).isFalse();
     TestRepository<Repo> repo = createProject("repo");
     Change change = insert(repo, newChange(repo));
 
@@ -1444,7 +1462,7 @@
 
   @Test
   public void prepopulateOnlyRequestedFields() throws Exception {
-    assume().that(notesMigration.enabled()).isFalse();
+    assume().that(notesMigration.readChanges()).isFalse();
     TestRepository<Repo> repo = createProject("repo");
     Change change = insert(repo, newChange(repo));
 
diff --git a/lib/BUCK b/lib/BUCK
index 0c424ad..65bdd5e 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -257,8 +257,8 @@
 
 maven_jar(
   name = 'javassist',
-  id = 'org.javassist:javassist:3.18.1-GA',
-  sha1 = 'd9a09f7732226af26bf99f19e2cffe0ae219db5b',
+  id = 'org.javassist:javassist:3.20.0-GA',
+  sha1 = 'a9cbcdfb7e9f86fbc74d3afae65f2248bfbf82a0',
   license = 'DO_NOT_DISTRIBUTE',
 )
 
diff --git a/lib/codemirror/BUCK b/lib/codemirror/BUCK
index 9b8a146..67779ab 100644
--- a/lib/codemirror/BUCK
+++ b/lib/codemirror/BUCK
@@ -1,14 +1,14 @@
 include_defs('//lib/maven.defs')
 include_defs('//lib/codemirror/cm.defs')
 
-VERSION = '5.14.2'
+VERSION = '5.15.2'
 TOP = 'META-INF/resources/webjars/codemirror/%s' % VERSION
 TOP_MINIFIED = 'META-INF/resources/webjars/codemirror-minified/%s' % VERSION
 
 maven_jar(
   name = 'codemirror-minified',
   id = 'org.webjars.npm:codemirror-minified:' + VERSION,
-  sha1 = 'c69056d2a0e07432326e67ea8fe2abb91a065030',
+  sha1 = '222152d6c4f9da6e812378499894e6f86688ac2a',
   attach_source = False,
   license = 'codemirror-minified',
   visibility = [],
@@ -17,7 +17,7 @@
 maven_jar(
   name = 'codemirror-original',
   id = 'org.webjars.npm:codemirror:' + VERSION,
-  sha1 = '1ed9697531be85c85edb70fcdf58f10045563f7b',
+  sha1 = '2f0c3e94bb133df1f07800ff0e361da8ac791442',
   attach_source = False,
   license = 'codemirror-original',
   visibility = [],
diff --git a/lib/easymock/BUCK b/lib/easymock/BUCK
index c0cb77b..93640a0 100644
--- a/lib/easymock/BUCK
+++ b/lib/easymock/BUCK
@@ -2,9 +2,9 @@
 
 maven_jar(
   name = 'easymock',
-  id = 'org.easymock:easymock:3.3.1', # When bumping the version
+  id = 'org.easymock:easymock:3.4', # When bumping the version
   # number, make sure to also move powermock to a compatible version
-  sha1 = 'a497d7f00c9af78b72b6d8f24762d9210309148a',
+  sha1 = '9fdeea183a399f25c2469497612cad131e920fa3',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':cglib-2_2',
@@ -22,8 +22,8 @@
 
 maven_jar(
   name = 'objenesis',
-  id = 'org.objenesis:objenesis:2.1',
-  sha1 = '87c0ea803b69252868d09308b4618f766f135a96',
+  id = 'org.objenesis:objenesis:2.2',
+  sha1 = '3fb533efdaa50a768c394aa4624144cf8df17845',
   license = 'DO_NOT_DISTRIBUTE',
   visibility = ['//lib/powermock:powermock-reflect'],
   attach_source = False,
diff --git a/lib/powermock/BUCK b/lib/powermock/BUCK
index 221a640..b642457 100644
--- a/lib/powermock/BUCK
+++ b/lib/powermock/BUCK
@@ -1,12 +1,12 @@
 include_defs('//lib/maven.defs')
 
-VERSION = '1.6.2' # When bumping VERSION, make sure to also move
+VERSION = '1.6.4' # When bumping VERSION, make sure to also move
 # easymock to a compatible version
 
 maven_jar(
   name = 'powermock-module-junit4',
   id = 'org.powermock:powermock-module-junit4:' + VERSION,
-  sha1 = 'dff58978da716e000463bc1b08013d6a7cf3d696',
+  sha1 = '8692eb1d9bb8eb1310ffe8a20c2da7ee6d1b5994',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-module-junit4-common',
@@ -17,7 +17,7 @@
 maven_jar(
   name = 'powermock-module-junit4-common',
   id = 'org.powermock:powermock-module-junit4-common:' + VERSION,
-  sha1 = '48dd7406e11a14fe2ae4ab641e1f27510e896640',
+  sha1 = 'b0b578da443794ceb8224bd5f5f852aaf40f1b81',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-reflect',
@@ -28,7 +28,7 @@
 maven_jar(
   name = 'powermock-reflect',
   id = 'org.powermock:powermock-reflect:' + VERSION,
-  sha1 = '1af1bbd1207c3ecdcf64973e6f9d57dcd17cc145',
+  sha1 = '5532f4e7c42db4bca4778bc9f1afcd4b0ee0b893',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     '//lib:junit',
@@ -39,7 +39,7 @@
 maven_jar(
   name = 'powermock-api-easymock',
   id = 'org.powermock:powermock-api-easymock:' + VERSION,
-  sha1 = 'addd25742ac9fe3e0491cbd68e2515e3b06c77fd',
+  sha1 = '5c385a0d8c13f84b731b75c6e90319c532f80b45',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-api-support',
@@ -50,7 +50,7 @@
 maven_jar(
   name = 'powermock-api-support',
   id = 'org.powermock:powermock-api-support:' + VERSION,
-  sha1 = '93b21413b4ee99b7bc0dd34e1416fdca96866aaf',
+  sha1 = '314daafb761541293595630e10a3699ebc07881d',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-core',
@@ -62,7 +62,7 @@
 maven_jar(
   name = 'powermock-core',
   id = 'org.powermock:powermock-core:' + VERSION,
-  sha1 = 'ea04e79244e19dcf0c3ccf6863c5b028b4b58c9c',
+  sha1 = '85fb32e9ccba748d569fc36aef92e0b9e7f40b87',
   license = 'DO_NOT_DISTRIBUTE',
   deps = [
     ':powermock-reflect',
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
index 9a7537b..3175517 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.html
@@ -225,7 +225,7 @@
           <span class="header-actions">
             <gr-button hidden
                 class="reply"
-                primary$="[[_computeReplyButtonHighlighted(_diffDrafts)]]"
+                primary$="[[_computeReplyButtonHighlighted(_diffDrafts.*)]]"
                 hidden$="[[!_loggedIn]]"
                 on-tap="_handleReplyTap">[[_replyButtonLabel]]</gr-button>
             <gr-button class="download" on-tap="_handleDownloadTap">Download</gr-button>
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
index 428d067..831fdb8 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view.js
@@ -55,7 +55,10 @@
       },
       _commitInfo: Object,
       _changeNum: String,
-      _diffDrafts: Object,
+      _diffDrafts: {
+        type: Object,
+        value: function() { return {}; },
+      },
       _patchRange: Object,
       _allPatchSets: {
         type: Array,
@@ -69,14 +72,10 @@
       _headerContainerEl: Object,
       _headerEl: Object,
       _projectConfig: Object,
-      _boundScrollHandler: {
-        type: Function,
-        value: function() { return this._handleBodyScroll.bind(this); },
-      },
       _replyButtonLabel: {
         type: String,
         value: 'Reply',
-        computed: '_computeReplyButtonLabel(_diffDrafts)',
+        computed: '_computeReplyButtonLabel(_diffDrafts.*)',
       },
     },
 
@@ -94,11 +93,14 @@
         this._loggedIn = loggedIn;
       }.bind(this));
 
-      window.addEventListener('scroll', this._boundScrollHandler);
+      this.addEventListener('comment-save', this._handleCommentSave.bind(this));
+      this.addEventListener('comment-discard',
+          this._handleCommentDiscard.bind(this));
+      this.listen(window, 'scroll', '_handleBodyScroll');
     },
 
     detached: function() {
-      window.removeEventListener('scroll', this._boundScrollHandler);
+      this.unlisten(window, 'scroll', '_handleBodyScroll');
     },
 
     _handleBodyScroll: function(e) {
@@ -124,6 +126,67 @@
       el.classList.remove('pinned');
     },
 
+    _handleCommentSave: function(e) {
+      if (!e.target.comment.__draft) { return; }
+
+      var draft = e.target.comment;
+      draft.patch_set = draft.patch_set || this._patchRange.patchNum;
+
+      // The use of path-based notification helpers (set, push) can’t be used
+      // because the paths could contain dots in them. A new object must be
+      // created to satisfy Polymer’s dirty checking.
+      // https://github.com/Polymer/polymer/issues/3127
+      // TODO(andybons): Polyfill for Object.assign in IE.
+      var diffDrafts = Object.assign({}, this._diffDrafts);
+      if (!diffDrafts[draft.path]) {
+        diffDrafts[draft.path] = [draft];
+        this._diffDrafts = diffDrafts;
+        return;
+      }
+      for (var i = 0; i < this._diffDrafts[draft.path].length; i++) {
+        if (this._diffDrafts[draft.path][i].id === draft.id) {
+          diffDrafts[draft.path][i] = draft;
+          this._diffDrafts = diffDrafts;
+          return;
+        }
+      }
+      diffDrafts[draft.path].push(draft);
+      this._diffDrafts = diffDrafts;
+    },
+
+    _handleCommentDiscard: function(e) {
+      if (!e.target.comment.__draft) { return; }
+
+      var draft = e.target.comment;
+      if (!this._diffDrafts[draft.path]) {
+        return;
+      }
+      var index = -1;
+      for (var i = 0; i < this._diffDrafts[draft.path].length; i++) {
+        if (this._diffDrafts[draft.path][i].id === draft.id) {
+          index = i;
+          break;
+        }
+      }
+      if (index === -1) {
+        throw Error('Unable to find draft with id ' + draft.id);
+      }
+
+      draft.patch_set = draft.patch_set || this._patchRange.patchNum;
+
+      // The use of path-based notification helpers (set, push) can’t be used
+      // because the paths could contain dots in them. A new object must be
+      // created to satisfy Polymer’s dirty checking.
+      // https://github.com/Polymer/polymer/issues/3127
+      // TODO(andybons): Polyfill for Object.assign in IE.
+      var diffDrafts = Object.assign({}, this._diffDrafts);
+      diffDrafts[draft.path].splice(index, 1);
+      if (diffDrafts[draft.path].length === 0) {
+        delete diffDrafts[draft.path];
+      }
+      this._diffDrafts = diffDrafts;
+    },
+
     _handlePatchChange: function(e) {
       var patchNum = e.target.value;
       var currentPatchNum =
@@ -220,6 +283,7 @@
 
         if (this.viewState.showReplyDialog) {
           this.$.replyOverlay.open();
+          this.async(function() { this.$.replyOverlay.center(); }, 1);
           this.set('viewState.showReplyDialog', false);
         }
       }.bind(this));
@@ -313,12 +377,13 @@
       return result;
     },
 
-    _computeReplyButtonHighlighted: function(drafts) {
-      return Object.keys(drafts || {}).length > 0;
+    _computeReplyButtonHighlighted: function(changeRecord) {
+      var drafts = (changeRecord && changeRecord.base) || {};
+      return Object.keys(drafts).length > 0;
     },
 
-    _computeReplyButtonLabel: function(drafts) {
-      drafts = drafts || {};
+    _computeReplyButtonLabel: function(changeRecord) {
+      var drafts = (changeRecord && changeRecord.base) || {};
       var draftCount = Object.keys(drafts).reduce(function(count, file) {
         return count + drafts[file].length;
       }, 0);
diff --git a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
index 7fcbd32..f4a0a52 100644
--- a/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
+++ b/polygerrit-ui/app/elements/change/gr-change-view/gr-change-view_test.html
@@ -81,6 +81,42 @@
       assert.equal(replyButton.textContent, 'Reply (3)');
     });
 
+    test('comment events properly update diff drafts', function() {
+      element._patchRange = {
+        basePatchNum: 'PARENT',
+        patchNum: 2,
+      };
+      var draft = {
+        __draft: true,
+        id: 'id1',
+        path: '/foo/bar.txt',
+        text: 'hello',
+      };
+      element._handleCommentSave({target: {comment: draft}});
+      draft.patch_set = 2;
+      assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
+      draft.patch_set = null;
+      draft.text = 'hello, there';
+      element._handleCommentSave({target: {comment: draft}});
+      draft.patch_set = 2;
+      assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft]});
+      var draft2 = {
+        __draft: true,
+        id: 'id2',
+        path: '/foo/bar.txt',
+        text: 'hola',
+      };
+      element._handleCommentSave({target: {comment: draft2}});
+      draft2.patch_set = 2;
+      assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft, draft2]});
+      draft.patch_set = null;
+      element._handleCommentDiscard({target: {comment: draft}});
+      draft.patch_set = 2;
+      assert.deepEqual(element._diffDrafts, {'/foo/bar.txt': [draft2]});
+      element._handleCommentDiscard({target: {comment: draft2}});
+      assert.deepEqual(element._diffDrafts, {});
+    });
+
     test('patch num change', function(done) {
       element._changeNum = '42';
       element._patchRange = {
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
index c06ba60..976cf9b 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.html
@@ -17,6 +17,7 @@
 <link rel="import" href="../../../bower_components/polymer/polymer.html">
 <link rel="import" href="../../../behaviors/keyboard-shortcut-behavior.html">
 <link rel="import" href="../../diff/gr-diff/gr-diff.html">
+<link rel="import" href="../../diff/gr-diff-cursor/gr-diff-cursor.html">
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
@@ -168,7 +169,7 @@
           <span class="removed">-[[file.lines_deleted]]</span>
         </div>
       </div>
-      <gr-diff id="diff" hidden
+      <gr-diff hidden
           change-num="[[changeNum]]"
           patch-range="[[patchRange]]"
           path="[[file.__path]]"
@@ -177,6 +178,9 @@
           view-mode="[[_userPrefs.diff_view]]"></gr-diff>
     </template>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+    <gr-diff-cursor
+        id="cursor"
+        fold-offset-top="[[topMargin]]"></gr-diff-cursor>
   </template>
   <script src="gr-file-list.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
index 5603ba9..3257469 100644
--- a/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
+++ b/polygerrit-ui/app/elements/change/gr-file-list/gr-file-list.js
@@ -37,7 +37,10 @@
         value: function() { return document.body; },
       },
 
-      _files: Array,
+      _files: {
+        type: Array,
+        observer: '_filesChanged',
+      },
       _loggedIn: {
         type: Boolean,
         value: false,
@@ -60,6 +63,8 @@
         return Promise.resolve();
       }
 
+      this._collapseAllDiffs();
+
       var promises = [];
       var _this = this;
 
@@ -122,19 +127,26 @@
       }
     },
 
-    _expandAllDiffs: function() {
+    _expandAllDiffs: function(e) {
       this._showInlineDiffs = true;
       this._forEachDiff(function(diff) {
         diff.hidden = false;
         diff.reload();
       });
+      if (e && e.target) {
+        e.target.blur();
+      }
     },
 
-    _collapseAllDiffs: function() {
+    _collapseAllDiffs: function(e) {
       this._showInlineDiffs = false;
       this._forEachDiff(function(diff) {
         diff.hidden = true;
       });
+      this.$.cursor.handleDiffUpdate();
+      if (e && e.target) {
+        e.target.blur();
+      }
     },
 
     _computeCommentsString: function(comments, patchNum, path) {
@@ -200,21 +212,49 @@
       if (this.shouldSupressKeyboardShortcut(e)) { return; }
 
       switch (e.keyCode) {
+        case 37: // left
+          if (e.shiftKey && this._showInlineDiffs) {
+            e.preventDefault();
+            this.$.cursor.moveLeft();
+          }
+          break;
+        case 39: // right
+          if (e.shiftKey && this._showInlineDiffs) {
+            e.preventDefault();
+            this.$.cursor.moveRight();
+          }
+          break;
         case 73:  // 'i'
           if (!e.shiftKey) { return; }
           e.preventDefault();
           this._toggleInlineDiffs();
           break;
+        case 40:  // down
         case 74:  // 'j'
           e.preventDefault();
-          this.selectedIndex =
-              Math.min(this._files.length - 1, this.selectedIndex + 1);
-          this._scrollToSelectedFile();
+          if (this._showInlineDiffs) {
+            this.$.cursor.moveDown();
+          } else {
+            this.selectedIndex =
+                Math.min(this._files.length - 1, this.selectedIndex + 1);
+            this._scrollToSelectedFile();
+          }
           break;
+        case 38:  // up
         case 75:  // 'k'
           e.preventDefault();
-          this.selectedIndex = Math.max(0, this.selectedIndex - 1);
-          this._scrollToSelectedFile();
+          if (this._showInlineDiffs) {
+            this.$.cursor.moveUp();
+          } else {
+            this.selectedIndex = Math.max(0, this.selectedIndex - 1);
+            this._scrollToSelectedFile();
+          }
+          break;
+        case 67: // 'c'
+          if (this._showInlineDiffs) {
+            e.preventDefault();
+            this._addDraftAtTarget();
+          }
           break;
         case 219:  // '['
           e.preventDefault();
@@ -227,7 +267,31 @@
         case 13:  // <enter>
         case 79:  // 'o'
           e.preventDefault();
-          this._openSelectedFile();
+          if (this._showInlineDiffs) {
+            this._openCursorFile();
+          } else {
+            this._openSelectedFile();
+          }
+          break;
+        case 78:  // 'n'
+          if (this._showInlineDiffs) {
+            e.preventDefault();
+            if (e.shiftKey) {
+              this.$.cursor.moveToNextCommentThread();
+            } else {
+              this.$.cursor.moveToNextChunk();
+            }
+          }
+          break;
+        case 80:  // 'p'
+          if (this._showInlineDiffs) {
+            e.preventDefault();
+            if (e.shiftKey) {
+              this.$.cursor.moveToPreviousCommentThread();
+            } else {
+              this.$.cursor.moveToPreviousChunk();
+            }
+          }
           break;
       }
     },
@@ -240,6 +304,12 @@
       }
     },
 
+    _openCursorFile: function() {
+      var diff = this.$.cursor.getTargetDiffElement();
+      page.show(this._computeDiffURL(diff.changeNum, diff.patchRange,
+          diff.path));
+    },
+
     _openSelectedFile: function(opt_index) {
       if (opt_index != null) {
         this.selectedIndex = opt_index;
@@ -248,6 +318,14 @@
           this._files[this.selectedIndex].__path));
     },
 
+    _addDraftAtTarget: function() {
+      var diff = this.$.cursor.getTargetDiffElement();
+      var target = this.$.cursor.getTargetLineElement();
+      if (diff && target) {
+        diff.addDraftAtLine(target);
+      }
+    },
+
     _scrollToSelectedFile: function() {
       var el = this.$$('.row[selected]');
       var top = 0;
@@ -295,5 +373,15 @@
       }
       return classes.join(' ');
     },
+
+    _filesChanged: function() {
+      this.async(function() {
+        var diffElements = Polymer.dom(this.root).querySelectorAll('gr-diff');
+
+        // Overwrite the cursor's list of diffs:
+        this.$.cursor.splice.apply(this.$.cursor,
+            ['diffs', 0, this.$.cursor.diffs.length].concat(diffElements));
+      }.bind(this), 1);
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
index 32de08a..5b70769 100644
--- a/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
+++ b/polygerrit-ui/app/elements/core/gr-keyboard-shortcuts-dialog/gr-keyboard-shortcuts-dialog.html
@@ -141,7 +141,10 @@
             <td>Show previous change</td>
           </tr>
           <tr>
-            <td><span class="key">Enter</span> or <span class="key">o</span></td>
+            <td>
+              <span class="key">Enter</span> or
+              <span class="key">o</span>
+            </td>
             <td>Show selected change</td>
           </tr>
         </tbody>
@@ -158,17 +161,70 @@
             <td></td><td class="header">File list</td>
           </tr>
           <tr>
-            <td><span class="key">j</span></td>
+            <td><span class="key">j</span> or <span class="key">↓</span></td>
             <td>Select next file</td>
           </tr>
           <tr>
-            <td><span class="key">k</span></td>
+            <td><span class="key">k</span> or <span class="key">↑</span></td>
             <td>Select previous file</td>
           </tr>
           <tr>
             <td><span class="key">Enter</span> or <span class="key">o</span></td>
             <td>Show selected file</td>
           </tr>
+          <tr>
+            <td></td><td class="header">Diffs</td>
+          </tr>
+          <tr>
+            <td><span class="key">j</span> or <span class="key">↓</span></td>
+            <td>Go to next line</td>
+          </tr>
+          <tr>
+            <td><span class="key">k</span> or <span class="key">↑</span></td>
+            <td>Go to previous line</td>
+          </tr>
+          <tr>
+            <td><span class="key">n</span></td>
+            <td>Go to next diff chunk</td>
+          </tr>
+          <tr>
+            <td><span class="key">p</span></td>
+            <td>Go to previous diff chunk</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">n</span>
+            </td>
+            <td>Go to next comment thread</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">p</span>
+            </td>
+            <td>Go to previous comment thread</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">←</span>
+            </td>
+            <td>Select left pane</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">→</span>
+            </td>
+            <td>Select right pane</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key">c</span>
+            </td>
+            <td>Draft new comment</td>
+          </tr>
         </tbody>
         <!-- Diff View -->
         <tbody hidden$="[[!_computeInView(view, 'gr-diff-view')]]" hidden>
@@ -176,6 +232,14 @@
             <td></td><td class="header">Actions</td>
           </tr>
           <tr>
+            <td><span class="key">j</span> or <span class="key">↓</span></td>
+            <td>Show next line</td>
+          </tr>
+          <tr>
+            <td><span class="key">k</span> or <span class="key">↑</span></td>
+            <td>Show previous line</td>
+          </tr>
+          <tr>
             <td><span class="key">n</span></td>
             <td>Show next diff chunk</td>
           </tr>
@@ -198,6 +262,26 @@
             <td>Show previous comment thread</td>
           </tr>
           <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">←</span>
+            </td>
+            <td>Select left pane</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key modifier">Shift</span>
+              <span class="key">→</span>
+            </td>
+            <td>Select right pane</td>
+          </tr>
+          <tr>
+            <td>
+              <span class="key">c</span>
+            </td>
+            <td>Draft new comment</td>
+          </tr>
+          <tr>
             <td><span class="key">a</span></td>
             <td>Review and publish comments</td>
           </tr>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
index fd35ad7..6e7a68a 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.html
@@ -19,6 +19,7 @@
 <link rel="import" href="../../shared/gr-button/gr-button.html">
 <link rel="import" href="../../shared/gr-date-formatter/gr-date-formatter.html">
 <link rel="import" href="../../shared/gr-linked-text/gr-linked-text.html">
+<link rel="import" href="../../shared/gr-storage/gr-storage.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 
 <dom-module id="gr-diff-comment">
@@ -128,7 +129,7 @@
           class="editMessage"
           disabled="{{disabled}}"
           rows="4"
-          bind-value="{{_editDraft}}"
+          bind-value="{{_messageText}}"
           on-keydown="_handleTextareaKeydown"></iron-autogrow-textarea>
       <gr-linked-text class="message"
           pre
@@ -140,7 +141,7 @@
         <gr-button class="action done" on-tap="_handleDone">Done</gr-button>
         <gr-button class="action edit" on-tap="_handleEdit">Edit</gr-button>
         <gr-button class="action save" on-tap="_handleSave"
-            disabled$="[[_computeSaveDisabled(_editDraft)]]">Save</gr-button>
+            disabled$="[[_computeSaveDisabled(_messageText)]]">Save</gr-button>
         <gr-button class="action cancel" on-tap="_handleCancel" hidden>Cancel</gr-button>
         <div class="danger">
           <gr-button class="action discard" on-tap="_handleDiscard">Discard</gr-button>
@@ -148,6 +149,7 @@
       </div>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+    <gr-storage id="storage"></gr-storage>
   </template>
   <script src="gr-diff-comment.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
index ded5108..f7b5f01 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment.js
@@ -14,6 +14,8 @@
 (function() {
   'use strict';
 
+  var STORAGE_DEBOUNCE_INTERVAL = 400;
+
   Polymer({
     is: 'gr-diff-comment',
 
@@ -35,6 +37,12 @@
      * @event comment-discard
      */
 
+    /**
+     * Fired when this comment is saved.
+     *
+     * @event comment-save
+     */
+
     properties: {
       changeNum: String,
       comment: {
@@ -61,17 +69,30 @@
       projectConfig: Object,
 
       _xhrPromise: Object,  // Used for testing.
-      _editDraft: String,
+      _messageText: {
+        type: String,
+        observer: '_messageTextChanged',
+      },
     },
 
     ready: function() {
-      this._editDraft = (this.comment && this.comment.message) || '';
-      this.editing = this._editDraft.length == 0;
+      this._loadLocalDraft().then(function(loadedLocal) {
+        this._messageText = (this.comment && this.comment.message) || '';
+        this.editing = !this._messageText.length || loadedLocal;
+      }.bind(this));
     },
 
     save: function() {
-      this.comment.message = this._editDraft;
+      this.comment.message = this._messageText;
       this.disabled = true;
+
+      this.$.storage.eraseDraftComment({
+        changeNum: this.changeNum,
+        patchNum: this.patchNum,
+        path: this.comment.path,
+        line: this.comment.line,
+      });
+
       this._xhrPromise = this._saveDraft(this.comment).then(function(response) {
         this.disabled = false;
         if (!response.ok) { return response; }
@@ -86,6 +107,7 @@
           }
           this.comment = comment;
           this.editing = false;
+          this.fire('comment-save');
 
           return obj;
         }.bind(this));
@@ -129,6 +151,29 @@
       }
     },
 
+    _messageTextChanged: function(newValue, oldValue) {
+      if (this.comment && this.comment.id) { return; }
+
+      this.debounce('store', function() {
+        var message = this._messageText;
+
+        var commentLocation = {
+          changeNum: this.changeNum,
+          patchNum: this.patchNum,
+          path: this.comment.path,
+          line: this.comment.line,
+        };
+
+        if ((!this._messageText || !this._messageText.length) && oldValue) {
+          // If the draft has been modified to be empty, then erase the storage
+          // entry.
+          this.$.storage.eraseDraftComment(commentLocation);
+        } else {
+          this.$.storage.setDraftComment(commentLocation, message);
+        }
+      }, STORAGE_DEBOUNCE_INTERVAL);
+    },
+
     _handleLinkTap: function(e) {
       e.preventDefault();
       var hash = this._computeLinkToComment(this.comment);
@@ -158,7 +203,7 @@
 
     _handleEdit: function(e) {
       this._preventDefaultAndBlur(e);
-      this._editDraft = this.comment.message;
+      this._messageText = this.comment.message;
       this.editing = true;
     },
 
@@ -173,7 +218,7 @@
         this.fire('comment-discard');
         return;
       }
-      this._editDraft = this.comment.message;
+      this._messageText = this.comment.message;
       this.editing = false;
     },
 
@@ -213,5 +258,35 @@
       return this.$.restAPI.deleteDiffDraft(this.changeNum, this.patchNum,
           draft);
     },
+
+    _loadLocalDraft: function() {
+      // Use an async promise to avoid blocking render on potentially slow
+      // localStorage calls.
+      return new Promise(function(resolve) {
+        this.async(function() {
+          // Only apply local drafts to comments that haven't been saved
+          // remotely, and haven't been given a default message already.
+          if (!this.comment || this.comment.id || this.comment.message) {
+            resolve(false);
+            return;
+          }
+
+          var draft = this.$.storage.getDraftComment({
+            changeNum: this.changeNum,
+            patchNum: this.patchNum,
+            path: this.comment.path,
+            line: this.comment.line,
+          });
+
+          if (draft) {
+            this.comment.message = draft.message;
+            resolve(true);
+            return;
+          }
+
+          resolve(false);
+        }.bind(this));
+      }.bind(this));
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
index a333e14..8ef222e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-comment/gr-diff-comment_test.html
@@ -183,11 +183,11 @@
       MockInteractions.tap(element.$$('.edit'));
       assert.isTrue(element.editing);
 
-      element._editDraft = '';
+      element._messageText = '';
       // Save should be disabled on an empty message.
       var disabled = element.$$('.save').hasAttribute('disabled');
       assert.isTrue(disabled, 'save button should be disabled.');
-      element._editDraft = '     ';
+      element._messageText = '     ';
       disabled = element.$$('.save').hasAttribute('disabled');
       assert.isTrue(disabled, 'save button should be disabled.');
 
@@ -206,7 +206,7 @@
     test('draft saving/editing', function(done) {
       element.draft = true;
       MockInteractions.tap(element.$$('.edit'));
-      element._editDraft = 'good news, everyone!';
+      element._messageText = 'good news, everyone!';
       MockInteractions.tap(element.$$('.save'));
       assert.isTrue(element.disabled,
           'Element should be disabled when creating draft.');
@@ -218,7 +218,7 @@
         assert.isFalse(element.editing);
       }).then(function() {
         MockInteractions.tap(element.$$('.edit'));
-        element._editDraft = 'You’ll be delivering a package to Chapek 9, a ' +
+        element._messageText = 'You’ll be delivering a package to Chapek 9, a ' +
             'world where humans are killed on sight.';
         MockInteractions.tap(element.$$('.save'));
         assert.isTrue(element.disabled,
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
new file mode 100644
index 0000000..5a41709
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.html
@@ -0,0 +1,30 @@
+<!--
+Copyright (C) 2016 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<link rel="import" href="../../shared/gr-cursor-manager/gr-cursor-manager.html">
+
+<dom-module id="gr-diff-cursor">
+  <template>
+    <gr-cursor-manager
+        id="cursorManager"
+        scroll="keep-visible"
+        cursor-target-class="target-row"
+        fold-offset-top="[[foldOffsetTop]]"
+        target="{{diffRow}}"></gr-cursor-manager>
+  </template>
+  <script src="gr-diff-cursor.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
new file mode 100644
index 0000000..3f71892
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor.js
@@ -0,0 +1,288 @@
+// Copyright (C) 2016 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.
+(function() {
+  'use strict';
+
+  var DiffSides = {
+    LEFT: 'left',
+    RIGHT: 'right',
+  };
+
+  var DiffViewMode = {
+    SIDE_BY_SIDE: 'SIDE_BY_SIDE',
+    UNIFIED: 'UNIFIED_DIFF',
+  };
+
+  var LEFT_SIDE_CLASS = 'target-side-left';
+  var RIGHT_SIDE_CLASS = 'target-side-right';
+
+  Polymer({
+    is: 'gr-diff-cursor',
+
+    properties: {
+      /**
+       * Either DiffSides.LEFT or DiffSides.RIGHT.
+       */
+      side: {
+        type: String,
+        value: DiffSides.RIGHT,
+      },
+      diffRow: {
+        type: Object,
+        notify: true,
+        observer: '_rowChanged',
+      },
+
+      /**
+       * The diff views to cursor through and listen to.
+       */
+      diffs: {
+        type: Array,
+        value: function() {
+          return [];
+        },
+      },
+
+      foldOffsetTop: {
+        type: Number,
+        value: 0,
+      },
+    },
+
+    observers: [
+      '_updateSideClass(side)',
+      '_diffsChanged(diffs.splices)',
+    ],
+
+    moveLeft: function() {
+      this.side = DiffSides.LEFT;
+      if (this._isTargetBlank()) {
+        this.moveUp()
+      }
+    },
+
+    moveRight: function() {
+      this.side = DiffSides.RIGHT;
+      if (this._isTargetBlank()) {
+        this.moveUp()
+      }
+    },
+
+    moveDown: function() {
+      if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+        this.$.cursorManager.next(this._rowHasSide.bind(this));
+      } else {
+        this.$.cursorManager.next();
+      }
+    },
+
+    moveUp: function() {
+      if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+        this.$.cursorManager.previous(this._rowHasSide.bind(this));
+      } else {
+        this.$.cursorManager.previous();
+      }
+    },
+
+    moveToNextChunk: function() {
+      this.$.cursorManager.next(this._isFirstRowOfChunk.bind(this));
+      this._fixSide();
+    },
+
+    moveToPreviousChunk: function() {
+      this.$.cursorManager.previous(this._isFirstRowOfChunk.bind(this));
+      this._fixSide();
+    },
+
+    moveToNextCommentThread: function() {
+      this.$.cursorManager.next(this._rowHasThread.bind(this));
+      this._fixSide();
+    },
+
+    moveToPreviousCommentThread: function() {
+      this.$.cursorManager.previous(this._rowHasThread.bind(this));
+      this._fixSide();
+    },
+
+    /**
+     * Get the line number element targeted by the cursor row and side.
+     * @return {DOMElement}
+     */
+    getTargetLineElement: function() {
+      var lineElSelector = '.lineNum';
+
+      if (!this.diffRow) {
+        return;
+      }
+
+      if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE) {
+        lineElSelector += this.side === DiffSides.LEFT ? '.left' : '.right';
+      }
+
+      return this.diffRow.querySelector(lineElSelector);
+    },
+
+    getTargetDiffElement: function() {
+      // Find the parent diff element of the cursor row.
+      for (var diff = this.diffRow; diff; diff = diff.parentElement) {
+        if (diff.tagName === 'GR-DIFF') { return diff; }
+      }
+      return null;
+    },
+
+    moveToFirstChunk: function() {
+      this.$.cursorManager.moveToStart();
+      this.moveToNextChunk();
+    },
+
+    reInitCursor: function() {
+      this._updateStops();
+      this.moveToFirstChunk();
+    },
+
+    handleDiffUpdate: function() {
+      this._updateStops();
+
+      if (!this.diffRow) {
+        this.reInitCursor();
+      }
+    },
+
+    _getViewMode: function() {
+      if (!this.diffRow) {
+        return null;
+      }
+
+      if (this.diffRow.classList.contains('side-by-side')) {
+        return DiffViewMode.SIDE_BY_SIDE;
+      } else {
+        return DiffViewMode.UNIFIED;
+      }
+    },
+
+    _rowHasSide: function(row) {
+      var selector = '.content';
+      selector += this.side === DiffSides.LEFT ? '.left' : '.right';
+      return row.querySelector(selector);
+    },
+
+    _isFirstRowOfChunk: function(row) {
+      var parentClassList = row.parentNode.classList;
+      return parentClassList.contains('section') &&
+          parentClassList.contains('delta') &&
+          !row.previousSibling;
+    },
+
+    _rowHasThread: function(row) {
+      return row.querySelector('gr-diff-comment-thread');
+    },
+
+    /**
+     * If we jumped to a row where there is no content on the current side then
+     * switch to the alternate side.
+     */
+    _fixSide: function() {
+      if (this._getViewMode() === DiffViewMode.SIDE_BY_SIDE &&
+          this._isTargetBlank()) {
+        this.side = this.side === DiffSides.LEFT ?
+            DiffSides.RIGHT : DiffSides.LEFT;
+      }
+    },
+
+    _isTargetBlank: function() {
+      if (!this.diffRow) {
+        return false;
+      }
+
+      var actions = this._getActionsForRow();
+      return (this.side === DiffSides.LEFT && !actions.left) ||
+          (this.side === DiffSides.RIGHT && !actions.right);
+    },
+
+    _rowChanged: function(newRow, oldRow) {
+      if (oldRow) {
+        oldRow.classList.remove(LEFT_SIDE_CLASS, RIGHT_SIDE_CLASS);
+      }
+      this._updateSideClass();
+    },
+
+    _updateSideClass: function() {
+      if (!this.diffRow) {
+        return;
+      }
+      this.toggleClass(LEFT_SIDE_CLASS, this.side === DiffSides.LEFT,
+          this.diffRow);
+      this.toggleClass(RIGHT_SIDE_CLASS, this.side === DiffSides.RIGHT,
+          this.diffRow);
+    },
+
+    _isActionType: function(type) {
+      return type !== 'blank' && type !== 'contextControl';
+    },
+
+    _getActionsForRow: function() {
+      var actions = {left: false, right: false};
+      if (this.diffRow) {
+        actions.left = this._isActionType(
+            this.diffRow.getAttribute('left-type'));
+        actions.right = this._isActionType(
+            this.diffRow.getAttribute('right-type'));
+      }
+      return actions;
+    },
+
+    _getStops: function() {
+      return this.diffs.reduce(
+          function(stops, diff) {
+            return stops.concat(diff.getCursorStops());
+          }, []);
+    },
+
+    _updateStops: function() {
+      this.$.cursorManager.stops = this._getStops();
+    },
+
+    /**
+     * Setup and tear down on-render listeners for any diffs that are added or
+     * removed from the cursor.
+     * @private
+     */
+    _diffsChanged: function(changeRecord) {
+      if (!changeRecord) { return; }
+
+      this._updateStops();
+
+      var splice;
+      var i;
+      for (var spliceIdx = 0;
+        changeRecord.indexSplices &&
+            spliceIdx < changeRecord.indexSplices.length;
+        spliceIdx++) {
+        splice = changeRecord.indexSplices[spliceIdx];
+
+        for (i = splice.index;
+            i < splice.index + splice.addedCount;
+            i++) {
+          this.listen(this.diffs[i], 'render', 'handleDiffUpdate');
+        }
+
+        for (i = 0;
+            i < splice.removed && splicee.removed.length;
+            i++) {
+          this.unlisten(splice.removed[i], 'render', 'handleDiffUpdate');
+        }
+      }
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
new file mode 100644
index 0000000..f3c9f95
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/gr-diff-cursor_test.html
@@ -0,0 +1,185 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-diff-cursor</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+<script src="../../../scripts/util.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="../gr-diff/gr-diff.html">
+<link rel="import" href="./gr-diff-cursor.html">
+<link rel="import" href="./mock-diff-response_test.html">
+
+<test-fixture id="basic">
+  <template>
+    <mock-diff-response></mock-diff-response>
+    <gr-diff></gr-diff>
+    <gr-diff-cursor></gr-diff-cursor>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-diff-cursor tests', function() {
+    var cursorElement;
+    var diffElement;
+    var mockDiffResponse;
+
+    setup(function(done) {
+      stub('gr-rest-api-interface', {
+        getLoggedIn: function() { return Promise.resolve(false); },
+      });
+
+      var fixtureElems = fixture('basic');
+      mockDiffResponse = fixtureElems[0];
+      diffElement = fixtureElems[1];
+      cursorElement = fixtureElems[2];
+
+      // Register the diff with the cursor.
+      cursorElement.push('diffs', diffElement);
+
+      diffElement.$.restAPI.getDiffPreferences().then(function(prefs) {
+        diffElement.prefs = prefs;
+      });
+
+      sinon.stub(diffElement, '_getDiff', function() {
+        return Promise.resolve(mockDiffResponse.diffResponse);
+      });
+
+      sinon.stub(diffElement, '_getDiffComments', function() {
+        return Promise.resolve({baseComments: [], comments: []});
+      });
+
+      sinon.stub(diffElement, '_getDiffDrafts', function() {
+        return Promise.resolve({baseComments: [], comments: []});
+      });
+
+      var setupDone = function() {
+        cursorElement.moveToFirstChunk();
+        done();
+        diffElement.removeEventListener('render', setupDone);
+      };
+      diffElement.addEventListener('render', setupDone);
+
+      diffElement.reload();
+    });
+
+    test('diff cursor functionality (side-by-side)', function() {
+      // The cursor has been initialized to the first delta.
+      assert.isOk(cursorElement.diffRow);
+
+      var firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+      cursorElement.moveDown()
+
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+      assert.equal(cursorElement.diffRow, firstDeltaRow.nextSibling);
+
+      cursorElement.moveUp();
+
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow.nextSibling);
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+    });
+
+    test('diff cursor functionality (unified)', function() {
+      diffElement.viewMode = 'UNIFIED_DIFF';
+      cursorElement.reInitCursor();
+
+      // The cursor has been initialized to the first delta.
+      assert.isOk(cursorElement.diffRow);
+
+      var firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+      firstDeltaRow = diffElement.$$('.section.delta .diff-row');
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+
+      cursorElement.moveDown();
+
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+      assert.equal(cursorElement.diffRow, firstDeltaRow.nextSibling);
+
+      cursorElement.moveUp();
+
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow.nextSibling);
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+    });
+
+    test('cursor side functionality', function() {
+      // The side only applies to side-by-side mode, which should be the default
+      // mode.
+      assert.equal(diffElement.viewMode, 'SIDE_BY_SIDE');
+
+      var firstDeltaSection = diffElement.$$('.section.delta');
+      var firstDeltaRow = firstDeltaSection.querySelector('.diff-row');
+
+      // Because the first delta in this diff is on the right, it should be set
+      // to the right side.
+      assert.equal(cursorElement.side, 'right');
+      assert.equal(cursorElement.diffRow, firstDeltaRow);
+      var firstIndex = cursorElement.$.cursorManager.index;
+
+      // Move the side to the left. Because this delta only has a right side, we
+      // should be moved up to the previous line where there is content on the
+      // right. The previous row is part of the previous section.
+      cursorElement.moveLeft();
+
+      assert.equal(cursorElement.side, 'left');
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+      assert.equal(cursorElement.$.cursorManager.index, firstIndex - 1);
+      assert.equal(cursorElement.diffRow.parentElement,
+          firstDeltaSection.previousSibling);
+
+      // If we move down, we should skip everything in the first delta because
+      // we are on the left side and the first delta has no content on the left.
+      cursorElement.moveDown();
+
+      assert.equal(cursorElement.side, 'left');
+      assert.notEqual(cursorElement.diffRow, firstDeltaRow);
+      assert.isTrue(cursorElement.$.cursorManager.index > firstIndex);
+      assert.equal(cursorElement.diffRow.parentElement,
+          firstDeltaSection.nextSibling);
+    });
+
+    test('chunk skip functionality', function() {
+      var chunks = Polymer.dom(diffElement.root).querySelectorAll(
+          '.section.delta');
+      var indexOfChunk = function(chunk) {
+        return Array.prototype.indexOf.call(chunks, chunk);
+      };
+
+      // We should be initialized to the first chunk. Since this chunk only has
+      // content on the right side, our side should be right.
+      var currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
+      assert.equal(currentIndex, 0);
+      assert.equal(cursorElement.side, 'right');
+
+      // Move to the next chunk.
+      cursorElement.moveToNextChunk();
+
+      // Since this chunk only has content on the left side. we should have been
+      // automatically mvoed over.
+      var previousIndex = currentIndex;
+      currentIndex = indexOfChunk(cursorElement.diffRow.parentElement);
+      assert.equal(currentIndex, previousIndex + 1);
+      assert.equal(cursorElement.side, 'left');
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html b/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html
new file mode 100644
index 0000000..ee4bd51
--- /dev/null
+++ b/polygerrit-ui/app/elements/diff/gr-diff-cursor/mock-diff-response_test.html
@@ -0,0 +1,163 @@
+<!--
+Copyright (C) 2016 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="mock-diff-response">
+  <template></template>
+  <script>
+    (function() {
+      'use strict';
+
+      var RESPONSE = {
+        "meta_a": {
+          "name": "lorem-ipsum.txt",
+          "content_type": "text/plain",
+          "lines": 45,
+        },
+        "meta_b": {
+          "name": "lorem-ipsum.txt",
+          "content_type": "text/plain",
+          "lines": 48,
+        },
+        "intraline_status": "OK",
+        "change_type": "MODIFIED",
+        "diff_header": [
+          "diff --git a/lorem-ipsum.txt b/lorem-ipsum.txt",
+          "index b2adcf4..554ae49 100644",
+          "--- a/lorem-ipsum.txt",
+          "+++ b/lorem-ipsum.txt",
+        ],
+        "content": [
+          {
+            "ab": [
+              "Lorem ipsum dolor sit amet, suspendisse inceptos vehicula, nulla phasellus.",
+              "Mattis lectus.",
+              "Sodales duis.",
+              "Orci a faucibus.",
+            ]
+          },
+          {
+            "b": [
+              "Nullam neque, ligula ac, id blandit.",
+              "Sagittis tincidunt torquent, tempor nunc amet.",
+              "At rhoncus id.",
+            ],
+          },
+          {
+            "ab": [
+              "Sem nascetur, erat ut, non in.",
+              "A donec, venenatis pellentesque dis.",
+              "Mauris mauris.",
+              "Quisque nisl duis, facilisis viverra.",
+              "Justo purus, semper eget et.",
+            ],
+          },
+          { "a": [
+              "Est amet, vestibulum pellentesque.",
+              "Erat ligula.",
+              "Justo eros.",
+              "Fringilla quisque.",
+            ],
+          },
+          {
+            "ab": [
+              "Arcu eget, rhoncus amet cursus, ipsum elementum.",
+              "Eros suspendisse.",
+            ],
+          },
+          {
+            "a": [
+              "Rhoncus tempor, ultricies aliquam ipsum.",
+            ],
+            "b": [
+              "Rhoncus tempor, ultricies praesent ipsum.",
+            ],
+            "edit_a": [
+              [
+                26,
+                7,
+              ],
+            ],
+            "edit_b": [
+              [
+                26,
+                8,
+              ],
+            ],
+          },
+          {
+            "ab": [
+              "Sollicitudin duis.",
+              "Blandit blandit, ante nisl fusce.",
+              "Felis ac at, tellus consectetuer.",
+              "Sociis ligula sapien, egestas leo.",
+              "Cum pulvinar, sed mauris, cursus neque velit.",
+              "Augue porta lobortis.",
+              "Nibh lorem, amet fermentum turpis, vel pulvinar diam.",
+              "Id quam ipsum, id urna et, massa suspendisse.",
+              "Ac nec, nibh praesent.",
+              "Rutrum vestibulum.",
+              "Est tellus, bibendum habitasse.",
+              "Justo facilisis, vel nulla.",
+              "Donec eu, vulputate neque aliquam, nulla dui.",
+              "Risus adipiscing in.",
+              "Lacus arcu arcu.",
+              "Urna velit.",
+              "Urna a dolor.",
+              "Lectus magna augue, convallis mattis tortor, sed tellus consequat.",
+              "Etiam dui, blandit wisi.",
+              "Mi nec.",
+              "Vitae eget vestibulum.",
+              "Ullamcorper nunc ante, nec imperdiet felis, consectetur in.",
+              "Ac eget.",
+              "Vel fringilla, interdum pellentesque placerat, proin ante.",
+            ],
+          },
+          {
+            "b": [
+              "Eu congue risus.",
+              "Enim ac, quis elementum.",
+              "Non et elit.",
+              "Etiam aliquam, diam vel nunc.",
+            ],
+          },
+          {
+            "ab": [
+              "Nec at.",
+              "Arcu mauris, venenatis lacus fermentum, praesent duis.",
+              "Pellentesque amet et, tellus duis.",
+              "Ipsum arcu vitae, justo elit, sed libero tellus.",
+              "Metus rutrum euismod, vivamus sodales, vel arcu nisl.",
+            ],
+          },
+        ],
+      };
+
+      Polymer({
+        is: 'mock-diff-response',
+        properties: {
+          diffResponse: {
+            type: Object,
+            value: function() {
+              return RESPONSE;
+            },
+          },
+        },
+      });
+    })();
+  </script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
index c52289f..68fed9e 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.html
@@ -21,6 +21,7 @@
 <link rel="import" href="../../shared/gr-overlay/gr-overlay.html">
 <link rel="import" href="../../shared/gr-rest-api-interface/gr-rest-api-interface.html">
 <link rel="import" href="../gr-diff/gr-diff.html">
+<link rel="import" href="../gr-diff-cursor/gr-diff-cursor.html">
 <link rel="import" href="../gr-diff-preferences/gr-diff-preferences.html">
 <link rel="import" href="../gr-patch-range-select/gr-patch-range-select.html">
 
@@ -201,6 +202,7 @@
       </gr-diff>
     </div>
     <gr-rest-api-interface id="restAPI"></gr-rest-api-interface>
+    <gr-diff-cursor id="cursor"></gr-diff-cursor>
   </template>
   <script src="gr-diff-view.js"></script>
 </dom-module>
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
index 26c1030..c75cb72 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view.js
@@ -99,6 +99,8 @@
         this.fire('title-change',
             {title: this._computeFileDisplayName(this._path)});
       }
+
+      this.$.cursor.push('diffs', this.$.diff);
     },
 
     detached: function() {
@@ -162,6 +164,35 @@
       if (this.shouldSupressKeyboardShortcut(e)) { return; }
 
       switch (e.keyCode) {
+        case 37: // left
+          if (e.shiftKey) {
+            e.preventDefault();
+            this.$.cursor.moveLeft();
+          }
+          break;
+        case 39: // right
+          if (e.shiftKey) {
+            e.preventDefault();
+            this.$.cursor.moveRight();
+          }
+          break;
+        case 40: // down
+        case 74: // 'j'
+          e.preventDefault();
+          this.$.cursor.moveDown();
+          break;
+        case 38: // up
+        case 75: // 'k'
+          e.preventDefault();
+          this.$.cursor.moveUp();
+          break;
+        case 67: // 'c'
+          e.preventDefault();
+          var line = this.$.cursor.getTargetLineElement();
+          if (line) {
+            this.$.diff.addDraftAtLine(line);
+          }
+          break;
         case 219:  // '['
           e.preventDefault();
           this._navToFile(this._fileList, -1);
@@ -173,17 +204,17 @@
         case 78:  // 'n'
           e.preventDefault();
           if (e.shiftKey) {
-            this.$.diff.scrollToNextCommentThread();
+            this.$.cursor.moveToNextCommentThread();
           } else {
-            this.$.diff.scrollToNextDiffChunk();
+            this.$.cursor.moveToNextChunk();
           }
           break;
         case 80:  // 'p'
           e.preventDefault();
           if (e.shiftKey) {
-            this.$.diff.scrollToPreviousCommentThread();
+            this.$.cursor.moveToPreviousCommentThread();
           } else {
-            this.$.diff.scrollToPreviousDiffChunk();
+            this.$.cursor.moveToPreviousChunk();
           }
           break;
         case 65:  // 'a'
diff --git a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
index ce7cdd6..5fb3126 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff-view/gr-diff-view_test.html
@@ -94,22 +94,22 @@
       MockInteractions.pressAndReleaseKeyOn(element, 188);  // ','
       assert(showPrefsStub.calledOnce);
 
-      var scrollStub = sinon.stub(element.$.diff, 'scrollToNextDiffChunk');
+      var scrollStub = sinon.stub(element.$.cursor, 'moveToNextChunk');
       MockInteractions.pressAndReleaseKeyOn(element, 78);  // 'n'
       assert(scrollStub.calledOnce);
       scrollStub.restore();
 
-      scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousDiffChunk');
+      scrollStub = sinon.stub(element.$.cursor, 'moveToPreviousChunk');
       MockInteractions.pressAndReleaseKeyOn(element, 80);  // 'p'
       assert(scrollStub.calledOnce);
       scrollStub.restore();
 
-      scrollStub = sinon.stub(element.$.diff, 'scrollToNextCommentThread');
+      scrollStub = sinon.stub(element.$.cursor, 'moveToNextCommentThread');
       MockInteractions.pressAndReleaseKeyOn(element, 78, ['shift']);  // 'N'
       assert(scrollStub.calledOnce);
       scrollStub.restore();
 
-      scrollStub = sinon.stub(element.$.diff, 'scrollToPreviousCommentThread');
+      scrollStub = sinon.stub(element.$.cursor, 'moveToPreviousCommentThread');
       MockInteractions.pressAndReleaseKeyOn(element, 80, ['shift']);  // 'P'
       assert(scrollStub.calledOnce);
       scrollStub.restore();
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
index 061ed4f..bf93112 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-side-by-side.js
@@ -35,6 +35,10 @@
   GrDiffBuilderSideBySide.prototype._createRow = function(section, leftLine,
       rightLine) {
     var row = this._createElement('tr');
+    row.classList.add('diff-row', 'side-by-side');
+    row.setAttribute('left-type', leftLine.type);
+    row.setAttribute('right-type', rightLine.type);
+
     this._appendPair(section, row, leftLine, leftLine.beforeNumber,
         GrDiffBuilder.Side.LEFT);
     this._appendPair(section, row, rightLine, rightLine.afterNumber,
@@ -44,7 +48,7 @@
 
   GrDiffBuilderSideBySide.prototype._appendPair = function(section, row, line,
       lineNumber, side) {
-    row.appendChild(this._createLineEl(line, lineNumber, line.type));
+    row.appendChild(this._createLineEl(line, lineNumber, line.type, side));
     var action = this._createContextControl(section, line);
     if (action) {
       row.appendChild(action);
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
index d9517d3..86340bd 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder-unified.js
@@ -23,6 +23,7 @@
   GrDiffBuilderUnified.prototype.emitGroup = function(group,
       opt_beforeSection) {
     var sectionEl = this._createElement('tbody', 'section');
+    sectionEl.classList.add(group.type);
 
     for (var i = 0; i < group.lines.length; ++i) {
       sectionEl.appendChild(this._createRow(sectionEl, group.lines[i]));
@@ -36,6 +37,7 @@
         GrDiffLine.Type.REMOVE));
     row.appendChild(this._createLineEl(line, line.afterNumber,
         GrDiffLine.Type.ADD));
+    row.classList.add('diff-row', 'unified');
 
     var action = this._createContextControl(section, line);
     if (action) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
index ce4515c..c2197eb 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff-builder.js
@@ -401,8 +401,12 @@
     return threadEl;
   };
 
-  GrDiffBuilder.prototype._createLineEl = function(line, number, type) {
+  GrDiffBuilder.prototype._createLineEl = function(line, number, type,
+      opt_class) {
     var td = this._createElement('td');
+    if (opt_class) {
+      td.classList.add(opt_class);
+    }
     if (line.type === GrDiffLine.Type.BLANK) {
       return td;
     } else if (line.type === GrDiffLine.Type.CONTEXT_CONTROL) {
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
index 7b1ce00..e4603b8 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.html
@@ -43,6 +43,16 @@
       .section {
         background-color: #eee;
       }
+      .diff-row.target-row.target-side-left .lineNum.left,
+      .diff-row.target-row.target-side-right .lineNum.right,
+      .diff-row.target-row.unified .lineNum {
+        background-color: #BBDEFB;
+      }
+      .diff-row.target-row.target-side-left .lineNum.left:before,
+      .diff-row.target-row.target-side-right .lineNum.right:before,
+      .diff-row.target-row.unified .lineNum:before {
+        color: #000;
+      }
       .blank,
       .content {
         background-color: #fff;
@@ -64,9 +74,6 @@
       .canComment .lineNum[data-value] {
         cursor: pointer;
       }
-      .canComment .lineNum[data-value]:before {
-        text-decoration: underline;
-      }
       .canComment .lineNum[data-value]:hover:before {
         background-color: #ccc;
       }
diff --git a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
index 447d307..660b86f 100644
--- a/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
+++ b/polygerrit-ui/app/elements/diff/gr-diff/gr-diff.js
@@ -58,14 +58,6 @@
         observer: '_selectionSideChanged',
       },
       _comments: Object,
-      _focusedSection: {
-        type: Number,
-        value: -1,
-      },
-      _focusedThread: {
-        type: Number,
-        value: -1,
-      },
     },
 
     observers: [
@@ -114,24 +106,29 @@
       this._scrollToElement(el);
     },
 
-    scrollToNextDiffChunk: function() {
-      this._focusedSection = this._advanceElementWithinNodeList(
-          this._getDeltaSections(), this._focusedSection, 1);
+    getCursorStops: function() {
+      if (this.hidden) {
+        return [];
+      }
+
+      return Polymer.dom(this.root).querySelectorAll('.diff-row');
     },
 
-    scrollToPreviousDiffChunk: function() {
-      this._focusedSection = this._advanceElementWithinNodeList(
-          this._getDeltaSections(), this._focusedSection, -1);
-    },
+    addDraftAtLine: function(el) {
+      this._getLoggedIn().then(function(loggedIn) {
+        if (!loggedIn) { return; }
 
-    scrollToNextCommentThread: function() {
-      this._focusedThread = this._advanceElementWithinNodeList(
-          this._getCommentThreads(), this._focusedThread, 1);
-    },
-
-    scrollToPreviousCommentThread: function() {
-      this._focusedThread = this._advanceElementWithinNodeList(
-          this._getCommentThreads(), this._focusedThread, -1);
+        var value = el.getAttribute('data-value');
+        if (value === GrDiffLine.FILE) {
+          this._addDraft(el);
+          return;
+        }
+        var lineNum = parseInt(value, 10);
+        if (isNaN(lineNum)) {
+          throw Error('Invalid line number: ' + value);
+        }
+        this._addDraft(el, lineNum);
+      }.bind(this));
     },
 
     _advanceElementWithinNodeList: function(els, curIndex, direction) {
@@ -147,10 +144,6 @@
       return Polymer.dom(this.root).querySelectorAll('gr-diff-comment-thread');
     },
 
-    _getDeltaSections: function() {
-      return Polymer.dom(this.root).querySelectorAll('.section.delta');
-    },
-
     _scrollToElement: function(el) {
       if (!el) { return; }
 
@@ -194,27 +187,10 @@
       if (el.classList.contains('showContext')) {
         this._showContext(e.detail.group, e.detail.section);
       } else if (el.classList.contains('lineNum')) {
-        this._handleLineTap(el);
+        this.addDraftAtLine(el);
       }
     },
 
-    _handleLineTap: function(el) {
-      this._getLoggedIn().then(function(loggedIn) {
-        if (!loggedIn) { return; }
-
-        var value = el.getAttribute('data-value');
-        if (value === GrDiffLine.FILE) {
-          this._addDraft(el);
-          return;
-        }
-        var lineNum = parseInt(value, 10);
-        if (isNaN(lineNum)) {
-          throw Error('Invalid line number: ' + value);
-        }
-        this._addDraft(el, lineNum);
-      }.bind(this));
-    },
-
     _addDraft: function(lineEl, opt_lineNum) {
       var threadEl;
 
@@ -338,6 +314,10 @@
     _showContext: function(group, sectionEl) {
       this._builder.emitGroup(group, sectionEl);
       sectionEl.parentNode.removeChild(sectionEl);
+
+      this.async(function() {
+        this.fire('render', null, {bubbles: false});
+      }.bind(this), 1);
     },
 
     _prefsChanged: function(prefsChangeRecord) {
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html
new file mode 100644
index 0000000..f213312
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.html
@@ -0,0 +1,22 @@
+<!--
+Copyright (C) 2016 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.
+-->
+
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+
+<dom-module id="gr-cursor-manager">
+  <template></template>
+  <script src="gr-cursor-manager.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
new file mode 100644
index 0000000..e1990c4
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager.js
@@ -0,0 +1,227 @@
+// Copyright (C) 2016 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.
+(function() {
+  'use strict';
+
+  var ScrollBehavior = {
+    ALWAYS: 'always',
+    NEVER: 'never',
+    KEEP_VISIBLE: 'keep-visible',
+  };
+
+  Polymer({
+    is: 'gr-cursor-manager',
+
+    properties: {
+      stops: {
+        type: Array,
+        value: function() {
+          return [];
+        },
+        observer: '_updateIndex',
+      },
+      target: {
+        type: Object,
+        notify: true,
+        observer: '_scrollToTarget',
+      },
+
+      /**
+       * The index of the current target (if any). -1 otherwise.
+       */
+      index: {
+        type: Number,
+        value: -1,
+      },
+
+      /**
+       * The class to apply to the current target. Use null for no class.
+       */
+      cursorTargetClass: {
+        type: String,
+        value: null,
+      },
+
+      /**
+       * The scroll behavior for the cursor. Values are 'never', 'always' and
+       * 'keep-visible'. 'keep-visible' will only scroll if the cursor is beyond
+       * the viewport.
+       */
+      scroll: {
+        type: String,
+        value: ScrollBehavior.NEVER,
+      },
+
+      /**
+       * When using the 'keep-visible' scroll behavior, set an offset to the top
+       * of the window for what is considered above the upper fold.
+       */
+      foldOffsetTop: {
+        type: Number,
+        value: 0,
+      },
+    },
+
+    detached: function() {
+      this.unsetCursor();
+    },
+
+    next: function(opt_condition) {
+      this._moveCursor(1, opt_condition);
+    },
+
+    previous: function(opt_condition) {
+      this._moveCursor(-1, opt_condition);
+    },
+
+    /**
+     * Set the cursor to an arbitrary element.
+     * @param {DOMElement}
+     */
+    setCursor: function(element) {
+      this.unsetCursor();
+      this.target = element;
+      this._updateIndex();
+      this._decorateTarget();
+    },
+
+    unsetCursor: function() {
+      this._unDecorateTarget();
+      this.index = -1;
+      this.target = null;
+    },
+
+    isAtStart: function() {
+      return this.index === 0;
+    },
+
+    isAtEnd: function() {
+      return this.index === this.stops.length - 1;
+    },
+
+    moveToStart: function() {
+      if (this.stops.length) {
+        this.setCursor(this.stops[0]);
+      }
+    },
+
+    /**
+     * Move the cursor forward or backward by delta. Noop if moving past either
+     * end of the stop list.
+     * @param {Number} delta: either -1 or 1.
+     * @param {Function} opt_condition Optional stop condition. If a condition
+     *    is passed the cursor will continue to move in the specified direction
+     *    until the condition is met.
+     * @private
+     */
+    _moveCursor: function(delta, opt_condition) {
+      if (!this.stops.length) {
+        this.unsetCursor();
+        return;
+      }
+
+      this._unDecorateTarget();
+
+      var newIndex = this._getNextindex(delta, opt_condition);
+
+      var newTarget = null;
+      if (newIndex != -1) {
+        newTarget = this.stops[newIndex];
+      }
+
+      this.index = newIndex;
+      this.target = newTarget;
+
+      this._decorateTarget();
+    },
+
+    _decorateTarget: function() {
+      if (this.target && this.cursorTargetClass) {
+        this.target.classList.add(this.cursorTargetClass);
+      }
+    },
+
+    _unDecorateTarget: function() {
+      if (this.target && this.cursorTargetClass) {
+        this.target.classList.remove(this.cursorTargetClass);
+      }
+    },
+
+    /**
+     * Get the next stop index indicated by the delta direction.
+     * @param {Number} delta: either -1 or 1.
+     * @param {Function} opt_condition Optional stop condition.
+     * @return {Number} the new index.
+     * @private
+     */
+    _getNextindex: function(delta, opt_condition) {
+      if (!this.stops.length || this.index === -1) {
+        return -1;
+      }
+
+      var newIndex = this.index;
+      do {
+        newIndex = newIndex + delta;
+      } while(newIndex > 0 &&
+              newIndex < this.stops.length - 1 &&
+              opt_condition && !opt_condition(this.stops[newIndex]));
+
+      newIndex = Math.max(0, Math.min(this.stops.length - 1, newIndex));
+
+      // If we failed to satisfy the condition:
+      if (opt_condition && !opt_condition(this.stops[newIndex])) {
+        return this.index;
+      }
+
+      return newIndex;
+    },
+
+    _updateIndex: function() {
+      if (!this.target) {
+        this.index = -1;
+        return;
+      }
+
+      var newIndex = Array.prototype.indexOf.call(this.stops, this.target);
+      if (newIndex === -1) {
+        this.unsetCursor();
+      } else {
+        this.index = newIndex;
+      }
+    },
+
+    _scrollToTarget: function() {
+      if (!this.target || this.scroll === ScrollBehavior.NEVER) { return; }
+
+      // Calculate where the element is relative to the window.
+      var top = this.target.offsetTop;
+      for (var offsetParent = this.target.offsetParent;
+           offsetParent;
+           offsetParent = offsetParent.offsetParent) {
+        top += offsetParent.offsetTop;
+      }
+
+      if (this.scroll === ScrollBehavior.KEEP_VISIBLE &&
+          top > window.pageYOffset + this.foldOffsetTop &&
+          top < window.pageYOffset + window.innerHeight) { return; }
+
+      // Scroll the element to the middle of the window. Dividing by a third
+      // instead of half the inner height feels a bit better otherwise the
+      // element appears to be below the center of the window even when it
+      // isn't.
+      window.scrollTo(0, top - (window.innerHeight / 3) +
+          (this.target.offsetHeight / 2));
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
new file mode 100644
index 0000000..1ad014d
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-cursor-manager/gr-cursor-manager_test.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 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.
+-->
+
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-cursor-manager</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="../../../bower_components/iron-test-helpers/iron-test-helpers.html">
+<link rel="import" href="gr-cursor-manager.html">
+
+<test-fixture id="basic">
+  <template>
+    <gr-cursor-manager
+        cursor-stop-selector="li"
+        cursor-target-class="targeted"></gr-cursor-manager>
+    <ul>
+      <li>A</li>
+      <li>B</li>
+      <li>C</li>
+      <li>D</li>
+    </ul>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-cursor-manager tests', function() {
+    var element;
+    var list;
+
+    setup(function() {
+      var fixtureElements = fixture('basic');
+      element = fixtureElements[0];
+      list = fixtureElements[1];
+    });
+
+    test('core cursor functionality', function() {
+      // The element is initialized into the proper state.
+      assert.isArray(element.stops);
+      assert.equal(element.stops.length, 0);
+      assert.equal(element.index, -1);
+      assert.isNotOk(element.target);
+
+      // Initialize the cursor with its stops.
+      element.stops = list.querySelectorAll('li');
+
+      // It should have the stops but it should not be targeting any of them.
+      assert.isNotNull(element.stops);
+      assert.equal(element.stops.length, 4);
+      assert.equal(element.index, -1);
+      assert.isNotOk(element.target);
+
+      // Select the third stop.
+      element.setCursor(list.children[2]);
+
+      // It should update its internal state and update the element's class.
+      assert.equal(element.index, 2);
+      assert.equal(element.target, list.children[2]);
+      assert.isTrue(list.children[2].classList.contains('targeted'));
+      assert.isFalse(element.isAtStart());
+      assert.isFalse(element.isAtEnd());
+
+      // Progress the cursor.
+      element.next();
+
+      // Confirm that the next stop is selected and that the previous stop is
+      // unselected.
+      assert.equal(element.index, 3);
+      assert.equal(element.target, list.children[3]);
+      assert.isTrue(element.isAtEnd());
+      assert.isFalse(list.children[2].classList.contains('targeted'));
+      assert.isTrue(list.children[3].classList.contains('targeted'));
+
+      // Progress the cursor.
+      element.next();
+
+      // We should still be at the end.
+      assert.equal(element.index, 3);
+      assert.equal(element.target, list.children[3]);
+      assert.isTrue(element.isAtEnd());
+
+      // Wind the cursor all the way back to the first stop.
+      element.previous();
+      element.previous();
+      element.previous();
+
+      // The element state should reflect the end of the list.
+      assert.equal(element.index, 0);
+      assert.equal(element.target, list.children[0]);
+      assert.isTrue(element.isAtStart());
+      assert.isTrue(list.children[0].classList.contains('targeted'));
+
+      var newLi = document.createElement('li');
+      newLi.textContent = 'Z';
+      list.insertBefore(newLi, list.children[0]);
+      element.stops = list.querySelectorAll('li');
+
+      assert.equal(element.index, 1);
+
+      // De-select all targets.
+      element.unsetCursor();
+
+      // There should now be no cursor target.
+      assert.isFalse(list.children[1].classList.contains('targeted'));
+      assert.isNotOk(element.target);
+      assert.equal(element.index, -1);
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
index 3795aa8..e681bf9 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface.js
@@ -589,6 +589,7 @@
 
       function onlyParent(c) { return c.side == PARENT_PATCH_NUM; }
       function withoutParent(c) { return c.side != PARENT_PATCH_NUM; }
+      function setPath(c) { c.path = opt_path; }
 
       var promises = [];
       var comments;
@@ -599,8 +600,11 @@
         comments = response[opt_path] || [];
         if (opt_basePatchNum == PARENT_PATCH_NUM) {
           baseComments = comments.filter(onlyParent);
+          baseComments.forEach(setPath);
         }
         comments = comments.filter(withoutParent);
+
+        comments.forEach(setPath);
       }.bind(this)));
 
       if (opt_basePatchNum != PARENT_PATCH_NUM) {
@@ -608,6 +612,7 @@
             opt_basePatchNum);
         promises.push(this.fetchJSON(baseURL).then(function(response) {
           baseComments = (response[opt_path] || []).filter(withoutParent);
+          baseComments.forEach(setPath);
         }));
       }
 
diff --git a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
index e7f7a21..ef0741a 100644
--- a/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
+++ b/polygerrit-ui/app/elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html
@@ -140,10 +140,12 @@
           assert.deepEqual(obj.baseComments[0], {
             side: 'PARENT',
             message: 'how did this work in the first place?',
+            path: 'sieve.go',
           });
           assert.equal(obj.comments.length, 1);
           assert.deepEqual(obj.comments[0], {
             message: 'this isn’t quite right',
+            path: 'sieve.go',
           });
           fetchJSONStub.restore();
           done();
@@ -188,13 +190,16 @@
           assert.equal(obj.baseComments.length, 1);
           assert.deepEqual(obj.baseComments[0], {
             message: 'this isn’t quite right',
+            path: 'sieve.go',
           });
           assert.equal(obj.comments.length, 2);
           assert.deepEqual(obj.comments[0], {
             message: 'What on earth are you thinking, here?',
+            path: 'sieve.go',
           });
           assert.deepEqual(obj.comments[1], {
             message: '¯\\_(ツ)_/¯',
+            path: 'sieve.go',
           });
           fetchJSONStub.restore();
           done();
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
new file mode 100644
index 0000000..74bfcdf
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.html
@@ -0,0 +1,19 @@
+<!--
+Copyright (C) 2016 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.
+-->
+<link rel="import" href="../../../bower_components/polymer/polymer.html">
+<dom-module id="gr-storage">
+  <script src="gr-storage.js"></script>
+</dom-module>
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
new file mode 100644
index 0000000..ef3a6c0
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage.js
@@ -0,0 +1,85 @@
+// Copyright (C) 2016 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.
+(function() {
+  'use strict';
+
+  // Date cutoff is one day:
+  var DRAFT_MAX_AGE = 24*60*60*1000;
+
+  // Clean up old entries no more frequently than one day.
+  var CLEANUP_THROTTLE_INTERVAL = 24*60*60*1000;
+
+  Polymer({
+    is: 'gr-storage',
+
+    properties: {
+      _lastCleanup: Number,
+      _storage: {
+        type: Object,
+        value: function() {
+          return window.localStorage;
+        },
+      },
+    },
+
+    getDraftComment: function(location) {
+      this._cleanupDrafts();
+      return this._getObject(this._getDraftKey(location));
+    },
+
+    setDraftComment: function(location, message) {
+      var key = this._getDraftKey(location);
+      this._setObject(key, {message: message, updated: Date.now()});
+    },
+
+    eraseDraftComment: function(location) {
+      var key = this._getDraftKey(location);
+      this._storage.removeItem(key);
+    },
+
+    _getDraftKey: function(location) {
+      return ['draft', location.changeNum, location.patchNum, location.path,
+          location.line].join(':');
+    },
+
+    _cleanupDrafts: function() {
+      // Throttle cleanup to the throttle interval.
+      if (this._lastCleanup &&
+          Date.now() - this._lastCleanup < CLEANUP_THROTTLE_INTERVAL) {
+        return;
+      }
+      this._lastCleanup = Date.now();
+
+      var draft;
+      for (var key in this._storage) {
+        if (key.indexOf('draft:') === 0) {
+          draft = this._getObject(key);
+          if (Date.now() - draft.updated > DRAFT_MAX_AGE) {
+            this._storage.removeItem(key);
+          }
+        }
+      }
+    },
+
+    _getObject: function(key) {
+      var serial = this._storage.getItem(key);
+      if (!serial) { return null; }
+      return JSON.parse(serial);
+    },
+
+    _setObject: function(key, obj) {
+      this._storage.setItem(key, JSON.stringify(obj));
+    },
+  });
+})();
diff --git a/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
new file mode 100644
index 0000000..4e17cb6
--- /dev/null
+++ b/polygerrit-ui/app/elements/shared/gr-storage/gr-storage_test.html
@@ -0,0 +1,117 @@
+<!DOCTYPE html>
+<!--
+Copyright (C) 2016 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.
+-->
+<meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1.0, user-scalable=yes">
+<title>gr-storage</title>
+
+<script src="../../../bower_components/webcomponentsjs/webcomponents.min.js"></script>
+<script src="../../../bower_components/web-component-tester/browser.js"></script>
+
+<link rel="import" href="gr-storage.html">
+
+<test-fixture id="basic">
+  <template>
+    <gr-storage></gr-storage>
+  </template>
+</test-fixture>
+
+<script>
+  suite('gr-storage tests', function() {
+    var element;
+    var storage;
+
+    setup(function() {
+      element = fixture('basic');
+      storage = element._storage;
+      cleanupStorage();
+    });
+
+    function cleanupStorage() {
+      // Make sure there are no entries in storage.
+      for (var key in window.localStorage) {
+        window.localStorage.removeItem(key);
+      }
+    }
+
+    test('storing, retrieving and erasing drafts', function() {
+      var changeNum = 1234;
+      var patchNum = 5;
+      var path = 'my_source_file.js';
+      var line = 123;
+      var location = {
+        changeNum: changeNum,
+        patchNum: patchNum,
+        path: path,
+        line: line,
+      };
+
+      // The key is in the expected format.
+      var key = element._getDraftKey(location);
+      assert.equal(key, ['draft', changeNum, patchNum, path, line].join(':'));
+
+      // There should be no draft initially.
+      var draft = element.getDraftComment(location);
+      assert.isNotOk(draft);
+
+      // Setting the draft stores it under the expected key.
+      element.setDraftComment(location, 'my comment');
+      assert.isOk(storage.getItem(key));
+      assert.equal(JSON.parse(storage.getItem(key)).message, 'my comment');
+      assert.isOk(JSON.parse(storage.getItem(key)).updated);
+
+      // Erasing the draft removes the key.
+      element.eraseDraftComment(location);
+      assert.isNotOk(storage.getItem(key));
+
+      cleanupStorage();
+    });
+
+    test('automatically removes old drafts', function() {
+      var changeNum = 1234;
+      var patchNum = 5;
+      var path = 'my_source_file.js';
+      var line = 123;
+      var location = {
+        changeNum: changeNum,
+        patchNum: patchNum,
+        path: path,
+        line: line,
+      };
+      var key = element._getDraftKey(location);
+
+      // Make sure that the call to cleanup doesn't get throttled.
+      element._lastCleanup = 0;
+
+      var cleanupSpy = sinon.spy(element, '_cleanupDrafts');
+
+      // Create a message with a timestamp that is a second behind the max age.
+      storage.setItem(key, JSON.stringify({
+        message: 'old message',
+        updated: Date.now() - 24*60*60*1000 - 1000,
+      }));
+
+      // Getting the draft should cause it to be removed.
+      var draft = element.getDraftComment(location);
+
+      assert.isTrue(cleanupSpy.called);
+      assert.isNotOk(draft);
+      assert.isNotOk(storage.getItem(key));
+
+      cleanupSpy.restore();
+      cleanupStorage();
+    });
+  });
+</script>
diff --git a/polygerrit-ui/app/test/index.html b/polygerrit-ui/app/test/index.html
index efdbaef..7c87037 100644
--- a/polygerrit-ui/app/test/index.html
+++ b/polygerrit-ui/app/test/index.html
@@ -46,6 +46,7 @@
     '../elements/diff/gr-diff/gr-diff_test.html',
     '../elements/diff/gr-diff-comment/gr-diff-comment_test.html',
     '../elements/diff/gr-diff-comment-thread/gr-diff-comment-thread_test.html',
+    '../elements/diff/gr-diff-cursor/gr-diff-cursor_test.html',
     '../elements/diff/gr-diff-preferences/gr-diff-preferences_test.html',
     '../elements/diff/gr-diff-view/gr-diff-view_test.html',
     '../elements/diff/gr-patch-range-select/gr-patch-range-select_test.html',
@@ -56,10 +57,12 @@
     '../elements/shared/gr-avatar/gr-avatar_test.html',
     '../elements/shared/gr-change-star/gr-change-star_test.html',
     '../elements/shared/gr-confirm-dialog/gr-confirm-dialog_test.html',
+    '../elements/shared/gr-cursor-manager/gr-cursor-manager_test.html',
     '../elements/shared/gr-date-formatter/gr-date-formatter_test.html',
     '../elements/shared/gr-js-api-interface/gr-js-api-interface_test.html',
     '../elements/shared/gr-linked-text/gr-linked-text_test.html',
     '../elements/shared/gr-rest-api-interface/gr-rest-api-interface_test.html',
+    '../elements/shared/gr-storage/gr-storage_test.html',
   ].forEach(function(file) {
     testFiles.push(file);
     testFiles.push(file + '?dom=shadow');