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 7.3KB

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