aboutsummaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit/src/org/eclipse/jgit/util/LfsFactory.java
blob: 7b7c1d0886a6a21ced166a018737d4a9e4020a15 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
/*
 * Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com> and others
 *
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Distribution License v. 1.0 which is available at
 * https://www.eclipse.org/org/documents/edl-v10.php.
 *
 * SPDX-License-Identifier: BSD-3-Clause
 */
package org.eclipse.jgit.util;

import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.text.MessageFormat;
import java.util.concurrent.Callable;

import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.attributes.Attribute;
import org.eclipse.jgit.attributes.Attributes;
import org.eclipse.jgit.hooks.PrePushHook;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectLoader;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilter;

/**
 * Represents an optionally present LFS support implementation
 *
 * @since 4.11
 */
public class LfsFactory {

	private static LfsFactory instance = new LfsFactory();

	/**
	 * Constructor
	 */
	protected LfsFactory() {
	}

	/**
	 * Get the LFS factory instance
	 *
	 * @return the current LFS implementation
	 */
	public static LfsFactory getInstance() {
		return instance;
	}

	/**
	 * Set the LFS factory instance
	 *
	 * @param instance
	 *            register a {@link LfsFactory} instance as the
	 *            {@link LfsFactory} implementation to use.
	 */
	public static void setInstance(LfsFactory instance) {
		LfsFactory.instance = instance;
	}

	/**
	 * Whether LFS support is available
	 *
	 * @return whether LFS support is available
	 */
	public boolean isAvailable() {
		return false;
	}

	/**
	 * Apply clean filtering to the given stream, writing the file content to
	 * the LFS storage if required and returning a stream to the LFS pointer
	 * instead.
	 *
	 * @param db
	 *            the repository
	 * @param input
	 *            the original input
	 * @param length
	 *            the expected input stream length
	 * @param attribute
	 *            the attribute used to check for LFS enablement (i.e. "merge",
	 *            "diff", "filter" from .gitattributes).
	 * @return a stream to the content that should be written to the object
	 *         store along with the expected length of the stream. the original
	 *         stream is not applicable.
	 * @throws IOException
	 *             in case of an error
	 */
	public LfsInputStream applyCleanFilter(Repository db,
			InputStream input, long length, Attribute attribute)
			throws IOException {
		return new LfsInputStream(input, length);
	}

	/**
	 * Apply smudge filtering to a given loader, potentially redirecting it to a
	 * LFS blob which is downloaded on demand.
	 *
	 * @param db
	 *            the repository
	 * @param loader
	 *            the loader for the blob
	 * @param attribute
	 *            the attribute used to check for LFS enablement (i.e. "merge",
	 *            "diff", "filter" from .gitattributes).
	 * @return a loader for the actual data of a blob, or the original loader in
	 *         case LFS is not applicable.
	 * @throws IOException
	 *             if an IO error occurred
	 */
	public ObjectLoader applySmudgeFilter(Repository db,
			ObjectLoader loader, Attribute attribute) throws IOException {
		return loader;
	}

	/**
	 * Retrieve a pre-push hook to be applied using the default error stream.
	 *
	 * @param repo
	 *            the {@link Repository} the hook is applied to.
	 * @param outputStream
	 *            output stream
	 * @return a {@link PrePushHook} implementation or <code>null</code>
	 */
	@Nullable
	public PrePushHook getPrePushHook(Repository repo,
			PrintStream outputStream) {
		return null;
	}

	/**
	 * Retrieve a pre-push hook to be applied.
	 *
	 * @param repo
	 *            the {@link Repository} the hook is applied to.
	 * @param outputStream
	 *            output stream
	 * @param errorStream
	 *            error stream
	 * @return a {@link PrePushHook} implementation or <code>null</code>
	 * @since 5.6
	 */
	@Nullable
	public PrePushHook getPrePushHook(Repository repo, PrintStream outputStream,
			PrintStream errorStream) {
		return getPrePushHook(repo, outputStream);
	}

