Migrate contributor agreements to All-Projects.
Added a new schema to move the ContributorAgreement, AccountAgreement, and AccountGroupAgreement information into the All-Projects project.config. Updated VersionedMetaData to expose a way to do batch commits to the meta data. Change-Id: Ifb3fdd8c0d6a0c988941d6949f6501b8f8856412
This commit is contained in:
@@ -80,9 +80,14 @@ public final class AccountGroupMemberAudit {
|
|||||||
|
|
||||||
public AccountGroupMemberAudit(final AccountGroupMember m,
|
public AccountGroupMemberAudit(final AccountGroupMember m,
|
||||||
final Account.Id adder) {
|
final Account.Id adder) {
|
||||||
|
this(m, adder, now());
|
||||||
|
}
|
||||||
|
|
||||||
|
public AccountGroupMemberAudit(final AccountGroupMember m,
|
||||||
|
final Account.Id adder, Timestamp addedOn) {
|
||||||
final Account.Id who = m.getAccountId();
|
final Account.Id who = m.getAccountId();
|
||||||
final AccountGroup.Id group = m.getAccountGroupId();
|
final AccountGroup.Id group = m.getAccountGroupId();
|
||||||
key = new AccountGroupMemberAudit.Key(who, group, now());
|
key = new AccountGroupMemberAudit.Key(who, group, addedOn);
|
||||||
addedBy = adder;
|
addedBy = adder;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -177,9 +177,7 @@ public class ProjectConfig extends VersionedMetaData {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void replace(ContributorAgreement section) {
|
public void replace(ContributorAgreement section) {
|
||||||
if (section.getAutoVerify() != null) {
|
section.setAutoVerify(resolve(section.getAutoVerify()));
|
||||||
resolve(section.getAutoVerify());
|
|
||||||
}
|
|
||||||
for (PermissionRule rule : section.getAccepted()) {
|
for (PermissionRule rule : section.getAccepted()) {
|
||||||
rule.setGroup(resolve(rule.getGroup()));
|
rule.setGroup(resolve(rule.getGroup()));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
package com.google.gerrit.server.git;
|
package com.google.gerrit.server.git;
|
||||||
|
|
||||||
|
import com.google.common.base.Objects;
|
||||||
import org.eclipse.jgit.dircache.DirCache;
|
import org.eclipse.jgit.dircache.DirCache;
|
||||||
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
import org.eclipse.jgit.dircache.DirCacheBuilder;
|
||||||
import org.eclipse.jgit.dircache.DirCacheEditor;
|
import org.eclipse.jgit.dircache.DirCacheEditor;
|
||||||
@@ -23,7 +24,7 @@ import org.eclipse.jgit.dircache.DirCacheEntry;
|
|||||||
import org.eclipse.jgit.errors.ConfigInvalidException;
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
|
||||||
import org.eclipse.jgit.errors.MissingObjectException;
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
import org.eclipse.jgit.errors.UnmergedPathException;
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
import org.eclipse.jgit.lib.CommitBuilder;
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
@@ -150,33 +151,96 @@ public abstract class VersionedMetaData {
|
|||||||
* executed as requested.
|
* executed as requested.
|
||||||
*/
|
*/
|
||||||
public boolean commit(MetaDataUpdate update) throws IOException {
|
public boolean commit(MetaDataUpdate update) throws IOException {
|
||||||
|
BatchMetaDataUpdate batch = openUpdate(update);
|
||||||
|
try {
|
||||||
|
batch.write(update.getCommitBuilder());
|
||||||
|
return batch.commit();
|
||||||
|
} finally {
|
||||||
|
batch.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public interface BatchMetaDataUpdate {
|
||||||
|
void write(CommitBuilder commit) throws IOException;
|
||||||
|
void write(VersionedMetaData config, CommitBuilder commit) throws IOException;
|
||||||
|
boolean commit() throws IOException;
|
||||||
|
boolean commitAt(ObjectId revision) throws IOException;
|
||||||
|
void close();
|
||||||
|
}
|
||||||
|
|
||||||
|
public BatchMetaDataUpdate openUpdate(final MetaDataUpdate update) throws IOException {
|
||||||
final Repository db = update.getRepository();
|
final Repository db = update.getRepository();
|
||||||
final CommitBuilder commit = update.getCommitBuilder();
|
|
||||||
|
|
||||||
reader = db.newObjectReader();
|
reader = db.newObjectReader();
|
||||||
inserter = db.newObjectInserter();
|
inserter = db.newObjectInserter();
|
||||||
try {
|
|
||||||
final RevWalk rw = new RevWalk(reader);
|
final RevWalk rw = new RevWalk(reader);
|
||||||
final RevTree src = revision != null ? rw.parseTree(revision) : null;
|
final RevTree tree = revision != null ? rw.parseTree(revision) : null;
|
||||||
final ObjectId res = writeTree(src, commit);
|
newTree = readTree(tree);
|
||||||
|
return new BatchMetaDataUpdate() {
|
||||||
|
AnyObjectId src = revision;
|
||||||
|
AnyObjectId srcTree = tree;
|
||||||
|
|
||||||
if (res.equals(src)) {
|
@Override
|
||||||
|
public void write(CommitBuilder commit) throws IOException {
|
||||||
|
write(VersionedMetaData.this, commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doSave(VersionedMetaData config, CommitBuilder commit) throws IOException {
|
||||||
|
DirCache nt = config.newTree;
|
||||||
|
ObjectReader r = config.reader;
|
||||||
|
ObjectInserter i = config.inserter;
|
||||||
|
try {
|
||||||
|
config.newTree = newTree;
|
||||||
|
config.reader = reader;
|
||||||
|
config.inserter = inserter;
|
||||||
|
config.onSave(commit);
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new IOException("Cannot update " + getRefName() + " in "
|
||||||
|
+ db.getDirectory() + ": " + e.getMessage(), e);
|
||||||
|
} finally {
|
||||||
|
config.newTree = nt;
|
||||||
|
config.reader = r;
|
||||||
|
config.inserter = i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(VersionedMetaData config, CommitBuilder commit) throws IOException {
|
||||||
|
doSave(config, commit);
|
||||||
|
|
||||||
|
final ObjectId res = newTree.writeTree(inserter);
|
||||||
|
if (res.equals(srcTree)) {
|
||||||
// If there are no changes to the content, don't create the commit.
|
// If there are no changes to the content, don't create the commit.
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
commit.setTreeId(res);
|
commit.setTreeId(res);
|
||||||
if (revision != null) {
|
if (src != null) {
|
||||||
commit.setParentId(revision);
|
commit.addParentId(src);
|
||||||
|
}
|
||||||
|
|
||||||
|
src = inserter.insert(commit);
|
||||||
|
srcTree = res;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean commit() throws IOException {
|
||||||
|
return commitAt(revision);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean commitAt(ObjectId expected) throws IOException {
|
||||||
|
if (Objects.equal(src, expected)) {
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
RefUpdate ru = db.updateRef(getRefName());
|
RefUpdate ru = db.updateRef(getRefName());
|
||||||
if (revision != null) {
|
if (expected != null) {
|
||||||
ru.setExpectedOldObjectId(revision);
|
ru.setExpectedOldObjectId(expected);
|
||||||
} else {
|
} else {
|
||||||
ru.setExpectedOldObjectId(ObjectId.zeroId());
|
ru.setExpectedOldObjectId(ObjectId.zeroId());
|
||||||
}
|
}
|
||||||
ru.setNewObjectId(inserter.insert(commit));
|
ru.setNewObjectId(src);
|
||||||
ru.disableRefLog();
|
ru.disableRefLog();
|
||||||
inserter.flush();
|
inserter.flush();
|
||||||
|
|
||||||
@@ -194,28 +258,23 @@ public abstract class VersionedMetaData {
|
|||||||
throw new IOException("Cannot update " + ru.getName() + " in "
|
throw new IOException("Cannot update " + ru.getName() + " in "
|
||||||
+ db.getDirectory() + ": " + ru.getResult());
|
+ db.getDirectory() + ": " + ru.getResult());
|
||||||
}
|
}
|
||||||
} catch (ConfigInvalidException e) {
|
}
|
||||||
throw new IOException("Cannot update " + getRefName() + " in "
|
|
||||||
+ db.getDirectory() + ": " + e.getMessage(), e);
|
@Override
|
||||||
} finally {
|
public void close() {
|
||||||
|
newTree = null;
|
||||||
|
|
||||||
|
if (inserter != null) {
|
||||||
inserter.release();
|
inserter.release();
|
||||||
inserter = null;
|
inserter = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (reader != null) {
|
||||||
reader.release();
|
reader.release();
|
||||||
reader = null;
|
reader = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
private ObjectId writeTree(RevTree srcTree, CommitBuilder commit)
|
|
||||||
throws IOException, MissingObjectException, IncorrectObjectTypeException,
|
|
||||||
UnmergedPathException, ConfigInvalidException {
|
|
||||||
try {
|
|
||||||
newTree = readTree(srcTree);
|
|
||||||
onSave(commit);
|
|
||||||
return newTree.writeTree(inserter);
|
|
||||||
} finally {
|
|
||||||
newTree = null;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private DirCache readTree(RevTree tree) throws IOException,
|
private DirCache readTree(RevTree tree) throws IOException,
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import java.util.List;
|
|||||||
/** A version of the database schema. */
|
/** A version of the database schema. */
|
||||||
public abstract class SchemaVersion {
|
public abstract class SchemaVersion {
|
||||||
/** The current schema version. */
|
/** The current schema version. */
|
||||||
public static final Class<Schema_64> C = Schema_64.class;
|
public static final Class<Schema_65> C = Schema_65.class;
|
||||||
|
|
||||||
public static class Module extends AbstractModule {
|
public static class Module extends AbstractModule {
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,458 @@
|
|||||||
|
// Copyright (C) 2012 The Android Open Source Project
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
package com.google.gerrit.server.schema;
|
||||||
|
|
||||||
|
import com.google.common.base.Strings;
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
|
import com.google.gerrit.common.data.AccessSection;
|
||||||
|
import com.google.gerrit.common.data.ContributorAgreement;
|
||||||
|
import com.google.gerrit.common.data.GlobalCapability;
|
||||||
|
import com.google.gerrit.common.data.GroupReference;
|
||||||
|
import com.google.gerrit.common.data.PermissionRule;
|
||||||
|
import com.google.gerrit.common.data.PermissionRule.Action;
|
||||||
|
import com.google.gerrit.reviewdb.client.Account;
|
||||||
|
import com.google.gerrit.reviewdb.client.AccountGroup;
|
||||||
|
import com.google.gerrit.reviewdb.client.AccountGroupMember;
|
||||||
|
import com.google.gerrit.reviewdb.client.AccountGroupMemberAudit;
|
||||||
|
import com.google.gerrit.reviewdb.client.AccountGroupName;
|
||||||
|
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||||
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
|
import com.google.gerrit.server.account.GroupUUID;
|
||||||
|
import com.google.gerrit.server.config.AllProjectsName;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
|
import com.google.gerrit.server.git.MetaDataUpdate;
|
||||||
|
import com.google.gerrit.server.git.NoReplication;
|
||||||
|
import com.google.gerrit.server.git.ProjectConfig;
|
||||||
|
import com.google.gerrit.server.git.VersionedMetaData.BatchMetaDataUpdate;
|
||||||
|
import com.google.gwtorm.jdbc.JdbcSchema;
|
||||||
|
import com.google.gwtorm.server.OrmException;
|
||||||
|
import com.google.inject.Inject;
|
||||||
|
import com.google.inject.Provider;
|
||||||
|
import org.eclipse.jgit.errors.ConfigInvalidException;
|
||||||
|
import org.eclipse.jgit.errors.RepositoryNotFoundException;
|
||||||
|
import org.eclipse.jgit.lib.CommitBuilder;
|
||||||
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.util.SystemReader;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.sql.ResultSet;
|
||||||
|
import java.sql.SQLException;
|
||||||
|
import java.sql.Statement;
|
||||||
|
import java.sql.Timestamp;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
public class Schema_65 extends SchemaVersion {
|
||||||
|
private final AllProjectsName allProjects;
|
||||||
|
private final GitRepositoryManager mgr;
|
||||||
|
private final PersonIdent serverUser;
|
||||||
|
private final @AnonymousCowardName String anonymousCowardName;
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
Schema_65(Provider<Schema_64> prior,
|
||||||
|
AllProjectsName allProjects,
|
||||||
|
GitRepositoryManager mgr,
|
||||||
|
@GerritPersonIdent PersonIdent serverUser,
|
||||||
|
@AnonymousCowardName String anonymousCowardName) {
|
||||||
|
super(prior);
|
||||||
|
this.allProjects = allProjects;
|
||||||
|
this.mgr = mgr;
|
||||||
|
this.serverUser = serverUser;
|
||||||
|
this.anonymousCowardName = anonymousCowardName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void migrateData(ReviewDb db, UpdateUI ui)
|
||||||
|
throws OrmException, SQLException {
|
||||||
|
Repository git;
|
||||||
|
try {
|
||||||
|
git = mgr.openRepository(allProjects);
|
||||||
|
} catch (RepositoryNotFoundException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
MetaDataUpdate md =
|
||||||
|
new MetaDataUpdate(new NoReplication(), allProjects, git);
|
||||||
|
ProjectConfig config = ProjectConfig.read(md);
|
||||||
|
Map<Integer, ContributorAgreement> agreements = getAgreementToAdd(db, config);
|
||||||
|
if (agreements.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ui.message("Moved contributor agreements to project.config");
|
||||||
|
|
||||||
|
// Create the auto verify groups.
|
||||||
|
List<AccountGroup.Id> adminGroupIds = getAdministrateServerGroups(db, config);
|
||||||
|
for (ContributorAgreement agreement : agreements.values()) {
|
||||||
|
if (agreement.getAutoVerify() != null) {
|
||||||
|
getOrCreateGroupForIndividuals(db, config, adminGroupIds, agreement);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan AccountAgreement
|
||||||
|
long minTime = addAccountAgreements(db, config, adminGroupIds, agreements);
|
||||||
|
|
||||||
|
ProjectConfig base = ProjectConfig.read(md, null);
|
||||||
|
for (ContributorAgreement agreement : agreements.values()) {
|
||||||
|
base.replace(agreement);
|
||||||
|
}
|
||||||
|
base.getAccountsSection().setSameGroupVisibility(
|
||||||
|
config.getAccountsSection().getSameGroupVisibility());
|
||||||
|
|
||||||
|
BatchMetaDataUpdate batch = base.openUpdate(md);
|
||||||
|
try {
|
||||||
|
// Scan AccountGroupAgreement
|
||||||
|
List<AccountGroupAgreement> groupAgreements =
|
||||||
|
getAccountGroupAgreements(db, agreements);
|
||||||
|
|
||||||
|
// Find the earliest change
|
||||||
|
for (AccountGroupAgreement aga : groupAgreements) {
|
||||||
|
minTime = Math.min(minTime, aga.getTime());
|
||||||
|
}
|
||||||
|
minTime -= 60 * 1000; // 1 Minute
|
||||||
|
|
||||||
|
CommitBuilder commit = new CommitBuilder();
|
||||||
|
commit.setAuthor(new PersonIdent(serverUser, new Date(minTime)));
|
||||||
|
commit.setCommitter(new PersonIdent(serverUser, new Date(minTime)));
|
||||||
|
commit.setMessage("Add the ContributorAgreements for upgrade to Gerrit Code Review schema 65\n");
|
||||||
|
batch.write(commit);
|
||||||
|
|
||||||
|
for (AccountGroupAgreement aga : groupAgreements) {
|
||||||
|
AccountGroup group = db.accountGroups().get(aga.groupId);
|
||||||
|
if (group == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ContributorAgreement agreement = agreements.get(aga.claId);
|
||||||
|
agreement.getAccepted().add(new PermissionRule(config.resolve(group)));
|
||||||
|
base.replace(agreement);
|
||||||
|
|
||||||
|
PersonIdent ident = null;
|
||||||
|
if (aga.reviewedBy != null) {
|
||||||
|
Account ua = db.accounts().get(aga.reviewedBy);
|
||||||
|
if (ua != null) {
|
||||||
|
String name = ua.getFullName();
|
||||||
|
String email = ua.getPreferredEmail();
|
||||||
|
|
||||||
|
if (email == null || email.isEmpty()) {
|
||||||
|
// No preferred email is configured. Use a generic identity so we
|
||||||
|
// don't leak an address the user may have given us, but doesn't
|
||||||
|
// necessarily want to publish through Git records.
|
||||||
|
//
|
||||||
|
String user = ua.getUserName();
|
||||||
|
if (user == null || user.isEmpty()) {
|
||||||
|
user = "account-" + ua.getId().toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
String host = SystemReader.getInstance().getHostname();
|
||||||
|
email = user + "@" + host;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (name == null || name.isEmpty()) {
|
||||||
|
final int at = email.indexOf('@');
|
||||||
|
if (0 < at) {
|
||||||
|
name = email.substring(0, at);
|
||||||
|
} else {
|
||||||
|
name = anonymousCowardName;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ident = new PersonIdent(name, email, new Date(aga.getTime()), TimeZone.getDefault());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (ident == null) {
|
||||||
|
ident = new PersonIdent(serverUser, new Date(aga.getTime()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Build the commits such that it keeps track of the date added and
|
||||||
|
// who added it.
|
||||||
|
commit = new CommitBuilder();
|
||||||
|
commit.setAuthor(ident);
|
||||||
|
commit.setCommitter(new PersonIdent(serverUser, new Date(aga.getTime())));
|
||||||
|
|
||||||
|
String msg = String.format("Accept %s contributor agreement for %s\n",
|
||||||
|
agreement.getName(), group.getName());
|
||||||
|
if (!Strings.isNullOrEmpty(aga.reviewComments)) {
|
||||||
|
msg += "\n" + aga.reviewComments + "\n";
|
||||||
|
}
|
||||||
|
commit.setMessage(msg);
|
||||||
|
batch.write(commit);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Merge the agreements with the other data in project.config.
|
||||||
|
commit = new CommitBuilder();
|
||||||
|
commit.setAuthor(serverUser);
|
||||||
|
commit.setCommitter(serverUser);
|
||||||
|
commit.setMessage("Upgrade to Gerrit Code Review schema 65\n");
|
||||||
|
commit.addParentId(config.getRevision());
|
||||||
|
batch.write(config, commit);
|
||||||
|
|
||||||
|
// Save the the final metadata.
|
||||||
|
if (!batch.commitAt(config.getRevision())) {
|
||||||
|
throw new OrmException("Cannot update " + allProjects);
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
batch.close();
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
} catch (ConfigInvalidException e) {
|
||||||
|
throw new OrmException(e);
|
||||||
|
} finally {
|
||||||
|
git.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<Integer, ContributorAgreement> getAgreementToAdd(
|
||||||
|
ReviewDb db, ProjectConfig config) throws SQLException {
|
||||||
|
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
|
||||||
|
try {
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT short_name, id, require_contact_information," +
|
||||||
|
" short_description, agreement_url, auto_verify " +
|
||||||
|
"FROM contributor_agreements WHERE active = 'Y'");
|
||||||
|
try {
|
||||||
|
Map<Integer, ContributorAgreement> agreements = Maps.newHashMap();
|
||||||
|
while (rs.next()) {
|
||||||
|
String name = rs.getString(1);
|
||||||
|
if (config.getContributorAgreement(name) != null) {
|
||||||
|
continue; // already exists
|
||||||
|
}
|
||||||
|
ContributorAgreement a = config.getContributorAgreement(name, true);
|
||||||
|
agreements.put(rs.getInt(2), a);
|
||||||
|
|
||||||
|
a.setRequireContactInformation("Y".equals(rs.getString(3)));
|
||||||
|
a.setDescription(rs.getString(4));
|
||||||
|
a.setAgreementUrl(rs.getString(5));
|
||||||
|
if ("Y".equals(rs.getString(6))) {
|
||||||
|
a.setAutoVerify(new GroupReference(null, null));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return agreements;
|
||||||
|
} finally {
|
||||||
|
rs.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stmt.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private AccountGroup createGroup(ReviewDb db, String groupName,
|
||||||
|
AccountGroup.Id adminGroupId, String description)
|
||||||
|
throws OrmException {
|
||||||
|
final AccountGroup.Id groupId =
|
||||||
|
new AccountGroup.Id(db.nextAccountGroupId());
|
||||||
|
final AccountGroup.NameKey nameKey = new AccountGroup.NameKey(groupName);
|
||||||
|
final AccountGroup.UUID uuid = GroupUUID.make(groupName, serverUser);
|
||||||
|
final AccountGroup group = new AccountGroup(nameKey, groupId, uuid);
|
||||||
|
group.setOwnerGroupId(adminGroupId);
|
||||||
|
group.setDescription(description);
|
||||||
|
final AccountGroupName gn = new AccountGroupName(group);
|
||||||
|
// first insert the group name to validate that the group name hasn't
|
||||||
|
// already been used to create another group
|
||||||
|
db.accountGroupNames().insert(Collections.singleton(gn));
|
||||||
|
db.accountGroups().insert(Collections.singleton(group));
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AccountGroup.Id> getAdministrateServerGroups(
|
||||||
|
ReviewDb db, ProjectConfig cfg) throws OrmException {
|
||||||
|
List<PermissionRule> rules = cfg.getAccessSection(AccessSection.GLOBAL_CAPABILITIES)
|
||||||
|
.getPermission(GlobalCapability.ADMINISTRATE_SERVER)
|
||||||
|
.getRules();
|
||||||
|
|
||||||
|
List<AccountGroup.Id> groups =
|
||||||
|
Lists.newArrayListWithExpectedSize(rules.size());
|
||||||
|
for (PermissionRule rule : rules) {
|
||||||
|
if (rule.getAction() == Action.ALLOW) {
|
||||||
|
groups.add(db.accountGroups()
|
||||||
|
.byUUID(rule.getGroup().getUUID())
|
||||||
|
.toList()
|
||||||
|
.get(0)
|
||||||
|
.getId());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (groups.isEmpty()) {
|
||||||
|
throw new IllegalStateException("no administrator group found");
|
||||||
|
}
|
||||||
|
|
||||||
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
private GroupReference getOrCreateGroupForIndividuals(ReviewDb db,
|
||||||
|
ProjectConfig config, List<AccountGroup.Id> adminGroupIds,
|
||||||
|
ContributorAgreement agreement)
|
||||||
|
throws OrmException {
|
||||||
|
if (!agreement.getAccepted().isEmpty()) {
|
||||||
|
return agreement.getAccepted().get(0).getGroup();
|
||||||
|
}
|
||||||
|
|
||||||
|
String name = "CLA Accepted - " + agreement.getName();
|
||||||
|
AccountGroupName agn =
|
||||||
|
db.accountGroupNames().get(new AccountGroup.NameKey(name));
|
||||||
|
AccountGroup ag;
|
||||||
|
if (agn != null) {
|
||||||
|
ag = db.accountGroups().get(agn.getId());
|
||||||
|
if (ag == null) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"account group name exists but account group does not: " + name);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!adminGroupIds.contains(ag.getOwnerGroupId())) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"individual group exists with non admin owner group: " + name);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ag = createGroup(db, name, adminGroupIds.get(0),
|
||||||
|
String.format("Users who have accepted the %s CLA", agreement.getName()));
|
||||||
|
}
|
||||||
|
GroupReference group = config.resolve(ag);
|
||||||
|
agreement.setAccepted(Lists.newArrayList(new PermissionRule(group)));
|
||||||
|
if (agreement.getAutoVerify() != null) {
|
||||||
|
agreement.setAutoVerify(group);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow accounts in the same individual CLA group to see each
|
||||||
|
// other in same group visibility mode.
|
||||||
|
List<PermissionRule> sameGroupVisibility =
|
||||||
|
config.getAccountsSection().getSameGroupVisibility();
|
||||||
|
PermissionRule rule = new PermissionRule(group);
|
||||||
|
rule.setDeny();
|
||||||
|
if (!sameGroupVisibility.contains(rule)) {
|
||||||
|
sameGroupVisibility.add(rule);
|
||||||
|
}
|
||||||
|
return group;
|
||||||
|
}
|
||||||
|
|
||||||
|
private long addAccountAgreements(ReviewDb db, ProjectConfig config,
|
||||||
|
List<AccountGroup.Id> adminGroupIds,
|
||||||
|
Map<Integer, ContributorAgreement> agreements)
|
||||||
|
throws SQLException, OrmException {
|
||||||
|
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
|
||||||
|
try {
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT account_id, cla_id, accepted_on, reviewed_by," +
|
||||||
|
" reviewed_on, review_comments " +
|
||||||
|
"FROM account_agreements WHERE status = 'V'");
|
||||||
|
try {
|
||||||
|
long minTime = System.currentTimeMillis();
|
||||||
|
while (rs.next()) {
|
||||||
|
Account.Id accountId = new Account.Id(rs.getInt(1));
|
||||||
|
Account.Id reviewerId = new Account.Id(rs.getInt(4));
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
reviewerId = accountId;
|
||||||
|
}
|
||||||
|
|
||||||
|
int claId = rs.getInt(2);
|
||||||
|
ContributorAgreement agreement = agreements.get(claId);
|
||||||
|
if (agreement == null) {
|
||||||
|
continue; // Agreement is invalid
|
||||||
|
}
|
||||||
|
|
||||||
|
Timestamp acceptedOn = rs.getTimestamp(3);
|
||||||
|
minTime = Math.min(minTime, acceptedOn.getTime());
|
||||||
|
|
||||||
|
// Enter Agreement
|
||||||
|
GroupReference individualGroup =
|
||||||
|
getOrCreateGroupForIndividuals(db, config, adminGroupIds, agreement);
|
||||||
|
AccountGroup.Id groupId = db.accountGroups()
|
||||||
|
.byUUID(individualGroup.getUUID())
|
||||||
|
.toList()
|
||||||
|
.get(0)
|
||||||
|
.getId();
|
||||||
|
|
||||||
|
final AccountGroupMember.Key key =
|
||||||
|
new AccountGroupMember.Key(accountId, groupId);
|
||||||
|
AccountGroupMember m = db.accountGroupMembers().get(key);
|
||||||
|
if (m == null) {
|
||||||
|
m = new AccountGroupMember(key);
|
||||||
|
db.accountGroupMembersAudit().insert(
|
||||||
|
Collections.singleton(
|
||||||
|
new AccountGroupMemberAudit(m, reviewerId, acceptedOn)));
|
||||||
|
db.accountGroupMembers().insert(Collections.singleton(m));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return minTime;
|
||||||
|
} finally {
|
||||||
|
rs.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stmt.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class AccountGroupAgreement {
|
||||||
|
private AccountGroup.Id groupId;
|
||||||
|
private int claId;
|
||||||
|
private Timestamp acceptedOn;
|
||||||
|
private Account.Id reviewedBy;
|
||||||
|
private Timestamp reviewedOn;
|
||||||
|
private String reviewComments;
|
||||||
|
|
||||||
|
private long getTime() {
|
||||||
|
return (reviewedOn == null) ? acceptedOn.getTime() : reviewedOn.getTime();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<AccountGroupAgreement> getAccountGroupAgreements(
|
||||||
|
ReviewDb db, Map<Integer, ContributorAgreement> agreements)
|
||||||
|
throws SQLException {
|
||||||
|
|
||||||
|
Statement stmt = ((JdbcSchema) db).getConnection().createStatement();
|
||||||
|
try {
|
||||||
|
ResultSet rs = stmt.executeQuery(
|
||||||
|
"SELECT group_id, cla_id, accepted_on, reviewed_by, reviewed_on, " +
|
||||||
|
" review_comments " +
|
||||||
|
"FROM account_group_agreements");
|
||||||
|
try {
|
||||||
|
List<AccountGroupAgreement> groupAgreements = Lists.newArrayList();
|
||||||
|
while (rs.next()) {
|
||||||
|
AccountGroupAgreement a = new AccountGroupAgreement();
|
||||||
|
a.groupId = new AccountGroup.Id(rs.getInt(1));
|
||||||
|
a.claId = rs.getInt(2);
|
||||||
|
if (!agreements.containsKey(a.claId)) {
|
||||||
|
continue; // Agreement is invalid
|
||||||
|
}
|
||||||
|
a.acceptedOn = rs.getTimestamp(3);
|
||||||
|
a.reviewedBy = new Account.Id(rs.getInt(4));
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
a.reviewedBy = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.reviewedOn = rs.getTimestamp(5);
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
a.reviewedOn = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
a.reviewComments = rs.getString(6);
|
||||||
|
if (rs.wasNull()) {
|
||||||
|
a.reviewComments = null;
|
||||||
|
}
|
||||||
|
groupAgreements.add(a);
|
||||||
|
}
|
||||||
|
return groupAgreements;
|
||||||
|
} finally {
|
||||||
|
rs.close();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
stmt.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,9 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
|
|||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.GerritPersonIdentProvider;
|
import com.google.gerrit.server.GerritPersonIdentProvider;
|
||||||
import com.google.gerrit.server.config.AllProjectsName;
|
import com.google.gerrit.server.config.AllProjectsName;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
||||||
|
import com.google.gerrit.server.config.FactoryModule;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.gerrit.server.config.SitePaths;
|
import com.google.gerrit.server.config.SitePaths;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@@ -27,7 +30,6 @@ import com.google.gerrit.testutil.InMemoryDatabase;
|
|||||||
import com.google.gwtorm.server.OrmException;
|
import com.google.gwtorm.server.OrmException;
|
||||||
import com.google.gwtorm.server.SchemaFactory;
|
import com.google.gwtorm.server.SchemaFactory;
|
||||||
import com.google.gwtorm.server.StatementExecutor;
|
import com.google.gwtorm.server.StatementExecutor;
|
||||||
import com.google.inject.AbstractModule;
|
|
||||||
import com.google.inject.Guice;
|
import com.google.inject.Guice;
|
||||||
import com.google.inject.TypeLiteral;
|
import com.google.inject.TypeLiteral;
|
||||||
|
|
||||||
@@ -63,7 +65,7 @@ public class SchemaUpdaterTest extends TestCase {
|
|||||||
|
|
||||||
final File site = new File(UUID.randomUUID().toString());
|
final File site = new File(UUID.randomUUID().toString());
|
||||||
final SitePaths paths = new SitePaths(site);
|
final SitePaths paths = new SitePaths(site);
|
||||||
SchemaUpdater u = Guice.createInjector(new AbstractModule() {
|
SchemaUpdater u = Guice.createInjector(new FactoryModule() {
|
||||||
@Override
|
@Override
|
||||||
protected void configure() {
|
protected void configure() {
|
||||||
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toInstance(db);
|
bind(new TypeLiteral<SchemaFactory<ReviewDb>>() {}).toInstance(db);
|
||||||
@@ -88,6 +90,10 @@ public class SchemaUpdaterTest extends TestCase {
|
|||||||
|
|
||||||
bind(GitRepositoryManager.class) //
|
bind(GitRepositoryManager.class) //
|
||||||
.to(LocalDiskRepositoryManager.class);
|
.to(LocalDiskRepositoryManager.class);
|
||||||
|
|
||||||
|
bind(String.class) //
|
||||||
|
.annotatedWith(AnonymousCowardName.class) //
|
||||||
|
.toProvider(AnonymousCowardNameProvider.class);
|
||||||
}
|
}
|
||||||
}).getInstance(SchemaUpdater.class);
|
}).getInstance(SchemaUpdater.class);
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,8 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
|
|||||||
import com.google.gerrit.server.GerritPersonIdent;
|
import com.google.gerrit.server.GerritPersonIdent;
|
||||||
import com.google.gerrit.server.GerritPersonIdentProvider;
|
import com.google.gerrit.server.GerritPersonIdentProvider;
|
||||||
import com.google.gerrit.server.config.AllProjectsName;
|
import com.google.gerrit.server.config.AllProjectsName;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardName;
|
||||||
|
import com.google.gerrit.server.config.AnonymousCowardNameProvider;
|
||||||
import com.google.gerrit.server.config.GerritServerConfig;
|
import com.google.gerrit.server.config.GerritServerConfig;
|
||||||
import com.google.gerrit.server.config.SitePath;
|
import com.google.gerrit.server.config.SitePath;
|
||||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||||
@@ -122,6 +124,10 @@ public class InMemoryDatabase implements SchemaFactory<ReviewDb> {
|
|||||||
|
|
||||||
bind(GitRepositoryManager.class) //
|
bind(GitRepositoryManager.class) //
|
||||||
.to(LocalDiskRepositoryManager.class);
|
.to(LocalDiskRepositoryManager.class);
|
||||||
|
|
||||||
|
bind(String.class) //
|
||||||
|
.annotatedWith(AnonymousCowardName.class) //
|
||||||
|
.toProvider(AnonymousCowardNameProvider.class);
|
||||||
}
|
}
|
||||||
}).getBinding(Key.get(SchemaVersion.class, Current.class))
|
}).getBinding(Key.get(SchemaVersion.class, Current.class))
|
||||||
.getProvider().get();
|
.getProvider().get();
|
||||||
|
|||||||
Reference in New Issue
Block a user