1 /*
2 * Copyright (C) 2017, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.internal.storage.reftable;
45
46 import static org.eclipse.jgit.lib.RefDatabase.MAX_SYMBOLIC_REF_DEPTH;
47
48 import java.io.ByteArrayOutputStream;
49 import java.io.IOException;
50 import java.util.Collection;
51
52 import org.eclipse.jgit.annotations.Nullable;
53 import org.eclipse.jgit.internal.storage.io.BlockSource;
54 import org.eclipse.jgit.lib.AnyObjectId;
55 import org.eclipse.jgit.lib.Ref;
56 import org.eclipse.jgit.lib.SymbolicRef;
57
58 /**
59 * Abstract table of references.
60 */
61 public abstract class Reftable implements AutoCloseable {
62 /**
63 * References to convert into a reftable
64 *
65 * @param refs
66 * references to convert into a reftable; may be empty.
67 * @return a reader for the supplied references.
68 */
69 public static Reftable from(Collection<Ref> refs) {
70 try {
71 ReftableConfig cfg = new ReftableConfig();
72 cfg.setIndexObjects(false);
73 cfg.setAlignBlocks(false);
74 ByteArrayOutputStream buf = new ByteArrayOutputStream();
75 new ReftableWriter()
76 .setConfig(cfg)
77 .begin(buf)
78 .sortAndWriteRefs(refs)
79 .finish();
80 return new ReftableReader(BlockSource.from(buf.toByteArray()));
81 } catch (IOException e) {
82 throw new RuntimeException(e);
83 }
84 }
85
86 /** {@code true} if deletions should be included in results. */
87 protected boolean includeDeletes;
88
89 /**
90 * Whether deleted references will be returned.
91 *
92 * @param deletes
93 * if {@code true} deleted references will be returned. If
94 * {@code false} (default behavior), deleted references will be
95 * skipped, and not returned.
96 */
97 public void setIncludeDeletes(boolean deletes) {
98 includeDeletes = deletes;
99 }
100
101 /**
102 * Seek to the first reference, to iterate in order.
103 *
104 * @return cursor to iterate.
105 * @throws java.io.IOException
106 * if references cannot be read.
107 */
108 public abstract RefCursor allRefs() throws IOException;
109
110 /**
111 * Seek either to a reference, or a reference subtree.
112 * <p>
113 * If {@code refName} ends with {@code "/"} the method will seek to the
114 * subtree of all references starting with {@code refName} as a prefix. If
115 * no references start with this prefix, an empty cursor is returned.
116 * <p>
117 * Otherwise exactly {@code refName} will be looked for. If present, the
118 * returned cursor will iterate exactly one entry. If not found, an empty
119 * cursor is returned.
120 *
121 * @param refName
122 * reference name or subtree to find.
123 * @return cursor to iterate; empty cursor if no references match.
124 * @throws java.io.IOException
125 * if references cannot be read.
126 */
127 public abstract RefCursor seekRef(String refName) throws IOException;
128
129 /**
130 * Match references pointing to a specific object.
131 *
132 * @param id
133 * object to find.
134 * @return cursor to iterate; empty cursor if no references match.
135 * @throws java.io.IOException
136 * if references cannot be read.
137 */
138 public abstract RefCursor byObjectId(AnyObjectId id) throws IOException;
139
140 /**
141 * Seek reader to read log records.
142 *
143 * @return cursor to iterate; empty cursor if no logs are present.
144 * @throws java.io.IOException
145 * if logs cannot be read.
146 */
147 public abstract LogCursor allLogs() throws IOException;
148
149 /**
150 * Read a single reference's log.
151 *
152 * @param refName
153 * exact name of the reference whose log to read.
154 * @return cursor to iterate; empty cursor if no logs match.
155 * @throws java.io.IOException
156 * if logs cannot be read.
157 */
158 public LogCursor seekLog(String refName) throws IOException {
159 return seekLog(refName, Long.MAX_VALUE);
160 }
161
162 /**
163 * Seek to an update index in a reference's log.
164 *
165 * @param refName
166 * exact name of the reference whose log to read.
167 * @param updateIndex
168 * most recent index to return first in the log cursor. Log
169 * records at or before {@code updateIndex} will be returned.
170 * @return cursor to iterate; empty cursor if no logs match.
171 * @throws java.io.IOException
172 * if logs cannot be read.
173 */
174 public abstract LogCursor seekLog(String refName, long updateIndex)
175 throws IOException;
176
177 /**
178 * Lookup a reference, or null if not found.
179 *
180 * @param refName
181 * reference name to find.
182 * @return the reference, or {@code null} if not found.
183 * @throws java.io.IOException
184 * if references cannot be read.
185 */
186 @Nullable
187 public Ref exactRef(String refName) throws IOException {
188 try (RefCursor rc = seekRef(refName)) {
189 return rc.next() ? rc.getRef() : null;
190 }
191 }
192
193 /**
194 * Test if a reference or reference subtree exists.
195 * <p>
196 * If {@code refName} ends with {@code "/"}, the method tests if any
197 * reference starts with {@code refName} as a prefix.
198 * <p>
199 * Otherwise, the method checks if {@code refName} exists.
200 *
201 * @param refName
202 * reference name or subtree to find.
203 * @return {@code true} if the reference exists, or at least one reference
204 * exists in the subtree.
205 * @throws java.io.IOException
206 * if references cannot be read.
207 */
208 public boolean hasRef(String refName) throws IOException {
209 try (RefCursor rc = seekRef(refName)) {
210 return rc.next();
211 }
212 }
213
214 /**
215 * Test if any reference directly refers to the object.
216 *
217 * @param id
218 * ObjectId to find.
219 * @return {@code true} if any reference exists directly referencing
220 * {@code id}, or a annotated tag that peels to {@code id}.
221 * @throws java.io.IOException
222 * if references cannot be read.
223 */
224 public boolean hasId(AnyObjectId id) throws IOException {
225 try (RefCursor rc = byObjectId(id)) {
226 return rc.next();
227 }
228 }
229
230 /**
231 * Resolve a symbolic reference to populate its value.
232 *
233 * @param symref
234 * reference to resolve.
235 * @return resolved {@code symref}, or {@code null}.
236 * @throws java.io.IOException
237 * if references cannot be read.
238 */
239 @Nullable
240 public Ref resolve(Ref symref) throws IOException {
241 return resolve(symref, 0);
242 }
243
244 private Ref resolve(Ref ref, int depth) throws IOException {
245 if (!ref.isSymbolic()) {
246 return ref;
247 }
248
249 Ref dst = ref.getTarget();
250 if (MAX_SYMBOLIC_REF_DEPTH <= depth) {
251 return null; // claim it doesn't exist
252 }
253
254 dst = exactRef(dst.getName());
255 if (dst == null) {
256 return ref;
257 }
258
259 dst = resolve(dst, depth + 1);
260 if (dst == null) {
261 return null; // claim it doesn't exist
262 }
263 return new SymbolicRef(ref.getName(), dst);
264 }
265
266 /** {@inheritDoc} */
267 @Override
268 public abstract void close() throws IOException;
269 }