Merge "Include in ProjectAccessInfo whether the calling user is project owner"
diff --git a/Documentation/rest-api-projects.txt b/Documentation/rest-api-projects.txt
index 89bad9d..83ef7a9 100644
--- a/Documentation/rest-api-projects.txt
+++ b/Documentation/rest-api-projects.txt
@@ -436,10 +436,26 @@
   )]}'
   {
     "kind": "gerritcodereview#project_config",
-    "use_contributor_agreements": false,
-    "use_content_merge": true,
-    "use_signed_off_by": false,
-    "require_change_id": true,
+    "use_contributor_agreements": {
+      "value": true,
+      "configured_value": "TRUE",
+      "inherited_value": false
+    },
+    "use_content_merge": {
+      "value": true,
+      "configured_value": "INHERIT",
+      "inherited_value": true
+    },
+    "use_signed_off_by": {
+      "value": false,
+      "configured_value": "INHERIT",
+      "inherited_value": false
+    },
+    "require_change_id": {
+      "value": false,
+      "configured_value": "FALSE",
+      "inherited_value": true
+    }
     "commentlinks": {}
   }
 ----
@@ -1042,6 +1058,43 @@
 If not set, `HEAD` will be used as base revision.
 |=======================
 
+[[config-info]]
+ConfigInfo
+~~~~~~~~~~
+The `ConfigInfo` entity contains information about the effective project
+configuration.
+
+Fields marked with * are only visible to users who have read access to
+`refs/meta/config`.
+
+[options="header",width="50%",cols="1,6"]
+|======================================
+|Field Name                   |Description
+|`use_contributor_agreements*`|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+authors must complete a contributor agreement on the site before
+pushing any commits or changes to this project.
+|`use_content_merge*`|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+Gerrit will try to perform a 3-way merge of text file content when a
+file has been modified by both the destination branch and the change
+being submitted. This option only takes effect if submit type is not
+FAST_FORWARD_ONLY.
+|`use_signed_off_by*`|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether
+each change must contain a Signed-off-by line from either the author or
+the uploader in the commit message.
+|`require_change_id*`|
+link:#inherited-boolean-info[InheritedBooleanInfo] that tells whether a
+valid link:user-changeid.html[Change-Id] footer in any commit uploaded
+for review is required. This does not apply to commits pushed directly
+to a branch or tag.
+|`commentlinks`|
+Comment link configuration for the project. Has the same format as the
+link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[commentlink section]
+of `gerrit.config`.
+|======================================
+
 [[dashboard-info]]
 DashboardInfo
 ~~~~~~~~~~~~~
@@ -1124,6 +1177,23 @@
 omitted.
 |============================
 
+[[inherited-boolean-info]]
+InheritedBooleanInfo
+~~~~~~~~~~~~~~~~~~~~
+A boolean value that can also be inherited.
+
+[options="header",width="50%",cols="1,^2,4"]
+|================================
+|Field Name         ||Description
+|`value`            ||
+The effective boolean value.
+|`configured_value` ||
+The configured value, can be `TRUE`, `FALSE` or `INHERITED`.
+|`inherited_value`  |optional|
+The boolean value inherited from the parent. +
+Not set if there is no parent.
+|================================
+
 [[project-description-input]]
 ProjectDescriptionInput
 ~~~~~~~~~~~~~~~~~~~~~~~
@@ -1248,39 +1318,6 @@
 |`size_of_packed_objects`  |Size of packed objects in bytes.
 |======================================
 
-[[config-info]]
-ConfigInfo
-~~~~~~~~~~
-The `ConfigInfo` entity contains information about the effective project
-configuration.
-
-Fields marked with * are only visible to users who have read access to
-`refs/meta/config`.
-
-[options="header",width="50%",cols="1,6"]
-|======================================
-|Field Name                   |Description
-|`use_contributor_agreements*`|
-If set, authors must complete a contributor agreement on the site
-before pushing any commits or changes to this project.
-|`use_content_merge*`|
-If set, Gerrit will try to perform a 3-way merge of text file content
-when a file has been modified by both the destination branch and the
-change being submitted. This option only takes effect if submit type is
-not FAST_FORWARD_ONLY.
-|`use_signed_off_by*`|
-If set, each change must contain a Signed-off-by line from either the
-author or the uploader in the commit message.
-|`require_change_id*`|
-If set, require a valid link:user-changeid.html[Change-Id] footer in any
-commit uploaded for review. This does not apply to commits pushed
-directly to a branch or tag.
-|`commentlinks`|
-Comment link configuration for the project. Has the same format as the
-link:config-gerrit.html#_a_id_commentlink_a_section_commentlink[commentlink section]
-of `gerrit.config`.
-|======================================
-
 
 GERRIT
 ------
diff --git a/Documentation/user-search.txt b/Documentation/user-search.txt
index e30c6bf..d7e9fd7 100644
--- a/Documentation/user-search.txt
+++ b/Documentation/user-search.txt
@@ -189,7 +189,10 @@
 `file:"^name[1-3].xml"`.
 +
 Currently this operator is only available on a watched project
