View Javadoc
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 }