Merge "Fix problems with applying css-styles to elements"
diff --git a/java/com/google/gerrit/server/project/ProjectConfig.java b/java/com/google/gerrit/server/project/ProjectConfig.java
index d01954c..7eb5f57 100644
--- a/java/com/google/gerrit/server/project/ProjectConfig.java
+++ b/java/com/google/gerrit/server/project/ProjectConfig.java
@@ -1191,6 +1191,7 @@
   }
 
   private void saveAccountsSection(Config rc, Set<AccountGroup.UUID> keepGroups) {
+    unsetSection(rc, ACCOUNTS);
     if (accountsSection != null) {
       rc.setStringList(
           ACCOUNTS,
@@ -1201,6 +1202,7 @@
   }
 
   private void saveCommentLinkSections(Config rc) {
+    unsetSection(rc, COMMENTLINK);
     if (commentLinkSections != null) {
       for (CommentLinkInfoImpl cm : commentLinkSections.values()) {
         rc.setString(COMMENTLINK, cm.name, KEY_MATCH, cm.match);
@@ -1218,6 +1220,7 @@
   }
 
   private void saveContributorAgreements(Config rc, Set<AccountGroup.UUID> keepGroups) {
+    unsetSection(rc, CONTRIBUTOR_AGREEMENT);
     for (ContributorAgreement ca : sort(contributorAgreements.values())) {
       set(rc, CONTRIBUTOR_AGREEMENT, ca.getName(), KEY_DESCRIPTION, ca.getDescription());
       set(rc, CONTRIBUTOR_AGREEMENT, ca.getName(), KEY_AGREEMENT_URL, ca.getAgreementUrl());
@@ -1251,6 +1254,7 @@
   }
 
   private void saveNotifySections(Config rc, Set<AccountGroup.UUID> keepGroups) {
+    unsetSection(rc, NOTIFY);
     for (NotifyConfig nc : sort(notifySections.values())) {
       nc.getGroups().stream()
           .map(GroupReference::getUUID)
@@ -1305,6 +1309,7 @@
   }
 
   private void saveAccessSections(Config rc, Set<AccountGroup.UUID> keepGroups) {
+    unsetSection(rc, CAPABILITY);
     AccessSection capability = accessSections.get(AccessSection.GLOBAL_CAPABILITIES);
     if (capability != null) {
       Set<String> have = new HashSet<>();
@@ -1387,9 +1392,7 @@
     List<String> existing = new ArrayList<>(rc.getSubsections(LABEL));
     if (!new ArrayList<>(labelSections.keySet()).equals(existing)) {
       // Order of sections changed, remove and rewrite them all.
-      for (String name : existing) {
-        rc.unsetSection(LABEL, name);
-      }
+      unsetSection(rc, LABEL);
     }
 
     Set<String> toUnset = new HashSet<>(existing);
@@ -1485,11 +1488,7 @@
   }
 
   private void savePluginSections(Config rc, Set<AccountGroup.UUID> keepGroups) {
-    List<String> existing = new ArrayList<>(rc.getSubsections(PLUGIN));
-    for (String name : existing) {
-      rc.unsetSection(PLUGIN, name);
-    }
-
+    unsetSection(rc, PLUGIN);
     for (Map.Entry<String, Config> e : pluginConfigs.entrySet()) {
       String plugin = e.getKey();
       Config pluginConfig = e.getValue();
@@ -1530,6 +1529,13 @@
     }
   }
 
+  private void unsetSection(Config rc, String sectionName) {
+    for (String subSectionName : rc.getSubsections(sectionName)) {
+      rc.unsetSection(sectionName, subSectionName);
+    }
+    rc.unsetSection(sectionName, null);
+  }
+
   private <E extends Enum<?>> E getEnum(
       Config rc, String section, String subsection, String name, E defaultValue) {
     try {
diff --git a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
index 07cfe2f..0b401df 100644
--- a/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
+++ b/javatests/com/google/gerrit/server/project/ProjectConfigTest.java
@@ -318,17 +318,17 @@
                 + "\tsubmit = group Staff\n"
                 + "  upload = group Developers\n"
                 + "  read = group Developers\n"
-                + "[accounts]\n"
-                + "  sameGroupVisibility = group Staff\n"
-                + "[contributor-agreement \"Individual\"]\n"
-                + "  description = A new description\n"
-                + "  accepted = group Staff\n"
-                + "  agreementUrl = http://www.example.com/agree\n"
-                + "\texcludeProjects = ^/theirproject\n"
                 + "[label \"CustomLabel\"]\n"
                 + LABEL_SCORES_CONFIG
                 + "\tfunction = MaxWithBlock\n" // label gets this function when it is created
-                + "\tdefaultValue = 0\n"); //  label gets this value when it is created
+                + "\tdefaultValue = 0\n" //  label gets this value when it is created
+                + "[accounts]\n"
+                + "\tsameGroupVisibility = group Staff\n"
+                + "[contributor-agreement \"Individual\"]\n"
+                + "\tdescription = A new description\n"
+                + "\tagreementUrl = http://www.example.com/agree\n"
+                + "\taccepted = group Staff\n"
+                + "\texcludeProjects = ^/theirproject\n");
   }
 
   @Test
@@ -454,27 +454,6 @@
   }
 
   @Test
-  public void pluginSectionIsUnsetIfAllPluginConfigsAreEmpty() throws Exception {
-    RevCommit rev =
-        tr.commit()
-            .add(
-                "project.config",
-                "[commentlink \"bugzilla\"]\n"
-                    + "  match = \"(bugs#?)(d+)\"\n"
-                    + "[plugin \"somePlugin\"]\n"
-                    + "  key = value\n")
-            .create();
-    update(rev);
-
-    ProjectConfig cfg = read(rev);
-    PluginConfig pluginCfg = cfg.getPluginConfig("somePlugin");
-    pluginCfg.unset("key");
-    rev = commit(cfg);
-    assertThat(text(rev, "project.config"))
-        .isEqualTo("[commentlink \"bugzilla\"]\n  match = \"(bugs#?)(d+)\"\n");
-  }
-
-  @Test
   public void readPluginConfigGroupReference() throws Exception {
     RevCommit rev =
         tr.commit()
@@ -661,6 +640,118 @@
         .isEqualTo(InheritableBoolean.INHERIT);
   }
 
+  @Test
+  public void accountsSectionIsUnsetIfNoSameGroupVisibilityIsSet() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+                    + "[accounts]\n"
+                    + "  sameGroupVisibility = group Staff\n")
+            .create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    cfg.getAccountsSection().setSameGroupVisibility(ImmutableList.of());
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo(
+            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+  }
+
+  @Test
+  public void contributorSectionIsUnsetIfNoContributorAgreementIsSet() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+                    + "[contributor-agreement \"Individual\"]\n"
+                    + "  accepted = group Developers\n"
+                    + "  accepted = group Staff\n")
+            .create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    ContributorAgreement section = cfg.getContributorAgreement("Individual");
+    section.setAccepted(ImmutableList.of());
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo(
+            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+  }
+
+  @Test
+  public void notifySectionIsUnsetIfNoNotificationsAreSet() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+                    + "[notify \"name\"]\n"
+                    + "  email = example@example.com\n")
+            .create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    cfg.getNotifyConfigs().clear();
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo(
+            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+  }
+
+  @Test
+  public void commentLinkSectionIsUnsetIfNoCommentLinksAreSet() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+                    + "[notify \"name\"]\n"
+                    + "  email = example@example.com\n")
+            .create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    cfg.getCommentLinkSections().clear();
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo("[notify \"name\"]\n\temail = example@example.com\n");
+  }
+
+  @Test
+  public void pluginSectionIsUnsetIfAllPluginConfigsAreEmpty() throws Exception {
+    RevCommit rev =
+        tr.commit()
+            .add(
+                "project.config",
+                "[commentlink \"bugzilla\"]\n"
+                    + "\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n"
+                    + "\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n"
+                    + "[plugin \"somePlugin\"]\n"
+                    + "  key = value\n")
+            .create();
+    update(rev);
+
+    ProjectConfig cfg = read(rev);
+    PluginConfig pluginCfg = cfg.getPluginConfig("somePlugin");
+    pluginCfg.unset("key");
+    rev = commit(cfg);
+    assertThat(text(rev, "project.config"))
+        .isEqualTo(
+            "[commentlink \"bugzilla\"]\n\tmatch = \"(bug\\\\s+#?)(\\\\d+)\"\n\tlink = http://bugs.example.com/show_bug.cgi?id=$2\n");
+  }
+
   private Path writeDefaultAllProjectsConfig(String... lines) throws IOException {
     Path dir = sitePaths.etc_dir.resolve(ALL_PROJECTS.get());
     Files.createDirectories(dir);
diff --git a/polygerrit-ui/app/elements/change/gr-message/gr-message.html b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
index b828327..a0f6fbe 100644
--- a/polygerrit-ui/app/elements/change/gr-message/gr-message.html
+++ b/polygerrit-ui/app/elements/change/gr-message/gr-message.html
@@ -37,6 +37,7 @@
         display: block;
         position: relative;
         cursor: pointer;
+        overflow-y: hidden;
       }
       :host(.expanded) {
         cursor: auto;
diff --git a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
index e21fc72..feef57d 100644
--- a/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
+++ b/polygerrit-ui/app/elements/plugins/gr-endpoint-param/gr-endpoint-param.js
@@ -25,7 +25,23 @@
       value: {
         type: Object,
         notify: true,
+        observer: '_valueChanged',
       },
     },
