summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMarkus Duft <markus.duft@ssi-schaefer.com>2017-11-10 11:10:28 +0100
committerMatthias Sohn <matthias.sohn@sap.com>2018-02-27 18:32:45 +0100
commitc0bb992845e6ba5df9f420739fe9075ed20e9ee2 (patch)
tree23e01159ba98b6d5040e4d47d4fbfdf0dc02e0c5
parent12a589fb577ebbd464cede7b06a7949c84a51350 (diff)
downloadjgit-c0bb992845e6ba5df9f420739fe9075ed20e9ee2.tar.gz
jgit-c0bb992845e6ba5df9f420739fe9075ed20e9ee2.zip
LFS: pre-push upload support
If JGit built in LFS support is enabled for the current repository (or user/system), any existing pre-push hook will cause an exception for the time beeing, as only a single pre-push hook is supported. Thus either native pre-push hooks OR JGit built-in LFS support may be enabled currently, but not both. Change-Id: Ie7d2b90e26e948d9cca3d05a7a19489488c75895 Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
-rw-r--r--org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF6
-rw-r--r--org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java165
-rw-r--r--org.eclipse.jgit.lfs/.settings/.api_filters11
-rw-r--r--org.eclipse.jgit.lfs/META-INF/MANIFEST.MF3
-rw-r--r--org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties3
-rw-r--r--org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java22
-rw-r--r--org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java272
-rw-r--r--org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java193
-rw-r--r--org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java294
-rw-r--r--org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java1
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties1
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java28
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java1
15 files changed, 837 insertions, 184 deletions
diff --git a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
index af046aecc7..1589f42285 100644
--- a/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs.server.test/META-INF/MANIFEST.MF
@@ -33,13 +33,17 @@ Import-Package: javax.servlet;version="[3.1.0,4.0.0)",
org.eclipse.jgit.junit;version="[4.11.0,4.12.0)",
org.eclipse.jgit.junit.http;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lfs;version="[4.11.0,4.12.0)",
- org.eclipse.jgit.lfs.lib;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lfs.errors;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.lfs.lib;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lfs.server;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lfs.server.fs;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lfs.test;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lib;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.revwalk;version="[4.11.0,4.12.0)",
org.eclipse.jgit.storage.file;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.transport;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.treewalk;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.treewalk.filter;version="[4.11.0,4.12.0)",
org.eclipse.jgit.util;version="[4.11.0,4.12.0)",
org.hamcrest.core;version="[1.1.0,2.0.0)",
org.junit;version="[4.12,5.0.0)",
diff --git a/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
new file mode 100644
index 0000000000..82566f351a
--- /dev/null
+++ b/org.eclipse.jgit.lfs.server.test/tst/org/eclipse/jgit/lfs/server/fs/PushTest.java
@@ -0,0 +1,165 @@
+/*
+ * Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.server.fs;
+
+import static org.junit.Assert.assertEquals;
+
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.RemoteAddCommand;
+import org.eclipse.jgit.junit.JGitTestUtil;
+import org.eclipse.jgit.junit.TestRepository;
+import org.eclipse.jgit.lfs.CleanFilter;
+import org.eclipse.jgit.lfs.SmudgeFilter;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectLoader;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.treewalk.TreeWalk;
+import org.eclipse.jgit.treewalk.filter.PathFilter;
+import org.eclipse.jgit.util.FileUtils;
+import org.eclipse.jgit.util.IO;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+public class PushTest extends LfsServerTest {
+
+ Git git;
+
+ private TestRepository localDb;
+
+ private Repository remoteDb;
+
+ @Override
+ @Before
+ public void setup() throws Exception {
+ super.setup();
+
+ SmudgeFilter.register();
+ CleanFilter.register();
+
+ Path rtmp = Files.createTempDirectory("jgit_test_");
+ remoteDb = FileRepositoryBuilder.create(rtmp.toFile());
+ remoteDb.create(true);
+
+ Path tmp = Files.createTempDirectory("jgit_test_");
+ Repository db = FileRepositoryBuilder
+ .create(tmp.resolve(".git").toFile());
+ db.create(false);
+ StoredConfig cfg = db.getConfig();
+ cfg.setString("filter", "lfs", "usejgitbuiltin", "true");
+ cfg.setString("lfs", null, "url", server.getURI().toString() + "/lfs");
+ cfg.save();
+
+ localDb = new TestRepository<>(db);
+ localDb.branch("master").commit().add(".gitattributes",
+ "*.bin filter=lfs diff=lfs merge=lfs -text ").create();
+ git = Git.wrap(db);
+
+ URIish uri = new URIish(
+ "file://" + remoteDb.getDirectory());
+ RemoteAddCommand radd = git.remoteAdd();
+ radd.setUri(uri);
+ radd.setName(Constants.DEFAULT_REMOTE_NAME);
+ radd.call();
+
+ git.checkout().setName("master").call();
+ git.push().call();
+ }
+
+ @After
+ public void cleanup() throws Exception {
+ remoteDb.close();
+ localDb.getRepository().close();
+ FileUtils.delete(localDb.getRepository().getWorkTree(),
+ FileUtils.RECURSIVE);
+ FileUtils.delete(remoteDb.getDirectory(), FileUtils.RECURSIVE);
+ }
+
+ @Test
+ public void testPushSimple() throws Exception {
+ JGitTestUtil.writeTrashFile(localDb.getRepository(), "a.bin",
+ "1234567");
+ git.add().addFilepattern("a.bin").call();
+ RevCommit commit = git.commit().setMessage("add lfs blob").call();
+ git.push().call();
+
+ // check object in remote db, should be LFS pointer
+ ObjectId id = commit.getId();
+ try (RevWalk walk = new RevWalk(remoteDb)) {
+ RevCommit rc = walk.parseCommit(id);
+ try (TreeWalk tw = new TreeWalk(walk.getObjectReader())) {
+ tw.addTree(rc.getTree());
+ tw.setFilter(PathFilter.create("a.bin"));
+ tw.next();
+
+ assertEquals(tw.getPathString(), "a.bin");
+ ObjectLoader ldr = walk.getObjectReader()
+ .open(tw.getObjectId(0), Constants.OBJ_BLOB);
+ try(InputStream is = ldr.openStream()) {
+ assertEquals(
+ "version https://git-lfs.github.com/spec/v1\noid sha256:8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414\nsize 7\n",
+ new String(IO
+ .readWholeStream(is,
+ (int) ldr.getSize())
+ .array()));
+ }
+ }
+
+ }
+
+ assertEquals(
+ "[POST /lfs/objects/batch 200, PUT /lfs/objects/8bb0cf6eb9b17d0f7d22b456f121257dc1254e1f01665370476383ea776df414 200]",
+ server.getRequests().toString());
+ }
+
+}
diff --git a/org.eclipse.jgit.lfs/.settings/.api_filters b/org.eclipse.jgit.lfs/.settings/.api_filters
new file mode 100644
index 0000000000..097fd20658
--- /dev/null
+++ b/org.eclipse.jgit.lfs/.settings/.api_filters
@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<component id="org.eclipse.jgit.lfs" version="2">
+ <resource path="src/org/eclipse/jgit/lfs/LfsPointer.java" type="org.eclipse.jgit.lfs.LfsPointer">
+ <filter id="336658481">
+ <message_arguments>
+ <message_argument value="org.eclipse.jgit.lfs.LfsPointer"/>
+ <message_argument value="SIZE_THRESHOLD"/>
+ </message_arguments>
+ </filter>
+ </resource>
+</component>
diff --git a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
index 740d7bbb12..798fccdfc4 100644
--- a/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit.lfs/META-INF/MANIFEST.MF
@@ -16,11 +16,14 @@ Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
org.apache.http.impl.client;version="[4.2.6,5.0.0)",
org.apache.http.impl.conn;version="[4.2.6,5.0.0)",
org.eclipse.jgit.annotations;version="[4.11.0,4.12.0)";resolution:=optional,
+ org.eclipse.jgit.api.errors;version="[4.11.0,4.12.0)",
org.eclipse.jgit.attributes;version="[4.11.0,4.12.0)",
org.eclipse.jgit.errors;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.hooks;version="[4.11.0,4.12.0)",
org.eclipse.jgit.internal.storage.file;version="[4.11.0,4.12.0)",
org.eclipse.jgit.lib;version="[4.11.0,4.12.0)",
org.eclipse.jgit.nls;version="[4.11.0,4.12.0)",
+ org.eclipse.jgit.revwalk;version="[4.11.0,4.12.0)",
org.eclipse.jgit.storage.file;version="[4.11.0,4.12.0)",
org.eclipse.jgit.transport;version="[4.11.0,4.12.0)",
org.eclipse.jgit.transport.http;version="[4.11.0,4.12.0)",
diff --git a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
index 19d5ec587f..29cee3a8e0 100644
--- a/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
+++ b/org.eclipse.jgit.lfs/resources/org/eclipse/jgit/lfs/internal/LfsText.properties
@@ -14,4 +14,5 @@ lfsFailedToGetRepository=failed to get repository {0}
lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL"
serverFailure=When trying to open a connection to {0} the server responded with an error code. rc={1}
wrongAmoutOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected
-userConfigInvalid="User config file {0} invalid {1}" \ No newline at end of file
+userConfigInvalid="User config file {0} invalid {1}"
+missingLocalObject="Local Object {0} is missing" \ No newline at end of file
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
index 360453116f..0e3830c098 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPointer.java
@@ -64,7 +64,7 @@ import org.eclipse.jgit.lfs.lib.LongObjectId;
*
* @since 4.6
*/
-public class LfsPointer {
+public class LfsPointer implements Comparable<LfsPointer> {
/**
* The version of the LfsPointer file format
*/
@@ -77,6 +77,13 @@ public class LfsPointer {
public static final String VERSION_LEGACY = "https://hawser.github.com/spec/v1"; //$NON-NLS-1$
/**
+ * Don't inspect files that are larger than this threshold to avoid
+ * excessive reading. No pointer file should be larger than this.
+ * @since 4.11
+ */
+ public static final int SIZE_THRESHOLD = 200;
+
+ /**
* The name of the hash function as used in the pointer files. This will
* evaluate to "sha256"
*/
@@ -185,5 +192,18 @@ public class LfsPointer {
return "LfsPointer: oid=" + oid.name() + ", size=" //$NON-NLS-1$ //$NON-NLS-2$
+ size;
}
+
+ /**
+ * @since 4.11
+ */
+ @Override
+ public int compareTo(LfsPointer o) {
+ int x = getOid().compareTo(o.getOid());
+ if (x != 0) {
+ return x;
+ }
+
+ return (int) (getSize() - o.getSize());
+ }
}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
new file mode 100644
index 0000000000..ffc1ee39a7
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/LfsPrePushHook.java
@@ -0,0 +1,272 @@
+/*
+ * Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs;
+
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static org.eclipse.jgit.lfs.internal.LfsConnectionFactory.toRequest;
+import static org.eclipse.jgit.lfs.Protocol.OPERATION_UPLOAD;
+import static org.eclipse.jgit.transport.http.HttpConnection.HTTP_OK;
+import static org.eclipse.jgit.util.HttpSupport.METHOD_POST;
+import static org.eclipse.jgit.util.HttpSupport.METHOD_PUT;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.text.MessageFormat;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.TreeSet;
+
+import org.eclipse.jgit.api.errors.AbortedByHookException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.hooks.PrePushHook;
+import org.eclipse.jgit.lfs.Protocol.ObjectInfo;
+import org.eclipse.jgit.lfs.errors.CorruptMediaFile;
+import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
+import org.eclipse.jgit.lfs.internal.LfsText;
+import org.eclipse.jgit.lib.AnyObjectId;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.ObjectReader;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.RefDatabase;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.ObjectWalk;
+import org.eclipse.jgit.revwalk.RevObject;
+import org.eclipse.jgit.transport.RemoteRefUpdate;
+import org.eclipse.jgit.transport.http.HttpConnection;
+
+import com.google.gson.Gson;
+import com.google.gson.stream.JsonReader;
+
+/**
+ * Pre-push hook that handles uploading LFS artefacts.
+ *
+ * @since 4.11
+ */
+public class LfsPrePushHook extends PrePushHook {
+
+ private static final String EMPTY = ""; //$NON-NLS-1$
+ private Collection<RemoteRefUpdate> refs;
+
+ /**
+ * @param repo
+ * the repository
+ * @param outputStream
+ * not used by this implementation
+ */
+ public LfsPrePushHook(Repository repo, PrintStream outputStream) {
+ super(repo, outputStream);
+ }
+
+ @Override
+ public void setRefs(Collection<RemoteRefUpdate> toRefs) {
+ this.refs = toRefs;
+ }
+
+ @Override
+ public String call() throws IOException, AbortedByHookException {
+ Set<LfsPointer> toPush = findObjectsToPush();
+ if (toPush.isEmpty()) {
+ return EMPTY;
+ }
+ HttpConnection api = LfsConnectionFactory.getLfsConnection(
+ getRepository(), METHOD_POST, OPERATION_UPLOAD);
+ Map<String, LfsPointer> oid2ptr = requestBatchUpload(api, toPush);
+ uploadContents(api, oid2ptr);
+ return EMPTY;
+ }
+
+ private Set<LfsPointer> findObjectsToPush() throws IOException,
+ MissingObjectException, IncorrectObjectTypeException {
+ Set<LfsPointer> toPush = new TreeSet<>();
+
+ try (ObjectWalk walk = new ObjectWalk(getRepository())) {
+ for (RemoteRefUpdate up : refs) {
+ walk.setRewriteParents(false);
+ excludeRemoteRefs(walk);
+ walk.markStart(walk.parseCommit(up.getNewObjectId()));
+ while (walk.next() != null) {
+ // walk all commits to populate objects
+ }
+ findLfsPointers(toPush, walk);
+ }
+ }
+ return toPush;
+ }
+
+ private static void findLfsPointers(Set<LfsPointer> toPush, ObjectWalk walk)
+ throws MissingObjectException, IncorrectObjectTypeException,
+ IOException {
+ RevObject obj;
+ ObjectReader r = walk.getObjectReader();
+ while ((obj = walk.nextObject()) != null) {
+ if (obj.getType() == Constants.OBJ_BLOB
+ && getObjectSize(r, obj) < LfsPointer.SIZE_THRESHOLD) {
+ LfsPointer ptr = loadLfsPointer(r, obj);
+ if (ptr != null) {
+ toPush.add(ptr);
+ }
+ }
+ }
+ }
+
+ private static long getObjectSize(ObjectReader r, RevObject obj)
+ throws IOException {
+ return r.getObjectSize(obj.getId(), Constants.OBJ_BLOB);
+ }
+
+ private static LfsPointer loadLfsPointer(ObjectReader r, AnyObjectId obj)
+ throws IOException {
+ try (InputStream is = r.open(obj, Constants.OBJ_BLOB).openStream()) {
+ return LfsPointer.parseLfsPointer(is);
+ }
+ }
+
+ private void excludeRemoteRefs(ObjectWalk walk) throws IOException {
+ RefDatabase refDatabase = getRepository().getRefDatabase();
+ Map<String, Ref> remoteRefs = refDatabase.getRefs(remote());
+ for (Ref r : remoteRefs.values()) {
+ ObjectId oid = r.getPeeledObjectId();
+ if (oid == null) {
+ oid = r.getObjectId();
+ }
+ RevObject o = walk.parseAny(oid);
+ if (o.getType() == Constants.OBJ_COMMIT
+ || o.getType() == Constants.OBJ_TAG) {
+ walk.markUninteresting(o);
+ }
+ }
+ }
+
+ private String remote() {
+ String remoteName = getRemoteName() == null
+ ? Constants.DEFAULT_REMOTE_NAME
+ : getRemoteName();
+ return Constants.R_REMOTES + remoteName;
+ }
+
+ private Map<String, LfsPointer> requestBatchUpload(HttpConnection api,
+ Set<LfsPointer> toPush) throws IOException {
+ LfsPointer[] res = toPush.toArray(new LfsPointer[toPush.size()]);
+ Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
+ for (LfsPointer p : res) {
+ oidStr2ptr.put(p.getOid().name(), p);
+ }
+ Gson gson = new Gson();
+ api.getOutputStream().write(
+ gson.toJson(toRequest(OPERATION_UPLOAD, res)).getBytes(UTF_8));
+ int responseCode = api.getResponseCode();
+ if (responseCode != HTTP_OK) {
+ throw new IOException(
+ MessageFormat.format(LfsText.get().serverFailure,
+ api.getURL(), Integer.valueOf(responseCode)));
+ }
+ return oidStr2ptr;
+ }
+
+ private void uploadContents(HttpConnection api,
+ Map<String, LfsPointer> oid2ptr) throws IOException {
+ try (JsonReader reader = new JsonReader(
+ new InputStreamReader(api.getInputStream()))) {
+ for (Protocol.ObjectInfo o : parseObjects(reader)) {
+ if (o.actions == null) {
+ continue;
+ }
+ LfsPointer ptr = oid2ptr.get(o.oid);
+ if (ptr == null) {
+ // received an object we didn't request
+ continue;
+ }
+ Protocol.Action uploadAction = o.actions.get(OPERATION_UPLOAD);
+ if (uploadAction == null || uploadAction.href == null) {
+ continue;
+ }
+
+ Lfs lfs = new Lfs(getRepository());
+ Path path = lfs.getMediaFile(ptr.getOid());
+ if (!Files.exists(path)) {
+ throw new IOException(MessageFormat
+ .format(LfsText.get().missingLocalObject, path));
+ }
+ uploadFile(o, uploadAction, path);
+ }
+ }
+ }
+
+ private List<ObjectInfo> parseObjects(JsonReader reader) {
+ Gson gson = new Gson();
+ Protocol.Response resp = gson.fromJson(reader, Protocol.Response.class);
+ return resp.objects;
+ }
+
+ private void uploadFile(Protocol.ObjectInfo o,
+ Protocol.Action uploadAction, Path path)
+ throws IOException, CorruptMediaFile {
+ HttpConnection contentServer = LfsConnectionFactory
+ .getLfsContentConnection(getRepository(), uploadAction,
+ METHOD_PUT);
+ contentServer.setDoOutput(true);
+ try (OutputStream out = contentServer
+ .getOutputStream()) {
+ long size = Files.copy(path, out);
+ if (size != o.size) {
+ throw new CorruptMediaFile(path, o.size, size);
+ }
+ }
+ int responseCode = contentServer.getResponseCode();
+ if (responseCode != HTTP_OK) {
+ throw new IOException(MessageFormat.format(
+ LfsText.get().serverFailure, contentServer.getURL(),
+ Integer.valueOf(responseCode)));
+ }
+ }
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
index 841c30a335..1b1b8c1e4a 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/SmudgeFilter.java
@@ -42,18 +42,10 @@
*/
package org.eclipse.jgit.lfs;
-import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
-import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
-import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
-import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
-
-import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
-import java.net.ProxySelector;
-import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -61,30 +53,18 @@ import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
-import java.util.LinkedList;
import java.util.Map;
-import java.util.TreeMap;
import org.eclipse.jgit.attributes.FilterCommand;
import org.eclipse.jgit.attributes.FilterCommandFactory;
import org.eclipse.jgit.attributes.FilterCommandRegistry;
-import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
+import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
import org.eclipse.jgit.lfs.internal.LfsText;
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
import org.eclipse.jgit.lfs.lib.Constants;
-import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.lib.StoredConfig;
-import org.eclipse.jgit.transport.HttpConfig;
-import org.eclipse.jgit.transport.HttpTransport;
-import org.eclipse.jgit.transport.RemoteSession;
-import org.eclipse.jgit.transport.SshSessionFactory;
-import org.eclipse.jgit.transport.URIish;
import org.eclipse.jgit.transport.http.HttpConnection;
-import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.HttpSupport;
-import org.eclipse.jgit.util.io.MessageWriter;
-import org.eclipse.jgit.util.io.StreamCopyThread;
import com.google.gson.Gson;
import com.google.gson.stream.JsonReader;
@@ -176,11 +156,14 @@ public class SmudgeFilter extends FilterCommand {
for (LfsPointer p : res) {
oidStr2ptr.put(p.getOid().name(), p);
}
- HttpConnection lfsServerConn = getLfsConnection(db,
- HttpSupport.METHOD_POST);
+ HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
+ HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
Gson gson = new Gson();
lfsServerConn.getOutputStream()
- .write(gson.toJson(body(res)).getBytes(StandardCharsets.UTF_8));
+ .write(gson
+ .toJson(LfsConnectionFactory
+ .toRequest(Protocol.OPERATION_DOWNLOAD, res))
+ .getBytes(StandardCharsets.UTF_8));
int responseCode = lfsServerConn.getResponseCode();
if (responseCode != HttpConnection.HTTP_OK) {
throw new IOException(
@@ -212,21 +195,11 @@ public class SmudgeFilter extends FilterCommand {
if (downloadAction == null || downloadAction.href == null) {
continue;
}
- URL contentUrl = new URL(downloadAction.href);
- HttpConnection contentServerConn = HttpTransport
- .getConnectionFactory().create(contentUrl,
- HttpSupport.proxyFor(ProxySelector.getDefault(),
- contentUrl));
- contentServerConn.setRequestMethod(HttpSupport.METHOD_GET);
- downloadAction.header.forEach(
- (k, v) -> contentServerConn.setRequestProperty(k, v));
- if (contentUrl.getProtocol().equals("https") && !db.getConfig() //$NON-NLS-1$
- .getBoolean(HttpConfig.HTTP, HttpConfig.SSL_VERIFY_KEY,
- true)) {
- HttpSupport.disableSslVerify(contentServerConn);
- }
- contentServerConn.setRequestProperty(HDR_ACCEPT_ENCODING,
- ENCODING_GZIP);
+
+ HttpConnection contentServerConn = LfsConnectionFactory
+ .getLfsContentConnection(db, downloadAction,
+ HttpSupport.METHOD_GET);
+
responseCode = contentServerConn.getResponseCode();
if (responseCode != HttpConnection.HTTP_OK) {
throw new IOException(
@@ -253,148 +226,6 @@ public class SmudgeFilter extends FilterCommand {
return downloadedPaths;
}
- private Protocol.Request body(LfsPointer... resources) {
- Protocol.Request req = new Protocol.Request();
- req.operation = Protocol.OPERATION_DOWNLOAD;
- if (resources != null) {
- req.objects = new LinkedList<>();
- for (LfsPointer res : resources) {
- Protocol.ObjectSpec o = new Protocol.ObjectSpec();
- o.oid = res.getOid().getName();
- o.size = res.getSize();
- req.objects.add(o);
- }
- }
- return req;
- }
-
- /**
- * Determine URL of LFS server by looking into config parameters lfs.url,
- * lfs.<remote>.url or remote.<remote>.url. The LFS server URL is computed
- * from remote.<remote>.url by appending "/info/lfs"
- *
- * @param db
- * the repository to work with
- * @param method
- * the method (GET,PUT,...) of the request this connection will
- * be used for
- * @return the url for the lfs server. e.g.
- * "https://github.com/github/git-lfs.git/info/lfs"
- * @throws IOException
- */
- private HttpConnection getLfsConnection(Repository db, String method)
- throws IOException {
- StoredConfig config = db.getConfig();
- String lfsEndpoint = config.getString(Constants.LFS, null,
- ConfigConstants.CONFIG_KEY_URL);
- Map<String, String> additionalHeaders = new TreeMap<>();
- if (lfsEndpoint == null) {
- String remoteUrl = null;
- for (String remote : db.getRemoteNames()) {
- lfsEndpoint = config.getString(Constants.LFS, remote,
- ConfigConstants.CONFIG_KEY_URL);
- if (lfsEndpoint == null
- && (remote.equals(
- org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME))) {
- remoteUrl = config.getString(
- ConfigConstants.CONFIG_KEY_REMOTE, remote,
- ConfigConstants.CONFIG_KEY_URL);
- }
- break;
- }
- if (lfsEndpoint == null && remoteUrl != null) {
- try {
- URIish u = new URIish(remoteUrl);
-
- if ("ssh".equals(u.getScheme())) { //$NON-NLS-1$
- // discover and authenticate; git-lfs does "ssh -p
- // <port> -- <host> git-lfs-authenticate <project>
- // <upload/download>"
- String json = runSshCommand(u.setPath(""), db.getFS(), //$NON-NLS-1$
- "git-lfs-authenticate " + extractProjectName(u) //$NON-NLS-1$
- + " " + Protocol.OPERATION_DOWNLOAD); //$NON-NLS-1$
-
- Protocol.Action action = new Gson().fromJson(json,
- Protocol.Action.class);
- additionalHeaders.putAll(action.header);
- lfsEndpoint = action.href;
- } else {
- lfsEndpoint = remoteUrl + Protocol.INFO_LFS_ENDPOINT;
- }
- } catch (Exception e) {
- lfsEndpoint = null; // could not discover
- }
- } else {
- lfsEndpoint = lfsEndpoint + Protocol.INFO_LFS_ENDPOINT;
- }
- }
- if (lfsEndpoint == null) {
- throw new LfsConfigInvalidException(LfsText.get().lfsNoDownloadUrl);
- }
- URL url = new URL(lfsEndpoint + Protocol.OBJECTS_LFS_ENDPOINT);
- HttpConnection connection = HttpTransport.getConnectionFactory().create(
- url, HttpSupport.proxyFor(ProxySelector.getDefault(), url));
- connection.setDoOutput(true);
- if (url.getProtocol().equals("https") //$NON-NLS-1$
- && !config.getBoolean(HttpConfig.HTTP,
- HttpConfig.SSL_VERIFY_KEY, true)) {
- HttpSupport.disableSslVerify(connection);
- }
- connection.setRequestMethod(method);
- connection.setRequestProperty(HDR_ACCEPT,
- Protocol.CONTENTTYPE_VND_GIT_LFS_JSON);
- connection.setRequestProperty(HDR_CONTENT_TYPE,
- Protocol.CONTENTTYPE_VND_GIT_LFS_JSON);
- additionalHeaders
- .forEach((k, v) -> connection.setRequestProperty(k, v));
- return connection;
- }
-
- private String extractProjectName(URIish u) {
- String path = u.getPath().substring(1);
- if (path.endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT)) {
- return path.substring(0, path.length() - 4);
- } else {
- return path;
- }
- }
-
- private String runSshCommand(URIish sshUri, FS fs, String command)
- throws IOException {
- RemoteSession session = null;
- Process process = null;
- StreamCopyThread errorThread = null;
- try (MessageWriter stderr = new MessageWriter()) {
- session = SshSessionFactory.getInstance().getSession(sshUri,
- null, fs, 5_000);
- process = session.exec(command, 0);
- errorThread = new StreamCopyThread(process.getErrorStream(),
- stderr.getRawStream());
- errorThread.start();
- try (BufferedReader reader = new BufferedReader(
- new InputStreamReader(process.getInputStream(),
- org.eclipse.jgit.lib.Constants.CHARSET))) {
- return reader.readLine();
- }
- } finally {
- if (process != null) {
- process.destroy();
- }
- if (errorThread != null) {
- try {
- errorThread.halt();
- } catch (InterruptedException e) {
- // Stop waiting and return anyway.
- } finally {
- errorThread = null;
- }
- }
- if (session != null) {
- SshSessionFactory.getInstance().releaseSession(session);
- }
- }
- }
-
/** {@inheritDoc} */
@Override
public int run() throws IOException {
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
new file mode 100644
index 0000000000..254bd5ae05
--- /dev/null
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsConnectionFactory.java
@@ -0,0 +1,294 @@
+/*
+ * Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com>
+ * and other copyright owners as documented in the project's IP log.
+ *
+ * This program and the accompanying materials are made available
+ * under the terms of the Eclipse Distribution License v1.0 which
+ * accompanies this distribution, is reproduced below, and is
+ * available at http://www.eclipse.org/org/documents/edl-v10.php
+ *
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or
+ * without modification, are permitted provided that the following
+ * conditions are met:
+ *
+ * - Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ *
+ * - Redistributions in binary form must reproduce the above
+ * copyright notice, this list of conditions and the following
+ * disclaimer in the documentation and/or other materials provided
+ * with the distribution.
+ *
+ * - Neither the name of the Eclipse Foundation, Inc. nor the
+ * names of its contributors may be used to endorse or promote
+ * products derived from this software without specific prior
+ * written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
+ * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
+ * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
+ * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
+ * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+ * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+ * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
+ * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+package org.eclipse.jgit.lfs.internal;
+
+import static org.eclipse.jgit.util.HttpSupport.ENCODING_GZIP;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT;
+import static org.eclipse.jgit.util.HttpSupport.HDR_ACCEPT_ENCODING;
+import static org.eclipse.jgit.util.HttpSupport.HDR_CONTENT_TYPE;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.ProxySelector;
+import java.net.URL;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.TreeMap;
+
+import org.eclipse.jgit.annotations.NonNull;
+import org.eclipse.jgit.lfs.LfsPointer;
+import org.eclipse.jgit.lfs.Protocol;
+import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lib.ConfigConstants;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.transport.HttpConfig;
+import org.eclipse.jgit.transport.HttpTransport;
+import org.eclipse.jgit.transport.RemoteSession;
+import org.eclipse.jgit.transport.SshSessionFactory;
+import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.FS;
+import org.eclipse.jgit.util.HttpSupport;
+import org.eclipse.jgit.util.io.MessageWriter;
+import org.eclipse.jgit.util.io.StreamCopyThread;
+
+import com.google.gson.Gson;
+
+/**
+ * Provides means to get a valid LFS connection for a given repository.
+ */
+public class LfsConnectionFactory {
+
+ private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
+ private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$
+
+ /**
+ * Determine URL of LFS server by looking into config parameters lfs.url,
+ * lfs.[remote].url or remote.[remote].url. The LFS server URL is computed
+ * from remote.[remote].url by appending "/info/lfs". In case there is no
+ * URL configured, a SSH remote URI can be used to auto-detect the LFS URI
+ * by using the remote "git-lfs-authenticate" command.
+ *
+ * @param db
+ * the repository to work with
+ * @param method
+ * the method (GET,PUT,...) of the request this connection will
+ * be used for
+ * @param purpose
+ * the action, e.g. Protocol.OPERATION_DOWNLOAD
+ * @return the url for the lfs server. e.g.
+ * "https://github.com/github/git-lfs.git/info/lfs"
+ * @throws IOException
+ */
+ public static HttpConnection getLfsConnection(Repository db, String method,
+ String purpose) throws IOException {
+ StoredConfig config = db.getConfig();
+ Map<String, String> additionalHeaders = new TreeMap<>();
+ String lfsUrl = getLfsUrl(db, purpose, additionalHeaders);
+ URL url = new URL(lfsUrl + Protocol.OBJECTS_LFS_ENDPOINT);
+ HttpConnection connection = HttpTransport.getConnectionFactory().create(
+ url, HttpSupport.proxyFor(ProxySelector.getDefault(), url));
+ connection.setDoOutput(true);
+ if (url.getProtocol().equals(SCHEME_HTTPS)
+ && !config.getBoolean(HttpConfig.HTTP,
+ HttpConfig.SSL_VERIFY_KEY, true)) {
+ HttpSupport.disableSslVerify(connection);
+ }
+ connection.setRequestMethod(method);
+ connection.setRequestProperty(HDR_ACCEPT,
+ Protocol.CONTENTTYPE_VND_GIT_LFS_JSON);
+ connection.setRequestProperty(HDR_CONTENT_TYPE,
+ Protocol.CONTENTTYPE_VND_GIT_LFS_JSON);
+ additionalHeaders
+ .forEach((k, v) -> connection.setRequestProperty(k, v));
+ return connection;
+ }
+
+ private static String getLfsUrl(Repository db, String purpose,
+ Map<String, String> additionalHeaders)
+ throws LfsConfigInvalidException {
+ StoredConfig config = db.getConfig();
+ String lfsUrl = config.getString(Constants.LFS, null,
+ ConfigConstants.CONFIG_KEY_URL);
+ if (lfsUrl == null) {
+ String remoteUrl = null;
+ for (String remote : db.getRemoteNames()) {
+ lfsUrl = config.getString(Constants.LFS, remote,
+ ConfigConstants.CONFIG_KEY_URL);
+ // This could be done better (more precise logic), but according
+ // to https://github.com/git-lfs/git-lfs/issues/1759 git-lfs
+ // generally only supports 'origin' in an integrated workflow.
+ if (lfsUrl == null && (remote.equals(
+ org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME))) {
+ remoteUrl = config.getString(
+ ConfigConstants.CONFIG_KEY_REMOTE, remote,
+ ConfigConstants.CONFIG_KEY_URL);
+ }
+ break;
+ }
+ if (lfsUrl == null && remoteUrl != null) {
+ lfsUrl = discoverLfsUrl(db, purpose, additionalHeaders,
+ remoteUrl);
+ } else {
+ lfsUrl = lfsUrl + Protocol.INFO_LFS_ENDPOINT;
+ }
+ }
+ if (lfsUrl == null) {
+ throw new LfsConfigInvalidException(LfsText.get().lfsNoDownloadUrl);
+ }
+ return lfsUrl;
+ }
+
+ private static String discoverLfsUrl(Repository db, String purpose,
+ Map<String, String> additionalHeaders, String remoteUrl) {
+ try {
+ URIish u = new URIish(remoteUrl);
+
+ if (SCHEME_SSH.equals(u.getScheme())) {
+ // discover and authenticate; git-lfs does "ssh -p
+ // <port> -- <host> git-lfs-authenticate <project>
+ // <upload/download>"
+ String json = runSshCommand(u.setPath(""), db.getFS(), //$NON-NLS-1$
+ "git-lfs-authenticate " + extractProjectName(u) //$NON-NLS-1$
+ + " " + purpose); //$NON-NLS-1$
+
+ Protocol.Action action = new Gson().fromJson(json,
+ Protocol.Action.class);
+ additionalHeaders.putAll(action.header);
+ return action.href;
+ } else {
+ return remoteUrl + Protocol.INFO_LFS_ENDPOINT;
+ }
+ } catch (Exception e) {
+ return null; // could not discover
+ }
+ }
+
+ /**
+ * Create a connection for the specified
+ * {@link org.eclipse.jgit.lfs.Protocol.Action}.
+ *
+ * @param repo
+ * the repo to fetch required configuration from
+ * @param action
+ * the action for which to create a connection
+ * @param method
+ * the target method (GET or PUT)
+ * @return a connection. output mode is not set.
+ * @throws IOException
+ * in case of any error.
+ */
+ public static @NonNull HttpConnection getLfsContentConnection(
+ Repository repo, Protocol.Action action, String method)
+ throws IOException {
+ URL contentUrl = new URL(action.href);
+ HttpConnection contentServerConn = HttpTransport.getConnectionFactory()
+ .create(contentUrl, HttpSupport
+ .proxyFor(ProxySelector.getDefault(), contentUrl));
+ contentServerConn.setRequestMethod(method);
+ action.header
+ .forEach((k, v) -> contentServerConn.setRequestProperty(k, v));
+ if (contentUrl.getProtocol().equals(SCHEME_HTTPS)
+ && !repo.getConfig().getBoolean(HttpConfig.HTTP,
+ HttpConfig.SSL_VERIFY_KEY, true)) {
+ HttpSupport.disableSslVerify(contentServerConn);
+ }
+
+ contentServerConn.setRequestProperty(HDR_ACCEPT_ENCODING,
+ ENCODING_GZIP);
+
+ return contentServerConn;
+ }
+
+ private static String extractProjectName(URIish u) {
+ String path = u.getPath().substring(1);
+ if (path.endsWith(org.eclipse.jgit.lib.Constants.DOT_GIT)) {
+ return path.substring(0, path.length() - 4);
+ } else {
+ return path;
+ }
+ }
+
+ private static String runSshCommand(URIish sshUri, FS fs, String command)
+ throws IOException {
+ RemoteSession session = null;
+ Process process = null;
+ StreamCopyThread errorThread = null;
+ try (MessageWriter stderr = new MessageWriter()) {
+ session = SshSessionFactory.getInstance().getSession(sshUri, null,
+ fs, 5_000);
+ process = session.exec(command, 0);
+ errorThread = new StreamCopyThread(process.getErrorStream(),
+ stderr.getRawStream());
+ errorThread.start();
+ try (BufferedReader reader = new BufferedReader(
+ new InputStreamReader(process.getInputStream(),
+ org.eclipse.jgit.lib.Constants.CHARSET))) {
+ return reader.readLine();
+ }
+ } finally {
+ if (process != null) {
+ process.destroy();
+ }
+ if (errorThread != null) {
+ try {
+ errorThread.halt();
+ } catch (InterruptedException e) {
+ // Stop waiting and return anyway.
+ } finally {
+ errorThread = null;
+ }
+ }
+ if (session != null) {
+ SshSessionFactory.getInstance().releaseSession(session);
+ }
+ }
+ }
+
+ /**
+ * @param operation
+ * the operation to perform, e.g. Protocol.OPERATION_DOWNLOAD
+ * @param resources
+ * the LFS resources affected
+ * @return a request that can be serialized to JSON
+ */
+ public static Protocol.Request toRequest(String operation,
+ LfsPointer... resources) {
+ Protocol.Request req = new Protocol.Request();
+ req.operation = operation;
+ if (resources != null) {
+ req.objects = new LinkedList<>();
+ for (LfsPointer res : resources) {
+ Protocol.ObjectSpec o = new Protocol.ObjectSpec();
+ o.oid = res.getOid().getName();
+ o.size = res.getSize();
+ req.objects.add(o);
+ }
+ }
+ return req;
+ }
+
+}
diff --git a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
index 8fc2b1bef5..d82ade2f7f 100644
--- a/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
+++ b/org.eclipse.jgit.lfs/src/org/eclipse/jgit/lfs/internal/LfsText.java
@@ -76,4 +76,5 @@ public class LfsText extends TranslationBundle {
/***/ public String serverFailure;
/***/ public String wrongAmoutOfDataReceived;
/***/ public String userConfigInvalid;
+ /***/ public String missingLocalObject;
}
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 9df6510c13..fdaee3ee01 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -407,6 +407,7 @@ largeObjectExceedsLimit=Object {0} exceeds {1} limit, actual size is {2}
largeObjectException={0} exceeds size limit
largeObjectOutOfMemory=Out of memory loading {0}
lengthExceedsMaximumArraySize=Length exceeds maximum array size
+lfsHookConflict=LFS built-in hook conflicts with existing pre-push hook in repository {0}. Either remove the pre-push hook or disable built-in LFS support.
listingAlternates=Listing alternates
listingPacks=Listing packs
localObjectsIncomplete=Local objects incomplete.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
index fb015c9166..ad43e2ca83 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/GitHook.java
@@ -172,4 +172,15 @@ abstract class GitHook<T> implements Callable<T> {
}
}
+ /**
+ * Check whether a 'native' (i.e. script) hook is installed in the
+ * repository.
+ *
+ * @return whether a native hook script is installed in the repository.
+ * @since 4.11
+ */
+ public boolean isNativeHookPresent() {
+ return FS.DETECTED.findHook(getRepository(), getHookName()) != null;
+ }
+
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
index 87db36b251..a5eeb64288 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/Hooks.java
@@ -43,8 +43,13 @@
package org.eclipse.jgit.hooks;
import java.io.PrintStream;
+import java.lang.reflect.Constructor;
+import java.text.MessageFormat;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
/**
* Factory class for instantiating supported hooks.
@@ -107,6 +112,29 @@ public class Hooks {
* @since 4.2
*/
public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
+ PrePushHook lfsHook = null;
+ try {
+ StoredConfig cfg = repo.getConfig();
+ if (cfg.getBoolean(ConfigConstants.CONFIG_FILTER_SECTION, "lfs", //$NON-NLS-1$
+ ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false)) {
+ @SuppressWarnings("unchecked")
+ Class<? extends PrePushHook> cls = (Class<? extends PrePushHook>) Class
+ .forName("org.eclipse.jgit.lfs.LfsPrePushHook"); //$NON-NLS-1$
+ Constructor<? extends PrePushHook> constructor = cls
+ .getConstructor(Repository.class, PrintStream.class);
+
+ lfsHook = constructor.newInstance(repo, outputStream);
+ }
+ } catch (Exception e) {
+ // no problem :) no LFS support present
+ }
+ if (lfsHook != null) {
+ if (lfsHook.isNativeHookPresent()) {
+ throw new IllegalStateException(MessageFormat
+ .format(JGitText.get().lfsHookConflict, repo));
+ }
+ return lfsHook;
+ }
return new PrePushHook(repo, outputStream);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
index f974eb7927..051a1d1c81 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java
@@ -137,6 +137,16 @@ public class PrePushHook extends GitHook<String> {
}
/**
+ * Get remote name
+ *
+ * @return remote name or null
+ * @since 4.11
+ */
+ protected String getRemoteName() {
+ return remoteName;
+ }
+
+ /**
* Set remote location
*
* @param location
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index 9739672f84..41f0c5c433 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -468,6 +468,7 @@ public class JGitText extends TranslationBundle {
/***/ public String largeObjectException;
/***/ public String largeObjectOutOfMemory;
/***/ public String lengthExceedsMaximumArraySize;
+ /***/ public String lfsHookConflict;
/***/ public String listingAlternates;
/***/ public String listingPacks;
/***/ public String localObjectsIncomplete;