blob: 85688102705d64ef6dd2fe2cf5d88ab73ce8e5c1 [file] [log] [blame]
// Copyright (C) 2019 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.git.receive;
import static com.google.common.collect.ImmutableList.toImmutableList;
import com.google.common.base.Supplier;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.MultimapBuilder;
import com.google.gerrit.entities.Change;
import com.google.gerrit.entities.PatchSet;
import com.google.gerrit.entities.RefNames;
import java.io.IOException;
import java.util.Map;
import java.util.Objects;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.RefDatabase;
/**
* Simple cache for accessing refs by name, prefix or {@link ObjectId}. Intended to be used when
* processing a {@code git push}.
*
* <p>This class is not thread safe.
*/
public interface ReceivePackRefCache {
/**
* Returns an instance that delegates all calls to the provided {@link RefDatabase}. To be used in
* tests or when the ref database is fast with forward (name to {@link ObjectId}) and inverse
* ({@code ObjectId} to name) lookups.
*/
static ReceivePackRefCache noCache(RefDatabase delegate) {
return new NoCache(delegate);
}
/**
* Returns an instance that answers calls based on refs previously advertised and captured in
* {@link AllRefsWatcher}. Speeds up inverse lookups by building a {@code Map<ObjectId,
* List<Ref>>} and a {@code Map<Change.Id, List<Ref>>}.
*
* <p>This implementation speeds up lookups when the ref database does not support inverse ({@code
* ObjectId} to name) lookups.
*/
static ReceivePackRefCache withAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) {
return new WithAdvertisedRefs(allRefsSupplier);
}
/** Returns a list of {@link com.google.gerrit.entities.PatchSet.Id}s that point to {@code id}. */
ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) throws IOException;
/** Returns all refs whose name starts with {@code prefix}. */
ImmutableList<Ref> byPrefix(String prefix) throws IOException;
/** Returns a ref whose name matches {@code ref} or {@code null} if such a ref does not exist. */
@Nullable
Ref exactRef(String ref) throws IOException;
class NoCache implements ReceivePackRefCache {
private final RefDatabase delegate;
private NoCache(RefDatabase delegate) {
this.delegate = delegate;
}
@Override
public ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) throws IOException {
return delegate.getTipsWithSha1(id).stream()
.map(r -> PatchSet.Id.fromRef(r.getName()))
.filter(Objects::nonNull)
.collect(toImmutableList());
}
@Override
public ImmutableList<Ref> byPrefix(String prefix) throws IOException {
return delegate.getRefsByPrefix(prefix).stream().collect(toImmutableList());
}
@Override
@Nullable
public Ref exactRef(String name) throws IOException {
return delegate.exactRef(name);
}
}
class WithAdvertisedRefs implements ReceivePackRefCache {
/** We estimate that a change has an average of 4 patch sets plus the meta ref. */
private static final int ESTIMATED_NUMBER_OF_REFS_PER_CHANGE = 5;
private final Supplier<Map<String, Ref>> allRefsSupplier;
// Collections lazily populated during processing.
private Map<String, Ref> allRefs;
/** Contains only patch set refs. */
private ListMultimap<Change.Id, Ref> refsByChange;
/** Contains all refs. */
private ListMultimap<ObjectId, Ref> refsByObjectId;
private WithAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) {
this.allRefsSupplier = allRefsSupplier;
}
@Override
public ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) {
lazilyInitRefMaps();
return refsByObjectId.get(id).stream()
.map(r -> PatchSet.Id.fromRef(r.getName()))
.filter(Objects::nonNull)
.collect(toImmutableList());
}
@Override
public ImmutableList<Ref> byPrefix(String prefix) {
lazilyInitRefMaps();
if (RefNames.isRefsChanges(prefix)) {
Change.Id cId = Change.Id.fromRefPart(prefix);
if (cId != null) {
return refsByChange.get(cId).stream()
.filter(r -> r.getName().startsWith(prefix))
.collect(toImmutableList());
}
}
return allRefs().values().stream()
.filter(r -> r.getName().startsWith(prefix))
.collect(toImmutableList());
}
@Override
@Nullable
public Ref exactRef(String name) {
return allRefs().get(name);
}
private Map<String, Ref> allRefs() {
if (allRefs == null) {
allRefs = allRefsSupplier.get();
}
return allRefs;
}
private void lazilyInitRefMaps() {
if (refsByChange != null) {
return;
}
refsByObjectId = MultimapBuilder.hashKeys().arrayListValues().build();
refsByChange =
MultimapBuilder.hashKeys(allRefs().size() / ESTIMATED_NUMBER_OF_REFS_PER_CHANGE)
.arrayListValues(ESTIMATED_NUMBER_OF_REFS_PER_CHANGE)
.build();
for (Ref ref : allRefs().values()) {
ObjectId objectId = ref.getObjectId();
if (objectId != null) {
refsByObjectId.put(objectId, ref);
Change.Id changeId = Change.Id.fromRef(ref.getName());
if (changeId != null) {
refsByChange.put(changeId, ref);
}
}
}
}
}
}