-and may not be used in the search bar.
+and may not be used in the search bar. The same holds true for web UI
+"My > Watched Changes", i. e. file:regex is used over the is:watched
+expression. It never produces any results, because the error message:
+"operator not permitted here: file:regex" is suppressed.
 
 [[has]]
 has:draft::
diff --git a/ReleaseNotes/ReleaseNotes-2.7.txt b/ReleaseNotes/ReleaseNotes-2.7.txt
index a74e31b..397a959 100644
--- a/ReleaseNotes/ReleaseNotes-2.7.txt
+++ b/ReleaseNotes/ReleaseNotes-2.7.txt
@@ -6,6 +6,10 @@
 
 link:https://gerrit-releases.storage.googleapis.com/gerrit-2.7-rc2.war[https://gerrit-releases.storage.googleapis.com/gerrit-2.7-rc2.war]
 
+Gerrit 2.7 includes the bug fixes done with
+link:ReleaseNotes-2.6.1.html[Gerrit 2.6.1] and
+link:ReleaseNotes-2.6.2.html[Gerrit 2.6.2]. These bug fixes are *not*
+listed in these release notes.
 
 Schema Change
 -------------
@@ -30,7 +34,7 @@
 * Comment links configurable per project.
 * Themes configurable per project.
 * Better support for binary files and images in diff screens.
-* User avatars.
+* User avatars in more places.
 * Several new REST APIs.
 
 
@@ -68,8 +72,8 @@
 
 * Allow opening new changes on existing commits.
 +
-The %base argument can be used with refs/for/ to identify a specific revision the server should
-start to look for new commits at. Any commits in the range $base..$tip will be opened as a new
+The `%base` argument can be used with `refs/for/` to identify a specific revision the server should
+start to look for new commits at. Any commits in the range `$base..$tip` will be opened as a new
 change, even if the commit already has another change on a different branch.
 
 * New setting `gitweb.linkDrafts` to control if gitweb links are shown on drafts.
@@ -82,9 +86,11 @@
 * Allow changes to be automatically submitted on push.
 +
 Teams that want to use Gerrit's submit strategies to handle contention on busy
-branches can use %submit to create a change and have it
+branches can use `%submit` to create a change and have it
 link:link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/user-upload.html#auto_merge[
-immediately submitted], if the caller has Submit permission on refs/for/<ref>.
+immediately submitted], if the caller has Submit permission on `refs/for/<ref>`.
+
+* Allow administrators to see all groups.
 
 
 Web UI
@@ -94,8 +100,12 @@
 Global
 ^^^^^^
 
-* User avatars are displayed in several places in the Web UI.  "Diffy" is used as
-avatar for the Gerrit server itself.
+* User avatars are displayed in more places in the Web UI.
+
+* 'Diffy' is used as avatar for the Gerrit server itself.
+
+* A popup with user profile information is shown when hovering the
+mouse over avatar images.
 
 
 Change Screens
@@ -121,11 +131,11 @@
 Diff Screens
 ^^^^^^^^^^^^
 
-* Show images in side-by-side and unified diffs
+* Show images in side-by-side and unified diffs.
 
 * Show diffed images above/below each other in unified diffs.
 
-* Harmonize unified diff's styling of images with that of text
+* Harmonize unified diff's styling of images with that of text.
 
 
 REST API
@@ -209,11 +219,13 @@
 the link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/rest-api-changes#get-change-detail[
 Get Change Detail REST API endpoint].
 
+* Correct URL encoding in 'GroupInfo'.
+
 
 Email
 ~~~~~
 
-* Log failure to access reviewer list for notification emails
+* Log failure to access reviewer list for notification emails.
 
 * Log when appropriate if email delivery is skipped.
 
@@ -242,5 +254,5 @@
 
 * Clarify the
 link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.7/config-gerrit.html#cache_names[
