Line data Source code
1 : // Copyright (C) 2019 The Android Open Source Project 2 : // 3 : // Licensed under the Apache License, Version 2.0 (the "License"); 4 : // you may not use this file except in compliance with the License. 5 : // You may obtain a copy of the License at 6 : // 7 : // http://www.apache.org/licenses/LICENSE-2.0 8 : // 9 : // Unless required by applicable law or agreed to in writing, software 10 : // distributed under the License is distributed on an "AS IS" BASIS, 11 : // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 : // See the License for the specific language governing permissions and 13 : // limitations under the License. 14 : 15 : package com.google.gerrit.server.git.receive; 16 : 17 : import static com.google.common.collect.ImmutableList.toImmutableList; 18 : 19 : import com.google.common.base.Supplier; 20 : import com.google.common.collect.ImmutableList; 21 : import com.google.common.collect.ListMultimap; 22 : import com.google.common.collect.MultimapBuilder; 23 : import com.google.gerrit.entities.Change; 24 : import com.google.gerrit.entities.PatchSet; 25 : import com.google.gerrit.entities.RefNames; 26 : import java.io.IOException; 27 : import java.util.Map; 28 : import java.util.Objects; 29 : import org.eclipse.jgit.annotations.Nullable; 30 : import org.eclipse.jgit.lib.ObjectId; 31 : import org.eclipse.jgit.lib.Ref; 32 : import org.eclipse.jgit.lib.RefDatabase; 33 : 34 : /** 35 : * Simple cache for accessing refs by name, prefix or {@link ObjectId}. Intended to be used when 36 : * processing a {@code git push}. 37 : * 38 : * <p>This class is not thread safe. 39 : */ 40 : public interface ReceivePackRefCache { 41 : 42 : /** 43 : * Returns an instance that delegates all calls to the provided {@link RefDatabase}. To be used in 44 : * tests or when the ref database is fast with forward (name to {@link ObjectId}) and inverse 45 : * ({@code ObjectId} to name) lookups. 46 : */ 47 : static ReceivePackRefCache noCache(RefDatabase delegate) { 48 2 : return new NoCache(delegate); 49 : } 50 : 51 : /** 52 : * Returns an instance that answers calls based on refs previously advertised and captured in 53 : * {@link AllRefsWatcher}. Speeds up inverse lookups by building a {@code Map<ObjectId, 54 : * List<Ref>>} and a {@code Map<Change.Id, List<Ref>>}. 55 : * 56 : * <p>This implementation speeds up lookups when the ref database does not support inverse ({@code 57 : * ObjectId} to name) lookups. 58 : */ 59 : static ReceivePackRefCache withAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) { 60 98 : return new WithAdvertisedRefs(allRefsSupplier); 61 : } 62 : 63 : /** Returns a list of {@link com.google.gerrit.entities.PatchSet.Id}s that point to {@code id}. */ 64 : ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) throws IOException; 65 : 66 : /** Returns all refs whose name starts with {@code prefix}. */ 67 : ImmutableList<Ref> byPrefix(String prefix) throws IOException; 68 : 69 : /** Returns a ref whose name matches {@code ref} or {@code null} if such a ref does not exist. */ 70 : @Nullable 71 : Ref exactRef(String ref) throws IOException; 72 : 73 : class NoCache implements ReceivePackRefCache { 74 : private final RefDatabase delegate; 75 : 76 2 : private NoCache(RefDatabase delegate) { 77 2 : this.delegate = delegate; 78 2 : } 79 : 80 : @Override 81 : public ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) throws IOException { 82 2 : return delegate.getTipsWithSha1(id).stream() 83 2 : .map(r -> PatchSet.Id.fromRef(r.getName())) 84 2 : .filter(Objects::nonNull) 85 2 : .collect(toImmutableList()); 86 : } 87 : 88 : @Override 89 : public ImmutableList<Ref> byPrefix(String prefix) throws IOException { 90 2 : return delegate.getRefsByPrefix(prefix).stream().collect(toImmutableList()); 91 : } 92 : 93 : @Override 94 : @Nullable 95 : public Ref exactRef(String name) throws IOException { 96 2 : return delegate.exactRef(name); 97 : } 98 : } 99 : 100 : class WithAdvertisedRefs implements ReceivePackRefCache { 101 : /** We estimate that a change has an average of 4 patch sets plus the meta ref. */ 102 : private static final int ESTIMATED_NUMBER_OF_REFS_PER_CHANGE = 5; 103 : 104 : private final Supplier<Map<String, Ref>> allRefsSupplier; 105 : 106 : // Collections lazily populated during processing. 107 : private Map<String, Ref> allRefs; 108 : /** Contains only patch set refs. */ 109 : private ListMultimap<Change.Id, Ref> refsByChange; 110 : /** Contains all refs. */ 111 : private ListMultimap<ObjectId, Ref> refsByObjectId; 112 : 113 98 : private WithAdvertisedRefs(Supplier<Map<String, Ref>> allRefsSupplier) { 114 98 : this.allRefsSupplier = allRefsSupplier; 115 98 : } 116 : 117 : @Override 118 : public ImmutableList<PatchSet.Id> patchSetIdsFromObjectId(ObjectId id) { 119 97 : lazilyInitRefMaps(); 120 97 : return refsByObjectId.get(id).stream() 121 97 : .map(r -> PatchSet.Id.fromRef(r.getName())) 122 97 : .filter(Objects::nonNull) 123 97 : .collect(toImmutableList()); 124 : } 125 : 126 : @Override 127 : public ImmutableList<Ref> byPrefix(String prefix) { 128 97 : lazilyInitRefMaps(); 129 97 : if (RefNames.isRefsChanges(prefix)) { 130 39 : Change.Id cId = Change.Id.fromRefPart(prefix); 131 39 : if (cId != null) { 132 0 : return refsByChange.get(cId).stream() 133 0 : .filter(r -> r.getName().startsWith(prefix)) 134 0 : .collect(toImmutableList()); 135 : } 136 : } 137 97 : return allRefs().values().stream() 138 97 : .filter(r -> r.getName().startsWith(prefix)) 139 97 : .collect(toImmutableList()); 140 : } 141 : 142 : @Override 143 : @Nullable 144 : public Ref exactRef(String name) { 145 97 : return allRefs().get(name); 146 : } 147 : 148 : private Map<String, Ref> allRefs() { 149 97 : if (allRefs == null) { 150 97 : allRefs = allRefsSupplier.get(); 151 : } 152 97 : return allRefs; 153 : } 154 : 155 : private void lazilyInitRefMaps() { 156 97 : if (refsByChange != null) { 157 96 : return; 158 : } 159 : 160 97 : refsByObjectId = MultimapBuilder.hashKeys().arrayListValues().build(); 161 97 : refsByChange = 162 97 : MultimapBuilder.hashKeys(allRefs().size() / ESTIMATED_NUMBER_OF_REFS_PER_CHANGE) 163 97 : .arrayListValues(ESTIMATED_NUMBER_OF_REFS_PER_CHANGE) 164 97 : .build(); 165 97 : for (Ref ref : allRefs().values()) { 166 97 : ObjectId objectId = ref.getObjectId(); 167 97 : if (objectId != null) { 168 97 : refsByObjectId.put(objectId, ref); 169 97 : Change.Id changeId = Change.Id.fromRef(ref.getName()); 170 97 : if (changeId != null) { 171 67 : refsByChange.put(changeId, ref); 172 : } 173 : } 174 97 : } 175 97 : } 176 : } 177 : }