Merge "Configurable front-end Servlet Filter as proxy surrogate."
diff --git a/Documentation/cmd-index.txt b/Documentation/cmd-index.txt
index a54803b..4f6c39d 100644
--- a/Documentation/cmd-index.txt
+++ b/Documentation/cmd-index.txt
@@ -123,6 +123,9 @@
 link:cmd-gsql.html[gerrit gsql]::
 	Administrative interface to active database.
 
+link:cmd-set-members.html[gerrit set-members]::
+	Set group members.
+
 link:cmd-set-project-parent.html[gerrit set-project-parent]::
 	Change the project permissions are inherited from.
 
diff --git a/Documentation/cmd-set-members.txt b/Documentation/cmd-set-members.txt
new file mode 100644
index 0000000..7524893
--- /dev/null
+++ b/Documentation/cmd-set-members.txt
@@ -0,0 +1,82 @@
+gerrit set-members
+==================
+
+NAME
+----
+gerrit set-members - Set group members
+
+SYNOPSIS
+--------
+[verse]
+'ssh' -p <port> <host> 'gerrit set-members'
+  [--add USER ...]
+  [--remove USER ...]
+  [--include GROUP ...]
+  [--exclude GROUP ...]
+  [--]
+  <GROUP> ...
+
+DESCRIPTION
+-----------
+Set the group members for the specified groups.
+
+OPTIONS
+-------
+<GROUP>::
+	Required; name of the group for which the members should be set.
+	The members for multiple groups can be set at once by specifying
+	multiple groups.
+
+--add::
+-a::
+	A user that should be added to the specified groups. Multiple
+	users can be added at once by using this option multiple times.
+
+--remove::
+-r::
+	Remove this user from the specified groups. Multiple users can be
+	removed at once by using this option multiple times.
+
+--include::
+-i::
+	A group that should be included to the specified groups. Multiple
+	groups can be included at once by using this option multiple
+	times.
+
+--exclude::
+-e::
+	Exclude this group from the specified groups. Multiple groups can
+	be excluded at once by using this option multiple times.
+
+The `set-members` command is processing the options in the following
+order: `--remove`, `--exclude`, `--add`, `--include`
+
+ACCESS
+------
+Any user who has configured an SSH key.
+
+SCRIPTING
+---------
+This command is intended to be used in scripts.
+
+EXAMPLES
+--------
+
+Add alice and bob, but remove eve from the groups my-committers and
+my-verifiers.
+=====
+	$ ssh -p 29418 review.example.com gerrit set-members \
+	  -a alice@example.com -a bob@example.com \
+	  -r eve@example.com my-committers my-verifiers
+=====
+
+Include the group my-friends into the group my-committers, but
+exclude the included group my-testers from the group my-committers.
+=====
+	$ ssh -p 29418 review.example.com gerrit set-members \
+	  -i my-friends -e my-testers my-committers
+=====
+
+GERRIT
+------
+Part of link:index.html[Gerrit Code Review]
diff --git a/Documentation/dev-readme.txt b/Documentation/dev-readme.txt
index a8aceaf..ced8648 100644
--- a/Documentation/dev-readme.txt
+++ b/Documentation/dev-readme.txt
@@ -57,7 +57,7 @@
 testing site for development use:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war init -d ../test_site
+  java -jar buck-out/gen/gerrit.war init -d ../test_site
 ----
 
 Accept defaults by pressing Enter until 'init' completes, or add
@@ -103,7 +103,7 @@
 copying to the test site:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war daemon -d ../test_site
+  java -jar buck-out/gen/gerrit.war daemon -d ../test_site
 ----
 
 
@@ -114,7 +114,7 @@
 command line.  If the daemon is not currently running:
 
 ----
-  java -jar gerrit-war/target/gerrit-*.war gsql -d ../test_site
+  java -jar buck-out/gen/gerrit.war gsql -d ../test_site
 ----
 
 Or, if it is running and the database is in use, connect over SSH
