Merge branch 'stable-2.13' into stable-2.14

* stable-2.13:
  Change copyright to AOSP

Change-Id: I26c16e7a912d9e7edecfe2fa501315d07a9ca88b
diff --git a/.buckconfig b/.buckconfig
deleted file mode 100644
index 0a84b64..0000000
--- a/.buckconfig
+++ /dev/null
@@ -1,15 +0,0 @@
-[alias]
-  project-group-structure = //:project-group-structure
-  plugin = //:project-group-structure
-  src = //:project-group-structure-sources
-
-[java]
-  src_roots = java, resources
-
-[project]
-  ignore = .git
-
-[cache]
-  mode = dir
-  dir = buck-out/cache
-
diff --git a/.gitignore b/.gitignore
index d781c5f..72f041f 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,9 +1,6 @@
-/.buckd
-/.buckversion
 /.classpath
+/.primary_build_tool
 /.project
 /.settings/
-/.watchmanconfig
-/buck-out
-/bucklets
+/bazel-*
 /eclipse-out/
diff --git a/BUCK b/BUCK
deleted file mode 100644
index dfd8810..0000000
--- a/BUCK
+++ /dev/null
@@ -1,42 +0,0 @@
-include_defs('//bucklets/gerrit_plugin.bucklet')
-include_defs('//bucklets/java_sources.bucklet')
-
-SOURCES = glob(['src/main/java/**/*.java'])
-RESOURCES = glob(['src/main/resources/**/*'])
-
-TEST_DEPS = GERRIT_TESTS + GERRIT_PLUGIN_API + [
-  ':project-group-structure__plugin',
-]
-
-gerrit_plugin(
-  name = 'project-group-structure',
-  srcs = SOURCES,
-  resources = RESOURCES,
-  manifest_entries = [
-    'Gerrit-PluginName: project-group-structure',
-    'Gerrit-ApiType: plugin',
-    'Gerrit-Module: com.ericsson.gerrit.plugins.projectgroupstructure.Module',
-    'Implementation-Title: project-group-structure plugin',
-    'Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/project-group-structure',
-    'Implementation-Vendor: Ericsson',
-  ],
-)
-
-java_test(
-  name = 'project-group-structure-tests',
-  srcs = glob(['src/test/java/**/*.java']),
-  labels = ['project-group-structure'],
-  deps = TEST_DEPS,
-)
-
-java_sources(
-  name = 'project-group-structure-sources',
-  srcs = SOURCES + RESOURCES,
-)
-
-# this is required for bucklets/tools/eclipse/project.py to work
-java_library(
-  name = 'classpath',
-  deps = TEST_DEPS,
-)
-
diff --git a/BUILD b/BUILD
new file mode 100644
index 0000000..41af36e
--- /dev/null
+++ b/BUILD
@@ -0,0 +1,38 @@
+load("//tools/bzl:junit.bzl", "junit_tests")
+load(
+    "//tools/bzl:plugin.bzl",
+    "gerrit_plugin",
+    "PLUGIN_DEPS",
+    "PLUGIN_TEST_DEPS",
+)
+
+gerrit_plugin(
+    name = "project-group-structure",
+    srcs = glob(["src/main/java/**/*.java"]),
+    manifest_entries = [
+        "Gerrit-PluginName: project-group-structure",
+        "Gerrit-Module: com.ericsson.gerrit.plugins.projectgroupstructure.Module",
+        "Implementation-Title: project-group-structure plugin",
+        "Implementation-URL: https://gerrit-review.googlesource.com/#/admin/projects/plugins/project-group-structure",
+        "Implementation-Vendor: Ericsson",
+    ],
+    resources = glob(["src/main/resources/**/*"]),
+)
+
+junit_tests(
+    name = "project_group_structure_tests",
+    srcs = glob(["src/test/java/**/*.java"]),
+    tags = ["project-group-structure"],
+    deps = [
+        ":project-group-structure__plugin_test_deps",
+    ],
+)
+
+java_library(
+    name = "project-group-structure__plugin_test_deps",
+    testonly = 1,
+    visibility = ["//visibility:public"],
+    exports = PLUGIN_DEPS + PLUGIN_TEST_DEPS + [
+        ":project-group-structure__plugin",
+    ],
+)
diff --git a/WORKSPACE b/WORKSPACE
new file mode 100644
index 0000000..0f5da43
--- /dev/null
+++ b/WORKSPACE
@@ -0,0 +1,26 @@
+workspace(name = "project_group_structure")
+
+load("//:bazlets.bzl", "load_bazlets")
+
+load_bazlets(
+    commit = "11ce7521051ca73598d099aa8a396c9ffe932a74",
+    #    local_path = "/home/<user>/projects/bazlets",
+)
+
+#Snapshot Plugin API
+#load(
+#    "@com_googlesource_gerrit_bazlets//:gerrit_api_maven_local.bzl",
+#    "gerrit_api_maven_local",
+#)
+
+# Load snapshot Plugin API
+#gerrit_api_maven_local()
+
+# Release Plugin API
+load(
+    "@com_googlesource_gerrit_bazlets//:gerrit_api.bzl",
+    "gerrit_api",
+)
+
+# Load release Plugin API
+gerrit_api()
diff --git a/bazlets.bzl b/bazlets.bzl
new file mode 100644
index 0000000..e14e488
--- /dev/null
+++ b/bazlets.bzl
@@ -0,0 +1,17 @@
+NAME = "com_googlesource_gerrit_bazlets"
+
+def load_bazlets(
+    commit,
+    local_path = None
+  ):
+  if not local_path:
+      native.git_repository(
+          name = NAME,
+          remote = "https://gerrit.googlesource.com/bazlets",
+          commit = commit,
+      )
+  else:
+      native.local_repository(
+          name = NAME,
+          path = local_path,
+      )
diff --git a/lib/gerrit/BUCK b/lib/gerrit/BUCK
deleted file mode 100644
index 950a90d..0000000
--- a/lib/gerrit/BUCK
+++ /dev/null
@@ -1,21 +0,0 @@
-include_defs('//bucklets/maven_jar.bucklet')
-
-VER = '2.13'
-REPO = MAVEN_CENTRAL
-
-maven_jar(
-  name = 'plugin-api',
-  id = 'com.google.gerrit:gerrit-plugin-api:' + VER,
-  sha1 = 'e25d55b8f41627c4ae6b9d2069ec398638b219a3',
-  license = 'Apache2.0',
-  attach_source = False,
-  repository = REPO,
-)
-maven_jar(
-  name = 'acceptance-framework',
-  id = 'com.google.gerrit:gerrit-acceptance-framework:' + VER,
-  sha1 = 'a6913a61196a8fccdb45e761f43a0b7e21867c90',
-  license = 'Apache2.0',
-  attach_source = False,
-  repository = REPO,
-)
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java
new file mode 100644
index 0000000..466fb55
--- /dev/null
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRights.java
@@ -0,0 +1,191 @@
+// Copyright (C) 2018 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.ericsson.gerrit.plugins.projectgroupstructure;
+
+import com.google.gerrit.common.data.AccessSection;
+import com.google.gerrit.common.data.Permission;
+import com.google.gerrit.common.data.PermissionRule;
+import com.google.gerrit.common.data.RefConfigSection;
+import com.google.gerrit.common.errors.InvalidNameException;
+import com.google.gerrit.extensions.annotations.PluginData;
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.account.GroupCache;
+import com.google.gerrit.server.git.MetaDataUpdate;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectCache;
+import com.google.gerrit.server.project.ProjectState;
+import com.google.gerrit.server.project.RefPattern;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import java.io.IOException;
+import java.nio.file.Path;
+import java.util.Set;
+import java.util.regex.Pattern;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.util.FS;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Set defaults access rights for root projects.
+ *
+ * <p>Default access rights are read from <review_site>/data/project-group-structure/project.config.
+ * The format of that file is the same as regular project.config except that group, in addition to
+ * be a group name, can be set to token ${owner} instead which will be replaced by the group owning
+ * the project.
+ */
+@Singleton
+public class DefaultAccessRights implements NewProjectCreatedListener {
+  private static final Logger log = LoggerFactory.getLogger(DefaultAccessRights.class);
+  private static final String OWNER_TOKEN = "${owner}";
+
+  private final GroupCache groupCache;
+  private final ProjectCache projectCache;
+  private final MetaDataUpdate.User metaDataUpdateFactory;
+  private final FileBasedConfig defaultAccessRightsConfig;
+
+  @Inject
+  public DefaultAccessRights(
+      MetaDataUpdate.User metaDataUpdateFactory,
+      ProjectCache projectCache,
+      GroupCache groupCache,
+      @PluginData Path dataDir) {
+    this.groupCache = groupCache;
+    this.projectCache = projectCache;
+    this.metaDataUpdateFactory = metaDataUpdateFactory;
+    defaultAccessRightsConfig =
+        new FileBasedConfig(dataDir.resolve(ProjectConfig.PROJECT_CONFIG).toFile(), FS.DETECTED);
+    try {
+      defaultAccessRightsConfig.load();
+    } catch (IOException | ConfigInvalidException e) {
+      // Swallow the exception to allow the plugin to load, we still want the
+      // project structure to be enforced even if defaults access rights will
+      // not be set.
+      log.error(
+          "Failed to load default access rights config {}, no access right will be set on root projects: {}",
+          defaultAccessRightsConfig.getFile().getAbsolutePath(),
+          e.getMessage(),
+          e);
+    }
+  }
+
+  @Override
+  public void onNewProjectCreated(NewProjectCreatedListener.Event event) {
+    String projectName = event.getProjectName();
+    // only set default access rights for root projects, if configured.
+    if (projectName.contains("/") || defaultAccessRightsConfig.getSections().isEmpty()) {
+      return;
+    }
+
+    ProjectState project = projectCache.get(Project.NameKey.parse(projectName));
+    if (project == null) {
+      log.error("Could not retrieve projet {} from cache", projectName);
+      return;
+    }
+
+    try (MetaDataUpdate md = metaDataUpdateFactory.create(project.getProject().getNameKey())) {
+      ProjectConfig config = ProjectConfig.read(md);
+      setAccessRights(config, project);
+      md.setMessage("Set default access rights\n");
+      config.commit(md);
+    } catch (Exception e) {
+      log.error("Failed to set defauts access rights {}", e.getMessage(), e);
+    }
+  }
+
+  private void setAccessRights(ProjectConfig config, ProjectState project) {
+    for (String refName : defaultAccessRightsConfig.getSubsections(ProjectConfig.ACCESS)) {
+      if (RefConfigSection.isValid(refName) && isValidRegex(refName)) {
+        AccessSection as = config.getAccessSection(refName, true);
+        for (String varName :
+            defaultAccessRightsConfig.getStringList(
+                ProjectConfig.ACCESS, refName, "exclusiveGroupPermissions")) {
+          for (String n : varName.split("[, \t]{1,}")) {
+            if (Permission.isPermission(n)) {
+              as.getPermission(n, true).setExclusiveGroup(true);
+            }
+          }
+        }
+        String ownerGroupName = getOwerGroupName(project);
+        for (String value : defaultAccessRightsConfig.getNames(ProjectConfig.ACCESS, refName)) {
+          if (Permission.isPermission(value)) {
+            Permission perm = as.getPermission(value, true);
+            setPermissionRules(ownerGroupName, perm, refName, value);
+          } else {
+            log.error("Invalid permission {}", value);
+          }
+        }
+      }
+    }
+  }
+
+  private String getOwerGroupName(ProjectState project) {
+    Set<AccountGroup.UUID> owners = project.getAllOwners();
+    if (!owners.isEmpty()) {
+      return groupCache.get(owners.iterator().next()).getName();
+    }
+    return String.format("no owners for project %s", project.getProject().getName());
+  }
+
+  private boolean isValidRegex(String refPattern) {
+    try {
+      RefPattern.validateRegExp(refPattern);
+    } catch (InvalidNameException e) {
+      log.error("Invalid ref name: " + e.getMessage());
+      return false;
+    }
+    return true;
+  }
+
+  private void setPermissionRules(
+      String ownerGroupName, Permission perm, String refName, String value) {
+    for (String ruleString :
+        defaultAccessRightsConfig.getStringList(ProjectConfig.ACCESS, refName, value)) {
+      PermissionRule rule;
+      try {
+        rule =
+            PermissionRule.fromString(
+                ruleString.replaceAll(Pattern.quote(OWNER_TOKEN), ownerGroupName),
+                Permission.hasRange(value));
+
+      } catch (IllegalArgumentException notRule) {
+        log.error(
+            "Invalid rule in {}{}.{}: {}",
+            ProjectConfig.ACCESS,
+            refName != null ? "." + refName : "",
+            value,
+            notRule.getMessage());
+        continue;
+      }
+
+      if (rule.getGroup().getUUID() == null) {
+        // this means that group is not already in the groups file, so
+        // we need to check if group exist if if it does, get its
+        // uuid.
+        AccountGroup group = groupCache.get(new AccountGroup.NameKey(rule.getGroup().getName()));
+
+        if (group == null) {
+          log.error("Group {} not found", rule.getGroup().getName());
+          continue;
+        }
+        rule.getGroup().setUUID(group.getGroupUUID());
+      }
+      perm.add(rule);
+    }
+  }
+}
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Module.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Module.java
index 5e80a96..ca9b88c 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Module.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/Module.java
@@ -14,15 +14,16 @@
 
 package com.ericsson.gerrit.plugins.projectgroupstructure;
 
