Merge branch 'stable-6.1'

* stable-6.1:
  Prepare 6.1.1-SNAPSHOT builds
  JGit v6.1.0.202203080745-r
  [checkout] Use .gitattributes from the commit to be checked out
  Don't use final for method parameters
  [push] support the "matching" RefSpecs ":" and "+:"
  [push] Call the pre-push hook later in the push process
  IndexDiff: use tree filter also for SubmoduleWalk
  Run license check with option -Ddash.projectId=technology.jgit
  Exclude transitive dependencies of sshd-sftp
  Update DEPENDENCIES for 6.1.0 release
  Add dependency to dash-licenses
  Fix typos of some keys in LfsText
  Sort LfsText entries alphabetically
  Support for "lfs.url" from ".lfsconfig"

Change-Id: I1b9f0c0ed647837e00b9640d235dbfab2329c5a6
This commit is contained in:
Matthias Sohn 2022-03-08 17:23:57 +01:00
commit 338f2adf16
29 changed files with 1813 additions and 363 deletions

View File

@ -1,69 +1,68 @@
maven/mavencentral/args4j/args4j/2.33, MIT, approved, CQ11068
maven/mavencentral/com.google.code.gson/gson/2.8.7, Apache-2.0, approved, CQ23496
maven/mavencentral/com.googlecode.javaewah/JavaEWAH/1.1.12, Apache-2.0, approved, CQ11658
maven/mavencentral/com.google.code.gson/gson/2.8.9, Apache-2.0, approved, CQ23496
maven/mavencentral/com.googlecode.javaewah/JavaEWAH/1.1.13, Apache-2.0, approved, CQ11658
maven/mavencentral/com.jcraft/jsch/0.1.55, BSD-3-Clause, approved, CQ19435
maven/mavencentral/com.jcraft/jzlib/1.1.1, BSD-2-Clause, approved, CQ6218
maven/mavencentral/commons-codec/commons-codec/1.11, Apache-2.0 AND BSD-3-Clause, approved, CQ15971
maven/mavencentral/commons-logging/commons-logging/1.2, Apache-2.0, approved, CQ10162
maven/mavencentral/javax.servlet/javax.servlet-api/3.1.0, Apache-2.0 AND (CDDL-1.1 OR GPL-2.0 WITH Classpath-exception-2.0), approved, CQ7248
maven/mavencentral/junit/junit/4.13, , approved, CQ22796
maven/mavencentral/log4j/log4j/1.2.15, Apache-2.0, approved, CQ7837
maven/mavencentral/javax.servlet/javax.servlet-api/4.0.0, , approved, CQ16125
maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636
maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.9.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/net.bytebuddy/byte-buddy/1.9.0, Apache-2.0, approved, clearlydefined
maven/mavencentral/net.i2p.crypto/eddsa/0.3.0, CC0-1.0, approved, CQ22537
maven/mavencentral/net.java.dev.jna/jna-platform/5.8.0, Apache-2.0 OR LGPL-2.1-or-later, approved, CQ23218
maven/mavencentral/net.java.dev.jna/jna/5.8.0, Apache-2.0 OR LGPL-2.1-or-later, approved, CQ23217
maven/mavencentral/net.sf.jopt-simple/jopt-simple/4.6, MIT, approved, clearlydefined
maven/mavencentral/org.apache.ant/ant-launcher/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
maven/mavencentral/org.apache.ant/ant/1.10.10, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
maven/mavencentral/org.apache.commons/commons-compress/1.20, Apache-2.0 AND BSD-3-Clause AND LicenseRef-Public-Domain, approved, CQ21771
maven/mavencentral/org.apache.ant/ant-launcher/1.10.12, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
maven/mavencentral/org.apache.ant/ant/1.10.12, Apache-2.0 AND W3C AND LicenseRef-Public-Domain, approved, CQ15560
maven/mavencentral/org.apache.commons/commons-compress/1.21, Apache-2.0 AND BSD-3-Clause AND bzip2-1.0.6 AND LicenseRef-Public-Domain, approved, CQ23710
maven/mavencentral/org.apache.commons/commons-math3/3.2, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.apache.httpcomponents/httpclient/4.5.13, Apache-2.0 AND LicenseRef-Public-Domain, approved, CQ23527
maven/mavencentral/org.apache.httpcomponents/httpcore/4.4.14, Apache-2.0, approved, CQ23528
maven/mavencentral/org.apache.sshd/sshd-common/2.7.0, Apache-2.0 and ISC, approved, CQ23469
maven/mavencentral/org.apache.sshd/sshd-core/2.7.0, Apache-2.0, approved, CQ23469
maven/mavencentral/org.apache.sshd/sshd-osgi/2.7.0, Apache-2.0 and ISC, approved, CQ23469
maven/mavencentral/org.apache.sshd/sshd-sftp/2.7.0, Apache-2.0, approved, CQ23470
maven/mavencentral/org.apache.sshd/sshd-osgi/2.8.0, Apache-2.0, approved, CQ23892
maven/mavencentral/org.apache.sshd/sshd-sftp/2.8.0, Apache-2.0, approved, CQ23893
maven/mavencentral/org.assertj/assertj-core/3.20.2, Apache-2.0, approved, clearlydefined
maven/mavencentral/org.bouncycastle/bcpg-jdk15on/1.69, MIT and Apache-2.0, approved, CQ23472
maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.69, MIT, approved, CQ23473
maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.69, MIT, approved, CQ23471
maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.69, MIT, approved, CQ23474
maven/mavencentral/org.eclipse.jetty/jetty-http/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-io/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-security/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-server/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-servlet/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-util-ajax/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jetty/jetty-util/9.4.43.v20210629, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/5.13.0-SNAPSHOT, , approved, eclipse
maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ7063
maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-2-Clause, approved, clearlydefined
maven/mavencentral/org.mockito/mockito-core/2.23.0, MIT, approved, CQ17976
maven/mavencentral/org.bouncycastle/bcpg-jdk15on/1.70, Apache-2.0, approved, #1713
maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.70, MIT, approved, clearlydefined
maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.70, MIT, approved, #1712
maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.70, MIT, approved, clearlydefined
maven/mavencentral/org.eclipse.jetty.toolchain/jetty-servlet-api/4.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-http/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-io/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-security/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-server/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-servlet/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jetty/jetty-util/10.0.6, EPL-2.0 OR Apache-2.0, approved, rt.jetty
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ant/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.archive/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.gpg.bc/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.apache/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.server/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.http.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.http/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit.ssh/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.junit/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.server/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.lfs/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.pgm/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.agent/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.apache/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ssh.jsch/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.test/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ11429
maven/mavencentral/org.mockito/mockito-core/2.23.0, Apache-2.0 AND MIT, approved, #958
maven/mavencentral/org.objenesis/objenesis/2.6, Apache-2.0, approved, CQ15478
maven/mavencentral/org.openjdk.jmh/jmh-core/1.32, GPL-2.0, approved, CQ23499
maven/mavencentral/org.openjdk.jmh/jmh-generator-annprocess/1.32, GPL-2.0, approved, CQ23500
maven/mavencentral/org.osgi/org.osgi.core/4.3.1, Apache-2.0, approved, CQ10111
maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.30, Apache-2.0, approved, CQ12843
maven/mavencentral/org.openjdk.jmh/jmh-core/1.32, GPL-2.0-only with Classpath-exception-2.0, approved, #959
maven/mavencentral/org.openjdk.jmh/jmh-generator-annprocess/1.32, GPL-2.0-only with Classpath-exception-2.0, approved, #962
maven/mavencentral/org.osgi/org.osgi.core/6.0.0, Apache-2.0, approved, #1794
maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.32, Apache-2.0, approved, CQ12843
maven/mavencentral/org.slf4j/slf4j-api/1.7.30, MIT, approved, CQ13368
maven/mavencentral/org.slf4j/slf4j-log4j12/1.7.30, MIT, approved, CQ7665
maven/mavencentral/org.slf4j/slf4j-simple/1.7.30, MIT, approved, CQ7952
maven/mavencentral/org.tukaani/xz/1.9, LicenseRef-Public-Domain, approved, CQ23498

View File

@ -55,6 +55,16 @@
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-sftp</artifactId>
<version>${apache-sshd-version}</version>
<exclusions>
<exclusion>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
</exclusion>
<exclusion>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-common</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>

View File