diff --git a/Documentation/man/Makefile b/Documentation/man/Makefile
index 75e6533..945f215 100644
--- a/Documentation/man/Makefile
+++ b/Documentation/man/Makefile
@@ -34,6 +34,7 @@
 cmd-rename-group.txt   \
 cmd-review.txt         \
 cmd-set-account.txt    \
+cmd-set-members.txt    \
 cmd-set-project-parent.txt \
 cmd-set-project.txt    \
 cmd-set-reviewers.txt  \
diff --git a/ReleaseNotes/ReleaseNotes-2.8.txt b/ReleaseNotes/ReleaseNotes-2.8.txt
index 94f4646..9952e8b 100644
--- a/ReleaseNotes/ReleaseNotes-2.8.txt
+++ b/ReleaseNotes/ReleaseNotes-2.8.txt
@@ -214,6 +214,12 @@
 * link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/cmd-ls-members.html[
 New `ls-members` command].
 
+* link:http://gerrit-documentation.googlecode.com/svn/Documentation/2.8/cmd-set-members.html[
+New `set-members` command]
++
+New command to manipulate group membership. Members can be added or removed
+and groups can be included or excluded in one specific group or number of groups.
+
 
 Daemon
 ~~~~~~
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 a9b5e85..5ddf1ae 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
@@ -124,7 +124,7 @@
     if (!super.equals(obj) || !(obj instanceof AccessSection)) {
       return false;
     }
-    return new HashSet<Permission>(permissions).equals(new HashSet<Permission>(
-        ((AccessSection) obj).permissions));
+    return new HashSet<Permission>(getPermissions()).equals(new HashSet<Permission>(
+        ((AccessSection) obj).getPermissions()));
   }
 }
diff --git a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
index 10d0b13..69263d9 100644
--- a/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
+++ b/gerrit-common/src/main/java/com/google/gerrit/common/data/Permission.java
@@ -261,8 +261,8 @@
     if (!name.equals(other.name) || exclusiveGroup != other.exclusiveGroup) {
       return false;
     }
-    return new HashSet<PermissionRule>(rules)
-        .equals(new HashSet<PermissionRule>(other.rules));
+    return new HashSet<PermissionRule>(getRules())
+        .equals(new HashSet<PermissionRule>(other.getRules()));
   }
 
   @Override
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 463f2d6..99a67d8 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
@@ -624,8 +624,16 @@
 
     @Override
     public String getServletPath() {
-      return ((HttpServletRequest) getRequest()).getRequestURI().substring(
-          contextPath.length());
+      return getRequestURI().substring(contextPath.length());
+    }
+
+    @Override
+    public String getRequestURI() {
+      String uri = super.getRequestURI();
+      if (uri.startsWith("/a/")) {
+        uri = uri.substring(2);
+      }
+      return uri;
     }
   }
 }
diff --git a/gerrit-lucene/BUCK b/gerrit-lucene/BUCK
index 4e6503e..927b66a 100644
--- a/gerrit-lucene/BUCK
+++ b/gerrit-lucene/BUCK
@@ -4,7 +4,7 @@
   deps = [
     '//gerrit-antlr:query_exception',
     '//gerrit-extension-api:api',
-    '//gerrit-reviewdb:client',
+    '//gerrit-reviewdb:server',
     '//gerrit-server:server',
     '//lib:guava',
     '//lib:gwtorm',
diff --git a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
index 63bfa71..bbef3ad 100644
--- a/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
+++ b/gerrit-server/src/main/java/com/google/gerrit/server/events/EventFactory.java
@@ -110,6 +110,7 @@
     a.subject = change.getSubject();
     a.url = getChangeUrl(change);
     a.owner = asAccountAttribute(change.getOwner());
+    a.status = change.getStatus();
     return a;
   }
 
@@ -141,7 +142,6 @@
     a.lastUpdated = change.getLastUpdatedOn().getTime() / 1000L;
     a.sortKey = change.getSortKey();
     a.open = change.getStatus().isOpen();
