Migrate accounts to NoteDb (part 1)

Always write account updates to both backends, ReviewDb and NoteDb.

In NoteDb accounts are represented as user branches in the All-Users
repository. Optionally a user branch can contain a 'account.config' file
that stores account properties, such as full name, preferred email,
status and the active flag. The timestamp of the first commit on a user
branch denotes the registration date. The initial commit on the user
branch may be empty (since having an 'account.config' is optional).

The 'account.config' file is a git config file that has one 'account'
section with the properties of the account:

  [account]
    active = false
    fullName = John Doe
    preferredEmail = john.doe@foo.com
    status = Overloaded with reviews

All keys are optional. This means 'account.config' may not exist on the
user branch if no properties are set.

If no value for 'active' is specified, by default the account is
considered as active.

AccountsUpdate is now sending RefUpdatedEvent's when an account is
updated. ReindexAfterRefUpdate receives the events and takes care to
evict the updated accounts from the account cache, which in turn
triggers reindex of the accounts. This is why AccountsUpdate no longer
needs to evict the updated accounts itself from the account cache. Since
AccountsUpdate doesn't reindex accounts on its own anymore the
ServerNoReindex factory can be removed.

To support a live migration on a multi-master Gerrit installation, the
migration of accounts from ReviewDb to NoteDb is done in 3 steps:
- part 1 (this change):
  * always write to both backends (ReviewDb and NoteDb)
  * always read accounts from ReviewDb
  * upgraded instances write to both backends, old instances only
    write to ReviewDb
  * after upgrading all instances (all still read from ReviewDb)
    run a batch to copy all accounts from the ReviewDb to NoteDb
- part 2 (next change):
  * bump the database schema version
  * migrate the accounts from ReviewDb to NoteDb (for single instance
    Gerrit servers)
  * config option to control whether accounts are read from ReviewDb or
    NoteDb
- part 3:
  * remove config option to control whether accounts are read from
    ReviewDb or NoteDb and always read from NoteDb
  * delete the database table

Change-Id: I2e0b13feb3465e086b49b2de2439a56696b5fba9
Signed-off-by: Edwin Kempin <ekempin@google.com>
This commit is contained in:
Edwin Kempin
2017-06-20 15:59:59 +02:00
parent 89e4ea8987
commit a80101b895
14 changed files with 548 additions and 125 deletions

View File

