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