org.eclipse.jgit.lib;version="[6.1.0,6.2.0)",
org.eclipse.jgit.revwalk;version="[6.1.0,6.2.0)",
org.eclipse.jgit.transport;version="[6.1.0,6.2.0)",
+ org.eclipse.jgit.transport.http;version="[6.1.0,6.2.0)",
org.eclipse.jgit.treewalk;version="[6.1.0,6.2.0)",
org.eclipse.jgit.treewalk.filter;version="[6.1.0,6.2.0)",
org.eclipse.jgit.util;version="[6.1.0,6.2.0)",
--- /dev/null
+/*
+ * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lfs;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertTrue;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.api.Git;
+import org.eclipse.jgit.api.ResetCommand.ResetType;
+import org.eclipse.jgit.attributes.FilterCommand;
+import org.eclipse.jgit.attributes.FilterCommandRegistry;
+import org.eclipse.jgit.junit.RepositoryTestCase;
+import org.eclipse.jgit.lfs.internal.LfsConnectionFactory;
+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.http.HttpConnection;
+import org.eclipse.jgit.util.HttpSupport;
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+/**
+ * Test if the lfs config is used in the correct way during checkout.
+ *
+ * Two lfs-files are created, one that comes before .gitattributes and
+ * .lfsconfig in git order (".aaa.txt") and one that comes after ("zzz.txt").
+ *
+ * During checkout/reset it is tested if the correct version of the lfs config
+ * is used.
+ *
+ * TODO: The current behavior seems a little bit strange/unintuitive. Some files
+ * are checked out before and some after the config files. This leads to the
+ * behavior, that during a single command the config changes. Since this seems
+ * to be the same way in native git, the behavior is accepted for now.
+ *
+ */
+public class LfsConfigGitTest extends RepositoryTestCase {
+
+ private static final String SMUDGE_NAME = org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
+ + Constants.ATTR_FILTER_DRIVER_PREFIX
+ + org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_SMUDGE;
+
+ private static final String LFS_SERVER_URI1 = "https://lfs.server1/test/uri";
+
+ private static final String EXPECTED_SERVER_URL1 = LFS_SERVER_URI1
+ + Protocol.OBJECTS_LFS_ENDPOINT;
+
+ private static final String LFS_SERVER_URI2 = "https://lfs.server2/test/uri";
+
+ private static final String EXPECTED_SERVER_URL2 = LFS_SERVER_URI2
+ + Protocol.OBJECTS_LFS_ENDPOINT;
+
+ private static final String LFS_SERVER_URI3 = "https://lfs.server3/test/uri";
+
+ private static final String EXPECTED_SERVER_URL3 = LFS_SERVER_URI3
+ + Protocol.OBJECTS_LFS_ENDPOINT;
+
+ private static final String FAKE_LFS_POINTER1 = "version https://git-lfs.github.com/spec/v1\n"
+ + "oid sha256:6ce9fab52ee9a6c4c097def4e049c6acdeba44c99d26e83ba80adec1473c9b2d\n"
+ + "size 253952\n";
+
+ private static final String FAKE_LFS_POINTER2 = "version https://git-lfs.github.com/spec/v1\n"
+ + "oid sha256:a4b711cd989863ae2038758a62672138347abbbae4076a7ad3a545fda7d08f82\n"
+ + "size 67072\n";
+
+ private static List<String> checkoutURLs = new ArrayList<>();
+
+ static class SmudgeFilterMock extends FilterCommand {
+ public SmudgeFilterMock(Repository db, InputStream in,
+ OutputStream out) throws IOException {
+ super(in, out);
+ HttpConnection lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
+ HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
+ checkoutURLs.add(lfsServerConn.getURL().toString());
+ }
+
+ @Override
+ public int run() throws IOException {
+ // Stupid no impl
+ in.transferTo(out);
+ return -1;
+ }
+ }
+
+ @BeforeClass
+ public static void installLfs() {
+ FilterCommandRegistry.register(SMUDGE_NAME, SmudgeFilterMock::new);
+ }
+
+ @AfterClass
+ public static void removeLfs() {
+ FilterCommandRegistry.unregister(SMUDGE_NAME);
+ }
+
+ private Git git;
+
+ @Override
+ @Before
+ public void setUp() throws Exception {
+ super.setUp();
+ git = new Git(db);
+ // commit something
+ writeTrashFile("Test.txt", "Hello world");
+ git.add().addFilepattern("Test.txt").call();
+ git.commit().setMessage("Initial commit").call();
+ // prepare the config for LFS
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString("filter", "lfs", "smudge", SMUDGE_NAME);
+ config.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_AUTOCRLF, "false");
+ config.save();
+
+ fileBefore = null;
+ fileAfter = null;
+ configFile = null;
+ gitAttributesFile = null;
+ }
+
+ File fileBefore;
+
+ File fileAfter;
+
+ File configFile;
+
+ File gitAttributesFile;
+
+ private void createLfsFiles(String lfsPointer) throws Exception {
+ /*
+ * FileNames ".aaa.txt" and "zzz.txt" seem to be sufficient to get the
+ * desired checkout order before and after ".lfsconfig", at least in a
+ * number of manual tries. Since the files to checkout are contained in
+ * a set (see DirCacheCheckout::doCheckout) the order cannot be
+ * guaranteed.
+ */
+
+ //File to be checked out before lfs config
+ String fileNameBefore = ".aaa.txt";
+ fileBefore = writeTrashFile(fileNameBefore, lfsPointer);
+ git.add().addFilepattern(fileNameBefore).call();
+
+ // File to be checked out after lfs config
+ String fileNameAfter = "zzz.txt";
+ fileAfter = writeTrashFile(fileNameAfter, lfsPointer);
+ git.add().addFilepattern(fileNameAfter).call();
+
+ git.commit().setMessage("Commit LFS Pointer files").call();
+ }
+
+
+ private String addLfsConfigFiles(String lfsServerUrl) throws Exception {
+ // Add config files to the repo
+ String lfsConfig1 = createLfsConfig(lfsServerUrl);
+ git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+ // Modify gitattributes on second call, to force checkout too.
+ if (gitAttributesFile == null) {
+ gitAttributesFile = writeTrashFile(".gitattributes",
+ "*.txt filter=lfs");
+ } else {
+ gitAttributesFile = writeTrashFile(".gitattributes",
+ "*.txt filter=lfs\n");
+ }
+
+ git.add().addFilepattern(".gitattributes").call();
+ git.commit().setMessage("Commit config files").call();
+ return lfsConfig1;
+ }
+
+ private String createLfsConfig(String lfsServerUrl) throws IOException {
+ String lfsConfig1 = "[lfs]\n url = " + lfsServerUrl;
+ configFile = writeTrashFile(Constants.DOT_LFS_CONFIG, lfsConfig1);
+ return lfsConfig1;
+ }
+
+ @Test
+ public void checkoutLfsObjects_reset() throws Exception {
+ createLfsFiles(FAKE_LFS_POINTER1);
+ String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
+
+ // Delete files to force action on reset
+ assertTrue(configFile.delete());
+ assertTrue(fileBefore.delete());
+ assertTrue(fileAfter.delete());
+
+ assertTrue(gitAttributesFile.delete());
+
+ // create config file with different url
+ createLfsConfig(LFS_SERVER_URI3);
+
+ checkoutURLs.clear();
+ git.reset().setMode(ResetType.HARD).call();
+
+ checkFile(configFile, lfsConfig1);
+ checkFile(fileBefore, FAKE_LFS_POINTER1);
+ checkFile(fileAfter, FAKE_LFS_POINTER1);
+
+ assertEquals(2, checkoutURLs.size());
+ // TODO: Should may be EXPECTED_SERVR_URL1
+ assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+ assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
+ }
+
+ @Test
+ public void checkoutLfsObjects_BranchSwitch() throws Exception {
+ // Create a new branch "URL1" and add config files
+ git.checkout().setCreateBranch(true).setName("URL1").call();
+
+ createLfsFiles(FAKE_LFS_POINTER1);
+ String lfsConfig1 = addLfsConfigFiles(LFS_SERVER_URI1);
+
+ // Create a second new branch "URL2" and add config files
+ git.checkout().setCreateBranch(true).setName("URL2").call();
+
+ createLfsFiles(FAKE_LFS_POINTER2);
+ String lfsConfig2 = addLfsConfigFiles(LFS_SERVER_URI2);
+
+ checkFile(configFile, lfsConfig2);
+ checkFile(fileBefore, FAKE_LFS_POINTER2);
+ checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+ checkoutURLs.clear();
+ git.checkout().setName("URL1").call();
+
+ checkFile(configFile, lfsConfig1);
+ checkFile(fileBefore, FAKE_LFS_POINTER1);
+ checkFile(fileAfter, FAKE_LFS_POINTER1);
+
+ assertEquals(2, checkoutURLs.size());
+ // TODO: Should may be EXPECTED_SERVR_URL1
+ assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(0));
+ assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(1));
+
+ checkoutURLs.clear();
+ git.checkout().setName("URL2").call();
+
+ checkFile(configFile, lfsConfig2);
+ checkFile(fileBefore, FAKE_LFS_POINTER2);
+ checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+ assertEquals(2, checkoutURLs.size());
+ // TODO: Should may be EXPECTED_SERVR_URL2
+ assertEquals(EXPECTED_SERVER_URL1, checkoutURLs.get(0));
+ assertEquals(EXPECTED_SERVER_URL2, checkoutURLs.get(1));
+ }
+
+ @Test
+ public void checkoutLfsObjects_BranchSwitch_ModifiedLocal()
+ throws Exception {
+
+ // Create a new branch "URL1" and add config files
+ git.checkout().setCreateBranch(true).setName("URL1").call();
+
+ createLfsFiles(FAKE_LFS_POINTER1);
+ addLfsConfigFiles(LFS_SERVER_URI1);
+
+ // Create a second new branch "URL2" and add config files
+ git.checkout().setCreateBranch(true).setName("URL2").call();
+
+ createLfsFiles(FAKE_LFS_POINTER2);
+ addLfsConfigFiles(LFS_SERVER_URI1);
+
+ // create config file with different url
+ assertTrue(configFile.delete());
+ String lfsConfig3 = createLfsConfig(LFS_SERVER_URI3);
+
+ checkFile(configFile, lfsConfig3);
+ checkFile(fileBefore, FAKE_LFS_POINTER2);
+ checkFile(fileAfter, FAKE_LFS_POINTER2);
+
+ checkoutURLs.clear();
+ git.checkout().setName("URL1").call();
+
+ checkFile(fileBefore, FAKE_LFS_POINTER1);
+ checkFile(fileAfter, FAKE_LFS_POINTER1);
+ checkFile(configFile, lfsConfig3);
+
+ assertEquals(2, checkoutURLs.size());
+
+ assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+ assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
+
+ checkoutURLs.clear();
+ git.checkout().setName("URL2").call();
+
+ checkFile(fileBefore, FAKE_LFS_POINTER2);
+ checkFile(fileAfter, FAKE_LFS_POINTER2);
+ checkFile(configFile, lfsConfig3);
+
+ assertEquals(2, checkoutURLs.size());
+ assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(0));
+ assertEquals(EXPECTED_SERVER_URL3, checkoutURLs.get(1));
+ }
+}
*/
package org.eclipse.jgit.lfs.internal;
+import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThrows;
+import static org.junit.Assert.assertTrue;
-import java.util.TreeMap;
+import java.io.File;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.RemoteAddCommand;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.StoredConfig;
import org.eclipse.jgit.transport.URIish;
+import org.eclipse.jgit.transport.http.HttpConnection;
+import org.eclipse.jgit.util.HttpSupport;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
+ Constants.ATTR_FILTER_DRIVER_PREFIX
+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_CLEAN;
+ private final static String LFS_SERVER_URL1 = "https://lfs.server1/test/uri";
+
+ private final static String LFS_SERVER_URL2 = "https://lfs.server2/test/uri";
+
+ private final static String ORIGIN_URL = "https://git.server/test/uri";
+
private Git git;
@BeforeClass
public void setUp() throws Exception {
super.setUp();
git = new Git(db);
+
+ // Just to have a non empty repo
+ writeTrashFile("Test.txt", "Hello world from the LFS Factory Test");
+ git.add().addFilepattern("Test.txt").call();
+ git.commit().setMessage("Initial commit").call();
}
@Test
public void lfsUrlFromRemoteUrlWithDotGit() throws Exception {
addRemoteUrl("https://localhost/repo.git");
-
- String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
- Protocol.OPERATION_DOWNLOAD,
- new TreeMap<>());
- assertEquals("https://localhost/repo.git/info/lfs", lfsUrl);
+ checkLfsUrl("https://localhost/repo.git/info/lfs");
}
@Test
public void lfsUrlFromRemoteUrlWithoutDotGit() throws Exception {
addRemoteUrl("https://localhost/repo");
-
- String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
- Protocol.OPERATION_DOWNLOAD,
- new TreeMap<>());
- assertEquals("https://localhost/repo.git/info/lfs", lfsUrl);
+ checkLfsUrl("https://localhost/repo.git/info/lfs");
}
@Test
"https://localhost/repo/lfs");
cfg.save();
- String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
- Protocol.OPERATION_DOWNLOAD,
- new TreeMap<>());
- assertEquals("https://localhost/repo/lfs", lfsUrl);
+ checkLfsUrl("https://localhost/repo/lfs");
}
@Test
"https://localhost/repo/lfs");
cfg.save();
- String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
- Protocol.OPERATION_DOWNLOAD,
- new TreeMap<>());
- assertEquals("https://localhost/repo/lfs", lfsUrl);
+ checkLfsUrl("https://localhost/repo/lfs");
}
@Test
public void lfsUrlNotConfigured() throws Exception {
- assertThrows(LfsConfigInvalidException.class, () -> LfsConnectionFactory
- .getLfsUrl(db, Protocol.OPERATION_DOWNLOAD, new TreeMap<>()));
+ assertThrows(LfsConfigInvalidException.class,
+ () -> LfsConnectionFactory.getLfsConnection(db,
+ HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD));
+ }
+
+ @Test
+ public void checkGetLfsConnection_lfsurl_lfsconfigFromWorkingDir()
+ throws Exception {
+ writeLfsConfig();
+ checkLfsUrl(LFS_SERVER_URL1);
+ }
+
+ @Test
+ public void checkGetLfsConnection_lfsurl_lfsconfigFromIndex()
+ throws Exception {
+ writeLfsConfig();
+ git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+ deleteTrashFile(Constants.DOT_LFS_CONFIG);
+ checkLfsUrl(LFS_SERVER_URL1);
+ }
+
+ @Test
+ public void checkGetLfsConnection_lfsurl_lfsconfigFromHEAD()
+ throws Exception {
+ writeLfsConfig();
+ git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+ git.commit().setMessage("Commit LFS Config").call();
+
+ /*
+ * reading .lfsconfig from HEAD seems only testable using a bare repo,
+ * since otherwise working tree or index are used
+ */
+ File directory = createTempDirectory("testBareRepo");
+ try (Repository bareRepoDb = Git.cloneRepository()
+ .setDirectory(directory)
+ .setURI(db.getDirectory().toURI().toString()).setBare(true)
+ .call().getRepository()) {
+
+ checkLfsUrl(LFS_SERVER_URL1);
+ }
+ }
+
+ @Test
+ public void checkGetLfsConnection_remote_lfsconfigFromWorkingDir()
+ throws Exception {
+ addRemoteUrl(ORIGIN_URL);
+ writeLfsConfig(LFS_SERVER_URL1, "lfs", DEFAULT_REMOTE_NAME, "url");
+ checkLfsUrl(LFS_SERVER_URL1);
+ }
+
+ /**
+ * Test the config file precedence.
+ *
+ * Checking only with the local repository config is sufficient since from
+ * that point the "normal" precedence is used.
+ *
+ * @throws Exception
+ */
+ @Test
+ public void checkGetLfsConnection_ConfigFilePrecedence_lfsconfigFromWorkingDir()
+ throws Exception {
+ writeLfsConfig();
+ checkLfsUrl(LFS_SERVER_URL1);
+
+ StoredConfig config = git.getRepository().getConfig();
+ config.setString(ConfigConstants.CONFIG_SECTION_LFS, null,
+ ConfigConstants.CONFIG_KEY_URL, LFS_SERVER_URL2);
+ config.save();
+
+ checkLfsUrl(LFS_SERVER_URL2);
+ }
+
+ @Test
+ public void checkGetLfsConnection_InvalidLfsConfig_WorkingDir()
+ throws Exception {
+ writeInvalidLfsConfig();
+ LfsConfigInvalidException actualException = assertThrows(
+ LfsConfigInvalidException.class, () -> {
+ LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
+ Protocol.OPERATION_DOWNLOAD);
+ });
+ assertTrue(getStackTrace(actualException)
+ .contains("Invalid line in config file"));
+ }
+
+ @Test
+ public void checkGetLfsConnection_InvalidLfsConfig_Index()
+ throws Exception {
+ writeInvalidLfsConfig();
+ git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+ deleteTrashFile(Constants.DOT_LFS_CONFIG);
+ LfsConfigInvalidException actualException = assertThrows(
+ LfsConfigInvalidException.class, () -> {
+ LfsConnectionFactory.getLfsConnection(db, HttpSupport.METHOD_POST,
+ Protocol.OPERATION_DOWNLOAD);
+ });
+ assertTrue(getStackTrace(actualException)
+ .contains("Invalid line in config file"));
+ }
+
+ @Test
+ public void checkGetLfsConnection_InvalidLfsConfig_HEAD() throws Exception {
+ writeInvalidLfsConfig();
+ git.add().addFilepattern(Constants.DOT_LFS_CONFIG).call();
+ git.commit().setMessage("Commit LFS Config").call();
+
+ /*
+ * reading .lfsconfig from HEAD seems only testable using a bare repo,
+ * since otherwise working tree or index are used
+ */
+ File directory = createTempDirectory("testBareRepo");
+ try (Repository bareRepoDb = Git.cloneRepository()
+ .setDirectory(directory)
+ .setURI(db.getDirectory().toURI().toString()).setBare(true)
+ .call().getRepository()) {
+ LfsConfigInvalidException actualException = assertThrows(
+ LfsConfigInvalidException.class,
+ () -> {
+ LfsConnectionFactory.getLfsConnection(db,
+ HttpSupport.METHOD_POST,
+ Protocol.OPERATION_DOWNLOAD);
+ });
+ assertTrue(getStackTrace(actualException)
+ .contains("Invalid line in config file"));
+ }
}
private void addRemoteUrl(String remotUrl) throws Exception {
add.setName(org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME);
add.call();
}
+
+ /**
+ * Returns the stack trace of the provided exception as string
+ *
+ * @param actualException
+ * @return The exception stack trace as string
+ */
+ private String getStackTrace(Exception actualException) {
+ StringWriter sw = new StringWriter();
+ PrintWriter pw = new PrintWriter(sw);
+ actualException.printStackTrace(pw);
+ return sw.toString();
+ }
+
+ private void writeLfsConfig() throws IOException {
+ writeLfsConfig(LFS_SERVER_URL1, "lfs", "url");
+ }
+
+ private void writeLfsConfig(String lfsUrl, String section, String name)
+ throws IOException {
+ writeLfsConfig(lfsUrl, section, null, name);
+ }
+
+ /*
+ * Write simple lfs config with single entry. Do not use FileBasedConfig to
+ * avoid introducing new dependency (for now).
+ */
+ private void writeLfsConfig(String lfsUrl, String section,
+ String subsection, String name) throws IOException {
+ StringBuilder config = new StringBuilder();
+ config.append("[");
+ config.append(section);
+ if (subsection != null) {
+ config.append(" \"");
+ config.append(subsection);
+ config.append("\"");
+ }
+ config.append("]\n");
+ config.append(" ");
+ config.append(name);
+ config.append(" = ");
+ config.append(lfsUrl);
+ writeTrashFile(Constants.DOT_LFS_CONFIG, config.toString());
+ }
+
+ private void writeInvalidLfsConfig() throws IOException {
+ writeTrashFile(Constants.DOT_LFS_CONFIG,
+ "{lfs]\n url = " + LFS_SERVER_URL1);
+ }
+
+ private void checkLfsUrl(String lfsUrl) throws IOException {
+ HttpConnection lfsServerConn;
+ lfsServerConn = LfsConnectionFactory.getLfsConnection(db,
+ HttpSupport.METHOD_POST, Protocol.OPERATION_DOWNLOAD);
+
+ assertEquals(lfsUrl + Protocol.OBJECTS_LFS_ENDPOINT,
+ lfsServerConn.getURL().toString());
+ }
}
org.eclipse.jgit.api.errors;version="[6.1.0,6.2.0)",
org.eclipse.jgit.attributes;version="[6.1.0,6.2.0)",
org.eclipse.jgit.diff;version="[6.1.0,6.2.0)",
+ org.eclipse.jgit.dircache;version="[6.1.0,6.2.0)",
org.eclipse.jgit.errors;version="[6.1.0,6.2.0)",
org.eclipse.jgit.hooks;version="[6.1.0,6.2.0)",
org.eclipse.jgit.internal.storage.file;version="[6.1.0,6.2.0)",
org.eclipse.jgit.treewalk;version="[6.1.0,6.2.0)",
org.eclipse.jgit.treewalk.filter;version="[6.1.0,6.2.0)",
org.eclipse.jgit.util;version="[6.1.0,6.2.0)",
- org.eclipse.jgit.util.io;version="[6.1.0,6.2.0)"
+ org.eclipse.jgit.util.io;version="[6.1.0,6.2.0)",
+ org.slf4j;version="[1.7.0,2.0.0)"
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}"
-missingLocalObject="Local Object {0} is missing"
\ No newline at end of file
+missingLocalObject="Local Object {0} is missing"
+dotLfsConfigReadFailed=Reading .lfsconfig failed
\ No newline at end of file
--- /dev/null
+/*
+ * Copyright (C) 2022, Matthias Fromme <mfromme@dspace.de>
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Distribution License v. 1.0 which is available at
+ * https://www.eclipse.org/org/documents/edl-v10.php.
+ *
+ * SPDX-License-Identifier: BSD-3-Clause
+ */
+package org.eclipse.jgit.lfs.internal;
+
+import java.io.File;
+import java.io.IOException;
+
+import org.eclipse.jgit.annotations.Nullable;
+import org.eclipse.jgit.dircache.DirCacheEntry;
+import org.eclipse.jgit.errors.ConfigInvalidException;
+import org.eclipse.jgit.lfs.errors.LfsConfigInvalidException;
+import org.eclipse.jgit.lfs.lib.Constants;
+import org.eclipse.jgit.lib.BlobBasedConfig;
+import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevTree;
+import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.treewalk.TreeWalk;
+
+import static org.eclipse.jgit.lib.Constants.HEAD;
+
+/**
+ * Encapsulate access to the .lfsconfig.
+ *
+ * According to the document
+ * https://github.com/git-lfs/git-lfs/blob/main/docs/man/git-lfs-config.5.ronn
+ * the order to find the .lfsconfig file is:
+ *
+ * <pre>
+ * 1. in the root of the working tree
+ * 2. in the index
+ * 3. in the HEAD, for bare repositories this is the only place
+ * that is searched
+ * </pre>
+ *
+ * Values from the .lfsconfig are used only if not specified in another git
+ * config file to allow local override without modifiction of a committed file.
+ */
+public class LfsConfig {
+ private Repository db;
+ private Config delegate;
+
+ /**
+ * Create a new instance of the LfsConfig.
+ *
+ * @param db
+ * the associated repo
+ * @throws IOException
+ */
+ public LfsConfig(Repository db) throws IOException {
+ this.db = db;
+ delegate = this.load();
+ }
+
+ /**
+ * Read the .lfsconfig file from the repository
+ *
+ * @return The loaded lfs config or null if it does not exist
+ *
+ * @throws IOException
+ */
+ private Config load() throws IOException {
+ Config result = null;
+
+ if (!db.isBare()) {
+ result = loadFromWorkingTree();
+ if (result == null) {
+ result = loadFromIndex();
+ }
+ }
+
+ if (result == null) {
+ result = loadFromHead();
+ }
+
+ if (result == null) {
+ result = emptyConfig();
+ }
+
+ return result;
+ }
+
+ /**
+ * Try to read the lfs config from a file called .lfsconfig at the top level
+ * of the working tree.
+ *
+ * @return the config, or <code>null</code>
+ * @throws IOException
+ */
+ @Nullable
+ private Config loadFromWorkingTree()
+ throws IOException {
+ File lfsConfig = db.getFS().resolve(db.getWorkTree(),
+ Constants.DOT_LFS_CONFIG);
+ if (lfsConfig.exists() && lfsConfig.isFile()) {
+ FileBasedConfig config = new FileBasedConfig(lfsConfig, db.getFS());
+ try {
+ config.load();
+ return config;
+ } catch (ConfigInvalidException e) {
+ throw new LfsConfigInvalidException(
+ LfsText.get().dotLfsConfigReadFailed, e);
+ }
+ }
+ return null;
+ }
+
+ /**
+ * Try to read the lfs config from an entry called .lfsconfig contained in
+ * the index.
+ *
+ * @return the config, or <code>null</code> if the entry does not exist
+ * @throws IOException
+ */
+ @Nullable
+ private Config loadFromIndex()
+ throws IOException {
+ try {
+ DirCacheEntry entry = db.readDirCache()
+ .getEntry(Constants.DOT_LFS_CONFIG);
+ if (entry != null) {
+ return new BlobBasedConfig(null, db, entry.getObjectId());
+ }
+ } catch (ConfigInvalidException e) {
+ throw new LfsConfigInvalidException(
+ LfsText.get().dotLfsConfigReadFailed, e);
+ }
+ return null;
+ }
+
+ /**
+ * Try to read the lfs config from an entry called .lfsconfig contained in
+ * the head revision.
+ *
+ * @return the config, or <code>null</code> if the file does not exist
+ * @throws IOException
+ */
+ @Nullable
+ private Config loadFromHead() throws IOException {
+ try (RevWalk revWalk = new RevWalk(db)) {
+ ObjectId headCommitId = db.resolve(HEAD);
+ if (headCommitId == null) {
+ return null;
+ }
+ RevCommit commit = revWalk.parseCommit(headCommitId);
+ RevTree tree = commit.getTree();
+ TreeWalk treewalk = TreeWalk.forPath(db, Constants.DOT_LFS_CONFIG,
+ tree);
+ if (treewalk != null) {
+ return new BlobBasedConfig(null, db, treewalk.getObjectId(0));
+ }
+ } catch (ConfigInvalidException e) {
+ throw new LfsConfigInvalidException(
+ LfsText.get().dotLfsConfigReadFailed, e);
+ }
+ return null;
+ }
+
+ /**
+ * Create an empty config as fallback to avoid null pointer checks.
+ *
+ * @return an empty config
+ */
+ private Config emptyConfig() {
+ return new Config();
+ }
+
+ /**
+ * Get string value or null if not found.
+ *
+ * First tries to find the value in the git config files. If not found tries
+ * to find data in .lfsconfig.
+ *
+ * @param section
+ * the section
+ * @param subsection
+ * the subsection for the value
+ * @param name
+ * the key name
+ * @return a String value from the config, <code>null</code> if not found
+ */
+ public String getString(final String section, final String subsection,
+ final String name) {
+ String result = db.getConfig().getString(section, subsection, name);
+ if (result == null) {
+ result = delegate.getString(section, subsection, name);
+ }
+ return result;
+ }
+}
* Provides means to get a valid LFS connection for a given repository.
*/
public class LfsConnectionFactory {
-
private static final int SSH_AUTH_TIMEOUT_SECONDS = 30;
private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
private static final String SCHEME_SSH = "ssh"; //$NON-NLS-1$
* additional headers that can be used to connect to LFS server
* @return the URL for the LFS server. e.g.
* "https://github.com/github/git-lfs.git/info/lfs"
- * @throws LfsConfigInvalidException
- * if the LFS config is invalid
+ * @throws IOException
+ * if the LFS config is invalid or cannot be accessed
* @see <a href=
* "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md">
* Server Discovery documentation</a>
*/
- static String getLfsUrl(Repository db, String purpose,
+ private static String getLfsUrl(Repository db, String purpose,
Map<String, String> additionalHeaders)
- throws LfsConfigInvalidException {
- StoredConfig config = db.getConfig();
+ throws IOException {
+ LfsConfig config = new LfsConfig(db);
String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
- null,
- ConfigConstants.CONFIG_KEY_URL);
+ null, ConfigConstants.CONFIG_KEY_URL);
+
Exception ex = null;
if (lfsUrl == null) {
String remoteUrl = null;
lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_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.
/***/ public String wrongAmoutOfDataReceived;
/***/ public String userConfigInvalid;
/***/ public String missingLocalObject;
+ /***/ public String dotLfsConfigReadFailed;
}
*/
public static final String ATTR_FILTER_DRIVER_PREFIX = "lfs/";
+ /**
+ * Config file name for lfs specific configuration
+ *
+ * @since 6.1
+ */
+ public static final String DOT_LFS_CONFIG = ".lfsconfig";
+
/**
* Create a new digest function for objects.
*