@@ -32,6 +32,7 @@ import static com.google.gerrit.server.group.SystemGroupBackend.ANONYMOUS_USERS;
import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS;
import static java.nio.charset.StandardCharsets.UTF_8; import static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.stream.Collectors.toSet; import static java.util.stream.Collectors.toSet;
import static org.eclipse.jgit.lib.Constants.OBJ_BLOB;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import com.google.common.collect.FluentIterable; import com.google.common.collect.FluentIterable;
@@ -74,6 +75,7 @@ import com.google.gerrit.gpg.testutil.TestKey;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.account.AccountByEmailCache; import com.google.gerrit.server.account.AccountByEmailCache;
import com.google.gerrit.server.account.AccountConfig;
import com.google.gerrit.server.account.WatchConfig; import com.google.gerrit.server.account.WatchConfig;
import com.google.gerrit.server.account.WatchConfig.NotifyType; import com.google.gerrit.server.account.WatchConfig.NotifyType;
import com.google.gerrit.server.account.externalids.ExternalId; import com.google.gerrit.server.account.externalids.ExternalId;
@@ -106,6 +108,7 @@ import org.eclipse.jgit.api.errors.TransportException;
import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository;
import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.junit.TestRepository;
import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.ObjectReader;
import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefUpdate;
import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.Repository;
@@ -114,6 +117,7 @@ import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.transport.PushCertificateIdent; import org.eclipse.jgit.transport.PushCertificateIdent;
import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.PushResult;
import org.eclipse.jgit.transport.RemoteRefUpdate; import org.eclipse.jgit.transport.RemoteRefUpdate;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.junit.After; import org.junit.After;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Before; import org.junit.Before;
@@ -220,6 +224,67 @@ public class AccountIT extends AbstractDaemonTest {
create(3); // account creation + external ID creation + adding SSH keys create(3); // account creation + external ID creation + adding SSH keys
} }
private void create(int expectedAccountReindexCalls) throws Exception {
String name = "foo";
TestAccount foo = accountCreator.create(name);
AccountInfo info = gApi.accounts().id(foo.id.get()).get();
assertThat(info.username).isEqualTo(name);
assertThat(info.name).isEqualTo(name);
accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls);
// check user branch
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo);
ObjectReader or = repo.newObjectReader()) {
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);
// Check the 'account.config' file.
try (TreeWalk tw = TreeWalk.forPath(or, AccountConfig.ACCOUNT_CONFIG, c.getTree())) {
assertThat(tw).isNotNull();
Config cfg = new Config();
cfg.fromText(new String(or.open(tw.getObjectId(0), OBJ_BLOB).getBytes(), UTF_8));
assertThat(cfg.getString(AccountConfig.ACCOUNT, null, AccountConfig.KEY_FULL_NAME))
.isEqualTo(name);
}
}
}
@Test
public void createAnonymousCoward() throws Exception {
TestAccount anonymousCoward = accountCreator.create();
accountIndexedCounter.assertReindexOf(anonymousCoward);
// check user branch
try (Repository repo = repoManager.openRepository(allUsers);
RevWalk rw = new RevWalk(repo);
ObjectReader or = repo.newObjectReader()) {
Ref ref = repo.exactRef(RefNames.refsUsers(anonymousCoward.getId()));
assertThat(ref).isNotNull();
RevCommit c = rw.parseCommit(ref.getObjectId());
long timestampDiffMs =
Math.abs(
c.getCommitTime() * 1000L
- accountCache
.get(anonymousCoward.getId())
.getAccount()
.getRegisteredOn()
.getTime());
assertThat(timestampDiffMs).isAtMost(ChangeRebuilderImpl.MAX_WINDOW_MS);
// No account properties were set, hence an 'account.config' file was not created.
try (TreeWalk tw = TreeWalk.forPath(or, AccountConfig.ACCOUNT_CONFIG, c.getTree())) {
assertThat(tw).isNull();
}
}
}
@Test @Test
public void get() throws Exception { public void get() throws Exception {
AccountInfo info = gApi.accounts().id("admin").get(); AccountInfo info = gApi.accounts().id("admin").get();
@@ -1071,26 +1136,6 @@ public class AccountIT extends AbstractDaemonTest {
assertThat(checkInfo.checkAccountsResult.problems).containsExactlyElementsIn(expectedProblems); assertThat(checkInfo.checkAccountsResult.problems).containsExactlyElementsIn(expectedProblems);
} }
public void create(int expectedAccountReindexCalls) throws Exception {
TestAccount foo = accountCreator.create("foo");
AccountInfo info = gApi.accounts().id(foo.id.get()).get();
assertThat(info.username).isEqualTo("foo");
accountIndexedCounter.assertReindexOf(foo, expectedAccountReindexCalls);
// 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);
}
}
private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) { private void assertSequenceNumbers(List<SshKeyInfo> sshKeys) {
int seq = 1; int seq = 1;
for (SshKeyInfo key : sshKeys) { for (SshKeyInfo key : sshKeys) {

View File

@@ -19,11 +19,13 @@ import static com.google.common.base.Preconditions.checkArgument;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gerrit.pgm.init.api.InitFlags; import com.google.gerrit.pgm.init.api.InitFlags;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdentProvider; import com.google.gerrit.server.GerritPersonIdentProvider;
import com.google.gerrit.server.account.Accounts; import com.google.gerrit.server.account.Accounts;
import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.config.SitePaths; import com.google.gerrit.server.config.SitePaths;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
import java.io.File; import java.io.File;
@@ -57,7 +59,15 @@ public class AccountsOnInit {
ObjectInserter oi = repo.newObjectInserter()) { ObjectInserter oi = repo.newObjectInserter()) {
PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get(); PersonIdent serverIdent = new GerritPersonIdentProvider(flags.cfg).get();
AccountsUpdate.createUserBranch( AccountsUpdate.createUserBranch(
repo, oi, serverIdent, serverIdent, account.getId(), account.getRegisteredOn()); repo,
new Project.NameKey(allUsers),
GitReferenceUpdated.DISABLED,
null,
oi,
serverIdent,
serverIdent,
account.getId(),
account.getRegisteredOn());
} }
} }
} }

View File

