[GERRITHUB-4] Set Gerrit refs/meta/config on imported repos. Create initial Project properties (project.config and groups) on refs/meta/config whenever a GitHub project gets imported. In order to allow a non-Gerrit administrator to have full control over the imported project, assign extra owner and GitHub-like permissions to the individual user that performed the operation. P.S. In order to allow Gerrit to assign permissions to an individual user, a SingleUserGroup plugin is needed to be installed. Change-Id: I596b2e80b4d9519668a1ab289d6c950139d6a922
diff --git a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneController.java b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneController.java index 6272cb2..7987001 100644 --- a/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneController.java +++ b/github-plugin/src/main/java/com/googlesource/gerrit/plugins/github/wizard/RepositoriesCloneController.java
@@ -63,7 +63,8 @@ String repository = req.getParameter(REPO_PARAM_PREFIX + repoIdx + "_repository"); try { - gitCloner.clone(repoIdx, organisation, repository); + gitCloner.clone(repoIdx, organisation, repository, + getDescription(hubLogin, organisation, repository)); } catch (Exception e) { errorMgr.submit(e); } @@ -71,4 +72,14 @@ } } + private String getDescription(GitHubLogin hubLogin, String organisation, + String repository) throws IOException { + if (organisation.equals(hubLogin.getMyself().getLogin())) { + return hubLogin.getMyself().getRepository(repository).getDescription(); + } else { + return hubLogin.hub.getOrganization(organisation) + .getRepository(repository).getDescription(); + } + } + }
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitClone.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitClone.java index 7dcdf43..49f2aaa 100644 --- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitClone.java +++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitClone.java
@@ -15,13 +15,33 @@ import java.io.File; import java.io.IOException; +import java.util.Set; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.api.CloneCommand; +import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.lib.ProgressMonitor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gerrit.common.data.AccessSection; +import com.google.gerrit.common.data.GroupDescription; +import com.google.gerrit.common.data.GroupReference; +import com.google.gerrit.common.data.Permission; +import com.google.gerrit.common.data.PermissionRule; +import com.google.gerrit.reviewdb.client.AccountGroup; +import com.google.gerrit.reviewdb.client.AccountGroup.UUID; +import com.google.gerrit.reviewdb.client.Project; +import com.google.gerrit.reviewdb.client.Project.InheritableBoolean; +import com.google.gerrit.reviewdb.client.Project.NameKey; +import com.google.gerrit.reviewdb.client.Project.SubmitType; +import com.google.gerrit.server.IdentifiedUser; +import com.google.gerrit.server.account.GroupBackend; +import com.google.gerrit.server.git.MetaDataUpdate; +import com.google.gerrit.server.git.MetaDataUpdate.User; +import com.google.gerrit.server.git.ProjectConfig; +import com.google.gerrit.server.project.ProjectCache; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; @@ -29,30 +49,54 @@ private static final String GITHUB_REPOSITORY_FORMAT = "https://github.com/%1$s/%2$s.git"; private static final Logger log = LoggerFactory.getLogger(GitCloner.class); + + private static final String CODE_REVIEW_REFS = "refs/for/refs/*"; + private static final String TAGS_REFS = "refs/tags/*"; + private static final String CODE_REVIEW_LABEL = "Code-Review"; + private static final String VERIFIED_LABEL = "Verified"; + private final String organisation; private final String repository; private final File gitDir; private String sourceUri; private File destinationDirectory; + private User metaDataUpdateFactory; + private String description; + private GroupBackend groupBackend; + private String username; + private ProjectConfig config; + private ProjectCache projectCache; public interface Factory { GitClone create(@Assisted("organisation") String organisation, - @Assisted("name") String repository); + @Assisted("name") String repository, + @Assisted("description") String description, + @Assisted("username") String username); } @Inject public GitClone(GitConfig gitConfig, + MetaDataUpdate.User metaDataUpdateFactory, + GroupBackend groupBackend, + ProjectCache projectCache, @Assisted("organisation") String organisation, - @Assisted("name") String repository) + @Assisted("name") String repository, + @Assisted("description") String description, + @Assisted("username") String username) throws GitDestinationAlreadyExistsException, GitDestinationNotWritableException { this.gitDir = gitConfig.gitDir; this.organisation = organisation; this.repository = repository; + this.description = description; this.sourceUri = getSourceUri(organisation, repository); this.destinationDirectory = getDestinationDirectory(organisation, repository); + this.metaDataUpdateFactory = metaDataUpdateFactory; + this.groupBackend = groupBackend; + this.projectCache = projectCache; + this.username = username; } public void doClone(ProgressMonitor progress) throws GitCloneFailedException, @@ -72,6 +116,99 @@ } } + public void configureProject(ProgressMonitor progress) + throws RepositoryNotFoundException, IOException, ConfigInvalidException { + MetaDataUpdate md = metaDataUpdateFactory.create(getProjectNameKey()); + try { + config = ProjectConfig.read(md); + progress.beginTask("Configure Gerrit project", 2); + setProjectSettings(); + progress.update(1); + setProjectPermissions(); + progress.update(1); + md.setMessage("Imported from " + sourceUri); + config.commit(md); + projectCache.onCreateProject(getProjectNameKey()); + } finally { + md.close(); + progress.endTask(); + } + } + + private void setProjectPermissions() { + addPermissions(AccessSection.ALL, Permission.OWNER); + + addPermissions(CODE_REVIEW_REFS, Permission.READ, Permission.PUSH, + Permission.REMOVE_REVIEWER, Permission.SUBMIT, Permission.REBASE); + + PermissionRule reviewRange = new PermissionRule(getMyGroup()); + reviewRange.setMin(-2); + reviewRange.setMax(+2); + addPermission(CODE_REVIEW_REFS, Permission.LABEL + CODE_REVIEW_LABEL, + reviewRange); + + PermissionRule verifiedRange = new PermissionRule(getMyGroup()); + verifiedRange.setMin(-1); + verifiedRange.setMax(+1); + addPermission(CODE_REVIEW_REFS, Permission.LABEL + VERIFIED_LABEL, + verifiedRange); + + addPermissions(AccessSection.HEADS, Permission.READ, Permission.CREATE, + Permission.PUSH_MERGE); + + PermissionRule forcePush = new PermissionRule(getMyGroup()); + forcePush.setForce(true); + addPermission(AccessSection.HEADS, Permission.PUSH, forcePush); + + addPermissions(TAGS_REFS, Permission.PUSH_TAG, Permission.PUSH_SIGNED_TAG); + + PermissionRule removeTag = new PermissionRule(getMyGroup()); + removeTag.setForce(true); + addPermission(TAGS_REFS, Permission.PUSH, removeTag); + } + + private void setProjectSettings() { + Project project = config.getProject(); + project.setDescription(description); + project.setSubmitType(SubmitType.MERGE_IF_NECESSARY); + project.setUseContributorAgreements(InheritableBoolean.INHERIT); + project.setUseSignedOffBy(InheritableBoolean.INHERIT); + project.setUseContentMerge(InheritableBoolean.INHERIT); + project.setRequireChangeID(InheritableBoolean.INHERIT); + } + + private void addPermissions(String refSpec, String... permissions) { + AccessSection accessSection = config.getAccessSection(refSpec, true); + for (String permission : permissions) { + String[] permParts = permission.split("="); + String action = permParts[0]; + PermissionRule rule; + if (permParts.length > 1) { + rule = PermissionRule.fromString(permParts[1], true); + rule.setGroup(getMyGroup()); + } else { + rule = new PermissionRule(getMyGroup()); + } + accessSection.getPermission(action, true).add(rule); + } + } + + private void addPermission(String refSpec, String action, PermissionRule rule) { + config.getAccessSection(refSpec, true).getPermission(action, true) + .add(rule); + } + + + private GroupReference getMyGroup() { + GroupDescription.Basic g = + groupBackend.get(AccountGroup.UUID.parse("user:" + username)); + return config.resolve(GroupReference.forGroup(g)); + } + + private NameKey getProjectNameKey() { + return Project.NameKey.parse(organisation + "/" + repository); + } + private File getDestinationDirectory(String organisation, String repository) throws GitDestinationAlreadyExistsException, GitDestinationNotWritableException {
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneJob.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneJob.java index 183203c..11d3e8a 100644 --- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneJob.java +++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloneJob.java
@@ -39,6 +39,7 @@ public void run() { try { cloneCommand.doClone(this); + cloneCommand.configureProject(this); status = GitCloneStatus.COMPLETE; } catch (Exception e) { if (status == GitCloneStatus.SYNC) {
diff --git a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloner.java b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloner.java index 685b693..d9ff5f9 100644 --- a/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloner.java +++ b/github-plugin/src/main/java/com/googlesrouce/gerrit/plugins/github/git/GitCloner.java
@@ -19,6 +19,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.gerrit.server.IdentifiedUser; import com.google.inject.Inject; import com.google.inject.servlet.SessionScoped; import com.googlesrouce.gerrit.plugins.github.git.GitClone.Factory; @@ -30,18 +31,20 @@ private final ConcurrentHashMap<Integer, CloneJob> cloneJobs = new ConcurrentHashMap<Integer, CloneJob>(); private final GitCommandsExecutor executor; + private IdentifiedUser user; @Inject - public GitCloner(GitClone.Factory cloneFactory, GitCommandsExecutor executor) { + public GitCloner(GitClone.Factory cloneFactory, GitCommandsExecutor executor, IdentifiedUser user) { this.cloneFactory = cloneFactory; this.executor = executor; + this.user = user; } - public void clone(int idx, String organisation, String repository) { + public void clone(int idx, String organisation, String repository, String description) { try { GitCloneJob gitCloneJob = - new GitCloneJob(idx, cloneFactory.create(organisation, repository)); + new GitCloneJob(idx, cloneFactory.create(organisation, repository, description, user.getUserName())); log.debug("New Git clone job created: " + gitCloneJob); executor.exec(gitCloneJob); cloneJobs.put(idx, gitCloneJob);
diff --git a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/wizard/test/GitClonerTest.java b/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/wizard/test/GitClonerTest.java deleted file mode 100644 index 54971aa..0000000 --- a/github-plugin/src/test/java/com/googlesource/gerrit/plugins/github/wizard/test/GitClonerTest.java +++ /dev/null
@@ -1,67 +0,0 @@ -package com.googlesource.gerrit.plugins.github.wizard.test; - -import static org.junit.Assert.assertTrue; - -import java.io.File; -import java.io.IOException; - -import org.apache.commons.io.FileUtils; -import org.jukito.JukitoRunner; -import org.jukito.TestModule; -import org.junit.Before; -import org.junit.Test; -import org.junit.runner.RunWith; - -import com.google.gerrit.extensions.annotations.PluginName; -import com.google.gerrit.server.config.SitePath; -import com.google.inject.Inject; -import com.googlesource.gerrit.plugins.github.GuiceModule; -import com.googlesrouce.gerrit.plugins.github.git.GitClone; -import com.googlesrouce.gerrit.plugins.github.git.GitCloneFailedException; -import com.googlesrouce.gerrit.plugins.github.git.GitConfig; -import com.googlesrouce.gerrit.plugins.github.git.GitDestinationAlreadyExistsException; -import com.googlesrouce.gerrit.plugins.github.git.GitDestinationNotWritableException; - -@RunWith(JukitoRunner.class) -public class GitClonerTest { - private static final String GERRIT_SITE_PATH = System.getProperty( - "gerrit.site", "/tmp/gerrit-site-test"); - private static final String TEST_ORGANISATION = "lucamilanesio"; - private static final String TEST_REPOSITORY = "33degree"; - public static final File GIT_BASE_PATH = new File(GERRIT_SITE_PATH, "git"); - - public static class GitClonerTestModule extends TestModule { - @Override - protected void configureTest() { - bind(GitConfig.class).toInstance(new GitConfig(GIT_BASE_PATH)); - bind(File.class).annotatedWith(SitePath.class).toInstance( - new File(GERRIT_SITE_PATH)); - bind(String.class).annotatedWith(PluginName.class).toInstance("TEST"); - install(new GuiceModule()); - } - } - - @Inject - private GitClone.Factory cloner; - - @Before - public void setUp() throws IOException { - if (GIT_BASE_PATH.exists()) { - FileUtils.deleteDirectory(GIT_BASE_PATH); - } - } - - @Test - public void testShouldCloneBareGitHubRepositoryCreateTargetDirectoryWithGitRepository() - throws GitDestinationAlreadyExistsException, GitCloneFailedException, - GitDestinationNotWritableException { - cloner.create(TEST_ORGANISATION, TEST_REPOSITORY).doClone(null); - - File destDirectory = - new File(new File(GIT_BASE_PATH, TEST_ORGANISATION), TEST_REPOSITORY - + ".git"); - assertTrue(destDirectory.exists()); - assertTrue(new File(destDirectory, "HEAD").exists()); - } - -}