-change cache configuration]
+change cache configuration].
 
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
new file mode 100644
index 0000000..4c9325e
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeInfo.java
@@ -0,0 +1,21 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import java.util.List;
+
+public class ChangeInfo {
+  List<ChangeMessageInfo> messages;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
new file mode 100644
index 0000000..b3c584a
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessageInfo.java
@@ -0,0 +1,19 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+public class ChangeMessageInfo {
+  String message;
+}
diff --git a/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
new file mode 100644
index 0000000..351c320
--- /dev/null
+++ b/gerrit-acceptance-tests/src/test/java/com/google/gerrit/acceptance/rest/change/ChangeMessagesIT.java
@@ -0,0 +1,151 @@
+// Copyright (C) 2013 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package com.google.gerrit.acceptance.rest.change;
+
+import static com.google.gerrit.acceptance.git.GitUtil.cloneProject;
+import static com.google.gerrit.acceptance.git.GitUtil.createProject;
+import static com.google.gerrit.acceptance.git.GitUtil.initSsh;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import com.google.gerrit.acceptance.AbstractDaemonTest;
+import com.google.gerrit.acceptance.AccountCreator;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.RestSession;
+import com.google.gerrit.acceptance.SshSession;
+import com.google.gerrit.acceptance.TestAccount;
+import com.google.gerrit.acceptance.git.PushOneCommit;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.server.ReviewDb;
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.google.gwtorm.server.SchemaFactory;
+import com.google.inject.Inject;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.io.IOException;
+import java.util.List;
+
+public class ChangeMessagesIT extends AbstractDaemonTest {
+
+  @Inject
+  private AccountCreator accounts;
+
+  @Inject
+  private SchemaFactory<ReviewDb> reviewDbProvider;
+
+  private TestAccount admin;
+  private RestSession session;
+  private Git git;
+  private ReviewDb db;
+
+  @Before
+  public void setUp() throws Exception {
+    admin = accounts.create("admin", "admin@example.com", "Administrator",
+            "Administrators");
+    session = new RestSession(admin);
+    initSsh(admin);
+    Project.NameKey project = new Project.NameKey("p");
+    SshSession sshSession = new SshSession(admin);
+    createProject(sshSession, project.get());
+    git = cloneProject(sshSession.getUrl() + "/" + project.get());
+    sshSession.close();
+    db = reviewDbProvider.open();
+  }
+
+  @After
+  public void cleanup() {
+    db.close();
+  }
+
+  @Test
+  public void messagesNotReturnedByDefault() throws GitAPIException,
+      IOException {
+    String changeId = createChange();
+    postMessage(changeId, "Some nits need to be fixed.");
+    ChangeInfo c = getChange(changeId);
+    assertNull(c.messages);
+  }
+
+  @Test
+  public void noMessages() throws GitAPIException,
+  IOException {
+    String changeId = createChange();
+    ChangeInfo c = getChangeWithMessages(changeId);
+    assertNotNull(c.messages);
+    assertTrue(c.messages.isEmpty());
+  }
+
+  @Test
+  public void messagesReturnedInChronologicalOrder() throws GitAPIException,
+      IOException {
+    String changeId = createChange();
+    String firstMessage = "Some nits need to be fixed.";
+    postMessage(changeId, firstMessage);
+    String secondMessage = "I like this feature.";
+    postMessage(changeId, secondMessage);
+    ChangeInfo c = getChangeWithMessages(changeId);
+    assertNotNull(c.messages);
+    assertEquals(2, c.messages.size());
+    assertMessage(firstMessage, c.messages.get(0).message);
+    assertMessage(secondMessage, c.messages.get(1).message);
+  }
+
+  private String createChange() throws GitAPIException,
+      IOException {
+    PushOneCommit push = new PushOneCommit(db, admin.getIdent());
+    return push.to(git, "refs/for/master").getChangeId();
+  }
+
+  private ChangeInfo getChange(String changeId) throws IOException {
+    return getChange(changeId, false);
+  }
+
+  private ChangeInfo getChangeWithMessages(String changeId) throws IOException {
+    return getChange(changeId, true);
+  }
+
+  private ChangeInfo getChange(String changeId, boolean includeMessages)
+      throws IOException {
+    RestResponse r =
+        session.get("/changes/?q=" + changeId
+            + (includeMessages ? "&o=MESSAGES" : ""));
+    List<ChangeInfo> c = (new Gson()).fromJson(r.getReader(),
+        new TypeToken<List<ChangeInfo>>() {}.getType());
+    return c.get(0);
+  }
+
+  private void assertMessage(String expected, String actual) {
+    assertEquals("Patch Set 1:\n\n" + expected, actual);
+  }
+
+  private void postMessage(String changeId, String msg) throws IOException {
+    ReviewInput in = new ReviewInput();
+    in.message = msg;
+    session.post("/changes/" + changeId + "/revisions/1/review", in).consume();
+  }
+
+  @SuppressWarnings("unused")
+  private class ReviewInput {
+    String message;
+  }
+}
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
index 4d39c6a..afe6397 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/CodeMirrorDemo.java
@@ -55,7 +55,6 @@
 import net.codemirror.lib.LineCharacter;
 import net.codemirror.lib.LineWidget;
 import net.codemirror.lib.ModeInjector;
-import net.codemirror.lib.TextMarker;
 
 import java.util.ArrayList;
 import java.util.Collections;
@@ -291,8 +290,8 @@
         int aLength = currentA.length();
         int bLength = currentB.length();
         String color = currentA == EMPTY || currentB == EMPTY
-            ? diffTable.style.diff()
-            : diffTable.style.intralineBg();
+            ? DiffTable.style.diff()
+            : DiffTable.style.intralineBg();
         colorLines(cmA, color, origLineA, aLength);
         colorLines(cmB, color, origLineB, bLength);
         mapper.appendCommon(Math.min(aLength, bLength));
@@ -359,7 +358,7 @@
     } else {
       // Estimated height at 21px, fixed by deferring after display
       manager = new PaddingManager(
-          addPaddingWidget(cm, diffTable.style.padding(), line, 21, Unit.PX, 0));
+          addPaddingWidget(cm, DiffTable.style.padding(), line, 21, Unit.PX, 0));
       linePaddingManagerMap.put(handle, manager);
     }
     int lineToPad = mapper.lineOnOther(mySide, line).getLine();
