Merge branch 'stable-3.4' * stable-3.4: Manage removal of refs Use correct placeholders when logging in RevisionReader Honour the fetch ref-spec in replication.config Log the incoming git refs events for replication Fix pull-replication after the removal of Log4J from Gerrit Allow replication of refs that point to non-commit objects Also fix the DeleteRefCommand and associated test to make it compatible with Gerrit stable-3.5 and master branches. Change-Id: I8d007c558fc7ebe8dbf1f8b390aad45117271270
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java index a10a658..5ab744c 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/FetchOne.java
@@ -28,6 +28,7 @@ import com.google.gerrit.server.git.ProjectRunnable; import com.google.gerrit.server.git.WorkQueue.CanceledWhileRunning; import com.google.gerrit.server.ioutil.HexFormat; +import com.google.gerrit.server.logging.TraceContext; import com.google.gerrit.server.util.IdGenerator; import com.google.inject.Inject; import com.google.inject.assistedinject.Assisted; @@ -39,6 +40,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.Set; import java.util.concurrent.Callable; import java.util.concurrent.atomic.AtomicBoolean; @@ -53,7 +55,6 @@ import org.eclipse.jgit.transport.RefSpec; import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; -import org.slf4j.MDC; /** * A pull from remote operation started by command-line. @@ -64,7 +65,7 @@ public class FetchOne implements ProjectRunnable, CanceledWhileRunning { private final ReplicationStateListener stateLog; static final String ALL_REFS = "..all.."; - static final String ID_MDC_KEY = "fetchOneId"; + static final String ID_KEY = "fetchOneId"; interface Factory { FetchOne create(Project.NameKey d, URIish u); @@ -269,11 +270,16 @@ } private void runFetchOperation() { + try (TraceContext ctx = TraceContext.open().addTag(ID_KEY, HexFormat.fromInt(id))) { + doRunFetchOperation(); + } + } + + private void doRunFetchOperation() { // Lock the queue, and remove ourselves, so we can't be modified once // we start replication (instead a new instance, with the same URI, is // created and scheduled for a future point in time.) // - MDC.put(ID_MDC_KEY, HexFormat.fromInt(id)); if (!pool.requestRunway(this)) { if (!canceled) { repLog.info( @@ -365,10 +371,25 @@ } private List<RefSpec> getFetchRefSpecs() { + List<RefSpec> configRefSpecs = config.getFetchRefSpecs(); if (delta.isEmpty()) { - return config.getFetchRefSpecs(); + return configRefSpecs; } - return delta.stream().map(ref -> new RefSpec(ref + ":" + ref)).collect(Collectors.toList()); + + return delta.stream() + .map(ref -> refToFetchRefSpec(ref, configRefSpecs)) + .filter(Optional::isPresent) + .map(Optional::get) + .collect(Collectors.toList()); + } + + private Optional<RefSpec> refToFetchRefSpec(String ref, List<RefSpec> configRefSpecs) { + for (RefSpec refSpec : configRefSpecs) { + if (refSpec.matchSource(ref)) { + return Optional.of(refSpec.expandFromSource(ref)); + } + } + return Optional.empty(); } private void updateStates(List<RefUpdateState> refUpdates) throws IOException {
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationLogFile.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationLogFile.java index 299648d..932103b 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationLogFile.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationLogFile.java
@@ -28,6 +28,6 @@ systemLog, serverInfo, PullReplicationLogger.PULL_REPLICATION_LOG_NAME, - new PatternLayout("[%d] [%X{" + FetchOne.ID_MDC_KEY + "}] %m%n")); + new PatternLayout("[%d] %m%n")); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java index 61f2a64..7449263 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueue.java
@@ -31,7 +31,6 @@ import com.googlesource.gerrit.plugins.replication.pull.FetchResultProcessing.GitUpdateProcessing; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData; import com.googlesource.gerrit.plugins.replication.pull.api.exception.MissingParentObjectException; -import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException; import com.googlesource.gerrit.plugins.replication.pull.client.FetchRestApiClient; import com.googlesource.gerrit.plugins.replication.pull.client.HttpResult; import com.googlesource.gerrit.plugins.replication.pull.filter.ExcludedRefsFilter; @@ -134,7 +133,18 @@ @Override public void onGitReferenceUpdated(GitReferenceUpdatedListener.Event event) { if (isRefToBeReplicated(event.getRefName())) { - fire(event.getProjectName(), ObjectId.fromString(event.getNewObjectId()), event.getRefName()); + repLog.info( + "Ref event received: {} on project {}:{} - {} => {}", + refUpdateType(event), + event.getProjectName(), + event.getRefName(), + event.getOldObjectId(), + event.getNewObjectId()); + fire( + event.getProjectName(), + ObjectId.fromString(event.getNewObjectId()), + event.getRefName(), + event.isDelete()); } } @@ -148,30 +158,47 @@ source.getApis().forEach(apiUrl -> source.scheduleDeleteProject(apiUrl, project))); } + private static String refUpdateType(GitReferenceUpdatedListener.Event event) { + String forcedPrefix = event.isNonFastForward() ? "FORCED " : " "; + if (event.isCreate()) { + return forcedPrefix + "CREATE"; + } else if (event.isDelete()) { + return forcedPrefix + "DELETE"; + } else { + return forcedPrefix + "UPDATE"; + } + } + private Boolean isRefToBeReplicated(String refName) { return !refsFilter.match(refName); } - private void fire(String projectName, ObjectId objectId, String refName) { + private void fire(String projectName, ObjectId objectId, String refName, boolean isDelete) { ReplicationState state = new ReplicationState(new GitUpdateProcessing(dispatcher.get())); - fire(Project.nameKey(projectName), objectId, refName, state); + fire(Project.nameKey(projectName), objectId, refName, isDelete, state); state.markAllFetchTasksScheduled(); } private void fire( - Project.NameKey project, ObjectId objectId, String refName, ReplicationState state) { + Project.NameKey project, + ObjectId objectId, + String refName, + boolean isDelete, + ReplicationState state) { if (!running) { stateLog.warn( "Replication plugin did not finish startup before event, event replication is postponed", state); - beforeStartupEventsQueue.add(ReferenceUpdatedEvent.create(project.get(), refName, objectId)); + beforeStartupEventsQueue.add( + ReferenceUpdatedEvent.create(project.get(), refName, objectId, isDelete)); return; } ForkJoinPool fetchCallsPool = null; try { fetchCallsPool = new ForkJoinPool(sources.get().getAll().size()); - final Consumer<Source> callFunction = callFunction(project, refName, state); + final Consumer<Source> callFunction = + callFunction(project, objectId, refName, isDelete, state); fetchCallsPool .submit(() -> sources.get().getAll().parallelStream().forEach(callFunction)) .get(fetchCallsTimeout, TimeUnit.MILLISECONDS); @@ -189,8 +216,13 @@ } } - private Consumer<Source> callFunction(NameKey project, String refName, ReplicationState state) { - CallFunction call = getCallFunction(project, refName, state); + private Consumer<Source> callFunction( + NameKey project, + ObjectId objectId, + String refName, + boolean isDelete, + ReplicationState state) { + CallFunction call = getCallFunction(project, objectId, refName, isDelete, state); return (source) -> { try { @@ -201,13 +233,23 @@ }; } - private CallFunction getCallFunction(NameKey project, String refName, ReplicationState state) { + private CallFunction getCallFunction( + NameKey project, + ObjectId objectId, + String refName, + boolean isDelete, + ReplicationState state) { + if (isDelete) { + return ((source) -> callSendObject(source, project, refName, isDelete, null, state)); + } + try { - Optional<RevisionData> revisionData = revisionReader.read(project, refName); + Optional<RevisionData> revisionData = revisionReader.read(project, objectId, refName); if (revisionData.isPresent()) { - return ((source) -> callSendObject(source, project, refName, revisionData.get(), state)); + return ((source) -> + callSendObject(source, project, refName, isDelete, revisionData.get(), state)); } - } catch (InvalidObjectIdException | IOException | RefUpdateException e) { + } catch (InvalidObjectIdException | IOException e) { stateLog.error( String.format( "Exception during reading ref: %s, project:%s, message: %s", @@ -223,6 +265,7 @@ Source source, Project.NameKey project, String refName, + boolean isDelete, RevisionData revision, ReplicationState state) throws MissingParentObjectException { @@ -232,7 +275,7 @@ URIish uri = new URIish(apiUrl); FetchRestApiClient fetchClient = fetchClientFactory.create(source); - HttpResult result = fetchClient.callSendObject(project, refName, revision, uri); + HttpResult result = fetchClient.callSendObject(project, refName, isDelete, revision, uri); if (isProjectMissing(result, project) && source.isCreateMissingRepositories()) { result = initProject(project, uri, fetchClient, result); } @@ -319,7 +362,7 @@ String eventKey = String.format("%s:%s", event.projectName(), event.refName()); if (!eventsReplayed.contains(eventKey)) { repLog.info("Firing pending task {}", event); - fire(event.projectName(), event.objectId(), event.refName()); + fire(event.projectName(), event.objectId(), event.refName(), event.isDelete()); eventsReplayed.add(eventKey); } } @@ -339,8 +382,10 @@ @AutoValue abstract static class ReferenceUpdatedEvent { - static ReferenceUpdatedEvent create(String projectName, String refName, ObjectId objectId) { - return new AutoValue_ReplicationQueue_ReferenceUpdatedEvent(projectName, refName, objectId); + static ReferenceUpdatedEvent create( + String projectName, String refName, ObjectId objectId, boolean isDelete) { + return new AutoValue_ReplicationQueue_ReferenceUpdatedEvent( + projectName, refName, objectId, isDelete); } public abstract String projectName(); @@ -348,6 +393,8 @@ public abstract String refName(); public abstract ObjectId objectId(); + + public abstract boolean isDelete(); } @FunctionalInterface
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java index 5718249..468c5fc 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReader.java
@@ -23,7 +23,6 @@ import com.googlesource.gerrit.plugins.replication.ReplicationConfig; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData; -import com.googlesource.gerrit.plugins.replication.pull.api.exception.RefUpdateException; import java.io.IOException; import java.util.List; import java.util.Optional; @@ -34,9 +33,9 @@ import org.eclipse.jgit.errors.LargeObjectException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.RepositoryNotFoundException; +import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; -import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.RevTree; @@ -57,22 +56,25 @@ .getLong("replication", CONFIG_MAX_API_PAYLOAD_SIZE, DEFAULT_MAX_PAYLOAD_SIZE_IN_BYTES); } - public Optional<RevisionData> read(Project.NameKey project, String refName) + public Optional<RevisionData> read(Project.NameKey project, ObjectId objectId, String refName) throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, - RepositoryNotFoundException, RefUpdateException, IOException { + RepositoryNotFoundException, IOException { try (Repository git = gitRepositoryManager.openRepository(project)) { - Ref head = git.exactRef(refName); - if (head == null) { - throw new RefUpdateException( - String.format("Cannot find ref %s in project %s", refName, project.get())); - } - Long totalRefSize = 0l; - ObjectLoader commitLoader = git.open(head.getObjectId()); + ObjectLoader commitLoader = git.open(objectId); totalRefSize += commitLoader.getSize(); verifySize(totalRefSize, commitLoader); + if (commitLoader.getType() != Constants.OBJ_COMMIT) { + repLog.trace( + "Ref {} for project {} points to an object type {}", + refName, + project, + commitLoader.getType()); + return Optional.empty(); + } + RevCommit commit = RevCommit.parse(commitLoader.getCachedBytes()); RevisionObjectData commitRev = new RevisionObjectData(commit.getType(), commitLoader.getCachedBytes()); @@ -99,8 +101,10 @@ return Optional.of(new RevisionData(commitRev, treeRev, blobs)); } catch (LargeObjectException e) { repLog.trace( - "Ref %s size for project %s is greater than configured '%s'", - refName, project, CONFIG_MAX_API_PAYLOAD_SIZE); + "Ref {} size for project {} is greater than configured '{}'", + refName, + project, + CONFIG_MAX_API_PAYLOAD_SIZE); return Optional.empty(); } }
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java index f029834..5132f41 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectAction.java
@@ -33,11 +33,16 @@ public class ApplyObjectAction implements RestModifyView<ProjectResource, RevisionInput> { private final ApplyObjectCommand command; + private final DeleteRefCommand deleteRefCommand; private final FetchPreconditions preConditions; @Inject - public ApplyObjectAction(ApplyObjectCommand command, FetchPreconditions preConditions) { + public ApplyObjectAction( + ApplyObjectCommand command, + DeleteRefCommand deleteRefCommand, + FetchPreconditions preConditions) { this.command = command; + this.deleteRefCommand = deleteRefCommand; this.preConditions = preConditions; } @@ -56,7 +61,8 @@ } if (Objects.isNull(input.getRevisionData())) { - throw new BadRequestException("Ref-update revision data cannot be null or empty"); + deleteRefCommand.deleteRef(resource.getNameKey(), input.getRefName(), input.getLabel()); + return Response.created(input); } if (Objects.isNull(input.getRevisionData().getCommitObject())
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java new file mode 100644 index 0000000..8e08977 --- /dev/null +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommand.java
@@ -0,0 +1,128 @@ +// Copyright (C) 2022 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.googlesource.gerrit.plugins.replication.pull.api; + +import static com.googlesource.gerrit.plugins.replication.pull.PullReplicationLogger.repLog; + +import com.google.common.flogger.FluentLogger; +import com.google.gerrit.entities.Project; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.extensions.restapi.ResourceConflictException; +import com.google.gerrit.extensions.restapi.ResourceNotFoundException; +import com.google.gerrit.extensions.restapi.RestApiException; +import com.google.gerrit.server.events.EventDispatcher; +import com.google.gerrit.server.permissions.PermissionBackendException; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.gerrit.server.restapi.project.DeleteRef; +import com.google.inject.Inject; +import com.googlesource.gerrit.plugins.replication.pull.Context; +import com.googlesource.gerrit.plugins.replication.pull.FetchRefReplicatedEvent; +import com.googlesource.gerrit.plugins.replication.pull.PullReplicationStateLogger; +import com.googlesource.gerrit.plugins.replication.pull.ReplicationState; +import com.googlesource.gerrit.plugins.replication.pull.Source; +import com.googlesource.gerrit.plugins.replication.pull.SourcesCollection; +import java.io.IOException; +import java.util.Optional; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.transport.URIish; + +public class DeleteRefCommand { + private static final FluentLogger logger = FluentLogger.forEnclosingClass(); + + private final PullReplicationStateLogger fetchStateLog; + private final DeleteRef deleteRef; + private final DynamicItem<EventDispatcher> eventDispatcher; + private final ProjectCache projectCache; + private final SourcesCollection sourcesCollection; + + @Inject + public DeleteRefCommand( + PullReplicationStateLogger fetchStateLog, + ProjectCache projectCache, + DeleteRef deleteRef, + DynamicItem<EventDispatcher> eventDispatcher, + SourcesCollection sourcesCollection) { + this.fetchStateLog = fetchStateLog; + this.projectCache = projectCache; + this.deleteRef = deleteRef; + this.eventDispatcher = eventDispatcher; + this.sourcesCollection = sourcesCollection; + } + + public void deleteRef(Project.NameKey name, String refName, String sourceLabel) + throws IOException, RestApiException { + try { + repLog.info("Delete ref from {} for project {}, ref name {}", sourceLabel, name, refName); + Optional<ProjectState> projectState = projectCache.get(name); + if (!projectState.isPresent()) { + throw new ResourceNotFoundException(String.format("Project %s was not found", name)); + } + + Source source = + sourcesCollection + .getByRemoteName(sourceLabel) + .orElseThrow( + () -> + new IllegalStateException( + String.format("Could not find URI for %s remote", sourceLabel))); + URIish sourceUri = source.getURI(name); + + try { + Context.setLocalEvent(true); + deleteRef.deleteSingleRef(projectState.get(), refName); + + eventDispatcher + .get() + .postEvent( + new FetchRefReplicatedEvent( + name.get(), + refName, + sourceUri, + ReplicationState.RefFetchResult.SUCCEEDED, + RefUpdate.Result.FORCED)); + } catch (PermissionBackendException e) { + logger.atSevere().withCause(e).log( + "Unexpected error while trying to delete ref '%s' on project %s and notifying it", + refName, name); + throw RestApiException.wrap(e.getMessage(), e); + } catch (ResourceConflictException e) { + eventDispatcher + .get() + .postEvent( + new FetchRefReplicatedEvent( + name.get(), + refName, + sourceUri, + ReplicationState.RefFetchResult.FAILED, + RefUpdate.Result.LOCK_FAILURE)); + String message = + String.format( + "RefUpdate lock failure for: sourceLabel=%s, project=%s, refName=%s", + sourceLabel, name, refName); + logger.atSevere().withCause(e).log(message); + fetchStateLog.error(message); + throw e; + } finally { + Context.unsetLocalEvent(); + } + + repLog.info( + "Delete ref from {} for project {}, ref name {} completed", sourceLabel, name, refName); + } catch (PermissionBackendException e) { + throw RestApiException.wrap(e.getMessage(), e); + } + } +}
diff --git a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java index a916a7c..ab3d3c5 100644 --- a/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java +++ b/src/main/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClient.java
@@ -21,6 +21,7 @@ import com.google.common.base.Strings; import com.google.common.flogger.FluentLogger; import com.google.common.net.MediaType; +import com.google.gerrit.common.Nullable; import com.google.gerrit.entities.Project; import com.google.gerrit.extensions.annotations.PluginName; import com.google.gerrit.extensions.restapi.Url; @@ -142,9 +143,19 @@ } public HttpResult callSendObject( - Project.NameKey project, String refName, RevisionData revisionData, URIish targetUri) + Project.NameKey project, + String refName, + boolean isDelete, + @Nullable RevisionData revisionData, + URIish targetUri) throws ClientProtocolException, IOException { + if (!isDelete) { + requireNonNull( + revisionData, "RevisionData MUST not be null when the ref-update is not a DELETE"); + } else { + requireNull(revisionData, "DELETE ref-updates cannot be associated with a RevisionData"); + } RevisionInput input = new RevisionInput(instanceLabel, refName, revisionData); String url = @@ -158,6 +169,12 @@ return httpClientFactory.create(source).execute(post, this, getContext(targetUri)); } + private void requireNull(Object object, String string) { + if (object != null) { + throw new IllegalArgumentException(string); + } + } + @Override public HttpResult handleResponse(HttpResponse response) { Optional<String> responseBody = Optional.empty();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java index c5b1790..cf94086 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/PullReplicationIT.java
@@ -15,6 +15,10 @@ package com.googlesource.gerrit.plugins.replication.pull; import static com.google.common.truth.Truth.assertThat; +import static com.google.gerrit.acceptance.GitUtil.fetch; +import static com.google.gerrit.acceptance.GitUtil.pushOne; +import static com.google.gerrit.acceptance.testsuite.project.TestProjectUpdate.allow; +import static com.google.gerrit.server.group.SystemGroupBackend.REGISTERED_USERS; import static java.util.stream.Collectors.toList; import com.google.common.flogger.FluentLogger; @@ -24,7 +28,10 @@ import com.google.gerrit.acceptance.TestPlugin; import com.google.gerrit.acceptance.UseLocalDisk; import com.google.gerrit.acceptance.testsuite.project.ProjectOperations; +import com.google.gerrit.entities.Permission; import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.api.changes.NotifyHandling; import com.google.gerrit.extensions.api.projects.BranchInput; import com.google.gerrit.extensions.common.Input; @@ -47,16 +54,23 @@ import java.nio.file.Path; import java.time.Duration; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import org.eclipse.jgit.errors.ConfigInvalidException; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.junit.TestRepository; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; +import org.eclipse.jgit.transport.PushResult; import org.eclipse.jgit.transport.ReceiveCommand; +import org.eclipse.jgit.transport.RemoteRefUpdate; +import org.eclipse.jgit.transport.RemoteRefUpdate.Status; import org.eclipse.jgit.util.FS; import org.junit.Test; @@ -69,7 +83,7 @@ private static final Optional<String> ALL_PROJECTS = Optional.empty(); private static final FluentLogger logger = FluentLogger.forEnclosingClass(); private static final int TEST_REPLICATION_DELAY = 60; - private static final Duration TEST_TIMEOUT = Duration.ofSeconds(TEST_REPLICATION_DELAY * 2); + private static final Duration TEST_TIMEOUT = Duration.ofSeconds(TEST_REPLICATION_DELAY * 2000); private static final String TEST_REPLICATION_SUFFIX = "suffix1"; private static final String TEST_REPLICATION_REMOTE = "remote1"; @@ -161,6 +175,76 @@ } @Test + @UseLocalDisk + public void shouldReplicateForceUpdatedBranch() throws Exception { + boolean forcedPush = true; + String testProjectName = project + TEST_REPLICATION_SUFFIX; + NameKey testProjectNameKey = createTestProject(testProjectName); + + String newBranch = "refs/heads/mybranch"; + String master = "refs/heads/master"; + BranchInput input = new BranchInput(); + input.revision = master; + gApi.projects().name(testProjectName).branch(newBranch).create(input); + + projectOperations + .project(testProjectNameKey) + .forUpdate() + .add(allow(Permission.PUSH).ref(newBranch).group(REGISTERED_USERS).force(true)) + .update(); + + String branchRevision = gApi.projects().name(testProjectName).branch(newBranch).get().revision; + + ReplicationQueue pullReplicationQueue = + plugin.getSysInjector().getInstance(ReplicationQueue.class); + GitReferenceUpdatedListener.Event event = + new FakeGitReferenceUpdatedEvent( + project, + newBranch, + ObjectId.zeroId().getName(), + branchRevision, + ReceiveCommand.Type.CREATE); + pullReplicationQueue.onGitReferenceUpdated(event); + + try (Repository repo = repoManager.openRepository(project)) { + waitUntil(() -> checkedGetRef(repo, newBranch) != null); + + Ref targetBranchRef = getRef(repo, newBranch); + assertThat(targetBranchRef).isNotNull(); + assertThat(targetBranchRef.getObjectId().getName()).isEqualTo(branchRevision); + } + + TestRepository<InMemoryRepository> testProject = cloneProject(testProjectNameKey); + fetch(testProject, RefNames.REFS_HEADS + "*:" + RefNames.REFS_HEADS + "*"); + RevCommit amendedCommit = testProject.amendRef(newBranch).message("Amended commit").create(); + PushResult pushResult = + pushOne(testProject, newBranch, newBranch, false, forcedPush, Collections.emptyList()); + Collection<RemoteRefUpdate> pushedRefs = pushResult.getRemoteUpdates(); + assertThat(pushedRefs).hasSize(1); + assertThat(pushedRefs.iterator().next().getStatus()).isEqualTo(Status.OK); + + GitReferenceUpdatedListener.Event forcedPushEvent = + new FakeGitReferenceUpdatedEvent( + project, + newBranch, + branchRevision, + amendedCommit.getId().getName(), + ReceiveCommand.Type.UPDATE_NONFASTFORWARD); + pullReplicationQueue.onGitReferenceUpdated(forcedPushEvent); + + try (Repository repo = repoManager.openRepository(project); + Repository sourceRepo = repoManager.openRepository(project)) { + waitUntil( + () -> + checkedGetRef(repo, newBranch) != null + && checkedGetRef(repo, newBranch) + .getObjectId() + .getName() + .equals(amendedCommit.getId().getName())); + } + } + + @Test public void shouldReplicateNewChangeRefCGitClient() throws Exception { AutoReloadConfigDecorator autoReloadConfigDecorator = getInstance(AutoReloadConfigDecorator.class);
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java index 8402782..82dc2b8 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/ReplicationQueueTest.java
@@ -16,6 +16,8 @@ import static com.google.common.truth.Truth.assertThat; import static java.nio.file.Files.createTempDirectory; +import static org.mockito.ArgumentMatchers.anyBoolean; +import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyString; import static org.mockito.Mockito.never; @@ -86,7 +88,7 @@ private Path pluginDataPath; @Before - public void setup() throws IOException, LargeObjectException, RefUpdateException { + public void setup() throws IOException, LargeObjectException { Path sitePath = createTempPath("site"); sitePaths = new SitePaths(sitePath); Path pluginDataPath = createTempPath("data"); @@ -99,9 +101,9 @@ when(source.getApis()).thenReturn(apis); when(sourceCollection.getAll()).thenReturn(Lists.newArrayList(source)); when(rd.get()).thenReturn(sourceCollection); - when(revReader.read(any(), anyString())).thenReturn(Optional.of(revisionData)); + when(revReader.read(any(), any(), anyString())).thenReturn(Optional.of(revisionData)); when(fetchClientFactory.create(any())).thenReturn(fetchRestApiClient); - when(fetchRestApiClient.callSendObject(any(), anyString(), any(), any())) + when(fetchRestApiClient.callSendObject(any(), anyString(), anyBoolean(), any(), any())) .thenReturn(httpResult); when(fetchRestApiClient.callFetch(any(), anyString(), any())).thenReturn(httpResult); when(httpResult.isSuccessful()).thenReturn(true); @@ -117,7 +119,7 @@ objectUnderTest.start(); objectUnderTest.onGitReferenceUpdated(event); - verify(fetchRestApiClient).callSendObject(any(), anyString(), any(), any()); + verify(fetchRestApiClient).callSendObject(any(), anyString(), eq(false), any(), any()); } @Test @@ -152,7 +154,7 @@ objectUnderTest.start(); objectUnderTest.onGitReferenceUpdated(event); - verify(fetchRestApiClient).callSendObject(any(), anyString(), any(), any()); + verify(fetchRestApiClient).callSendObject(any(), anyString(), eq(false), any(), any()); } @Test @@ -161,7 +163,7 @@ Event event = new TestEvent("refs/changes/01/1/meta"); objectUnderTest.start(); - when(revReader.read(any(), anyString())).thenThrow(IOException.class); + when(revReader.read(any(), any(), anyString())).thenThrow(IOException.class); objectUnderTest.onGitReferenceUpdated(event); @@ -174,7 +176,7 @@ Event event = new TestEvent("refs/changes/01/1/1"); objectUnderTest.start(); - when(revReader.read(any(), anyString())).thenReturn(Optional.empty()); + when(revReader.read(any(), any(), anyString())).thenReturn(Optional.empty()); objectUnderTest.onGitReferenceUpdated(event); @@ -189,7 +191,7 @@ when(httpResult.isSuccessful()).thenReturn(false); when(httpResult.isParentObjectMissing()).thenReturn(true); - when(fetchRestApiClient.callSendObject(any(), anyString(), any(), any())) + when(fetchRestApiClient.callSendObject(any(), anyString(), eq(false), any(), any())) .thenReturn(httpResult); objectUnderTest.onGitReferenceUpdated(event); @@ -362,7 +364,7 @@ @Override public String getOldObjectId() { - return null; + return ObjectId.zeroId().getName(); } @Override
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java index d1a4c85..e300613 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/RevisionReaderIT.java
@@ -18,6 +18,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.common.truth.Truth8; import com.google.gerrit.acceptance.LightweightPluginDaemonTest; import com.google.gerrit.acceptance.PushOneCommit.Result; import com.google.gerrit.acceptance.TestPlugin; @@ -29,14 +30,19 @@ import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput; import com.google.gerrit.extensions.client.Comment; import com.google.gerrit.extensions.config.FactoryModule; +import com.google.gerrit.server.notedb.Sequences; import com.google.inject.Scopes; import com.googlesource.gerrit.plugins.replication.ReplicationConfig; import com.googlesource.gerrit.plugins.replication.ReplicationFileBasedConfig; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionData; import com.googlesource.gerrit.plugins.replication.pull.api.data.RevisionObjectData; import com.googlesource.gerrit.plugins.replication.pull.fetch.ApplyObject; +import java.io.IOException; import java.util.Optional; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.lib.Repository; import org.junit.Before; import org.junit.Test; @@ -57,7 +63,8 @@ Result pushResult = createChange(); String refName = RefNames.changeMetaRef(pushResult.getChange().getId()); - Optional<RevisionData> revisionDataOption = objectUnderTest.read(project, refName); + Optional<RevisionData> revisionDataOption = + refObjectId(refName).flatMap(objId -> readRevisionFromObjectUnderTest(refName, objId)); assertThat(revisionDataOption.isPresent()).isTrue(); RevisionData revisionData = revisionDataOption.get(); @@ -73,6 +80,20 @@ assertThat(revisionData.getBlobs()).isEmpty(); } + private Optional<RevisionData> readRevisionFromObjectUnderTest(String refName, ObjectId objId) { + try { + return objectUnderTest.read(project, objId, refName); + } catch (Exception e) { + throw new IllegalStateException(e); + } + } + + protected Optional<ObjectId> refObjectId(String refName) throws IOException { + try (Repository repo = repoManager.openRepository(project)) { + return Optional.ofNullable(repo.exactRef(refName)).map(Ref::getObjectId); + } + } + @Test public void shouldReadRefMetaObjectWithComments() throws Exception { Result pushResult = createChange(); @@ -85,7 +106,8 @@ reviewInput.comments = ImmutableMap.of(Patch.COMMIT_MSG, ImmutableList.of(comment)); gApi.changes().id(changeId.get()).current().review(reviewInput); - Optional<RevisionData> revisionDataOption = objectUnderTest.read(project, refName); + Optional<RevisionData> revisionDataOption = + refObjectId(refName).flatMap(objId -> readRevisionFromObjectUnderTest(refName, objId)); assertThat(revisionDataOption.isPresent()).isTrue(); RevisionData revisionData = revisionDataOption.get(); @@ -104,6 +126,16 @@ assertThat(blobObject.getContent()).isNotEmpty(); } + @Test + public void shouldNotReadRefsSequences() throws Exception { + createChange().assertOkStatus(); + String refName = RefNames.REFS_SEQUENCES + Sequences.NAME_CHANGES; + Optional<RevisionData> revisionDataOption = + refObjectId(refName).flatMap(objId -> readRevisionFromObjectUnderTest(refName, objId)); + + Truth8.assertThat(revisionDataOption).isEmpty(); + } + private CommentInput createCommentInput( int startLine, int startCharacter, int endLine, int endCharacter, String message) { CommentInput comment = new CommentInput();
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java index 34aaf8b..cef1051 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ActionITBase.java
@@ -55,6 +55,7 @@ import org.apache.http.entity.StringEntity; import org.apache.http.impl.client.BasicCredentialsProvider; import org.apache.http.message.BasicHeader; +import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.storage.file.FileBasedConfig; import org.eclipse.jgit.util.FS; @@ -147,7 +148,9 @@ protected Optional<RevisionData> createRevisionData(NameKey projectName, String refName) throws Exception { - return revisionReader.read(projectName, refName); + try (Repository repository = repoManager.openRepository(projectName)) { + return revisionReader.read(projectName, repository.exactRef(refName).getObjectId(), refName); + } } protected Object encode(byte[] content) {
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionTest.java index 8738046..56e1429 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/ApplyObjectActionTest.java
@@ -72,6 +72,7 @@ + "Submitted-with: OK: Code-Review: Gerrit User 1000000 <1000000@69ec38f0-350e-4d9c-96d4-bc956f2faaac>"; @Mock ApplyObjectCommand applyObjectCommand; + @Mock DeleteRefCommand deleteRefCommand; @Mock ProjectResource projectResource; @Mock FetchPreconditions preConditions; @@ -79,7 +80,7 @@ public void setup() { when(preConditions.canCallFetchApi()).thenReturn(true); - applyObjectAction = new ApplyObjectAction(applyObjectCommand, preConditions); + applyObjectAction = new ApplyObjectAction(applyObjectCommand, deleteRefCommand, preConditions); } @Test @@ -129,13 +130,6 @@ } @Test(expected = BadRequestException.class) - public void shouldThrowBadRequestExceptionWhenMissingRevisionData() throws Exception { - RevisionInput inputParams = new RevisionInput(label, refName, null); - - applyObjectAction.apply(projectResource, inputParams); - } - - @Test(expected = BadRequestException.class) public void shouldThrowBadRequestExceptionWhenMissingCommitObjectData() throws Exception { RevisionObjectData commitData = new RevisionObjectData(Constants.OBJ_COMMIT, null); RevisionObjectData treeData = new RevisionObjectData(Constants.OBJ_TREE, new byte[] {});
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java new file mode 100644 index 0000000..ea9d85f --- /dev/null +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/api/DeleteRefCommandTest.java
@@ -0,0 +1,88 @@ +// Copyright (C) 2022 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.googlesource.gerrit.plugins.replication.pull.api; + +import static com.google.common.truth.Truth.assertThat; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; +import com.google.gerrit.extensions.registration.DynamicItem; +import com.google.gerrit.server.events.Event; +import com.google.gerrit.server.events.EventDispatcher; +import com.google.gerrit.server.project.ProjectCache; +import com.google.gerrit.server.project.ProjectState; +import com.google.gerrit.server.restapi.project.DeleteRef; +import com.googlesource.gerrit.plugins.replication.pull.FetchRefReplicatedEvent; +import com.googlesource.gerrit.plugins.replication.pull.PullReplicationStateLogger; +import com.googlesource.gerrit.plugins.replication.pull.Source; +import com.googlesource.gerrit.plugins.replication.pull.SourcesCollection; +import java.net.URISyntaxException; +import java.util.Optional; +import org.eclipse.jgit.transport.URIish; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnitRunner; + +@RunWith(MockitoJUnitRunner.class) +public class DeleteRefCommandTest { + private static final String TEST_SOURCE_LABEL = "test-source-label"; + private static final String TEST_REF_NAME = "refs/changes/01/1/1"; + private static final NameKey TEST_PROJECT_NAME = Project.nameKey("test-project"); + private static URIish TEST_REMOTE_URI; + + @Mock private PullReplicationStateLogger fetchStateLog; + @Mock private DynamicItem<EventDispatcher> eventDispatcherDataItem; + @Mock private EventDispatcher eventDispatcher; + @Mock private ProjectCache projectCache; + @Mock private DeleteRef deleteRef; + @Mock private ProjectState projectState; + @Mock private SourcesCollection sourceCollection; + @Mock private Source source; + @Captor ArgumentCaptor<Event> eventCaptor; + + private DeleteRefCommand objectUnderTest; + + @Before + public void setup() throws URISyntaxException { + when(eventDispatcherDataItem.get()).thenReturn(eventDispatcher); + when(projectCache.get(any())).thenReturn(Optional.of(projectState)); + when(sourceCollection.getByRemoteName(TEST_SOURCE_LABEL)).thenReturn(Optional.of(source)); + TEST_REMOTE_URI = new URIish("git://some.remote.uri"); + when(source.getURI(TEST_PROJECT_NAME)).thenReturn(TEST_REMOTE_URI); + + objectUnderTest = + new DeleteRefCommand( + fetchStateLog, projectCache, deleteRef, eventDispatcherDataItem, sourceCollection); + } + + @Test + public void shouldSendEventWhenDeletingRef() throws Exception { + objectUnderTest.deleteRef(TEST_PROJECT_NAME, TEST_REF_NAME, TEST_SOURCE_LABEL); + + verify(eventDispatcher).postEvent(eventCaptor.capture()); + Event sentEvent = eventCaptor.getValue(); + assertThat(sentEvent).isInstanceOf(FetchRefReplicatedEvent.class); + FetchRefReplicatedEvent fetchEvent = (FetchRefReplicatedEvent) sentEvent; + assertThat(fetchEvent.getProjectNameKey()).isEqualTo(TEST_PROJECT_NAME); + assertThat(fetchEvent.getRefName()).isEqualTo(TEST_REF_NAME); + } +}
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java index a778049..394a4a4 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/client/FetchRestApiClientTest.java
@@ -64,6 +64,8 @@ @RunWith(MockitoJUnitRunner.class) public class FetchRestApiClientTest { + private static final boolean IS_REF_UPDATE = false; + @Mock CredentialsProvider credentialProvider; @Mock CredentialsFactory credentials; @Mock HttpClient httpClient; @@ -273,7 +275,11 @@ throws ClientProtocolException, IOException, URISyntaxException { objectUnderTest.callSendObject( - Project.nameKey("test_repo"), refName, createSampleRevisionData(), new URIish(api)); + Project.nameKey("test_repo"), + refName, + IS_REF_UPDATE, + createSampleRevisionData(), + new URIish(api)); verify(httpClient, times(1)).execute(httpPostCaptor.capture(), any(), any()); @@ -288,7 +294,11 @@ throws ClientProtocolException, IOException, URISyntaxException { objectUnderTest.callSendObject( - Project.nameKey("test_repo"), refName, createSampleRevisionData(), new URIish(api)); + Project.nameKey("test_repo"), + refName, + IS_REF_UPDATE, + createSampleRevisionData(), + new URIish(api)); verify(httpClient, times(1)).execute(httpPostCaptor.capture(), any(), any());
diff --git a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java index f6cd342..e2a162e 100644 --- a/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java +++ b/src/test/java/com/googlesource/gerrit/plugins/replication/pull/fetch/ApplyObjectIT.java
@@ -29,6 +29,7 @@ import com.google.gerrit.entities.Change; import com.google.gerrit.entities.Patch; import com.google.gerrit.entities.Project; +import com.google.gerrit.entities.Project.NameKey; import com.google.gerrit.entities.RefNames; import com.google.gerrit.extensions.api.changes.ReviewInput; import com.google.gerrit.extensions.api.changes.ReviewInput.CommentInput; @@ -77,16 +78,16 @@ String refName = RefNames.changeMetaRef(pushResult.getChange().getId()); Optional<RevisionData> revisionData = - reader.read(Project.nameKey(testRepoProjectName), refName); + reader.read( + Project.nameKey(testRepoProjectName), pushResult.getCommit().toObjectId(), refName); RefSpec refSpec = new RefSpec(refName); objectUnderTest.apply(project, refSpec, revisionData.get()); - Optional<RevisionData> newRevisionData = reader.read(project, refName); - - compareObjects(revisionData.get(), newRevisionData); - try (Repository repo = repoManager.openRepository(project); TestRepository<Repository> testRepo = new TestRepository<>(repo); ) { + Optional<RevisionData> newRevisionData = + reader.read(project, repo.exactRef(refName).getObjectId(), refName); + compareObjects(revisionData.get(), newRevisionData); testRepo.fsck(); } } @@ -101,26 +102,30 @@ String refName = RefNames.changeMetaRef(changeId); RefSpec refSpec = new RefSpec(refName); - Optional<RevisionData> revisionData = - reader.read(Project.nameKey(testRepoProjectName), refName); - objectUnderTest.apply(project, refSpec, revisionData.get()); + NameKey testRepoKey = Project.nameKey(testRepoProjectName); + try (Repository repo = repoManager.openRepository(testRepoKey)) { + Optional<RevisionData> revisionData = + reader.read(testRepoKey, repo.exactRef(refName).getObjectId(), refName); + objectUnderTest.apply(project, refSpec, revisionData.get()); + } ReviewInput reviewInput = new ReviewInput(); CommentInput comment = createCommentInput(1, 0, 1, 1, "Test comment"); reviewInput.comments = ImmutableMap.of(Patch.COMMIT_MSG, ImmutableList.of(comment)); gApi.changes().id(changeId.get()).current().review(reviewInput); - Optional<RevisionData> revisionDataWithComment = - reader.read(Project.nameKey(testRepoProjectName), refName); - - objectUnderTest.apply(project, refSpec, revisionDataWithComment.get()); - - Optional<RevisionData> newRevisionData = reader.read(project, refName); - - compareObjects(revisionDataWithComment.get(), newRevisionData); - try (Repository repo = repoManager.openRepository(project); TestRepository<Repository> testRepo = new TestRepository<>(repo)) { + Optional<RevisionData> revisionDataWithComment = + reader.read(testRepoKey, repo.exactRef(refName).getObjectId(), refName); + + objectUnderTest.apply(project, refSpec, revisionDataWithComment.get()); + + Optional<RevisionData> newRevisionData = + reader.read(project, repo.exactRef(refName).getObjectId(), refName); + + compareObjects(revisionDataWithComment.get(), newRevisionData); + testRepo.fsck(); } } @@ -128,24 +133,27 @@ @Test public void shouldThrowExceptionWhenParentCommitObjectIsMissing() throws Exception { String testRepoProjectName = project + TEST_REPLICATION_SUFFIX; - testRepo = cloneProject(createTestProject(testRepoProjectName)); + NameKey createTestProject = createTestProject(testRepoProjectName); + try (Repository repo = repoManager.openRepository(createTestProject)) { + testRepo = cloneProject(createTestProject); - Result pushResult = createChange(); - Change.Id changeId = pushResult.getChange().getId(); - String refName = RefNames.changeMetaRef(changeId); + Result pushResult = createChange(); + Change.Id changeId = pushResult.getChange().getId(); + String refName = RefNames.changeMetaRef(changeId); - CommentInput comment = createCommentInput(1, 0, 1, 1, "Test comment"); - ReviewInput reviewInput = new ReviewInput(); - reviewInput.comments = ImmutableMap.of(Patch.COMMIT_MSG, ImmutableList.of(comment)); - gApi.changes().id(changeId.get()).current().review(reviewInput); + CommentInput comment = createCommentInput(1, 0, 1, 1, "Test comment"); + ReviewInput reviewInput = new ReviewInput(); + reviewInput.comments = ImmutableMap.of(Patch.COMMIT_MSG, ImmutableList.of(comment)); + gApi.changes().id(changeId.get()).current().review(reviewInput); - Optional<RevisionData> revisionData = - reader.read(Project.nameKey(testRepoProjectName), refName); + Optional<RevisionData> revisionData = + reader.read(createTestProject, repo.exactRef(refName).getObjectId(), refName); - RefSpec refSpec = new RefSpec(refName); - assertThrows( - MissingParentObjectException.class, - () -> objectUnderTest.apply(project, refSpec, revisionData.get())); + RefSpec refSpec = new RefSpec(refName); + assertThrows( + MissingParentObjectException.class, + () -> objectUnderTest.apply(project, refSpec, revisionData.get())); + } } private void compareObjects(RevisionData expected, Optional<RevisionData> actualOption) {