Create a user branch for every account
Make sure that for every account a user branch exists that has an initial empty commit with the registration date as commit time. The commit timestamp of the first commit on a user branch will be used as registration timestamp when accounts are stored in NoteDb. When an account is created create the user branch with an initial empty commit that has the registration date as commit time. For existing accounts add a schema migration that: - creates the user branch with an initial empty commit that has the registration date as commit time if the user branch doesn't exist yet - rewrites the user branch if it already exists and inserts an initial empty commit with the registration date as commit time (if such a commit doesn't exist yet). Change-Id: I81491a253350a43f094fdfcb32298efde0cb086a Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
@@ -53,7 +53,7 @@ public class AccountCreator {
|
||||
private final Map<String, TestAccount> accounts;
|
||||
|
||||
private final SchemaFactory<ReviewDb> reviewDbProvider;
|
||||
private final AccountsUpdate accountsUpdate;
|
||||
private final AccountsUpdate.Server accountsUpdate;
|
||||
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
|
||||
private final GroupCache groupCache;
|
||||
private final SshKeyCache sshKeyCache;
|
||||
@@ -65,7 +65,7 @@ public class AccountCreator {
|
||||
@Inject
|
||||
AccountCreator(
|
||||
SchemaFactory<ReviewDb> schema,
|
||||
AccountsUpdate accountsUpdate,
|
||||
AccountsUpdate.Server accountsUpdate,
|
||||
VersionedAuthorizedKeys.Accessor authorizedKeys,
|
||||
GroupCache groupCache,
|
||||
SshKeyCache sshKeyCache,
|
||||
@@ -114,7 +114,7 @@ public class AccountCreator {
|
||||
Account a = new Account(id, TimeUtil.nowTs());
|
||||
a.setFullName(fullName);
|
||||
a.setPreferredEmail(email);
|
||||
accountsUpdate.insert(db, a);
|
||||
accountsUpdate.create().insert(db, a);
|
||||
|
||||
if (groups != null) {
|
||||
for (String n : groups) {
|
||||
|
||||
@@ -51,7 +51,6 @@ import com.google.gerrit.extensions.api.accounts.EmailInput;
|
||||
import com.google.gerrit.extensions.api.changes.AddReviewerInput;
|
||||
import com.google.gerrit.extensions.api.changes.ReviewInput;
|
||||
import com.google.gerrit.extensions.api.changes.StarsInput;
|
||||
import com.google.gerrit.extensions.client.GeneralPreferencesInfo;
|
||||
import com.google.gerrit.extensions.common.AccountInfo;
|
||||
import com.google.gerrit.extensions.common.ChangeInfo;
|
||||
import com.google.gerrit.extensions.common.GpgKeyInfo;
|
||||
@@ -74,6 +73,7 @@ import com.google.gerrit.server.account.externalids.ExternalIds;
|
||||
import com.google.gerrit.server.account.externalids.ExternalIdsUpdate;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.git.ProjectConfig;
|
||||
import com.google.gerrit.server.notedb.rebuild.ChangeRebuilderImpl;
|
||||
import com.google.gerrit.server.project.RefPattern;
|
||||
import com.google.gerrit.server.util.MagicBranch;
|
||||
import com.google.gerrit.testutil.ConfigSuite;
|
||||
@@ -100,6 +100,8 @@ import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
import org.eclipse.jgit.transport.PushCertificateIdent;
|
||||
import org.eclipse.jgit.transport.PushResult;
|
||||
import org.eclipse.jgit.transport.RemoteRefUpdate;
|
||||
@@ -183,6 +185,26 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void create() throws Exception {
|
||||
TestAccount foo = accounts.create("foo");
|
||||
AccountInfo info = gApi.accounts().id(foo.id.get()).get();
|
||||
assertThat(info.username).isEqualTo("foo");
|
||||
|
||||
// check user branch
|
||||
try (Repository repo = repoManager.openRepository(allUsers);
|
||||
RevWalk rw = new RevWalk(repo)) {
|
||||
Ref ref = repo.exactRef(RefNames.refsUsers(foo.getId()));
|
||||
assertThat(ref).isNotNull();
|
||||
RevCommit c = rw.parseCommit(ref.getObjectId());
|
||||
long timestampDiffMs =
|
||||
Math.abs(
|
||||
c.getCommitTime() * 1000L
|
||||
- accountCache.get(foo.getId()).getAccount().getRegisteredOn().getTime());
|
||||
assertThat(timestampDiffMs).isAtMost(ChangeRebuilderImpl.MAX_WINDOW_MS);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void get() throws Exception {
|
||||
AccountInfo info = gApi.accounts().id("admin").get();
|
||||
@@ -553,7 +575,7 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
@Sandboxed
|
||||
public void fetchUserBranch() throws Exception {
|
||||
ensureUserBranchCreated(user);
|
||||
setApiUser(user);
|
||||
|
||||
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers, user);
|
||||
String userRefName = RefNames.refsUsers(user.id);
|
||||
@@ -603,8 +625,6 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void pushToUserBranch() throws Exception {
|
||||
ensureUserBranchCreated(admin);
|
||||
|
||||
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
|
||||
fetch(allUsersRepo, RefNames.refsUsers(admin.id) + ":userRef");
|
||||
allUsersRepo.reset("userRef");
|
||||
@@ -617,8 +637,6 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void pushToUserBranchForReview() throws Exception {
|
||||
ensureUserBranchCreated(admin);
|
||||
|
||||
String userRefName = RefNames.refsUsers(admin.id);
|
||||
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
|
||||
fetch(allUsersRepo, userRefName + ":userRef");
|
||||
@@ -640,8 +658,6 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
|
||||
@Test
|
||||
public void pushWatchConfigToUserBranch() throws Exception {
|
||||
ensureUserBranchCreated(admin);
|
||||
|
||||
TestRepository<InMemoryRepository> allUsersRepo = cloneProject(allUsers);
|
||||
fetch(allUsersRepo, RefNames.refsUsers(admin.id) + ":userRef");
|
||||
allUsersRepo.reset("userRef");
|
||||
@@ -683,8 +699,6 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
@Sandboxed
|
||||
public void cannotDeleteUserBranch() throws Exception {
|
||||
ensureUserBranchCreated(admin);
|
||||
|
||||
grant(
|
||||
Permission.DELETE,
|
||||
allUsers,
|
||||
@@ -707,8 +721,6 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
@Test
|
||||
@Sandboxed
|
||||
public void deleteUserBranchWithAccessDatabaseCapability() throws Exception {
|
||||
ensureUserBranchCreated(admin);
|
||||
|
||||
allowGlobalCapabilities(REGISTERED_USERS, GlobalCapability.ACCESS_DATABASE);
|
||||
grant(
|
||||
Permission.DELETE,
|
||||
@@ -1019,12 +1031,4 @@ public class AccountIT extends AbstractDaemonTest {
|
||||
assertThat(accounts).hasSize(1);
|
||||
assertThat(Iterables.getOnlyElement(accounts)).isEqualTo(expectedAccount.getId());
|
||||
}
|
||||
|
||||
private void ensureUserBranchCreated(TestAccount account) throws Exception {
|
||||
// Change something in the user preferences to ensure that the user branch is created.
|
||||
setApiUser(account);
|
||||
GeneralPreferencesInfo input = new GeneralPreferencesInfo();
|
||||
input.changesPerPage = GeneralPreferencesInfo.defaults().changesPerPage + 10;
|
||||
gApi.accounts().self().setPreferences(input);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
// 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.
|
||||
|
||||
package com.google.gerrit.pgm.init;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkArgument;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.pgm.init.api.InitFlags;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.GerritPersonIdentProvider;
|
||||
import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.config.SitePaths;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.RepositoryCache.FileKey;
|
||||
import org.eclipse.jgit.util.FS;
|
||||
|
||||
public class AccountsOnInit {
|
||||
private final InitFlags flags;
|
||||
private final SitePaths site;
|
||||
private final String allUsers;
|
||||
|
||||
@Inject
|
||||
public AccountsOnInit(InitFlags flags, SitePaths site, AllUsersNameOnInitProvider allUsers) {
|
||||
this.flags = flags;
|
||||
this.site = site;
|
||||
this.allUsers = allUsers.get();
|
||||
}
|
||||
|
||||
public void insert(ReviewDb db, Account account) throws OrmException, IOException {
|
||||
db.accounts().insert(ImmutableSet.of(account));
|
||||
|
||||
File path = getPath();
|
||||
if (path != null) {
|
||||
try (Repository repo = new FileRepository(path);
|
||||
ObjectInserter oi = repo.newObjectInserter()) {
|
||||
PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
|
||||
AccountsUpdate.createUserBranch(repo, oi, serverIdent, serverIdent, account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private File getPath() {
|
||||
Path basePath = site.resolve(flags.cfg.getString("gerrit", null, "basePath"));
|
||||
checkArgument(basePath != null, "gerrit.basePath must be configured");
|
||||
return FileKey.resolve(basePath.resolve(allUsers).toFile(), FS.DETECTED);
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,6 @@ import com.google.gerrit.reviewdb.client.AccountGroupName;
|
||||
import com.google.gerrit.reviewdb.client.AccountSshKey;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.account.AccountState;
|
||||
import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.account.externalids.ExternalId;
|
||||
import com.google.gerrit.server.index.account.AccountIndex;
|
||||
import com.google.gerrit.server.index.account.AccountIndexCollection;
|
||||
@@ -48,7 +47,7 @@ import org.apache.commons.validator.routines.EmailValidator;
|
||||
public class InitAdminUser implements InitStep {
|
||||
private final ConsoleUI ui;
|
||||
private final InitFlags flags;
|
||||
private final AccountsUpdate accountsUpdate;
|
||||
private final AccountsOnInit accounts;
|
||||
private final VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory;
|
||||
private final ExternalIdsOnInit externalIds;
|
||||
private SchemaFactory<ReviewDb> dbFactory;
|
||||
@@ -58,12 +57,12 @@ public class InitAdminUser implements InitStep {
|
||||
InitAdminUser(
|
||||
InitFlags flags,
|
||||
ConsoleUI ui,
|
||||
AccountsUpdate accountsUpdate,
|
||||
AccountsOnInit accounts,
|
||||
VersionedAuthorizedKeysOnInit.Factory authorizedKeysFactory,
|
||||
ExternalIdsOnInit externalIds) {
|
||||
this.flags = flags;
|
||||
this.ui = ui;
|
||||
this.accountsUpdate = accountsUpdate;
|
||||
this.accounts = accounts;
|
||||
this.authorizedKeysFactory = authorizedKeysFactory;
|
||||
this.externalIds = externalIds;
|
||||
}
|
||||
@@ -110,7 +109,7 @@ public class InitAdminUser implements InitStep {
|
||||
Account a = new Account(id, TimeUtil.nowTs());
|
||||
a.setFullName(name);
|
||||
a.setPreferredEmail(email);
|
||||
accountsUpdate.insert(db, a);
|
||||
accounts.insert(db, a);
|
||||
|
||||
AccountGroupName adminGroupName =
|
||||
db.accountGroupNames().get(new AccountGroup.NameKey("Administrators"));
|
||||
|
||||
@@ -52,6 +52,7 @@ public class AccountManager {
|
||||
private static final Logger log = LoggerFactory.getLogger(AccountManager.class);
|
||||
|
||||
private final SchemaFactory<ReviewDb> schema;
|
||||
private final AccountsUpdate.Server accountsUpdateFactory;
|
||||
private final AccountCache byIdCache;
|
||||
private final AccountByEmailCache byEmailCache;
|
||||
private final Realm realm;
|
||||
@@ -67,6 +68,7 @@ public class AccountManager {
|
||||
@Inject
|
||||
AccountManager(
|
||||
SchemaFactory<ReviewDb> schema,
|
||||
AccountsUpdate.Server accountsUpdateFactory,
|
||||
AccountCache byIdCache,
|
||||
AccountByEmailCache byEmailCache,
|
||||
Realm accountMapper,
|
||||
@@ -78,6 +80,7 @@ public class AccountManager {
|
||||
ExternalIds externalIds,
|
||||
ExternalIdsUpdate.Server externalIdsUpdateFactory) {
|
||||
this.schema = schema;
|
||||
this.accountsUpdateFactory = accountsUpdateFactory;
|
||||
this.byIdCache = byIdCache;
|
||||
this.byEmailCache = byEmailCache;
|
||||
this.realm = accountMapper;
|
||||
@@ -229,7 +232,7 @@ public class AccountManager {
|
||||
awaitsFirstAccountCheck.getAndSet(false) && db.accounts().anyAccounts().toList().isEmpty();
|
||||
|
||||
try {
|
||||
db.accounts().upsert(Collections.singleton(account));
|
||||
accountsUpdateFactory.create().upsert(db, account);
|
||||
|
||||
ExternalId existingExtId = externalIds.get(db, extId.key());
|
||||
if (existingExtId != null && !existingExtId.accountId().equals(extId.accountId())) {
|
||||
|
||||
@@ -14,22 +14,199 @@
|
||||
|
||||
package com.google.gerrit.server.account;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.IdentifiedUser;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gwtorm.server.OrmDuplicateKeyException;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import com.google.inject.Singleton;
|
||||
import java.io.IOException;
|
||||
import java.sql.Timestamp;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
|
||||
/** Updates accounts. */
|
||||
@Singleton
|
||||
public class AccountsUpdate {
|
||||
/**
|
||||
* Factory to create an AccountsUpdate instance for updating accounts by the Gerrit server.
|
||||
*
|
||||
* <p>The Gerrit server identity will be used as author and committer for all commits that update
|
||||
* the accounts.
|
||||
*/
|
||||
@Singleton
|
||||
public static class Server {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
private final Provider<PersonIdent> serverIdent;
|
||||
|
||||
@Inject
|
||||
public Server(
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName,
|
||||
@GerritPersonIdent Provider<PersonIdent> serverIdent) {
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
this.serverIdent = serverIdent;
|
||||
}
|
||||
|
||||
public AccountsUpdate create() {
|
||||
PersonIdent i = serverIdent.get();
|
||||
return new AccountsUpdate(repoManager, allUsersName, i, i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Factory to create an AccountsUpdate instance for updating accounts by the current user.
|
||||
*
|
||||
* <p>The identity of the current user will be used as author for all commits that update the
|
||||
* accounts. The Gerrit server identity will be used as committer.
|
||||
*/
|
||||
@Singleton
|
||||
public static class User {
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
private final Provider<PersonIdent> serverIdent;
|
||||
private final Provider<IdentifiedUser> identifiedUser;
|
||||
|
||||
@Inject
|
||||
public User(
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName,
|
||||
@GerritPersonIdent Provider<PersonIdent> serverIdent,
|
||||
Provider<IdentifiedUser> identifiedUser) {
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
this.serverIdent = serverIdent;
|
||||
this.identifiedUser = identifiedUser;
|
||||
}
|
||||
|
||||
public AccountsUpdate create() {
|
||||
PersonIdent i = serverIdent.get();
|
||||
return new AccountsUpdate(
|
||||
repoManager, allUsersName, createPersonIdent(i, identifiedUser.get()), i);
|
||||
}
|
||||
|
||||
private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
|
||||
return user.newCommitterIdent(ident.getWhen(), ident.getTimeZone());
|
||||
}
|
||||
}
|
||||
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
private final PersonIdent committerIdent;
|
||||
private final PersonIdent authorIdent;
|
||||
|
||||
private AccountsUpdate(
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName,
|
||||
PersonIdent committerIdent,
|
||||
PersonIdent authorIdent) {
|
||||
this.repoManager = checkNotNull(repoManager, "repoManager");
|
||||
this.allUsersName = checkNotNull(allUsersName, "allUsersName");
|
||||
this.committerIdent = checkNotNull(committerIdent, "committerIdent");
|
||||
this.authorIdent = checkNotNull(authorIdent, "authorIdent");
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts a new account.
|
||||
*
|
||||
* @throws OrmDuplicateKeyException if the account already exists
|
||||
* @throws IOException if updating the user branch fails
|
||||
*/
|
||||
public void insert(ReviewDb db, Account account) throws OrmException {
|
||||
public void insert(ReviewDb db, Account account) throws OrmException, IOException {
|
||||
db.accounts().insert(ImmutableSet.of(account));
|
||||
createUserBranch(account);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts or updates an account.
|
||||
*
|
||||
* <p>If the account already exists, it is overwritten, otherwise it is inserted.
|
||||
*/
|
||||
public void upsert(ReviewDb db, Account account) throws OrmException, IOException {
|
||||
db.accounts().upsert(ImmutableSet.of(account));
|
||||
createUserBranchIfNeeded(account);
|
||||
}
|
||||
|
||||
private void createUserBranch(Account account) throws IOException {
|
||||
try (Repository repo = repoManager.openRepository(allUsersName);
|
||||
ObjectInserter oi = repo.newObjectInserter()) {
|
||||
String refName = RefNames.refsUsers(account.getId());
|
||||
if (repo.exactRef(refName) != null) {
|
||||
throw new IOException(
|
||||
String.format(
|
||||
"User branch %s for newly created account %s already exists.",
|
||||
refName, account.getId().get()));
|
||||
}
|
||||
createUserBranch(repo, oi, committerIdent, authorIdent, account);
|
||||
}
|
||||
}
|
||||
|
||||
private void createUserBranchIfNeeded(Account account) throws IOException {
|
||||
try (Repository repo = repoManager.openRepository(allUsersName);
|
||||
ObjectInserter oi = repo.newObjectInserter()) {
|
||||
if (repo.exactRef(RefNames.refsUsers(account.getId())) == null) {
|
||||
createUserBranch(repo, oi, committerIdent, authorIdent, account);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void createUserBranch(
|
||||
Repository repo,
|
||||
ObjectInserter oi,
|
||||
PersonIdent committerIdent,
|
||||
PersonIdent authorIdent,
|
||||
Account account)
|
||||
throws IOException {
|
||||
ObjectId id =
|
||||
createInitialEmptyCommit(oi, committerIdent, authorIdent, account.getRegisteredOn());
|
||||
|
||||
String refName = RefNames.refsUsers(account.getId());
|
||||
RefUpdate ru = repo.updateRef(refName);
|
||||
ru.setExpectedOldObjectId(ObjectId.zeroId());
|
||||
ru.setNewObjectId(id);
|
||||
ru.setForceUpdate(true);
|
||||
ru.setRefLogIdent(committerIdent);
|
||||
ru.setRefLogMessage("Create Account", true);
|
||||
Result result = ru.update();
|
||||
if (result != Result.NEW) {
|
||||
throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name()));
|
||||
}
|
||||
}
|
||||
|
||||
private static ObjectId createInitialEmptyCommit(
|
||||
ObjectInserter oi,
|
||||
PersonIdent committerIdent,
|
||||
PersonIdent authorIdent,
|
||||
Timestamp registrationDate)
|
||||
throws IOException {
|
||||
CommitBuilder cb = new CommitBuilder();
|
||||
cb.setTreeId(emptyTree(oi));
|
||||
cb.setCommitter(new PersonIdent(committerIdent, registrationDate));
|
||||
cb.setAuthor(new PersonIdent(authorIdent, registrationDate));
|
||||
cb.setMessage("Create Account");
|
||||
ObjectId id = oi.insert(cb);
|
||||
oi.flush();
|
||||
return id;
|
||||
}
|
||||
|
||||
private static ObjectId emptyTree(ObjectInserter oi) throws IOException {
|
||||
return oi.insert(Constants.OBJ_TREE, new byte[] {});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -69,7 +69,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
|
||||
private final VersionedAuthorizedKeys.Accessor authorizedKeys;
|
||||
private final SshKeyCache sshKeyCache;
|
||||
private final AccountCache accountCache;
|
||||
private final AccountsUpdate accountsUpdate;
|
||||
private final AccountsUpdate.User accountsUpdate;
|
||||
private final AccountIndexer indexer;
|
||||
private final AccountByEmailCache byEmailCache;
|
||||
private final AccountLoader.Factory infoLoader;
|
||||
@@ -87,7 +87,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
|
||||
VersionedAuthorizedKeys.Accessor authorizedKeys,
|
||||
SshKeyCache sshKeyCache,
|
||||
AccountCache accountCache,
|
||||
AccountsUpdate accountsUpdate,
|
||||
AccountsUpdate.User accountsUpdate,
|
||||
AccountIndexer indexer,
|
||||
AccountByEmailCache byEmailCache,
|
||||
AccountLoader.Factory infoLoader,
|
||||
@@ -175,7 +175,7 @@ public class CreateAccount implements RestModifyView<TopLevelResource, AccountIn
|
||||
Account a = new Account(id, TimeUtil.nowTs());
|
||||
a.setFullName(input.name);
|
||||
a.setPreferredEmail(input.email);
|
||||
accountsUpdate.insert(db, a);
|
||||
accountsUpdate.create().insert(db, a);
|
||||
|
||||
for (AccountGroup.Id groupId : groups) {
|
||||
AccountGroupMember m = new AccountGroupMember(new AccountGroupMember.Key(id, groupId));
|
||||
|
||||
@@ -35,7 +35,7 @@ import java.util.concurrent.TimeUnit;
|
||||
/** A version of the database schema. */
|
||||
public abstract class SchemaVersion {
|
||||
/** The current schema version. */
|
||||
public static final Class<Schema_145> C = Schema_145.class;
|
||||
public static final Class<Schema_146> C = Schema_146.class;
|
||||
|
||||
public static int getBinaryVersion() {
|
||||
return guessVersion(C);
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// 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.
|
||||
|
||||
package com.google.gerrit.server.schema;
|
||||
|
||||
import com.google.gerrit.reviewdb.client.Account;
|
||||
import com.google.gerrit.reviewdb.client.RefNames;
|
||||
import com.google.gerrit.reviewdb.server.ReviewDb;
|
||||
import com.google.gerrit.server.GerritPersonIdent;
|
||||
import com.google.gerrit.server.account.AccountsUpdate;
|
||||
import com.google.gerrit.server.config.AllUsersName;
|
||||
import com.google.gerrit.server.git.GitRepositoryManager;
|
||||
import com.google.gwtorm.server.OrmException;
|
||||
import com.google.inject.Inject;
|
||||
import com.google.inject.Provider;
|
||||
import java.io.IOException;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Timestamp;
|
||||
import org.eclipse.jgit.lib.CommitBuilder;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
import org.eclipse.jgit.lib.RefUpdate;
|
||||
import org.eclipse.jgit.lib.RefUpdate.Result;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevSort;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
|
||||
/**
|
||||
* Make sure that for every account a user branch exists that has an initial empty commit with the
|
||||
* registration date as commit time.
|
||||
*
|
||||
* <p>For accounts that don't have a user branch yet the user branch is created with an initial
|
||||
* empty commit that has the registration date as commit time.
|
||||
*
|
||||
* <p>For accounts that already have a user branch the user branch is rewritten and an initial empty
|
||||
* commit with the registration date as commit time is inserted (if such a commit doesn't exist
|
||||
* yet).
|
||||
*/
|
||||
public class Schema_146 extends SchemaVersion {
|
||||
private static final String CREATE_ACCOUNT_MSG = "Create Account";
|
||||
|
||||
private final GitRepositoryManager repoManager;
|
||||
private final AllUsersName allUsersName;
|
||||
private final PersonIdent serverIdent;
|
||||
|
||||
@Inject
|
||||
Schema_146(
|
||||
Provider<Schema_145> prior,
|
||||
GitRepositoryManager repoManager,
|
||||
AllUsersName allUsersName,
|
||||
@GerritPersonIdent PersonIdent serverIdent) {
|
||||
super(prior);
|
||||
this.repoManager = repoManager;
|
||||
this.allUsersName = allUsersName;
|
||||
this.serverIdent = serverIdent;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void migrateData(ReviewDb db, UpdateUI ui) throws OrmException, SQLException {
|
||||
try (Repository repo = repoManager.openRepository(allUsersName);
|
||||
RevWalk rw = new RevWalk(repo);
|
||||
ObjectInserter oi = repo.newObjectInserter()) {
|
||||
ObjectId emptyTree = emptyTree(oi);
|
||||
|
||||
for (Account account : db.accounts().all()) {
|
||||
String refName = RefNames.refsUsers(account.getId());
|
||||
Ref ref = repo.exactRef(refName);
|
||||
if (ref != null) {
|
||||
rewriteUserBranch(repo, rw, oi, emptyTree, ref, account);
|
||||
} else {
|
||||
AccountsUpdate.createUserBranch(repo, oi, serverIdent, serverIdent, account);
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new OrmException("Failed to rewrite user branches.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void rewriteUserBranch(
|
||||
Repository repo, RevWalk rw, ObjectInserter oi, ObjectId emptyTree, Ref ref, Account account)
|
||||
throws IOException {
|
||||
ObjectId current = createInitialEmptyCommit(oi, emptyTree, account.getRegisteredOn());
|
||||
|
||||
rw.reset();
|
||||
rw.sort(RevSort.TOPO);
|
||||
rw.sort(RevSort.REVERSE, true);
|
||||
rw.markStart(rw.parseCommit(ref.getObjectId()));
|
||||
|
||||
RevCommit c;
|
||||
while ((c = rw.next()) != null) {
|
||||
if (isInitialEmptyCommit(emptyTree, c)) {
|
||||
return;
|
||||
}
|
||||
|
||||
CommitBuilder cb = new CommitBuilder();
|
||||
cb.setParentId(current);
|
||||
cb.setTreeId(c.getTree());
|
||||
cb.setAuthor(c.getAuthorIdent());
|
||||
cb.setCommitter(c.getCommitterIdent());
|
||||
cb.setMessage(c.getFullMessage());
|
||||
cb.setEncoding(c.getEncoding());
|
||||
current = oi.insert(cb);
|
||||
}
|
||||
|
||||
oi.flush();
|
||||
|
||||
RefUpdate ru = repo.updateRef(ref.getName());
|
||||
ru.setExpectedOldObjectId(ref.getObjectId());
|
||||
ru.setNewObjectId(current);
|
||||
ru.setForceUpdate(true);
|
||||
ru.setRefLogIdent(serverIdent);
|
||||
ru.setRefLogMessage(getClass().getSimpleName(), true);
|
||||
Result result = ru.update();
|
||||
if (result != Result.FORCED) {
|
||||
throw new IOException(
|
||||
String.format("Failed to update ref %s: %s", ref.getName(), result.name()));
|
||||
}
|
||||
}
|
||||
|
||||
private ObjectId createInitialEmptyCommit(
|
||||
ObjectInserter oi, ObjectId emptyTree, Timestamp registrationDate) throws IOException {
|
||||
PersonIdent ident = new PersonIdent(serverIdent, registrationDate);
|
||||
|
||||
CommitBuilder cb = new CommitBuilder();
|
||||
cb.setTreeId(emptyTree);
|
||||
cb.setCommitter(ident);
|
||||
cb.setAuthor(ident);
|
||||
cb.setMessage(CREATE_ACCOUNT_MSG);
|
||||
return oi.insert(cb);
|
||||
}
|
||||
|
||||
private boolean isInitialEmptyCommit(ObjectId emptyTree, RevCommit c) {
|
||||
return c.getParentCount() == 0
|
||||
&& c.getTree().equals(emptyTree)
|
||||
&& c.getShortMessage().equals(CREATE_ACCOUNT_MSG);
|
||||
}
|
||||
|
||||
private static ObjectId emptyTree(ObjectInserter oi) throws IOException {
|
||||
return oi.insert(Constants.OBJ_TREE, new byte[] {});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user