@@ -368,7 +367,7 @@
       PaddingManager.link(manager, linePaddingManagerMap.get(otherHandle));
     } else {
       PaddingManager otherManager = new PaddingManager(
-          addPaddingWidget(other, diffTable.style.padding(), lineToPad, 21, Unit.PX, 0));
+          addPaddingWidget(other, DiffTable.style.padding(), lineToPad, 21, Unit.PX, 0));
       linePaddingManagerMap.put(otherHandle, otherManager);
       PaddingManager.link(manager, otherManager);
     }
@@ -486,25 +485,26 @@
     hiddenSkipMap.put(cm.getLineHandle(markEnd), size);
     SkipBar bar = new SkipBar(cm, hiddenSkipMap);
     diffTable.add(bar);
-    TextMarker marker = cm.markText(
-        CodeMirror.pos(markStart),
-        CodeMirror.pos(markEnd),
-        Configuration.create().set("collapsed", true));
     /**
-     * TODO: Due to CodeMirror limitation, there's no way to make the first
-     * line disappear completely. The current approach leaves an empty line
-     * with line number "1" still showing, and CodeMirror doesn't like manually
-     * setting the display of a line to "none". A workaround may be to use
+     * Due to CodeMirror limitation, there's no way to make the first
+     * line disappear completely, and CodeMirror doesn't like manually
+     * setting the display of a line to "none". The workaround here uses
      * inline widget for the first line and regular line widgets for others.
      */
-    boolean isZero = markStart == -1;
-    Configuration config = Configuration.create()
-      .set("coverGutter", true)
-      .set("above", isZero);
-    LineWidget widget = cm.addLineWidget(
-        isZero ? markEnd + 1 : markStart, bar.getElement(), config);
-    bar.setWidget(widget);
-    bar.setMarker(marker, size);
+    Configuration markerConfig;
+    if (markStart == -1) {
+      markerConfig = Configuration.create()
+        .set("inclusiveLeft", true)
+        .set("inclusiveRight", true)
+        .set("replacedWith", bar.getElement());
+      cm.addLineClass(0, LineClassWhere.WRAP, DiffTable.style.hideNumber());
+    } else {
+      markerConfig = Configuration.create().set("collapsed", true);
+      Configuration config = Configuration.create().set("coverGutter", true);
+      bar.setWidget(cm.addLineWidget(markStart, bar.getElement(), config));
+    }
+    bar.setMarker(cm.markText(CodeMirror.pos(markStart),
+        CodeMirror.pos(markEnd), markerConfig), size);
     return bar;
   }
 
@@ -523,10 +523,10 @@
     }
     EditIterator iter = new EditIterator(lines, startLine);
     Configuration intralineBgOpt = Configuration.create()
-        .set("className", diffTable.style.intralineBg())
+        .set("className", DiffTable.style.intralineBg())
         .set("readOnly", true);
     Configuration diffOpt = Configuration.create()
-        .set("className", diffTable.style.diff())
+        .set("className", DiffTable.style.diff())
         .set("readOnly", true);
     LineCharacter last = CodeMirror.pos(0, 0);
     for (int i = 0; i < edits.length(); i++) {
@@ -543,7 +543,7 @@
       last = to;
       for (int line = fromLine; line < to.getLine(); line++) {
         cm.addLineClass(line, LineClassWhere.BACKGROUND,
-            diffTable.style.diff());
+            DiffTable.style.diff());
       }
     }
   }
@@ -556,7 +556,7 @@
 
   private void insertEmptyLines(CodeMirror cm, int nextLine, int cnt) {
     // -1 to compensate for the line we went past when this method is called.
-    addPaddingWidget(cm, diffTable.style.padding(), nextLine - 1,
+    addPaddingWidget(cm, DiffTable.style.padding(), nextLine - 1,
         cnt, Unit.EM, null);
   }
 
@@ -590,19 +590,23 @@
       public void run() {
         if (cm.hasActiveLine()) {
           cm.removeLineClass(cm.getActiveLine(),
-              LineClassWhere.WRAP, diffTable.style.activeLine());
+              LineClassWhere.WRAP, DiffTable.style.activeLine());
           cm.removeLineClass(cm.getActiveLine(),
-              LineClassWhere.BACKGROUND, diffTable.style.activeLineBg());
+              LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
         }
         if (other.hasActiveLine()) {
           other.removeLineClass(other.getActiveLine(),
-              LineClassWhere.WRAP, diffTable.style.activeLine());
+              LineClassWhere.WRAP, DiffTable.style.activeLine());
           other.removeLineClass(other.getActiveLine(),
-              LineClassWhere.BACKGROUND, diffTable.style.activeLineBg());
+              LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
         }
         int line = cm.getCursor("head").getLine();
         LineHandle handle = cm.getLineHandle(line);