@@ -0,0 +1,260 @@
// 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.account;
import static com.google.common.base.Preconditions.checkState;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableList;
import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.server.git.ValidationError;
import com.google.gerrit.server.git.VersionedMetaData;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gwtorm.server.OrmDuplicateKeyException;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.PersonIdent;
import org.eclipse.jgit.revwalk.RevSort;
/**
* account.config file in the user branch in the All-Users repository that contains the properties
* of the account.
*
* <p>The 'account.config' file is a git config file that has one 'account' section with the
* properties of the account:
*
* <pre>
* [account]
* active = false
* fullName = John Doe
* preferredEmail = john.doe@foo.com
* status = Overloaded with reviews
* </pre>
*
* <p>All keys are optional. This means 'account.config' may not exist on the user branch if no
* properties are set.
*
* <p>Not setting a key and setting a key to an empty string are treated the same way and result in
* a {@code null} value.
*
* <p>If no value for 'active' is specified, by default the account is considered as active.
*
* <p>The commit date of the first commit on the user branch is used as registration date of the
* account. The first commit may be an empty commit (if no properties were set and 'account.config'
* doesn't exist).
*/
public class AccountConfig extends VersionedMetaData implements ValidationError.Sink {
public static final String ACCOUNT_CONFIG = "account.config";
public static final String ACCOUNT = "account";
public static final String KEY_ACTIVE = "active";
public static final String KEY_FULL_NAME = "fullName";
public static final String KEY_PREFERRED_EMAIL = "preferredEmail";
public static final String KEY_STATUS = "status";
@Nullable private final OutgoingEmailValidator emailValidator;
private final Account.Id accountId;
private final String ref;
private boolean isLoaded;
private Account account;
private Timestamp registeredOn;
private List<ValidationError> validationErrors;
public AccountConfig(@Nullable OutgoingEmailValidator emailValidator, Account.Id accountId) {
this.emailValidator = emailValidator;
this.accountId = accountId;
this.ref = RefNames.refsUsers(accountId);
}
@Override
protected String getRefName() {
return ref;
}
/**
* Get the loaded account.
*
* @return loaded account.
* @throws IllegalStateException if the account was not loaded yet
*/
public Account getAccount() {
checkLoaded();
return account;
}
/**
* Sets the account. This means the loaded account will be overwritten with the given account.
*
* <p>Changing the registration date of an account is not supported.
*
* @param account account that should be set
* @throws IllegalStateException if the account was not loaded yet
*/
public void setAccount(Account account) {
checkLoaded();
this.account = account;
this.registeredOn = account.getRegisteredOn();
}
/**
* Creates a new account.
*
* @return the new account
* @throws OrmDuplicateKeyException if the user branch already exists
*/
public Account getNewAccount() throws OrmDuplicateKeyException {
checkLoaded();
if (revision != null) {
throw new OrmDuplicateKeyException(String.format("account %s already exists", accountId));
}
this.registeredOn = TimeUtil.nowTs();
this.account = new Account(accountId, registeredOn);
return account;
}
@Override
protected void onLoad() throws IOException, ConfigInvalidException {
if (revision != null) {
rw.markStart(revision);
rw.sort(RevSort.REVERSE);
registeredOn = new Timestamp(rw.next().getCommitTime() * 1000L);
Config cfg = readConfig(ACCOUNT_CONFIG);
account = parse(cfg);
}
isLoaded = true;
}
private Account parse(Config cfg) {
Account account = new Account(accountId, registeredOn);
account.setActive(cfg.getBoolean(ACCOUNT, null, KEY_ACTIVE, true));
account.setFullName(get(cfg, KEY_FULL_NAME));
String preferredEmail = get(cfg, KEY_PREFERRED_EMAIL);
account.setPreferredEmail(preferredEmail);
if (emailValidator != null && !emailValidator.isValid(preferredEmail)) {
error(
new ValidationError(
ACCOUNT_CONFIG, String.format("Invalid preferred email: %s", preferredEmail)));
}
account.setStatus(get(cfg, KEY_STATUS));
return account;
}
@Override
protected boolean onSave(CommitBuilder commit) throws IOException, ConfigInvalidException {
checkLoaded();
if (revision != null) {
commit.setMessage("Update account\n");
} else if (account != null) {
commit.setMessage("Create account\n");
commit.setAuthor(new PersonIdent(commit.getAuthor(), registeredOn));
commit.setCommitter(new PersonIdent(commit.getCommitter(), registeredOn));
}
Config cfg = readConfig(ACCOUNT_CONFIG);
setActive(cfg, account.isActive());
set(cfg, KEY_FULL_NAME, account.getFullName());
set(cfg, KEY_PREFERRED_EMAIL, account.getPreferredEmail());
set(cfg, KEY_STATUS, account.getStatus());
saveConfig(ACCOUNT_CONFIG, cfg);
return true;
}
/**
* Sets/Unsets {@code account.active} in the given config.
*
* <p>{@code account.active} is set to {@code false} if the account is inactive.
*
* <p>If the account is active {@code account.active} is unset since {@code true} is the default
* if this field is missing.
*
* @param cfg the config
* @param value whether the account is active
*/
private static void setActive(Config cfg, boolean value) {
if (!value) {
cfg.setBoolean(ACCOUNT, null, KEY_ACTIVE, false);
} else {
cfg.unset(ACCOUNT, null, KEY_ACTIVE);
}
}
/**
* Sets/Unsets the given key in the given config.
*
* <p>The key unset if the value is {@code null}.
*
* @param cfg the config
* @param key the key
* @param value the value
*/
private static void set(Config cfg, String key, String value) {
if (!Strings.isNullOrEmpty(value)) {
cfg.setString(ACCOUNT, null, key, value);
} else {
cfg.unset(ACCOUNT, null, key);
}
}
/**
* Gets the given key from the given config.
*
* <p>Empty values are returned as {@code null}
*
* @param cfg the config
* @param key the key
* @return the value, {@code null} if key was not set or key was set to empty string
*/
private static String get(Config cfg, String key) {
return Strings.emptyToNull(cfg.getString(ACCOUNT, null, key));
}
private void checkLoaded() {
checkState(isLoaded, "account not loaded yet");
}
/**
* Get the validation errors, if any were discovered during load.
*
* @return list of errors; empty list if there are no errors.
*/
public List<ValidationError> getValidationErrors() {
if (validationErrors != null) {
return ImmutableList.copyOf(validationErrors);
}
return ImmutableList.of();
}
@Override
public void error(ValidationError error) {
if (validationErrors == null) {
validationErrors = new ArrayList<>(4);
}
validationErrors.add(error);
}
}

View File