	/**
	 * Retrieve an {@link LfsInstallCommand} which can be used to enable LFS
	 * support (if available) either per repository or for the user.
	 *
	 * @return a command to install LFS support.
	 */
	@Nullable
	public LfsInstallCommand getInstallCommand() {
		return null;
	}

	/**
	 * Whether LFS is enabled
	 *
	 * @param db
	 *            the repository to check
	 * @return whether LFS is enabled for the given repository locally or
	 *         globally.
	 */
	public boolean isEnabled(Repository db) {
		return false;
	}

	/**
	 * Get git attributes for given path
	 *
	 * @param db
	 *            the repository
	 * @param path
	 *            the path to find attributes for
	 * @return the {@link Attributes} for the given path.
	 * @throws IOException
	 *             in case of an error
	 */
	public static Attributes getAttributesForPath(Repository db, String path)
			throws IOException {
		try (TreeWalk walk = new TreeWalk(db)) {
			walk.addTree(new FileTreeIterator(db));
			PathFilter f = PathFilter.create(path);
			walk.setFilter(f);
			walk.setRecursive(false);
			Attributes attr = null;
			while (walk.next()) {
				if (f.isDone(walk)) {
					attr = walk.getAttributes();
					break;
				} else if (walk.isSubtree()) {
					walk.enterSubtree();
				}
			}
			if (attr == null) {
				throw new IOException(MessageFormat
						.format(JGitText.get().noPathAttributesFound, path));
			}

			return attr;
		}
	}

	/**
	 * Get attributes for given path and commit
	 *
	 * @param db
	 *            the repository
	 * @param path
	 *            the path to find attributes for
	 * @param commit
	 *            the commit to inspect.
	 * @return the {@link Attributes} for the given path.
	 * @throws IOException
	 *             in case of an error
	 */
	public static Attributes getAttributesForPath(Repository db, String path,
			RevCommit commit) throws IOException {
		if (commit == null) {
			return getAttributesForPath(db, path);
		}

		try (TreeWalk walk = TreeWalk.forPath(db, path, commit.getTree())) {
			Attributes attr = walk == null ? null : walk.getAttributes();
			if (attr == null) {
				throw new IOException(MessageFormat
						.format(JGitText.get().noPathAttributesFound, path));
			}

			return attr;
		}
	}

	/**
	 * Encapsulate a potentially exchanged {@link InputStream} along with the
	 * expected stream content length.
	 */
	public static final class LfsInputStream extends InputStream {
		/**
		 * The actual stream.
		 */
		private InputStream stream;

		/**
		 * The expected stream content length.
		 */
		private long length;

		/**
		 * Create a new wrapper around a certain stream
		 *
		 * @param stream
		 *            the stream to wrap. the stream will be closed on
		 *            {@link #close()}.
		 * @param length
		 *            the expected length of the stream
		 */
		public LfsInputStream(InputStream stream, long length) {
			this.stream = stream;
			this.length = length;
		}

		/**
		 * Create a new wrapper around a temporary buffer.
		 *
		 * @param buffer
		 *            the buffer to initialize stream and length from. The
		 *            buffer will be destroyed on {@link #close()}
		 * @throws IOException
		 *             in case of an error opening the stream to the buffer.
		 */
		public LfsInputStream(TemporaryBuffer buffer) throws IOException {
			this.stream = buffer.openInputStreamWithAutoDestroy();
			this.length = buffer.length();
		}

		@Override
		public void close() throws IOException {
			stream.close();
		}

		@Override
		public int read() throws IOException {
			return stream.read();
		}

		@Override
		public int read(byte[] b, int off, int len) throws IOException {
			return stream.read(b, off, len);
		}

		/**
		 * Get stream length
		 *
		 * @return the length of the stream
		 */
		public long getLength() {
			return length;
		}
	}

	/**
	 * A command to enable LFS. Optionally set a {@link Repository} to enable
	 * locally on the repository only.
	 */
	public interface LfsInstallCommand extends Callable<Void> {
		/**
		 * Set the repository to enable LFS for
		 *
		 * @param repo
		 *            the repository to enable support for.
		 * @return The {@link LfsInstallCommand} for chaining.
		 */
		public LfsInstallCommand setRepository(Repository repo);
	}

}