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:
commit
338f2adf16
105
DEPENDENCIES
105
DEPENDENCIES
|
@ -1,69 +1,68 @@
|
||||||
maven/mavencentral/args4j/args4j/2.33, MIT, approved, CQ11068
|
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.google.code.gson/gson/2.8.9, Apache-2.0, approved, CQ23496
|
||||||
maven/mavencentral/com.googlecode.javaewah/JavaEWAH/1.1.12, Apache-2.0, approved, CQ11658
|
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/jsch/0.1.55, BSD-3-Clause, approved, CQ19435
|
||||||
maven/mavencentral/com.jcraft/jzlib/1.1.1, BSD-2-Clause, approved, CQ6218
|
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-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/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/javax.servlet/javax.servlet-api/4.0.0, , approved, CQ16125
|
||||||
maven/mavencentral/junit/junit/4.13, , approved, CQ22796
|
maven/mavencentral/junit/junit/4.13.2, EPL-2.0, approved, CQ23636
|
||||||
maven/mavencentral/log4j/log4j/1.2.15, Apache-2.0, approved, CQ7837
|
|
||||||
maven/mavencentral/net.bytebuddy/byte-buddy-agent/1.9.0, Apache-2.0, approved, clearlydefined
|
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.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.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/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-launcher/1.10.12, 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.ant/ant/1.10.12, 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.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.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/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.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-osgi/2.8.0, Apache-2.0, approved, CQ23892
|
||||||
maven/mavencentral/org.apache.sshd/sshd-core/2.7.0, Apache-2.0, approved, CQ23469
|
maven/mavencentral/org.apache.sshd/sshd-sftp/2.8.0, Apache-2.0, approved, CQ23893
|
||||||
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.assertj/assertj-core/3.20.2, Apache-2.0, approved, clearlydefined
|
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/bcpg-jdk15on/1.70, Apache-2.0, approved, #1713
|
||||||
maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.69, MIT, approved, CQ23473
|
maven/mavencentral/org.bouncycastle/bcpkix-jdk15on/1.70, MIT, approved, clearlydefined
|
||||||
maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.69, MIT, approved, CQ23471
|
maven/mavencentral/org.bouncycastle/bcprov-jdk15on/1.70, MIT, approved, #1712
|
||||||
maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.69, MIT, approved, CQ23474
|
maven/mavencentral/org.bouncycastle/bcutil-jdk15on/1.70, MIT, approved, clearlydefined
|
||||||
maven/mavencentral/org.eclipse.jetty/jetty-http/9.4.43.v20210629, , approved, eclipse
|
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-io/9.4.43.v20210629, , approved, eclipse
|
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-security/9.4.43.v20210629, , approved, eclipse
|
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-server/9.4.43.v20210629, , approved, eclipse
|
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-servlet/9.4.43.v20210629, , approved, eclipse
|
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-util-ajax/9.4.43.v20210629, , approved, eclipse
|
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/9.4.43.v20210629, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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.test/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
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.jsch/5.13.0-SNAPSHOT, , approved, eclipse
|
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.test/5.13.0-SNAPSHOT, , approved, eclipse
|
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.ui/5.13.0-SNAPSHOT, , approved, eclipse
|
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/5.13.0-SNAPSHOT, , approved, eclipse
|
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit.ui/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
|
||||||
maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ7063
|
maven/mavencentral/org.eclipse.jgit/org.eclipse.jgit/6.1.0-SNAPSHOT, BSD-3-Clause, approved, technology.jgit
|
||||||
maven/mavencentral/org.hamcrest/hamcrest/2.2, BSD-2-Clause, approved, clearlydefined
|
maven/mavencentral/org.hamcrest/hamcrest-core/1.3, BSD-2-Clause, approved, CQ11429
|
||||||
maven/mavencentral/org.mockito/mockito-core/2.23.0, MIT, approved, CQ17976
|
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.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-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, approved, CQ23500
|
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/4.3.1, Apache-2.0, approved, CQ10111
|
maven/mavencentral/org.osgi/org.osgi.core/6.0.0, Apache-2.0, approved, #1794
|
||||||
maven/mavencentral/org.slf4j/jcl-over-slf4j/1.7.30, Apache-2.0, approved, CQ12843
|
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-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
|
maven/mavencentral/org.tukaani/xz/1.9, LicenseRef-Public-Domain, approved, CQ23498
|
||||||
|
|
|
@ -55,6 +55,16 @@
|
||||||
<groupId>org.apache.sshd</groupId>
|
<groupId>org.apache.sshd</groupId>
|
||||||
<artifactId>sshd-sftp</artifactId>
|
<artifactId>sshd-sftp</artifactId>
|
||||||
<version>${apache-sshd-version}</version>
|
<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>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -67,6 +67,27 @@ public void setUp() throws Exception {
|
||||||
config.save();
|
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
|
@Test
|
||||||
public void checkoutNonLfsPointer() throws Exception {
|
public void checkoutNonLfsPointer() throws Exception {
|
||||||
String content = "size_t\nsome_function(void* ptr);\n";
|
String content = "size_t\nsome_function(void* ptr);\n";
|
||||||
|
|
|
@ -9,10 +9,15 @@
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.lfs.internal;
|
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.assertEquals;
|
||||||
import static org.junit.Assert.assertThrows;
|
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.Git;
|
||||||
import org.eclipse.jgit.api.RemoteAddCommand;
|
import org.eclipse.jgit.api.RemoteAddCommand;
|
||||||
|
@ -27,6 +32,8 @@
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
import org.eclipse.jgit.transport.URIish;
|
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.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.BeforeClass;
|
import org.junit.BeforeClass;
|
||||||
|
@ -42,6 +49,12 @@ public class LfsConnectionFactoryTest extends RepositoryTestCase {
|
||||||
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
||||||
+ org.eclipse.jgit.lib.Constants.ATTR_FILTER_TYPE_CLEAN;
|
+ 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;
|
private Git git;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@ -61,26 +74,23 @@ public static void removeLfs() {
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
super.setUp();
|
super.setUp();
|
||||||
git = new Git(db);
|
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
|
@Test
|
||||||
public void lfsUrlFromRemoteUrlWithDotGit() throws Exception {
|
public void lfsUrlFromRemoteUrlWithDotGit() throws Exception {
|
||||||
addRemoteUrl("https://localhost/repo.git");
|
addRemoteUrl("https://localhost/repo.git");
|
||||||
|
checkLfsUrl("https://localhost/repo.git/info/lfs");
|
||||||
String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
|
|
||||||
Protocol.OPERATION_DOWNLOAD,
|
|
||||||
new TreeMap<>());
|
|
||||||
assertEquals("https://localhost/repo.git/info/lfs", lfsUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lfsUrlFromRemoteUrlWithoutDotGit() throws Exception {
|
public void lfsUrlFromRemoteUrlWithoutDotGit() throws Exception {
|
||||||
addRemoteUrl("https://localhost/repo");
|
addRemoteUrl("https://localhost/repo");
|
||||||
|
checkLfsUrl("https://localhost/repo.git/info/lfs");
|
||||||
String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
|
|
||||||
Protocol.OPERATION_DOWNLOAD,
|
|
||||||
new TreeMap<>());
|
|
||||||
assertEquals("https://localhost/repo.git/info/lfs", lfsUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -94,10 +104,7 @@ public void lfsUrlFromLocalConfig() throws Exception {
|
||||||
"https://localhost/repo/lfs");
|
"https://localhost/repo/lfs");
|
||||||
cfg.save();
|
cfg.save();
|
||||||
|
|
||||||
String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
|
checkLfsUrl("https://localhost/repo/lfs");
|
||||||
Protocol.OPERATION_DOWNLOAD,
|
|
||||||
new TreeMap<>());
|
|
||||||
assertEquals("https://localhost/repo/lfs", lfsUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -111,16 +118,136 @@ public void lfsUrlFromOriginConfig() throws Exception {
|
||||||
"https://localhost/repo/lfs");
|
"https://localhost/repo/lfs");
|
||||||
cfg.save();
|
cfg.save();
|
||||||
|
|
||||||
String lfsUrl = LfsConnectionFactory.getLfsUrl(db,
|
checkLfsUrl("https://localhost/repo/lfs");
|
||||||
Protocol.OPERATION_DOWNLOAD,
|
|
||||||
new TreeMap<>());
|
|
||||||
assertEquals("https://localhost/repo/lfs", lfsUrl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void lfsUrlNotConfigured() throws Exception {
|
public void lfsUrlNotConfigured() throws Exception {
|
||||||
assertThrows(LfsConfigInvalidException.class, () -> LfsConnectionFactory
|
assertThrows(LfsConfigInvalidException.class,
|
||||||
.getLfsUrl(db, Protocol.OPERATION_DOWNLOAD, new TreeMap<>()));
|
() -> 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 {
|
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.setName(org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME);
|
||||||
add.call();
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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.
|
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.
|
dotLfsConfigReadFailed=Reading .lfsconfig failed
|
||||||
inconsistentMediafileLength=Mediafile {0} has unexpected length; expected {1} but found {2}.
|
|
||||||
inconsistentContentLength=Unexpected content length reported by LFS server ({0}), expected {1} but reported was {2}
|
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}
|
invalidLongId=Invalid id: {0}
|
||||||
invalidLongIdLength=Invalid id length {0}; should be {1}
|
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}
|
lfsFailedToGetRepository=failed to get repository {0}
|
||||||
lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL"
|
lfsNoDownloadUrl="Need to download object from LFS server but couldn't determine LFS server URL"
|
||||||
serverFailure=When trying to open a connection to {0} the server responded with an error code. rc={1}
|
lfsUnauthorized=Not authorized to perform operation {0} on repository {1}
|
||||||
wrongAmoutOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected
|
lfsUnavailable=LFS is not available for repository {0}
|
||||||
userConfigInvalid="User config file {0} invalid {1}"
|
|
||||||
missingLocalObject="Local Object {0} is missing"
|
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}
|
||||||
|
userConfigInvalid="User config file {0} invalid {1}"
|
||||||
|
wrongAmountOfDataReceived=While downloading data from the content server {0} {1} bytes have been received while {2} have been expected
|
|
@ -205,7 +205,7 @@ public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
|
||||||
long bytesCopied = Files.copy(contentIn, path);
|
long bytesCopied = Files.copy(contentIn, path);
|
||||||
if (bytesCopied != o.size) {
|
if (bytesCopied != o.size) {
|
||||||
throw new IOException(MessageFormat.format(
|
throw new IOException(MessageFormat.format(
|
||||||
LfsText.get().wrongAmoutOfDataReceived,
|
LfsText.get().wrongAmountOfDataReceived,
|
||||||
contentServerConn.getURL(),
|
contentServerConn.getURL(),
|
||||||
Long.valueOf(bytesCopied),
|
Long.valueOf(bytesCopied),
|
||||||
Long.valueOf(o.size)));
|
Long.valueOf(o.size)));
|
||||||
|
|
|
@ -31,7 +31,7 @@ public class LfsUnauthorized extends LfsException {
|
||||||
* the repository name.
|
* the repository name.
|
||||||
*/
|
*/
|
||||||
public LfsUnauthorized(String operation, String name) {
|
public LfsUnauthorized(String operation, String name) {
|
||||||
super(MessageFormat.format(LfsText.get().lfsUnathorized, operation,
|
super(MessageFormat.format(LfsText.get().lfsUnauthorized, operation,
|
||||||
name));
|
name));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -45,7 +45,6 @@
|
||||||
* Provides means to get a valid LFS connection for a given repository.
|
* Provides means to get a valid LFS connection for a given repository.
|
||||||
*/
|
*/
|
||||||
public class LfsConnectionFactory {
|
public class LfsConnectionFactory {
|
||||||
|
|
||||||
private static final int SSH_AUTH_TIMEOUT_SECONDS = 30;
|
private static final int SSH_AUTH_TIMEOUT_SECONDS = 30;
|
||||||
private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
|
private static final String SCHEME_HTTPS = "https"; //$NON-NLS-1$
|
||||||
private static final String SCHEME_SSH = "ssh"; //$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
|
* additional headers that can be used to connect to LFS server
|
||||||
* @return the URL for the LFS server. e.g.
|
* @return the URL for the LFS server. e.g.
|
||||||
* "https://github.com/github/git-lfs.git/info/lfs"
|
* "https://github.com/github/git-lfs.git/info/lfs"
|
||||||
* @throws LfsConfigInvalidException
|
* @throws IOException
|
||||||
* if the LFS config is invalid
|
* if the LFS config is invalid or cannot be accessed
|
||||||
* @see <a href=
|
* @see <a href=
|
||||||
* "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md">
|
* "https://github.com/git-lfs/git-lfs/blob/main/docs/api/server-discovery.md">
|
||||||
* Server Discovery documentation</a>
|
* Server Discovery documentation</a>
|
||||||
*/
|
*/
|
||||||
static String getLfsUrl(Repository db, String purpose,
|
private static String getLfsUrl(Repository db, String purpose,
|
||||||
Map<String, String> additionalHeaders)
|
Map<String, String> additionalHeaders)
|
||||||
throws LfsConfigInvalidException {
|
throws IOException {
|
||||||
StoredConfig config = db.getConfig();
|
LfsConfig config = new LfsConfig(db);
|
||||||
String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
|
String lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
|
||||||
null,
|
null, ConfigConstants.CONFIG_KEY_URL);
|
||||||
ConfigConstants.CONFIG_KEY_URL);
|
|
||||||
Exception ex = null;
|
Exception ex = null;
|
||||||
if (lfsUrl == null) {
|
if (lfsUrl == null) {
|
||||||
String remoteUrl = null;
|
String remoteUrl = null;
|
||||||
|
@ -124,6 +123,7 @@ static String getLfsUrl(Repository db, String purpose,
|
||||||
lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
|
lfsUrl = config.getString(ConfigConstants.CONFIG_SECTION_LFS,
|
||||||
remote,
|
remote,
|
||||||
ConfigConstants.CONFIG_KEY_URL);
|
ConfigConstants.CONFIG_KEY_URL);
|
||||||
|
|
||||||
// This could be done better (more precise logic), but according
|
// This could be done better (more precise logic), but according
|
||||||
// to https://github.com/git-lfs/git-lfs/issues/1759 git-lfs
|
// to https://github.com/git-lfs/git-lfs/issues/1759 git-lfs
|
||||||
// generally only supports 'origin' in an integrated workflow.
|
// generally only supports 'origin' in an integrated workflow.
|
||||||
|
|
|
@ -28,21 +28,22 @@ public static LfsText get() {
|
||||||
|
|
||||||
// @formatter:off
|
// @formatter:off
|
||||||
/***/ public String corruptLongObject;
|
/***/ public String corruptLongObject;
|
||||||
/***/ public String inconsistentMediafileLength;
|
/***/ public String dotLfsConfigReadFailed;
|
||||||
/***/ public String inconsistentContentLength;
|
/***/ public String inconsistentContentLength;
|
||||||
|
/***/ public String inconsistentMediafileLength;
|
||||||
/***/ public String incorrectLONG_OBJECT_ID_LENGTH;
|
/***/ public String incorrectLONG_OBJECT_ID_LENGTH;
|
||||||
/***/ public String invalidLongId;
|
/***/ public String invalidLongId;
|
||||||
/***/ public String invalidLongIdLength;
|
/***/ 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 lfsFailedToGetRepository;
|
||||||
/***/ public String lfsNoDownloadUrl;
|
/***/ public String lfsNoDownloadUrl;
|
||||||
/***/ public String serverFailure;
|
/***/ public String lfsUnauthorized;
|
||||||
/***/ public String wrongAmoutOfDataReceived;
|
/***/ public String lfsUnavailable;
|
||||||
/***/ public String userConfigInvalid;
|
|
||||||
/***/ public String missingLocalObject;
|
/***/ public String missingLocalObject;
|
||||||
|
/***/ public String protocolError;
|
||||||
|
/***/ public String repositoryNotFound;
|
||||||
|
/***/ public String repositoryReadOnly;
|
||||||
|
/***/ public String requiredHashFunctionNotAvailable;
|
||||||
|
/***/ public String serverFailure;
|
||||||
|
/***/ public String userConfigInvalid;
|
||||||
|
/***/ public String wrongAmountOfDataReceived;
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,6 +81,13 @@ public final class Constants {
|
||||||
*/
|
*/
|
||||||
public static final String ATTR_FILTER_DRIVER_PREFIX = "lfs/";
|
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.
|
* Create a new digest function for objects.
|
||||||
*
|
*
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
package org.eclipse.jgit.api;
|
package org.eclipse.jgit.api;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
@ -392,28 +393,64 @@ public void testPushDefaultMatching() throws Exception {
|
||||||
git.add().addFilepattern("f").call();
|
git.add().addFilepattern("f").call();
|
||||||
RevCommit commit = git.commit().setMessage("adding 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();
|
git.checkout().setName("branchtopush").setCreateBranch(true).call();
|
||||||
|
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/branchtopush"));
|
git2.getRepository().resolve("refs/heads/branchtopush"));
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/also-pushed"));
|
git2.getRepository().resolve("refs/heads/not-pushed"));
|
||||||
assertEquals(null,
|
assertEquals(null,
|
||||||
git2.getRepository().resolve("refs/heads/master"));
|
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();
|
.call();
|
||||||
assertEquals(commit.getId(),
|
|
||||||
git2.getRepository().resolve("refs/heads/branchtopush"));
|
|
||||||
assertEquals(commit.getId(),
|
|
||||||
git2.getRepository().resolve("refs/heads/also-pushed"));
|
|
||||||
assertEquals(commit.getId(),
|
assertEquals(commit.getId(),
|
||||||
git2.getRepository().resolve("refs/heads/master"));
|
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(),
|
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"));
|
git.getRepository().resolve("refs/remotes/origin/master"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,8 +21,10 @@
|
||||||
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
import org.eclipse.jgit.api.errors.NoFilepatternException;
|
||||||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.Sets;
|
import org.eclipse.jgit.lib.Sets;
|
||||||
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
import org.eclipse.jgit.storage.file.FileBasedConfig;
|
||||||
|
import org.eclipse.jgit.storage.file.FileRepositoryBuilder;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
import org.junit.Test;
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,19 @@
|
||||||
import static org.junit.Assert.assertNotNull;
|
import static org.junit.Assert.assertNotNull;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
|
|
||||||
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.api.errors.AbortedByHookException;
|
||||||
import org.eclipse.jgit.errors.NotSupportedException;
|
import org.eclipse.jgit.errors.NotSupportedException;
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
|
import org.eclipse.jgit.hooks.PrePushHook;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectIdRef;
|
import org.eclipse.jgit.lib.ObjectIdRef;
|
||||||
import org.eclipse.jgit.lib.ProgressMonitor;
|
import org.eclipse.jgit.lib.ProgressMonitor;
|
||||||
|
@ -31,6 +36,7 @@
|
||||||
import org.eclipse.jgit.lib.TextProgressMonitor;
|
import org.eclipse.jgit.lib.TextProgressMonitor;
|
||||||
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
|
import org.eclipse.jgit.test.resources.SampleDataRepositoryTestCase;
|
||||||
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
|
import org.eclipse.jgit.transport.RemoteRefUpdate.Status;
|
||||||
|
import org.eclipse.jgit.util.io.NullOutputStream;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
|
@ -220,7 +226,17 @@ public void testUpdateUnexpectedRemoteVsForce() throws IOException {
|
||||||
.fromString("0000000000000000000000000000000000000001"));
|
.fromString("0000000000000000000000000000000000000001"));
|
||||||
final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
|
final Ref ref = new ObjectIdRef.Unpeeled(Ref.Storage.LOOSE, "refs/heads/master",
|
||||||
ObjectId.fromString("ac7e7e44c1885efb472ad54a78327d66bfc4ecef"));
|
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(rruOk);
|
||||||
refUpdates.add(rruReject);
|
refUpdates.add(rruReject);
|
||||||
advertisedRefs.add(refToChange);
|
advertisedRefs.add(refToChange);
|
||||||
executePush();
|
try (ByteArrayOutputStream bytes = new ByteArrayOutputStream();
|
||||||
assertEquals(Status.OK, rruOk.getStatus());
|
PrintStream out = new PrintStream(bytes, true,
|
||||||
assertTrue(rruOk.isFastForward());
|
StandardCharsets.UTF_8);
|
||||||
assertEquals(Status.NON_EXISTING, rruReject.getStatus());
|
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,
|
final Ref advertisedRef, final Status expectedStatus,
|
||||||
Boolean fastForward) throws NotSupportedException,
|
Boolean fastForward) throws NotSupportedException,
|
||||||
TransportException {
|
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);
|
refUpdates.add(rru);
|
||||||
if (advertisedRef != null)
|
if (advertisedRef != null)
|
||||||
advertisedRefs.add(advertisedRef);
|
advertisedRefs.add(advertisedRef);
|
||||||
final PushResult result = executePush();
|
final PushResult result = executePush(hook);
|
||||||
assertEquals(expectedStatus, rru.getStatus());
|
assertEquals(expectedStatus, rru.getStatus());
|
||||||
if (fastForward != null)
|
if (fastForward != null)
|
||||||
assertEquals(fastForward, Boolean.valueOf(rru.isFastForward()));
|
assertEquals(fastForward, Boolean.valueOf(rru.isFastForward()));
|
||||||
|
@ -358,7 +394,12 @@ private PushResult testOneUpdateStatus(final RemoteRefUpdate rru,
|
||||||
|
|
||||||
private PushResult executePush() throws NotSupportedException,
|
private PushResult executePush() throws NotSupportedException,
|
||||||
TransportException {
|
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());
|
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());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -466,4 +466,18 @@ public void onlyWildCard() {
|
||||||
assertTrue(a.matchSource("refs/heads/master"));
|
assertTrue(a.matchSource("refs/heads/master"));
|
||||||
assertNull(a.getDestination());
|
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());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
|
@ -10,12 +10,17 @@
|
||||||
package org.eclipse.jgit.util;
|
package org.eclipse.jgit.util;
|
||||||
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertFalse;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.OutputStream;
|
import java.io.OutputStream;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
|
import org.eclipse.jgit.api.MergeResult;
|
||||||
import org.eclipse.jgit.api.errors.GitAPIException;
|
import org.eclipse.jgit.api.errors.GitAPIException;
|
||||||
import org.eclipse.jgit.attributes.FilterCommand;
|
import org.eclipse.jgit.attributes.FilterCommand;
|
||||||
import org.eclipse.jgit.attributes.FilterCommandFactory;
|
import org.eclipse.jgit.attributes.FilterCommandFactory;
|
||||||
|
@ -86,6 +91,14 @@ public void setUp() throws Exception {
|
||||||
secondCommit = git.commit().setMessage("Second commit").call();
|
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
|
@Test
|
||||||
public void testBuiltinCleanFilter()
|
public void testBuiltinCleanFilter()
|
||||||
throws IOException, GitAPIException {
|
throws IOException, GitAPIException {
|
||||||
|
@ -217,4 +230,133 @@ public void testBuiltinCleanAndSmudgeFilter() throws IOException, GitAPIExceptio
|
||||||
config.save();
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -39,6 +39,26 @@
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</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">
|
<resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection">
|
||||||
<filter id="338792546">
|
<filter id="338792546">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
|
|
|
@ -230,7 +230,7 @@ private void determineDefaultRefSpecs(Config config)
|
||||||
refSpecs.add(new RefSpec(getCurrentBranch()));
|
refSpecs.add(new RefSpec(getCurrentBranch()));
|
||||||
break;
|
break;
|
||||||
case MATCHING:
|
case MATCHING:
|
||||||
setPushAll();
|
refSpecs.add(new RefSpec(":")); //$NON-NLS-1$
|
||||||
break;
|
break;
|
||||||
case NOTHING:
|
case NOTHING:
|
||||||
throw new InvalidRefNameException(
|
throw new InvalidRefNameException(
|
||||||
|
|
|
@ -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
|
* This program and the accompanying materials are made available under the
|
||||||
* under the terms of the Eclipse Distribution License v1.0 which
|
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
||||||
* accompanies this distribution, is reproduced below, and is
|
* https://www.eclipse.org/org/documents/edl-v10.php.
|
||||||
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
|
||||||
*
|
*
|
||||||
* All rights reserved.
|
* SPDX-License-Identifier: BSD-3-Clause
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or
|
|
||||||
* without modification, are permitted provided that the following
|
|
||||||
* conditions are met:
|
|
||||||
*
|
|
||||||
* - Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* - Redistributions in binary form must reproduce the above
|
|
||||||
* copyright notice, this list of conditions and the following
|
|
||||||
* disclaimer in the documentation and/or other materials provided
|
|
||||||
* with the distribution.
|
|
||||||
*
|
|
||||||
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
|
||||||
* names of its contributors may be used to endorse or promote
|
|
||||||
* products derived from this software without specific prior
|
|
||||||
* written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
|
||||||
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
|
||||||
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
|
||||||
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
|
||||||
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
|
||||||
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
|
||||||
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
||||||
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
|
||||||
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
||||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
|
||||||
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
*/
|
||||||
package org.eclipse.jgit.attributes;
|
package org.eclipse.jgit.attributes;
|
||||||
|
|
||||||
|
@ -46,6 +14,7 @@
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.ListIterator;
|
import java.util.ListIterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.function.Supplier;
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.Nullable;
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
import org.eclipse.jgit.attributes.Attribute.State;
|
import org.eclipse.jgit.attributes.Attribute.State;
|
||||||
|
@ -84,6 +53,8 @@ public class AttributesHandler {
|
||||||
|
|
||||||
private final TreeWalk treeWalk;
|
private final TreeWalk treeWalk;
|
||||||
|
|
||||||
|
private final Supplier<CanonicalTreeParser> attributesTree;
|
||||||
|
|
||||||
private final AttributesNode globalNode;
|
private final AttributesNode globalNode;
|
||||||
|
|
||||||
private final AttributesNode infoNode;
|
private final AttributesNode infoNode;
|
||||||
|
@ -98,22 +69,41 @@ public class AttributesHandler {
|
||||||
* @param treeWalk
|
* @param treeWalk
|
||||||
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
|
* a {@link org.eclipse.jgit.treewalk.TreeWalk}
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
|
* @deprecated since 6.1, use {@link #AttributesHandler(TreeWalk, Supplier)}
|
||||||
|
* instead
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public AttributesHandler(TreeWalk treeWalk) throws IOException {
|
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;
|
this.treeWalk = treeWalk;
|
||||||
AttributesNodeProvider attributesNodeProvider =treeWalk.getAttributesNodeProvider();
|
this.attributesTree = attributesTree;
|
||||||
|
AttributesNodeProvider attributesNodeProvider = treeWalk
|
||||||
|
.getAttributesNodeProvider();
|
||||||
this.globalNode = attributesNodeProvider != null
|
this.globalNode = attributesNodeProvider != null
|
||||||
? attributesNodeProvider.getGlobalAttributesNode() : null;
|
? attributesNodeProvider.getGlobalAttributesNode() : null;
|
||||||
this.infoNode = attributesNodeProvider != null
|
this.infoNode = attributesNodeProvider != null
|
||||||
? attributesNodeProvider.getInfoAttributesNode() : null;
|
? attributesNodeProvider.getInfoAttributesNode() : null;
|
||||||
|
|
||||||
AttributesNode rootNode = attributesNode(treeWalk,
|
AttributesNode rootNode = attributesNode(treeWalk,
|
||||||
rootOf(
|
rootOf(treeWalk.getTree(WorkingTreeIterator.class)),
|
||||||
treeWalk.getTree(WorkingTreeIterator.class)),
|
rootOf(treeWalk.getTree(DirCacheIterator.class)),
|
||||||
rootOf(
|
rootOf(attributesTree.get()));
|
||||||
treeWalk.getTree(DirCacheIterator.class)),
|
|
||||||
rootOf(treeWalk
|
|
||||||
.getTree(CanonicalTreeParser.class)));
|
|
||||||
|
|
||||||
expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
|
expansions.put(BINARY_RULE_KEY, BINARY_RULE_ATTRIBUTES);
|
||||||
for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
|
for (AttributesNode node : new AttributesNode[] { globalNode, rootNode,
|
||||||
|
@ -152,7 +142,7 @@ public Attributes getAttributes() throws IOException {
|
||||||
isDirectory,
|
isDirectory,
|
||||||
treeWalk.getTree(WorkingTreeIterator.class),
|
treeWalk.getTree(WorkingTreeIterator.class),
|
||||||
treeWalk.getTree(DirCacheIterator.class),
|
treeWalk.getTree(DirCacheIterator.class),
|
||||||
treeWalk.getTree(CanonicalTreeParser.class),
|
attributesTree.get(),
|
||||||
attributes);
|
attributes);
|
||||||
|
|
||||||
// Gets the attributes located in the global attribute file
|
// Gets the attributes located in the global attribute file
|
||||||
|
|
|
@ -4,7 +4,8 @@
|
||||||
* Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
|
* Copyright (C) 2008, Roger C. Soares <rogersoares@intelinet.com.br>
|
||||||
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
|
* Copyright (C) 2006, Shawn O. Pearce <spearce@spearce.org>
|
||||||
* Copyright (C) 2010, Chrisian Halstrick <christian.halstrick@sap.com>
|
* 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
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* 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);
|
walk = new NameConflictTreeWalk(repo);
|
||||||
builder = dc.builder();
|
builder = dc.builder();
|
||||||
|
|
||||||
addTree(walk, headCommitTree);
|
walk.setHead(addTree(walk, headCommitTree));
|
||||||
addTree(walk, mergeCommitTree);
|
addTree(walk, mergeCommitTree);
|
||||||
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
|
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
|
||||||
walk.addTree(workingTree);
|
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
|
* Scan index and merge tree (no HEAD). Used e.g. for initial checkout when
|
||||||
* there is no head yet.
|
* there is no head yet.
|
||||||
|
@ -341,7 +335,7 @@ public void prescanOneTree()
|
||||||
builder = dc.builder();
|
builder = dc.builder();
|
||||||
|
|
||||||
walk = new NameConflictTreeWalk(repo);
|
walk = new NameConflictTreeWalk(repo);
|
||||||
addTree(walk, mergeCommitTree);
|
walk.setHead(addTree(walk, mergeCommitTree));
|
||||||
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
|
int dciPos = walk.addTree(new DirCacheBuildIterator(builder));
|
||||||
walk.addTree(workingTree);
|
walk.addTree(workingTree);
|
||||||
workingTree.setDirCacheIterator(walk, dciPos);
|
workingTree.setDirCacheIterator(walk, dciPos);
|
||||||
|
@ -356,6 +350,14 @@ public void prescanOneTree()
|
||||||
conflicts.removeAll(removed);
|
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
|
* Processing an entry in the context of {@link #prescanOneTree()} when only
|
||||||
* one tree is given
|
* one tree is given
|
||||||
|
@ -382,17 +384,14 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
|
||||||
// failOnConflict is false. Putting something to conflicts
|
// failOnConflict is false. Putting something to conflicts
|
||||||
// would mean we delete it. Instead we want the mergeCommit
|
// would mean we delete it. Instead we want the mergeCommit
|
||||||
// content to be checked out.
|
// content to be checked out.
|
||||||
update(m.getEntryPathString(), m.getEntryObjectId(),
|
update(m);
|
||||||
m.getEntryFileMode());
|
|
||||||
}
|
}
|
||||||
} else
|
} else
|
||||||
update(m.getEntryPathString(), m.getEntryObjectId(),
|
update(m);
|
||||||
m.getEntryFileMode());
|
|
||||||
} else if (f == null || !m.idEqual(i)) {
|
} else if (f == null || !m.idEqual(i)) {
|
||||||
// The working tree file is missing or the merge content differs
|
// The working tree file is missing or the merge content differs
|
||||||
// from index content
|
// from index content
|
||||||
update(m.getEntryPathString(), m.getEntryObjectId(),
|
update(m);
|
||||||
m.getEntryFileMode());
|
|
||||||
} else if (i.getDirCacheEntry() != null) {
|
} else if (i.getDirCacheEntry() != null) {
|
||||||
// The index contains a file (and not a folder)
|
// The index contains a file (and not a folder)
|
||||||
if (f.isModified(i.getDirCacheEntry(), true,
|
if (f.isModified(i.getDirCacheEntry(), true,
|
||||||
|
@ -400,8 +399,7 @@ void processEntry(CanonicalTreeParser m, DirCacheBuildIterator i,
|
||||||
|| i.getDirCacheEntry().getStage() != 0)
|
|| i.getDirCacheEntry().getStage() != 0)
|
||||||
// The working tree file is dirty or the index contains a
|
// The working tree file is dirty or the index contains a
|
||||||
// conflict
|
// conflict
|
||||||
update(m.getEntryPathString(), m.getEntryObjectId(),
|
update(m);
|
||||||
m.getEntryFileMode());
|
|
||||||
else {
|
else {
|
||||||
// update the timestamp of the index with the one from the
|
// update the timestamp of the index with the one from the
|
||||||
// file if not set, as we are sure to be in sync here.
|
// 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)) {
|
if (f != null && isModifiedSubtree_IndexWorkingtree(name)) {
|
||||||
conflict(name, dce, h, m); // 1
|
conflict(name, dce, h, m); // 1
|
||||||
} else {
|
} else {
|
||||||
update(name, mId, mMode); // 2
|
update(1, name, mId, mMode); // 2
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
|
@ -828,7 +826,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
|
||||||
// are found later
|
// are found later
|
||||||
break;
|
break;
|
||||||
case 0xD0F: // 19
|
case 0xD0F: // 19
|
||||||
update(name, mId, mMode);
|
update(1, name, mId, mMode);
|
||||||
break;
|
break;
|
||||||
case 0xDF0: // conflict without a rule
|
case 0xDF0: // conflict without a rule
|
||||||
case 0x0FD: // 15
|
case 0x0FD: // 15
|
||||||
|
@ -839,7 +837,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
|
||||||
if (isModifiedSubtree_IndexWorkingtree(name))
|
if (isModifiedSubtree_IndexWorkingtree(name))
|
||||||
conflict(name, dce, h, m); // 8
|
conflict(name, dce, h, m); // 8
|
||||||
else
|
else
|
||||||
update(name, mId, mMode); // 7
|
update(1, name, mId, mMode); // 7
|
||||||
} else
|
} else
|
||||||
conflict(name, dce, h, m); // 9
|
conflict(name, dce, h, m); // 9
|
||||||
break;
|
break;
|
||||||
|
@ -859,7 +857,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
|
||||||
break;
|
break;
|
||||||
case 0x0DF: // 16 17
|
case 0x0DF: // 16 17
|
||||||
if (!isModifiedSubtree_IndexWorkingtree(name))
|
if (!isModifiedSubtree_IndexWorkingtree(name))
|
||||||
update(name, mId, mMode);
|
update(1, name, mId, mMode);
|
||||||
else
|
else
|
||||||
conflict(name, dce, h, m);
|
conflict(name, dce, h, m);
|
||||||
break;
|
break;
|
||||||
|
@ -929,7 +927,7 @@ void processEntry(CanonicalTreeParser h, CanonicalTreeParser m,
|
||||||
// At least one of Head, Index, Merge is not empty
|
// At least one of Head, Index, Merge is not empty
|
||||||
// -> only Merge contains something for this path. Use it!
|
// -> only Merge contains something for this path. Use it!
|
||||||
// Potentially update the file
|
// Potentially update the file
|
||||||
update(name, mId, mMode); // 1
|
update(1, name, mId, mMode); // 1
|
||||||
else if (m == null)
|
else if (m == null)
|
||||||
// Nothing in Merge
|
// Nothing in Merge
|
||||||
// Something in Head
|
// Something in Head
|
||||||
|
@ -947,7 +945,7 @@ else if (m == null)
|
||||||
// find in Merge. Potentially updates the file.
|
// find in Merge. Potentially updates the file.
|
||||||
if (equalIdAndMode(hId, hMode, mId, mMode)) {
|
if (equalIdAndMode(hId, hMode, mId, mMode)) {
|
||||||
if (initialCheckout || force) {
|
if (initialCheckout || force) {
|
||||||
update(name, mId, mMode);
|
update(1, name, mId, mMode);
|
||||||
} else {
|
} else {
|
||||||
keep(name, dce, f);
|
keep(name, dce, f);
|
||||||
}
|
}
|
||||||
|
@ -1131,7 +1129,7 @@ && isModified_IndexTree(name, iId, iMode, mId, mMode,
|
||||||
|
|
||||||
// TODO check that we don't overwrite some unsaved
|
// TODO check that we don't overwrite some unsaved
|
||||||
// file content
|
// file content
|
||||||
update(name, mId, mMode);
|
update(1, name, mId, mMode);
|
||||||
} else if (dce != null
|
} else if (dce != null
|
||||||
&& (f != null && f.isModified(dce, true,
|
&& (f != null && f.isModified(dce, true,
|
||||||
this.walk.getObjectReader()))) {
|
this.walk.getObjectReader()))) {
|
||||||
|
@ -1150,7 +1148,7 @@ && isModified_IndexTree(name, iId, iMode, mId, mMode,
|
||||||
// -> Standard case when switching between branches:
|
// -> Standard case when switching between branches:
|
||||||
// Nothing new in index but something different in
|
// Nothing new in index but something different in
|
||||||
// Merge. Update index and file
|
// Merge. Update index and file
|
||||||
update(name, mId, mMode);
|
update(1, name, mId, mMode);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Head differs from index or merge is same as index
|
// Head differs from index or merge is same as index
|
||||||
|
@ -1237,12 +1235,17 @@ private void remove(String path) {
|
||||||
removed.add(path);
|
removed.add(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void update(String path, ObjectId mId, FileMode mode)
|
private void update(CanonicalTreeParser tree) throws IOException {
|
||||||
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)) {
|
if (!FileMode.TREE.equals(mode)) {
|
||||||
updated.put(path, new CheckoutMetadata(
|
updated.put(path, new CheckoutMetadata(
|
||||||
walk.getEolStreamType(CHECKOUT_OP),
|
walk.getCheckoutEolStreamType(index),
|
||||||
walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)));
|
walk.getSmudgeCommand(index)));
|
||||||
|
|
||||||
DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
|
DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0);
|
||||||
entry.setObjectId(mId);
|
entry.setObjectId(mId);
|
||||||
|
|
|
@ -568,6 +568,9 @@ public boolean diff(ProgressMonitor monitor, int estWorkTreeSize,
|
||||||
if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
|
if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
|
||||||
try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
|
try (SubmoduleWalk smw = new SubmoduleWalk(repository)) {
|
||||||
smw.setTree(new DirCacheIterator(dirCache));
|
smw.setTree(new DirCacheIterator(dirCache));
|
||||||
|
if (filter != null) {
|
||||||
|
smw.setFilter(filter);
|
||||||
|
}
|
||||||
smw.setBuilderFactory(factory);
|
smw.setBuilderFactory(factory);
|
||||||
while (smw.next()) {
|
while (smw.next()) {
|
||||||
IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
|
IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
* Copyright (C) 2010-2012, Matthias Sohn <matthias.sohn@sap.com>
|
||||||
* Copyright (C) 2012, Research In Motion Limited
|
* Copyright (C) 2012, Research In Motion Limited
|
||||||
* Copyright (C) 2017, Obeo (mathieu.cartaud@obeo.fr)
|
* 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
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* 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;
|
private ContentMergeStrategy contentStrategy = ContentMergeStrategy.CONFLICT;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Keeps {@link CheckoutMetadata} for {@link #checkout()} and
|
* Keeps {@link CheckoutMetadata} for {@link #checkout()}.
|
||||||
* {@link #cleanUp()}.
|
|
||||||
*/
|
*/
|
||||||
private Map<String, CheckoutMetadata> checkoutMetadata;
|
private Map<String, CheckoutMetadata> checkoutMetadata;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Keeps {@link CheckoutMetadata} for {@link #cleanUp()}.
|
||||||
|
*/
|
||||||
|
private Map<String, CheckoutMetadata> cleanupMetadata;
|
||||||
|
|
||||||
private static MergeAlgorithm getMergeAlgorithm(Config config) {
|
private static MergeAlgorithm getMergeAlgorithm(Config config) {
|
||||||
SupportedAlgorithm diffAlg = config.getEnum(
|
SupportedAlgorithm diffAlg = config.getEnum(
|
||||||
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
|
CONFIG_DIFF_SECTION, null, CONFIG_KEY_ALGORITHM,
|
||||||
|
@ -383,12 +387,14 @@ protected boolean mergeImpl() throws IOException {
|
||||||
}
|
}
|
||||||
if (!inCore) {
|
if (!inCore) {
|
||||||
checkoutMetadata = new HashMap<>();
|
checkoutMetadata = new HashMap<>();
|
||||||
|
cleanupMetadata = new HashMap<>();
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
|
return mergeTrees(mergeBase(), sourceTrees[0], sourceTrees[1],
|
||||||
false);
|
false);
|
||||||
} finally {
|
} finally {
|
||||||
checkoutMetadata = null;
|
checkoutMetadata = null;
|
||||||
|
cleanupMetadata = null;
|
||||||
if (implicitDirCache) {
|
if (implicitDirCache) {
|
||||||
dircache.unlock();
|
dircache.unlock();
|
||||||
}
|
}
|
||||||
|
@ -447,7 +453,7 @@ protected void cleanUp() throws NoWorkTreeException,
|
||||||
DirCacheEntry entry = dc.getEntry(mpath);
|
DirCacheEntry entry = dc.getEntry(mpath);
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
DirCacheCheckout.checkoutEntry(db, entry, reader, false,
|
DirCacheCheckout.checkoutEntry(db, entry, reader, false,
|
||||||
checkoutMetadata.get(mpath));
|
cleanupMetadata.get(mpath));
|
||||||
}
|
}
|
||||||
mpathsIt.remove();
|
mpathsIt.remove();
|
||||||
}
|
}
|
||||||
|
@ -501,22 +507,26 @@ private DirCacheEntry keep(DirCacheEntry e) {
|
||||||
* Remembers the {@link CheckoutMetadata} for the given path; it may be
|
* Remembers the {@link CheckoutMetadata} for the given path; it may be
|
||||||
* needed in {@link #checkout()} or in {@link #cleanUp()}.
|
* needed in {@link #checkout()} or in {@link #cleanUp()}.
|
||||||
*
|
*
|
||||||
|
* @param map
|
||||||
|
* to add the metadata to
|
||||||
* @param path
|
* @param path
|
||||||
* of the current node
|
* of the current node
|
||||||
* @param attributes
|
* @param attributes
|
||||||
* for the current node
|
* to use for determining the metadata
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if the smudge filter cannot be determined
|
* 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 {
|
throws IOException {
|
||||||
if (checkoutMetadata != null) {
|
if (map != null) {
|
||||||
EolStreamType eol = EolStreamTypeUtil.detectStreamType(
|
EolStreamType eol = EolStreamTypeUtil.detectStreamType(
|
||||||
OperationType.CHECKOUT_OP, workingTreeOptions, attributes);
|
OperationType.CHECKOUT_OP, workingTreeOptions,
|
||||||
|
attributes);
|
||||||
CheckoutMetadata data = new CheckoutMetadata(eol,
|
CheckoutMetadata data = new CheckoutMetadata(eol,
|
||||||
tw.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE));
|
tw.getSmudgeCommand(attributes));
|
||||||
checkoutMetadata.put(path, data);
|
map.put(path, data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,15 +539,17 @@ protected void addCheckoutMetadata(String path, Attributes attributes)
|
||||||
* @param entry
|
* @param entry
|
||||||
* to add
|
* to add
|
||||||
* @param attributes
|
* @param attributes
|
||||||
* for the current entry
|
* the {@link Attributes} of the trees
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if the {@link CheckoutMetadata} cannot be determined
|
* if the {@link CheckoutMetadata} cannot be determined
|
||||||
* @since 5.1
|
* @since 6.1
|
||||||
*/
|
*/
|
||||||
protected void addToCheckout(String path, DirCacheEntry entry,
|
protected void addToCheckout(String path, DirCacheEntry entry,
|
||||||
Attributes attributes) throws IOException {
|
Attributes[] attributes)
|
||||||
|
throws IOException {
|
||||||
toBeCheckedOut.put(path, entry);
|
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
|
* @param isFile
|
||||||
* whether it is a file
|
* whether it is a file
|
||||||
* @param attributes
|
* @param attributes
|
||||||
* for the entry
|
* to use for determining the {@link CheckoutMetadata}
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
* if the {@link CheckoutMetadata} cannot be determined
|
* if the {@link CheckoutMetadata} cannot be determined
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
|
@ -558,7 +570,7 @@ protected void addDeletion(String path, boolean isFile,
|
||||||
Attributes attributes) throws IOException {
|
Attributes attributes) throws IOException {
|
||||||
toBeDeleted.add(path);
|
toBeDeleted.add(path);
|
||||||
if (isFile) {
|
if (isFile) {
|
||||||
addCheckoutMetadata(path, attributes);
|
addCheckoutMetadata(cleanupMetadata, path, attributes);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -599,7 +611,7 @@ protected void addDeletion(String path, boolean isFile,
|
||||||
* see
|
* see
|
||||||
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
|
* {@link org.eclipse.jgit.merge.ResolveMerger#mergeTrees(AbstractTreeIterator, RevTree, RevTree, boolean)}
|
||||||
* @param attributes
|
* @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
|
* @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
|
* didn't match ours or the working-dir file was dirty and a
|
||||||
* conflict occurred
|
* conflict occurred
|
||||||
|
@ -607,12 +619,12 @@ protected void addDeletion(String path, boolean isFile,
|
||||||
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
|
* @throws org.eclipse.jgit.errors.IncorrectObjectTypeException
|
||||||
* @throws org.eclipse.jgit.errors.CorruptObjectException
|
* @throws org.eclipse.jgit.errors.CorruptObjectException
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
* @since 4.9
|
* @since 6.1
|
||||||
*/
|
*/
|
||||||
protected boolean processEntry(CanonicalTreeParser base,
|
protected boolean processEntry(CanonicalTreeParser base,
|
||||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||||
DirCacheBuildIterator index, WorkingTreeIterator work,
|
DirCacheBuildIterator index, WorkingTreeIterator work,
|
||||||
boolean ignoreConflicts, Attributes attributes)
|
boolean ignoreConflicts, Attributes[] attributes)
|
||||||
throws MissingObjectException, IncorrectObjectTypeException,
|
throws MissingObjectException, IncorrectObjectTypeException,
|
||||||
CorruptObjectException, IOException {
|
CorruptObjectException, IOException {
|
||||||
enterSubtree = true;
|
enterSubtree = true;
|
||||||
|
@ -729,7 +741,7 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
// Base, ours, and theirs all contain a folder: don't delete
|
// Base, ours, and theirs all contain a folder: don't delete
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
addDeletion(tw.getPathString(), nonTree(modeO), attributes);
|
addDeletion(tw.getPathString(), nonTree(modeO), attributes[T_OURS]);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -772,7 +784,7 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
if (nonTree(modeO) && nonTree(modeT)) {
|
if (nonTree(modeO) && nonTree(modeT)) {
|
||||||
// Check worktree before modifying files
|
// Check worktree before modifying files
|
||||||
boolean worktreeDirty = isWorktreeDirty(work, ourDce);
|
boolean worktreeDirty = isWorktreeDirty(work, ourDce);
|
||||||
if (!attributes.canBeContentMerged() && worktreeDirty) {
|
if (!attributes[T_OURS].canBeContentMerged() && worktreeDirty) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,7 +803,7 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
mergeResults.put(tw.getPathString(), result);
|
mergeResults.put(tw.getPathString(), result);
|
||||||
unmergedPaths.add(tw.getPathString());
|
unmergedPaths.add(tw.getPathString());
|
||||||
return true;
|
return true;
|
||||||
} else if (!attributes.canBeContentMerged()) {
|
} else if (!attributes[T_OURS].canBeContentMerged()) {
|
||||||
// File marked as binary
|
// File marked as binary
|
||||||
switch (getContentMergeStrategy()) {
|
switch (getContentMergeStrategy()) {
|
||||||
case OURS:
|
case OURS:
|
||||||
|
@ -842,13 +854,16 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
if (ignoreConflicts) {
|
if (ignoreConflicts) {
|
||||||
result.setContainsConflicts(false);
|
result.setContainsConflicts(false);
|
||||||
}
|
}
|
||||||
updateIndex(base, ours, theirs, result, attributes);
|
updateIndex(base, ours, theirs, result, attributes[T_OURS]);
|
||||||
String currentPath = tw.getPathString();
|
String currentPath = tw.getPathString();
|
||||||
if (result.containsConflicts() && !ignoreConflicts) {
|
if (result.containsConflicts() && !ignoreConflicts) {
|
||||||
unmergedPaths.add(currentPath);
|
unmergedPaths.add(currentPath);
|
||||||
}
|
}
|
||||||
modifiedFiles.add(currentPath);
|
modifiedFiles.add(currentPath);
|
||||||
addCheckoutMetadata(currentPath, attributes);
|
addCheckoutMetadata(cleanupMetadata, currentPath,
|
||||||
|
attributes[T_OURS]);
|
||||||
|
addCheckoutMetadata(checkoutMetadata, currentPath,
|
||||||
|
attributes[T_THEIRS]);
|
||||||
} else if (modeO != modeT) {
|
} else if (modeO != modeT) {
|
||||||
// OURS or THEIRS has been deleted
|
// OURS or THEIRS has been deleted
|
||||||
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
|
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
|
// markers). But also stage 0 of the index is filled
|
||||||
// with that content.
|
// with that content.
|
||||||
result.setContainsConflicts(false);
|
result.setContainsConflicts(false);
|
||||||
updateIndex(base, ours, theirs, result, attributes);
|
updateIndex(base, ours, theirs, result,
|
||||||
|
attributes[T_OURS]);
|
||||||
} else {
|
} else {
|
||||||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
|
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, EPOCH,
|
||||||
0);
|
0);
|
||||||
|
@ -896,11 +912,9 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
if (isWorktreeDirty(work, ourDce)) {
|
if (isWorktreeDirty(work, ourDce)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (nonTree(modeT)) {
|
if (nonTree(modeT) && e != null) {
|
||||||
if (e != null) {
|
addToCheckout(tw.getPathString(), e,
|
||||||
addToCheckout(tw.getPathString(), e,
|
attributes);
|
||||||
attributes);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -945,14 +959,16 @@ private static MergeResult<SubmoduleConflict> createGitLinksMergeResult(
|
||||||
*/
|
*/
|
||||||
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||||
Attributes attributes, ContentMergeStrategy strategy)
|
Attributes[] attributes, ContentMergeStrategy strategy)
|
||||||
throws BinaryBlobException, IOException {
|
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
|
RawText baseText = base == null ? RawText.EMPTY_TEXT
|
||||||
: getRawText(base.getEntryObjectId(), attributes);
|
: getRawText(base.getEntryObjectId(), attributes[T_BASE]);
|
||||||
RawText ourText = ours == null ? RawText.EMPTY_TEXT
|
RawText ourText = ours == null ? RawText.EMPTY_TEXT
|
||||||
: getRawText(ours.getEntryObjectId(), attributes);
|
: getRawText(ours.getEntryObjectId(), attributes[T_OURS]);
|
||||||
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
|
RawText theirsText = theirs == null ? RawText.EMPTY_TEXT
|
||||||
: getRawText(theirs.getEntryObjectId(), attributes);
|
: getRawText(theirs.getEntryObjectId(), attributes[T_THEIRS]);
|
||||||
mergeAlgorithm.setContentMergeStrategy(strategy);
|
mergeAlgorithm.setContentMergeStrategy(strategy);
|
||||||
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
|
return mergeAlgorithm.merge(RawTextComparator.DEFAULT, baseText,
|
||||||
ourText, theirsText);
|
ourText, theirsText);
|
||||||
|
@ -1342,7 +1358,7 @@ protected boolean mergeTrees(AbstractTreeIterator baseTree,
|
||||||
|
|
||||||
tw = new NameConflictTreeWalk(db, reader);
|
tw = new NameConflictTreeWalk(db, reader);
|
||||||
tw.addTree(baseTree);
|
tw.addTree(baseTree);
|
||||||
tw.addTree(headTree);
|
tw.setHead(tw.addTree(headTree));
|
||||||
tw.addTree(mergeTree);
|
tw.addTree(mergeTree);
|
||||||
int dciPos = tw.addTree(buildIt);
|
int dciPos = tw.addTree(buildIt);
|
||||||
if (workingTreeIterator != null) {
|
if (workingTreeIterator != null) {
|
||||||
|
@ -1403,6 +1419,13 @@ protected boolean mergeTreeWalk(TreeWalk treeWalk, boolean ignoreConflicts)
|
||||||
boolean hasAttributeNodeProvider = treeWalk
|
boolean hasAttributeNodeProvider = treeWalk
|
||||||
.getAttributesNodeProvider() != null;
|
.getAttributesNodeProvider() != null;
|
||||||
while (treeWalk.next()) {
|
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(
|
if (!processEntry(
|
||||||
treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
|
treeWalk.getTree(T_BASE, CanonicalTreeParser.class),
|
||||||
treeWalk.getTree(T_OURS, 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),
|
treeWalk.getTree(T_INDEX, DirCacheBuildIterator.class),
|
||||||
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
|
hasWorkingTreeIterator ? treeWalk.getTree(T_FILE,
|
||||||
WorkingTreeIterator.class) : null,
|
WorkingTreeIterator.class) : null,
|
||||||
ignoreConflicts, hasAttributeNodeProvider
|
ignoreConflicts, attributes)) {
|
||||||
? treeWalk.getAttributes()
|
|
||||||
: NO_ATTRIBUTES)) {
|
|
||||||
cleanUp();
|
cleanUp();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,11 +18,15 @@
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.MissingObjectException;
|
||||||
import org.eclipse.jgit.errors.NotSupportedException;
|
import org.eclipse.jgit.errors.NotSupportedException;
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
|
import org.eclipse.jgit.hooks.PrePushHook;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ProgressMonitor;
|
import org.eclipse.jgit.lib.ProgressMonitor;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
@ -58,6 +62,8 @@ class PushProcess {
|
||||||
/** A list of option strings associated with this push */
|
/** A list of option strings associated with this push */
|
||||||
private List<String> pushOptions;
|
private List<String> pushOptions;
|
||||||
|
|
||||||
|
private final PrePushHook prePush;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create process for specified transport and refs updates specification.
|
* Create process for specified transport and refs updates specification.
|
||||||
*
|
*
|
||||||
|
@ -66,12 +72,14 @@ class PushProcess {
|
||||||
* connection.
|
* connection.
|
||||||
* @param toPush
|
* @param toPush
|
||||||
* specification of refs updates (and local tracking branches).
|
* specification of refs updates (and local tracking branches).
|
||||||
*
|
* @param prePush
|
||||||
|
* {@link PrePushHook} to run after the remote advertisement has
|
||||||
|
* been gotten
|
||||||
* @throws TransportException
|
* @throws TransportException
|
||||||
*/
|
*/
|
||||||
PushProcess(final Transport transport,
|
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
|
||||||
final Collection<RemoteRefUpdate> toPush) throws TransportException {
|
PrePushHook prePush) throws TransportException {
|
||||||
this(transport, toPush, null);
|
this(transport, toPush, prePush, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -82,16 +90,19 @@ class PushProcess {
|
||||||
* connection.
|
* connection.
|
||||||
* @param toPush
|
* @param toPush
|
||||||
* specification of refs updates (and local tracking branches).
|
* specification of refs updates (and local tracking branches).
|
||||||
|
* @param prePush
|
||||||
|
* {@link PrePushHook} to run after the remote advertisement has
|
||||||
|
* been gotten
|
||||||
* @param out
|
* @param out
|
||||||
* OutputStream to write messages to
|
* OutputStream to write messages to
|
||||||
* @throws TransportException
|
* @throws TransportException
|
||||||
*/
|
*/
|
||||||
PushProcess(final Transport transport,
|
PushProcess(Transport transport, Collection<RemoteRefUpdate> toPush,
|
||||||
final Collection<RemoteRefUpdate> toPush, OutputStream out)
|
PrePushHook prePush, OutputStream out) throws TransportException {
|
||||||
throws TransportException {
|
|
||||||
this.walker = new RevWalk(transport.local);
|
this.walker = new RevWalk(transport.local);
|
||||||
this.transport = transport;
|
this.transport = transport;
|
||||||
this.toPush = new LinkedHashMap<>();
|
this.toPush = new LinkedHashMap<>();
|
||||||
|
this.prePush = prePush;
|
||||||
this.out = out;
|
this.out = out;
|
||||||
this.pushOptions = transport.getPushOptions();
|
this.pushOptions = transport.getPushOptions();
|
||||||
for (RemoteRefUpdate rru : toPush) {
|
for (RemoteRefUpdate rru : toPush) {
|
||||||
|
@ -129,10 +140,38 @@ PushResult execute(ProgressMonitor monitor)
|
||||||
res.setAdvertisedRefs(transport.getURI(), connection
|
res.setAdvertisedRefs(transport.getURI(), connection
|
||||||
.getRefsMap());
|
.getRefsMap());
|
||||||
res.peerUserAgent = connection.getPeerUserAgent();
|
res.peerUserAgent = connection.getPeerUserAgent();
|
||||||
res.setRemoteUpdates(toPush);
|
|
||||||
monitor.endTask();
|
monitor.endTask();
|
||||||
|
|
||||||
|
Map<String, RemoteRefUpdate> expanded = expandMatching();
|
||||||
|
toPush.clear();
|
||||||
|
toPush.putAll(expanded);
|
||||||
|
|
||||||
|
res.setRemoteUpdates(toPush);
|
||||||
final Map<String, RemoteRefUpdate> preprocessed = prepareRemoteUpdates();
|
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())
|
if (transport.isDryRun())
|
||||||
modifyUpdatesForDryRun();
|
modifyUpdatesForDryRun();
|
||||||
else if (!preprocessed.isEmpty())
|
else if (!preprocessed.isEmpty())
|
||||||
|
@ -201,25 +240,8 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// check for fast-forward:
|
boolean fastForward = isFastForward(advertisedOld,
|
||||||
// - both old and new ref must point to commits, AND
|
rru.getNewObjectId());
|
||||||
// - 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);
|
|
||||||
}
|
|
||||||
rru.setFastForward(fastForward);
|
rru.setFastForward(fastForward);
|
||||||
if (!fastForward && !rru.isForceUpdate()) {
|
if (!fastForward && !rru.isForceUpdate()) {
|
||||||
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
|
rru.setStatus(Status.REJECTED_NONFASTFORWARD);
|
||||||
|
@ -233,6 +255,134 @@ private Map<String, RemoteRefUpdate> prepareRemoteUpdates()
|
||||||
return result;
|
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() {
|
private Map<String, RemoteRefUpdate> rejectAll() {
|
||||||
for (RemoteRefUpdate rru : toPush.values()) {
|
for (RemoteRefUpdate rru : toPush.values()) {
|
||||||
if (rru.getStatus() == Status.NOT_ATTEMPTED) {
|
if (rru.getStatus() == Status.NOT_ATTEMPTED) {
|
||||||
|
|
|
@ -12,11 +12,11 @@
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
import org.eclipse.jgit.util.References;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Describes how refs in one repository copy into another repository.
|
* 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? */
|
/** Is this specification actually a wildcard match? */
|
||||||
private boolean wildcard;
|
private boolean wildcard;
|
||||||
|
|
||||||
|
/** Is this the special ":" RefSpec? */
|
||||||
|
private boolean matching;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* How strict to be about wildcards.
|
* How strict to be about wildcards.
|
||||||
*
|
*
|
||||||
|
@ -71,6 +74,7 @@ public enum WildcardMode {
|
||||||
*/
|
*/
|
||||||
ALLOW_MISMATCH
|
ALLOW_MISMATCH
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Whether a wildcard is allowed on one side but not the other. */
|
/** Whether a wildcard is allowed on one side but not the other. */
|
||||||
private WildcardMode allowMismatchedWildcards;
|
private WildcardMode allowMismatchedWildcards;
|
||||||
|
|
||||||
|
@ -87,6 +91,7 @@ public enum WildcardMode {
|
||||||
* applications, as at least one field must be set to match a source name.
|
* applications, as at least one field must be set to match a source name.
|
||||||
*/
|
*/
|
||||||
public RefSpec() {
|
public RefSpec() {
|
||||||
|
matching = false;
|
||||||
force = false;
|
force = false;
|
||||||
wildcard = false;
|
wildcard = false;
|
||||||
srcName = Constants.HEAD;
|
srcName = Constants.HEAD;
|
||||||
|
@ -133,17 +138,25 @@ public RefSpec(String spec, WildcardMode mode) {
|
||||||
s = s.substring(1);
|
s = s.substring(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean matchPushSpec = false;
|
||||||
final int c = s.lastIndexOf(':');
|
final int c = s.lastIndexOf(':');
|
||||||
if (c == 0) {
|
if (c == 0) {
|
||||||
s = s.substring(1);
|
s = s.substring(1);
|
||||||
if (isWildcard(s)) {
|
if (s.isEmpty()) {
|
||||||
|
matchPushSpec = true;
|
||||||
wildcard = true;
|
wildcard = true;
|
||||||
if (mode == WildcardMode.REQUIRE_MATCH) {
|
srcName = Constants.R_HEADS + '*';
|
||||||
throw new IllegalArgumentException(MessageFormat
|
dstName = srcName;
|
||||||
.format(JGitText.get().invalidWildcards, spec));
|
} 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) {
|
} else if (c > 0) {
|
||||||
String src = s.substring(0, c);
|
String src = s.substring(0, c);
|
||||||
String dst = s.substring(c + 1);
|
String dst = s.substring(c + 1);
|
||||||
|
@ -168,6 +181,7 @@ public RefSpec(String spec, WildcardMode mode) {
|
||||||
}
|
}
|
||||||
srcName = checkValid(s);
|
srcName = checkValid(s);
|
||||||
}
|
}
|
||||||
|
matching = matchPushSpec;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -195,6 +209,7 @@ public RefSpec(String spec) {
|
||||||
}
|
}
|
||||||
|
|
||||||
private RefSpec(RefSpec p) {
|
private RefSpec(RefSpec p) {
|
||||||
|
matching = false;
|
||||||
force = p.isForceUpdate();
|
force = p.isForceUpdate();
|
||||||
wildcard = p.isWildcard();
|
wildcard = p.isWildcard();
|
||||||
srcName = p.getSource();
|
srcName = p.getSource();
|
||||||
|
@ -202,6 +217,17 @@ private RefSpec(RefSpec p) {
|
||||||
allowMismatchedWildcards = p.allowMismatchedWildcards;
|
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.
|
* Check if this specification wants to forcefully update the destination.
|
||||||
*
|
*
|
||||||
|
@ -220,6 +246,7 @@ public boolean isForceUpdate() {
|
||||||
*/
|
*/
|
||||||
public RefSpec setForceUpdate(boolean forceUpdate) {
|
public RefSpec setForceUpdate(boolean forceUpdate) {
|
||||||
final RefSpec r = new RefSpec(this);
|
final RefSpec r = new RefSpec(this);
|
||||||
|
r.matching = matching;
|
||||||
r.force = forceUpdate;
|
r.force = forceUpdate;
|
||||||
return r;
|
return r;
|
||||||
}
|
}
|
||||||
|
@ -322,8 +349,7 @@ public RefSpec setDestination(String destination) {
|
||||||
* The wildcard status of the new source disagrees with the
|
* The wildcard status of the new source disagrees with the
|
||||||
* wildcard status of the new destination.
|
* wildcard status of the new destination.
|
||||||
*/
|
*/
|
||||||
public RefSpec setSourceDestination(final String source,
|
public RefSpec setSourceDestination(String source, String destination) {
|
||||||
final String destination) {
|
|
||||||
if (isWildcard(source) != isWildcard(destination))
|
if (isWildcard(source) != isWildcard(destination))
|
||||||
throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
|
throw new IllegalStateException(JGitText.get().sourceDestinationMustMatch);
|
||||||
final RefSpec r = new RefSpec(this);
|
final RefSpec r = new RefSpec(this);
|
||||||
|
@ -541,37 +567,36 @@ public boolean equals(Object obj) {
|
||||||
if (!(obj instanceof RefSpec))
|
if (!(obj instanceof RefSpec))
|
||||||
return false;
|
return false;
|
||||||
final RefSpec b = (RefSpec) obj;
|
final RefSpec b = (RefSpec) obj;
|
||||||
if (isForceUpdate() != b.isForceUpdate())
|
if (isForceUpdate() != b.isForceUpdate()) {
|
||||||
return false;
|
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 false;
|
||||||
return a.equals(b);
|
}
|
||||||
|
return isWildcard() == b.isWildcard()
|
||||||
|
&& Objects.equals(getSource(), b.getSource())
|
||||||
|
&& Objects.equals(getDestination(), b.getDestination());
|
||||||
}
|
}
|
||||||
|
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
final StringBuilder r = new StringBuilder();
|
final StringBuilder r = new StringBuilder();
|
||||||
if (isForceUpdate())
|
if (isForceUpdate()) {
|
||||||
r.append('+');
|
r.append('+');
|
||||||
if (getSource() != null)
|
}
|
||||||
r.append(getSource());
|
if (isMatching()) {
|
||||||
if (getDestination() != null) {
|
|
||||||
r.append(':');
|
r.append(':');
|
||||||
r.append(getDestination());
|
} else {
|
||||||
|
if (getSource() != null) {
|
||||||
|
r.append(getSource());
|
||||||
|
}
|
||||||
|
if (getDestination() != null) {
|
||||||
|
r.append(':');
|
||||||
|
r.append(getDestination());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return r.toString();
|
return r.toString();
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,9 @@
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
@ -115,6 +117,12 @@ public enum Status {
|
||||||
|
|
||||||
private RefUpdate localUpdate;
|
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.
|
* Construct remote ref update request by providing an update specification.
|
||||||
* Object is created with default
|
* Object is created with default
|
||||||
|
@ -157,9 +165,8 @@ public enum Status {
|
||||||
* @throws java.lang.IllegalArgumentException
|
* @throws java.lang.IllegalArgumentException
|
||||||
* if some required parameter was null
|
* if some required parameter was null
|
||||||
*/
|
*/
|
||||||
public RemoteRefUpdate(final Repository localDb, final String srcRef,
|
public RemoteRefUpdate(Repository localDb, String srcRef, String remoteName,
|
||||||
final String remoteName, final boolean forceUpdate,
|
boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
|
||||||
final String localName, final ObjectId expectedOldObjectId)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
|
this(localDb, srcRef, srcRef != null ? localDb.resolve(srcRef)
|
||||||
: ObjectId.zeroId(), remoteName, forceUpdate, localName,
|
: ObjectId.zeroId(), remoteName, forceUpdate, localName,
|
||||||
|
@ -203,9 +210,8 @@ public RemoteRefUpdate(final Repository localDb, final String srcRef,
|
||||||
* @throws java.lang.IllegalArgumentException
|
* @throws java.lang.IllegalArgumentException
|
||||||
* if some required parameter was null
|
* if some required parameter was null
|
||||||
*/
|
*/
|
||||||
public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
|
public RemoteRefUpdate(Repository localDb, Ref srcRef, String remoteName,
|
||||||
final String remoteName, final boolean forceUpdate,
|
boolean forceUpdate, String localName, ObjectId expectedOldObjectId)
|
||||||
final String localName, final ObjectId expectedOldObjectId)
|
|
||||||
throws IOException {
|
throws IOException {
|
||||||
this(localDb, srcRef != null ? srcRef.getName() : null,
|
this(localDb, srcRef != null ? srcRef.getName() : null,
|
||||||
srcRef != null ? srcRef.getObjectId() : null, remoteName,
|
srcRef != null ? srcRef.getObjectId() : null, remoteName,
|
||||||
|
@ -255,28 +261,41 @@ public RemoteRefUpdate(final Repository localDb, final Ref srcRef,
|
||||||
* @throws java.lang.IllegalArgumentException
|
* @throws java.lang.IllegalArgumentException
|
||||||
* if some required parameter was null
|
* if some required parameter was null
|
||||||
*/
|
*/
|
||||||
public RemoteRefUpdate(final Repository localDb, final String srcRef,
|
public RemoteRefUpdate(Repository localDb, String srcRef, ObjectId srcId,
|
||||||
final ObjectId srcId, final String remoteName,
|
String remoteName, boolean forceUpdate, String localName,
|
||||||
final boolean forceUpdate, final String localName,
|
ObjectId expectedOldObjectId) throws IOException {
|
||||||
final ObjectId expectedOldObjectId) throws IOException {
|
this(localDb, srcRef, srcId, remoteName, forceUpdate, localName, null,
|
||||||
if (remoteName == null)
|
expectedOldObjectId);
|
||||||
throw new IllegalArgumentException(JGitText.get().remoteNameCannotBeNull);
|
}
|
||||||
if (srcId == null && srcRef != null)
|
|
||||||
throw new IOException(MessageFormat.format(
|
|
||||||
JGitText.get().sourceRefDoesntResolveToAnyObject, srcRef));
|
|
||||||
|
|
||||||
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;
|
this.srcRef = srcRef;
|
||||||
else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
|
} else if (srcId != null && !srcId.equals(ObjectId.zeroId())) {
|
||||||
this.srcRef = srcId.name();
|
this.srcRef = srcId.name();
|
||||||
else
|
} else {
|
||||||
this.srcRef = null;
|
this.srcRef = null;
|
||||||
|
}
|
||||||
if (srcId != null)
|
if (srcId != null) {
|
||||||
this.newObjectId = srcId;
|
this.newObjectId = srcId;
|
||||||
else
|
} else {
|
||||||
this.newObjectId = ObjectId.zeroId();
|
this.newObjectId = ObjectId.zeroId();
|
||||||
|
}
|
||||||
|
this.fetchSpecs = fetchSpecs;
|
||||||
this.remoteName = remoteName;
|
this.remoteName = remoteName;
|
||||||
this.forceUpdate = forceUpdate;
|
this.forceUpdate = forceUpdate;
|
||||||
if (localName != null && localDb != null) {
|
if (localName != null && localDb != null) {
|
||||||
|
@ -292,8 +311,9 @@ else if (srcId != null && !srcId.equals(ObjectId.zeroId()))
|
||||||
? localUpdate.getOldObjectId()
|
? localUpdate.getOldObjectId()
|
||||||
: ObjectId.zeroId(),
|
: ObjectId.zeroId(),
|
||||||
newObjectId);
|
newObjectId);
|
||||||
} else
|
} else {
|
||||||
trackingRefUpdate = null;
|
trackingRefUpdate = null;
|
||||||
|
}
|
||||||
this.localDb = localDb;
|
this.localDb = localDb;
|
||||||
this.expectedOldObjectId = expectedOldObjectId;
|
this.expectedOldObjectId = expectedOldObjectId;
|
||||||
this.status = Status.NOT_ATTEMPTED;
|
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
|
* local tracking branch or srcRef of base object no longer can
|
||||||
* be resolved to any object.
|
* be resolved to any object.
|
||||||
*/
|
*/
|
||||||
public RemoteRefUpdate(final RemoteRefUpdate base,
|
public RemoteRefUpdate(RemoteRefUpdate base,
|
||||||
final ObjectId newExpectedOldObjectId) throws IOException {
|
ObjectId newExpectedOldObjectId) throws IOException {
|
||||||
this(base.localDb, base.srcRef, base.remoteName, base.forceUpdate,
|
this(base.localDb, base.srcRef, base.newObjectId, base.remoteName,
|
||||||
|
base.forceUpdate,
|
||||||
(base.trackingRefUpdate == null ? null : base.trackingRefUpdate
|
(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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -40,7 +40,6 @@
|
||||||
|
|
||||||
import org.eclipse.jgit.annotations.NonNull;
|
import org.eclipse.jgit.annotations.NonNull;
|
||||||
import org.eclipse.jgit.annotations.Nullable;
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
import org.eclipse.jgit.api.errors.AbortedByHookException;
|
|
||||||
import org.eclipse.jgit.errors.NotSupportedException;
|
import org.eclipse.jgit.errors.NotSupportedException;
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
import org.eclipse.jgit.hooks.Hooks;
|
import org.eclipse.jgit.hooks.Hooks;
|
||||||
|
@ -590,6 +589,11 @@ public static Collection<RemoteRefUpdate> findRemoteRefUpdatesFor(
|
||||||
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
|
final Collection<RefSpec> procRefs = expandPushWildcardsFor(db, specs);
|
||||||
|
|
||||||
for (RefSpec spec : procRefs) {
|
for (RefSpec spec : procRefs) {
|
||||||
|
if (spec.isMatching()) {
|
||||||
|
result.add(new RemoteRefUpdate(db, spec.isForceUpdate(),
|
||||||
|
fetchSpecs));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String srcSpec = spec.getSource();
|
String srcSpec = spec.getSource();
|
||||||
final Ref srcRef = db.findRef(srcSpec);
|
final Ref srcRef = db.findRef(srcSpec);
|
||||||
if (srcRef != null)
|
if (srcRef != null)
|
||||||
|
@ -660,7 +664,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
|
||||||
|
|
||||||
List<Ref> localRefs = null;
|
List<Ref> localRefs = null;
|
||||||
for (RefSpec spec : specs) {
|
for (RefSpec spec : specs) {
|
||||||
if (spec.isWildcard()) {
|
if (!spec.isMatching() && spec.isWildcard()) {
|
||||||
if (localRefs == null) {
|
if (localRefs == null) {
|
||||||
localRefs = db.getRefDatabase().getRefs();
|
localRefs = db.getRefDatabase().getRefs();
|
||||||
}
|
}
|
||||||
|
@ -676,7 +680,7 @@ private static Collection<RefSpec> expandPushWildcardsFor(
|
||||||
return procRefs;
|
return procRefs;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String findTrackingRefName(final String remoteName,
|
static String findTrackingRefName(final String remoteName,
|
||||||
final Collection<RefSpec> fetchSpecs) {
|
final Collection<RefSpec> fetchSpecs) {
|
||||||
// try to find matching tracking refs
|
// try to find matching tracking refs
|
||||||
for (RefSpec fetchSpec : fetchSpecs) {
|
for (RefSpec fetchSpec : fetchSpecs) {
|
||||||
|
@ -1375,16 +1379,9 @@ public PushResult push(final ProgressMonitor monitor,
|
||||||
if (toPush.isEmpty())
|
if (toPush.isEmpty())
|
||||||
throw new TransportException(JGitText.get().nothingToPush);
|
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);
|
return pushProcess.execute(monitor);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2008-2009, Google Inc.
|
* Copyright (C) 2008, 2009 Google Inc.
|
||||||
* Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
|
* Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> and others
|
||||||
*
|
*
|
||||||
* This program and the accompanying materials are made available under the
|
* This program and the accompanying materials are made available under the
|
||||||
* terms of the Eclipse Distribution License v. 1.0 which is available at
|
* 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 static java.nio.charset.StandardCharsets.UTF_8;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
@ -73,6 +74,7 @@
|
||||||
* threads.
|
* threads.
|
||||||
*/
|
*/
|
||||||
public class TreeWalk implements AutoCloseable, AttributesProvider {
|
public class TreeWalk implements AutoCloseable, AttributesProvider {
|
||||||
|
|
||||||
private static final AbstractTreeIterator[] NO_TREES = {};
|
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;
|
private OperationType operationType = OperationType.CHECKOUT_OP;
|
||||||
|
|
||||||
|
@ -284,11 +286,20 @@ public static TreeWalk forPath(final Repository db, final String path,
|
||||||
|
|
||||||
AbstractTreeIterator currentHead;
|
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;
|
private Config config;
|
||||||
|
|
||||||
|
@ -514,6 +525,24 @@ public AttributesNodeProvider getAttributesNodeProvider() {
|
||||||
return attributesNodeProvider;
|
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}
|
* {@inheritDoc}
|
||||||
* <p>
|
* <p>
|
||||||
|
@ -556,25 +585,51 @@ public AttributesNodeProvider getAttributesNodeProvider() {
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public Attributes getAttributes() {
|
public Attributes getAttributes() {
|
||||||
if (attrs != null)
|
return getAttributes(headIndex);
|
||||||
return attrs;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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) {
|
if (attributesNodeProvider == null) {
|
||||||
// The work tree should have a AttributesNodeProvider to be able to
|
|
||||||
// retrieve the info and global attributes node
|
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
|
"The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Lazy create the attributesHandler on the first access of
|
AttributesHandler handler = attributesHandlers[attrIndex];
|
||||||
// attributes. This requires the info, global and root
|
if (handler == null) {
|
||||||
// attributes nodes
|
if (index < 0) {
|
||||||
if (attributesHandler == null) {
|
// Legacy behavior (headIndex not set, getAttributes() above
|
||||||
attributesHandler = new AttributesHandler(this);
|
// 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();
|
result = handler.getAttributes();
|
||||||
return attrs;
|
attrs[attrIndex] = result;
|
||||||
|
return result;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
|
throw new JGitInternalException("Error while parsing attributes", //$NON-NLS-1$
|
||||||
e);
|
e);
|
||||||
|
@ -595,11 +650,34 @@ public Attributes getAttributes() {
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public EolStreamType getEolStreamType(OperationType opType) {
|
public EolStreamType getEolStreamType(OperationType opType) {
|
||||||
if (attributesNodeProvider == null || config == null)
|
if (attributesNodeProvider == null || config == null) {
|
||||||
return null;
|
return null;
|
||||||
return EolStreamTypeUtil.detectStreamType(
|
}
|
||||||
opType != null ? opType : operationType,
|
OperationType op = opType != null ? opType : operationType;
|
||||||
config.get(WorkingTreeOptions.KEY), getAttributes());
|
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() {
|
public void reset() {
|
||||||
attrs = null;
|
attrs = null;
|
||||||
attributesHandler = null;
|
attributesHandlers = null;
|
||||||
|
headIndex = -1;
|
||||||
trees = NO_TREES;
|
trees = NO_TREES;
|
||||||
advance = false;
|
advance = false;
|
||||||
depth = 0;
|
depth = 0;
|
||||||
|
@ -651,7 +730,9 @@ public void reset(AnyObjectId id) throws MissingObjectException,
|
||||||
|
|
||||||
advance = false;
|
advance = false;
|
||||||
depth = 0;
|
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;
|
trees = r;
|
||||||
advance = false;
|
advance = false;
|
||||||
depth = 0;
|
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;
|
p.matchShift = 0;
|
||||||
|
|
||||||
trees = newTrees;
|
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;
|
return n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -800,7 +898,7 @@ public boolean next() throws MissingObjectException,
|
||||||
}
|
}
|
||||||
|
|
||||||
for (;;) {
|
for (;;) {
|
||||||
attrs = null;
|
Arrays.fill(attrs, null);
|
||||||
final AbstractTreeIterator t = min();
|
final AbstractTreeIterator t = min();
|
||||||
if (t.eof()) {
|
if (t.eof()) {
|
||||||
if (depth > 0) {
|
if (depth > 0) {
|
||||||
|
@ -1255,7 +1353,7 @@ public boolean isPostChildren() {
|
||||||
*/
|
*/
|
||||||
public void enterSubtree() throws MissingObjectException,
|
public void enterSubtree() throws MissingObjectException,
|
||||||
IncorrectObjectTypeException, CorruptObjectException, IOException {
|
IncorrectObjectTypeException, CorruptObjectException, IOException {
|
||||||
attrs = null;
|
Arrays.fill(attrs, null);
|
||||||
final AbstractTreeIterator ch = currentHead;
|
final AbstractTreeIterator ch = currentHead;
|
||||||
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
|
final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length];
|
||||||
for (int i = 0; i < trees.length; i++) {
|
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
|
* Inspect config and attributes to return a filtercommand applicable for
|
||||||
* the current path, but without expanding %f occurences
|
* the current path.
|
||||||
*
|
*
|
||||||
* @param filterCommandType
|
* @param filterCommandType
|
||||||
* which type of filterCommand should be executed. E.g. "clean",
|
* which type of filterCommand should be executed. E.g. "clean",
|
||||||
* "smudge"
|
* "smudge". For "smudge" consider using
|
||||||
|
* {{@link #getSmudgeCommand(int)} instead.
|
||||||
* @return a filter command
|
* @return a filter command
|
||||||
* @throws java.io.IOException
|
* @throws java.io.IOException
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
|
@ -1406,6 +1505,54 @@ public String getFilterCommand(String filterCommandType)
|
||||||
QuotedString.BOURNE.quote((getPathString()))));
|
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
|
* 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
|
* string may contain "%f" which needs to be replaced by the current path
|
||||||
|
|
17
pom.xml
17
pom.xml
|
@ -203,6 +203,14 @@
|
||||||
<id>repo.eclipse.org.cbi-snapshots</id>
|
<id>repo.eclipse.org.cbi-snapshots</id>
|
||||||
<url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
|
<url>https://repo.eclipse.org/content/repositories/cbi-snapshots/</url>
|
||||||
</pluginRepository>
|
</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>
|
</pluginRepositories>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
|
@ -382,6 +390,11 @@
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
<version>2.5.4</version>
|
<version>2.5.4</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.eclipse.dash</groupId>
|
||||||
|
<artifactId>license-tool-plugin</artifactId>
|
||||||
|
<version>0.0.1-SNAPSHOT</version>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</pluginManagement>
|
</pluginManagement>
|
||||||
|
|
||||||
|
@ -540,6 +553,10 @@
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-report-plugin</artifactId>
|
<artifactId>maven-surefire-report-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.eclipse.dash</groupId>
|
||||||
|
<artifactId>license-tool-plugin</artifactId>
|
||||||
|
</plugin>
|
||||||
</plugins>
|
</plugins>
|
||||||
</build>
|
</build>
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue