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 }