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