-    a.status = change.getStatus();
   }
 
   /**
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
index 30f3c95..abe202e 100644
--- a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/MasterCommandModule.java
@@ -39,6 +39,7 @@
     // deprecated alias to review command
     alias(gerrit, "approve", ReviewCommand.class);
     command(gerrit, SetAccountCommand.class);
+    command(gerrit, SetMembersCommand.class);
     command(gerrit, SetProjectCommand.class);
 
     command(gerrit, "test-submit").toProvider(new DispatchCommandProvider(testSubmit));
diff --git a/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
new file mode 100644
index 0000000..cd2710f
--- /dev/null
+++ b/gerrit-sshd/src/main/java/com/google/gerrit/sshd/commands/SetMembersCommand.java
@@ -0,0 +1,164 @@
+// 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.sshd.commands;
+
+import com.google.common.base.Function;
+import com.google.common.base.Joiner;
+import com.google.common.base.Objects;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Lists;
+import com.google.gerrit.extensions.restapi.IdString;
+import com.google.gerrit.extensions.restapi.TopLevelResource;
+import com.google.gerrit.reviewdb.client.Account;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.server.account.AccountCache;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.group.AddIncludedGroups;
+import com.google.gerrit.server.group.AddMembers;
+import com.google.gerrit.server.group.DeleteIncludedGroups;
+import com.google.gerrit.server.group.DeleteMembers;
+import com.google.gerrit.server.group.GroupResource;
+import com.google.gerrit.server.group.GroupsCollection;
+import com.google.gerrit.sshd.CommandMetaData;
+import com.google.gerrit.sshd.SshCommand;
+import com.google.inject.Inject;
+import com.google.inject.Provider;
+
+import org.kohsuke.args4j.Argument;
+import org.kohsuke.args4j.Option;
+
+import java.io.IOException;
+import java.io.UnsupportedEncodingException;
+import java.util.List;
+
+@CommandMetaData(name = "set-members", descr = "Modifies members of specific group or number of groups")
+public class SetMembersCommand extends SshCommand {
+
+  @Option(name = "--add", aliases = {"-a"}, metaVar = "USER", usage = "users that should be added as group member")
+  private List<Account.Id> accountsToAdd = Lists.newArrayList();
+
+  @Option(name = "--remove", aliases = {"-r"}, metaVar = "USER", usage = "users that should be removed from the group")
+  private List<Account.Id> accountsToRemove = Lists.newArrayList();
+
+  @Option(name = "--include", aliases = {"-i"}, metaVar = "GROUP", usage = "group that should be included as group member")
+  private List<AccountGroup.UUID> groupsToInclude = Lists.newArrayList();
+
+  @Option(name = "--exclude", aliases = {"-e"}, metaVar = "GROUP", usage = "group that should be excluded from the group")
+  private List<AccountGroup.UUID> groupsToRemove = Lists.newArrayList();
+
+  @Argument(index = 0, required = true, multiValued = true, metaVar = "GROUP", usage = "groups to modify")
+  private List<AccountGroup.UUID> groups = Lists.newArrayList();
+
+  @Inject
+  private Provider<AddMembers> addMembers;
+
+  @Inject
+  private Provider<DeleteMembers> deleteMembers;
+
+  @Inject
+  private Provider<AddIncludedGroups> addIncludedGroups;
+
+  @Inject
+  private Provider<DeleteIncludedGroups> deleteIncludedGroups;
+
+  @Inject
+  private GroupsCollection groupsCollection;
+
+  @Inject
+  private GroupCache groupCache;
+
+  @Inject
+  private AccountCache accountCache;
+
+  @Override
+  protected void run() throws UnloggedFailure, Failure, Exception {
+    for (AccountGroup.UUID groupUuid : groups) {
+      GroupResource resource =
+          groupsCollection.parse(TopLevelResource.INSTANCE,
+              IdString.fromUrl(groupUuid.get()));
+      if (!accountsToRemove.isEmpty()) {
+        deleteMembers.get().apply(resource, fromMembers(accountsToRemove));
+        reportMembersAction("removed from", resource, accountsToRemove);
+      }
+      if (!groupsToRemove.isEmpty()) {
+        deleteIncludedGroups.get().apply(resource, fromGroups(groupsToRemove));
+        reportGroupsAction("excluded from", resource, groupsToRemove);
+      }
+      if (!accountsToAdd.isEmpty()) {
+        addMembers.get().apply(resource, fromMembers(accountsToAdd));
+        reportMembersAction("added to", resource, accountsToAdd);
+      }
+      if (!groupsToInclude.isEmpty()) {
+        addIncludedGroups.get().apply(resource, fromGroups(groupsToInclude));
+        reportGroupsAction("included to", resource, groupsToInclude);
+      }
+    }
+  }
+
+  private void reportMembersAction(String action, GroupResource group,
+      List<Account.Id> accountIdList) throws UnsupportedEncodingException,
+      IOException {
+    out.write(String.format(
+        "Members %s group %s: %s\n",
+        action,
+        group.getName(),
+        Joiner.on(", ").join(
+            Iterables.transform(accountIdList,
+                new Function<Account.Id, String>() {
+                  @Override
+                  public String apply(Account.Id accountId) {
+                    return Objects.firstNonNull(accountCache.get(accountId)
+                        .getAccount().getPreferredEmail(), "n/a");
+                  }
+                }))).getBytes(ENC));
+  }
+
+  private void reportGroupsAction(String action, GroupResource group,
+      List<AccountGroup.UUID> groupUuidList)
+      throws UnsupportedEncodingException, IOException {
+    out.write(String.format(
+        "Groups %s group %s: %s\n",
+        action,
+        group.getName(),
+        Joiner.on(", ").join(
+            Iterables.transform(groupUuidList,
+                new Function<AccountGroup.UUID, String>() {
+                  @Override
+                  public String apply(AccountGroup.UUID uuid) {
+                    return groupCache.get(uuid).getName();
+                  }
+                }))).getBytes(ENC));
+  }
+
+  private AddIncludedGroups.Input fromGroups(List<AccountGroup.UUID> accounts) {
+    return AddIncludedGroups.Input.fromGroups(Lists.newArrayList(Iterables
+        .transform(accounts, new Function<AccountGroup.UUID, String>() {
+          @Override
+          public String apply(AccountGroup.UUID uuid) {
+            return uuid.toString();
+          }
+        })));
+  }
+
+  private AddMembers.Input fromMembers(List<Account.Id> accounts) {
+    return AddMembers.Input.fromMembers(Lists.newArrayList(Iterables.transform(
+        accounts, new Function<Account.Id, String>() {
+          @Override
+          public String apply(Account.Id id) {
+            return id.toString();
+          }
+        })));
+  }
+}
diff --git a/lib/BUCK b/lib/BUCK
index 0ce791f..5f55f70 100644
--- a/lib/BUCK
+++ b/lib/BUCK
@@ -253,6 +253,10 @@
   bin_sha1 = 'd4e40fe5661b8de5d8c66db3d63a47b6b3ecf7f3',
   src_sha1 = '86c29288b1930e33ba7ffea1b866af9a52d3d24a',
   license = 'Apache2.0',
+  exclude = [
+    'META-INF/LICENSE.txt',
+    'META-INF/NOTICE.txt',
+  ],
 )
 
 maven_jar(
@@ -261,4 +265,8 @@
   bin_sha1 = 'e7c3976156d292f696016e138b67ab5e6bfc1a56',
   src_sha1 = '3606622b3c1f09b4b7cf34070cbf60d414af9b6b',
   license = 'Apache2.0',
+  exclude = [
+    'META-INF/LICENSE.txt',
+    'META-INF/NOTICE.txt',
+  ],
 )