blob: a188805398f89fc439e79920c45662ba589de7ca [file] [log] [blame]
// Copyright (C) 2008 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.pgm;
import com.google.gerrit.client.reviewdb.Account;
import com.google.gerrit.client.reviewdb.AccountGroup;
import com.google.gerrit.client.reviewdb.AccountGroupMember;
import com.google.gerrit.client.reviewdb.ApprovalCategory;
import com.google.gerrit.client.reviewdb.Change;
import com.google.gerrit.client.reviewdb.ChangeApproval;
import com.google.gerrit.client.reviewdb.PatchSet;
import com.google.gerrit.client.reviewdb.Project;
import com.google.gerrit.client.reviewdb.ProjectRight;
import com.google.gerrit.client.reviewdb.ReviewDb;
import com.google.gerrit.client.reviewdb.SystemConfig;
import com.google.gerrit.client.rpc.Common;
import com.google.gerrit.git.InvalidRepositoryException;
import com.google.gerrit.git.PatchSetImporter;
import com.google.gerrit.git.WorkQueue;
import com.google.gerrit.server.ChangeUtil;
import com.google.gerrit.server.GerritServer;
import com.google.gwtjsonrpc.server.XsrfException;
import com.google.gwtorm.client.OrmException;
import com.google.gwtorm.jdbc.JdbcSchema;
import org.spearce.jgit.lib.Constants;
import org.spearce.jgit.lib.LockFile;
import org.spearce.jgit.lib.ObjectId;
import org.spearce.jgit.lib.ProgressMonitor;
import org.spearce.jgit.lib.Ref;
import org.spearce.jgit.lib.RefUpdate;
import org.spearce.jgit.lib.Repository;
import org.spearce.jgit.lib.TextProgressMonitor;
import org.spearce.jgit.revwalk.RevCommit;
import org.spearce.jgit.revwalk.RevWalk;
import java.io.File;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* Imports data from Gerrit 1 into Gerrit 2.
* <p>
* The tool assumes that <code>gerrit1_import/import_gerrit1.sql</code> has
* already been executed on this schema. All existing ProjectRight entities are
* wiped from the database and generated from scratch.
* <p>
* The tool requires Gerrit 1 tables (<code>gerrit1.$table_name</code>) through
* the same database connection as the ReviewDb schema is on.
*/
public class ImportGerrit1 {
private static GerritServer gs;
private static ReviewDb db;
private static Connection sql;
private static ApprovalCategory verifyCategory;
private static ApprovalCategory approveCategory;
private static ApprovalCategory submitCategory;
public static void main(final String[] argv) throws OrmException,
XsrfException, IOException, SQLException, InvalidRepositoryException {
try {
mainImpl(argv);
} finally {
WorkQueue.terminate();
}
}
private static void mainImpl(final String[] argv) throws OrmException,
XsrfException, IOException, SQLException, InvalidRepositoryException {
final ProgressMonitor pm = new TextProgressMonitor();
gs = GerritServer.getInstance();
db = Common.getSchemaFactory().open();
sql = ((JdbcSchema) db).getConnection();
try {
verifyCategory = db.approvalCategories().byName("Verified");
approveCategory = db.approvalCategories().byName("Code Review");
submitCategory = db.approvalCategories().get(ApprovalCategory.SUBMIT);
final Statement query = sql.createStatement();
java.sql.ResultSet srcs;
// Set project descriptions back into git
//
pm.start(1);
pm.beginTask("Export project descriptions", ProgressMonitor.UNKNOWN);
srcs = query.executeQuery("SELECT p.project_id FROM projects p");
while (srcs.next()) {
final Project.Id projectId = new Project.Id(srcs.getInt(1));
if (ProjectRight.WILD_PROJECT.equals(projectId)) {
continue;
}
final Project proj = db.projects().get(projectId);
final Repository e;
final LockFile f;
e = gs.getRepositoryCache().get(proj.getName());
f = new LockFile(new File(e.getDirectory(), "description"));
if (f.lock()) {
String d = proj.getDescription();
if (d != null) {
d = d.trim() + "\n";
} else {
d = "";
}
f.write(Constants.encode(d));
f.commit();
} else {
throw new IOException("Cannot set description for " + proj.getName());
}
pm.update(1);
}
srcs.close();
pm.endTask();
// Convert the approval right data from projects.
//
pm.start(1);
pm.beginTask("Import project rights", ProgressMonitor.UNKNOWN);
query.executeUpdate("DELETE FROM project_rights");
insertApprovalWildCard();
insertAccessWildCard();
srcs =
query.executeQuery("SELECT p.project_id, r.ar_key"
+ " FROM gerrit1.project_code_reviews r, projects p"
+ " WHERE p.project_id = r.project_id");
while (srcs.next()) {
final Project.Id projectId = new Project.Id(srcs.getInt(1));
final String arKey = srcs.getString(2);
doImport(projectId, arKey);
pm.update(1);
}
srcs.close();
pm.endTask();
// Compute the sort keys for change entities
//
srcs = query.executeQuery("SELECT change_id FROM changes");
final ArrayList<Change.Id> changesToDo = new ArrayList<Change.Id>();
while (srcs.next()) {
final Change.Id changeId = new Change.Id(srcs.getInt(1));
changesToDo.add(changeId);
}
srcs.close();
pm.start(1);
pm.beginTask("Compute sort keys", changesToDo.size());
for (final Change.Id changeId : changesToDo) {
final Change c = db.changes().get(changeId);
ChangeUtil.computeSortKey(c);
final List<ChangeApproval> approvals =
db.changeApprovals().byChange(changeId).toList();
for (final ChangeApproval a : approvals) {
a.cache(c);
}
db.changeApprovals().update(approvals);
db.changes().update(Collections.singleton(c));
pm.update(1);
}
pm.endTask();
// Rebuild the cached PatchSet information directly from Git.
// There's some oddities in the Gerrit 1 data that we got from
// Google App Engine's data store; the quickest way to fix it
// is to just recache the data from Git.
//
srcs =
query.executeQuery("SELECT change_id,patch_set_id FROM patch_sets");
final ArrayList<PatchSet.Id> psToDo = new ArrayList<PatchSet.Id>();
while (srcs.next()) {
final Change.Id changeId = new Change.Id(srcs.getInt(1));
final PatchSet.Id psId = new PatchSet.Id(changeId, srcs.getInt(2));
psToDo.add(psId);
}
query.close();
final Map<String, Set<String>> validRefs =
new HashMap<String, Set<String>>();
pm.start(1);
pm.beginTask("Import patch sets", psToDo.size());
for (final PatchSet.Id psId : psToDo) {
final Change c = db.changes().get(psId.getParentKey());
final PatchSet ps = db.patchSets().get(psId);
final String projectName = c.getDest().getParentKey().get();
final Repository repo = gs.getRepositoryCache().get(projectName);
final RevWalk rw = new RevWalk(repo);
final RevCommit src =
rw.parseCommit(ObjectId.fromString(ps.getRevision().get()));
new PatchSetImporter(db, repo, src, ps, false).run();
Set<String> s = validRefs.get(projectName);
if (s == null) {
s = new HashSet<String>();
validRefs.put(projectName, s);
}
s.add(ps.getRefName());
pm.update(1);
}
pm.endTask();
pruneProjectRefs(pm, validRefs);
} finally {
db.close();
}
}
private static void pruneProjectRefs(final ProgressMonitor pm,
final Map<String, Set<String>> validRefs) throws OrmException,
IOException {
final List<Project> pToClean = db.projects().all().toList();
pm.start(1);
pm.beginTask("Prune old refs", pToClean.size());
for (final Project p : pToClean) {
final Repository repo;
try {
repo = gs.getRepositoryCache().get(p.getName());
} catch (InvalidRepositoryException e) {
pm.update(1);
continue;
}
Set<String> valid = validRefs.get(p.getName());
if (valid == null) {
valid = Collections.emptySet();
}
final RevWalk rw = new RevWalk(repo);
for (final Ref r : repo.getAllRefs().values()) {
boolean delete = false;
if (r.getName().startsWith("refs/merges/")) {
delete = true;
}
if (r.getName().startsWith("refs/changes/")
&& !valid.contains(r.getName())) {
delete = true;
}
if (delete) {
final RefUpdate u = repo.updateRef(r.getName());
u.setForceUpdate(true);
u.delete(rw);
}
}
pm.update(1);
}
pm.endTask();
}
private static void insertApprovalWildCard() throws OrmException {
final ProjectRight.Key key =
new ProjectRight.Key(ProjectRight.WILD_PROJECT,
approveCategory.getId(), db.systemConfig().get(
new SystemConfig.Key()).registeredGroupId);
final ProjectRight pr = new ProjectRight(key);
pr.setMinValue((short) -1);
pr.setMaxValue((short) 1);
db.projectRights().insert(Collections.singleton(pr));
}
private static void insertAccessWildCard() throws OrmException {
final ProjectRight.Key key =
new ProjectRight.Key(ProjectRight.WILD_PROJECT, ApprovalCategory.READ,
db.systemConfig().get(new SystemConfig.Key()).anonymousGroupId);
final ProjectRight pr = new ProjectRight(key);
pr.setMinValue((short) 1);
pr.setMaxValue((short) 1);
db.projectRights().insert(Collections.singleton(pr));
}
private static void doImport(final Project.Id projectId, final String arKey)
throws OrmException, SQLException {
final int arId = findId(arKey);
if (arId < 0) {
return;
}
final Project proj = db.projects().get(projectId);
final Set<AccountGroup.Id> approverg = groups(arId, "approver");
final Set<AccountGroup.Id> verifierg = groups(arId, "verifier");
final Set<AccountGroup.Id> submitterg = groups(arId, "submitter");
final Set<Account.Id> approveru = users(arId, "approver");
final Set<Account.Id> verifieru = users(arId, "verifier");
final Set<Account.Id> submitteru = users(arId, "submitter");
importCat(proj, "approvers", approveCategory, approverg, approveru);
importCat(proj, "verifiers", verifyCategory, verifierg, verifieru);
importCat(proj, "submitters", submitCategory, submitterg, submitteru);
}
private static void importCat(final Project proj, final String type,
final ApprovalCategory category, final Set<AccountGroup.Id> groups,
final Set<Account.Id> users) throws OrmException {
final HashSet<Account.Id> needGroup = new HashSet<Account.Id>(users);
for (final AccountGroup.Id groupId : groups) {
insertRight(proj, category, groupId);
for (final Iterator<Account.Id> i = needGroup.iterator(); i.hasNext();) {
if (Common.getGroupCache().isInGroup(i.next(), groupId)) {
i.remove();
}
}
}
if (!needGroup.isEmpty()) {
final AccountGroup.Id groupId =
new AccountGroup.Id(db.nextAccountGroupId());
final AccountGroup group =
new AccountGroup(new AccountGroup.NameKey(shortName(proj) + "_"
+ proj.getId().get() + "-" + type), groupId);
group.setOwnerGroupId(proj.getOwnerGroupId());
group.setDescription(proj.getName() + " " + type);
db.accountGroups().insert(Collections.singleton(group));
for (final Account.Id aId : needGroup) {
db.accountGroupMembers().insert(
Collections.singleton(new AccountGroupMember(
new AccountGroupMember.Key(aId, groupId))));
}
insertRight(proj, category, groupId);
}
}
private static void insertRight(final Project proj,
final ApprovalCategory category, final AccountGroup.Id groupId)
throws OrmException {
final ProjectRight.Key key =
new ProjectRight.Key(proj.getId(), category.getId(), groupId);
final ProjectRight pr = new ProjectRight(key);
if (category == approveCategory) {
pr.setMinValue((short) -2);
pr.setMaxValue((short) 2);
} else if (category == verifyCategory) {
pr.setMinValue((short) -1);
pr.setMaxValue((short) 1);
} else if (category == submitCategory) {
pr.setMinValue((short) 1);
pr.setMaxValue((short) 1);
} else {
throw new OrmException("Cannot import category " + category.getId());
}
db.projectRights().insert(Collections.singleton(pr));
}
private static String shortName(final Project proj) {
final String n = proj.getName();
final int s = n.lastIndexOf('/');
return 0 < s ? n.substring(s + 1) : n;
}
private static Set<AccountGroup.Id> groups(final int arId, final String type)
throws SQLException {
final PreparedStatement ps =
sql.prepareStatement("SELECT g.group_id FROM account_groups g,"
+ " gerrit1.approval_right_groups s, gerrit1.account_groups o"
+ " WHERE s.ar_id = ? AND s.type = ?"
+ " AND o.gae_key = s.group_key AND (g.name = o.name"
+ " OR (g.name = 'Administrators' AND o.name = 'admin'))");
try {
ps.setInt(1, arId);
ps.setString(2, type);
final java.sql.ResultSet rs = ps.executeQuery();
try {
final HashSet<AccountGroup.Id> r = new HashSet<AccountGroup.Id>();
while (rs.next()) {
r.add(new AccountGroup.Id(rs.getInt(1)));
}
return r;
} finally {
rs.close();
}
} finally {
ps.close();
}
}
private static Set<Account.Id> users(final int arId, final String type)
throws SQLException {
final PreparedStatement ps =
sql.prepareStatement("SELECT a.account_id FROM accounts a,"
+ " gerrit1.approval_right_users s"
+ " WHERE s.ar_id = ? AND s.type = ?"
+ " AND a.preferred_email = s.email");
try {
ps.setInt(1, arId);
ps.setString(2, type);
final java.sql.ResultSet rs = ps.executeQuery();
try {
final HashSet<Account.Id> r = new HashSet<Account.Id>();
while (rs.next()) {
r.add(new Account.Id(rs.getInt(1)));
}
return r;
} finally {
rs.close();
}
} finally {
ps.close();
}
}
private static int findId(final String arKey) throws SQLException {
final PreparedStatement ps =
sql.prepareStatement("SELECT ar_id FROM gerrit1.approval_rights"
+ " WHERE gae_key=?");
try {
ps.setString(1, arKey);
final java.sql.ResultSet rs = ps.executeQuery();
try {
if (rs.next()) {
return rs.getInt(1);
}
return -1;
} finally {
rs.close();
}
} finally {
ps.close();
}
}
}