@ -0,0 +1,309 @@
/*
* 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));
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2021, Thomas Wolf <thomas.wolf@paranor.ch> and others
* Copyright (C) 2021, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@ -67,6 +67,27 @@ public void setUp() throws Exception {
config.save();
}
@Test
public void testBranchSwitch() throws Exception {
git.branchCreate().setName("abranch").call();
git.checkout().setName("abranch").call();
File aFile = writeTrashFile("a.bin", "aaa");
writeTrashFile(".gitattributes", "a.bin filter=lfs");
git.add().addFilepattern(".").call();
git.commit().setMessage("acommit").call();
git.checkout().setName("master").call();
git.branchCreate().setName("bbranch").call();
git.checkout().setName("bbranch").call();
File bFile = writeTrashFile("b.bin", "bbb");
writeTrashFile(".gitattributes", "b.bin filter=lfs");
git.add().addFilepattern(".").call();
git.commit().setMessage("bcommit").call();
git.checkout().setName("abranch").call();
checkFile(aFile, "aaa");
git.checkout().setName("bbranch").call();
checkFile(bFile, "bbb");
}
@Test
public void checkoutNonLfsPointer() throws Exception {
String content = "size_t\nsome_function(void* ptr);\n";

View File

@ -9,10 +9,15 @@
*/
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;
@ -27,6 +32,8 @@
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;
@ -42,6 +49,12 @@ public class LfsConnectionFactoryTest extends RepositoryTestCase {
+ 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
@ -61,26 +74,23 @@ public static void removeLfs() {
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
@ -94,10 +104,7 @@ public void lfsUrlFromLocalConfig() throws Exception {
"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
@ -111,16 +118,136 @@ public void lfsUrlFromOriginConfig() throws Exception {
"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 {
@ -129,4 +256,62 @@ 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());
}
}

View File

@ -1,19 +1,19 @@
corruptLongObject=The content hash ''{0}'' of the long object ''{1}'' doesn''t match its id, the corrupt object will be deleted.
incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}.
dotLfsConfigReadFailed=Reading .lfsconfig failed
inconsistentContentLength=Unexpected content length reported by LFS server ({0}), expected {1} but reported was {2}
inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}.
incorrectLONG_OBJECT_ID_LENGTH=Incorrect LONG_OBJECT_ID_LENGTH.
invalidLongId=Invalid id: {0}
invalidLongIdLength=Invalid id length {0}; should be {1}
lfsUnavailable=LFS is not available for repository {0}
protocolError=LFS Protocol Error {0}: {1}
requiredHashFunctionNotAvailable=Required hash function {0} not available.
repositoryNotFound=Repository {0} not found
repositoryReadOnly=Repository {0} is read-only
lfsUnavailable=LFS is not available for repository {0}
lfsUnathorized=Not authorized to perform operation {0} on repository {1}
lfsFailedToGetRepository=failed to get repository {0}
lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL"
lfsUnauthorized=Not authorized to perform operation {0} on repository {1}
lfsUnavailable=LFS is not available for repository {0}
missingLocalObject="Local Object {0} is missing"
protocolError=LFS Protocol Error {0}: {1}
repositoryNotFound=Repository {0} not found
repositoryReadOnly=Repository {0} is read-only
requiredHashFunctionNotAvailable=Required hash function {0} not available.
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"
wrongAmountOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected

View File

@ -205,7 +205,7 @@ public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
long bytesCopied = Files.copy(contentIn, path);
if (bytesCopied != o.size) {
throw new IOException(MessageFormat.format(
LfsText.get().wrongAmoutOfDataReceived,
LfsText.get().wrongAmountOfDataReceived,
contentServerConn.getURL(),
Long.valueOf(bytesCopied),
Long.valueOf(o.size)));

View File

@ -31,7 +31,7 @@ public class LfsUnauthorized extends LfsException {
* the repository name.
*/
public LfsUnauthorized(String operation, String name) {
super(MessageFormat.format(LfsText.get().lfsUnathorized, operation,
super(MessageFormat.format(LfsText.get().lfsUnauthorized, operation,
name));
}
}

View File

@ -0,0 +1,200 @@
/*
* 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;
}
}

View File

@ -45,7 +45,6 @@
* 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$
@ -104,19 +103,19 @@ public static HttpConnection getLfsConnection(Repository db, String method,
* 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;
@ -124,6 +123,7 @@ static String getLfsUrl(Repository db, String purpose,
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.

View File

@ -28,21 +28,22 @@ public static LfsText get() {
// @formatter:off
/***/ public String corruptLongObject;
/***/ public String inconsistentMediafileLength;
/***/ public String dotLfsConfigReadFailed;
/***/ public String inconsistentContentLength;
/***/ public String inconsistentMediafileLength;
/***/ public String incorrectLONG_OBJECT_ID_LENGTH;
/***/ public String invalidLongId;
/***/ public String invalidLongIdLength;
/***/ public String lfsUnavailable;
/***/ public String protocolError;
/***/ public String requiredHashFunctionNotAvailable;
/***/ public String repositoryNotFound;
/***/ public String repositoryReadOnly;
/***/ public String lfsUnathorized;
/***/ public String lfsFailedToGetRepository;
/***/ public String lfsNoDownloadUrl;
/***/ public String serverFailure;
/***/ public String wrongAmoutOfDataReceived;
/***/ public String userConfigInvalid;
/***/ public String lfsUnauthorized;
/***/ public String lfsUnavailable;
/***/ public String missingLocalObject;
/***/ public String protocolError;
/***/ public String repositoryNotFound;
/***/ public String repositoryReadOnly;
/***/ public String requiredHashFunctionNotAvailable;
/***/ public String serverFailure;
/***/ public String userConfigInvalid;
/***/ public String wrongAmountOfDataReceived;
}

View File

@ -81,6 +81,13 @@ public final class Constants {
*/
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.
*

View File

@ -10,6 +10,7 @@
package org.eclipse.jgit.api;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
@ -392,28 +393,64 @@ public void testPushDefaultMatching() throws Exception {
git.add().addFilepattern("f").call();
RevCommit commit = git.commit().setMessage("adding f").call();
git.checkout().setName("also-pushed").setCreateBranch(true).call();
git.checkout().setName("not-pushed").setCreateBranch(true).call();
git.checkout().setName("branchtopush").setCreateBranch(true).call();
assertEquals(null,
git2.getRepository().resolve("refs/heads/branchtopush"));
assertEquals(null,
git2.getRepository().resolve("refs/heads/also-pushed"));
git2.getRepository().resolve("refs/heads/not-pushed"));
assertEquals(null,
git2.getRepository().resolve("refs/heads/master"));
git.push().setRemote("test").setPushDefault(PushDefault.MATCHING)
// push master and branchtopush
git.push().setRemote("test").setRefSpecs(
new RefSpec("refs/heads/master:refs/heads/master"),
new RefSpec(
"refs/heads/branchtopush:refs/heads/branchtopush"))
.call();
assertEquals(commit.getId(),
git2.getRepository().resolve("refs/heads/branchtopush"));
assertEquals(commit.getId(),
git2.getRepository().resolve("refs/heads/also-pushed"));
assertEquals(commit.getId(),
git2.getRepository().resolve("refs/heads/master"));
assertEquals(commit.getId(), git.getRepository()
.resolve("refs/remotes/origin/branchtopush"));
assertEquals(commit.getId(), git.getRepository()
.resolve("refs/remotes/origin/also-pushed"));
assertEquals(commit.getId(),
git2.getRepository().resolve("refs/heads/branchtopush"));
assertEquals(null,
git2.getRepository().resolve("refs/heads/not-pushed"));
// Create two different commits on these two branches
writeTrashFile("b", "on branchtopush");
git.add().addFilepattern("b").call();
RevCommit bCommit = git.commit().setMessage("on branchtopush")
.call();
git.checkout().setName("master").call();
writeTrashFile("m", "on master");
git.add().addFilepattern("m").call();
RevCommit mCommit = git.commit().setMessage("on master").call();
// Now push with mode "matching": should push both branches.
Iterable<PushResult> result = git.push().setRemote("test")
.setPushDefault(PushDefault.MATCHING)
.call();
int n = 0;
for (PushResult r : result) {
n++;
assertEquals(1, n);
assertEquals(2, r.getRemoteUpdates().size());
for (RemoteRefUpdate update : r.getRemoteUpdates()) {
assertFalse(update.isMatching());
assertTrue(update.getSrcRef()
.equals("refs/heads/branchtopush")
|| update.getSrcRef().equals("refs/heads/master"));
assertEquals(RemoteRefUpdate.Status.OK, update.getStatus());
}
}
assertEquals(bCommit.getId(),
git2.getRepository().resolve("refs/heads/branchtopush"));
assertEquals(null,
git2.getRepository().resolve("refs/heads/not-pushed"));
assertEquals(mCommit.getId(),
git2.getRepository().resolve("refs/heads/master"));
assertEquals(bCommit.getId(), git.getRepository()
.resolve("refs/remotes/origin/branchtopush"));
assertEquals(null, git.getRepository()
.resolve("refs/remotes/origin/not-pushed"));
assertEquals(mCommit.getId(),
git.getRepository().resolve("refs/remotes/origin/master"));
}
}

View File

@ -21,8 +21,10 @@
import org.eclipse.jgit.api.errors.NoFilepatternException;
import org.eclipse.jgit.errors.NoWorkTreeException;
import org.eclipse.jgit.junit.RepositoryTestCase;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.Sets;
import org.eclipse.jgit.storage.file.FileBasedConfig;
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
import org.eclipse.jgit.util.FS;
import org.junit.Test;
@ -181,4 +183,31 @@ public void testFolderPrefix() throws Exception {
}
}
@Test
public void testNestedCommittedGitRepoAndPathFilter() throws Exception {
commitFile("file.txt", "file", "master");
try (Repository inner = new FileRepositoryBuilder()
.setWorkTree(new File(db.getWorkTree(), "subgit")).build()) {
inner.create();
writeTrashFile("subgit/sub.txt", "sub");
try (Git outerGit = new Git(db); Git innerGit = new Git(inner)) {
innerGit.add().addFilepattern("sub.txt").call();
innerGit.commit().setMessage("Inner commit").call();
outerGit.add().addFilepattern("subgit").call();
outerGit.commit().setMessage("Outer commit").call();
assertTrue(innerGit.status().call().isClean());
assertTrue(outerGit.status().call().isClean());
writeTrashFile("subgit/sub.txt", "sub2");
assertFalse(innerGit.status().call().isClean());
assertFalse(outerGit.status().call().isClean());
assertTrue(
outerGit.status().addPath("file.txt").call().isClean());
assertTrue(outerGit.status().addPath("doesntexist").call()
.isClean());
assertFalse(
outerGit.status().addPath("subgit").call().isClean());
}
}
}
}

View File

