LFS: support merge/rebase/cherry-pick/diff/compare with LFS files
Respect merge=lfs and diff=lfs attributes where required to replace (in memory) the content of LFS pointers with the actual blob content from the LFS storage (and vice versa when staging/merging). Does not implement general support for merge/diff attributes for any other use case apart from LFS. Change-Id: Ibad8875de1e0bee8fe3a1dffb1add93111534cae Signed-off-by: Markus Duft <markus.duft@ssi-schaefer.com> Signed-off-by: Matthias Sohn <matthias.sohn@sap.com>
This commit is contained in:
parent
169de08a78
commit
d3ed64bcd4
|
@ -51,8 +51,7 @@
|
||||||
import org.eclipse.jgit.api.Git;
|
import org.eclipse.jgit.api.Git;
|
||||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
import org.eclipse.jgit.lfs.CleanFilter;
|
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
|
||||||
import org.eclipse.jgit.lfs.lib.LongObjectId;
|
import org.eclipse.jgit.lfs.lib.LongObjectId;
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.lib.StoredConfig;
|
||||||
|
@ -72,8 +71,7 @@ public class CheckoutTest extends LfsServerTest {
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
super.setup();
|
super.setup();
|
||||||
|
|
||||||
SmudgeFilter.register();
|
BuiltinLFS.register();
|
||||||
CleanFilter.register();
|
|
||||||
|
|
||||||
Path tmp = Files.createTempDirectory("jgit_test_");
|
Path tmp = Files.createTempDirectory("jgit_test_");
|
||||||
Repository db = FileRepositoryBuilder
|
Repository db = FileRepositoryBuilder
|
||||||
|
|
|
@ -52,8 +52,7 @@
|
||||||
import org.eclipse.jgit.api.RemoteAddCommand;
|
import org.eclipse.jgit.api.RemoteAddCommand;
|
||||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.TestRepository;
|
import org.eclipse.jgit.junit.TestRepository;
|
||||||
import org.eclipse.jgit.lfs.CleanFilter;
|
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
@ -84,8 +83,7 @@ public class PushTest extends LfsServerTest {
|
||||||
public void setup() throws Exception {
|
public void setup() throws Exception {
|
||||||
super.setup();
|
super.setup();
|
||||||
|
|
||||||
SmudgeFilter.register();
|
BuiltinLFS.register();
|
||||||
CleanFilter.register();
|
|
||||||
|
|
||||||
Path rtmp = Files.createTempDirectory("jgit_test_");
|
Path rtmp = Files.createTempDirectory("jgit_test_");
|
||||||
remoteDb = FileRepositoryBuilder.create(rtmp.toFile());
|
remoteDb = FileRepositoryBuilder.create(rtmp.toFile());
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<component id="org.eclipse.jgit.lfs" version="2">
|
<component id="org.eclipse.jgit.lfs" version="2">
|
||||||
|
<resource path="src/org/eclipse/jgit/lfs/CleanFilter.java" type="org.eclipse.jgit.lfs.CleanFilter">
|
||||||
|
<filter id="421572723">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="org.eclipse.jgit.lfs.CleanFilter"/>
|
||||||
|
<message_argument value="register()"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
<resource path="src/org/eclipse/jgit/lfs/LfsPointer.java" type="org.eclipse.jgit.lfs.LfsPointer">
|
<resource path="src/org/eclipse/jgit/lfs/LfsPointer.java" type="org.eclipse.jgit.lfs.LfsPointer">
|
||||||
<filter id="336658481">
|
<filter id="336658481">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
|
@ -8,4 +16,12 @@
|
||||||
</message_arguments>
|
</message_arguments>
|
||||||
</filter>
|
</filter>
|
||||||
</resource>
|
</resource>
|
||||||
|
<resource path="src/org/eclipse/jgit/lfs/SmudgeFilter.java" type="org.eclipse.jgit.lfs.SmudgeFilter">
|
||||||
|
<filter id="421572723">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="org.eclipse.jgit.lfs.SmudgeFilter"/>
|
||||||
|
<message_argument value="register()"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
</component>
|
</component>
|
||||||
|
|
|
@ -18,6 +18,7 @@ Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
|
||||||
org.eclipse.jgit.annotations;version="[4.11.0,4.12.0)";resolution:=optional,
|
org.eclipse.jgit.annotations;version="[4.11.0,4.12.0)";resolution:=optional,
|
||||||
org.eclipse.jgit.api.errors;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.api.errors;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.attributes;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.attributes;version="[4.11.0,4.12.0)",
|
||||||
|
org.eclipse.jgit.diff;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.errors;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.errors;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.hooks;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.hooks;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.internal.storage.file;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.internal.storage.file;version="[4.11.0,4.12.0)",
|
||||||
|
@ -25,6 +26,7 @@ Import-Package: com.google.gson;version="[2.8.2,3.0.0)",
|
||||||
org.eclipse.jgit.nls;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.nls;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.revwalk;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.revwalk;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.storage.file;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.storage.file;version="[4.11.0,4.12.0)",
|
||||||
|
org.eclipse.jgit.storage.pack;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.transport;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.transport;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.transport.http;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.transport.http;version="[4.11.0,4.12.0)",
|
||||||
org.eclipse.jgit.treewalk;version="[4.11.0,4.12.0)",
|
org.eclipse.jgit.treewalk;version="[4.11.0,4.12.0)",
|
||||||
|
|
|
@ -0,0 +1,140 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.lfs;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
|
import org.eclipse.jgit.attributes.Attribute;
|
||||||
|
import org.eclipse.jgit.hooks.PrePushHook;
|
||||||
|
import org.eclipse.jgit.lfs.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Implementation of {@link LfsFactory}, using built-in (optional) LFS support.
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public class BuiltinLFS extends LfsFactory {
|
||||||
|
|
||||||
|
private BuiltinLFS() {
|
||||||
|
SmudgeFilter.register();
|
||||||
|
CleanFilter.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Activates the built-in LFS support.
|
||||||
|
*/
|
||||||
|
public static void register() {
|
||||||
|
setInstance(new BuiltinLFS());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectLoader applySmudgeFilter(Repository db, ObjectLoader loader,
|
||||||
|
Attribute attribute) throws IOException {
|
||||||
|
if (isEnabled(db) && (attribute == null || isEnabled(db, attribute))) {
|
||||||
|
return LfsBlobFilter.smudgeLfsBlob(db, loader);
|
||||||
|
} else {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public LfsInputStream applyCleanFilter(Repository db, InputStream input,
|
||||||
|
long length, Attribute attribute) throws IOException {
|
||||||
|
if (isEnabled(db, attribute)) {
|
||||||
|
return new LfsInputStream(LfsBlobFilter.cleanLfsBlob(db, input));
|
||||||
|
} else {
|
||||||
|
return new LfsInputStream(input, length);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable PrePushHook getPrePushHook(Repository repo,
|
||||||
|
PrintStream outputStream) {
|
||||||
|
if (isEnabled(repo)) {
|
||||||
|
return new LfsPrePushHook(repo, outputStream);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @return whether LFS is requested for the given repo.
|
||||||
|
*/
|
||||||
|
private boolean isEnabled(Repository db) {
|
||||||
|
if (db == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return db.getConfig().getBoolean(ConfigConstants.CONFIG_FILTER_SECTION,
|
||||||
|
Constants.LFS, ConfigConstants.CONFIG_KEY_USEJGITBUILTIN,
|
||||||
|
false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @param attribute
|
||||||
|
* the attribute to check
|
||||||
|
* @return whether LFS filter is enabled for the given .gitattribute
|
||||||
|
* attribute.
|
||||||
|
*/
|
||||||
|
private boolean isEnabled(Repository db, Attribute attribute) {
|
||||||
|
if (attribute == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return isEnabled(db) && Constants.LFS.equals(attribute.getValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -91,7 +91,7 @@ public FilterCommand create(Repository db, InputStream in,
|
||||||
* Registers this filter by calling
|
* Registers this filter by calling
|
||||||
* {@link FilterCommandRegistry#register(String, FilterCommandFactory)}
|
* {@link FilterCommandRegistry#register(String, FilterCommandFactory)}
|
||||||
*/
|
*/
|
||||||
public final static void register() {
|
static void register() {
|
||||||
FilterCommandRegistry
|
FilterCommandRegistry
|
||||||
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
|
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
|
||||||
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
||||||
|
|
|
@ -0,0 +1,130 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.lfs;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.lfs.lib.AnyLongObjectId;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||||
|
import org.eclipse.jgit.util.TemporaryBuffer.LocalFile;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides transparently either a stream to the blob or a LFS media file if
|
||||||
|
* managed by LFS.
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public class LfsBlobFilter {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* In case the given {@link ObjectLoader} points to a LFS pointer file
|
||||||
|
* replace the loader with one pointing to the LFS media file contents.
|
||||||
|
* Missing LFS files are downloaded on the fly - same logic as the smudge
|
||||||
|
* filter.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* the repo
|
||||||
|
* @param loader
|
||||||
|
* the loader for the blob
|
||||||
|
* @return either the original loader, or a loader for the LFS media file if
|
||||||
|
* managed by LFS. Files are downloaded on demand if required.
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error
|
||||||
|
*/
|
||||||
|
public static ObjectLoader smudgeLfsBlob(Repository db, ObjectLoader loader)
|
||||||
|
throws IOException {
|
||||||
|
if (loader.getSize() > LfsPointer.SIZE_THRESHOLD) {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream is = loader.openStream()) {
|
||||||
|
LfsPointer ptr = LfsPointer.parseLfsPointer(is);
|
||||||
|
if (ptr != null) {
|
||||||
|
Lfs lfs = new Lfs(db);
|
||||||
|
AnyLongObjectId oid = ptr.getOid();
|
||||||
|
Path mediaFile = lfs.getMediaFile(oid);
|
||||||
|
if (!Files.exists(mediaFile)) {
|
||||||
|
SmudgeFilter.downloadLfsResource(lfs, db, ptr);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new LfsBlobLoader(mediaFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Run the LFS clean filter on the given stream and return a stream to the
|
||||||
|
* LFS pointer file buffer. Used when inserting objects.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* the {@link Repository}
|
||||||
|
* @param originalContent
|
||||||
|
* the {@link InputStream} to the original content
|
||||||
|
* @return a {@link TemporaryBuffer} representing the LFS pointer. The
|
||||||
|
* caller is responsible to destroy the buffer.
|
||||||
|
* @throws IOException
|
||||||
|
* in case of any error.
|
||||||
|
*/
|
||||||
|
public static TemporaryBuffer cleanLfsBlob(Repository db,
|
||||||
|
InputStream originalContent) throws IOException {
|
||||||
|
LocalFile buffer = new TemporaryBuffer.LocalFile(null);
|
||||||
|
CleanFilter f = new CleanFilter(db, originalContent, buffer);
|
||||||
|
try {
|
||||||
|
while (f.run() != -1) {
|
||||||
|
// loop as long as f.run() tells there is work to do
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
buffer.destroy();
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,119 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017, Markus Duft <markus.duft@ssi-schaefer.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.lfs;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.file.Path;
|
||||||
|
import java.nio.file.attribute.BasicFileAttributes;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.errors.LargeObjectException;
|
||||||
|
import org.eclipse.jgit.errors.MissingObjectException;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.ObjectStream;
|
||||||
|
import org.eclipse.jgit.storage.pack.PackConfig;
|
||||||
|
import org.eclipse.jgit.util.IO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link ObjectLoader} implementation that reads a media file from the LFS
|
||||||
|
* storage.
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public class LfsBlobLoader extends ObjectLoader {
|
||||||
|
|
||||||
|
private Path mediaFile;
|
||||||
|
|
||||||
|
private BasicFileAttributes attributes;
|
||||||
|
|
||||||
|
private byte[] cached;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a loader for the LFS media file at the given path.
|
||||||
|
*
|
||||||
|
* @param mediaFile
|
||||||
|
* path to the file
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error reading attributes
|
||||||
|
*/
|
||||||
|
public LfsBlobLoader(Path mediaFile) throws IOException {
|
||||||
|
this.mediaFile = mediaFile;
|
||||||
|
this.attributes = Files.readAttributes(mediaFile,
|
||||||
|
BasicFileAttributes.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getType() {
|
||||||
|
return Constants.OBJ_BLOB;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSize() {
|
||||||
|
return attributes.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public byte[] getCachedBytes() throws LargeObjectException {
|
||||||
|
if (getSize() > PackConfig.DEFAULT_BIG_FILE_THRESHOLD) {
|
||||||
|
throw new LargeObjectException();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cached == null) {
|
||||||
|
try {
|
||||||
|
cached = IO.readFully(mediaFile.toFile());
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
throw new LargeObjectException(ioe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cached;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ObjectStream openStream()
|
||||||
|
throws MissingObjectException, IOException {
|
||||||
|
return new ObjectStream.Filter(getType(), getSize(),
|
||||||
|
Files.newInputStream(mediaFile));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -100,9 +100,9 @@ public FilterCommand create(Repository db, InputStream in,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers this filter in JGit by calling
|
* Register this filter in JGit
|
||||||
*/
|
*/
|
||||||
public final static void register() {
|
static void register() {
|
||||||
FilterCommandRegistry
|
FilterCommandRegistry
|
||||||
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
|
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
|
||||||
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
||||||
|
@ -110,8 +110,6 @@ public final static void register() {
|
||||||
FACTORY);
|
FACTORY);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Lfs lfs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for SmudgeFilter.
|
* Constructor for SmudgeFilter.
|
||||||
*
|
*
|
||||||
|
@ -126,13 +124,13 @@ public final static void register() {
|
||||||
public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
super(in, out);
|
super(in, out);
|
||||||
lfs = new Lfs(db);
|
Lfs lfs = new Lfs(db);
|
||||||
LfsPointer res = LfsPointer.parseLfsPointer(in);
|
LfsPointer res = LfsPointer.parseLfsPointer(in);
|
||||||
if (res != null) {
|
if (res != null) {
|
||||||
AnyLongObjectId oid = res.getOid();
|
AnyLongObjectId oid = res.getOid();
|
||||||
Path mediaFile = lfs.getMediaFile(oid);
|
Path mediaFile = lfs.getMediaFile(oid);
|
||||||
if (!Files.exists(mediaFile)) {
|
if (!Files.exists(mediaFile)) {
|
||||||
downloadLfsResource(db, res);
|
downloadLfsResource(lfs, db, res);
|
||||||
}
|
}
|
||||||
this.in = Files.newInputStream(mediaFile);
|
this.in = Files.newInputStream(mediaFile);
|
||||||
}
|
}
|
||||||
|
@ -141,6 +139,8 @@ public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
||||||
/**
|
/**
|
||||||
* Download content which is hosted on a LFS server
|
* Download content which is hosted on a LFS server
|
||||||
*
|
*
|
||||||
|
* @param lfs
|
||||||
|
* local {@link Lfs} storage.
|
||||||
* @param db
|
* @param db
|
||||||
* the repository to work with
|
* the repository to work with
|
||||||
* @param res
|
* @param res
|
||||||
|
@ -148,9 +148,8 @@ public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
||||||
* @return the paths of all mediafiles which have been downloaded
|
* @return the paths of all mediafiles which have been downloaded
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private Collection<Path> downloadLfsResource(Repository db,
|
public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
|
||||||
LfsPointer... res)
|
LfsPointer... res) throws IOException {
|
||||||
throws IOException {
|
|
||||||
Collection<Path> downloadedPaths = new ArrayList<>();
|
Collection<Path> downloadedPaths = new ArrayList<>();
|
||||||
Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
|
Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
|
||||||
for (LfsPointer p : res) {
|
for (LfsPointer p : res) {
|
||||||
|
|
|
@ -56,7 +56,7 @@
|
||||||
@SuppressWarnings("nls")
|
@SuppressWarnings("nls")
|
||||||
public final class Constants {
|
public final class Constants {
|
||||||
/**
|
/**
|
||||||
* lfs folder
|
* lfs folder/section/filter name
|
||||||
*
|
*
|
||||||
* @since 4.6
|
* @since 4.6
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -65,8 +65,7 @@
|
||||||
import org.eclipse.jgit.awtui.AwtAuthenticator;
|
import org.eclipse.jgit.awtui.AwtAuthenticator;
|
||||||
import org.eclipse.jgit.awtui.AwtCredentialsProvider;
|
import org.eclipse.jgit.awtui.AwtCredentialsProvider;
|
||||||
import org.eclipse.jgit.errors.TransportException;
|
import org.eclipse.jgit.errors.TransportException;
|
||||||
import org.eclipse.jgit.lfs.CleanFilter;
|
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.RepositoryBuilder;
|
import org.eclipse.jgit.lib.RepositoryBuilder;
|
||||||
import org.eclipse.jgit.pgm.internal.CLIText;
|
import org.eclipse.jgit.pgm.internal.CLIText;
|
||||||
|
@ -111,8 +110,7 @@ public class Main {
|
||||||
*/
|
*/
|
||||||
public Main() {
|
public Main() {
|
||||||
HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
|
HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
|
||||||
CleanFilter.register();
|
BuiltinLFS.register();
|
||||||
SmudgeFilter.register();
|
|
||||||
gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||||
private final ThreadFactory baseFactory = Executors
|
private final ThreadFactory baseFactory = Executors
|
||||||
.defaultThreadFactory();
|
.defaultThreadFactory();
|
||||||
|
|
|
@ -63,8 +63,7 @@
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.lfs.CleanFilter;
|
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
|
@ -92,8 +91,7 @@ public class AddCommandTest extends RepositoryTestCase {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
CleanFilter.register();
|
BuiltinLFS.register();
|
||||||
SmudgeFilter.register();
|
|
||||||
super.setUp();
|
super.setUp();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -74,8 +74,7 @@
|
||||||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||||
import org.eclipse.jgit.lfs.CleanFilter;
|
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
import org.eclipse.jgit.lib.Constants;
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.Ref;
|
import org.eclipse.jgit.lib.Ref;
|
||||||
|
@ -102,8 +101,7 @@ public class CheckoutCommandTest extends RepositoryTestCase {
|
||||||
@Override
|
@Override
|
||||||
@Before
|
@Before
|
||||||
public void setUp() throws Exception {
|
public void setUp() throws Exception {
|
||||||
CleanFilter.register();
|
BuiltinLFS.register();
|
||||||
SmudgeFilter.register();
|
|
||||||
super.setUp();
|
super.setUp();
|
||||||
git = new Git(db);
|
git = new Git(db);
|
||||||
// commit something
|
// commit something
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
<component id="org.eclipse.jgit" version="2">
|
<component id="org.eclipse.jgit" version="2">
|
||||||
|
<resource path="src/org/eclipse/jgit/diff/DiffEntry.java" type="org.eclipse.jgit.diff.DiffEntry">
|
||||||
|
<filter id="336658481">
|
||||||
|
<message_arguments>
|
||||||
|
<message_argument value="org.eclipse.jgit.diff.DiffEntry"/>
|
||||||
|
<message_argument value="diffAttribute"/>
|
||||||
|
</message_arguments>
|
||||||
|
</filter>
|
||||||
|
</resource>
|
||||||
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
|
<resource path="src/org/eclipse/jgit/lib/ConfigConstants.java" type="org.eclipse.jgit.lib.ConfigConstants">
|
||||||
<filter id="336658481">
|
<filter id="336658481">
|
||||||
<message_arguments>
|
<message_arguments>
|
||||||
|
|
|
@ -462,6 +462,7 @@ noHMACsupport=No {0} support: {1}
|
||||||
noMergeBase=No merge base could be determined. Reason={0}. {1}
|
noMergeBase=No merge base could be determined. Reason={0}. {1}
|
||||||
noMergeHeadSpecified=No merge head specified
|
noMergeHeadSpecified=No merge head specified
|
||||||
nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos
|
nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos
|
||||||
|
noPathAttributesFound=No Attributes found for {0}.
|
||||||
noSuchRef=no such ref
|
noSuchRef=no such ref
|
||||||
noSuchSubmodule=no such submodule {0}
|
noSuchSubmodule=no such submodule {0}
|
||||||
notABoolean=Not a boolean: {0}
|
notABoolean=Not a boolean: {0}
|
||||||
|
|
|
@ -303,7 +303,8 @@ public BlameGenerator push(String description, RawText contents)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
if (description == null)
|
if (description == null)
|
||||||
description = JGitText.get().blameNotCommittedYet;
|
description = JGitText.get().blameNotCommittedYet;
|
||||||
BlobCandidate c = new BlobCandidate(description, resultPath);
|
BlobCandidate c = new BlobCandidate(getRepository(), description,
|
||||||
|
resultPath);
|
||||||
c.sourceText = contents;
|
c.sourceText = contents;
|
||||||
c.regionList = new Region(0, 0, contents.size());
|
c.regionList = new Region(0, 0, contents.size());
|
||||||
remaining = contents.size();
|
remaining = contents.size();
|
||||||
|
@ -333,7 +334,8 @@ public BlameGenerator push(String description, AnyObjectId id)
|
||||||
if (ldr.getType() == OBJ_BLOB) {
|
if (ldr.getType() == OBJ_BLOB) {
|
||||||
if (description == null)
|
if (description == null)
|
||||||
description = JGitText.get().blameNotCommittedYet;
|
description = JGitText.get().blameNotCommittedYet;
|
||||||
BlobCandidate c = new BlobCandidate(description, resultPath);
|
BlobCandidate c = new BlobCandidate(getRepository(), description,
|
||||||
|
resultPath);
|
||||||
c.sourceBlob = id.toObjectId();
|
c.sourceBlob = id.toObjectId();
|
||||||
c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
|
c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
|
||||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||||
|
@ -346,7 +348,7 @@ public BlameGenerator push(String description, AnyObjectId id)
|
||||||
if (!find(commit, resultPath))
|
if (!find(commit, resultPath))
|
||||||
return this;
|
return this;
|
||||||
|
|
||||||
Candidate c = new Candidate(commit, resultPath);
|
Candidate c = new Candidate(getRepository(), commit, resultPath);
|
||||||
c.sourceBlob = idBuf.toObjectId();
|
c.sourceBlob = idBuf.toObjectId();
|
||||||
c.loadText(reader);
|
c.loadText(reader);
|
||||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||||
|
@ -430,7 +432,8 @@ public BlameGenerator reverse(AnyObjectId start,
|
||||||
// just pump the queue
|
// just pump the queue
|
||||||
}
|
}
|
||||||
|
|
||||||
ReverseCandidate c = new ReverseCandidate(result, resultPath);
|
ReverseCandidate c = new ReverseCandidate(getRepository(), result,
|
||||||
|
resultPath);
|
||||||
c.sourceBlob = idBuf.toObjectId();
|
c.sourceBlob = idBuf.toObjectId();
|
||||||
c.loadText(reader);
|
c.loadText(reader);
|
||||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||||
|
@ -637,7 +640,8 @@ private boolean processOne(Candidate n) throws IOException {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
Candidate next = n.create(parent, PathFilter.create(r.getOldPath()));
|
Candidate next = n.create(getRepository(), parent,
|
||||||
|
PathFilter.create(r.getOldPath()));
|
||||||
next.sourceBlob = r.getOldId().toObjectId();
|
next.sourceBlob = r.getOldId().toObjectId();
|
||||||
next.renameScore = r.getScore();
|
next.renameScore = r.getScore();
|
||||||
next.loadText(reader);
|
next.loadText(reader);
|
||||||
|
@ -653,7 +657,7 @@ private boolean blameEntireRegionOnParent(Candidate n, RevCommit parent) {
|
||||||
|
|
||||||
private boolean splitBlameWithParent(Candidate n, RevCommit parent)
|
private boolean splitBlameWithParent(Candidate n, RevCommit parent)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Candidate next = n.create(parent, n.sourcePath);
|
Candidate next = n.create(getRepository(), parent, n.sourcePath);
|
||||||
next.sourceBlob = idBuf.toObjectId();
|
next.sourceBlob = idBuf.toObjectId();
|
||||||
next.loadText(reader);
|
next.loadText(reader);
|
||||||
return split(next, n);
|
return split(next, n);
|
||||||
|
@ -740,12 +744,12 @@ private boolean processMerge(Candidate n) throws IOException {
|
||||||
|
|
||||||
Candidate p;
|
Candidate p;
|
||||||
if (renames != null && renames[pIdx] != null) {
|
if (renames != null && renames[pIdx] != null) {
|
||||||
p = n.create(parent,
|
p = n.create(getRepository(), parent,
|
||||||
PathFilter.create(renames[pIdx].getOldPath()));
|
PathFilter.create(renames[pIdx].getOldPath()));
|
||||||
p.renameScore = renames[pIdx].getScore();
|
p.renameScore = renames[pIdx].getScore();
|
||||||
p.sourceBlob = renames[pIdx].getOldId().toObjectId();
|
p.sourceBlob = renames[pIdx].getOldId().toObjectId();
|
||||||
} else if (ids != null && ids[pIdx] != null) {
|
} else if (ids != null && ids[pIdx] != null) {
|
||||||
p = n.create(parent, n.sourcePath);
|
p = n.create(getRepository(), parent, n.sourcePath);
|
||||||
p.sourceBlob = ids[pIdx];
|
p.sourceBlob = ids[pIdx];
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
|
|
|
@ -55,10 +55,12 @@
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
import org.eclipse.jgit.lib.ObjectReader;
|
||||||
import org.eclipse.jgit.lib.PersonIdent;
|
import org.eclipse.jgit.lib.PersonIdent;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevCommit;
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
import org.eclipse.jgit.revwalk.RevFlag;
|
import org.eclipse.jgit.revwalk.RevFlag;
|
||||||
import org.eclipse.jgit.revwalk.RevWalk;
|
import org.eclipse.jgit.revwalk.RevWalk;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A source that may have supplied some (or all) of the result file.
|
* A source that may have supplied some (or all) of the result file.
|
||||||
|
@ -109,7 +111,11 @@ class Candidate {
|
||||||
*/
|
*/
|
||||||
int renameScore;
|
int renameScore;
|
||||||
|
|
||||||
Candidate(RevCommit commit, PathFilter path) {
|
/** repository used for LFS blob handling */
|
||||||
|
private Repository sourceRepository;
|
||||||
|
|
||||||
|
Candidate(Repository repo, RevCommit commit, PathFilter path) {
|
||||||
|
sourceRepository = repo;
|
||||||
sourceCommit = commit;
|
sourceCommit = commit;
|
||||||
sourcePath = path;
|
sourcePath = path;
|
||||||
}
|
}
|
||||||
|
@ -150,12 +156,12 @@ PersonIdent getAuthor() {
|
||||||
return sourceCommit.getAuthorIdent();
|
return sourceCommit.getAuthorIdent();
|
||||||
}
|
}
|
||||||
|
|
||||||
Candidate create(RevCommit commit, PathFilter path) {
|
Candidate create(Repository repo, RevCommit commit, PathFilter path) {
|
||||||
return new Candidate(commit, path);
|
return new Candidate(repo, commit, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
Candidate copy(RevCommit commit) {
|
Candidate copy(RevCommit commit) {
|
||||||
Candidate r = create(commit, sourcePath);
|
Candidate r = create(sourceRepository, commit, sourcePath);
|
||||||
r.sourceBlob = sourceBlob;
|
r.sourceBlob = sourceBlob;
|
||||||
r.sourceText = sourceText;
|
r.sourceText = sourceText;
|
||||||
r.regionList = regionList;
|
r.regionList = regionList;
|
||||||
|
@ -164,7 +170,11 @@ Candidate copy(RevCommit commit) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void loadText(ObjectReader reader) throws IOException {
|
void loadText(ObjectReader reader) throws IOException {
|
||||||
ObjectLoader ldr = reader.open(sourceBlob, Constants.OBJ_BLOB);
|
ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(sourceRepository,
|
||||||
|
reader.open(sourceBlob, Constants.OBJ_BLOB),
|
||||||
|
LfsFactory.getAttributesForPath(sourceRepository,
|
||||||
|
sourcePath.getPath(), sourceCommit)
|
||||||
|
.get(Constants.ATTR_DIFF));
|
||||||
sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
|
sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -349,8 +359,9 @@ public String toString() {
|
||||||
* children pointers, allowing reverse navigation of history.
|
* children pointers, allowing reverse navigation of history.
|
||||||
*/
|
*/
|
||||||
static final class ReverseCandidate extends Candidate {
|
static final class ReverseCandidate extends Candidate {
|
||||||
ReverseCandidate(ReverseCommit commit, PathFilter path) {
|
ReverseCandidate(Repository repo, ReverseCommit commit,
|
||||||
super(commit, path);
|
PathFilter path) {
|
||||||
|
super(repo, commit, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -370,8 +381,8 @@ int getTime() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
Candidate create(RevCommit commit, PathFilter path) {
|
Candidate create(Repository repo, RevCommit commit, PathFilter path) {
|
||||||
return new ReverseCandidate((ReverseCommit) commit, path);
|
return new ReverseCandidate(repo, (ReverseCommit) commit, path);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -400,8 +411,8 @@ static final class BlobCandidate extends Candidate {
|
||||||
/** Author name to refer to this blob with. */
|
/** Author name to refer to this blob with. */
|
||||||
String description;
|
String description;
|
||||||
|
|
||||||
BlobCandidate(String name, PathFilter path) {
|
BlobCandidate(Repository repo, String name, PathFilter path) {
|
||||||
super(null, path);
|
super(repo, null, path);
|
||||||
description = name;
|
description = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,9 +48,11 @@
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.attributes.Attribute;
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||||
import org.eclipse.jgit.lib.AnyObjectId;
|
import org.eclipse.jgit.lib.AnyObjectId;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.MutableObjectId;
|
import org.eclipse.jgit.lib.MutableObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
|
@ -196,6 +198,11 @@ public static List<DiffEntry> scan(TreeWalk walk, boolean includeTrees,
|
||||||
entry.newMode = walk.getFileMode(1);
|
entry.newMode = walk.getFileMode(1);
|
||||||
entry.newPath = entry.oldPath = walk.getPathString();
|
entry.newPath = entry.oldPath = walk.getPathString();
|
||||||
|
|
||||||
|
if (walk.getAttributesNodeProvider() != null) {
|
||||||
|
entry.diffAttribute = walk.getAttributes()
|
||||||
|
.get(Constants.ATTR_DIFF);
|
||||||
|
}
|
||||||
|
|
||||||
if (treeFilterMarker != null)
|
if (treeFilterMarker != null)
|
||||||
entry.treeFilterMarks = treeFilterMarker.getMarks(walk);
|
entry.treeFilterMarks = treeFilterMarker.getMarks(walk);
|
||||||
|
|
||||||
|
@ -282,6 +289,7 @@ static List<DiffEntry> breakModify(DiffEntry entry) {
|
||||||
del.newMode = FileMode.MISSING;
|
del.newMode = FileMode.MISSING;
|
||||||
del.newPath = DiffEntry.DEV_NULL;
|
del.newPath = DiffEntry.DEV_NULL;
|
||||||
del.changeType = ChangeType.DELETE;
|
del.changeType = ChangeType.DELETE;
|
||||||
|
del.diffAttribute = entry.diffAttribute;
|
||||||
|
|
||||||
DiffEntry add = new DiffEntry();
|
DiffEntry add = new DiffEntry();
|
||||||
add.oldId = A_ZERO;
|
add.oldId = A_ZERO;
|
||||||
|
@ -292,6 +300,7 @@ static List<DiffEntry> breakModify(DiffEntry entry) {
|
||||||
add.newMode = entry.getNewMode();
|
add.newMode = entry.getNewMode();
|
||||||
add.newPath = entry.getNewPath();
|
add.newPath = entry.getNewPath();
|
||||||
add.changeType = ChangeType.ADD;
|
add.changeType = ChangeType.ADD;
|
||||||
|
add.diffAttribute = entry.diffAttribute;
|
||||||
return Arrays.asList(del, add);
|
return Arrays.asList(del, add);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -306,6 +315,7 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
|
||||||
r.newId = dst.newId;
|
r.newId = dst.newId;
|
||||||
r.newMode = dst.newMode;
|
r.newMode = dst.newMode;
|
||||||
r.newPath = dst.newPath;
|
r.newPath = dst.newPath;
|
||||||
|
r.diffAttribute = dst.diffAttribute;
|
||||||
|
|
||||||
r.changeType = changeType;
|
r.changeType = changeType;
|
||||||
r.score = score;
|
r.score = score;
|
||||||
|
@ -321,6 +331,13 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
|
||||||
/** File name of the new (post-image). */
|
/** File name of the new (post-image). */
|
||||||
protected String newPath;
|
protected String newPath;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* diff filter attribute
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
protected Attribute diffAttribute;
|
||||||
|
|
||||||
/** Old mode of the file, if described by the patch, else null. */
|
/** Old mode of the file, if described by the patch, else null. */
|
||||||
protected FileMode oldMode;
|
protected FileMode oldMode;
|
||||||
|
|
||||||
|
@ -394,6 +411,14 @@ public String getPath(Side side) {
|
||||||
return side == Side.OLD ? getOldPath() : getNewPath();
|
return side == Side.OLD ? getOldPath() : getNewPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the {@link Attribute} determining filters to be applied.
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public Attribute getDiffAttribute() {
|
||||||
|
return diffAttribute;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the old file mode
|
* Get the old file mode
|
||||||
*
|
*
|
||||||
|
|
|
@ -98,6 +98,7 @@
|
||||||
import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
|
import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
|
||||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
import org.eclipse.jgit.util.QuotedString;
|
import org.eclipse.jgit.util.QuotedString;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -141,6 +142,8 @@ public class DiffFormatter implements AutoCloseable {
|
||||||
|
|
||||||
private ContentSource.Pair source;
|
private ContentSource.Pair source;
|
||||||
|
|
||||||
|
private Repository repository;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new formatter with a default level of context.
|
* Create a new formatter with a default level of context.
|
||||||
*
|
*
|
||||||
|
@ -172,6 +175,7 @@ protected OutputStream getOutputStream() {
|
||||||
* source repository holding referenced objects.
|
* source repository holding referenced objects.
|
||||||
*/
|
*/
|
||||||
public void setRepository(Repository repository) {
|
public void setRepository(Repository repository) {
|
||||||
|
this.repository = repository;
|
||||||
setReader(repository.newObjectReader(), repository.getConfig(), true);
|
setReader(repository.newObjectReader(), repository.getConfig(), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1057,7 +1061,8 @@ private RawText open(DiffEntry.Side side, DiffEntry entry)
|
||||||
throw new AmbiguousObjectException(id, ids);
|
throw new AmbiguousObjectException(id, ids);
|
||||||
}
|
}
|
||||||
|
|
||||||
ObjectLoader ldr = source.open(side, entry);
|
ObjectLoader ldr = LfsFactory.getInstance().applySmudgeFilter(repository,
|
||||||
|
source.open(side, entry), entry.getDiffAttribute());
|
||||||
return RawText.load(ldr, binaryFileThreshold);
|
return RawText.load(ldr, binaryFileThreshold);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -110,6 +110,14 @@ public RawText(File file) throws IOException {
|
||||||
this(IO.readFully(file));
|
this(IO.readFully(file));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the raw, unprocessed content read.
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public byte[] getRawContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
/** @return total number of items in the sequence. */
|
/** @return total number of items in the sequence. */
|
||||||
/** {@inheritDoc} */
|
/** {@inheritDoc} */
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -43,13 +43,11 @@
|
||||||
package org.eclipse.jgit.hooks;
|
package org.eclipse.jgit.hooks;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.lang.reflect.Constructor;
|
|
||||||
import java.text.MessageFormat;
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
import org.eclipse.jgit.internal.JGitText;
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.lib.StoredConfig;
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory class for instantiating supported hooks.
|
* Factory class for instantiating supported hooks.
|
||||||
|
@ -112,28 +110,16 @@ public static CommitMsgHook commitMsg(Repository repo,
|
||||||
* @since 4.2
|
* @since 4.2
|
||||||
*/
|
*/
|
||||||
public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
|
public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
|
||||||
PrePushHook lfsHook = null;
|
if (LfsFactory.getInstance().isAvailable()) {
|
||||||
try {
|
PrePushHook hook = LfsFactory.getInstance().getPrePushHook(repo,
|
||||||
StoredConfig cfg = repo.getConfig();
|
outputStream);
|
||||||
if (cfg.getBoolean(ConfigConstants.CONFIG_FILTER_SECTION, "lfs", //$NON-NLS-1$
|
if (hook != null) {
|
||||||
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false)) {
|
if (hook.isNativeHookPresent()) {
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Class<? extends PrePushHook> cls = (Class<? extends PrePushHook>) Class
|
|
||||||
.forName("org.eclipse.jgit.lfs.LfsPrePushHook"); //$NON-NLS-1$
|
|
||||||
Constructor<? extends PrePushHook> constructor = cls
|
|
||||||
.getConstructor(Repository.class, PrintStream.class);
|
|
||||||
|
|
||||||
lfsHook = constructor.newInstance(repo, outputStream);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// no problem :) no LFS support present
|
|
||||||
}
|
|
||||||
if (lfsHook != null) {
|
|
||||||
if (lfsHook.isNativeHookPresent()) {
|
|
||||||
throw new IllegalStateException(MessageFormat
|
throw new IllegalStateException(MessageFormat
|
||||||
.format(JGitText.get().lfsHookConflict, repo));
|
.format(JGitText.get().lfsHookConflict, repo));
|
||||||
}
|
}
|
||||||
return lfsHook;
|
return hook;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return new PrePushHook(repo, outputStream);
|
return new PrePushHook(repo, outputStream);
|
||||||
}
|
}
|
||||||
|
|
|
@ -523,6 +523,7 @@ public static JGitText get() {
|
||||||
/***/ public String noMergeBase;
|
/***/ public String noMergeBase;
|
||||||
/***/ public String noMergeHeadSpecified;
|
/***/ public String noMergeHeadSpecified;
|
||||||
/***/ public String nonBareLinkFilesNotSupported;
|
/***/ public String nonBareLinkFilesNotSupported;
|
||||||
|
/***/ public String noPathAttributesFound;
|
||||||
/***/ public String noSuchRef;
|
/***/ public String noSuchRef;
|
||||||
/***/ public String noSuchSubmodule;
|
/***/ public String noSuchSubmodule;
|
||||||
/***/ public String notABoolean;
|
/***/ public String notABoolean;
|
||||||
|
|
|
@ -437,6 +437,13 @@ public final class Constants {
|
||||||
*/
|
*/
|
||||||
public static final String ATTR_MERGE = "merge"; //$NON-NLS-1$
|
public static final String ATTR_MERGE = "merge"; //$NON-NLS-1$
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Diff attribute.
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public static final String ATTR_DIFF = "diff"; //$NON-NLS-1$
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Binary value for custom merger.
|
* Binary value for custom merger.
|
||||||
*
|
*
|
||||||
|
|
|
@ -87,12 +87,12 @@
|
||||||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||||
import org.eclipse.jgit.lib.Config;
|
import org.eclipse.jgit.lib.Config;
|
||||||
import org.eclipse.jgit.lib.ConfigConstants;
|
import org.eclipse.jgit.lib.ConfigConstants;
|
||||||
|
import org.eclipse.jgit.lib.Constants;
|
||||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||||
import org.eclipse.jgit.lib.FileMode;
|
import org.eclipse.jgit.lib.FileMode;
|
||||||
import org.eclipse.jgit.lib.ObjectId;
|
import org.eclipse.jgit.lib.ObjectId;
|
||||||
import org.eclipse.jgit.lib.ObjectInserter;
|
import org.eclipse.jgit.lib.ObjectInserter;
|
||||||
import org.eclipse.jgit.lib.ObjectLoader;
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
import org.eclipse.jgit.lib.ObjectReader;
|
|
||||||
import org.eclipse.jgit.lib.Repository;
|
import org.eclipse.jgit.lib.Repository;
|
||||||
import org.eclipse.jgit.revwalk.RevTree;
|
import org.eclipse.jgit.revwalk.RevTree;
|
||||||
import org.eclipse.jgit.storage.pack.PackConfig;
|
import org.eclipse.jgit.storage.pack.PackConfig;
|
||||||
|
@ -106,6 +106,8 @@
|
||||||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||||
import org.eclipse.jgit.util.FS;
|
import org.eclipse.jgit.util.FS;
|
||||||
|
import org.eclipse.jgit.util.LfsFactory;
|
||||||
|
import org.eclipse.jgit.util.LfsFactory.LfsInputStream;
|
||||||
import org.eclipse.jgit.util.TemporaryBuffer;
|
import org.eclipse.jgit.util.TemporaryBuffer;
|
||||||
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
|
import org.eclipse.jgit.util.io.EolStreamTypeUtil;
|
||||||
|
|
||||||
|
@ -769,11 +771,12 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MergeResult<RawText> result = contentMerge(base, ours, theirs);
|
MergeResult<RawText> result = contentMerge(base, ours, theirs,
|
||||||
|
attributes);
|
||||||
if (ignoreConflicts) {
|
if (ignoreConflicts) {
|
||||||
result.setContainsConflicts(false);
|
result.setContainsConflicts(false);
|
||||||
}
|
}
|
||||||
updateIndex(base, ours, theirs, result);
|
updateIndex(base, ours, theirs, result, attributes);
|
||||||
if (result.containsConflicts() && !ignoreConflicts)
|
if (result.containsConflicts() && !ignoreConflicts)
|
||||||
unmergedPaths.add(tw.getPathString());
|
unmergedPaths.add(tw.getPathString());
|
||||||
modifiedFiles.add(tw.getPathString());
|
modifiedFiles.add(tw.getPathString());
|
||||||
|
@ -781,7 +784,8 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
// 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
|
||||||
.idEqual(T_BASE, T_THEIRS)))) {
|
.idEqual(T_BASE, T_THEIRS)))) {
|
||||||
MergeResult<RawText> result = contentMerge(base, ours, theirs);
|
MergeResult<RawText> result = contentMerge(base, ours, theirs,
|
||||||
|
attributes);
|
||||||
|
|
||||||
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
|
add(tw.getRawPath(), base, DirCacheEntry.STAGE_1, 0, 0);
|
||||||
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
|
add(tw.getRawPath(), ours, DirCacheEntry.STAGE_2, 0, 0);
|
||||||
|
@ -816,12 +820,14 @@ protected boolean processEntry(CanonicalTreeParser base,
|
||||||
* @param base
|
* @param base
|
||||||
* @param ours
|
* @param ours
|
||||||
* @param theirs
|
* @param theirs
|
||||||
|
* @param attributes
|
||||||
*
|
*
|
||||||
* @return the result of the content merge
|
* @return the result of the content merge
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||||
CanonicalTreeParser ours, CanonicalTreeParser theirs)
|
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||||
|
Attributes attributes)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
RawText baseText;
|
RawText baseText;
|
||||||
RawText ourText;
|
RawText ourText;
|
||||||
|
@ -829,11 +835,11 @@ private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||||
|
|
||||||
try {
|
try {
|
||||||
baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
|
baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
|
||||||
base.getEntryObjectId(), reader);
|
base.getEntryObjectId(), attributes);
|
||||||
ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
|
ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
|
||||||
ours.getEntryObjectId(), reader);
|
ours.getEntryObjectId(), attributes);
|
||||||
theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
|
theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
|
||||||
theirs.getEntryObjectId(), reader);
|
theirs.getEntryObjectId(), attributes);
|
||||||
} catch (BinaryBlobException e) {
|
} catch (BinaryBlobException e) {
|
||||||
MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList());
|
MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList());
|
||||||
r.setContainsConflicts(true);
|
r.setContainsConflicts(true);
|
||||||
|
@ -897,17 +903,20 @@ private boolean isWorktreeDirty(WorkingTreeIterator work,
|
||||||
* @param ours
|
* @param ours
|
||||||
* @param theirs
|
* @param theirs
|
||||||
* @param result
|
* @param result
|
||||||
|
* @param attributes
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private void updateIndex(CanonicalTreeParser base,
|
private void updateIndex(CanonicalTreeParser base,
|
||||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||||
MergeResult<RawText> result) throws FileNotFoundException,
|
MergeResult<RawText> result, Attributes attributes)
|
||||||
|
throws FileNotFoundException,
|
||||||
IOException {
|
IOException {
|
||||||
TemporaryBuffer rawMerged = null;
|
TemporaryBuffer rawMerged = null;
|
||||||
try {
|
try {
|
||||||
rawMerged = doMerge(result);
|
rawMerged = doMerge(result);
|
||||||
File mergedFile = inCore ? null : writeMergedFile(rawMerged);
|
File mergedFile = inCore ? null
|
||||||
|
: writeMergedFile(rawMerged, attributes);
|
||||||
if (result.containsConflicts()) {
|
if (result.containsConflicts()) {
|
||||||
// A conflict occurred, the file will contain conflict markers
|
// A conflict occurred, the file will contain conflict markers
|
||||||
// the index will be populated with the three stages and the
|
// the index will be populated with the three stages and the
|
||||||
|
@ -934,7 +943,7 @@ private void updateIndex(CanonicalTreeParser base,
|
||||||
nonNullRepo().getFS().lastModified(mergedFile));
|
nonNullRepo().getFS().lastModified(mergedFile));
|
||||||
dce.setLength((int) mergedFile.length());
|
dce.setLength((int) mergedFile.length());
|
||||||
}
|
}
|
||||||
dce.setObjectId(insertMergeResult(rawMerged));
|
dce.setObjectId(insertMergeResult(rawMerged, attributes));
|
||||||
builder.add(dce);
|
builder.add(dce);
|
||||||
} finally {
|
} finally {
|
||||||
if (rawMerged != null) {
|
if (rawMerged != null) {
|
||||||
|
@ -948,11 +957,14 @@ private void updateIndex(CanonicalTreeParser base,
|
||||||
*
|
*
|
||||||
* @param rawMerged
|
* @param rawMerged
|
||||||
* the raw merged content
|
* the raw merged content
|
||||||
|
* @param attributes
|
||||||
|
* the files .gitattributes entries
|
||||||
* @return the working tree file to which the merged content was written.
|
* @return the working tree file to which the merged content was written.
|
||||||
* @throws FileNotFoundException
|
* @throws FileNotFoundException
|
||||||
* @throws IOException
|
* @throws IOException
|
||||||
*/
|
*/
|
||||||
private File writeMergedFile(TemporaryBuffer rawMerged)
|
private File writeMergedFile(TemporaryBuffer rawMerged,
|
||||||
|
Attributes attributes)
|
||||||
throws FileNotFoundException, IOException {
|
throws FileNotFoundException, IOException {
|
||||||
File workTree = nonNullRepo().getWorkTree();
|
File workTree = nonNullRepo().getWorkTree();
|
||||||
FS fs = nonNullRepo().getFS();
|
FS fs = nonNullRepo().getFS();
|
||||||
|
@ -963,7 +975,7 @@ private File writeMergedFile(TemporaryBuffer rawMerged)
|
||||||
}
|
}
|
||||||
EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
|
EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
|
||||||
OperationType.CHECKOUT_OP, workingTreeOptions,
|
OperationType.CHECKOUT_OP, workingTreeOptions,
|
||||||
tw.getAttributes());
|
attributes);
|
||||||
try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
|
try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
|
||||||
new BufferedOutputStream(new FileOutputStream(of)),
|
new BufferedOutputStream(new FileOutputStream(of)),
|
||||||
streamType)) {
|
streamType)) {
|
||||||
|
@ -987,9 +999,13 @@ private TemporaryBuffer doMerge(MergeResult<RawText> result)
|
||||||
return buf;
|
return buf;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ObjectId insertMergeResult(TemporaryBuffer buf) throws IOException {
|
private ObjectId insertMergeResult(TemporaryBuffer buf,
|
||||||
try (InputStream in = buf.openInputStream()) {
|
Attributes attributes) throws IOException {
|
||||||
return getObjectInserter().insert(OBJ_BLOB, buf.length(), in);
|
InputStream in = buf.openInputStream();
|
||||||
|
try (LfsInputStream is = LfsFactory.getInstance().applyCleanFilter(
|
||||||
|
getRepository(), in,
|
||||||
|
buf.length(), attributes.get(Constants.ATTR_MERGE))) {
|
||||||
|
return getObjectInserter().insert(OBJ_BLOB, is.getLength(), is);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1021,12 +1037,15 @@ private int mergeFileModes(int modeB, int modeO, int modeT) {
|
||||||
return FileMode.MISSING.getBits();
|
return FileMode.MISSING.getBits();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RawText getRawText(ObjectId id, ObjectReader reader)
|
private RawText getRawText(ObjectId id,
|
||||||
|
Attributes attributes)
|
||||||
throws IOException, BinaryBlobException {
|
throws IOException, BinaryBlobException {
|
||||||
if (id.equals(ObjectId.zeroId()))
|
if (id.equals(ObjectId.zeroId()))
|
||||||
return new RawText(new byte[] {});
|
return new RawText(new byte[] {});
|
||||||
|
|
||||||
ObjectLoader loader = reader.open(id, OBJ_BLOB);
|
ObjectLoader loader = LfsFactory.getInstance().applySmudgeFilter(
|
||||||
|
getRepository(), reader.open(id, OBJ_BLOB),
|
||||||
|
attributes.get(Constants.ATTR_MERGE));
|
||||||
int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
|
int threshold = PackConfig.DEFAULT_BIG_FILE_THRESHOLD;
|
||||||
return RawText.load(loader, threshold);
|
return RawText.load(loader, threshold);
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,284 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2018, Markus Duft <markus.duft@ssi-schaefer.com>
|
||||||
|
* and other copyright owners as documented in the project's IP log.
|
||||||
|
*
|
||||||
|
* This program and the accompanying materials are made available
|
||||||
|
* under the terms of the Eclipse Distribution License v1.0 which
|
||||||
|
* accompanies this distribution, is reproduced below, and is
|
||||||
|
* available at http://www.eclipse.org/org/documents/edl-v10.php
|
||||||
|
*
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or
|
||||||
|
* without modification, are permitted provided that the following
|
||||||
|
* conditions are met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above
|
||||||
|
* copyright notice, this list of conditions and the following
|
||||||
|
* disclaimer in the documentation and/or other materials provided
|
||||||
|
* with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of the Eclipse Foundation, Inc. nor the
|
||||||
|
* names of its contributors may be used to endorse or promote
|
||||||
|
* products derived from this software without specific prior
|
||||||
|
* written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
|
||||||
|
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
|
||||||
|
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
|
||||||
|
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
|
||||||
|
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
|
||||||
|
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||||
|
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
||||||
|
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
|
||||||
|
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||||
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
|
||||||
|
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
package org.eclipse.jgit.util;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.PrintStream;
|
||||||
|
import java.text.MessageFormat;
|
||||||
|
|
||||||
|
import org.eclipse.jgit.annotations.Nullable;
|
||||||
|
import org.eclipse.jgit.attributes.Attribute;
|
||||||
|
import org.eclipse.jgit.attributes.Attributes;
|
||||||
|
import org.eclipse.jgit.hooks.PrePushHook;
|
||||||
|
import org.eclipse.jgit.internal.JGitText;
|
||||||
|
import org.eclipse.jgit.lib.ObjectLoader;
|
||||||
|
import org.eclipse.jgit.lib.Repository;
|
||||||
|
import org.eclipse.jgit.revwalk.RevCommit;
|
||||||
|
import org.eclipse.jgit.treewalk.FileTreeIterator;
|
||||||
|
import org.eclipse.jgit.treewalk.TreeWalk;
|
||||||
|
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an optionally present LFS support implementation
|
||||||
|
*
|
||||||
|
* @since 4.11
|
||||||
|
*/
|
||||||
|
public class LfsFactory {
|
||||||
|
|
||||||
|
private static LfsFactory instance = new LfsFactory();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
protected LfsFactory() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the current LFS implementation
|
||||||
|
*/
|
||||||
|
public static LfsFactory getInstance() {
|
||||||
|
return instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param instance
|
||||||
|
* register a {@link LfsFactory} instance as the
|
||||||
|
* {@link LfsFactory} implementation to use.
|
||||||
|
*/
|
||||||
|
public static void setInstance(LfsFactory instance) {
|
||||||
|
LfsFactory.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return whether LFS support is available
|
||||||
|
*/
|
||||||
|
public boolean isAvailable() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply clean filtering to the given stream, writing the file content to
|
||||||
|
* the LFS storage if required and returning a stream to the LFS pointer
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @param input
|
||||||
|
* the original input
|
||||||
|
* @param length
|
||||||
|
* the expected input stream length
|
||||||
|
* @param attribute
|
||||||
|
* the attribute used to check for LFS enablement (i.e. "merge",
|
||||||
|
* "diff", "filter" from .gitattributes).
|
||||||
|
* @return a stream to the content that should be written to the object
|
||||||
|
* store along with the expected length of the stream. the original
|
||||||
|
* stream is not applicable.
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error
|
||||||
|
*/
|
||||||
|
public LfsInputStream applyCleanFilter(Repository db,
|
||||||
|
InputStream input, long length, Attribute attribute)
|
||||||
|
throws IOException {
|
||||||
|
return new LfsInputStream(input, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply smudge filtering to a given loader, potentially redirecting it to a
|
||||||
|
* LFS blob which is downloaded on demand.
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @param loader
|
||||||
|
* the loader for the blob
|
||||||
|
* @param attribute
|
||||||
|
* the attribute used to check for LFS enablement (i.e. "merge",
|
||||||
|
* "diff", "filter" from .gitattributes).
|
||||||
|
* @return a loader for the actual data of a blob, or the original loader in
|
||||||
|
* case LFS is not applicable.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public ObjectLoader applySmudgeFilter(Repository db,
|
||||||
|
ObjectLoader loader, Attribute attribute) throws IOException {
|
||||||
|
return loader;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve a pre-push hook to be applied.
|
||||||
|
*
|
||||||
|
* @param repo
|
||||||
|
* the {@link Repository} the hook is applied to.
|
||||||
|
* @param outputStream
|
||||||
|
* @return a {@link PrePushHook} implementation or <code>null</code>
|
||||||
|
*/
|
||||||
|
public @Nullable PrePushHook getPrePushHook(Repository repo,
|
||||||
|
PrintStream outputStream) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @param path
|
||||||
|
* the path to find attributes for
|
||||||
|
* @return the {@link Attributes} for the given path.
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error
|
||||||
|
*/
|
||||||
|
public static Attributes getAttributesForPath(Repository db, String path)
|
||||||
|
throws IOException {
|
||||||
|
try (TreeWalk walk = new TreeWalk(db)) {
|
||||||
|
walk.addTree(new FileTreeIterator(db));
|
||||||
|
PathFilter f = PathFilter.create(path);
|
||||||
|
walk.setFilter(f);
|
||||||
|
walk.setRecursive(false);
|
||||||
|
Attributes attr = null;
|
||||||
|
while (walk.next()) {
|
||||||
|
if (f.isDone(walk)) {
|
||||||
|
attr = walk.getAttributes();
|
||||||
|
break;
|
||||||
|
} else if (walk.isSubtree()) {
|
||||||
|
walk.enterSubtree();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (attr == null) {
|
||||||
|
throw new IOException(MessageFormat
|
||||||
|
.format(JGitText.get().noPathAttributesFound, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get attributes for given path and commit
|
||||||
|
*
|
||||||
|
* @param db
|
||||||
|
* the repository
|
||||||
|
* @param path
|
||||||
|
* the path to find attributes for
|
||||||
|
* @param commit
|
||||||
|
* the commit to inspect.
|
||||||
|
* @return the {@link Attributes} for the given path.
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error
|
||||||
|
*/
|
||||||
|
public static Attributes getAttributesForPath(Repository db, String path,
|
||||||
|
RevCommit commit) throws IOException {
|
||||||
|
if (commit == null) {
|
||||||
|
return getAttributesForPath(db, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (TreeWalk walk = TreeWalk.forPath(db, path, commit.getTree())) {
|
||||||
|
Attributes attr = walk == null ? null : walk.getAttributes();
|
||||||
|
if (attr == null) {
|
||||||
|
throw new IOException(MessageFormat
|
||||||
|
.format(JGitText.get().noPathAttributesFound, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
return attr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Encapsulate a potentially exchanged {@link InputStream} along with the
|
||||||
|
* expected stream content length.
|
||||||
|
*/
|
||||||
|
public static final class LfsInputStream extends InputStream {
|
||||||
|
/**
|
||||||
|
* The actual stream.
|
||||||
|
*/
|
||||||
|
private InputStream stream;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The expected stream content length.
|
||||||
|
*/
|
||||||
|
private long length;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new wrapper around a certain stream
|
||||||
|
*
|
||||||
|
* @param stream
|
||||||
|
* the stream to wrap. the stream will be closed on
|
||||||
|
* {@link #close()}.
|
||||||
|
* @param length
|
||||||
|
* the expected length of the stream
|
||||||
|
*/
|
||||||
|
public LfsInputStream(InputStream stream, long length) {
|
||||||
|
this.stream = stream;
|
||||||
|
this.length = length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new wrapper around a temporary buffer.
|
||||||
|
*
|
||||||
|
* @param buffer
|
||||||
|
* the buffer to initialize stream and length from. The
|
||||||
|
* buffer will be destroyed on {@link #close()}
|
||||||
|
* @throws IOException
|
||||||
|
* in case of an error opening the stream to the buffer.
|
||||||
|
*/
|
||||||
|
public LfsInputStream(TemporaryBuffer buffer) throws IOException {
|
||||||
|
this.stream = buffer.openInputStream();
|
||||||
|
this.length = buffer.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() throws IOException {
|
||||||
|
stream.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read() throws IOException {
|
||||||
|
return stream.read();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return the length of the stream
|
||||||
|
*/
|
||||||
|
public long getLength() {
|
||||||
|
return length;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
Loading…
Reference in New Issue