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:
Edwin Kempin
2017-04-06 15:09:38 +02:00
parent a2b6bd9648
commit c9c54da9e1
9 changed files with 441 additions and 34 deletions

View File

@@ -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) {

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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"));

View File

@@ -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())) {

View File

@@ -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[] {});
}
}

View File

@@ -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));

View File

@@ -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);

View File

@@ -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[] {});
}
}