@ -14,14 +14,19 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintStream;
import java.nio.charset.StandardCharsets;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.hooks.PrePushHook;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ObjectIdRef;
import org.eclipse.jgit.lib.ProgressMonitor;
@ -31,6 +36,7 @@
import org.eclipse.jgit.lib.TextProgressMonitor;
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
import org.eclipse.jgit.util.io.NullOutputStream;
import org.junit.Before;
import org.junit.Test;
@ -220,7 +226,17 @@ public void testUpdateUnexpectedRemoteVsForce() throws IOException {
.fromString("0000000000000000000000000000000000000001"));
final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null);
try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes, true,
StandardCharsets.UTF_8);
PrintStream err = new PrintStream(NullOutputStream.INSTANCE)) {
MockPrePushHook hook = new MockPrePushHook(db, out, err);
testOneUpdateStatus(rru, ref, Status.REJECTED_REMOTE_CHANGED, null,
hook);
out.flush();
String result = new String(bytes.toString(StandardCharsets.UTF_8));
assertEquals("", result);
}
}
/**
@ -256,10 +272,22 @@ public void testUpdateMixedCases() throws IOException {
refUpdates.add(rruOk);
refUpdates.add(rruReject);
advertisedRefs.add(refToChange);
executePush();
assertEquals(Status.OK, rruOk.getStatus());
assertTrue(rruOk.isFastForward());
assertEquals(Status.NON_EXISTING, rruReject.getStatus());
try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
PrintStream out = new PrintStream(bytes, true,
StandardCharsets.UTF_8);
PrintStream err = new PrintStream(NullOutputStream.INSTANCE)) {
MockPrePushHook hook = new MockPrePushHook(db, out, err);
executePush(hook);
assertEquals(Status.OK, rruOk.getStatus());
assertTrue(rruOk.isFastForward());
assertEquals(Status.NON_EXISTING, rruReject.getStatus());
out.flush();
String result = new String(bytes.toString(StandardCharsets.UTF_8));
assertEquals(
"null 0000000000000000000000000000000000000000 "
+ "refs/heads/master 2c349335b7f797072cf729c4f3bb0914ecb6dec9\n",
result);
}
}
/**
@ -346,10 +374,18 @@ private PushResult testOneUpdateStatus(final RemoteRefUpdate rru,
final Ref advertisedRef, final Status expectedStatus,
Boolean fastForward) throws NotSupportedException,
TransportException {
return testOneUpdateStatus(rru, advertisedRef, expectedStatus,
fastForward, null);
}
private PushResult testOneUpdateStatus(final RemoteRefUpdate rru,
final Ref advertisedRef, final Status expectedStatus,
Boolean fastForward, PrePushHook hook)
throws NotSupportedException, TransportException {
refUpdates.add(rru);
if (advertisedRef != null)
advertisedRefs.add(advertisedRef);
final PushResult result = executePush();
final PushResult result = executePush(hook);
assertEquals(expectedStatus, rru.getStatus());
if (fastForward != null)
assertEquals(fastForward, Boolean.valueOf(rru.isFastForward()));
@ -358,7 +394,12 @@ private PushResult testOneUpdateStatus(final RemoteRefUpdate rru,
private PushResult executePush() throws NotSupportedException,
TransportException {
process = new PushProcess(transport, refUpdates);
return executePush(null);
}
private PushResult executePush(PrePushHook hook)
throws NotSupportedException, TransportException {
process = new PushProcess(transport, refUpdates, hook);
return process.execute(new TextProgressMonitor());
}
@ -416,4 +457,20 @@ public void push(ProgressMonitor monitor,
}
}
}
private static class MockPrePushHook extends PrePushHook {
private final PrintStream output;
public MockPrePushHook(Repository repo, PrintStream out,
PrintStream err) {
super(repo, out, err);
output = out;
}
@Override
protected void doRun() throws AbortedByHookException, IOException {
output.print(getStdinArgs());
}
}
}

View File

@ -466,4 +466,18 @@ public void onlyWildCard() {
assertTrue(a.matchSource("refs/heads/master"));
assertNull(a.getDestination());
}
@Test
public void matching() {
RefSpec a = new RefSpec(":");
assertTrue(a.isMatching());
assertFalse(a.isForceUpdate());
}
@Test
public void matchingForced() {
RefSpec a = new RefSpec("+:");
assertTrue(a.isMatching());
assertTrue(a.isForceUpdate());
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2016, Christian Halstrick <christian.halstrick@sap.com> and others
* Copyright (C) 2016, 2022 Christian Halstrick <christian.halstrick@sap.com> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@ -10,12 +10,17 @@
package org.eclipse.jgit.util;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.HashSet;
import java.util.Set;
import org.eclipse.jgit.api.Git;
import org.eclipse.jgit.api.MergeResult;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.attributes.FilterCommand;
import org.eclipse.jgit.attributes.FilterCommandFactory;
@ -86,6 +91,14 @@ public void setUp() throws Exception {
secondCommit = git.commit().setMessage("Second commit").call();
}
@Override
public void tearDown() throws Exception {
Set<String> existingFilters = new HashSet<>(
FilterCommandRegistry.getRegisteredFilterCommands());
existingFilters.forEach(FilterCommandRegistry::unregister);
super.tearDown();
}
@Test
public void testBuiltinCleanFilter()
throws IOException, GitAPIException {
@ -217,4 +230,133 @@ public void testBuiltinCleanAndSmudgeFilter() throws IOException, GitAPIExceptio
config.save();
}
@Test
public void testBranchSwitch() throws Exception {
String builtinCommandPrefix = "jgit://builtin/test/";
FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
new TestCommandFactory('s'));
FilterCommandRegistry.register(builtinCommandPrefix + "clean",
new TestCommandFactory('c'));
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "test", "smudge",
builtinCommandPrefix + "smudge");
config.setString("filter", "test", "clean",
builtinCommandPrefix + "clean");
config.save();
// We're on the test branch
File aFile = writeTrashFile("a.txt", "a");
writeTrashFile(".gitattributes", "a.txt filter=test");
File cFile = writeTrashFile("cc/c.txt", "C");
writeTrashFile("cc/.gitattributes", "c.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On test").call();
git.checkout().setName("master").call();
git.branchCreate().setName("other").call();
git.checkout().setName("other").call();
writeTrashFile("b.txt", "b");
writeTrashFile(".gitattributes", "b.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On other").call();
git.checkout().setName("test").call();
checkFile(aFile, "scsa");
checkFile(cFile, "scsC");
}
@Test
public void testCheckoutSingleFile() throws Exception {
String builtinCommandPrefix = "jgit://builtin/test/";
FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
new TestCommandFactory('s'));
FilterCommandRegistry.register(builtinCommandPrefix + "clean",
new TestCommandFactory('c'));
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "test", "smudge",
builtinCommandPrefix + "smudge");
config.setString("filter", "test", "clean",
builtinCommandPrefix + "clean");
config.save();
// We're on the test branch
File aFile = writeTrashFile("a.txt", "a");
File attributes = writeTrashFile(".gitattributes", "a.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On test").call();
git.checkout().setName("master").call();
git.branchCreate().setName("other").call();
git.checkout().setName("other").call();
writeTrashFile("b.txt", "b");
writeTrashFile(".gitattributes", "b.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On other").call();
git.checkout().setName("master").call();
assertFalse(aFile.exists());
assertFalse(attributes.exists());
git.checkout().setStartPoint("test").addPath("a.txt").call();
checkFile(aFile, "scsa");
}
@Test
public void testCheckoutSingleFile2() throws Exception {
String builtinCommandPrefix = "jgit://builtin/test/";
FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
new TestCommandFactory('s'));
FilterCommandRegistry.register(builtinCommandPrefix + "clean",
new TestCommandFactory('c'));
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "test", "smudge",
builtinCommandPrefix + "smudge");
config.setString("filter", "test", "clean",
builtinCommandPrefix + "clean");
config.save();
// We're on the test branch
File aFile = writeTrashFile("a.txt", "a");
File attributes = writeTrashFile(".gitattributes", "a.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On test").call();
git.checkout().setName("master").call();
git.branchCreate().setName("other").call();
git.checkout().setName("other").call();
writeTrashFile("b.txt", "b");
writeTrashFile(".gitattributes", "b.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On other").call();
git.checkout().setName("master").call();
assertFalse(aFile.exists());
assertFalse(attributes.exists());
writeTrashFile(".gitattributes", "");
git.checkout().setStartPoint("test").addPath("a.txt").call();
checkFile(aFile, "scsa");
}
@Test
public void testMerge() throws Exception {
String builtinCommandPrefix = "jgit://builtin/test/";
FilterCommandRegistry.register(builtinCommandPrefix + "smudge",
new TestCommandFactory('s'));
FilterCommandRegistry.register(builtinCommandPrefix + "clean",
new TestCommandFactory('c'));
StoredConfig config = git.getRepository().getConfig();
config.setString("filter", "test", "smudge",
builtinCommandPrefix + "smudge");
config.setString("filter", "test", "clean",
builtinCommandPrefix + "clean");
config.save();
// We're on the test branch. Set up two branches that are expected to
// merge cleanly.
File aFile = writeTrashFile("a.txt", "a");
writeTrashFile(".gitattributes", "a.txt filter=test");
git.add().addFilepattern(".").call();
RevCommit aCommit = git.commit().setMessage("On test").call();
git.checkout().setName("master").call();
assertFalse(aFile.exists());
git.branchCreate().setName("other").call();
git.checkout().setName("other").call();
writeTrashFile("b/b.txt", "b");
writeTrashFile("b/.gitattributes", "b.txt filter=test");
git.add().addFilepattern(".").call();
git.commit().setMessage("On other").call();
MergeResult result = git.merge().include(aCommit).call();
assertEquals(MergeResult.MergeStatus.MERGED, result.getMergeStatus());
checkFile(aFile, "scsa");
}
}

View File

@ -39,6 +39,26 @@
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/merge/ResolveMerger.java" type="org.eclipse.jgit.merge.ResolveMerger">
<filter id="338792546">
<message_arguments>
<message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
<message_argument value="addCheckoutMetadata(String, Attributes)"/>
</message_arguments>
</filter>
<filter id="338792546">
<message_arguments>
<message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
<message_argument value="addToCheckout(String, DirCacheEntry, Attributes)"/>
</message_arguments>
</filter>
<filter id="338792546">
<message_arguments>
<message_argument value="org.eclipse.jgit.merge.ResolveMerger"/>
<message_argument value="processEntry(CanonicalTreeParser, CanonicalTreeParser, CanonicalTreeParser, DirCacheBuildIterator, WorkingTreeIterator, boolean, Attributes)"/>
</message_arguments>
</filter>
</resource>
<resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
<filter id="338792546">
<message_arguments>

View File

@ -230,7 +230,7 @@ private void determineDefaultRefSpecs(Config config)
refSpecs.add(new RefSpec(getCurrentBranch()));
break;
case MATCHING:
setPushAll();
refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
break;
case NOTHING:
throw new InvalidRefNameException(

View File

@ -1,43 +1,11 @@
/*
* Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com>
* Copyright (C) 2015, 2022 Ivan Motsch <ivan.motsch@bsiag.com> and others
*
* 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
* 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.
*
* 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.
* SPDX-License-Identifier: BSD-3-Clause
*/
package org.eclipse.jgit.attributes;
@ -46,6 +14,7 @@
import java.util.List;
import java.util.ListIterator;
import java.util.Map;
import java.util.function.Supplier;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.attributes.Attribute.State;
@ -84,6 +53,8 @@ public class AttributesHandler {
private final TreeWalk treeWalk;
private final Supplier<CanonicalTreeParser> attributesTree;
private final AttributesNode globalNode;
private final AttributesNode infoNode;
@ -98,22 +69,41 @@ public class AttributesHandler {
* @param treeWalk
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
* @throws java.io.IOException
* @deprecated since 6.1, use {@link #AttributesHandler(TreeWalk, Supplier)}
* instead
*/
@Deprecated
public AttributesHandler(TreeWalk treeWalk) throws IOException {
this(treeWalk, () -> treeWalk.getTree(CanonicalTreeParser.class));
}
/**
* Create an {@link org.eclipse.jgit.attributes.AttributesHandler} with
* default rules as well as merged rules from global, info and worktree root
* attributes
*
* @param treeWalk
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
* @param attributesTree
* the tree to read .gitattributes from
* @throws java.io.IOException
* @since 6.1
*/
public AttributesHandler(TreeWalk treeWalk,
Supplier<CanonicalTreeParser> attributesTree) throws IOException {
this.treeWalk = treeWalk;
AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
this.attributesTree = attributesTree;
AttributesNodeProvider attributesNodeProvider = treeWalk
.getAttributesNodeProvider();
this.globalNode = attributesNodeProvider != null
? attributesNodeProvider.getGlobalAttributesNode() : null;
this.infoNode = attributesNodeProvider != null
? attributesNodeProvider.getInfoAttributesNode() : null;
AttributesNode rootNode = attributesNode(treeWalk,
rootOf(
treeWalk.getTree(WorkingTreeIterator.class)),
rootOf(
treeWalk.getTree(DirCacheIterator.class)),
rootOf(treeWalk
.getTree(CanonicalTreeParser.class)));
rootOf(treeWalk.getTree(WorkingTreeIterator.class)),
rootOf(treeWalk.getTree(DirCacheIterator.class)),
rootOf(attributesTree.get()));
expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
@ -152,7 +142,7 @@ public Attributes getAttributes() throws IOException {
isDirectory,
treeWalk.getTree(WorkingTreeIterator.class),
treeWalk.getTree(DirCacheIterator.class),
treeWalk.getTree(CanonicalTreeParser.class),
attributesTree.get(),
attributes);
// Gets the attributes located in the global attribute file

View File

@ -4,7 +4,8 @@
* Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
* Copyright (C) 2019-2020, Andre Bossert <andre.bossert@siemens.com>
* Copyright (C) 2019, 2020, Andre Bossert <andre.bossert@siemens.com>
* Copyright (C) 2017, 2022, Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@ -299,7 +300,7 @@ public void preScanTwoTrees() throws CorruptObjectException, IOException {
walk = new NameConflictTreeWalk(repo);
builder = dc.builder();
addTree(walk, headCommitTree);
walk.setHead(addTree(walk, headCommitTree));
addTree(walk, mergeCommitTree);
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
walk.addTree(workingTree);
@ -315,13 +316,6 @@ public void preScanTwoTrees() throws CorruptObjectException, IOException {
}
}
private void addTree(TreeWalk tw, ObjectId id) throws MissingObjectException, IncorrectObjectTypeException, IOException {
if (id == null)
tw.addTree(new EmptyTreeIterator());
else
tw.addTree(id);
}
/**
* Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
* there is no head yet.
@ -341,7 +335,7 @@ public void prescanOneTree()
builder = dc.builder();
walk = new NameConflictTreeWalk(repo);
addTree(walk, mergeCommitTree);
walk.setHead(addTree(walk, mergeCommitTree));
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
walk.addTree(workingTree);
workingTree.setDirCacheIterator(walk, dciPos);
@ -356,6 +350,14 @@ public void prescanOneTree()
conflicts.removeAll(removed);
}
private int addTree(TreeWalk tw, ObjectId id) throws MissingObjectException,
IncorrectObjectTypeException, IOException {
if (id == null) {
return tw.addTree(new EmptyTreeIterator());
}
return tw.addTree(id);
}
/**
* Processing an entry in the context of {@link #prescanOneTree()} when only
* one tree is given
@ -382,17 +384,14 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
// failOnConflict is false. Putting something to conflicts
// would mean we delete it. Instead we want the mergeCommit
// content to be checked out.
update(m.getEntryPathString(), m.getEntryObjectId(),
m.getEntryFileMode());
update(m);
}
} else
update(m.getEntryPathString(), m.getEntryObjectId(),
m.getEntryFileMode());
update(m);
} else if (f == null || !m.idEqual(i)) {
// The working tree file is missing or the merge content differs
// from index content
update(m.getEntryPathString(), m.getEntryObjectId(),
m.getEntryFileMode());
update(m);
} else if (i.getDirCacheEntry() != null) {
// The index contains a file (and not a folder)
if (f.isModified(i.getDirCacheEntry(), true,
@ -400,8 +399,7 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
|| i.getDirCacheEntry().getStage() != 0)
// The working tree file is dirty or the index contains a
// conflict
update(m.getEntryPathString(), m.getEntryObjectId(),
m.getEntryFileMode());
update(m);
else {
// update the timestamp of the index with the one from the
// file if not set, as we are sure to be in sync here.
@ -802,7 +800,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
conflict(name, dce, h, m); // 1
} else {
update(name, mId, mMode); // 2
update(1, name, mId, mMode); // 2
}
break;
@ -828,7 +826,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
// are found later
break;
case 0xD0F: // 19
update(name, mId, mMode);
update(1, name, mId, mMode);
break;
case 0xDF0: // conflict without a rule
case 0x0FD: // 15
@ -839,7 +837,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
if (isModifiedSubtree_IndexWorkingtree(name))
conflict(name, dce, h, m); // 8
else
update(name, mId, mMode); // 7
update(1, name, mId, mMode); // 7
} else
conflict(name, dce, h, m); // 9
break;
@ -859,7 +857,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
break;
case 0x0DF: // 16 17
if (!isModifiedSubtree_IndexWorkingtree(name))
update(name, mId, mMode);
update(1, name, mId, mMode);
else
conflict(name, dce, h, m);
break;
@ -929,7 +927,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
// At least one of Head, Index, Merge is not empty
// -> only Merge contains something for this path. Use it!
// Potentially update the file
update(name, mId, mMode); // 1
update(1, name, mId, mMode); // 1
else if (m == null)
// Nothing in Merge
// Something in Head
@ -947,7 +945,7 @@ else if (m == null)
// find in Merge. Potentially updates the file.
if (equalIdAndMode(hId, hMode, mId, mMode)) {
if (initialCheckout || force) {
update(name, mId, mMode);
update(1, name, mId, mMode);
} else {
keep(name, dce, f);
}
@ -1131,7 +1129,7 @@ && isModified_IndexTree(name, iId, iMode, mId, mMode,
// TODO check that we don't overwrite some unsaved
// file content
update(name, mId, mMode);
update(1, name, mId, mMode);
} else if (dce != null
&& (f != null && f.isModified(dce, true,
this.walk.getObjectReader()))) {
@ -1150,7 +1148,7 @@ && isModified_IndexTree(name, iId, iMode, mId, mMode,
// -> Standard case when switching between branches:
// Nothing new in index but something different in
// Merge. Update index and file
update(name, mId, mMode);
update(1, name, mId, mMode);
}
} else {
// Head differs from index or merge is same as index
@ -1237,12 +1235,17 @@ private void remove(String path) {
removed.add(path);
}
private void update(String path, ObjectId mId, FileMode mode)
throws IOException {
private void update(CanonicalTreeParser tree) throws IOException {
update(0, tree.getEntryPathString(), tree.getEntryObjectId(),
tree.getEntryFileMode());
}
private void update(int index, String path, ObjectId mId,
FileMode mode) throws IOException {
if (!FileMode.TREE.equals(mode)) {
updated.put(path, new CheckoutMetadata(
walk.getEolStreamType(CHECKOUT_OP),
walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
walk.getCheckoutEolStreamType(index),
walk.getSmudgeCommand(index)));
DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
entry.setObjectId(mId);

View File

@ -568,6 +568,9 @@ public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
smw.setTree(new DirCacheIterator(dirCache));
if (filter != null) {
smw.setFilter(filter);
}
smw.setBuilderFactory(factory);
while (smw.next()) {
IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;

View File

@ -3,7 +3,7 @@
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
* Copyright (C) 2012, Research In Motion Limited
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
* Copyright (C) 2018, Thomas Wolf <thomas.wolf@paranor.ch> and others
* Copyright (C) 2018, 2022 Thomas Wolf <thomas.wolf@paranor.ch> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@ -276,11 +276,15 @@ public enum MergeFailureReason {
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
/**
* Keeps {@link CheckoutMetadata} for {@link #checkout()} and
* {@link #cleanUp()}.
* Keeps {@link CheckoutMetadata} for {@link #checkout()}.
*/
private Map<String, CheckoutMetadata> checkoutMetadata;
/**
* Keeps {@link CheckoutMetadata} for {@link #cleanUp()}.
*/
private Map<String, CheckoutMetadata> cleanupMetadata;
private static MergeAlgorithm getMergeAlgorithm(Config config) {
SupportedAlgorithm diffAlg = config.getEnum(
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
@ -383,12 +387,14 @@ protected boolean mergeImpl() throws IOException {
}
if (!inCore) {
checkoutMetadata = new HashMap<>();
cleanupMetadata = new HashMap<>();
}
try {
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
false);
} finally {
checkoutMetadata = null;
cleanupMetadata = null;
if (implicitDirCache) {
dircache.unlock();
}
@ -447,7 +453,7 @@ protected void cleanUp() throws NoWorkTreeException,
DirCacheEntry entry = dc.getEntry(mpath);
if (entry != null) {
DirCacheCheckout.checkoutEntry(db, entry, reader, false,
checkoutMetadata.get(mpath));
cleanupMetadata.get(mpath));
}
mpathsIt.remove();
}
@ -501,22 +507,26 @@ private DirCacheEntry keep(DirCacheEntry e) {
* Remembers the {@link CheckoutMetadata} for the given path; it may be
* needed in {@link #checkout()} or in {@link #cleanUp()}.
*
* @param map
* to add the metadata to
* @param path
* of the current node
* @param attributes
* for the current node
* to use for determining the metadata
* @throws IOException
* if the smudge filter cannot be determined
* @since 5.1
* @since 6.1
*/
protected void addCheckoutMetadata(String path, Attributes attributes)
protected void addCheckoutMetadata(Map<String, CheckoutMetadata> map,
String path, Attributes attributes)
throws IOException {
if (checkoutMetadata != null) {
if (map != null) {
EolStreamType eol = EolStreamTypeUtil.detectStreamType(
OperationType.CHECKOUT_OP, workingTreeOptions, attributes);
OperationType.CHECKOUT_OP, workingTreeOptions,
attributes);
CheckoutMetadata data = new CheckoutMetadata(eol,
tw.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
checkoutMetadata.put(path, data);
tw.getSmudgeCommand(attributes));
map.put(path, data);
}
}
@ -529,15 +539,17 @@ protected void addCheckoutMetadata(String path, Attributes attributes)
* @param entry
* to add
* @param attributes
* for the current entry
* the {@link Attributes} of the trees
* @throws IOException
* if the {@link CheckoutMetadata} cannot be determined
* @since 5.1
* @since 6.1
*/
protected void addToCheckout(String path, DirCacheEntry entry,
Attributes attributes) throws IOException {
Attributes[] attributes)
throws IOException {
toBeCheckedOut.put(path, entry);
addCheckoutMetadata(path, attributes);
addCheckoutMetadata(cleanupMetadata, path, attributes[T_OURS]);
addCheckoutMetadata(checkoutMetadata, path, attributes[T_THEIRS]);
}
/**
@ -549,7 +561,7 @@ protected void addToCheckout(String path, DirCacheEntry entry,
* @param isFile
* whether it is a file
* @param attributes
* for the entry
* to use for determining the {@link CheckoutMetadata}
* @throws IOException
* if the {@link CheckoutMetadata} cannot be determined
* @since 5.1
@ -558,7 +570,7 @@ protected void addDeletion(String path, boolean isFile,
Attributes attributes) throws IOException {
toBeDeleted.add(path);
if (isFile) {
addCheckoutMetadata(path, attributes);
addCheckoutMetadata(cleanupMetadata, path, attributes);
}
}
@ -599,7 +611,7 @@ protected void addDeletion(String path, boolean isFile,
* see
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
* @param attributes
* the attributes defined for this entry
* the {@link Attributes} for the three trees
* @return <code>false</code> if the merge will fail because the index entry
* didn't match ours or the working-dir file was dirty and a
* conflict occurred
@ -607,12 +619,12 @@ protected void addDeletion(String path, boolean isFile,
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
* @throws org.eclipse.jgit.errors.CorruptObjectException
* @throws java.io.IOException
* @since 4.9
* @since 6.1
*/
protected boolean processEntry(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
DirCacheBuildIterator index, WorkingTreeIterator work,
boolean ignoreConflicts, Attributes attributes)
boolean ignoreConflicts, Attributes[] attributes)
throws MissingObjectException, IncorrectObjectTypeException,
CorruptObjectException, IOException {
enterSubtree = true;
@ -729,7 +741,7 @@ protected boolean processEntry(CanonicalTreeParser base,
// Base, ours, and theirs all contain a folder: don't delete
return true;
}
addDeletion(tw.getPathString(), nonTree(modeO), attributes);
addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
return true;
}
@ -772,7 +784,7 @@ protected boolean processEntry(CanonicalTreeParser base,
if (nonTree(modeO) && nonTree(modeT)) {
// Check worktree before modifying files
boolean worktreeDirty = isWorktreeDirty(work, ourDce);
if (!attributes.canBeContentMerged() && worktreeDirty) {
if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
return false;
}
@ -791,7 +803,7 @@ protected boolean processEntry(CanonicalTreeParser base,
mergeResults.put(tw.getPathString(), result);
unmergedPaths.add(tw.getPathString());
return true;
} else if (!attributes.canBeContentMerged()) {
} else if (!attributes[T_OURS].canBeContentMerged()) {
// File marked as binary
switch (getContentMergeStrategy()) {
case OURS:
@ -842,13 +854,16 @@ protected boolean processEntry(CanonicalTreeParser base,
if (ignoreConflicts) {
result.setContainsConflicts(false);
}
updateIndex(base, ours, theirs, result, attributes);
updateIndex(base, ours, theirs, result, attributes[T_OURS]);
String currentPath = tw.getPathString();
if (result.containsConflicts() && !ignoreConflicts) {
unmergedPaths.add(currentPath);
}
modifiedFiles.add(currentPath);
addCheckoutMetadata(currentPath, attributes);
addCheckoutMetadata(cleanupMetadata, currentPath,
attributes[T_OURS]);
addCheckoutMetadata(checkoutMetadata, currentPath,
attributes[T_THEIRS]);
} else if (modeO != modeT) {
// OURS or THEIRS has been deleted
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
@ -881,7 +896,8 @@ protected boolean processEntry(CanonicalTreeParser base,
// markers). But also stage 0 of the index is filled
// with that content.
result.setContainsConflicts(false);
updateIndex(base, ours, theirs, result, attributes);
updateIndex(base, ours, theirs, result,
attributes[T_OURS]);
} else {
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
0);
@ -896,11 +912,9 @@ protected boolean processEntry(CanonicalTreeParser base,
if (isWorktreeDirty(work, ourDce)) {
return false;
}
if (nonTree(modeT)) {
if (e != null) {
addToCheckout(tw.getPathString(), e,
attributes);
}
if (nonTree(modeT) && e != null) {
addToCheckout(tw.getPathString(), e,
attributes);
}
}
@ -945,14 +959,16 @@ private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
*/
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
CanonicalTreeParser ours, CanonicalTreeParser theirs,
Attributes attributes, ContentMergeStrategy strategy)
Attributes[] attributes, ContentMergeStrategy strategy)
throws BinaryBlobException, IOException {
// TW: The attributes here are used to determine the LFS smudge filter.
// Is doing a content merge on LFS items really a good idea??
RawText baseText = base == null ? RawText.EMPTY_TEXT
: getRawText(base.getEntryObjectId(), attributes);
: getRawText(base.getEntryObjectId(), attributes[T_BASE]);
RawText ourText = ours == null ? RawText.EMPTY_TEXT
: getRawText(ours.getEntryObjectId(), attributes);
: getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
: getRawText(theirs.getEntryObjectId(), attributes);
: getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
mergeAlgorithm.setContentMergeStrategy(strategy);
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
ourText, theirsText);
@ -1342,7 +1358,7 @@ protected boolean mergeTrees(AbstractTreeIterator baseTree,
tw = new NameConflictTreeWalk(db, reader);
tw.addTree(baseTree);
tw.addTree(headTree);
tw.setHead(tw.addTree(headTree));
tw.addTree(mergeTree);
int dciPos = tw.addTree(buildIt);
if (workingTreeIterator != null) {
@ -1403,6 +1419,13 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
boolean hasAttributeNodeProvider = treeWalk
.getAttributesNodeProvider() != null;
while (treeWalk.next()) {
Attributes[] attributes = { NO_ATTRIBUTES, NO_ATTRIBUTES,
NO_ATTRIBUTES };
if (hasAttributeNodeProvider) {
attributes[T_BASE] = treeWalk.getAttributes(T_BASE);
attributes[T_OURS] = treeWalk.getAttributes(T_OURS);
attributes[T_THEIRS] = treeWalk.getAttributes(T_THEIRS);
}
if (!processEntry(
treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
treeWalk.getTree(T_OURS, CanonicalTreeParser.class),
@ -1410,9 +1433,7 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
WorkingTreeIterator.class) : null,
ignoreConflicts, hasAttributeNodeProvider
? treeWalk.getAttributes()
: NO_ATTRIBUTES)) {
ignoreConflicts, attributes)) {
cleanUp();
return false;
}

View File

@ -18,11 +18,15 @@
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.hooks.PrePushHook;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
@ -58,6 +62,8 @@ class PushProcess {
/** A list of option strings associated with this push */
private List<String> pushOptions;
private final PrePushHook prePush;
/**
* Create process for specified transport and refs updates specification.
*
@ -66,12 +72,14 @@ class PushProcess {
* connection.
* @param toPush
* specification of refs updates (and local tracking branches).
*
* @param prePush
* {@link PrePushHook} to run after the remote advertisement has
* been gotten
* @throws TransportException
*/
PushProcess(final Transport transport,
final Collection<RemoteRefUpdate> toPush) throws TransportException {
this(transport, toPush, null);
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
PrePushHook prePush) throws TransportException {
this(transport, toPush, prePush, null);
}
/**
@ -82,16 +90,19 @@ class PushProcess {
* connection.
* @param toPush
* specification of refs updates (and local tracking branches).
* @param prePush
* {@link PrePushHook} to run after the remote advertisement has
* been gotten
* @param out
* OutputStream to write messages to
* @throws TransportException
*/
PushProcess(final Transport transport,
final Collection<RemoteRefUpdate> toPush, OutputStream out)
throws TransportException {
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
PrePushHook prePush, OutputStream out) throws TransportException {
this.walker = new RevWalk(transport.local);
this.transport = transport;
this.toPush = new LinkedHashMap<>();
this.prePush = prePush;
this.out = out;
this.pushOptions = transport.getPushOptions();
for (RemoteRefUpdate rru : toPush) {
@ -129,10 +140,38 @@ PushResult execute(ProgressMonitor monitor)
res.setAdvertisedRefs(transport.getURI(), connection
.getRefsMap());
res.peerUserAgent = connection.getPeerUserAgent();
res.setRemoteUpdates(toPush);
monitor.endTask();
Map<String, RemoteRefUpdate> expanded = expandMatching();
toPush.clear();
toPush.putAll(expanded);
res.setRemoteUpdates(toPush);
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
List<RemoteRefUpdate> willBeAttempted = preprocessed.values()
.stream().filter(u -> {
switch (u.getStatus()) {
case NON_EXISTING:
case REJECTED_NODELETE:
case REJECTED_NONFASTFORWARD:
case REJECTED_OTHER_REASON:
case REJECTED_REMOTE_CHANGED:
case UP_TO_DATE:
return false;
default:
return true;
}
}).collect(Collectors.toList());
if (!willBeAttempted.isEmpty()) {
if (prePush != null) {
try {
prePush.setRefs(willBeAttempted);
prePush.call();
} catch (AbortedByHookException | IOException e) {
throw new TransportException(e.getMessage(), e);
}
}
}
if (transport.isDryRun())
modifyUpdatesForDryRun();
else if (!preprocessed.isEmpty())
@ -201,25 +240,8 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
continue;
}
// check for fast-forward:
// - both old and new ref must point to commits, AND
// - both of them must be known for us, exist in repository, AND
// - old commit must be ancestor of new commit
boolean fastForward = true;
try {
RevObject oldRev = walker.parseAny(advertisedOld);
final RevObject newRev = walker.parseAny(rru.getNewObjectId());
if (!(oldRev instanceof RevCommit)
|| !(newRev instanceof RevCommit)
|| !walker.isMergedInto((RevCommit) oldRev,
(RevCommit) newRev))
fastForward = false;
} catch (MissingObjectException x) {
fastForward = false;
} catch (Exception x) {
throw new TransportException(transport.getURI(), MessageFormat.format(
JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x);
}
boolean fastForward = isFastForward(advertisedOld,
rru.getNewObjectId());
rru.setFastForward(fastForward);
if (!fastForward && !rru.isForceUpdate()) {
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
@ -233,6 +255,134 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
return result;
}
/**
* Determines whether an update from {@code oldOid} to {@code newOid} is a
* fast-forward update:
* <ul>
* <li>both old and new must be commits, AND</li>
* <li>both of them must be known to us and exist in the repository,
* AND</li>
* <li>the old commit must be an ancestor of the new commit.</li>
* </ul>
*
* @param oldOid
* {@link ObjectId} of the old commit
* @param newOid
* {@link ObjectId} of the new commit
* @return {@code true} if the update fast-forwards, {@code false} otherwise
* @throws TransportException
*/
private boolean isFastForward(ObjectId oldOid, ObjectId newOid)
throws TransportException {
try {
RevObject oldRev = walker.parseAny(oldOid);
RevObject newRev = walker.parseAny(newOid);
if (!(oldRev instanceof RevCommit) || !(newRev instanceof RevCommit)
|| !walker.isMergedInto((RevCommit) oldRev,
(RevCommit) newRev)) {
return false;
}
} catch (MissingObjectException x) {
return false;
} catch (Exception x) {
throw new TransportException(transport.getURI(),
MessageFormat.format(JGitText
.get().readingObjectsFromLocalRepositoryFailed,
x.getMessage()),
x);
}
return true;
}
/**
* Expands all placeholder {@link RemoteRefUpdate}s for "matching"
* {@link RefSpec}s ":" in {@link #toPush} and returns the resulting map in
* which the placeholders have been replaced by their expansion.
*
* @return a new map of {@link RemoteRefUpdate}s keyed by remote name
* @throws TransportException
* if the expansion results in duplicate updates
*/
private Map<String, RemoteRefUpdate> expandMatching()
throws TransportException {
Map<String, RemoteRefUpdate> result = new LinkedHashMap<>();
boolean hadMatch = false;
for (RemoteRefUpdate update : toPush.values()) {
if (update.isMatching()) {
if (hadMatch) {
throw new TransportException(MessageFormat.format(
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
":")); //$NON-NLS-1$
}
expandMatching(result, update);
hadMatch = true;
} else if (result.put(update.getRemoteName(), update) != null) {
throw new TransportException(MessageFormat.format(
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
update.getRemoteName()));
}
}
return result;
}
/**
* Expands the placeholder {@link RemoteRefUpdate} {@code match} for a
* "matching" {@link RefSpec} ":" or "+:" and puts the expansion into the
* given map {@code updates}.
*
* @param updates
* map to put the expansion in
* @param match
* the placeholder {@link RemoteRefUpdate} to expand
*
* @throws TransportException
* if the expansion results in duplicate updates, or the local
* branches cannot be determined
*/
private void expandMatching(Map<String, RemoteRefUpdate> updates,
RemoteRefUpdate match) throws TransportException {
try {
Map<String, Ref> advertisement = connection.getRefsMap();
Collection<RefSpec> fetchSpecs = match.getFetchSpecs();
boolean forceUpdate = match.isForceUpdate();
for (Ref local : transport.local.getRefDatabase()
.getRefsByPrefix(Constants.R_HEADS)) {
if (local.isSymbolic()) {
continue;
}
String name = local.getName();
Ref advertised = advertisement.get(name);
if (advertised == null || advertised.isSymbolic()) {
continue;
}
ObjectId oldOid = advertised.getObjectId();
if (oldOid == null || ObjectId.zeroId().equals(oldOid)) {
continue;
}
ObjectId newOid = local.getObjectId();
if (newOid == null || ObjectId.zeroId().equals(newOid)) {
continue;
}
RemoteRefUpdate rru = new RemoteRefUpdate(transport.local, name,
newOid, name, forceUpdate,
Transport.findTrackingRefName(name, fetchSpecs),
oldOid);
if (updates.put(rru.getRemoteName(), rru) != null) {
throw new TransportException(MessageFormat.format(
JGitText.get().duplicateRemoteRefUpdateIsIllegal,
rru.getRemoteName()));
}
}
} catch (IOException x) {
throw new TransportException(transport.getURI(),
MessageFormat.format(JGitText
.get().readingObjectsFromLocalRepositoryFailed,
x.getMessage()),
x);
}
}
private Map<String, RemoteRefUpdate> rejectAll() {
for (RemoteRefUpdate rru : toPush.values()) {
if (rru.getStatus() == Status.NOT_ATTEMPTED) {

View File

@ -12,11 +12,11 @@
import java.io.Serializable;
import java.text.MessageFormat;
import java.util.Objects;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.util.References;
/**
* Describes how refs in one repository copy into another repository.
@ -50,6 +50,9 @@ public static boolean isWildcard(String s) {
/** Is this specification actually a wildcard match? */
private boolean wildcard;
/** Is this the special ":" RefSpec? */
private boolean matching;
/**
* How strict to be about wildcards.
*
@ -71,6 +74,7 @@ public enum WildcardMode {
*/
ALLOW_MISMATCH
}
/** Whether a wildcard is allowed on one side but not the other. */
private WildcardMode allowMismatchedWildcards;
@ -87,6 +91,7 @@ public enum WildcardMode {
* applications, as at least one field must be set to match a source name.
*/
public RefSpec() {
matching = false;
force = false;
wildcard = false;
srcName = Constants.HEAD;
@ -133,17 +138,25 @@ public RefSpec(String spec, WildcardMode mode) {
s = s.substring(1);
}
boolean matchPushSpec = false;
final int c = s.lastIndexOf(':');
if (c == 0) {
s = s.substring(1);
if (isWildcard(s)) {
if (s.isEmpty()) {
matchPushSpec = true;
wildcard = true;
if (mode == WildcardMode.REQUIRE_MATCH) {
throw new IllegalArgumentException(MessageFormat
.format(JGitText.get().invalidWildcards, spec));
srcName = Constants.R_HEADS + '*';
dstName = srcName;
} else {
if (isWildcard(s)) {
wildcard = true;
if (mode == WildcardMode.REQUIRE_MATCH) {
throw new IllegalArgumentException(MessageFormat
.format(JGitText.get().invalidWildcards, spec));
}
}
dstName = checkValid(s);
}
dstName = checkValid(s);
} else if (c > 0) {
String src = s.substring(0, c);
String dst = s.substring(c + 1);
@ -168,6 +181,7 @@ public RefSpec(String spec, WildcardMode mode) {
}
srcName = checkValid(s);
}
matching = matchPushSpec;
}
/**
@ -195,6 +209,7 @@ public RefSpec(String spec) {
}
private RefSpec(RefSpec p) {
matching = false;
force = p.isForceUpdate();
wildcard = p.isWildcard();
srcName = p.getSource();
@ -202,6 +217,17 @@ private RefSpec(RefSpec p) {
allowMismatchedWildcards = p.allowMismatchedWildcards;
}
/**
* Tells whether this {@link RefSpec} is the special "matching" RefSpec ":"
* for pushing.
*
* @return whether this is a "matching" RefSpec
* @since 6.1
*/
public boolean isMatching() {
return matching;
}
/**
* Check if this specification wants to forcefully update the destination.
*
@ -220,6 +246,7 @@ public boolean isForceUpdate() {
*/
public RefSpec setForceUpdate(boolean forceUpdate) {
final RefSpec r = new RefSpec(this);
r.matching = matching;
r.force = forceUpdate;
return r;
}
@ -322,8 +349,7 @@ public RefSpec setDestination(String destination) {
* The wildcard status of the new source disagrees with the
* wildcard status of the new destination.
*/
public RefSpec setSourceDestination(final String source,
final String destination) {
public RefSpec setSourceDestination(String source, String destination) {
if (isWildcard(source) != isWildcard(destination))
throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
final RefSpec r = new RefSpec(this);
@ -541,37 +567,36 @@ public boolean equals(Object obj) {
if (!(obj instanceof RefSpec))
return false;
final RefSpec b = (RefSpec) obj;
if (isForceUpdate() != b.isForceUpdate())
if (isForceUpdate() != b.isForceUpdate()) {
return false;
if (isWildcard() != b.isWildcard())
return false;
if (!eq(getSource(), b.getSource()))
return false;
if (!eq(getDestination(), b.getDestination()))
return false;
return true;
}
private static boolean eq(String a, String b) {
if (References.isSameObject(a, b)) {
return true;
}
if (a == null || b == null)
if (isMatching()) {
return b.isMatching();
} else if (b.isMatching()) {
return false;
return a.equals(b);
}
return isWildcard() == b.isWildcard()
&& Objects.equals(getSource(), b.getSource())
&& Objects.equals(getDestination(), b.getDestination());
}
/** {@inheritDoc} */
@Override
public String toString() {
final StringBuilder r = new StringBuilder();
if (isForceUpdate())
if (isForceUpdate()) {
r.append('+');
if (getSource() != null)
r.append(getSource());
if (getDestination() != null) {
}
if (isMatching()) {
r.append(':');
r.append(getDestination());
} else {
if (getSource() != null) {
r.append(getSource());
}
if (getDestination() != null) {
r.append(':');
r.append(getDestination());
}
}
return r.toString();
}

View File

@ -12,7 +12,9 @@
import java.io.IOException;
import java.text.MessageFormat;
import java.util.Collection;
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ObjectId;
import org.eclipse.jgit.lib.Ref;
@ -115,6 +117,12 @@ public enum Status {
private RefUpdate localUpdate;
/**
* If set, the RemoteRefUpdate is a placeholder for the "matching" RefSpec
* to be expanded after the advertisements have been received in a push.
*/
private Collection<RefSpec> fetchSpecs;
/**
* Construct remote ref update request by providing an update specification.
* Object is created with default
@ -157,9 +165,8 @@ public enum Status {
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
public RemoteRefUpdate(final Repository localDb, final String srcRef,
final String remoteName, final boolean forceUpdate,
final String localName, final ObjectId expectedOldObjectId)
public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName,
boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
throws IOException {
this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
: ObjectId.zeroId(), remoteName, forceUpdate, localName,
@ -203,9 +210,8 @@ public RemoteRefUpdate(final Repository localDb, final String srcRef,
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
final String remoteName, final boolean forceUpdate,
final String localName, final ObjectId expectedOldObjectId)
public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName,
boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
throws IOException {
this(localDb, srcRef != null ? srcRef.getName() : null,
srcRef != null ? srcRef.getObjectId() : null, remoteName,
@ -255,28 +261,41 @@ public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
* @throws java.lang.IllegalArgumentException
* if some required parameter was null
*/
public RemoteRefUpdate(final Repository localDb, final String srcRef,
final ObjectId srcId, final String remoteName,
final boolean forceUpdate, final String localName,
final ObjectId expectedOldObjectId) throws IOException {
if (remoteName == null)
throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull);
if (srcId == null && srcRef != null)
throw new IOException(MessageFormat.format(
JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
String remoteName, boolean forceUpdate, String localName,
ObjectId expectedOldObjectId) throws IOException {
this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
expectedOldObjectId);
}
if (srcRef != null)
private RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
String remoteName, boolean forceUpdate, String localName,
Collection<RefSpec> fetchSpecs, ObjectId expectedOldObjectId)
throws IOException {
if (fetchSpecs == null) {
if (remoteName == null) {
throw new IllegalArgumentException(
JGitText.get().remoteNameCannotBeNull);
}
if (srcId == null && srcRef != null) {
throw new IOException(MessageFormat.format(
JGitText.get().sourceRefDoesntResolveToAnyObject,
srcRef));
}
}
if (srcRef != null) {
this.srcRef = srcRef;
else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
} else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
this.srcRef = srcId.name();
else
} else {
this.srcRef = null;
if (srcId != null)
}
if (srcId != null) {
this.newObjectId = srcId;
else
} else {
this.newObjectId = ObjectId.zeroId();
}
this.fetchSpecs = fetchSpecs;
this.remoteName = remoteName;
this.forceUpdate = forceUpdate;
if (localName != null && localDb != null) {
@ -292,8 +311,9 @@ else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
? localUpdate.getOldObjectId()
: ObjectId.zeroId(),
newObjectId);
} else
} else {
trackingRefUpdate = null;
}
this.localDb = localDb;
this.expectedOldObjectId = expectedOldObjectId;
this.status = Status.NOT_ATTEMPTED;
@ -316,11 +336,57 @@ else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
* local tracking branch or srcRef of base object no longer can
* be resolved to any object.
*/
public RemoteRefUpdate(final RemoteRefUpdate base,
final ObjectId newExpectedOldObjectId) throws IOException {
this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
public RemoteRefUpdate(RemoteRefUpdate base,
ObjectId newExpectedOldObjectId) throws IOException {
this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
base.forceUpdate,
(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
.getLocalName()), newExpectedOldObjectId);
.getLocalName()),
base.fetchSpecs, newExpectedOldObjectId);
}
/**
* Creates a "placeholder" update for the "matching" RefSpec ":".
*
* @param localDb
* local repository to push from
* @param forceUpdate
* whether non-fast-forward updates shall be allowed
* @param fetchSpecs
* The fetch {@link RefSpec}s to use when this placeholder is
* expanded to determine remote tracking branch updates
*/
RemoteRefUpdate(Repository localDb, boolean forceUpdate,
@NonNull Collection<RefSpec> fetchSpecs) {
this.localDb = localDb;
this.forceUpdate = forceUpdate;
this.fetchSpecs = fetchSpecs;
this.trackingRefUpdate = null;
this.srcRef = null;
this.remoteName = null;
this.newObjectId = null;
this.status = Status.NOT_ATTEMPTED;
}
/**
* Tells whether this {@link RemoteRefUpdate} is a placeholder for a
* "matching" {@link RefSpec}.
*
* @return {@code true} if this is a placeholder, {@code false} otherwise
* @since 6.1
*/
public boolean isMatching() {
return fetchSpecs != null;
}
/**
* Retrieves the fetch {@link RefSpec}s of this {@link RemoteRefUpdate}.
*
* @return the fetch {@link RefSpec}s, or {@code null} if
* {@code this.}{@link #isMatching()} {@code == false}
*/
Collection<RefSpec> getFetchSpecs() {
return fetchSpecs;
}
/**

View File

@ -40,7 +40,6 @@
import org.eclipse.jgit.annotations.NonNull;
import org.eclipse.jgit.annotations.Nullable;
import org.eclipse.jgit.api.errors.AbortedByHookException;
import org.eclipse.jgit.errors.NotSupportedException;
import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.hooks.Hooks;
@ -590,6 +589,11 @@ public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
for (RefSpec spec : procRefs) {
if (spec.isMatching()) {
result.add(new RemoteRefUpdate(db, spec.isForceUpdate(),
fetchSpecs));
continue;
}
String srcSpec = spec.getSource();
final Ref srcRef = db.findRef(srcSpec);
if (srcRef != null)
@ -660,7 +664,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
List<Ref> localRefs = null;
for (RefSpec spec : specs) {
if (spec.isWildcard()) {
if (!spec.isMatching() && spec.isWildcard()) {
if (localRefs == null) {
localRefs = db.getRefDatabase().getRefs();
}
@ -676,7 +680,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
return procRefs;
}
private static String findTrackingRefName(final String remoteName,
static String findTrackingRefName(final String remoteName,
final Collection<RefSpec> fetchSpecs) {
// try to find matching tracking refs
for (RefSpec fetchSpec : fetchSpecs) {
@ -1375,16 +1379,9 @@ public PushResult push(final ProgressMonitor monitor,
if (toPush.isEmpty())
throw new TransportException(JGitText.get().nothingToPush);
}
if (prePush != null) {
try {
prePush.setRefs(toPush);
prePush.call();
} catch (AbortedByHookException | IOException e) {
throw new TransportException(e.getMessage(), e);
}
}
final PushProcess pushProcess = new PushProcess(this, toPush, out);
final PushProcess pushProcess = new PushProcess(this, toPush, prePush,
out);
return pushProcess.execute(monitor);
}

View File

@ -1,6 +1,6 @@
/*
* Copyright (C) 2008-2009, Google Inc.
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
* Copyright (C) 2008, 2009 Google Inc.
* Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> and others
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Distribution License v. 1.0 which is available at
@ -14,6 +14,7 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
@ -73,6 +74,7 @@
* threads.
*/
public class TreeWalk implements AutoCloseable, AttributesProvider {
private static final AbstractTreeIterator[] NO_TREES = {};
/**
@ -92,7 +94,7 @@ public enum OperationType {
}
/**
* Type of operation you want to retrieve the git attributes for.
* Type of operation you want to retrieve the git attributes for.
*/
private OperationType operationType = OperationType.CHECKOUT_OP;
@ -284,11 +286,20 @@ public static TreeWalk forPath(final Repository db, final String path,
AbstractTreeIterator currentHead;
/** Cached attribute for the current entry */
private Attributes attrs = null;
/**
* Cached attributes for the current entry; per tree. Index i+1 is for tree
* i; index 0 is for the deprecated legacy behavior.
*/
private Attributes[] attrs;
/** Cached attributes handler */
private AttributesHandler attributesHandler;
/**
* Cached attributes handler; per tree. Index i+1 is for tree i; index 0 is
* for the deprecated legacy behavior.
*/
private AttributesHandler[] attributesHandlers;
/** Can be set to identify the tree to use for {@link #getAttributes()}. */
private int headIndex = -1;
private Config config;
@ -514,6 +525,24 @@ public AttributesNodeProvider getAttributesNodeProvider() {
return attributesNodeProvider;
}
/**
* Identifies the tree at the given index as the head tree. This is the tree
* use by default to determine attributes and EOL modes.
*
* @param index
* of the tree to use as head
* @throws IllegalArgumentException
* if the index is out of range
* @since 6.1
*/
public void setHead(int index) {
if (index < 0 || index >= trees.length) {
throw new IllegalArgumentException("Head index " + index //$NON-NLS-1$
+ " out of range [0," + trees.length + ')'); //$NON-NLS-1$
}
headIndex = index;
}
/**
* {@inheritDoc}
* <p>
@ -556,25 +585,51 @@ public AttributesNodeProvider getAttributesNodeProvider() {
*/
@Override
public Attributes getAttributes() {
if (attrs != null)
return attrs;
return getAttributes(headIndex);
}
/**
* Retrieves the git attributes based on the given tree.
*
* @param index
* of the tree to use as base for the attributes
* @return the attributes
* @since 6.1
*/
public Attributes getAttributes(int index) {
int attrIndex = index + 1;
Attributes result = attrs[attrIndex];
if (result != null) {
return result;
}
if (attributesNodeProvider == null) {
// The work tree should have a AttributesNodeProvider to be able to
// retrieve the info and global attributes node
throw new IllegalStateException(
"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
}
try {
// Lazy create the attributesHandler on the first access of
// attributes. This requires the info, global and root
// attributes nodes
if (attributesHandler == null) {
attributesHandler = new AttributesHandler(this);
AttributesHandler handler = attributesHandlers[attrIndex];
if (handler == null) {
if (index < 0) {
// Legacy behavior (headIndex not set, getAttributes() above
// called)
handler = new AttributesHandler(this, () -> {
return getTree(CanonicalTreeParser.class);
});
} else {
handler = new AttributesHandler(this, () -> {
AbstractTreeIterator tree = trees[index];
if (tree instanceof CanonicalTreeParser) {
return (CanonicalTreeParser) tree;
}
return null;
});
}
attributesHandlers[attrIndex] = handler;
}
attrs = attributesHandler.getAttributes();
return attrs;
result = handler.getAttributes();
attrs[attrIndex] = result;
return result;
} catch (IOException e) {
throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
e);
@ -595,11 +650,34 @@ public Attributes getAttributes() {
*/
@Nullable
public EolStreamType getEolStreamType(OperationType opType) {
if (attributesNodeProvider == null || config == null)
if (attributesNodeProvider == null || config == null) {
return null;
return EolStreamTypeUtil.detectStreamType(
opType != null ? opType : operationType,
config.get(WorkingTreeOptions.KEY), getAttributes());
}
OperationType op = opType != null ? opType : operationType;
return EolStreamTypeUtil.detectStreamType(op,
config.get(WorkingTreeOptions.KEY), getAttributes());
}
/**
* Get the EOL stream type of the current entry for checking out using the
* config and {@link #getAttributes()}.
*
* @param tree
* index of the tree the check-out is to be from
* @return the EOL stream type of the current entry using the config and
* {@link #getAttributes()}. Note that this method may return null
* if the {@link org.eclipse.jgit.treewalk.TreeWalk} is not based on
* a working tree
* @since 6.1
*/
@Nullable
public EolStreamType getCheckoutEolStreamType(int tree) {
if (attributesNodeProvider == null || config == null) {
return null;
}
Attributes attr = getAttributes(tree);
return EolStreamTypeUtil.detectStreamType(OperationType.CHECKOUT_OP,
config.get(WorkingTreeOptions.KEY), attr);
}
/**
@ -607,7 +685,8 @@ public EolStreamType getEolStreamType(OperationType opType) {
*/
public void reset() {
attrs = null;
attributesHandler = null;
attributesHandlers = null;
headIndex = -1;
trees = NO_TREES;
advance = false;
depth = 0;
@ -651,7 +730,9 @@ public void reset(AnyObjectId id) throws MissingObjectException,
advance = false;
depth = 0;
attrs = null;
attrs = new Attributes[2];
attributesHandlers = new AttributesHandler[2];
headIndex = -1;
}
/**
@ -701,7 +782,14 @@ public void reset(AnyObjectId... ids) throws MissingObjectException,
trees = r;
advance = false;
depth = 0;
attrs = null;
if (oldLen == newLen) {
Arrays.fill(attrs, null);
Arrays.fill(attributesHandlers, null);
} else {
attrs = new Attributes[newLen + 1];
attributesHandlers = new AttributesHandler[newLen + 1];
}
headIndex = -1;
}
/**
@ -758,6 +846,16 @@ public int addTree(AbstractTreeIterator p) {
p.matchShift = 0;
trees = newTrees;
if (attrs == null) {
attrs = new Attributes[n + 2];
} else {
attrs = Arrays.copyOf(attrs, n + 2);
}
if (attributesHandlers == null) {
attributesHandlers = new AttributesHandler[n + 2];
} else {
attributesHandlers = Arrays.copyOf(attributesHandlers, n + 2);
}
return n;
}
@ -800,7 +898,7 @@ public boolean next() throws MissingObjectException,
}
for (;;) {
attrs = null;
Arrays.fill(attrs, null);
final AbstractTreeIterator t = min();
if (t.eof()) {
if (depth > 0) {
@ -1255,7 +1353,7 @@ public boolean isPostChildren() {
*/
public void enterSubtree() throws MissingObjectException,
IncorrectObjectTypeException, CorruptObjectException, IOException {
attrs = null;
Arrays.fill(attrs, null);
final AbstractTreeIterator ch = currentHead;
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
for (int i = 0; i < trees.length; i++) {
@ -1374,11 +1472,12 @@ public <T extends AbstractTreeIterator> T getTree(Class<T> type) {
/**
* Inspect config and attributes to return a filtercommand applicable for
* the current path, but without expanding %f occurences
* the current path.
*
* @param filterCommandType
* which type of filterCommand should be executed. E.g. "clean",
* "smudge"
* "smudge". For "smudge" consider using
* {{@link #getSmudgeCommand(int)} instead.
* @return a filter command
* @throws java.io.IOException
* @since 4.2
@ -1406,6 +1505,54 @@ public String getFilterCommand(String filterCommandType)
QuotedString.BOURNE.quote((getPathString()))));
}
/**
* Inspect config and attributes to return a filtercommand applicable for
* the current path.
*
* @param index
* of the tree the item to be smudged is in
* @return a filter command
* @throws java.io.IOException
* @since 6.1
*/
public String getSmudgeCommand(int index)
throws IOException {
return getSmudgeCommand(getAttributes(index));
}
/**
* Inspect config and attributes to return a filtercommand applicable for
* the current path.
*
* @param attributes
* to use
* @return a filter command
* @throws java.io.IOException
* @since 6.1
*/
public String getSmudgeCommand(Attributes attributes) throws IOException {
if (attributes == null) {
return null;
}
Attribute f = attributes.get(Constants.ATTR_FILTER);
if (f == null) {
return null;
}
String filterValue = f.getValue();
if (filterValue == null) {
return null;
}
String filterCommand = getFilterCommandDefinition(filterValue,
Constants.ATTR_FILTER_TYPE_SMUDGE);
if (filterCommand == null) {
return null;
}
return filterCommand.replaceAll("%f", //$NON-NLS-1$
Matcher.quoteReplacement(
QuotedString.BOURNE.quote((getPathString()))));
}
/**
* Get the filter command how it is defined in gitconfig. The returned
* string may contain "%f" which needs to be replaced by the current path

17
pom.xml
View File

@ -203,6 +203,14 @@
<id>repo.eclipse.org.cbi-snapshots</id>
<url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
</pluginRepository>
<pluginRepository>
<id>repo.eclipse.org.dash-releases</id>
<url>https://repo.eclipse.org/content/repositories/dash-licenses-releases/</url>
</pluginRepository>
<pluginRepository>
<id>repo.eclipse.org.dash-snapshots</id>
<url>https://repo.eclipse.org/content/repositories/dash-licenses-snapshots/</url>
</pluginRepository>
</pluginRepositories>
<build>
@ -382,6 +390,11 @@
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.5.4</version>
</plugin>
<plugin>
<groupId>org.eclipse.dash</groupId>
<artifactId>license-tool-plugin</artifactId>
<version>0.0.1-SNAPSHOT</version>
</plugin>
</plugins>
</pluginManagement>
@ -540,6 +553,10 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-report-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.eclipse.dash</groupId>
<artifactId>license-tool-plugin</artifactId>
</plugin>
</plugins>
</build>