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.junit.JGitTestUtil;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lfs.CleanFilter;
|
||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
||||
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||
import org.eclipse.jgit.lfs.lib.LongObjectId;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.StoredConfig;
|
||||
|
@ -72,8 +71,7 @@ public class CheckoutTest extends LfsServerTest {
|
|||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
|
||||
SmudgeFilter.register();
|
||||
CleanFilter.register();
|
||||
BuiltinLFS.register();
|
||||
|
||||
Path tmp = Files.createTempDirectory("jgit_test_");
|
||||
Repository db = FileRepositoryBuilder
|
||||
|
|
|
@ -52,8 +52,7 @@
|
|||
import org.eclipse.jgit.api.RemoteAddCommand;
|
||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||
import org.eclipse.jgit.junit.TestRepository;
|
||||
import org.eclipse.jgit.lfs.CleanFilter;
|
||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
||||
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
|
@ -84,8 +83,7 @@ public class PushTest extends LfsServerTest {
|
|||
public void setup() throws Exception {
|
||||
super.setup();
|
||||
|
||||
SmudgeFilter.register();
|
||||
CleanFilter.register();
|
||||
BuiltinLFS.register();
|
||||
|
||||
Path rtmp = Files.createTempDirectory("jgit_test_");
|
||||
remoteDb = FileRepositoryBuilder.create(rtmp.toFile());
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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">
|
||||
<filter id="336658481">
|
||||
<message_arguments>
|
||||
|
@ -8,4 +16,12 @@
|
|||
</message_arguments>
|
||||
</filter>
|
||||
</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>
|
||||
|
|
|
@ -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.api.errors;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.hooks;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.revwalk;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.http;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
|
||||
* {@link FilterCommandRegistry#register(String, FilterCommandFactory)}
|
||||
*/
|
||||
public final static void register() {
|
||||
static void register() {
|
||||
FilterCommandRegistry
|
||||
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_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
|
||||
.register(org.eclipse.jgit.lib.Constants.BUILTIN_FILTER_PREFIX
|
||||
+ Constants.ATTR_FILTER_DRIVER_PREFIX
|
||||
|
@ -110,8 +110,6 @@ public final static void register() {
|
|||
FACTORY);
|
||||
}
|
||||
|
||||
private Lfs lfs;
|
||||
|
||||
/**
|
||||
* Constructor for SmudgeFilter.
|
||||
*
|
||||
|
@ -126,13 +124,13 @@ public final static void register() {
|
|||
public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
||||
throws IOException {
|
||||
super(in, out);
|
||||
lfs = new Lfs(db);
|
||||
Lfs lfs = new Lfs(db);
|
||||
LfsPointer res = LfsPointer.parseLfsPointer(in);
|
||||
if (res != null) {
|
||||
AnyLongObjectId oid = res.getOid();
|
||||
Path mediaFile = lfs.getMediaFile(oid);
|
||||
if (!Files.exists(mediaFile)) {
|
||||
downloadLfsResource(db, res);
|
||||
downloadLfsResource(lfs, db, res);
|
||||
}
|
||||
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
|
||||
*
|
||||
* @param lfs
|
||||
* local {@link Lfs} storage.
|
||||
* @param db
|
||||
* the repository to work with
|
||||
* @param res
|
||||
|
@ -148,9 +148,8 @@ public SmudgeFilter(Repository db, InputStream in, OutputStream out)
|
|||
* @return the paths of all mediafiles which have been downloaded
|
||||
* @throws IOException
|
||||
*/
|
||||
private Collection<Path> downloadLfsResource(Repository db,
|
||||
LfsPointer... res)
|
||||
throws IOException {
|
||||
public static Collection<Path> downloadLfsResource(Lfs lfs, Repository db,
|
||||
LfsPointer... res) throws IOException {
|
||||
Collection<Path> downloadedPaths = new ArrayList<>();
|
||||
Map<String, LfsPointer> oidStr2ptr = new HashMap<>();
|
||||
for (LfsPointer p : res) {
|
||||
|
|
|
@ -56,7 +56,7 @@
|
|||
@SuppressWarnings("nls")
|
||||
public final class Constants {
|
||||
/**
|
||||
* lfs folder
|
||||
* lfs folder/section/filter name
|
||||
*
|
||||
* @since 4.6
|
||||
*/
|
||||
|
|
|
@ -65,8 +65,7 @@
|
|||
import org.eclipse.jgit.awtui.AwtAuthenticator;
|
||||
import org.eclipse.jgit.awtui.AwtCredentialsProvider;
|
||||
import org.eclipse.jgit.errors.TransportException;
|
||||
import org.eclipse.jgit.lfs.CleanFilter;
|
||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
||||
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.RepositoryBuilder;
|
||||
import org.eclipse.jgit.pgm.internal.CLIText;
|
||||
|
@ -111,8 +110,7 @@ public class Main {
|
|||
*/
|
||||
public Main() {
|
||||
HttpTransport.setConnectionFactory(new HttpClientConnectionFactory());
|
||||
CleanFilter.register();
|
||||
SmudgeFilter.register();
|
||||
BuiltinLFS.register();
|
||||
gcExecutor = Executors.newSingleThreadExecutor(new ThreadFactory() {
|
||||
private final ThreadFactory baseFactory = Executors
|
||||
.defaultThreadFactory();
|
||||
|
|
|
@ -63,8 +63,7 @@
|
|||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||
import org.eclipse.jgit.lfs.CleanFilter;
|
||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
||||
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||
import org.eclipse.jgit.lib.ConfigConstants;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
|
@ -92,8 +91,7 @@ public class AddCommandTest extends RepositoryTestCase {
|
|||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
CleanFilter.register();
|
||||
SmudgeFilter.register();
|
||||
BuiltinLFS.register();
|
||||
super.setUp();
|
||||
}
|
||||
|
||||
|
|
|
@ -74,8 +74,7 @@
|
|||
import org.eclipse.jgit.dircache.DirCacheEntry;
|
||||
import org.eclipse.jgit.junit.JGitTestUtil;
|
||||
import org.eclipse.jgit.junit.RepositoryTestCase;
|
||||
import org.eclipse.jgit.lfs.CleanFilter;
|
||||
import org.eclipse.jgit.lfs.SmudgeFilter;
|
||||
import org.eclipse.jgit.lfs.BuiltinLFS;
|
||||
import org.eclipse.jgit.lib.ConfigConstants;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.Ref;
|
||||
|
@ -102,8 +101,7 @@ public class CheckoutCommandTest extends RepositoryTestCase {
|
|||
@Override
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
CleanFilter.register();
|
||||
SmudgeFilter.register();
|
||||
BuiltinLFS.register();
|
||||
super.setUp();
|
||||
git = new Git(db);
|
||||
// commit something
|
||||
|
|
|
@ -1,5 +1,13 @@
|
|||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<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">
|
||||
<filter id="336658481">
|
||||
<message_arguments>
|
||||
|
|
|
@ -462,6 +462,7 @@ noHMACsupport=No {0} support: {1}
|
|||
noMergeBase=No merge base could be determined. Reason={0}. {1}
|
||||
noMergeHeadSpecified=No merge head specified
|
||||
nonBareLinkFilesNotSupported=Link files are not supported with nonbare repos
|
||||
noPathAttributesFound=No Attributes found for {0}.
|
||||
noSuchRef=no such ref
|
||||
noSuchSubmodule=no such submodule {0}
|
||||
notABoolean=Not a boolean: {0}
|
||||
|
|
|
@ -303,7 +303,8 @@ public BlameGenerator push(String description, RawText contents)
|
|||
throws IOException {
|
||||
if (description == null)
|
||||
description = JGitText.get().blameNotCommittedYet;
|
||||
BlobCandidate c = new BlobCandidate(description, resultPath);
|
||||
BlobCandidate c = new BlobCandidate(getRepository(), description,
|
||||
resultPath);
|
||||
c.sourceText = contents;
|
||||
c.regionList = new Region(0, 0, contents.size());
|
||||
remaining = contents.size();
|
||||
|
@ -333,7 +334,8 @@ public BlameGenerator push(String description, AnyObjectId id)
|
|||
if (ldr.getType() == OBJ_BLOB) {
|
||||
if (description == null)
|
||||
description = JGitText.get().blameNotCommittedYet;
|
||||
BlobCandidate c = new BlobCandidate(description, resultPath);
|
||||
BlobCandidate c = new BlobCandidate(getRepository(), description,
|
||||
resultPath);
|
||||
c.sourceBlob = id.toObjectId();
|
||||
c.sourceText = new RawText(ldr.getCachedBytes(Integer.MAX_VALUE));
|
||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||
|
@ -346,7 +348,7 @@ public BlameGenerator push(String description, AnyObjectId id)
|
|||
if (!find(commit, resultPath))
|
||||
return this;
|
||||
|
||||
Candidate c = new Candidate(commit, resultPath);
|
||||
Candidate c = new Candidate(getRepository(), commit, resultPath);
|
||||
c.sourceBlob = idBuf.toObjectId();
|
||||
c.loadText(reader);
|
||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||
|
@ -430,7 +432,8 @@ public BlameGenerator reverse(AnyObjectId start,
|
|||
// just pump the queue
|
||||
}
|
||||
|
||||
ReverseCandidate c = new ReverseCandidate(result, resultPath);
|
||||
ReverseCandidate c = new ReverseCandidate(getRepository(), result,
|
||||
resultPath);
|
||||
c.sourceBlob = idBuf.toObjectId();
|
||||
c.loadText(reader);
|
||||
c.regionList = new Region(0, 0, c.sourceText.size());
|
||||
|
@ -637,7 +640,8 @@ private boolean processOne(Candidate n) throws IOException {
|
|||
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.renameScore = r.getScore();
|
||||
next.loadText(reader);
|
||||
|
@ -653,7 +657,7 @@ private boolean blameEntireRegionOnParent(Candidate n, RevCommit parent) {
|
|||
|
||||
private boolean splitBlameWithParent(Candidate n, RevCommit parent)
|
||||
throws IOException {
|
||||
Candidate next = n.create(parent, n.sourcePath);
|
||||
Candidate next = n.create(getRepository(), parent, n.sourcePath);
|
||||
next.sourceBlob = idBuf.toObjectId();
|
||||
next.loadText(reader);
|
||||
return split(next, n);
|
||||
|
@ -740,12 +744,12 @@ private boolean processMerge(Candidate n) throws IOException {
|
|||
|
||||
Candidate p;
|
||||
if (renames != null && renames[pIdx] != null) {
|
||||
p = n.create(parent,
|
||||
p = n.create(getRepository(), parent,
|
||||
PathFilter.create(renames[pIdx].getOldPath()));
|
||||
p.renameScore = renames[pIdx].getScore();
|
||||
p.sourceBlob = renames[pIdx].getOldId().toObjectId();
|
||||
} else if (ids != null && ids[pIdx] != null) {
|
||||
p = n.create(parent, n.sourcePath);
|
||||
p = n.create(getRepository(), parent, n.sourcePath);
|
||||
p.sourceBlob = ids[pIdx];
|
||||
} else {
|
||||
continue;
|
||||
|
|
|
@ -55,10 +55,12 @@
|
|||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.lib.PersonIdent;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevCommit;
|
||||
import org.eclipse.jgit.revwalk.RevFlag;
|
||||
import org.eclipse.jgit.revwalk.RevWalk;
|
||||
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.
|
||||
|
@ -109,7 +111,11 @@ class Candidate {
|
|||
*/
|
||||
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;
|
||||
sourcePath = path;
|
||||
}
|
||||
|
@ -150,12 +156,12 @@ PersonIdent getAuthor() {
|
|||
return sourceCommit.getAuthorIdent();
|
||||
}
|
||||
|
||||
Candidate create(RevCommit commit, PathFilter path) {
|
||||
return new Candidate(commit, path);
|
||||
Candidate create(Repository repo, RevCommit commit, PathFilter path) {
|
||||
return new Candidate(repo, commit, path);
|
||||
}
|
||||
|
||||
Candidate copy(RevCommit commit) {
|
||||
Candidate r = create(commit, sourcePath);
|
||||
Candidate r = create(sourceRepository, commit, sourcePath);
|
||||
r.sourceBlob = sourceBlob;
|
||||
r.sourceText = sourceText;
|
||||
r.regionList = regionList;
|
||||
|
@ -164,7 +170,11 @@ Candidate copy(RevCommit commit) {
|
|||
}
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
|
@ -349,8 +359,9 @@ public String toString() {
|
|||
* children pointers, allowing reverse navigation of history.
|
||||
*/
|
||||
static final class ReverseCandidate extends Candidate {
|
||||
ReverseCandidate(ReverseCommit commit, PathFilter path) {
|
||||
super(commit, path);
|
||||
ReverseCandidate(Repository repo, ReverseCommit commit,
|
||||
PathFilter path) {
|
||||
super(repo, commit, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -370,8 +381,8 @@ int getTime() {
|
|||
}
|
||||
|
||||
@Override
|
||||
Candidate create(RevCommit commit, PathFilter path) {
|
||||
return new ReverseCandidate((ReverseCommit) commit, path);
|
||||
Candidate create(Repository repo, RevCommit commit, PathFilter path) {
|
||||
return new ReverseCandidate(repo, (ReverseCommit) commit, path);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -400,8 +411,8 @@ static final class BlobCandidate extends Candidate {
|
|||
/** Author name to refer to this blob with. */
|
||||
String description;
|
||||
|
||||
BlobCandidate(String name, PathFilter path) {
|
||||
super(null, path);
|
||||
BlobCandidate(Repository repo, String name, PathFilter path) {
|
||||
super(repo, null, path);
|
||||
description = name;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,9 +48,11 @@
|
|||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import org.eclipse.jgit.attributes.Attribute;
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.lib.AbbreviatedObjectId;
|
||||
import org.eclipse.jgit.lib.AnyObjectId;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
import org.eclipse.jgit.lib.MutableObjectId;
|
||||
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.newPath = entry.oldPath = walk.getPathString();
|
||||
|
||||
if (walk.getAttributesNodeProvider() != null) {
|
||||
entry.diffAttribute = walk.getAttributes()
|
||||
.get(Constants.ATTR_DIFF);
|
||||
}
|
||||
|
||||
if (treeFilterMarker != null)
|
||||
entry.treeFilterMarks = treeFilterMarker.getMarks(walk);
|
||||
|
||||
|
@ -282,6 +289,7 @@ static List<DiffEntry> breakModify(DiffEntry entry) {
|
|||
del.newMode = FileMode.MISSING;
|
||||
del.newPath = DiffEntry.DEV_NULL;
|
||||
del.changeType = ChangeType.DELETE;
|
||||
del.diffAttribute = entry.diffAttribute;
|
||||
|
||||
DiffEntry add = new DiffEntry();
|
||||
add.oldId = A_ZERO;
|
||||
|
@ -292,6 +300,7 @@ static List<DiffEntry> breakModify(DiffEntry entry) {
|
|||
add.newMode = entry.getNewMode();
|
||||
add.newPath = entry.getNewPath();
|
||||
add.changeType = ChangeType.ADD;
|
||||
add.diffAttribute = entry.diffAttribute;
|
||||
return Arrays.asList(del, add);
|
||||
}
|
||||
|
||||
|
@ -306,6 +315,7 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
|
|||
r.newId = dst.newId;
|
||||
r.newMode = dst.newMode;
|
||||
r.newPath = dst.newPath;
|
||||
r.diffAttribute = dst.diffAttribute;
|
||||
|
||||
r.changeType = changeType;
|
||||
r.score = score;
|
||||
|
@ -321,6 +331,13 @@ static DiffEntry pair(ChangeType changeType, DiffEntry src, DiffEntry dst,
|
|||
/** File name of the new (post-image). */
|
||||
protected String newPath;
|
||||
|
||||
/**
|
||||
* diff filter attribute
|
||||
*
|
||||
* @since 4.11
|
||||
*/
|
||||
protected Attribute diffAttribute;
|
||||
|
||||
/** Old mode of the file, if described by the patch, else null. */
|
||||
protected FileMode oldMode;
|
||||
|
||||
|
@ -394,6 +411,14 @@ public String getPath(Side side) {
|
|||
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
|
||||
*
|
||||
|
|
|
@ -98,6 +98,7 @@
|
|||
import org.eclipse.jgit.treewalk.filter.NotIgnoredFilter;
|
||||
import org.eclipse.jgit.treewalk.filter.PathFilter;
|
||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||
import org.eclipse.jgit.util.LfsFactory;
|
||||
import org.eclipse.jgit.util.QuotedString;
|
||||
|
||||
/**
|
||||
|
@ -141,6 +142,8 @@ public class DiffFormatter implements AutoCloseable {
|
|||
|
||||
private ContentSource.Pair source;
|
||||
|
||||
private Repository repository;
|
||||
|
||||
/**
|
||||
* Create a new formatter with a default level of context.
|
||||
*
|
||||
|
@ -172,6 +175,7 @@ protected OutputStream getOutputStream() {
|
|||
* source repository holding referenced objects.
|
||||
*/
|
||||
public void setRepository(Repository repository) {
|
||||
this.repository = repository;
|
||||
setReader(repository.newObjectReader(), repository.getConfig(), true);
|
||||
}
|
||||
|
||||
|
@ -1057,7 +1061,8 @@ private RawText open(DiffEntry.Side side, DiffEntry entry)
|
|||
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);
|
||||
}
|
||||
|
||||
|
|
|
@ -110,6 +110,14 @@ public RawText(File file) throws IOException {
|
|||
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. */
|
||||
/** {@inheritDoc} */
|
||||
@Override
|
||||
|
|
|
@ -43,13 +43,11 @@
|
|||
package org.eclipse.jgit.hooks;
|
||||
|
||||
import java.io.PrintStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.text.MessageFormat;
|
||||
|
||||
import org.eclipse.jgit.internal.JGitText;
|
||||
import org.eclipse.jgit.lib.ConfigConstants;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.lib.StoredConfig;
|
||||
import org.eclipse.jgit.util.LfsFactory;
|
||||
|
||||
/**
|
||||
* Factory class for instantiating supported hooks.
|
||||
|
@ -112,28 +110,16 @@ public static CommitMsgHook commitMsg(Repository repo,
|
|||
* @since 4.2
|
||||
*/
|
||||
public static PrePushHook prePush(Repository repo, PrintStream outputStream) {
|
||||
PrePushHook lfsHook = null;
|
||||
try {
|
||||
StoredConfig cfg = repo.getConfig();
|
||||
if (cfg.getBoolean(ConfigConstants.CONFIG_FILTER_SECTION, "lfs", //$NON-NLS-1$
|
||||
ConfigConstants.CONFIG_KEY_USEJGITBUILTIN, false)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<? extends PrePushHook> cls = (Class<? extends PrePushHook>) Class
|
||||
.forName("org.eclipse.jgit.lfs.LfsPrePushHook"); //$NON-NLS-1$
|
||||
Constructor<? extends PrePushHook> constructor = cls
|
||||
.getConstructor(Repository.class, PrintStream.class);
|
||||
|
||||
lfsHook = constructor.newInstance(repo, outputStream);
|
||||
if (LfsFactory.getInstance().isAvailable()) {
|
||||
PrePushHook hook = LfsFactory.getInstance().getPrePushHook(repo,
|
||||
outputStream);
|
||||
if (hook != null) {
|
||||
if (hook.isNativeHookPresent()) {
|
||||
throw new IllegalStateException(MessageFormat
|
||||
.format(JGitText.get().lfsHookConflict, repo));
|
||||
}
|
||||
return hook;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
// no problem :) no LFS support present
|
||||
}
|
||||
if (lfsHook != null) {
|
||||
if (lfsHook.isNativeHookPresent()) {
|
||||
throw new IllegalStateException(MessageFormat
|
||||
.format(JGitText.get().lfsHookConflict, repo));
|
||||
}
|
||||
return lfsHook;
|
||||
}
|
||||
return new PrePushHook(repo, outputStream);
|
||||
}
|
||||
|
|
|
@ -523,6 +523,7 @@ public static JGitText get() {
|
|||
/***/ public String noMergeBase;
|
||||
/***/ public String noMergeHeadSpecified;
|
||||
/***/ public String nonBareLinkFilesNotSupported;
|
||||
/***/ public String noPathAttributesFound;
|
||||
/***/ public String noSuchRef;
|
||||
/***/ public String noSuchSubmodule;
|
||||
/***/ public String notABoolean;
|
||||
|
|
|
@ -437,6 +437,13 @@ public final class Constants {
|
|||
*/
|
||||
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.
|
||||
*
|
||||
|
|
|
@ -87,12 +87,12 @@
|
|||
import org.eclipse.jgit.errors.NoWorkTreeException;
|
||||
import org.eclipse.jgit.lib.Config;
|
||||
import org.eclipse.jgit.lib.ConfigConstants;
|
||||
import org.eclipse.jgit.lib.Constants;
|
||||
import org.eclipse.jgit.lib.CoreConfig.EolStreamType;
|
||||
import org.eclipse.jgit.lib.FileMode;
|
||||
import org.eclipse.jgit.lib.ObjectId;
|
||||
import org.eclipse.jgit.lib.ObjectInserter;
|
||||
import org.eclipse.jgit.lib.ObjectLoader;
|
||||
import org.eclipse.jgit.lib.ObjectReader;
|
||||
import org.eclipse.jgit.lib.Repository;
|
||||
import org.eclipse.jgit.revwalk.RevTree;
|
||||
import org.eclipse.jgit.storage.pack.PackConfig;
|
||||
|
@ -106,6 +106,8 @@
|
|||
import org.eclipse.jgit.treewalk.WorkingTreeOptions;
|
||||
import org.eclipse.jgit.treewalk.filter.TreeFilter;
|
||||
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.io.EolStreamTypeUtil;
|
||||
|
||||
|
@ -769,11 +771,12 @@ protected boolean processEntry(CanonicalTreeParser base,
|
|||
return false;
|
||||
}
|
||||
|
||||
MergeResult<RawText> result = contentMerge(base, ours, theirs);
|
||||
MergeResult<RawText> result = contentMerge(base, ours, theirs,
|
||||
attributes);
|
||||
if (ignoreConflicts) {
|
||||
result.setContainsConflicts(false);
|
||||
}
|
||||
updateIndex(base, ours, theirs, result);
|
||||
updateIndex(base, ours, theirs, result, attributes);
|
||||
if (result.containsConflicts() && !ignoreConflicts)
|
||||
unmergedPaths.add(tw.getPathString());
|
||||
modifiedFiles.add(tw.getPathString());
|
||||
|
@ -781,7 +784,8 @@ protected boolean processEntry(CanonicalTreeParser base,
|
|||
// OURS or THEIRS has been deleted
|
||||
if (((modeO != 0 && !tw.idEqual(T_BASE, T_OURS)) || (modeT != 0 && !tw
|
||||
.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(), ours, DirCacheEntry.STAGE_2, 0, 0);
|
||||
|
@ -816,12 +820,14 @@ protected boolean processEntry(CanonicalTreeParser base,
|
|||
* @param base
|
||||
* @param ours
|
||||
* @param theirs
|
||||
* @param attributes
|
||||
*
|
||||
* @return the result of the content merge
|
||||
* @throws IOException
|
||||
*/
|
||||
private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
||||
CanonicalTreeParser ours, CanonicalTreeParser theirs)
|
||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||
Attributes attributes)
|
||||
throws IOException {
|
||||
RawText baseText;
|
||||
RawText ourText;
|
||||
|
@ -829,11 +835,11 @@ private MergeResult<RawText> contentMerge(CanonicalTreeParser base,
|
|||
|
||||
try {
|
||||
baseText = base == null ? RawText.EMPTY_TEXT : getRawText(
|
||||
base.getEntryObjectId(), reader);
|
||||
base.getEntryObjectId(), attributes);
|
||||
ourText = ours == null ? RawText.EMPTY_TEXT : getRawText(
|
||||
ours.getEntryObjectId(), reader);
|
||||
ours.getEntryObjectId(), attributes);
|
||||
theirsText = theirs == null ? RawText.EMPTY_TEXT : getRawText(
|
||||
theirs.getEntryObjectId(), reader);
|
||||
theirs.getEntryObjectId(), attributes);
|
||||
} catch (BinaryBlobException e) {
|
||||
MergeResult<RawText> r = new MergeResult<>(Collections.<RawText>emptyList());
|
||||
r.setContainsConflicts(true);
|
||||
|
@ -897,17 +903,20 @@ private boolean isWorktreeDirty(WorkingTreeIterator work,
|
|||
* @param ours
|
||||
* @param theirs
|
||||
* @param result
|
||||
* @param attributes
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private void updateIndex(CanonicalTreeParser base,
|
||||
CanonicalTreeParser ours, CanonicalTreeParser theirs,
|
||||
MergeResult<RawText> result) throws FileNotFoundException,
|
||||
MergeResult<RawText> result, Attributes attributes)
|
||||
throws FileNotFoundException,
|
||||
IOException {
|
||||
TemporaryBuffer rawMerged = null;
|
||||
try {
|
||||
rawMerged = doMerge(result);
|
||||
File mergedFile = inCore ? null : writeMergedFile(rawMerged);
|
||||
File mergedFile = inCore ? null
|
||||
: writeMergedFile(rawMerged, attributes);
|
||||
if (result.containsConflicts()) {
|
||||
// A conflict occurred, the file will contain conflict markers
|
||||
// the index will be populated with the three stages and the
|
||||
|
@ -934,7 +943,7 @@ private void updateIndex(CanonicalTreeParser base,
|
|||
nonNullRepo().getFS().lastModified(mergedFile));
|
||||
dce.setLength((int) mergedFile.length());
|
||||
}
|
||||
dce.setObjectId(insertMergeResult(rawMerged));
|
||||
dce.setObjectId(insertMergeResult(rawMerged, attributes));
|
||||
builder.add(dce);
|
||||
} finally {
|
||||
if (rawMerged != null) {
|
||||
|
@ -948,11 +957,14 @@ private void updateIndex(CanonicalTreeParser base,
|
|||
*
|
||||
* @param rawMerged
|
||||
* the raw merged content
|
||||
* @param attributes
|
||||
* the files .gitattributes entries
|
||||
* @return the working tree file to which the merged content was written.
|
||||
* @throws FileNotFoundException
|
||||
* @throws IOException
|
||||
*/
|
||||
private File writeMergedFile(TemporaryBuffer rawMerged)
|
||||
private File writeMergedFile(TemporaryBuffer rawMerged,
|
||||
Attributes attributes)
|
||||
throws FileNotFoundException, IOException {
|
||||
File workTree = nonNullRepo().getWorkTree();
|
||||
FS fs = nonNullRepo().getFS();
|
||||
|
@ -963,7 +975,7 @@ private File writeMergedFile(TemporaryBuffer rawMerged)
|
|||
}
|
||||
EolStreamType streamType = EolStreamTypeUtil.detectStreamType(
|
||||
OperationType.CHECKOUT_OP, workingTreeOptions,
|
||||
tw.getAttributes());
|
||||
attributes);
|
||||
try (OutputStream os = EolStreamTypeUtil.wrapOutputStream(
|
||||
new BufferedOutputStream(new FileOutputStream(of)),
|
||||
streamType)) {
|
||||
|
@ -987,9 +999,13 @@ private TemporaryBuffer doMerge(MergeResult<RawText> result)
|
|||
return buf;
|
||||
}
|
||||
|
||||
private ObjectId insertMergeResult(TemporaryBuffer buf) throws IOException {
|
||||
try (InputStream in = buf.openInputStream()) {
|
||||
return getObjectInserter().insert(OBJ_BLOB, buf.length(), in);
|
||||
private ObjectId insertMergeResult(TemporaryBuffer buf,
|
||||
Attributes attributes) throws IOException {
|
||||
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();
|
||||
}
|
||||
|
||||
private static RawText getRawText(ObjectId id, ObjectReader reader)
|
||||
private RawText getRawText(ObjectId id,
|
||||
Attributes attributes)
|
||||
throws IOException, BinaryBlobException {
|
||||
if (id.equals(ObjectId.zeroId()))
|
||||
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;
|
||||
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