1 /*
2 * Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com>
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 package org.eclipse.jgit.util;
44
45 import java.io.IOException;
46 import java.io.InputStream;
47 import java.io.PrintStream;
48 import java.text.MessageFormat;
49 import java.util.concurrent.Callable;
50
51 import org.eclipse.jgit.annotations.Nullable;
52 import org.eclipse.jgit.attributes.Attribute;
53 import org.eclipse.jgit.attributes.Attributes;
54 import org.eclipse.jgit.hooks.PrePushHook;
55 import org.eclipse.jgit.internal.JGitText;
56 import org.eclipse.jgit.lib.ObjectLoader;
57 import org.eclipse.jgit.lib.Repository;
58 import org.eclipse.jgit.revwalk.RevCommit;
59 import org.eclipse.jgit.treewalk.FileTreeIterator;
60 import org.eclipse.jgit.treewalk.TreeWalk;
61 import org.eclipse.jgit.treewalk.filter.PathFilter;
62
63 /**
64 * Represents an optionally present LFS support implementation
65 *
66 * @since 4.11
67 */
68 public class LfsFactory {
69
70 private static LfsFactorytml#LfsFactory">LfsFactory instance = new LfsFactory();
71
72 /**
73 * Constructor
74 */
75 protected LfsFactory() {
76 }
77
78 /**
79 * @return the current LFS implementation
80 */
81 public static LfsFactory getInstance() {
82 return instance;
83 }
84
85 /**
86 * @param instance
87 * register a {@link LfsFactory} instance as the
88 * {@link LfsFactory} implementation to use.
89 */
90 public static void setInstance(LfsFactory instance) {
91 LfsFactory.instance = instance;
92 }
93
94 /**
95 * @return whether LFS support is available
96 */
97 public boolean isAvailable() {
98 return false;
99 }
100
101 /**
102 * Apply clean filtering to the given stream, writing the file content to
103 * the LFS storage if required and returning a stream to the LFS pointer
104 * instead.
105 *
106 * @param db
107 * the repository
108 * @param input
109 * the original input
110 * @param length
111 * the expected input stream length
112 * @param attribute
113 * the attribute used to check for LFS enablement (i.e. "merge",
114 * "diff", "filter" from .gitattributes).
115 * @return a stream to the content that should be written to the object
116 * store along with the expected length of the stream. the original
117 * stream is not applicable.
118 * @throws IOException
119 * in case of an error
120 */
121 public LfsInputStream applyCleanFilter(Repository db,
122 InputStream input, long length, Attribute attribute)
123 throws IOException {
124 return new LfsInputStream(input, length);
125 }
126
127 /**
128 * Apply smudge filtering to a given loader, potentially redirecting it to a
129 * LFS blob which is downloaded on demand.
130 *
131 * @param db
132 * the repository
133 * @param loader
134 * the loader for the blob
135 * @param attribute
136 * the attribute used to check for LFS enablement (i.e. "merge",
137 * "diff", "filter" from .gitattributes).
138 * @return a loader for the actual data of a blob, or the original loader in
139 * case LFS is not applicable.
140 * @throws IOException
141 */
142 public ObjectLoader applySmudgeFilter(Repository db,
143 ObjectLoader loader, Attribute attribute) throws IOException {
144 return loader;
145 }
146
147 /**
148 * Retrieve a pre-push hook to be applied using the default error stream.
149 *
150 * @param repo
151 * the {@link Repository} the hook is applied to.
152 * @param outputStream
153 * @return a {@link PrePushHook} implementation or <code>null</code>
154 */
155 @Nullable
156 public PrePushHook getPrePushHook(Repository repo,
157 PrintStream outputStream) {
158 return null;
159 }
160
161 /**
162 * Retrieve a pre-push hook to be applied.
163 *
164 * @param repo
165 * the {@link Repository} the hook is applied to.
166 * @param outputStream
167 * @param errorStream
168 * @return a {@link PrePushHook} implementation or <code>null</code>
169 * @since 5.6
170 */
171 @Nullable
172 public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream,
173 PrintStream errorStream) {
174 return getPrePushHook(repo, outputStream);
175 }
176
177 /**
178 * Retrieve an {@link LfsInstallCommand} which can be used to enable LFS
179 * support (if available) either per repository or for the user.
180 *
181 * @return a command to install LFS support.
182 */
183 @Nullable
184 public LfsInstallCommand getInstallCommand() {
185 return null;
186 }
187
188 /**
189 * @param db
190 * the repository to check
191 * @return whether LFS is enabled for the given repository locally or
192 * globally.
193 */
194 public boolean isEnabled(Repository db) {
195 return false;
196 }
197
198 /**
199 * @param db
200 * the repository
201 * @param path
202 * the path to find attributes for
203 * @return the {@link Attributes} for the given path.
204 * @throws IOException
205 * in case of an error
206 */
207 public static Attributes getAttributesForPath(Repository db, String path)
208 throws IOException {
209 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(db)) {
210 walk.addTree(new FileTreeIterator(db));
211 PathFilter f = PathFilter.create(path);
212 walk.setFilter(f);
213 walk.setRecursive(false);
214 Attributes attr = null;
215 while (walk.next()) {
216 if (f.isDone(walk)) {
217 attr = walk.getAttributes();
218 break;
219 } else if (walk.isSubtree()) {
220 walk.enterSubtree();
221 }
222 }
223 if (attr == null) {
224 throw new IOException(MessageFormat
225 .format(JGitText.get().noPathAttributesFound, path));
226 }
227
228 return attr;
229 }
230 }
231
232 /**
233 * Get attributes for given path and commit
234 *
235 * @param db
236 * the repository
237 * @param path
238 * the path to find attributes for
239 * @param commit
240 * the commit to inspect.
241 * @return the {@link Attributes} for the given path.
242 * @throws IOException
243 * in case of an error
244 */
245 public static Attributes getAttributesForPath(Repository db, String path,
246 RevCommit commit) throws IOException {
247 if (commit == null) {
248 return getAttributesForPath(db, path);
249 }
250
251 try (TreeWalk walk = TreeWalk.forPath(db, path, commit.getTree())) {
252 Attributes attr = walk == null ? null : walk.getAttributes();
253 if (attr == null) {
254 throw new IOException(MessageFormat
255 .format(JGitText.get().noPathAttributesFound, path));
256 }
257
258 return attr;
259 }
260 }
261
262 /**
263 * Encapsulate a potentially exchanged {@link InputStream} along with the
264 * expected stream content length.
265 */
266 public static final class LfsInputStream extends InputStream {
267 /**
268 * The actual stream.
269 */
270 private InputStream stream;
271
272 /**
273 * The expected stream content length.
274 */
275 private long length;
276
277 /**
278 * Create a new wrapper around a certain stream
279 *
280 * @param stream
281 * the stream to wrap. the stream will be closed on
282 * {@link #close()}.
283 * @param length
284 * the expected length of the stream
285 */
286 public LfsInputStream(InputStream stream, long length) {
287 this.stream = stream;
288 this.length = length;
289 }
290
291 /**
292 * Create a new wrapper around a temporary buffer.
293 *
294 * @param buffer
295 * the buffer to initialize stream and length from. The
296 * buffer will be destroyed on {@link #close()}
297 * @throws IOException
298 * in case of an error opening the stream to the buffer.
299 */
300 public LfsInputStream(TemporaryBuffer buffer) throws IOException {
301 this.stream = buffer.openInputStreamWithAutoDestroy();
302 this.length = buffer.length();
303 }
304
305 @Override
306 public void close() throws IOException {
307 stream.close();
308 }
309
310 @Override
311 public int read() throws IOException {
312 return stream.read();
313 }
314
315 @Override
316 public int read(byte b[], int off, int len) throws IOException {
317 return stream.read(b, off, len);
318 }
319
320 /**
321 * @return the length of the stream
322 */
323 public long getLength() {
324 return length;
325 }
326 }
327
328 /**
329 * A command to enable LFS. Optionally set a {@link Repository} to enable
330 * locally on the repository only.
331 */
332 public interface LfsInstallCommand extends Callable<Void> {
333 /**
334 * @param repo
335 * the repository to enable support for.
336 * @return The {@link LfsInstallCommand} for chaining.
337 */
338 public LfsInstallCommand setRepository(Repository repo);
339 }
340
341 }