You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

SmudgeFilter.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281
  1. /*
  2. * Copyright (C) 2016, Christian Halstrick <christian.halstrick@sap.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.lfs;
  44. import static java.nio.charset.StandardCharsets.UTF_8;
  45. import java.io.IOException;
  46. import java.io.InputStream;
  47. import java.io.InputStreamReader;
  48. import java.io.OutputStream;
  49. import java.nio.file.Files;
  50. import java.nio.file.Path;
  51. import java.text.MessageFormat;
  52. import java.util.ArrayList;
  53. import java.util.Collection;
  54. import java.util.HashMap;
  55. import java.util.Map;
  56. import org.eclipse.jgit.attributes.FilterCommand;
  57. import org.eclipse.jgit.attributes.FilterCommandFactory;
  58. import org.eclipse.jgit.attributes.FilterCommandRegistry;
  59. import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
  60. import org.eclipse.jgit.lfs.internal.LfsText;
  61. import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
  62. import org.eclipse.jgit.lfs.lib.Constants;
  63. import org.eclipse.jgit.lib.Repository;
  64. import org.eclipse.jgit.transport.http.HttpConnection;
  65. import org.eclipse.jgit.util.HttpSupport;
  66. import com.google.gson.Gson;
  67. import com.google.gson.stream.JsonReader;
  68. /**
  69. * Built-in LFS smudge filter
  70. *
  71. * When content is read from git's object-database and written to the filesystem
  72. * and this filter is configured for that content, then this filter will replace
  73. * the content of LFS pointer files with the original content. This happens e.g.
  74. * when a checkout needs to update a working tree file which is under LFS
  75. * control.
  76. *
  77. * @since 4.6
  78. */
  79. public class SmudgeFilter extends FilterCommand {
  80. /**
  81. * Max number of bytes to copy in a single {@link #run()} call.
  82. */
  83. private static final int MAX_COPY_BYTES = 1024 * 1024 * 256;
  84. /**
  85. * The factory is responsible for creating instances of
  86. * {@link org.eclipse.jgit.lfs.SmudgeFilter}
  87. */
  88. public final static FilterCommandFactory FACTORY = new FilterCommandFactory() {
  89. @Override
  90. public FilterCommand create(Repository db, InputStream in,
  91. OutputStream out) throws IOException {
  92. return new SmudgeFilter(db, in, out);
  93. }
  94. };
  95. /**
  96. * Register this filter in JGit
  97. */
  98. static void register() {
  99. FilterCommandRegistry
  100. .register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
  101. + Constants.ATTR_FILTER_DRIVER_PREFIX
  102. + org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE,
  103. FACTORY);
  104. }
  105. /**
  106. * Constructor for SmudgeFilter.
  107. *
  108. * @param db
  109. * a {@link org.eclipse.jgit.lib.Repository} object.
  110. * @param in
  111. * a {@link java.io.InputStream} object. The stream is closed in
  112. * any case.
  113. * @param out
  114. * a {@link java.io.OutputStream} object.
  115. * @throws java.io.IOException
  116. * in case of an error
  117. */
  118. public SmudgeFilter(Repository db, InputStream in, OutputStream out)
  119. throws IOException {
  120. super(in, out);
  121. try {
  122. Lfs lfs = new Lfs(db);
  123. LfsPointer res = LfsPointer.parseLfsPointer(in);
  124. if (res != null) {
  125. AnyLongObjectId oid = res.getOid();
  126. Path mediaFile = lfs.getMediaFile(oid);
  127. if (!Files.exists(mediaFile)) {
  128. downloadLfsResource(lfs, db, res);
  129. }
  130. this.in = Files.newInputStream(mediaFile);
  131. }
  132. } finally {
  133. in.close(); // make sure the swapped stream is closed properly.
  134. }
  135. }
  136. /**
  137. * Download content which is hosted on a LFS server
  138. *
  139. * @param lfs
  140. * local {@link Lfs} storage.
  141. * @param db
  142. * the repository to work with
  143. * @param res
  144. * the objects to download
  145. * @return the paths of all mediafiles which have been downloaded
  146. * @throws IOException
  147. * @since 4.11
  148. */
  149. public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
  150. LfsPointer... res) throws IOException {
  151. Collection<Path> downloadedPaths = new ArrayList<>();
  152. Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
  153. for (LfsPointer p : res) {
  154. oidStr2ptr.put(p.getOid().name(), p);
  155. }
  156. HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
  157. HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
  158. Gson gson = Protocol.gson();
  159. lfsServerConn.getOutputStream()
  160. .write(gson
  161. .toJson(LfsConnectionFactory
  162. .toRequest(Protocol.OPERATION_DOWNLOAD, res))
  163. .getBytes(UTF_8));
  164. int responseCode = lfsServerConn.getResponseCode();
  165. if (responseCode != HttpConnection.HTTP_OK) {
  166. throw new IOException(
  167. MessageFormat.format(LfsText.get().serverFailure,
  168. lfsServerConn.getURL(),
  169. Integer.valueOf(responseCode)));
  170. }
  171. try (JsonReader reader = new JsonReader(
  172. new InputStreamReader(lfsServerConn.getInputStream(),
  173. UTF_8))) {
  174. Protocol.Response resp = gson.fromJson(reader,
  175. Protocol.Response.class);
  176. for (Protocol.ObjectInfo o : resp.objects) {
  177. if (o.error != null) {
  178. throw new IOException(
  179. MessageFormat.format(LfsText.get().protocolError,
  180. Integer.valueOf(o.error.code),
  181. o.error.message));
  182. }
  183. if (o.actions == null) {
  184. continue;
  185. }
  186. LfsPointer ptr = oidStr2ptr.get(o.oid);
  187. if (ptr == null) {
  188. // received an object we didn't request
  189. continue;
  190. }
  191. if (ptr.getSize() != o.size) {
  192. throw new IOException(MessageFormat.format(
  193. LfsText.get().inconsistentContentLength,
  194. lfsServerConn.getURL(), Long.valueOf(ptr.getSize()),
  195. Long.valueOf(o.size)));
  196. }
  197. Protocol.Action downloadAction = o.actions
  198. .get(Protocol.OPERATION_DOWNLOAD);
  199. if (downloadAction == null || downloadAction.href == null) {
  200. continue;
  201. }
  202. HttpConnection contentServerConn = LfsConnectionFactory
  203. .getLfsContentConnection(db, downloadAction,
  204. HttpSupport.METHOD_GET);
  205. responseCode = contentServerConn.getResponseCode();
  206. if (responseCode != HttpConnection.HTTP_OK) {
  207. throw new IOException(
  208. MessageFormat.format(LfsText.get().serverFailure,
  209. contentServerConn.getURL(),
  210. Integer.valueOf(responseCode)));
  211. }
  212. Path path = lfs.getMediaFile(ptr.getOid());
  213. path.getParent().toFile().mkdirs();
  214. try (InputStream contentIn = contentServerConn
  215. .getInputStream()) {
  216. long bytesCopied = Files.copy(contentIn, path);
  217. if (bytesCopied != o.size) {
  218. throw new IOException(MessageFormat.format(
  219. LfsText.get().wrongAmoutOfDataReceived,
  220. contentServerConn.getURL(),
  221. Long.valueOf(bytesCopied),
  222. Long.valueOf(o.size)));
  223. }
  224. downloadedPaths.add(path);
  225. }
  226. }
  227. }
  228. return downloadedPaths;
  229. }
  230. /** {@inheritDoc} */
  231. @Override
  232. public int run() throws IOException {
  233. try {
  234. int totalRead = 0;
  235. int length = 0;
  236. if (in != null) {
  237. byte[] buf = new byte[8192];
  238. while ((length = in.read(buf)) != -1) {
  239. out.write(buf, 0, length);
  240. totalRead += length;
  241. // when threshold reached, loop back to the caller.
  242. // otherwise we could only support files up to 2GB (int
  243. // return type) properly. we will be called again as long as
  244. // we don't return -1 here.
  245. if (totalRead >= MAX_COPY_BYTES) {
  246. // leave streams open - we need them in the next call.
  247. return totalRead;
  248. }
  249. }
  250. }
  251. if (totalRead == 0 && length == -1) {
  252. // we're totally done :) cleanup all streams
  253. in.close();
  254. out.close();
  255. return length;
  256. }
  257. return totalRead;
  258. } catch (IOException e) {
  259. in.close(); // clean up - we swapped this stream.
  260. out.close();
  261. throw e;
  262. }
  263. }
  264. }