-        // Ugly workaround because CodeMirror never hides lines completely.
+        /**
+         * Ugly workaround because CodeMirror never hides lines completely.
+         * TODO: Change to use CodeMirror's official workaround after
+         * updating the library to latest HEAD.
+         */
         if (hiddenSkipMap.containsKey(handle)) {
           line -= hiddenSkipMap.get(handle);
           handle = cm.getLineHandle(line);
@@ -611,17 +615,17 @@
         if (cm.somethingSelected()) {
           return;
         }
-        cm.addLineClass(line, LineClassWhere.WRAP, diffTable.style.activeLine());
-        cm.addLineClass(line, LineClassWhere.BACKGROUND, diffTable.style.activeLineBg());
+        cm.addLineClass(line, LineClassWhere.WRAP, DiffTable.style.activeLine());
+        cm.addLineClass(line, LineClassWhere.BACKGROUND, DiffTable.style.activeLineBg());
         LineOnOtherInfo info =
             mapper.lineOnOther(cm == cmA ? Side.PARENT : Side.REVISION, line);
         int oLine = info.getLine();
         if (info.isAligned()) {
           other.setActiveLine(other.getLineHandle(oLine));
           other.addLineClass(oLine, LineClassWhere.WRAP,
-              diffTable.style.activeLine());
+              DiffTable.style.activeLine());
           other.addLineClass(oLine, LineClassWhere.BACKGROUND,
-              diffTable.style.activeLineBg());
+              DiffTable.style.activeLineBg());
         }
       }
     };
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
index e4b780c..486f924 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.java
@@ -37,6 +37,7 @@
     String padding();
     String activeLine();
     String activeLineBg();
+    String hideNumber();
   }
 
   @UiField
@@ -46,7 +47,7 @@
   Element cmB;
 
   @UiField
-  LineStyle style;
+  static LineStyle style;
 
   DiffTable() {
     initWidget(uiBinder.createAndBindUi(this));
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
index 404a9e2..251a350 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/DiffTable.ui.xml
@@ -62,14 +62,20 @@
     }
     .activeLine .CodeMirror-linenumber,
     .activeLine .diff, .activeLine .intralineBg {
-      background-color: #D8EDF9 !important;
+      background-color: #FFFF99 !important;
     }
     .activeLineBg {
-      background-color: #D8EDF9 !important;
+      background-color: #FFFF99 !important;
     }
     .cm-trailingspace {
       background-color: red !important;
     }
+    .hideNumber .CodeMirror-linenumber {
+      color: transparent;
+      background-color: #def;
+      width: 23px !important;
+      height: 16px;
+    }
   </ui:style>
   <g:HTMLPanel styleName='{style.difftable}'>
     <table class='{style.table}'>
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
index df2b181..54c0cc6 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.java
@@ -26,6 +26,7 @@
 import com.google.gwt.user.client.ui.HTMLPanel;
 
 import net.codemirror.lib.CodeMirror;
+import net.codemirror.lib.CodeMirror.LineClassWhere;
 import net.codemirror.lib.CodeMirror.LineHandle;
 import net.codemirror.lib.Configuration;
 import net.codemirror.lib.LineWidget;
@@ -46,6 +47,7 @@
   private LineWidget widget;
 
   interface SkipBarStyle extends CssResource {
+    String isLineWidget();
     String noExpand();
   }
 
@@ -78,6 +80,7 @@
 
   void setWidget(LineWidget widget) {
     this.widget = widget;
+    addStyleName(style.isLineWidget());
   }
 
   void setMarker(TextMarker marker, int length) {
@@ -110,24 +113,31 @@
     return true;
   }
 
