Du kannst nicht mehr als 25 Themen auswählen Themen müssen mit entweder einem Buchstaben oder einer Ziffer beginnen. Sie können Bindestriche („-“) enthalten und bis zu 35 Zeichen lang sein.

SmudgeFilter.java 7.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  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. || responseCode == HttpConnection.HTTP_NOT_AUTHORITATIVE)) {
  128. throw new IOException(
  129. MessageFormat.format(LfsText.get().serverFailure,
  130. lfsServerConn.getURL(),
  131. Integer.valueOf(responseCode)));
  132. }
  133. try (JsonReader reader = new JsonReader(
  134. new InputStreamReader(lfsServerConn.getInputStream(),
  135. UTF_8))) {
  136. Protocol.Response resp = gson.fromJson(reader,
  137. Protocol.Response.class);
  138. for (Protocol.ObjectInfo o : resp.objects) {
  139. if (o.error != null) {
  140. throw new IOException(
  141. MessageFormat.format(LfsText.get().protocolError,
  142. Integer.valueOf(o.error.code),
  143. o.error.message));
  144. }
  145. if (o.actions == null) {
  146. continue;
  147. }
  148. LfsPointer ptr = oidStr2ptr.get(o.oid);
  149. if (ptr == null) {
  150. // received an object we didn't request
  151. continue;
  152. }
  153. if (ptr.getSize() != o.size) {
  154. throw new IOException(MessageFormat.format(
  155. LfsText.get().inconsistentContentLength,
  156. lfsServerConn.getURL(), Long.valueOf(ptr.getSize()),
  157. Long.valueOf(o.size)));
  158. }
  159. Protocol.Action downloadAction = o.actions
  160. .get(Protocol.OPERATION_DOWNLOAD);
  161. if (downloadAction == null || downloadAction.href == null) {
  162. continue;
  163. }
  164. HttpConnection contentServerConn = LfsConnectionFactory
  165. .getLfsContentConnection(db, downloadAction,
  166. HttpSupport.METHOD_GET);
  167. responseCode = contentServerConn.getResponseCode();
  168. if (responseCode != HttpConnection.HTTP_OK) {
  169. throw new IOException(
  170. MessageFormat.format(LfsText.get().serverFailure,
  171. contentServerConn.getURL(),
  172. Integer.valueOf(responseCode)));
  173. }
  174. Path path = lfs.getMediaFile(ptr.getOid());
  175. Path parent = path.getParent();
  176. if (parent != null) {
  177. parent.toFile().mkdirs();
  178. }
  179. try (InputStream contentIn = contentServerConn
  180. .getInputStream()) {
  181. long bytesCopied = Files.copy(contentIn, path);
  182. if (bytesCopied != o.size) {
  183. throw new IOException(MessageFormat.format(
  184. LfsText.get().wrongAmoutOfDataReceived,
  185. contentServerConn.getURL(),
  186. Long.valueOf(bytesCopied),
  187. Long.valueOf(o.size)));
  188. }
  189. downloadedPaths.add(path);
  190. }
  191. }
  192. }
  193. return downloadedPaths;
  194. }
  195. /** {@inheritDoc} */
  196. @Override
  197. public int run() throws IOException {
  198. try {
  199. int totalRead = 0;
  200. int length = 0;
  201. if (in != null) {
  202. byte[] buf = new byte[8192];
  203. while ((length = in.read(buf)) != -1) {
  204. out.write(buf, 0, length);
  205. totalRead += length;
  206. // when threshold reached, loop back to the caller.
  207. // otherwise we could only support files up to 2GB (int
  208. // return type) properly. we will be called again as long as
  209. // we don't return -1 here.
  210. if (totalRead >= MAX_COPY_BYTES) {
  211. // leave streams open - we need them in the next call.
  212. return totalRead;
  213. }
  214. }
  215. }
  216. if (totalRead == 0 && length == -1) {
  217. // we're totally done :) cleanup all streams
  218. in.close();
  219. out.close();
  220. return length;
  221. }
  222. return totalRead;
  223. } catch (IOException e) {
  224. in.close(); // clean up - we swapped this stream.
  225. out.close();
  226. throw e;
  227. }
  228. }
  229. }