+import com.google.gerrit.extensions.events.NewProjectCreatedListener;
 import com.google.gerrit.extensions.registration.DynamicSet;
 import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.inject.AbstractModule;
 
-
 class Module extends AbstractModule {
   @Override
   protected void configure() {
     DynamicSet.bind(binder(), ProjectCreationValidationListener.class)
         .to(ProjectCreationValidator.class);
+    DynamicSet.bind(binder(), NewProjectCreatedListener.class).to(DefaultAccessRights.class);
   }
 }
diff --git a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
index d11afca..e422f17 100644
--- a/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
+++ b/src/main/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidator.java
@@ -16,7 +16,9 @@
 
 import com.google.common.base.Charsets;
 import com.google.common.hash.Hashing;
+import com.google.gerrit.common.data.GroupReference;
 import com.google.gerrit.extensions.annotations.PluginCanonicalWebUrl;
+import com.google.gerrit.extensions.annotations.PluginName;
 import com.google.gerrit.extensions.api.groups.GroupInput;
 import com.google.gerrit.extensions.common.GroupInfo;
 import com.google.gerrit.extensions.restapi.ResourceConflictException;
@@ -24,37 +26,38 @@
 import com.google.gerrit.extensions.restapi.TopLevelResource;
 import com.google.gerrit.reviewdb.client.AccountGroup;
 import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.account.GroupMembership;
 import com.google.gerrit.server.config.AllProjectsNameProvider;