+  private void clearMarkerAndWidget() {
+    marker.clear();
+    if (widget != null) {
+      widget.clear();
+    } else {
+      cm.removeLineClass(0, LineClassWhere.WRAP, DiffTable.style.hideNumber());
+    }
+  }
+
   private void expandAll() {
     hiddenSkipMap.remove(
         cm.getLineHandle(marker.find().getTo().getLine()));
-    marker.clear();
-    widget.clear();
+    clearMarkerAndWidget();
     removeFromParent();
   }
 
   private void expandBefore() {
     FromTo fromTo = marker.find();
-    marker.clear();
     int oldStart = fromTo.getFrom().getLine();
     int newStart = oldStart + NUM_ROWS_TO_EXPAND;
     int end = fromTo.getTo().getLine();
+    clearMarkerAndWidget();
     marker = cm.markText(CodeMirror.pos(newStart), CodeMirror.pos(end), COLLAPSED);
     Configuration config = Configuration.create().set("coverGutter", true);
     LineWidget newWidget = cm.addLineWidget(newStart, getElement(), config);
-    widget.clear();
     setWidget(newWidget);
     updateSkipNum();
     hiddenSkipMap.put(cm.getLineHandle(end), numSkipLines);
@@ -135,9 +145,9 @@
 
   private void expandAfter() {
     FromTo fromTo = marker.find();
-    marker.clear();
     int oldEnd = fromTo.getTo().getLine();
     int newEnd = oldEnd - NUM_ROWS_TO_EXPAND;
+    marker.clear();
     marker = cm.markText(CodeMirror.pos(fromTo.getFrom().getLine()),
         CodeMirror.pos(newEnd),
         COLLAPSED);
diff --git a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml
index 8bff20c..2d51f54 100644
--- a/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml
+++ b/gerrit-gwtui/src/main/java/com/google/gerrit/client/diff/SkipBar.ui.xml
@@ -27,6 +27,9 @@
       font-style: italic;
       overflow: hidden;
     }
+    .isLineWidget .text {
+      padding-left: 31px;
+    }
     .anchor {
       color: inherit;
       text-decoration: none;
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
index 3ae78b9..cd5e5d7 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/project/GetConfig.java
@@ -14,47 +14,81 @@
 
 package com.google.gerrit.server.project;
 
+import com.google.common.collect.Iterables;
 import com.google.common.collect.Maps;
 import com.google.gerrit.extensions.restapi.RestReadView;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.reviewdb.client.Project.InheritableBoolean;
 import com.google.gerrit.server.git.GitRepositoryManager;
 
 import java.util.Map;
 
 public class GetConfig implements RestReadView<ProjectResource> {
-  public static class ConfigInfo {
-    public final String kind = "gerritcodereview#project_config";
-
-    public Boolean useContributorAgreements;
-    public Boolean useContentMerge;
-    public Boolean useSignedOffBy;
-    public Boolean requireChangeId;
-
-    public Map<String, CommentLinkInfo> commentlinks;
-    public ThemeInfo theme;
-  }
 
   @Override
   public ConfigInfo apply(ProjectResource resource) {
     ConfigInfo result = new ConfigInfo();
     RefControl refConfig = resource.getControl()
         .controlForRef(GitRepositoryManager.REF_CONFIG);
-    ProjectState project = resource.getControl().getProjectState();
+    ProjectState state = resource.getControl().getProjectState();
     if (refConfig.isVisible()) {
-      result.useContributorAgreements = project.isUseContributorAgreements();
-      result.useContentMerge = project.isUseContentMerge();
-      result.useSignedOffBy = project.isUseSignedOffBy();
-      result.requireChangeId = project.isRequireChangeID();
+      InheritedBooleanInfo useContributorAgreements = new InheritedBooleanInfo();
+      InheritedBooleanInfo useSignedOffBy = new InheritedBooleanInfo();
+      InheritedBooleanInfo useContentMerge = new InheritedBooleanInfo();
+      InheritedBooleanInfo requireChangeId = new InheritedBooleanInfo();
+
+      useContributorAgreements.value = state.isUseContributorAgreements();
+      useSignedOffBy.value = state.isUseSignedOffBy();
+      useContentMerge.value = state.isUseContentMerge();
+      requireChangeId.value = state.isRequireChangeID();
+
+      Project p = state.getProject();
+      useContributorAgreements.configuredValue = p.getUseContributorAgreements();
+      useSignedOffBy.configuredValue = p.getUseSignedOffBy();
+      useContentMerge.configuredValue = p.getUseContentMerge();
+      requireChangeId.configuredValue = p.getRequireChangeID();
+
+      ProjectState parentState = Iterables.getFirst(state.parents(), null);
+      if (parentState != null) {
+        useContributorAgreements.inheritedValue = parentState.isUseContributorAgreements();
+        useSignedOffBy.inheritedValue = parentState.isUseSignedOffBy();
+        useContentMerge.inheritedValue = parentState.isUseContentMerge();
+        requireChangeId.inheritedValue = parentState.isRequireChangeID();
+      }
+
+      result.useContributorAgreements = useContributorAgreements;
+      result.useSignedOffBy = useSignedOffBy;
+      result.useContentMerge = useContentMerge;
+      result.requireChangeId = requireChangeId;
     }
 
     // commentlinks are visible to anyone, as they are used for linkification
     // on the client side.
     result.commentlinks = Maps.newLinkedHashMap();
-    for (CommentLinkInfo cl : project.getCommentLinks()) {
+    for (CommentLinkInfo cl : state.getCommentLinks()) {
       result.commentlinks.put(cl.name, cl);
     }
 
     // Themes are visible to anyone, as they are rendered client-side.
-    result.theme = project.getTheme();
+    result.theme = state.getTheme();
     return result;
   }
+
+  public static class ConfigInfo {
+    public final String kind = "gerritcodereview#project_config";
+
+    public InheritedBooleanInfo useContributorAgreements;
+    public InheritedBooleanInfo useContentMerge;
+    public InheritedBooleanInfo useSignedOffBy;
+    public InheritedBooleanInfo requireChangeId;
+
+    public Map<String, CommentLinkInfo> commentlinks;
+    public ThemeInfo theme;
+  }
+
+  public static class InheritedBooleanInfo {
+    public Boolean value;
+    public InheritableBoolean configuredValue;
+    public Boolean inheritedValue;
+  }
 }
diff --git a/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java b/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java
deleted file mode 100644
index 14e0ebf..0000000
--- a/gerrit-server/src/test/java/com/google/gerrit/server/change/ChangeJsonTest.java
+++ /dev/null
@@ -1,233 +0,0 @@
-// Copyright (C) 2013 The Android Open Source Project
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-// http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package com.google.gerrit.server.change;
-
-import static org.easymock.EasyMock.anyBoolean;
-import static org.easymock.EasyMock.anyObject;
-import static org.easymock.EasyMock.createMock;
-import static org.easymock.EasyMock.expect;
-import static org.easymock.EasyMock.expectLastCall;
-import static org.easymock.EasyMock.replay;
-
-import com.google.common.collect.Lists;
-import com.google.common.collect.Sets;
-import com.google.gerrit.common.changes.ListChangesOption;
-import com.google.gerrit.reviewdb.client.Account;
-import com.google.gerrit.reviewdb.client.Branch;
-import com.google.gerrit.reviewdb.client.Change;
-import com.google.gerrit.reviewdb.client.ChangeMessage;
-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.server.ChangeAccess;
-import com.google.gerrit.reviewdb.server.ChangeMessageAccess;
-import com.google.gerrit.reviewdb.server.PatchSetAccess;
-import com.google.gerrit.reviewdb.server.PatchSetApprovalAccess;
-import com.google.gerrit.reviewdb.server.ReviewDb;
-import com.google.gerrit.server.CurrentUser;
-import com.google.gerrit.server.account.AccountByEmailCache;
-import com.google.gerrit.server.account.AccountCache;
-import com.google.gerrit.server.account.AccountInfo;
-import com.google.gerrit.server.account.CapabilityControl;
-import com.google.gerrit.server.account.GroupBackend;
-import com.google.gerrit.server.account.Realm;
-import com.google.gerrit.server.change.ChangeJson.ChangeInfo;
-import com.google.gerrit.server.change.ChangeJson.ChangeMessageInfo;
-import com.google.gerrit.server.config.AnonymousCowardName;
-import com.google.gerrit.server.config.CanonicalWebUrl;
-import com.google.gerrit.server.config.GerritServerConfig;
-import com.google.gerrit.server.git.GitRepositoryManager;
-import com.google.gerrit.server.patch.PatchListCache;
-import com.google.gerrit.server.project.ProjectCache;
-import com.google.gerrit.server.query.change.ChangeData;
-import com.google.gwtorm.server.ListResultSet;
-import com.google.gwtorm.server.OrmException;
-import com.google.gwtorm.server.ResultSet;
-import com.google.inject.Binder;
-import com.google.inject.Guice;
-import com.google.inject.Module;
-
-import junit.framework.TestCase;
-
-import org.easymock.EasyMock;
-import org.easymock.IAnswer;
-import org.eclipse.jgit.lib.Config;
-
-import java.sql.Timestamp;
-import java.util.Collections;
-import java.util.Iterator;
-import java.util.List;
-
-public class ChangeJsonTest extends TestCase {
-
-  public void testFormatChangeMessages() throws OrmException {
-
-    // create mocks
-    final CurrentUser currentUser = createMock(CurrentUser.class);
-    final GitRepositoryManager grm = createMock(GitRepositoryManager.class);
-    final AccountByEmailCache abec = createMock(AccountByEmailCache.class);
-    final AccountCache ac = createMock(AccountCache.class);
-    final AccountInfo.Loader.Factory alf =
-        createMock(AccountInfo.Loader.Factory.class);
-    final CapabilityControl.Factory ccf =
-        createMock(CapabilityControl.Factory.class);
-    final GroupBackend gb = createMock(GroupBackend.class);
-    final Realm r = createMock(Realm.class);
-    final PatchListCache plc = createMock(PatchListCache.class);
-    final ProjectCache pc = createMock(ProjectCache.class);
-    final Config config = new Config();  // unable to mock
-    final ReviewDb rdb = createMock(ReviewDb.class);
-    final ChangeAccess ca = createMock(ChangeAccess.class);
-    final PatchSetAccess psa = createMock(PatchSetAccess.class);
-    final PatchSetApprovalAccess psaa =
-        createMock(PatchSetApprovalAccess.class);
-    final ChangeMessageAccess cma = createMock(ChangeMessageAccess.class);
-    AccountInfo.Loader accountLoader = createMock(AccountInfo.Loader.class);
-
-    // create ChangeJson instance
-    Module mod = new Module() {
-      @Override
-      public void configure(Binder binder) {
-        binder.bind(CurrentUser.class).toInstance(currentUser);
-        binder.bind(GitRepositoryManager.class).toInstance(grm);
-        binder.bind(AccountByEmailCache.class).toInstance(abec);
-        binder.bind(AccountCache.class).toInstance(ac);
-        binder.bind(AccountInfo.Loader.Factory.class).toInstance(alf);
-        binder.bind(CapabilityControl.Factory.class).toInstance(ccf);
-        binder.bind(GroupBackend.class).toInstance(gb);
-        binder.bind(Realm.class).toInstance(r);
-        binder.bind(PatchListCache.class).toInstance(plc);
-        binder.bind(ProjectCache.class).toInstance(pc);
-        binder.bind(ReviewDb.class).toInstance(rdb);
-        binder.bind(Config.class).annotatedWith(GerritServerConfig.class)
-            .toInstance(config);
-        binder.bind(String.class).annotatedWith(CanonicalWebUrl.class)
-            .toInstance("");
-        binder.bind(String.class).annotatedWith(AnonymousCowardName.class)
-            .toInstance("");
-      }
-    };
-    ChangeJson json = Guice.createInjector(mod).getInstance(ChangeJson.class);
-
-    // define mock behavior for tests
-    expect(alf.create(anyBoolean())).andReturn(accountLoader).anyTimes();
-
-    Project.NameKey proj = new Project.NameKey("ProjectNameKey");
-    Branch.NameKey forBranch = new Branch.NameKey(proj, "BranchNameKey");
-
-    Change.Key changeKey123 = new Change.Key("ChangeKey123");
-    Change.Id changeId123 = new Change.Id(123);
-    Change change123 = new Change(changeKey123, changeId123, null, forBranch);
-
-    Change.Key changeKey234 = new Change.Key("ChangeKey234");
-    Change.Id changeId234 = new Change.Id(234);
-    Change change234 = new Change(changeKey234, changeId234, null, forBranch);
-
-    expect(ca.get(Sets.newHashSet(changeId123)))
-        .andAnswer(results(Change.class, change123)).anyTimes();
-    expect(ca.get(changeId123)).andReturn(change123).anyTimes();
-    expect(ca.get(Sets.newHashSet(changeId234)))
-        .andAnswer(results(Change.class, change234));
-    expect(ca.get(changeId234)).andReturn(change234);
-    expect(rdb.changes()).andReturn(ca).anyTimes();
-
-    expect(psa.get(EasyMock.<Iterable<PatchSet.Id>>anyObject()))
-        .andAnswer(results(PatchSet.class)).anyTimes();
-    expect(rdb.patchSets()).andReturn(psa).anyTimes();
-
-    expect(psaa.byPatchSet(anyObject(PatchSet.Id.class)))
-        .andAnswer(results(PatchSetApproval.class)).anyTimes();
-    expect(rdb.patchSetApprovals()).andReturn(psaa).anyTimes();
-
-    expect(currentUser.getStarredChanges())
-        .andReturn(Collections.<Change.Id>emptySet()).anyTimes();
-
-    long timeBase = System.currentTimeMillis();
-    ChangeMessage changeMessage1 =changeMessage(
-        changeId123, "cm1", 111, timeBase, 1111, "first message");
-    ChangeMessage changeMessage2 = changeMessage(
-        changeId123, "cm2", 222, timeBase + 1000, 1111, "second message");
-    expect(cma.byChange(changeId123))
-        .andAnswer(results(ChangeMessage.class, changeMessage2, changeMessage1))
-        .anyTimes();
-    expect(cma.byChange(changeId234)).andAnswer(results(ChangeMessage.class));
-    expect(rdb.changeMessages()).andReturn(cma).anyTimes();
-
-    expect(accountLoader.get(anyObject(Account.Id.class)))
-        .andAnswer(accountForId()).anyTimes();
-    accountLoader.fill();
-    expectLastCall().anyTimes();
-
-    replay(rdb, ca, psa, psaa, alf, currentUser, cma, accountLoader);
-
-    // test 1: messages not returned by default
-    ChangeInfo ci = json.format(new ChangeData(changeId123));
-    assertNull(ci.messages);
-
-    json.addOption(ListChangesOption.MESSAGES);
-
-    // test 2: two change messages, in chronological order
-    ci = json.format(new ChangeData(changeId123));
-    assertNotNull(ci.messages);
-    assertEquals(2, ci.messages.size());
-    Iterator<ChangeMessageInfo> cmis = ci.messages.iterator();
-    assertEquals(changeMessage1, cmis.next());
-    assertEquals(changeMessage2, cmis.next());
-
-    // test 3: no change messages
-    ci = json.format(new ChangeData(changeId234));
-    assertNotNull(ci.messages);
-    assertEquals(0, ci.messages.size());
-  }
-
-  private static IAnswer<AccountInfo> accountForId() {
-    return new IAnswer<AccountInfo>() {
-      @Override
-      public AccountInfo answer() throws Throwable {
-        Account.Id id = (Account.Id) EasyMock.getCurrentArguments()[0];
-        AccountInfo ai = new AccountInfo(id);
-        return ai;
-      }};
-  }
-
-  private static <T> IAnswer<ResultSet<T>> results(Class<T> type, T... items) {
-    final List<T> list = Lists.newArrayList(items);
-    return new IAnswer<ResultSet<T>>() {
-      @Override
-      public ResultSet<T> answer() throws Throwable {
-        return new ListResultSet<T>(list);
-      }};
-  }
-
-  private static void assertEquals(ChangeMessage cm, ChangeMessageInfo cmi) {
-    assertEquals(cm.getPatchSetId().get(), (int) cmi._revisionNumber);
-    assertEquals(cm.getMessage(), cmi.message);
-    assertEquals(cm.getKey().get(), cmi.id);
-    assertEquals(cm.getWrittenOn(), cmi.date);
-    assertNotNull(cmi.author);
-    assertEquals(cm.getAuthor(), cmi.author._id);
-  }
-
-  private static ChangeMessage changeMessage(Change.Id changeId,
-      String uuid, int accountId, long time, int psId, String message) {
-    ChangeMessage.Key key = new ChangeMessage.Key(changeId, uuid);
-    Account.Id author = new Account.Id(accountId);
-    Timestamp updated = new Timestamp(time);
-    PatchSet.Id ps = new PatchSet.Id(changeId, psId);
-    ChangeMessage changeMessage = new ChangeMessage(key, author, updated, ps);
-    changeMessage.setMessage(message);
-    return changeMessage;
-  }
-}