@@ -18,14 +18,18 @@ import static com.google.common.base.Preconditions.checkNotNull;
import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSet;
import com.google.gerrit.common.Nullable; import com.google.gerrit.common.Nullable;
import com.google.gerrit.common.TimeUtil;
import com.google.gerrit.reviewdb.client.Account; import com.google.gerrit.reviewdb.client.Account;
import com.google.gerrit.reviewdb.client.Project;
import com.google.gerrit.reviewdb.client.RefNames; import com.google.gerrit.reviewdb.client.RefNames;
import com.google.gerrit.reviewdb.server.ReviewDb; import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.IdentifiedUser; import com.google.gerrit.server.IdentifiedUser;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gerrit.server.git.MetaDataUpdate;
import com.google.gerrit.server.index.change.ReindexAfterRefUpdate;
import com.google.gerrit.server.mail.send.OutgoingEmailValidator;
import com.google.gwtorm.server.OrmDuplicateKeyException; import com.google.gwtorm.server.OrmDuplicateKeyException;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -34,6 +38,7 @@ import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.sql.Timestamp; import java.sql.Timestamp;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.lib.CommitBuilder; import org.eclipse.jgit.lib.CommitBuilder;
import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectId;
@@ -47,8 +52,18 @@ import org.eclipse.jgit.lib.Repository;
/** /**
* Updates accounts. * Updates accounts.
* *
* <p>On updating accounts this class takes care to evict them from the account cache and thus * <p>The account updates are written to both ReviewDb and NoteDb.
* triggers reindex for them. *
* <p>In NoteDb accounts are represented as user branches in the All-Users repository. Optionally a
* user branch can contain a 'account.config' file that stores account properties, such as full
* name, preferred email, status and the active flag. The timestamp of the first commit on a user
* branch denotes the registration date. The initial commit on the user branch may be empty (since
* having an 'account.config' is optional). See {@link AccountConfig} for details of the
* 'account.config' file format.
*
* <p>On updating accounts the accounts are evicted from the account cache and thus reindexed. The
* eviction from the account cache is done by the {@link ReindexAfterRefUpdate} class which receives
* the event about updating the user branch that is triggered by this class.
*/ */
@Singleton @Singleton
public class AccountsUpdate { public class AccountsUpdate {
@@ -61,56 +76,38 @@ public class AccountsUpdate {
@Singleton @Singleton
public static class Server { public static class Server {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final AccountCache accountCache; private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
private final Provider<PersonIdent> serverIdent; private final Provider<PersonIdent> serverIdent;
private final Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory;
@Inject @Inject
public Server( public Server(
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
AccountCache accountCache, GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName, AllUsersName allUsersName,
@GerritPersonIdent Provider<PersonIdent> serverIdent) { OutgoingEmailValidator emailValidator,
@GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<MetaDataUpdate.Server> metaDataUpdateServerFactory) {
this.repoManager = repoManager; this.repoManager = repoManager;
this.accountCache = accountCache; this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.emailValidator = emailValidator;
this.serverIdent = serverIdent; this.serverIdent = serverIdent;
this.metaDataUpdateServerFactory = metaDataUpdateServerFactory;
} }
public AccountsUpdate create() { public AccountsUpdate create() {
PersonIdent i = serverIdent.get(); PersonIdent i = serverIdent.get();
return new AccountsUpdate(repoManager, accountCache, allUsersName, i, i); return new AccountsUpdate(
} repoManager,
} gitRefUpdated,
null,
/** allUsersName,
* Factory to create an AccountsUpdate instance for updating accounts by the Gerrit server. emailValidator,
* i,
* <p>Using this class will not perform reindexing for the updated accounts and they will also not () -> metaDataUpdateServerFactory.get().create(allUsersName));
* be evicted from the account cache.
*
* <p>The Gerrit server identity will be used as author and committer for all commits that update
* the accounts.
*/
@Singleton
public static class ServerNoReindex {
private final GitRepositoryManager repoManager;
private final AllUsersName allUsersName;
private final Provider<PersonIdent> serverIdent;
@Inject
public ServerNoReindex(
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, null, allUsersName, i, i);
} }
} }
@@ -123,29 +120,42 @@ public class AccountsUpdate {
@Singleton @Singleton
public static class User { public static class User {
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
private final AccountCache accountCache; private final GitReferenceUpdated gitRefUpdated;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
private final Provider<PersonIdent> serverIdent; private final Provider<PersonIdent> serverIdent;
private final Provider<IdentifiedUser> identifiedUser; private final Provider<IdentifiedUser> identifiedUser;
private final Provider<MetaDataUpdate.User> metaDataUpdateUserFactory;
@Inject @Inject
public User( public User(
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
AccountCache accountCache, GitReferenceUpdated gitRefUpdated,
AllUsersName allUsersName, AllUsersName allUsersName,
OutgoingEmailValidator emailValidator,
@GerritPersonIdent Provider<PersonIdent> serverIdent, @GerritPersonIdent Provider<PersonIdent> serverIdent,
Provider<IdentifiedUser> identifiedUser) { Provider<IdentifiedUser> identifiedUser,
Provider<MetaDataUpdate.User> metaDataUpdateUserFactory) {
this.repoManager = repoManager; this.repoManager = repoManager;
this.accountCache = accountCache; this.gitRefUpdated = gitRefUpdated;
this.allUsersName = allUsersName; this.allUsersName = allUsersName;
this.serverIdent = serverIdent; this.serverIdent = serverIdent;
this.emailValidator = emailValidator;
this.identifiedUser = identifiedUser; this.identifiedUser = identifiedUser;
this.metaDataUpdateUserFactory = metaDataUpdateUserFactory;
} }
public AccountsUpdate create() { public AccountsUpdate create() {
IdentifiedUser user = identifiedUser.get();
PersonIdent i = serverIdent.get(); PersonIdent i = serverIdent.get();
return new AccountsUpdate( return new AccountsUpdate(
repoManager, accountCache, allUsersName, createPersonIdent(i, identifiedUser.get()), i); repoManager,
gitRefUpdated,
user,
allUsersName,
emailValidator,
createPersonIdent(i, user),
() -> metaDataUpdateUserFactory.get().create(allUsersName));
} }
private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) { private PersonIdent createPersonIdent(PersonIdent ident, IdentifiedUser user) {
@@ -154,22 +164,28 @@ public class AccountsUpdate {
} }
private final GitRepositoryManager repoManager; private final GitRepositoryManager repoManager;
@Nullable private final AccountCache accountCache; private final GitReferenceUpdated gitRefUpdated;
@Nullable private final IdentifiedUser currentUser;
private final AllUsersName allUsersName; private final AllUsersName allUsersName;
private final OutgoingEmailValidator emailValidator;
private final PersonIdent committerIdent; private final PersonIdent committerIdent;
private final PersonIdent authorIdent; private final MetaDataUpdateFactory metaDataUpdateFactory;
private AccountsUpdate( private AccountsUpdate(
GitRepositoryManager repoManager, GitRepositoryManager repoManager,
@Nullable AccountCache accountCache, GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser currentUser,
AllUsersName allUsersName, AllUsersName allUsersName,
OutgoingEmailValidator emailValidator,
PersonIdent committerIdent, PersonIdent committerIdent,
PersonIdent authorIdent) { MetaDataUpdateFactory metaDataUpdateFactory) {
this.repoManager = checkNotNull(repoManager, "repoManager"); this.repoManager = checkNotNull(repoManager, "repoManager");
this.accountCache = accountCache; this.gitRefUpdated = checkNotNull(gitRefUpdated, "gitRefUpdated");
this.currentUser = currentUser;
this.allUsersName = checkNotNull(allUsersName, "allUsersName"); this.allUsersName = checkNotNull(allUsersName, "allUsersName");
this.emailValidator = checkNotNull(emailValidator, "emailValidator");
this.committerIdent = checkNotNull(committerIdent, "committerIdent"); this.committerIdent = checkNotNull(committerIdent, "committerIdent");
this.authorIdent = checkNotNull(authorIdent, "authorIdent"); this.metaDataUpdateFactory = checkNotNull(metaDataUpdateFactory, "metaDataUpdateFactory");
} }
/** /**
@@ -179,79 +195,116 @@ public class AccountsUpdate {
* @param accountId ID of the new account * @param accountId ID of the new account
* @param init consumer to populate the new account * @param init consumer to populate the new account
* @return the newly created account * @return the newly created account
* @throws OrmException if updating the database fails
* @throws OrmDuplicateKeyException if the account already exists * @throws OrmDuplicateKeyException if the account already exists
* @throws IOException if updating the user branch fails * @throws IOException if updating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/ */
public Account insert(ReviewDb db, Account.Id accountId, Consumer<Account> init) public Account insert(ReviewDb db, Account.Id accountId, Consumer<Account> init)
throws OrmException, IOException { throws OrmException, IOException, ConfigInvalidException {
Account account = new Account(accountId, TimeUtil.nowTs()); AccountConfig accountConfig = read(accountId);
Account account = accountConfig.getNewAccount();
init.accept(account); init.accept(account);
// Create in ReviewDb
db.accounts().insert(ImmutableSet.of(account)); db.accounts().insert(ImmutableSet.of(account));
createUserBranch(account);
evictAccount(accountId); // Create in NoteDb
commitNew(accountConfig);
return account; return account;
} }
/** Updates the account. */ /**
public void update(ReviewDb db, Account account) throws OrmException, IOException { * Updates the account.
*
* <p>Changing the registration date of an account is not supported.
*
* @param db ReviewDb
* @param account the account
* @throws OrmException if updating the database fails
* @throws IOException if updating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/
public void update(ReviewDb db, Account account)
throws OrmException, IOException, ConfigInvalidException {
// Update in ReviewDb
db.accounts().update(ImmutableSet.of(account)); db.accounts().update(ImmutableSet.of(account));
evictAccount(account.getId());
// Update in NoteDb
AccountConfig accountConfig = read(account.getId());
accountConfig.setAccount(account);
commit(accountConfig);
} }
/** /**
* Gets the account and updates it atomically. * Gets the account and updates it atomically.
* *
* <p>Changing the registration date of an account is not supported.
*
* @param db ReviewDb * @param db ReviewDb
* @param accountId ID of the account * @param accountId ID of the account
* @param consumer consumer to update the account, only invoked if the account exists * @param consumer consumer to update the account, only invoked if the account exists
* @return the updated account, {@code null} if the account doesn't exist * @return the updated account, {@code null} if the account doesn't exist
* @throws OrmException if updating the account fails * @throws OrmException if updating the database fails
* @throws IOException if updating the user branch fails
* @throws ConfigInvalidException if any of the account fields has an invalid value
*/ */
public Account atomicUpdate(ReviewDb db, Account.Id accountId, Consumer<Account> consumer) public Account atomicUpdate(ReviewDb db, Account.Id accountId, Consumer<Account> consumer)
throws OrmException, IOException { throws OrmException, IOException, ConfigInvalidException {
Account account = // Update in ReviewDb
db.accounts() db.accounts()
.atomicUpdate( .atomicUpdate(
accountId, accountId,
a -> { a -> {
consumer.accept(a); consumer.accept(a);
return a; return a;
}); });
evictAccount(accountId);
// Update in NoteDb
AccountConfig accountConfig = read(accountId);
Account account = accountConfig.getAccount();
consumer.accept(account);
commit(accountConfig);
return account; return account;
} }
/** Deletes the account. */ /**
* Deletes the account.
*
* @param db ReviewDb
* @param account the account that should be deleted
* @throws OrmException if updating the database fails
* @throws IOException if updating the user branch fails
*/
public void delete(ReviewDb db, Account account) throws OrmException, IOException { public void delete(ReviewDb db, Account account) throws OrmException, IOException {
// Delete in ReviewDb
db.accounts().delete(ImmutableSet.of(account)); db.accounts().delete(ImmutableSet.of(account));
// Delete in NoteDb
deleteUserBranch(account.getId()); deleteUserBranch(account.getId());
evictAccount(account.getId());
} }
/** Deletes the account. */ /**
* Deletes the account.
*
* @param db ReviewDb
* @param accountId the ID of the account that should be deleted
* @throws OrmException if updating the database fails
* @throws IOException if updating the user branch fails
*/
public void deleteByKey(ReviewDb db, Account.Id accountId) throws OrmException, IOException { public void deleteByKey(ReviewDb db, Account.Id accountId) throws OrmException, IOException {
// Delete in ReviewDb
db.accounts().deleteKeys(ImmutableSet.of(accountId)); db.accounts().deleteKeys(ImmutableSet.of(accountId));
deleteUserBranch(accountId);
evictAccount(accountId);
}
private void createUserBranch(Account account) throws IOException { // Delete in NoteDb
try (Repository repo = repoManager.openRepository(allUsersName); deleteUserBranch(accountId);
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.getId(), account.getRegisteredOn());
}
} }
public static void createUserBranch( public static void createUserBranch(
Repository repo, Repository repo,
Project.NameKey project,
GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser user,
ObjectInserter oi, ObjectInserter oi,
PersonIdent committerIdent, PersonIdent committerIdent,
PersonIdent authorIdent, PersonIdent authorIdent,
@@ -271,6 +324,7 @@ public class AccountsUpdate {
if (result != Result.NEW) { if (result != Result.NEW) {
throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name())); throw new IOException(String.format("Failed to update ref %s: %s", refName, result.name()));
} }
gitRefUpdated.fire(project, ru, user != null ? user.getAccount() : null);
} }
private static ObjectId createInitialEmptyCommit( private static ObjectId createInitialEmptyCommit(
@@ -295,12 +349,18 @@ public class AccountsUpdate {
private void deleteUserBranch(Account.Id accountId) throws IOException { private void deleteUserBranch(Account.Id accountId) throws IOException {
try (Repository repo = repoManager.openRepository(allUsersName)) { try (Repository repo = repoManager.openRepository(allUsersName)) {
deleteUserBranch(repo, committerIdent, accountId); deleteUserBranch(repo, allUsersName, gitRefUpdated, currentUser, committerIdent, accountId);
} }
} }
public static void deleteUserBranch( public static void deleteUserBranch(
Repository repo, PersonIdent refLogIdent, Account.Id accountId) throws IOException { Repository repo,
Project.NameKey project,
GitReferenceUpdated gitRefUpdated,
@Nullable IdentifiedUser user,
PersonIdent refLogIdent,
Account.Id accountId)
throws IOException {
String refName = RefNames.refsUsers(accountId); String refName = RefNames.refsUsers(accountId);
Ref ref = repo.exactRef(refName); Ref ref = repo.exactRef(refName);
if (ref == null) { if (ref == null) {
@@ -317,11 +377,37 @@ public class AccountsUpdate {
if (result != Result.FORCED) { if (result != Result.FORCED) {
throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name())); throw new IOException(String.format("Failed to delete ref %s: %s", refName, result.name()));
} }
gitRefUpdated.fire(project, ru, user != null ? user.getAccount() : null);
} }
private void evictAccount(Account.Id accountId) throws IOException { private AccountConfig read(Account.Id accountId) throws IOException, ConfigInvalidException {
if (accountCache != null) { try (Repository repo = repoManager.openRepository(allUsersName)) {
accountCache.evict(accountId); AccountConfig accountConfig = new AccountConfig(emailValidator, accountId);
accountConfig.load(repo);
return accountConfig;
} }
} }
private void commitNew(AccountConfig accountConfig) throws IOException {
// When creating a new account we must allow empty commits so that the user branch gets created
// with an empty commit when no account properties are set and hence no 'account.config' file
// will be created.
commit(accountConfig, true);
}
private void commit(AccountConfig accountConfig) throws IOException {
commit(accountConfig, false);
}
private void commit(AccountConfig accountConfig, boolean allowEmptyCommit) throws IOException {
try (MetaDataUpdate md = metaDataUpdateFactory.create()) {
md.setAllowEmpty(allowEmptyCommit);
accountConfig.commit(md);
}
}
@FunctionalInterface
private static interface MetaDataUpdateFactory {
MetaDataUpdate create() throws IOException;
}
} }

View File

@@ -31,6 +31,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.errors.ConfigInvalidException;
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT) @RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton @Singleton
@@ -53,7 +54,7 @@ public class DeleteActive implements RestModifyView<AccountResource, Input> {
@Override @Override
public Response<?> apply(AccountResource rsrc, Input input) public Response<?> apply(AccountResource rsrc, Input input)
throws RestApiException, OrmException, IOException { throws RestApiException, OrmException, IOException, ConfigInvalidException {
if (self.get() == rsrc.getUser()) { if (self.get() == rsrc.getUser()) {
throw new ResourceConflictException("cannot deactivate own account"); throw new ResourceConflictException("cannot deactivate own account");
} }

View File

@@ -28,6 +28,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.errors.ConfigInvalidException;
@RequiresCapability(GlobalCapability.MODIFY_ACCOUNT) @RequiresCapability(GlobalCapability.MODIFY_ACCOUNT)
@Singleton @Singleton
@@ -45,7 +46,7 @@ public class PutActive implements RestModifyView<AccountResource, Input> {
@Override @Override
public Response<String> apply(AccountResource rsrc, Input input) public Response<String> apply(AccountResource rsrc, Input input)
throws ResourceNotFoundException, OrmException, IOException { throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException {
AtomicBoolean alreadyActive = new AtomicBoolean(false); AtomicBoolean alreadyActive = new AtomicBoolean(false);
Account account = Account account =
accountsUpdate accountsUpdate

View File

@@ -35,6 +35,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton @Singleton
public class PutName implements RestModifyView<AccountResource, Input> { public class PutName implements RestModifyView<AccountResource, Input> {
@@ -65,7 +66,7 @@ public class PutName implements RestModifyView<AccountResource, Input> {
@Override @Override
public Response<String> apply(AccountResource rsrc, Input input) public Response<String> apply(AccountResource rsrc, Input input)
throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException, throws AuthException, MethodNotAllowedException, ResourceNotFoundException, OrmException,
IOException, PermissionBackendException { IOException, PermissionBackendException, ConfigInvalidException {
if (self.get() != rsrc.getUser()) { if (self.get() != rsrc.getUser()) {
permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT); permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
} }
@@ -73,7 +74,8 @@ public class PutName implements RestModifyView<AccountResource, Input> {
} }
public Response<String> apply(IdentifiedUser user, Input input) public Response<String> apply(IdentifiedUser user, Input input)
throws MethodNotAllowedException, ResourceNotFoundException, OrmException, IOException { throws MethodNotAllowedException, ResourceNotFoundException, OrmException, IOException,
ConfigInvalidException {
if (input == null) { if (input == null) {
input = new Input(); input = new Input();
} }

View File

@@ -32,6 +32,7 @@ import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean; import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton @Singleton
public class PutPreferred implements RestModifyView<AccountResource.Email, Input> { public class PutPreferred implements RestModifyView<AccountResource.Email, Input> {
@@ -57,7 +58,7 @@ public class PutPreferred implements RestModifyView<AccountResource.Email, Input
@Override @Override
public Response<String> apply(AccountResource.Email rsrc, Input input) public Response<String> apply(AccountResource.Email rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException, IOException, throws AuthException, ResourceNotFoundException, OrmException, IOException,
PermissionBackendException { PermissionBackendException, ConfigInvalidException {
if (self.get() != rsrc.getUser()) { if (self.get() != rsrc.getUser()) {
permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT); permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
} }
@@ -65,7 +66,7 @@ public class PutPreferred implements RestModifyView<AccountResource.Email, Input
} }
public Response<String> apply(IdentifiedUser user, String email) public Response<String> apply(IdentifiedUser user, String email)
throws ResourceNotFoundException, OrmException, IOException { throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException {
AtomicBoolean alreadyPreferred = new AtomicBoolean(false); AtomicBoolean alreadyPreferred = new AtomicBoolean(false);
Account account = Account account =
accountsUpdate accountsUpdate

View File

@@ -33,6 +33,7 @@ import com.google.inject.Inject;
import com.google.inject.Provider; import com.google.inject.Provider;
import com.google.inject.Singleton; import com.google.inject.Singleton;
import java.io.IOException; import java.io.IOException;
import org.eclipse.jgit.errors.ConfigInvalidException;
@Singleton @Singleton
public class PutStatus implements RestModifyView<AccountResource, Input> { public class PutStatus implements RestModifyView<AccountResource, Input> {
@@ -66,7 +67,7 @@ public class PutStatus implements RestModifyView<AccountResource, Input> {
@Override @Override
public Response<String> apply(AccountResource rsrc, Input input) public Response<String> apply(AccountResource rsrc, Input input)
throws AuthException, ResourceNotFoundException, OrmException, IOException, throws AuthException, ResourceNotFoundException, OrmException, IOException,
PermissionBackendException { PermissionBackendException, ConfigInvalidException {
if (self.get() != rsrc.getUser()) { if (self.get() != rsrc.getUser()) {
permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT); permissionBackend.user(self).check(GlobalPermission.MODIFY_ACCOUNT);
} }
@@ -74,7 +75,7 @@ public class PutStatus implements RestModifyView<AccountResource, Input> {
} }
public Response<String> apply(IdentifiedUser user, Input input) public Response<String> apply(IdentifiedUser user, Input input)
throws ResourceNotFoundException, OrmException, IOException { throws ResourceNotFoundException, OrmException, IOException, ConfigInvalidException {
if (input == null) { if (input == null) {
input = new Input(); input = new Input();
} }

View File

@@ -155,6 +155,7 @@ import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future; import java.util.concurrent.Future;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
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.lib.Constants; import org.eclipse.jgit.lib.Constants;
@@ -2789,7 +2790,7 @@ public class ReceiveCommits {
accountsUpdate.create().update(db, a); accountsUpdate.create().update(db, a);
user.getAccount().setFullName(a.getFullName()); user.getAccount().setFullName(a.getFullName());
} }
} catch (OrmException e) { } catch (OrmException | IOException | ConfigInvalidException e) {
logWarn("Cannot default full_name", e); logWarn("Cannot default full_name", e);
} finally { } finally {
defaultName = false; defaultName = false;

View File

@@ -75,6 +75,7 @@ public abstract class VersionedMetaData {
} }
protected RevCommit revision; protected RevCommit revision;
protected RevWalk rw;
protected ObjectReader reader; protected ObjectReader reader;
protected ObjectInserter inserter; protected ObjectInserter inserter;
protected DirCache newTree; protected DirCache newTree;
@@ -153,11 +154,13 @@ public abstract class VersionedMetaData {
* @throws ConfigInvalidException * @throws ConfigInvalidException
*/ */
public void load(RevWalk walk, ObjectId id) throws IOException, ConfigInvalidException { public void load(RevWalk walk, ObjectId id) throws IOException, ConfigInvalidException {
this.rw = walk;
this.reader = walk.getObjectReader(); this.reader = walk.getObjectReader();
try { try {
revision = id != null ? walk.parseCommit(id) : null; revision = id != null ? walk.parseCommit(id) : null;
onLoad(); onLoad();
} finally { } finally {
walk = null;
reader = null; reader = null;
} }
} }

View File

@@ -20,6 +20,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -88,7 +89,15 @@ public class Schema_146 extends SchemaVersion {
rewriteUserBranch(repo, rw, oi, emptyTree, ref, e.getValue()); rewriteUserBranch(repo, rw, oi, emptyTree, ref, e.getValue());
} else { } else {
AccountsUpdate.createUserBranch( AccountsUpdate.createUserBranch(
repo, oi, serverIdent, serverIdent, e.getKey(), e.getValue()); repo,
allUsersName,
GitReferenceUpdated.DISABLED,
null,
oi,
serverIdent,
serverIdent,
e.getKey(),
e.getValue());
} }
} }
} catch (IOException e) { } catch (IOException e) {

View File

@@ -22,6 +22,7 @@ import com.google.gerrit.reviewdb.server.ReviewDb;
import com.google.gerrit.server.GerritPersonIdent; import com.google.gerrit.server.GerritPersonIdent;
import com.google.gerrit.server.account.AccountsUpdate; import com.google.gerrit.server.account.AccountsUpdate;
import com.google.gerrit.server.config.AllUsersName; import com.google.gerrit.server.config.AllUsersName;
import com.google.gerrit.server.extensions.events.GitReferenceUpdated;
import com.google.gerrit.server.git.GitRepositoryManager; import com.google.gerrit.server.git.GitRepositoryManager;
import com.google.gwtorm.server.OrmException; import com.google.gwtorm.server.OrmException;
import com.google.inject.Inject; import com.google.inject.Inject;
@@ -68,7 +69,8 @@ public class Schema_147 extends SchemaVersion {
.collect(toSet()); .collect(toSet());
accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb); accountIdsFromUserBranches.removeAll(accountIdsFromReviewDb);
for (Account.Id accountId : accountIdsFromUserBranches) { for (Account.Id accountId : accountIdsFromUserBranches) {
AccountsUpdate.deleteUserBranch(repo, serverIdent, accountId); AccountsUpdate.deleteUserBranch(
repo, allUsersName, GitReferenceUpdated.DISABLED, null, serverIdent, accountId);
} }
} catch (IOException e) { } catch (IOException e) {
throw new OrmException("Failed to delete user branches for non-existing accounts.", e); throw new OrmException("Failed to delete user branches for non-existing accounts.", e);

View File

@@ -292,7 +292,8 @@ final class SetAccountCommand extends SshCommand {
} }
private void putPreferred(String email) private void putPreferred(String email)
throws RestApiException, OrmException, IOException, PermissionBackendException { throws RestApiException, OrmException, IOException, PermissionBackendException,
ConfigInvalidException {
for (EmailInfo e : getEmails.apply(rsrc)) { for (EmailInfo e : getEmails.apply(rsrc)) {
if (e.email.equals(email)) { if (e.email.equals(email)) {
putPreferred.apply(new AccountResource.Email(user, email), null); putPreferred.apply(new AccountResource.Email(user, email), null);