+
+    _valueChanged(newValue, oldValue) {
+      /* In polymer 2 the following change was made:
+      "Property change notifications (property-changed events) aren't fired when
+      the value changes as a result of a binding from the host"
+      (see https://polymer-library.polymer-project.org/2.0/docs/about_20).
+      To workaround this problem, we fire the event from the observer.
+      In some cases this fire the event twice, but our code is
+      ready for it.
+      */
+      const detail = {
+        value: newValue,
+      };
+      this.dispatchEvent(new CustomEvent('value-changed', {detail}));
+    },
   });
 })();
diff --git a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
index 1d608c0..55e09ea 100644
--- a/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
+++ b/polygerrit-ui/app/elements/shared/gr-dropdown-list/gr-dropdown-list.html
@@ -48,7 +48,7 @@
         max-height: 70vh;
         margin-top: 2em;
         min-width: 266px;
-        @apply --dropdown-content-style
+        @apply --dropdown-content-style;
       }
       paper-listbox {
         --paper-listbox: {
diff --git a/polygerrit-ui/server.go b/polygerrit-ui/server.go
index 305a6b8..1a2d299 100644
--- a/polygerrit-ui/server.go
+++ b/polygerrit-ui/server.go
@@ -38,11 +38,12 @@
 )
 
 var (
-	plugins    = flag.String("plugins", "", "comma seperated plugin paths to serve")
-	port       = flag.String("port", ":8081", "Port to serve HTTP requests on")
-	host       = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
-	scheme     = flag.String("scheme", "https", "URL scheme")
-	cdnPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_ui/[0-9.]*")
+	plugins               = flag.String("plugins", "", "comma seperated plugin paths to serve")
+	port                  = flag.String("port", ":8081", "Port to serve HTTP requests on")
+	host                  = flag.String("host", "gerrit-review.googlesource.com", "Host to proxy requests to")
+	scheme                = flag.String("scheme", "https", "URL scheme")
+	cdnPattern            = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_ui/[0-9.]*")
+	bundledPluginsPattern = regexp.MustCompile("https://cdn.googlesource.com/polygerrit_assets/[0-9.]*")
 )
 
 func main() {
@@ -192,6 +193,9 @@
 	// contains window.INITIAL_DATA=...
 	// Here we rely on the fact that the <script> snippet that we want to append to is the first one.
 	if len(*plugins) > 0 {
+		// If the host page contains a reference to a plugin bundle that would be preloaded, then remove it.
+		replaced = bundledPluginsPattern.ReplaceAllString(replaced, "")
+
 		insertionPoint := strings.Index(replaced, "</script>")
 		builder := new(strings.Builder)
 		builder.WriteString(