+import com.google.gerrit.server.config.PluginConfigFactory;
 import com.google.gerrit.server.group.CreateGroup;
 import com.google.gerrit.server.project.CreateProjectArgs;
+import com.google.gerrit.server.project.NoSuchProjectException;
 import com.google.gerrit.server.project.ProjectControl;
 import com.google.gerrit.server.validators.ProjectCreationValidationListener;
 import com.google.gerrit.server.validators.ValidationException;
 import com.google.gwtorm.server.OrmException;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
-
+import java.io.IOException;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 
-import java.io.IOException;
-
 @Singleton
-public class ProjectCreationValidator
-    implements ProjectCreationValidationListener {
-  private static final Logger log =
-      LoggerFactory.getLogger(ProjectCreationValidator.class);
+public class ProjectCreationValidator implements ProjectCreationValidationListener {
+  private static final Logger log = LoggerFactory.getLogger(ProjectCreationValidator.class);
 
   private static final String AN_ERROR_OCCURRED_MSG =
       "An error occurred while creating project, please contact Gerrit support";
 
-  private static final String SEE_DOCUMENTATION_MSG =
-      "\n\nSee documentation for more info: %s";
+  private static final String SEE_DOCUMENTATION_MSG = "\n\nSee documentation for more info: %s";
 
   private static final String MUST_BE_OWNER_TO_CREATE_PROJECT_MSG =
       "You must be owner of the parent project \"%s\" to create a nested project."
           + SEE_DOCUMENTATION_MSG;
 
+  private static final String PROJECT_CANNOT_CONTAINS_SPACES_MSG =
+      "Project name cannot contains spaces." + SEE_DOCUMENTATION_MSG;
+
   private static final String ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG =
       "Root project name cannot contains slashes." + SEE_DOCUMENTATION_MSG;
 
@@ -63,34 +66,48 @@
           + "Please create a root parent project (project with option "
           + "\"Only serve as parent for other projects\") that will hold "
           + "all your access rights and then create your regular project that "
-          + "inherits rights from your root project.\n\n" + "Example:\n"
+          + "inherits rights from your root project.\n\n"
+          + "Example:\n"
           + "\"someOrganization\"->parent project\n"
           + "\"someOrganization/someProject\"->regular project."
           + SEE_DOCUMENTATION_MSG;
 
   private static final String PROJECT_MUST_START_WITH_PARENT_NAME_MSG =
-      "Project name must start with parent project name, e.g. %s."
-          + SEE_DOCUMENTATION_MSG;
+      "Project name must start with parent project name, e.g. %s." + SEE_DOCUMENTATION_MSG;
+
+  static final String DELEGATE_PROJECT_CREATION_TO = "delegateProjectCreationTo";
+
+  static final String DISABLE_GRANTING_PROJECT_OWNERSHIP = "disableGrantingProjectOwnership";
 
   private final CreateGroup.Factory createGroupFactory;
   private final String documentationUrl;
   private final AllProjectsNameProvider allProjectsName;
+  private final PluginConfigFactory cfg;
+  private final String pluginName;
 
   @Inject
-  public ProjectCreationValidator(CreateGroup.Factory createGroupFactory,
+  public ProjectCreationValidator(
+      CreateGroup.Factory createGroupFactory,
       @PluginCanonicalWebUrl String url,
-      AllProjectsNameProvider allProjectsName) {
+      AllProjectsNameProvider allProjectsName,
+      PluginConfigFactory cfg,
+      @PluginName String pluginName) {
     this.createGroupFactory = createGroupFactory;
     this.documentationUrl = url + "Documentation/index.html";
     this.allProjectsName = allProjectsName;
-
+    this.cfg = cfg;
+    this.pluginName = pluginName;
   }
 
   @Override
-  public void validateNewProject(CreateProjectArgs args)
-      throws ValidationException {
+  public void validateNewProject(CreateProjectArgs args) throws ValidationException {
     String name = args.getProjectName();
     log.debug("validating creation of {}", name);
+    if (name.contains(" ")) {
+      throw new ValidationException(
+          String.format(PROJECT_CANNOT_CONTAINS_SPACES_MSG, documentationUrl));
+    }
+
     ProjectControl parentCtrl = args.newParent;
     if (parentCtrl.getUser().getCapabilities().canAdministrateServer()) {
       // Admins can bypass any rules to support creating projects that doesn't
@@ -103,74 +120,110 @@
 
     if (allProjectsName.get().equals(parentCtrl.getProject().getNameKey())) {
       validateRootProject(name, args.permissionsOnly);
-      args.ownerIds.add(createGroup(name + "-admins"));
-      return;
+    } else {
+      validateProject(name, parentCtrl);
     }
 
-    validateProject(name, parentCtrl);
+    // If we reached that point, it means we allow project creation. Make the
+    // user an owner if not already by inheritance.
+    if (!parentCtrl.isOwner() && !configDisableGrantingOwnership(parentCtrl)) {
+      args.ownerIds.add(createGroup(name + "-admins"));
+    }
   }
 
-  private AccountGroup.UUID createGroup(String name)
+  private boolean configDisableGrantingOwnership(ProjectControl parentCtrl)
       throws ValidationException {
     try {
+      return cfg.getFromProjectConfigWithInheritance(
+              parentCtrl.getProject().getNameKey(), pluginName)
+          .getBoolean(DISABLE_GRANTING_PROJECT_OWNERSHIP, false);
+    } catch (NoSuchProjectException e) {
+      log.error(
+          "Failed to check project config for "
+              + parentCtrl.getProject().getName()
+              + ": "
+              + e.getMessage(),
+          e);
+      throw new ValidationException(AN_ERROR_OCCURRED_MSG);
+    }
+  }
+
+  private AccountGroup.UUID createGroup(String name) throws ValidationException {
+    try {
       GroupInfo groupInfo = null;
       try {
-        groupInfo = createGroupFactory.create(name)
-            .apply(TopLevelResource.INSTANCE, new GroupInput());
+        groupInfo =
+            createGroupFactory.create(name).apply(TopLevelResource.INSTANCE, new GroupInput());
       } catch (ResourceConflictException e) {
         // name already exists, make sure it is unique by adding a abbreviated
         // sha1
-        String nameWithSha1 = name + "-" + Hashing.sha1()
-            .hashString(name, Charsets.UTF_8).toString().substring(0, 7);
+        String nameWithSha1 =
+            name
+                + "-"
+                + Hashing.sha256().hashString(name, Charsets.UTF_8).toString().substring(0, 7);
         log.info(
             "Failed to create group name {} because of a conflict: {}, trying to create {} instead",
-            name, e.getMessage(), nameWithSha1);
-        groupInfo = createGroupFactory.create(nameWithSha1)
-            .apply(TopLevelResource.INSTANCE, new GroupInput());
+            name,
+            e.getMessage(),
+            nameWithSha1);
+        groupInfo =
+            createGroupFactory
+                .create(nameWithSha1)
+                .apply(TopLevelResource.INSTANCE, new GroupInput());
       }
       return AccountGroup.UUID.parse(groupInfo.id);
-    } catch (RestApiException | OrmException | IOException e ) {
+    } catch (RestApiException | OrmException | IOException e) {
       log.error("Failed to create project " + name + ": " + e.getMessage(), e);
       throw new ValidationException(AN_ERROR_OCCURRED_MSG);
     }
   }
 
-  private void validateRootProject(String name, boolean permissionOnly)
-      throws ValidationException {
+  private void validateRootProject(String name, boolean permissionOnly) throws ValidationException {
     log.debug("validating root project name {}", name);
     if (name.contains("/")) {
       log.debug("rejecting creation of {}: name contains slashes", name);
-      throw new ValidationException(String
-          .format(ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG, documentationUrl));
+      throw new ValidationException(
+          String.format(ROOT_PROJECT_CANNOT_CONTAINS_SLASHES_MSG, documentationUrl));
     }
     if (!permissionOnly) {
-      log.debug("rejecting creation of {}: missing permissions only option",
-          name);
-      throw new ValidationException(String
-          .format(REGULAR_PROJECT_NOT_ALLOWED_AS_ROOT_MSG, documentationUrl));
+      log.debug("rejecting creation of {}: missing permissions only option", name);
+      throw new ValidationException(
+          String.format(REGULAR_PROJECT_NOT_ALLOWED_AS_ROOT_MSG, documentationUrl));
     }
     log.debug("allowing creation of root project {}", name);
   }
 
-  private void validateProject(String name, ProjectControl parentCtrl)
-      throws ValidationException {
+  private void validateProject(String name, ProjectControl parentCtrl) throws ValidationException {
     log.debug("validating name prefix of {}", name);
     Project parent = parentCtrl.getProject();
     String prefix = parent.getName() + "/";
     if (!name.startsWith(prefix)) {
-      log.debug("rejecting creation of {}: name is not starting with {}", name,
-          prefix);
+      log.debug("rejecting creation of {}: name is not starting with {}", name, prefix);
       throw new ValidationException(
-          String.format(PROJECT_MUST_START_WITH_PARENT_NAME_MSG, prefix + name,
-              documentationUrl));
+          String.format(PROJECT_MUST_START_WITH_PARENT_NAME_MSG, prefix + name, documentationUrl));
     }
-    if (!parentCtrl.isOwner()) {
-      log.debug("rejecting creation of {}: user is not owner of {}", name,
-          parent.getName());
+    if (!parentCtrl.isOwner() && !isInDelegatingGroup(parentCtrl)) {
+      log.debug("rejecting creation of {}: user is not owner of {}", name, parent.getName());
       throw new ValidationException(
-          String.format(MUST_BE_OWNER_TO_CREATE_PROJECT_MSG, parent.getName(),
-              documentationUrl));
+          String.format(MUST_BE_OWNER_TO_CREATE_PROJECT_MSG, parent.getName(), documentationUrl));
     }
     log.debug("allowing creation of project {}", name);
   }
+
+  private boolean isInDelegatingGroup(ProjectControl parentCtrl) {
+    try {
+      GroupReference delegateProjectCreationTo =
+          cfg.getFromProjectConfigWithInheritance(parentCtrl.getProject().getNameKey(), pluginName)
+              .getGroupReference(DELEGATE_PROJECT_CREATION_TO);
+      if (delegateProjectCreationTo == null) {
+        return false;
+      }
+      log.debug("delegateProjectCreationTo: {}", delegateProjectCreationTo);
+      GroupMembership effectiveGroups = parentCtrl.getUser().getEffectiveGroups();
+      return effectiveGroups.contains(delegateProjectCreationTo.getUUID());
+    } catch (NoSuchProjectException e) {
+      log.error("isInDelegatingGroup with error ({}): {}", e.getClass().getName(), e.getMessage());
+      return false;
+    }
+  }
 }
diff --git a/src/main/resources/Documentation/about.md b/src/main/resources/Documentation/about.md
index 36998dd..216ccc2 100644
--- a/src/main/resources/Documentation/about.md
+++ b/src/main/resources/Documentation/about.md
@@ -1,5 +1,7 @@
 This plugin enforce a project group structure and restrict project creation
-within this structure to project group owners only.
+within this structure to project group owners only. Besides the rules the plugin
+enforce for the project group structure, it also enforce other naming
+rules like project name cannot contain spaces.
 
 To start creating a project group structure, simply create a root project, i.e.
 a project which inherits rights from `All-Projects`. The root project name
@@ -15,3 +17,79 @@
 root project and the project names must start with root project name, e.g.
 `some-organization/some-project`.
 
+Delegating group
+----------------
+Project creation can also be delegated to non-owner users by configuring
+`delegateProjectCreationTo` in the `project.config` of
+`refs/meta/config` branch of the parent project.
+
+The value of `delegateProjectCreationTo` must be set to a
+[group reference](@URL@Documentation/dev-plugins.html#configuring-groups).
+
+`project.config` file
+
+```
+[plugin "@PLUGIN@"]
+delegateProjectCreationTo = group group_name
+```
+
+`groups` file
+
+```
+group_uuid		group_name
+```
+
+The UUID of a group can be found on the "General" tab of the group's page.
+
+If creating-project is delegated to built-in groups, e.g. "Registered Users"
+group, then the value is as following:
+
+`project.config` file
+
+```
+[plugin "@PLUGIN@"]
+delegateProjectCreationTo = group Registered Users
+```
+
+`groups` file
+
+```
+global:Registered-Users		Registered Users
+```
+
+A way to edit `project.config` and `groups` file is from Gerrit UI.
+For example, to delegate project creation under `orgA` root project to
+`orgA-project-creators` group:
+
+- From main menu, click `People` -> `List Groups`
+- Type `orgA-project-creators` as the filter then click on
+`orgA-project-creators` group
+- Copy the group UUID (example: 3d2bef3b667a577f2dd5232e0848c526efd11b1f)
+- From main menu, click `Projects` -> `List`
+- Type `orgA` as the filter then click on `orgA` project
+- Click `Edit Config` button
+- Add the following then click `Save` -> `Close`:
+
+	```
+	[plugin "@PLUGIN@"]
+	delegateProjectCreationTo = orgA-project-creators
+	```
+- Click `Add...` button then type and open `groups` file
+- Add the following then click `Save` -> `Close`:
+
+	```
+	3d2bef3b667a577f2dd5232e0848c526efd11b1f	orgA-project-creators
+	```
+- Click `Publish` button, review, vote and submit the change to apply new
+configuration
+
+Ownership of a project created by delegated user is given automatically to that
+user by adding him to a group named `<root-project-name>-admins`. It is
+possible to disable granting the ownership by configuring
+`disableGrantingProjectOwnership` in the `project.config` of
+`refs/meta/config` branch of the parent project:
+
+```
+[plugin "@PLUGIN@"]
+disableGrantingProjectOwnership = true
+```
diff --git a/src/main/resources/Documentation/build.md b/src/main/resources/Documentation/build.md
index bedf322..630c6fb 100644
--- a/src/main/resources/Documentation/build.md
+++ b/src/main/resources/Documentation/build.md
@@ -1,72 +1,92 @@
-Build
-=====
+# Build
 
-This plugin is built with Buck.
+This plugin can be built with Bazel, and two build modes are supported:
 
-Buck
-----
+* Standalone
+* In Gerrit tree
 
-Two build modes are supported: Standalone and in Gerrit tree.
-The standalone build mode is recommended, as this mode doesn't require
-the Gerrit tree to exist locally.
+Standalone build mode is recommended, as this mode doesn't require local Gerrit
+tree to exist. Moreover, there are some limitations and additional manual steps
+required when building in Gerrit tree mode (see corresponding sections).
 
-Build standalone
-----------------
-
-Clone bucklets library:
-
-```
-  git clone https://gerrit.googlesource.com/bucklets
-
-```
-and link it to @PLUGIN@ plugin directory:
-
-```
-  cd @PLUGIN@ && ln -s ../bucklets .
-```
-
-Add link to the .buckversion file:
-
-```
-  cd @PLUGIN@ && ln -s bucklets/buckversion .buckversion
-```
-
-Add link to the .watchmanconfig file:
-```
-  cd @PLUGIN@ && ln -s bucklets/watchmanconfig .watchmanconfig
-```
+## Build standalone
 
 To build the plugin, issue the following command:
 
-
 ```
-  buck build plugin
+  bazel build @PLUGIN@
 ```
 
 The output is created in
 
 ```
-  buck-out/gen/@PLUGIN@.jar
+  bazel-genfiles/@PLUGIN@.jar
 ```
 
-Build in Gerrit tree
---------------------
-
-Clone or link this plugin to the plugins directory of Gerrit's source
-tree, and issue the command:
+To package the plugin sources run:
 
 ```
-  buck build plugins/@PLUGIN@
+  bazel build lib@PLUGIN@__plugin-src.jar
 ```
 
-The output is created in
+The output is created in:
 
 ```
-  buck-out/gen/plugins/@PLUGIN@/@PLUGIN@.jar
+  bazel-bin/lib@PLUGIN@__plugin-src.jar
+```
+
+To execute the tests run:
+
+```
+  bazel test //...
 ```
 
 This project can be imported into the Eclipse IDE:
 
 ```
+  ./tools/eclipse/project.sh
+```
+
+## Build in Gerrit tree
+
+Clone or link this plugin to the plugins directory of Gerrit's
+source tree. From Gerrit source tree issue the command:
+
+```
+  bazel build plugins/@PLUGIN@
+```
+
+Note that due to a [known issue in Bazel][bazelissue], if the plugin
+has previously been built in standalone mode, it is necessary to clean
+the workspace before building in-tree:
+
+```
+  cd plugins/@PLUGIN@
+  bazel clean --expunge
+```
+
+The output is created in
+
+```
+  bazel-genfiles/plugins/@PLUGIN@/@PLUGIN@.jar
+```
+
+This project can be imported into the Eclipse IDE:
+Add the plugin name to the `CUSTOM_PLUGINS` in `tools/bzl/plugins.bzl`, and
+execute:
+
+```
   ./tools/eclipse/project.py
 ```
+
+To execute the tests run:
+
+```
+  bazel test --test_tag_filters=@PLUGIN@ //...
+```
+
+
+[Back to @PLUGIN@ documentation index][index]
+
+[index]: index.html
+[bazelissue]: https://github.com/bazelbuild/bazel/issues/2797
diff --git a/src/main/resources/Documentation/config.md b/src/main/resources/Documentation/config.md
index a444519..ac9a12e 100644
--- a/src/main/resources/Documentation/config.md
+++ b/src/main/resources/Documentation/config.md
@@ -1,2 +1,26 @@
-The only configuration required is to grant "Create-group" and "Create-project"
-global permissions to "Registered Users" group.
\ No newline at end of file
+# Config
+The only configuration required is to grant `Create-group` and `Create-project`
+global permissions to `Registered Users` group.
+
+## Default Access Rights
+
+This plugin can set default access rights for newly created root projects if configured.
+
+Default access rights are read from `<review_site>/data/@PLUGIN@/project.config`.
+The format of that file is the same as regular project.config except that group, in addition to
+be a group name, can be set to token `${owner}` instead which will be replaced by the group owning
+the project.
+
+Example of default access rights config file:
+
+```
+[access "refs/*"]
+  read = group ${owner}
+  read = group existing group
+[access "refs/heads/*"]
+  create = group ${owner}
+  push = group ${owner}
+  label-Code-Review = -2..+2 group ${owner}
+  submit = group ${owner}
+
+```
\ No newline at end of file
diff --git a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRightsIT.java b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRightsIT.java
new file mode 100644
index 0000000..2746ce7
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/DefaultAccessRightsIT.java
@@ -0,0 +1,137 @@
+// Copyright (C) 2018 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.ericsson.gerrit.plugins.projectgroupstructure;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.TestPlugin;
+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.projects.ProjectInput;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectState;
+import java.nio.file.Files;
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+  name = "project-group-structure",
+  sysModule = "com.ericsson.gerrit.plugins.projectgroupstructure.Module"
+)
+public class DefaultAccessRightsIT extends LightweightPluginDaemonTest {
+
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    String defaultAccessRights =
+        "[access \"refs/*\"]\n"
+            + "  read = group ${owner}\n"
+            + "  read = group unexiting group\n"
+            + "  push = group Administrators\n"
+            + "  exclusiveGroupPermissions = read andInvalidOne\n"
+            + "[access \"refs/heads/*\"]\n"
+            + "  create = group ${owner}\n"
+            + "  push = group ${owner}\n"
+            + "  label-Code-Review = -2..+2 group ${owner}\n"
+            + "  submit = group ${owner}\n"
+            + "  invalidPermission = group ${owner}\n"
+            + "  editTopicName = group\n"
+            + "[access \"/invalidrefs\"]\n"
+            + "[access \"refs/invalidregex/${username(((((\"]\n";
+    Files.write(
+        tempDataDir.newFile(ProjectConfig.PROJECT_CONFIG).toPath(), defaultAccessRights.getBytes());
+    super.setUp();
+    // These access rights are mandatory configuration for this plugin as
+    // documented in config.md
+    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_GROUP);
+    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+  }
+
+  @Test
+  public void shouldConfigureForRootProject() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    String projectName = name("someProject");
+    userRestSession.put("/projects/" + projectName, in).assertCreated();
+
+    ProjectState projectState = projectCache.get(new Project.NameKey(projectName));
+    AccountGroup.UUID ownerUUID = projectState.getOwners().iterator().next();
+    ProjectConfig projectConfig = projectState.getConfig();
+
+    assertThat(projectConfig.getAccessSections().size()).isEqualTo(2);
+
+    AccessSection refsSection = projectConfig.getAccessSection("refs/*");
+    assertThat(refsSection.getPermissions().size()).isEqualTo(3);
+    assertThat(refsSection.getPermission(Permission.OWNER).getRules().get(0).getGroup().getUUID())
+        .isEqualTo(ownerUUID);
+    assertThat(refsSection.getPermission(Permission.READ).getRules().get(0).getGroup().getUUID())
+        .isEqualTo(ownerUUID);
+    assertThat(refsSection.getPermission(Permission.READ).getExclusiveGroup()).isTrue();
+    assertThat(refsSection.getPermission(Permission.PUSH).getRules().get(0).getGroup().getName())
+        .isEqualTo("Administrators");
+
+    AccessSection refsHeadsSection = projectConfig.getAccessSection("refs/heads/*");
+    assertThat(refsHeadsSection.getPermissions().size()).isEqualTo(4);
+    assertThat(
+            refsHeadsSection
+                .getPermission(Permission.CREATE)
+                .getRules()
+                .get(0)
+                .getGroup()
+                .getUUID())
+        .isEqualTo(ownerUUID);
+    assertThat(
+            refsHeadsSection.getPermission(Permission.PUSH).getRules().get(0).getGroup().getUUID())
+        .isEqualTo(ownerUUID);
+    assertThat(
+            refsHeadsSection
+                .getPermission(Permission.LABEL + "Code-Review")
+                .getRules()
+                .get(0)
+                .getGroup()
+                .getUUID())
+        .isEqualTo(ownerUUID);
+    assertThat(
+            refsHeadsSection
+                .getPermission(Permission.SUBMIT)
+                .getRules()
+                .get(0)
+                .getGroup()
+                .getUUID())
+        .isEqualTo(ownerUUID);
+  }
+
+  @Test
+  public void shoudNotConfigureForNonRootProject() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = false;
+    String projectName = name("some/project");
+    adminRestSession.put("/projects/" + Url.encode(projectName), in).assertCreated();
+
+    assertThat(
+            projectCache
+                .get(new Project.NameKey(projectName))
+                .getConfig()
+                .getAccessSections()
+                .size())
+        .isEqualTo(0);
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
new file mode 100644
index 0000000..92d8820
--- /dev/null
+++ b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorIT.java
@@ -0,0 +1,473 @@
+// 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.ericsson.gerrit.plugins.projectgroupstructure;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
+
+import com.google.common.base.Charsets;
+import com.google.common.collect.Lists;
+import com.google.common.hash.Hashing;
+import com.google.gerrit.acceptance.LightweightPluginDaemonTest;
+import com.google.gerrit.acceptance.RestResponse;
+import com.google.gerrit.acceptance.TestPlugin;
+import com.google.gerrit.common.data.GlobalCapability;
+import com.google.gerrit.common.data.GroupReference;
+import com.google.gerrit.extensions.api.groups.GroupApi;
+import com.google.gerrit.extensions.api.projects.ProjectInput;
+import com.google.gerrit.extensions.restapi.Url;
+import com.google.gerrit.reviewdb.client.AccountGroup;
+import com.google.gerrit.reviewdb.client.Project;
+import com.google.gerrit.server.git.ProjectConfig;
+import com.google.gerrit.server.project.ProjectState;
+import org.junit.Before;
+import org.junit.Test;
+
+@TestPlugin(
+  name = "project-group-structure",
+  sysModule = "com.ericsson.gerrit.plugins.projectgroupstructure.Module"
+)
+public class ProjectCreationValidatorIT extends LightweightPluginDaemonTest {
+
+  private static final String PLUGIN_NAME = "project-group-structure";
+
+  @Override
+  @Before
+  public void setUp() throws Exception {
+    super.setUp();
+    // These access rights are mandatory configuration for this plugin as
+    // documented in config.md
+    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_GROUP);
+    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
+  }
+
+  @Test
+  public void shouldProjectWithASpaceInTheirName() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode("project with space"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Project name cannot contains spaces");
+  }
+
+  @Test
+  public void shouldAllowAnyUsersToCreateUnderAllProjects() throws Exception {
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    adminRestSession.put("/projects/" + name("someProject"), in).assertCreated();
+
+    userRestSession.put("/projects/" + name("someOtherProject"), in).assertCreated();
+  }
+
+  @Test
+  public void shouldBlockRootProjectWithSlashesInTheirName() throws Exception {
+    // Root project is OK without slash
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    userRestSession.put("/projects/" + parent, in).assertCreated();
+
+    // Creation is rejected when root project name contains slashes
+    RestResponse r = userRestSession.put("/projects/" + Url.encode("a/parentProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Root project name cannot contains slashes");
+  }
+
+  @Test
+  public void shouldBlockProjectWithParentNotPartOfProjectName() throws Exception {
+    // Root project is OK without parent part of the name
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    String parent = name("parentProject");
+    userRestSession.put("/projects/" + parent, in).assertCreated();
+
+    // Creation is rejected when project name does not start with parent
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/childProject", in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Project name must start with parent project name");
+
+    // Creation is OK when project name starts with parent
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
+
+    // Creation is rejected when project name does not start with nested parent
+    String nestedParent = parent + "/childProject";
+    in.parent = nestedParent;
+    r = userRestSession.put("/projects/grandchild", in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Project name must start with parent project name");
+
+    // Creation is OK when project name starts with nested parent
+    userRestSession
+        .put("/projects/" + Url.encode(nestedParent + "/grandchild"), in)
+        .assertCreated();
+  }
+
+  @Test
+  public void shouldBlockCreationToNonOwnersOfParentProject() throws Exception {
+    String ownerGroup = name("groupA");
+    GroupApi g = gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    // Creation is rejected when user is not owner of parent
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // Creation is OK when user is owner of parent
+    g.addMembers(user.username);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
+  }
+
+  @Test
+  public void shouldAllowAdminsToByPassAnyRule() throws Exception {
+    // Root project with a slash
+    String parent = name("orgA/parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    adminRestSession.put("/projects/" + Url.encode(parent), in).assertCreated();
+
+    // Child project without name of parent as prefix
+    in = new ProjectInput();
+    in.parent = parent;
+    adminRestSession.put("/projects/" + Url.encode("orgA/childProject"), in).assertCreated();
+  }
+
+  @Test
+  public void shouldMakeUserOwnerOnRootProjectCreation() throws Exception {
+    // normal case, when <project-name>-admins group does not exist
+    String rootProject = name("rootProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    userRestSession.put("/projects/" + rootProject, in).assertCreated();
+    ProjectState projectState = projectCache.get(new Project.NameKey(rootProject));
+    assertThat(projectState.getOwners().size()).isEqualTo(1);
+    assertThat(projectState.getOwners())
+        .contains(groupCache.get(new AccountGroup.NameKey(rootProject + "-admins")).getGroupUUID());
+
+    // case when <project-name>-admins group already exists
+    rootProject = name("rootProject2");
+    String existingGroupName = rootProject + "-admins";
+    gApi.groups().create(existingGroupName);
+    userRestSession.put("/projects/" + rootProject, in).assertCreated();
+    projectState = projectCache.get(new Project.NameKey(rootProject));
+    assertThat(projectState.getOwners().size()).isEqualTo(1);
+    String expectedOwnerGroup =
+        existingGroupName
+            + "-"
+            + Hashing.sha256()
+                .hashString(existingGroupName, Charsets.UTF_8)
+                .toString()
+                .substring(0, 7);
+    assertThat(projectState.getOwners())
+        .contains(groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).getGroupUUID());
+  }
+
+  @Test
+  public void shouldBlockRootCodeProject() throws Exception {
+    RestResponse r = userRestSession.put("/projects/" + Url.encode("project1"));
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("Regular projects are not allowed as root");
+  }
+
+  @Test
+  public void shouldAllowCreationIfUserIsInDelegatingGroup() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // the user is in the delegating group
+    String delegatingGroup = name("groupB");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+    dGroup.addMembers(user.username);
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
+  }
+
+  @Test
+  public void shouldMakeUserOwnerIfNotAlreadyOwnerByInheritance() throws Exception {
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    String delegatingGroup = name("someGroup");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+    dGroup.addMembers(user.username);
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+
+    // normal case, when <project-name>-admins group does not exist
+    in = new ProjectInput();
+    in.parent = parent;
+    String childProject = parent + "/childProject";
+    userRestSession.put("/projects/" + Url.encode(childProject), in).assertCreated();
+    ProjectState projectState = projectCache.get(new Project.NameKey(childProject));
+    assertThat(projectState.getOwners().size()).isEqualTo(1);
+    assertThat(projectState.getOwners())
+        .contains(
+            groupCache.get(new AccountGroup.NameKey(childProject + "-admins")).getGroupUUID());
+
+    // case when <project-name>-admins group already exists
+    String childProject2 = parent + "/childProject2";
+    String existingGroupName = childProject2 + "-admins";
+    gApi.groups().create(existingGroupName);
+    userRestSession.put("/projects/" + Url.encode(childProject2), in).assertCreated();
+    projectState = projectCache.get(new Project.NameKey(childProject2));
+    assertThat(projectState.getOwners().size()).isEqualTo(1);
+    String expectedOwnerGroup =
+        existingGroupName
+            + "-"
+            + Hashing.sha256()
+                .hashString(existingGroupName, Charsets.UTF_8)
+                .toString()
+                .substring(0, 7);
+    assertThat(projectState.getOwners())
+        .contains(groupCache.get(new AccountGroup.NameKey(expectedOwnerGroup)).getGroupUUID());
+  }
+
+  @Test
+  public void shouldNotMakeUserOwnerIfNotAlreadyOwnerByInheritanceAndGrantingIsDisabled()
+      throws Exception {
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    String delegatingGroup = name("someGroup");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+    dGroup.addMembers(user.username);
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setBoolean(ProjectCreationValidator.DISABLE_GRANTING_PROJECT_OWNERSHIP, true);
+    saveProjectConfig(parentNameKey, cfg);
+
+    in = new ProjectInput();
+    in.parent = parent;
+    String childProject = parent + "/childProject";
+    userRestSession.put("/projects/" + Url.encode(childProject), in).assertCreated();
+    ProjectState projectState = projectCache.get(new Project.NameKey(childProject));
+    assertThat(projectState.getOwners().size()).isEqualTo(0);
+  }
+
+  @Test
+  public void shouldBlockCreationIfGroupRefIsNotUsed() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // the user is in the delegating group
+    String delegatingGroup = name("groupB");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+    dGroup.addMembers(user.username);
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setString(ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO, delegatingGroup);
+    saveProjectConfig(parentNameKey, cfg);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
+  }
+
+  @Test
+  public void shouldAllowCreationIfUserIsInDelegatingGroupNested() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // the user is in the nested delegating group
+    String delegatingGroup = name("groupB");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+
+    String nestedGroup = name("groupC");
+    GroupApi nGroup = gApi.groups().create(nestedGroup);
+    nGroup.addMembers(user.username);
+
+    dGroup.addGroups(nestedGroup);
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
+  }
+
+  @Test
+  public void shouldBlockCreationIfUserIsNotInDelegatingGroup() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // the user is in the delegating group
+    String delegatingGroup = name("groupB");
+    gApi.groups().create(delegatingGroup);
+    // The user is not added to the delegated group
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
+  }
+
+  @Test
+  public void shouldBlockCreationIfDelegatingGroupDoesNotExist() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // The delegating group is not created
+    String delegatingGroup = name("groupB");
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+    String gId = "fake-gId";
+    cfg.getPluginConfig(PLUGIN_NAME)
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertConflict();
+  }
+
+  @Test
+  public void shouldNotBlockCreationIfDelegatingGroupIsRenamed() throws Exception {
+    String ownerGroup = name("groupA");
+    gApi.groups().create(ownerGroup);
+
+    String parent = name("parentProject");
+    ProjectInput in = new ProjectInput();
+    in.permissionsOnly = true;
+    in.owners = Lists.newArrayList(ownerGroup);
+    adminRestSession.put("/projects/" + parent, in).assertCreated();
+
+    in = new ProjectInput();
+    in.parent = parent;
+    RestResponse r = userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in);
+    r.assertConflict();
+    assertThat(r.getEntityContent()).contains("You must be owner of the parent project");
+
+    // the user is in the delegating group
+    String delegatingGroup = name("groupB");
+    GroupApi dGroup = gApi.groups().create(delegatingGroup);
+    dGroup.addMembers(user.username);
+    // the group is in the project.config
+    Project.NameKey parentNameKey = new Project.NameKey(parent);
+    ProjectConfig cfg = projectCache.checkedGet(parentNameKey).getConfig();
+
+    String gId = gApi.groups().id(delegatingGroup).get().id;
+    cfg.getPluginConfig("project-group-structure")
+        .setGroupReference(
+            ProjectCreationValidator.DELEGATE_PROJECT_CREATION_TO,
+            new GroupReference(AccountGroup.UUID.parse(gId), delegatingGroup));
+    saveProjectConfig(parentNameKey, cfg);
+
+    String newDelegatingGroup = name("groupC");
+    gApi.groups().id(delegatingGroup).name(newDelegatingGroup);
+
+    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in).assertCreated();
+  }
+}
diff --git a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorTest.java b/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorTest.java
deleted file mode 100644
index b61f1aa..0000000
--- a/src/test/java/com/ericsson/gerrit/plugins/projectgroupstructure/ProjectCreationValidatorTest.java
+++ /dev/null
@@ -1,185 +0,0 @@
-// 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.ericsson.gerrit.plugins.projectgroupstructure;
-
-import static com.google.common.truth.Truth.assertThat;
-import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
-
-import com.google.common.base.Charsets;
-import com.google.common.collect.Lists;
-import com.google.common.hash.Hashing;
-import com.google.gerrit.acceptance.PluginDaemonTest;
-import com.google.gerrit.acceptance.RestResponse;
-import com.google.gerrit.common.data.GlobalCapability;
-import com.google.gerrit.extensions.api.groups.GroupApi;
-import com.google.gerrit.extensions.api.projects.ProjectInput;
-import com.google.gerrit.extensions.restapi.Url;
-import com.google.gerrit.reviewdb.client.AccountGroup;
-import com.google.gerrit.reviewdb.client.Project;
-import com.google.gerrit.server.project.ProjectState;
-
-import org.junit.Before;
-import org.junit.Ignore;
-import org.junit.Test;
-
-@Ignore
-public class ProjectCreationValidatorTest extends PluginDaemonTest {
-
-  @Before
-  public void setUp() throws Exception {
-    // These access rights are mandatory configuration for this plugin as
-    // documented in config.md
-    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_GROUP);
-    allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.CREATE_PROJECT);
-  }
-
-  @Test
-  public void shouldAllowAnyUsersToCreateUnderAllProjects() throws Exception {
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    adminRestSession.put("/projects/" + name("someProject"), in)
-        .assertCreated();
-
-    userRestSession.put("/projects/" + name("someOtherProject"), in)
-        .assertCreated();
-  }
-
-  @Test
-  public void shouldBlockRootProjectWithSlashesInTheirName() throws Exception {
-    // Root project is OK without slash
-    String parent = name("parentProject");
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    userRestSession.put("/projects/" + parent, in).assertCreated();
-
-    // Creation is rejected when root project name contains slashes
-    RestResponse r =
-        userRestSession.put("/projects/" + Url.encode("a/parentProject"), in);
-    r.assertConflict();
-    assertThat(r.getEntityContent())
-        .contains("Root project name cannot contains slashes");
-  }
-
-  @Test
-  public void shouldBlockProjectWithParentNotPartOfProjectName()
-      throws Exception {
-    // Root project is OK without parent part of the name
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    String parent = name("parentProject");
-    userRestSession.put("/projects/" + parent, in).assertCreated();
-
-    // Creation is rejected when project name does not start with parent
-    in = new ProjectInput();
-    in.parent = parent;
-    RestResponse r = userRestSession.put("/projects/childProject", in);
-    r.assertConflict();
-    assertThat(r.getEntityContent())
-        .contains("Project name must start with parent project name");
-
-    // Creation is OK when project name starts with parent
-    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in)
-        .assertCreated();
-
-    // Creation is rejected when project name does not start with nested parent
-    String nestedParent = parent + "/childProject";
-    in.parent = nestedParent;
-    r = userRestSession.put("/projects/grandchild", in);
-    r.assertConflict();
-    assertThat(r.getEntityContent())
-        .contains("Project name must start with parent project name");
-
-    // Creation is OK when project name starts with nested parent
-    userRestSession
-        .put("/projects/" + Url.encode(nestedParent + "/grandchild"), in)
-        .assertCreated();
-  }
-
-  @Test
-  public void shouldBlockCreationToNonOwnersOfParentProject() throws Exception {
-    String ownerGroup = name("groupA");
-    GroupApi g = gApi.groups().create(ownerGroup);
-
-    String parent = name("parentProject");
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    in.owners = Lists.newArrayList(ownerGroup);
-    adminRestSession.put("/projects/" + parent, in).assertCreated();
-
-    // Creation is rejected when user is not owner of parent
-    in = new ProjectInput();
-    in.parent = parent;
-    RestResponse r = userRestSession
-        .put("/projects/" + Url.encode(parent + "/childProject"), in);
-    r.assertConflict();
-    assertThat(r.getEntityContent())
-        .contains("You must be owner of the parent project");
-
-    // Creation is OK when user is owner of parent
-    g.addMembers(user.username);
-    userRestSession.put("/projects/" + Url.encode(parent + "/childProject"), in)
-        .assertCreated();
-  }
-
-  @Test
-  public void shouldAllowAdminsToByPassAnyRule() throws Exception {
-    // Root project with a slash
-    String parent = name("orgA/parentProject");
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    adminRestSession.put("/projects/" + Url.encode(parent), in).assertCreated();
-
-    // Child project without name of parent as prefix
-    in = new ProjectInput();
-    in.parent = parent;
-    adminRestSession.put("/projects/" + Url.encode("orgA/childProject"), in)
-        .assertCreated();
-  }
-
-  @Test
-  public void shouldMakeUserOwnerOnRootProjectCreation() throws Exception {
-    // normal case, when <project-name>-admins group does not exist
-    String rootProject = name("rootProject");
-    ProjectInput in = new ProjectInput();
-    in.permissionsOnly = true;
-    userRestSession.put("/projects/" + rootProject, in).assertCreated();
-    ProjectState projectState =
-        projectCache.get(new Project.NameKey(rootProject));
-    assertThat(projectState.getOwners().size()).isEqualTo(1);
-    assertThat(projectState.getOwners()).contains(groupCache
-        .get(new AccountGroup.NameKey(rootProject + "-admins")).getGroupUUID());
-
-    // case when <project-name>-admins group already exists
-    rootProject = name("rootProject2");
-    String existingGroupName = rootProject + "-admins";
-    gApi.groups().create(existingGroupName);
-    userRestSession.put("/projects/" + rootProject, in).assertCreated();
-    projectState = projectCache.get(new Project.NameKey(rootProject));
-    assertThat(projectState.getOwners().size()).isEqualTo(1);
-    String expectedOwnerGroup = existingGroupName + "-"
-        + Hashing.sha1().hashString(existingGroupName, Charsets.UTF_8)
-            .toString().substring(0, 7);
-    assertThat(projectState.getOwners()).contains(groupCache
-        .get(new AccountGroup.NameKey(expectedOwnerGroup)).getGroupUUID());
-  }
-
-  @Test
-  public void shouldBlockRootCodeProject() throws Exception {
-    RestResponse r = userRestSession.put("/projects/" + Url.encode("project1"));
-    r.assertConflict();
-    assertThat(r.getEntityContent())
-        .contains("Regular projects are not allowed as root");
-  }
-}
diff --git a/tools/bazel.rc b/tools/bazel.rc
new file mode 100644
index 0000000..4ed16cf
--- /dev/null
+++ b/tools/bazel.rc
@@ -0,0 +1,2 @@
+build --workspace_status_command=./tools/workspace-status.sh
+test --build_tests_only
diff --git a/tools/bzl/BUILD b/tools/bzl/BUILD
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tools/bzl/BUILD
diff --git a/tools/bzl/classpath.bzl b/tools/bzl/classpath.bzl
new file mode 100644
index 0000000..d5764f7
--- /dev/null
+++ b/tools/bzl/classpath.bzl
@@ -0,0 +1,4 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:classpath.bzl",
+    "classpath_collector",
+)
diff --git a/tools/bzl/junit.bzl b/tools/bzl/junit.bzl
new file mode 100644
index 0000000..3af7e58
--- /dev/null
+++ b/tools/bzl/junit.bzl
@@ -0,0 +1,4 @@
+load(
+    "@com_googlesource_gerrit_bazlets//tools:junit.bzl",
+    "junit_tests",
+)
diff --git a/tools/bzl/plugin.bzl b/tools/bzl/plugin.bzl
new file mode 100644
index 0000000..a2e438f
--- /dev/null
+++ b/tools/bzl/plugin.bzl
@@ -0,0 +1,6 @@
+load(
+    "@com_googlesource_gerrit_bazlets//:gerrit_plugin.bzl",
+    "gerrit_plugin",
+    "PLUGIN_DEPS",
+    "PLUGIN_TEST_DEPS",
+)
diff --git a/tools/eclipse/BUILD b/tools/eclipse/BUILD
new file mode 100644
index 0000000..57b7b83
--- /dev/null
+++ b/tools/eclipse/BUILD
@@ -0,0 +1,9 @@
+load("//tools/bzl:classpath.bzl", "classpath_collector")
+
+classpath_collector(
+    name = "main_classpath_collect",
+    testonly = 1,
+    deps = [
+        "//:project-group-structure__plugin_test_deps",
+    ],
+)
diff --git a/tools/eclipse/project.sh b/tools/eclipse/project.sh
new file mode 100755
index 0000000..be0fd7a
--- /dev/null
+++ b/tools/eclipse/project.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Copyright (C) 2017 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.
+`bazel query @com_googlesource_gerrit_bazlets//tools/eclipse:project --output location | sed s/BUILD:.*//`project.py -n project-group-structure -r .
diff --git a/tools/sonar/sonar.sh b/tools/sonar/sonar.sh
new file mode 100755
index 0000000..39df185
--- /dev/null
+++ b/tools/sonar/sonar.sh
@@ -0,0 +1,15 @@
+#!/bin/bash
+# Copyright (C) 2018 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.
+`bazel query @com_googlesource_gerrit_bazlets//tools/sonar:sonar --output location | sed s/BUILD:.*//`sonar.py
diff --git a/tools/workspace-status.sh b/tools/workspace-status.sh
new file mode 100755
index 0000000..5320776
--- /dev/null
+++ b/tools/workspace-status.sh
@@ -0,0 +1,17 @@
+#!/bin/bash
+
+# This script will be run by bazel when the build process starts to
+# generate key-value information that represents the status of the
+# workspace. The output should be like
+#
+# KEY1 VALUE1
+# KEY2 VALUE2
+#
+# If the script exits with non-zero code, it's considered as a failure
+# and the output will be discarded.
+
+function rev() {
+  cd $1; git describe --always --match "v[0-9].*" --dirty
+}
+
+echo STABLE_BUILD_PROJECT-GROUP-STRUCTURE_LABEL $(rev .)