summaryrefslogtreecommitdiffstats
path: root/org.eclipse.jgit
diff options
context:
space:
mode:
Diffstat (limited to 'org.eclipse.jgit')
-rw-r--r--org.eclipse.jgit/META-INF/MANIFEST.MF69
-rw-r--r--org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF4
-rw-r--r--org.eclipse.jgit/pom.xml2
-rw-r--r--org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java63
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java27
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java98
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java88
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java10
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java142
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java229
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java76
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java88
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java80
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java83
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java112
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java254
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java404
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java82
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java74
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java75
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java5
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java22
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java18
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java14
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java57
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java6
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java60
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java166
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java39
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java9
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java11
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java36
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java8
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java49
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java70
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java72
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java3
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java15
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java7
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java23
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java4
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java35
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java12
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java2
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java33
-rw-r--r--org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java41
60 files changed, 2690 insertions, 318 deletions
diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF
index df62f83d8d..80399f6008 100644
--- a/org.eclipse.jgit/META-INF/MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/MANIFEST.MF
@@ -2,10 +2,10 @@ Manifest-Version: 1.0
Bundle-ManifestVersion: 2
Bundle-Name: %plugin_name
Bundle-SymbolicName: org.eclipse.jgit
-Bundle-Version: 3.5.3.201412180710-r
+Bundle-Version: 3.6.0.qualifier
Bundle-Localization: plugin
Bundle-Vendor: %provider_name
-Export-Package: org.eclipse.jgit.api;version="3.5.3";
+Export-Package: org.eclipse.jgit.api;version="3.6.0";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
@@ -18,49 +18,50 @@ Export-Package: org.eclipse.jgit.api;version="3.5.3";
org.eclipse.jgit.blame,
org.eclipse.jgit.transport,
org.eclipse.jgit.merge",
- org.eclipse.jgit.api.errors;version="3.5.3";
+ org.eclipse.jgit.api.errors;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors",
- org.eclipse.jgit.blame;version="3.5.3";
+ org.eclipse.jgit.blame;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff",
- org.eclipse.jgit.diff;version="3.5.3";
+ org.eclipse.jgit.diff;version="3.6.0";
uses:="org.eclipse.jgit.patch,
org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util",
- org.eclipse.jgit.dircache;version="3.5.3";
+ org.eclipse.jgit.dircache;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.util,
org.eclipse.jgit.events",
- org.eclipse.jgit.errors;version="3.5.3";
+ org.eclipse.jgit.errors;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.internal.storage.pack,
org.eclipse.jgit.transport,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.events;version="3.5.3";
+ org.eclipse.jgit.events;version="3.6.0";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.fnmatch;version="3.5.3",
- org.eclipse.jgit.gitrepo;version="3.5.3";
+ org.eclipse.jgit.fnmatch;version="3.6.0",
+ org.eclipse.jgit.gitrepo;version="3.6.0";
uses:="org.eclipse.jgit.api,
org.eclipse.jgit.lib",
- org.eclipse.jgit.gitrepo.internal;version="3.5.3";x-internal:=true,
- org.eclipse.jgit.ignore;version="3.5.3",
- org.eclipse.jgit.internal;version="3.5.3";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
- org.eclipse.jgit.internal.storage.dfs;version="3.5.3";x-friends:="org.eclipse.jgit.test",
- org.eclipse.jgit.internal.storage.file;version="3.5.3";
+ org.eclipse.jgit.gitrepo.internal;version="3.6.0";x-internal:=true,
+ org.eclipse.jgit.ignore;version="3.6.0",
+ org.eclipse.jgit.ignore.internal;version="3.6.0";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal;version="3.6.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test",
+ org.eclipse.jgit.internal.storage.dfs;version="3.6.0";x-friends:="org.eclipse.jgit.test",
+ org.eclipse.jgit.internal.storage.file;version="3.6.0";
x-friends:="org.eclipse.jgit.test,
org.eclipse.jgit.junit,
org.eclipse.jgit.junit.http,
org.eclipse.jgit.http.server,
org.eclipse.jgit.java7.test,
org.eclipse.jgit.pgm",
- org.eclipse.jgit.internal.storage.pack;version="3.5.3";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
- org.eclipse.jgit.lib;version="3.5.3";
+ org.eclipse.jgit.internal.storage.pack;version="3.6.0";x-friends:="org.eclipse.jgit.junit,org.eclipse.jgit.test,org.eclipse.jgit.pgm",
+ org.eclipse.jgit.lib;version="3.6.0";
uses:="org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
@@ -69,37 +70,37 @@ Export-Package: org.eclipse.jgit.api;version="3.5.3";
org.eclipse.jgit.internal.storage.file,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.transport",
- org.eclipse.jgit.merge;version="3.5.3";
+ org.eclipse.jgit.merge;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.diff,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.nls;version="3.5.3",
- org.eclipse.jgit.notes;version="3.5.3";
+ org.eclipse.jgit.nls;version="3.6.0",
+ org.eclipse.jgit.notes;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.merge",
- org.eclipse.jgit.patch;version="3.5.3";
+ org.eclipse.jgit.patch;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff",
- org.eclipse.jgit.revplot;version="3.5.3";
+ org.eclipse.jgit.revplot;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk",
- org.eclipse.jgit.revwalk;version="3.5.3";
+ org.eclipse.jgit.revwalk;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.treewalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.diff,
org.eclipse.jgit.revwalk.filter",
- org.eclipse.jgit.revwalk.filter;version="3.5.3";
+ org.eclipse.jgit.revwalk.filter;version="3.6.0";
uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.file;version="3.5.3";
+ org.eclipse.jgit.storage.file;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util",
- org.eclipse.jgit.storage.pack;version="3.5.3";
+ org.eclipse.jgit.storage.pack;version="3.6.0";
uses:="org.eclipse.jgit.lib",
- org.eclipse.jgit.submodule;version="3.5.3";
+ org.eclipse.jgit.submodule;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk,org.eclipse.jgit.treewalk.filter",
- org.eclipse.jgit.transport;version="3.5.3";
+ org.eclipse.jgit.transport;version="3.6.0";
uses:="org.eclipse.jgit.transport.resolver,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.internal.storage.pack,
@@ -111,21 +112,21 @@ Export-Package: org.eclipse.jgit.api;version="3.5.3";
org.eclipse.jgit.transport.http,
org.eclipse.jgit.errors,
org.eclipse.jgit.storage.pack",
- org.eclipse.jgit.transport.http;version="3.5.3";
+ org.eclipse.jgit.transport.http;version="3.6.0";
uses:="javax.net.ssl",
- org.eclipse.jgit.transport.resolver;version="3.5.3";
+ org.eclipse.jgit.transport.resolver;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport",
- org.eclipse.jgit.treewalk;version="3.5.3";
+ org.eclipse.jgit.treewalk;version="3.6.0";
uses:="org.eclipse.jgit.lib,
org.eclipse.jgit.revwalk,
org.eclipse.jgit.treewalk.filter,
org.eclipse.jgit.util,
org.eclipse.jgit.dircache",
- org.eclipse.jgit.treewalk.filter;version="3.5.3";
+ org.eclipse.jgit.treewalk.filter;version="3.6.0";
uses:="org.eclipse.jgit.treewalk",
- org.eclipse.jgit.util;version="3.5.3";
+ org.eclipse.jgit.util;version="3.6.0";
uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport.http,org.eclipse.jgit.storage.file",
- org.eclipse.jgit.util.io;version="3.5.3"
+ org.eclipse.jgit.util.io;version="3.6.0"
Bundle-ActivationPolicy: lazy
Bundle-RequiredExecutionEnvironment: J2SE-1.5
Require-Bundle: com.jcraft.jsch;bundle-version="[0.1.37,0.2.0)"
diff --git a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
index 23ef26e153..3954fc2e26 100644
--- a/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
+++ b/org.eclipse.jgit/META-INF/SOURCE-MANIFEST.MF
@@ -3,5 +3,5 @@ Bundle-ManifestVersion: 2
Bundle-Name: org.eclipse.jgit - Sources
Bundle-SymbolicName: org.eclipse.jgit.source
Bundle-Vendor: Eclipse.org - JGit
-Bundle-Version: 3.5.3.201412180710-r
-Eclipse-SourceBundle: org.eclipse.jgit;version="3.5.3.201412180710-r";roots="."
+Bundle-Version: 3.6.0.qualifier
+Eclipse-SourceBundle: org.eclipse.jgit;version="3.6.0.qualifier";roots="."
diff --git a/org.eclipse.jgit/pom.xml b/org.eclipse.jgit/pom.xml
index 59f60fe718..33cd02df66 100644
--- a/org.eclipse.jgit/pom.xml
+++ b/org.eclipse.jgit/pom.xml
@@ -53,7 +53,7 @@
<parent>
<groupId>org.eclipse.jgit</groupId>
<artifactId>org.eclipse.jgit-parent</artifactId>
- <version>3.5.3.201412180710-r</version>
+ <version>3.6.0-SNAPSHOT</version>
</parent>
<artifactId>org.eclipse.jgit</artifactId>
diff --git a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
index 45021e847c..a753188e88 100644
--- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
+++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
@@ -91,7 +91,7 @@ canOnlyRevertCommitsWithOneParent=Cannot revert commit ''{0}'' because it has {1
commitDoesNotHaveGivenParent=The commit ''{0}'' does not have a parent number {1}.
cantFindObjectInReversePackIndexForTheSpecifiedOffset=Can''t find object in (reverse) pack index for the specified offset {0}
cantPassMeATree=Can't pass me a tree!
-channelMustBeInRange0_255=channel {0} must be in range [0, 255]
+channelMustBeInRange1_255=channel {0} must be in range [1, 255]
characterClassIsNotSupported=The character class {0} is not supported.
checkoutConflictWithFile=Checkout conflict with file: {0}
checkoutConflictWithFiles=Checkout conflict with files: {0}
@@ -249,6 +249,8 @@ indexFileIsInUse=Index file is in use
indexFileIsTooLargeForJgit=Index file is too large for jgit
indexSignatureIsInvalid=Index signature is invalid: {0}
indexWriteException=Modified index could not be written
+initFailedBareRepoDifferentDirs=When initializing a bare repo with directory {0} and separate git-dir {1} specified both folders must point to the same location
+initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location
inMemoryBufferLimitExceeded=In-memory buffer limit exceeded
inputStreamMustSupportMark=InputStream must support mark()
integerValueOutOfRange=Integer value {0}.{1} out of range
@@ -500,6 +502,7 @@ tagOnRepoWithoutHEADCurrentlyNotSupported=Tag on repository without HEAD current
theFactoryMustNotBeNull=The factory must not be null
timerAlreadyTerminated=Timer already terminated
topologicalSortRequired=Topological sort required.
+transactionAborted=transaction aborted
transportExceptionBadRef=Empty ref: {0}: {1}
transportExceptionEmptyRef=Empty ref: {0}
transportExceptionInvalid=Invalid {0} {1}:{2}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
index 0eb25cf11d..3787ac5117 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java
@@ -43,7 +43,6 @@
*/
package org.eclipse.jgit.api;
-import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
import java.util.ArrayList;
@@ -81,7 +80,6 @@ import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
-import org.eclipse.jgit.util.FileUtils;
/**
* Checkout a branch to the working tree.
@@ -407,14 +405,17 @@ public class CheckoutCommand extends GitCommand<Ref> {
DirCacheIterator dci = new DirCacheIterator(dc);
treeWalk.addTree(dci);
+ String previousPath = null;
+
final ObjectReader r = treeWalk.getObjectReader();
DirCacheEditor editor = dc.editor();
while (treeWalk.next()) {
- DirCacheEntry entry = dci.getDirCacheEntry();
+ String path = treeWalk.getPathString();
// Only add one edit per path
- if (entry != null && entry.getStage() > DirCacheEntry.STAGE_1)
+ if (path.equals(previousPath))
continue;
- editor.add(new PathEdit(treeWalk.getPathString()) {
+
+ editor.add(new PathEdit(path) {
public void apply(DirCacheEntry ent) {
int stage = ent.getStage();
if (stage > DirCacheEntry.STAGE_0) {
@@ -431,6 +432,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
}
});
+
+ previousPath = path;
}
editor.commit();
}
@@ -455,11 +458,8 @@ public class CheckoutCommand extends GitCommand<Ref> {
}
private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
- File file = new File(repo.getWorkTree(), entry.getPathString());
- File parentDir = file.getParentFile();
try {
- FileUtils.mkdirs(parentDir, true);
- DirCacheCheckout.checkoutEntry(repo, file, entry, reader);
+ DirCacheCheckout.checkoutEntry(repo, entry, reader);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
index 645d3e7815..de24dadff6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
@@ -86,6 +86,8 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private File directory;
+ private File gitDir;
+
private boolean bare;
private String remote = Constants.DEFAULT_REMOTE_NAME;
@@ -137,12 +139,19 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
private Repository init(URIish u) throws GitAPIException {
InitCommand command = Git.init();
command.setBare(bare);
- if (directory == null)
+ if (directory == null && gitDir == null)
directory = new File(u.getHumanishName(), Constants.DOT_GIT);
- if (directory.exists() && directory.listFiles().length != 0)
+ if (directory != null && directory.exists()
+ && directory.listFiles().length != 0)
throw new JGitInternalException(MessageFormat.format(
JGitText.get().cloneNonEmptyDirectory, directory.getName()));
- command.setDirectory(directory);
+ if (gitDir != null && gitDir.exists() && gitDir.listFiles().length != 0)
+ throw new JGitInternalException(MessageFormat.format(
+ JGitText.get().cloneNonEmptyDirectory, gitDir.getName()));
+ if (directory != null)
+ command.setDirectory(directory);
+ if (gitDir != null)
+ command.setGitDir(gitDir);
return command.call().getRepository();
}
@@ -336,18 +345,47 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
* @param directory
* the directory to clone to
* @return this instance
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
*/
public CloneCommand setDirectory(File directory) {
+ validateDirs(directory, gitDir, bare);
this.directory = directory;
return this;
}
/**
+ * @param gitDir
+ * the repository meta directory
+ * @return this instance
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
+ * @since 3.6
+ */
+ public CloneCommand setGitDir(File gitDir) {
+ validateDirs(directory, gitDir, bare);
+ this.gitDir = gitDir;
+ return this;
+ }
+
+ /**
* @param bare
* whether the cloned repository is bare or not
* @return this instance
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
*/
- public CloneCommand setBare(boolean bare) {
+ public CloneCommand setBare(boolean bare) throws IllegalStateException {
+ validateDirs(directory, gitDir, bare);
this.bare = bare;
return this;
}
@@ -438,4 +476,21 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> {
this.noCheckout = noCheckout;
return this;
}
+
+ private static void validateDirs(File directory, File gitDir, boolean bare)
+ throws IllegalStateException {
+ if (directory != null) {
+ if (bare) {
+ if (gitDir != null && !gitDir.equals(directory))
+ throw new IllegalStateException(MessageFormat.format(
+ JGitText.get().initFailedBareRepoDifferentDirs,
+ gitDir, directory));
+ } else {
+ if (gitDir != null && gitDir.equals(directory))
+ throw new IllegalStateException(MessageFormat.format(
+ JGitText.get().initFailedNonBareRepoSameDirs,
+ gitDir, directory));
+ }
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
index 2ca8422fe4..08e41e4b0e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java
@@ -42,17 +42,7 @@
*/
package org.eclipse.jgit.api;
-import org.eclipse.jgit.api.errors.GitAPIException;
-import org.eclipse.jgit.api.errors.JGitInternalException;
-import org.eclipse.jgit.api.errors.RefNotFoundException;
-import org.eclipse.jgit.errors.IncorrectObjectTypeException;
-import org.eclipse.jgit.errors.MissingObjectException;
-import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
-import org.eclipse.jgit.lib.ObjectId;
-import org.eclipse.jgit.lib.Ref;
-import org.eclipse.jgit.lib.Repository;
-import org.eclipse.jgit.revwalk.*;
+import static org.eclipse.jgit.lib.Constants.R_TAGS;
import java.io.IOException;
import java.text.MessageFormat;
@@ -63,7 +53,20 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
-import static org.eclipse.jgit.lib.Constants.R_TAGS;
+import org.eclipse.jgit.api.errors.GitAPIException;
+import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.api.errors.RefNotFoundException;
+import org.eclipse.jgit.errors.IncorrectObjectTypeException;
+import org.eclipse.jgit.errors.MissingObjectException;
+import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.lib.Constants;
+import org.eclipse.jgit.lib.ObjectId;
+import org.eclipse.jgit.lib.Ref;
+import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.revwalk.RevCommit;
+import org.eclipse.jgit.revwalk.RevFlag;
+import org.eclipse.jgit.revwalk.RevFlagSet;
+import org.eclipse.jgit.revwalk.RevWalk;
/**
* Given a commit, show the most recent tag that is reachable from a commit.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
index 77b84d3a36..d0f729cc62 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/GarbageCollectCommand.java
@@ -51,11 +51,16 @@ import java.util.Properties;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
import org.eclipse.jgit.internal.JGitText;
+import org.eclipse.jgit.internal.storage.dfs.DfsGarbageCollector;
+import org.eclipse.jgit.internal.storage.dfs.DfsRepository;
import org.eclipse.jgit.internal.storage.file.FileRepository;
import org.eclipse.jgit.internal.storage.file.GC;
import org.eclipse.jgit.internal.storage.file.GC.RepoStatistics;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.lib.StoredConfig;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.util.GitDateParser;
/**
@@ -63,25 +68,40 @@ import org.eclipse.jgit.util.GitDateParser;
* supported options and arguments of this command and a {@link #call()} method
* to finally execute the command. Each instance of this class should only be
* used for one invocation of the command (means: one call to {@link #call()})
- *
+ *
* @since 2.2
* @see <a href="http://www.kernel.org/pub/software/scm/git/docs/git-gc.html"
* >Git documentation about gc</a>
*/
public class GarbageCollectCommand extends GitCommand<Properties> {
+ /**
+ * Default value of maximum delta chain depth during aggressive garbage
+ * collection: {@value}
+ *
+ * @since 3.6
+ */
+ public static final int DEFAULT_GC_AGGRESSIVE_DEPTH = 250;
+
+ /**
+ * Default window size during packing during aggressive garbage collection:
+ * * {@value}
+ *
+ * @since 3.6
+ */
+ public static final int DEFAULT_GC_AGGRESSIVE_WINDOW = 250;
private ProgressMonitor monitor;
private Date expire;
+ private PackConfig pconfig;
+
/**
* @param repo
*/
protected GarbageCollectCommand(Repository repo) {
super(repo);
- if (!(repo instanceof FileRepository))
- throw new UnsupportedOperationException(MessageFormat.format(
- JGitText.get().unsupportedGC, repo.getClass().toString()));
+ pconfig = new PackConfig(repo);
}
/**
@@ -110,22 +130,66 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
return this;
}
+ /**
+ * Whether to use aggressive mode or not. If set to true JGit behaves more
+ * similar to native git's "git gc --aggressive". If set to
+ * <code>true</code> compressed objects found in old packs are not reused
+ * but every object is compressed again. Configuration variables
+ * pack.window and pack.depth are set to 250 for this GC.
+ *
+ * @since 3.6
+ * @param aggressive
+ * whether to turn on or off aggressive mode
+ * @return this instance
+ */
+ public GarbageCollectCommand setAggressive(boolean aggressive) {
+ if (aggressive) {
+ StoredConfig repoConfig = repo.getConfig();
+ pconfig.setDeltaSearchWindowSize(repoConfig.getInt(
+ ConfigConstants.CONFIG_GC_SECTION,
+ ConfigConstants.CONFIG_KEY_AGGRESSIVE_WINDOW,
+ DEFAULT_GC_AGGRESSIVE_WINDOW));
+ pconfig.setMaxDeltaDepth(repoConfig.getInt(
+ ConfigConstants.CONFIG_GC_SECTION,
+ ConfigConstants.CONFIG_KEY_AGGRESSIVE_DEPTH,
+ DEFAULT_GC_AGGRESSIVE_DEPTH));
+ pconfig.setReuseObjects(false);
+ } else
+ pconfig = new PackConfig(repo);
+ return this;
+ }
+
@Override
public Properties call() throws GitAPIException {
checkCallable();
- GC gc = new GC((FileRepository) repo);
- gc.setProgressMonitor(monitor);
- if (this.expire != null)
- gc.setExpire(expire);
-
try {
- gc.gc();
- return toProperties(gc.getStatistics());
+ if (repo instanceof FileRepository) {
+ GC gc = new GC((FileRepository) repo);
+ gc.setPackConfig(pconfig);
+ gc.setProgressMonitor(monitor);
+ if (this.expire != null)
+ gc.setExpire(expire);
+
+ try {
+ gc.gc();
+ return toProperties(gc.getStatistics());
+ } catch (ParseException e) {
+ throw new JGitInternalException(JGitText.get().gcFailed, e);
+ }
+ } else if (repo instanceof DfsRepository) {
+ DfsGarbageCollector gc =
+ new DfsGarbageCollector((DfsRepository) repo);
+ gc.setPackConfig(pconfig);
+ gc.pack(monitor);
+ return new Properties();
+ } else {
+ throw new UnsupportedOperationException(MessageFormat.format(
+ JGitText.get().unsupportedGC,
+ repo.getClass().toString()));
+ }
} catch (IOException e) {
throw new JGitInternalException(JGitText.get().gcFailed, e);
- } catch (ParseException e) {
- throw new JGitInternalException(JGitText.get().gcFailed, e);
}
}
@@ -139,8 +203,12 @@ public class GarbageCollectCommand extends GitCommand<Properties> {
*/
public Properties getStatistics() throws GitAPIException {
try {
- GC gc = new GC((FileRepository) repo);
- return toProperties(gc.getStatistics());
+ if (repo instanceof FileRepository) {
+ GC gc = new GC((FileRepository) repo);
+ return toProperties(gc.getStatistics());
+ } else {
+ return new Properties();
+ }
} catch (IOException e) {
throw new JGitInternalException(
JGitText.get().couldNotGetRepoStatistics, e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
index bf43e90d42..37a788e85c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/InitCommand.java
@@ -44,13 +44,16 @@ package org.eclipse.jgit.api;
import java.io.File;
import java.io.IOException;
+import java.text.MessageFormat;
import java.util.concurrent.Callable;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.JGitInternalException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.Repository;
import org.eclipse.jgit.lib.RepositoryBuilder;
+import org.eclipse.jgit.util.SystemReader;
/**
* Create an empty git repository or reinitalize an existing one
@@ -61,6 +64,8 @@ import org.eclipse.jgit.lib.RepositoryBuilder;
public class InitCommand implements Callable<Git> {
private File directory;
+ private File gitDir;
+
private boolean bare;
/**
@@ -74,18 +79,36 @@ public class InitCommand implements Callable<Git> {
if (bare)
builder.setBare();
builder.readEnvironment();
+ if (gitDir != null)
+ builder.setGitDir(gitDir);
+ else
+ gitDir = builder.getGitDir();
if (directory != null) {
- File d = directory;
- if (!bare)
- d = new File(d, Constants.DOT_GIT);
- builder.setGitDir(d);
+ if (bare)
+ builder.setGitDir(directory);
+ else {
+ builder.setWorkTree(directory);
+ if (gitDir == null)
+ builder.setGitDir(new File(directory, Constants.DOT_GIT));
+ }
} else if (builder.getGitDir() == null) {
- File d = new File("."); //$NON-NLS-1$
- if (d.getParentFile() != null)
- d = d.getParentFile();
+ String dStr = SystemReader.getInstance()
+ .getProperty("user.dir"); //$NON-NLS-1$
+ if (dStr == null)
+ dStr = "."; //$NON-NLS-1$
+ File d = new File(dStr);
if (!bare)
d = new File(d, Constants.DOT_GIT);
builder.setGitDir(d);
+ } else {
+ // directory was not set but gitDir was set
+ if (!bare) {
+ String dStr = SystemReader.getInstance().getProperty(
+ "user.dir"); //$NON-NLS-1$
+ if (dStr == null)
+ dStr = "."; //$NON-NLS-1$
+ builder.setWorkTree(new File(dStr));
+ }
}
Repository repository = builder.build();
if (!repository.getObjectDatabase().exists())
@@ -103,20 +126,67 @@ public class InitCommand implements Callable<Git> {
* @param directory
* the directory to init to
* @return this instance
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
*/
- public InitCommand setDirectory(File directory) {
+ public InitCommand setDirectory(File directory)
+ throws IllegalStateException {
+ validateDirs(directory, gitDir, bare);
this.directory = directory;
return this;
}
/**
+ * @param gitDir
+ * the repository meta directory
+ * @return this instance
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
+ * @since 3.6
+ */
+ public InitCommand setGitDir(File gitDir)
+ throws IllegalStateException {
+ validateDirs(directory, gitDir, bare);
+ this.gitDir = gitDir;
+ return this;
+ }
+
+ private static void validateDirs(File directory, File gitDir, boolean bare)
+ throws IllegalStateException {
+ if (directory != null) {
+ if (bare) {
+ if (gitDir != null && !gitDir.equals(directory))
+ throw new IllegalStateException(MessageFormat.format(
+ JGitText.get().initFailedBareRepoDifferentDirs,
+ gitDir, directory));
+ } else {
+ if (gitDir != null && gitDir.equals(directory))
+ throw new IllegalStateException(MessageFormat.format(
+ JGitText.get().initFailedNonBareRepoSameDirs,
+ gitDir, directory));
+ }
+ }
+ }
+
+ /**
* @param bare
* whether the repository is bare or not
+ * @throws IllegalStateException
+ * if the combination of directory, gitDir and bare is illegal.
+ * E.g. if for a non-bare repository directory and gitDir point
+ * to the same directory of if for a bare repository both
+ * directory and gitDir are specified
* @return this instance
*/
public InitCommand setBare(boolean bare) {
+ validateDirs(directory, gitDir, bare);
this.bare = bare;
return this;
}
-
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
index 7cc682e6b5..47424a9074 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java
@@ -99,6 +99,7 @@ import org.eclipse.jgit.merge.MergeStrategy;
import org.eclipse.jgit.revwalk.RevCommit;
import org.eclipse.jgit.revwalk.RevWalk;
import org.eclipse.jgit.revwalk.filter.RevFilter;
+import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils;
@@ -294,7 +295,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> {
walk.parseCommit(repo.resolve(Constants.HEAD)),
upstreamCommit)) {
org.eclipse.jgit.api.Status status = Git.wrap(repo)
- .status().call();
+ .status().setIgnoreSubmodules(IgnoreSubmoduleMode.ALL).call();
if (status.hasUncommittedChanges()) {
List<String> list = new ArrayList<String>();
list.addAll(status.getUncommittedChanges());
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
index 8cd78aebe1..4536af1be0 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ReflogCommand.java
@@ -48,6 +48,7 @@ import java.util.Collection;
import org.eclipse.jgit.api.errors.GitAPIException;
import org.eclipse.jgit.api.errors.InvalidRefNameException;
+import org.eclipse.jgit.api.errors.RefNotFoundException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ReflogEntry;
@@ -97,6 +98,9 @@ public class ReflogCommand extends GitCommand<Collection<ReflogEntry>> {
try {
ReflogReader reader = repo.getReflogReader(ref);
+ if (reader == null)
+ throw new RefNotFoundException(MessageFormat.format(
+ JGitText.get().refNotResolved, ref));
return reader.getReverseEntries();
} catch (IOException e) {
throw new InvalidRefNameException(MessageFormat.format(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
index 7c2192dd9f..17b1242308 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java
@@ -195,6 +195,9 @@ public class ResetCommand extends GitCommand<Ref> {
result = repo.getRef(Constants.HEAD);
}
+ if (mode == null)
+ mode = ResetType.MIXED;
+
switch (mode) {
case HARD:
checkoutIndex(commitTree);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
index 4d54e0be97..356723db4b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StashApplyCommand.java
@@ -42,7 +42,6 @@
*/
package org.eclipse.jgit.api;
-import java.io.File;
import java.io.IOException;
import java.text.MessageFormat;
@@ -329,9 +328,8 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
private void resetUntracked(RevTree tree) throws CheckoutConflictException,
IOException {
- TreeWalk walk = null;
+ TreeWalk walk = new TreeWalk(repo); // maybe NameConflictTreeWalk;
try {
- walk = new TreeWalk(repo); // maybe NameConflictTreeWalk?
walk.addTree(tree);
walk.addTree(new FileTreeIterator(repo));
walk.setRecursive(true);
@@ -362,15 +360,13 @@ public class StashApplyCommand extends GitCommand<ObjectId> {
checkoutPath(entry, reader);
}
} finally {
- if (walk != null)
- walk.release();
+ walk.release();
}
}
private void checkoutPath(DirCacheEntry entry, ObjectReader reader) {
try {
- File file = new File(repo.getWorkTree(), entry.getPathString());
- DirCacheCheckout.checkoutEntry(repo, file, entry, reader);
+ DirCacheCheckout.checkoutEntry(repo, entry, reader);
} catch (IOException e) {
throw new JGitInternalException(MessageFormat.format(
JGitText.get().checkoutConflictWithFile,
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
index dee0a31b91..9752195b95 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/StatusCommand.java
@@ -53,6 +53,7 @@ import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.IndexDiff;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
@@ -72,6 +73,8 @@ public class StatusCommand extends GitCommand<Status> {
private List<String> paths = null;
private ProgressMonitor progressMonitor = null;
+ private IgnoreSubmoduleMode ignoreSubmoduleMode = null;
+
/**
* @param repo
*/
@@ -80,6 +83,16 @@ public class StatusCommand extends GitCommand<Status> {
}
/**
+ * @param mode
+ * @return {@code this}
+ * @since 3.6
+ */
+ public StatusCommand setIgnoreSubmodules(IgnoreSubmoduleMode mode) {
+ ignoreSubmoduleMode = mode;
+ return this;
+ }
+
+ /**
* Show only the status of files which match the given paths. The path must
* either name a file or a directory exactly. All paths are always relative
* to the repository root. If a directory is specified all files recursively
@@ -127,6 +140,8 @@ public class StatusCommand extends GitCommand<Status> {
try {
IndexDiff diff = new IndexDiff(repo, Constants.HEAD, workingTreeIt);
+ if (ignoreSubmoduleMode != null)
+ diff.setIgnoreSubmoduleMode(ignoreSubmoduleMode);
if (paths != null)
diff.setFilter(PathFilterGroup.createFromStrings(paths));
if (progressMonitor == null)
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
index de1a3e9fd0..81a30156a6 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/SubmoduleUpdateCommand.java
@@ -42,6 +42,7 @@
*/
package org.eclipse.jgit.api;
+import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
@@ -163,6 +164,8 @@ public class SubmoduleUpdateCommand extends
configure(clone);
clone.setURI(url);
clone.setDirectory(generator.getDirectory());
+ clone.setGitDir(new File(new File(repo.getDirectory(),
+ Constants.MODULES), generator.getPath()));
if (monitor != null)
clone.setProgressMonitor(monitor);
submoduleRepo = clone.call().getRepository();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
index 399c1d130a..015d9d6a85 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java
@@ -58,7 +58,6 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.internal.JGitText;
-import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig.AutoCRLF;
import org.eclipse.jgit.lib.CoreConfig.SymLinks;
import org.eclipse.jgit.lib.FileMode;
@@ -447,19 +446,9 @@ public class DirCacheCheckout {
removeEmptyParents(file);
for (String path : updated.keySet()) {
- // ... create/overwrite this file ...
- file = new File(repo.getWorkTree(), path);
- if (!file.getParentFile().mkdirs()) {
- // ignore
- }
-
DirCacheEntry entry = dc.getEntry(path);
-
- // submodules are handled with separate operations
- if (FileMode.GITLINK.equals(entry.getRawMode()))
- continue;
-
- checkoutEntry(repo, file, entry, objectReader);
+ if (!FileMode.GITLINK.equals(entry.getRawMode()))
+ checkoutEntry(repo, entry, objectReader);
}
// commit the index builder - a new index is persisted
@@ -698,7 +687,7 @@ public class DirCacheCheckout {
// Nothing in Index
// At least one of Head, Index, Merge is not empty
// make sure not to overwrite untracked files
- if (f != null) {
+ if (f != null && !f.isEntryIgnored()) {
// A submodule is not a file. We should ignore it
if (!FileMode.GITLINK.equals(mMode)) {
// a dirty worktree: the index is empty but we have a
@@ -1158,17 +1147,18 @@ public class DirCacheCheckout {
*
* @param repository
* @param f
- * the file to be modified. The parent directory for this file
- * has to exist already
+ * this parameter is ignored.
* @param entry
* the entry containing new mode and content
* @throws IOException
+ * @deprecated Use the overloaded form that accepts {@link ObjectReader}.
*/
+ @Deprecated
public static void checkoutEntry(final Repository repository, File f,
DirCacheEntry entry) throws IOException {
ObjectReader or = repository.newObjectReader();
try {
- checkoutEntry(repository, f, entry, repository.newObjectReader());
+ checkoutEntry(repository, f, entry, or);
} finally {
or.release();
}
@@ -1188,19 +1178,51 @@ public class DirCacheCheckout {
*
* @param repo
* @param f
- * the file to be modified. The parent directory for this file
- * has to exist already
+ * this parameter is ignored.
* @param entry
* the entry containing new mode and content
* @param or
* object reader to use for checkout
* @throws IOException
+ * @deprecated Do not pass File object.
*/
+ @Deprecated
public static void checkoutEntry(final Repository repo, File f,
DirCacheEntry entry, ObjectReader or) throws IOException {
+ if (f == null || repo.getWorkTree() == null)
+ throw new IllegalArgumentException();
+ if (!f.equals(new File(repo.getWorkTree(), entry.getPathString())))
+ throw new IllegalArgumentException();
+ checkoutEntry(repo, entry, or);
+ }
+
+ /**
+ * Updates the file in the working tree with content and mode from an entry
+ * in the index. The new content is first written to a new temporary file in
+ * the same directory as the real file. Then that new file is renamed to the
+ * final filename.
+ *
+ * <p>
+ * TODO: this method works directly on File IO, we may need another
+ * abstraction (like WorkingTreeIterator). This way we could tell e.g.
+ * Eclipse that Files in the workspace got changed
+ * </p>
+ *
+ * @param repo
+ * repository managing the destination work tree.
+ * @param entry
+ * the entry containing new mode and content
+ * @param or
+ * object reader to use for checkout
+ * @throws IOException
+ * @since 3.6
+ */
+ public static void checkoutEntry(Repository repo, DirCacheEntry entry,
+ ObjectReader or) throws IOException {
ObjectLoader ol = or.open(entry.getObjectId());
+ File f = new File(repo.getWorkTree(), entry.getPathString());
File parentDir = f.getParentFile();
- parentDir.mkdirs();
+ FileUtils.mkdirs(parentDir, true);
FS fs = repo.getFS();
WorkingTreeOptions opt = repo.getConfig().get(WorkingTreeOptions.KEY);
if (entry.getFileMode() == FileMode.SYMLINK
@@ -1210,42 +1232,40 @@ public class DirCacheCheckout {
fs.createSymLink(f, target);
entry.setLength(bytes.length);
entry.setLastModified(fs.lastModified(f));
- } else {
- File tmpFile = File.createTempFile(
- "._" + f.getName(), null, parentDir); //$NON-NLS-1$
- FileOutputStream rawChannel = new FileOutputStream(tmpFile);
- OutputStream channel;
- if (opt.getAutoCRLF() == AutoCRLF.TRUE)
- channel = new AutoCRLFOutputStream(rawChannel);
- else
- channel = rawChannel;
- try {
- ol.copyTo(channel);
- } finally {
- channel.close();
- }
- if (opt.isFileMode() && fs.supportsExecute()) {
- if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
- if (!fs.canExecute(tmpFile))
- fs.setExecute(tmpFile, true);
- } else {
- if (fs.canExecute(tmpFile))
- fs.setExecute(tmpFile, false);
- }
- }
- try {
- FileUtils.rename(tmpFile, f);
- } catch (IOException e) {
- throw new IOException(MessageFormat.format(
- JGitText.get().renameFileFailed, tmpFile.getPath(),
- f.getPath()));
+ return;
+ }
+
+ File tmpFile = File.createTempFile(
+ "._" + f.getName(), null, parentDir); //$NON-NLS-1$
+ OutputStream channel = new FileOutputStream(tmpFile);
+ if (opt.getAutoCRLF() == AutoCRLF.TRUE)
+ channel = new AutoCRLFOutputStream(channel);
+ try {
+ ol.copyTo(channel);
+ } finally {
+ channel.close();
+ }
+ entry.setLength(opt.getAutoCRLF() == AutoCRLF.TRUE ? //
+ tmpFile.length() // AutoCRLF wants on-disk-size
+ : (int) ol.getSize());
+
+ if (opt.isFileMode() && fs.supportsExecute()) {
+ if (FileMode.EXECUTABLE_FILE.equals(entry.getRawMode())) {
+ if (!fs.canExecute(tmpFile))
+ fs.setExecute(tmpFile, true);
+ } else {
+ if (fs.canExecute(tmpFile))
+ fs.setExecute(tmpFile, false);
}
}
+ try {
+ FileUtils.rename(tmpFile, f);
+ } catch (IOException e) {
+ throw new IOException(MessageFormat.format(
+ JGitText.get().renameFileFailed, tmpFile.getPath(),
+ f.getPath()));
+ }
entry.setLastModified(f.lastModified());
- if (opt.getAutoCRLF() != AutoCRLF.FALSE)
- entry.setLength(f.length()); // AutoCRLF wants on-disk-size
- else
- entry.setLength((int) ol.getSize());
}
private static void checkValidPath(CanonicalTreeParser t)
@@ -1264,22 +1284,12 @@ public class DirCacheCheckout {
* @throws InvalidPathException
* if the path is invalid
* @since 3.3
+ * @deprecated Use {@link SystemReader#checkPath(String)}.
*/
+ @Deprecated
public static void checkValidPath(String path) throws InvalidPathException {
- ObjectChecker chk = new ObjectChecker()
- .setSafeForWindows(SystemReader.getInstance().isWindows())
- .setSafeForMacOS(SystemReader.getInstance().isMacOS());
-
- byte[] bytes = Constants.encode(path);
- int segmentStart = 0;
try {
- for (int i = 0; i < bytes.length; i++) {
- if (bytes[i] == '/') {
- chk.checkPathSegment(bytes, segmentStart, i);
- segmentStart = i + 1;
- }
- }
- chk.checkPathSegment(bytes, segmentStart, bytes.length);
+ SystemReader.getInstance().checkPath(path);
} catch (CorruptObjectException e) {
InvalidPathException p = new InvalidPathException(path);
p.initCause(e);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
index 1c4c3db0d3..4207513e70 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java
@@ -193,6 +193,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
try {
return readFileFromRepo(repo, ref, path);
} finally {
+ repo.close();
FileUtils.delete(dir, FileUtils.RECURSIVE);
}
}
@@ -860,6 +861,7 @@ public class RepoCommand extends GitCommand<RevCommit> {
if (revision != null) {
Git sub = new Git(subRepo);
sub.checkout().setName(findRef(revision, subRepo)).call();
+ subRepo.close();
git.add().addFilepattern(name).call();
}
for (CopyFile copyfile : copyfiles) {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
new file mode 100644
index 0000000000..02863bd16a
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/FastIgnoreRule.java
@@ -0,0 +1,229 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore;
+
+import static org.eclipse.jgit.ignore.internal.Strings.stripTrailing;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.ignore.internal.IMatcher;
+import org.eclipse.jgit.ignore.internal.PathMatcher;
+
+/**
+ * "Fast" (compared with IgnoreRule) git ignore rule implementation supporting
+ * also double star <code>**<code> pattern.
+ * <p>
+ * This class is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public class FastIgnoreRule {
+
+ /**
+ * Character used as default path separator for ignore entries
+ */
+ public static final char PATH_SEPARATOR = '/';
+
+ private static final NoResultMatcher NO_MATCH = new NoResultMatcher();
+
+ private final IMatcher matcher;
+
+ private final boolean inverse;
+
+ private final boolean dirOnly;
+
+ /**
+ *
+ * @param pattern
+ * ignore pattern as described in <a href=
+ * "https://www.kernel.org/pub/software/scm/git/docs/gitignore.html"
+ * >git manual</a>. If pattern is invalid or is not a pattern
+ * (comment), this rule doesn't match anything.
+ */
+ public FastIgnoreRule(String pattern) {
+ if (pattern == null)
+ throw new IllegalArgumentException("Pattern must not be null!"); //$NON-NLS-1$
+ if (pattern.length() == 0) {
+ dirOnly = false;
+ inverse = false;
+ this.matcher = NO_MATCH;
+ return;
+ }
+ inverse = pattern.charAt(0) == '!';
+ if (inverse) {
+ pattern = pattern.substring(1);
+ if (pattern.length() == 0) {
+ dirOnly = false;
+ this.matcher = NO_MATCH;
+ return;
+ }
+ }
+ if (pattern.charAt(0) == '#') {
+ this.matcher = NO_MATCH;
+ dirOnly = false;
+ } else {
+ dirOnly = pattern.charAt(pattern.length() - 1) == PATH_SEPARATOR;
+ if (dirOnly) {
+ pattern = stripTrailing(pattern, PATH_SEPARATOR);
+ if (pattern.length() == 0) {
+ this.matcher = NO_MATCH;
+ return;
+ }
+ }
+ IMatcher m;
+ try {
+ m = PathMatcher.createPathMatcher(pattern,
+ Character.valueOf(PATH_SEPARATOR), dirOnly);
+ } catch (InvalidPatternException e) {
+ m = NO_MATCH;
+ }
+ this.matcher = m;
+ }
+ }
+
+ /**
+ * Returns true if a match was made. <br>
+ * This function does NOT return the actual ignore status of the target!
+ * Please consult {@link #getResult()} for the negation status. The actual
+ * ignore status may be true or false depending on whether this rule is an
+ * ignore rule or a negation rule.
+ *
+ * @param path
+ * Name pattern of the file, relative to the base directory of
+ * this rule
+ * @param directory
+ * Whether the target file is a directory or not
+ * @return True if a match was made. This does not necessarily mean that the
+ * target is ignored. Call {@link #getResult() getResult()} for the
+ * result.
+ */
+ public boolean isMatch(String path, boolean directory) {
+ if (path == null)
+ return false;
+ if (path.length() == 0)
+ return false;
+ boolean match = matcher.matches(path, directory);
+ return match;
+ }
+
+ /**
+ * @return True if the pattern is just a file name and not a path
+ */
+ public boolean getNameOnly() {
+ return !(matcher instanceof PathMatcher);
+ }
+
+ /**
+ *
+ * @return True if the pattern should match directories only
+ */
+ public boolean dirOnly() {
+ return dirOnly;
+ }
+
+ /**
+ * Indicates whether the rule is non-negation or negation.
+ *
+ * @return True if the pattern had a "!" in front of it
+ */
+ public boolean getNegation() {
+ return inverse;
+ }
+
+ /**
+ * Indicates whether the rule is non-negation or negation.
+ *
+ * @return True if the target is to be ignored, false otherwise.
+ */
+ public boolean getResult() {
+ return !inverse;
+ }
+
+ @Override
+ public String toString() {
+ StringBuilder sb = new StringBuilder();
+ if (inverse)
+ sb.append('!');
+ sb.append(matcher);
+ if (dirOnly)
+ sb.append(PATH_SEPARATOR);
+ return sb.toString();
+
+ }
+
+ @Override
+ public int hashCode() {
+ final int prime = 31;
+ int result = 1;
+ result = prime * result + (inverse ? 1231 : 1237);
+ result = prime * result + (dirOnly ? 1231 : 1237);
+ result = prime * result + ((matcher == null) ? 0 : matcher.hashCode());
+ return result;
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof FastIgnoreRule))
+ return false;
+
+ FastIgnoreRule other = (FastIgnoreRule) obj;
+ if (inverse != other.inverse)
+ return false;
+ if (dirOnly != other.dirOnly)
+ return false;
+ return matcher.equals(other.matcher);
+ }
+
+ static final class NoResultMatcher implements IMatcher {
+
+ public boolean matches(String path, boolean assumeDirectory) {
+ return false;
+ }
+
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ return false;
+ }
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
index 2cddddbe11..efaeacd533 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreNode.java
@@ -67,15 +67,23 @@ public class IgnoreNode {
IGNORED,
/** The ignore status is unknown, check inherited rules. */
- CHECK_PARENT;
+ CHECK_PARENT,
+
+ /**
+ * The first previous (parent) ignore rule match (if any) should be
+ * negated, and then inherited rules applied.
+ *
+ * @since 3.6
+ */
+ CHECK_PARENT_NEGATE_FIRST_MATCH;
}
/** The rules that have been parsed into this node. */
- private final List<IgnoreRule> rules;
+ private final List<FastIgnoreRule> rules;
/** Create an empty ignore node with no rules. */
public IgnoreNode() {
- rules = new ArrayList<IgnoreRule>();
+ rules = new ArrayList<FastIgnoreRule>();
}
/**
@@ -84,7 +92,7 @@ public class IgnoreNode {
* @param rules
* list of rules.
**/
- public IgnoreNode(List<IgnoreRule> rules) {
+ public IgnoreNode(List<FastIgnoreRule> rules) {
this.rules = rules;
}
@@ -103,7 +111,7 @@ public class IgnoreNode {
while ((txt = br.readLine()) != null) {
txt = txt.trim();
if (txt.length() > 0 && !txt.startsWith("#") && !txt.equals("/")) //$NON-NLS-1$ //$NON-NLS-2$
- rules.add(new IgnoreRule(txt));
+ rules.add(new FastIgnoreRule(txt));
}
}
@@ -112,7 +120,7 @@ public class IgnoreNode {
}
/** @return list of all ignore rules held by this node. */
- public List<IgnoreRule> getRules() {
+ public List<FastIgnoreRule> getRules() {
return Collections.unmodifiableList(rules);
}
@@ -128,19 +136,63 @@ public class IgnoreNode {
* @return status of the path.
*/
public MatchResult isIgnored(String entryPath, boolean isDirectory) {
+ return isIgnored(entryPath, isDirectory, false);
+ }
+
+ /**
+ * Determine if an entry path matches an ignore rule.
+ *
+ * @param entryPath
+ * the path to test. The path must be relative to this ignore
+ * node's own repository path, and in repository path format
+ * (uses '/' and not '\').
+ * @param isDirectory
+ * true if the target item is a directory.
+ * @param negateFirstMatch
+ * true if the first match should be negated
+ * @return status of the path.
+ * @since 3.6
+ */
+ public MatchResult isIgnored(String entryPath, boolean isDirectory,
+ boolean negateFirstMatch) {
if (rules.isEmpty())
- return MatchResult.CHECK_PARENT;
+ if (negateFirstMatch)
+ return MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH;
+ else
+ return MatchResult.CHECK_PARENT;
// Parse rules in the reverse order that they were read
for (int i = rules.size() - 1; i > -1; i--) {
- IgnoreRule rule = rules.get(i);
+ FastIgnoreRule rule = rules.get(i);
if (rule.isMatch(entryPath, isDirectory)) {
- if (rule.getResult())
- return MatchResult.IGNORED;
- else
- return MatchResult.NOT_IGNORED;
+ if (rule.getResult()) {
+ // rule matches: path could be ignored
+ if (negateFirstMatch)
+ // ignore current match, reset "negate" flag, continue
+ negateFirstMatch = false;
+ else
+ // valid match, just return
+ return MatchResult.IGNORED;
+ } else {
+ // found negated rule
+ if (negateFirstMatch)
+ // not possible to re-include excluded ignore rule
+ return MatchResult.NOT_IGNORED;
+ else
+ // set the flag and continue
+ negateFirstMatch = true;
+ }
}
}
+ if (negateFirstMatch)
+ // negated rule found but there is no previous rule in *this* file
+ return MatchResult.CHECK_PARENT_NEGATE_FIRST_MATCH;
+ // *this* file has no matching rules
return MatchResult.CHECK_PARENT;
}
+
+ @Override
+ public String toString() {
+ return rules.toString();
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
index 42bbd9e9b8..f14d1bd0b2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/IgnoreRule.java
@@ -46,11 +46,16 @@ import org.eclipse.jgit.errors.InvalidPatternException;
import org.eclipse.jgit.fnmatch.FileNameMatcher;
/**
- * A single ignore rule corresponding to one line in a .gitignore or
- * ignore file. Parses the ignore pattern
+ * A single ignore rule corresponding to one line in a .gitignore or ignore
+ * file. Parses the ignore pattern
*
* Inspiration from: Ferry Huberts
+ *
+ * @deprecated this rule does not support double star pattern and is slow
+ * parsing glob expressions. Consider to use {@link FastIgnoreRule}
+ * instead. See https://bugs.eclipse.org/bugs/show_bug.cgi?id=440732
*/
+@Deprecated
public class IgnoreRule {
private String pattern;
private boolean negation;
@@ -270,4 +275,4 @@ public class IgnoreRule {
public String toString() {
return pattern;
}
-} \ No newline at end of file
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
new file mode 100644
index 0000000000..4e90d8c3cb
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/AbstractMatcher.java
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+/**
+ * Base class for default methods as {@link #toString()} and such.
+ * <p>
+ * This class is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public abstract class AbstractMatcher implements IMatcher {
+
+ final boolean dirOnly;
+
+ final String pattern;
+
+ /**
+ * @param pattern
+ * string to parse
+ * @param dirOnly
+ * true if this matcher should match only directories
+ */
+ AbstractMatcher(String pattern, boolean dirOnly) {
+ this.pattern = pattern;
+ this.dirOnly = dirOnly;
+ }
+
+ @Override
+ public String toString() {
+ return pattern;
+ }
+
+ @Override
+ public int hashCode() {
+ return pattern.hashCode();
+ }
+
+ @Override
+ public boolean equals(Object obj) {
+ if (this == obj)
+ return true;
+ if (!(obj instanceof AbstractMatcher))
+ return false;
+ AbstractMatcher other = (AbstractMatcher) obj;
+ return dirOnly == other.dirOnly && pattern.equals(other.pattern);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
new file mode 100644
index 0000000000..10b5e49e1f
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/IMatcher.java
@@ -0,0 +1,80 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+/**
+ * Generic string matcher
+ *
+ * @since 3.6
+ */
+public interface IMatcher {
+
+ /**
+ * Matches entire given string
+ *
+ * @param path
+ * string which is not null, but might be empty
+ * @param assumeDirectory
+ * true to assume this path as directory (even if it doesn't end
+ * with a slash)
+ * @return true if this matcher pattern matches given string
+ */
+ boolean matches(String path, boolean assumeDirectory);
+
+ /**
+ * Matches only part of given string
+ *
+ * @param segment
+ * string which is not null, but might be empty
+ * @param startIncl
+ * start index, inclusive
+ * @param endExcl
+ * end index, exclusive
+ * @param assumeDirectory
+ * true to assume this path as directory (even if it doesn't end
+ * with a slash)
+ * @return true if this matcher pattern matches given string
+ */
+ boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory);
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
new file mode 100644
index 0000000000..f1153d9c69
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/LeadingAsteriskMatcher.java
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+/**
+ * Matcher for simple regex patterns starting with an asterisk, e.g. "*.tmp"
+ *
+ * @since 3.6
+ */
+public class LeadingAsteriskMatcher extends NameMatcher {
+
+ LeadingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
+ super(pattern, pathSeparator, dirOnly);
+
+ if (subPattern.charAt(0) != '*')
+ throw new IllegalArgumentException(
+ "Pattern must have leading asterisk: " + pattern); //$NON-NLS-1$
+ }
+
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ // faster local access, same as in string.indexOf()
+ String s = subPattern;
+
+ // we don't need to count '*' character itself
+ int subLength = s.length() - 1;
+ // simple /*/ pattern
+ if (subLength == 0)
+ return true;
+
+ if (subLength > (endExcl - startIncl))
+ return false;
+
+ for (int i = subLength, j = endExcl - 1; i > 0; i--, j--) {
+ char c1 = s.charAt(i);
+ char c2 = segment.charAt(j);
+ if (c1 != c2)
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
new file mode 100644
index 0000000000..6c4c8092fe
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/NameMatcher.java
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+import static org.eclipse.jgit.ignore.internal.Strings.getPathSeparator;
+
+/**
+ * Matcher built from patterns for file names (single path segments). This class
+ * is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public class NameMatcher extends AbstractMatcher {
+
+ final boolean beginning;
+
+ final char slash;
+
+ final String subPattern;
+
+ NameMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
+ super(pattern, dirOnly);
+ slash = getPathSeparator(pathSeparator);
+ beginning = pattern.length() == 0 ? false : pattern.charAt(0) == slash;
+ if (!beginning)
+ this.subPattern = pattern;
+ else
+ this.subPattern = pattern.substring(1);
+ }
+
+ public boolean matches(String path, boolean assumeDirectory) {
+ int end = 0;
+ int firstChar = 0;
+ do {
+ firstChar = getFirstNotSlash(path, end);
+ end = getFirstSlash(path, firstChar);
+ boolean match = matches(path, firstChar, end, assumeDirectory);
+ if (match)
+ // make sure the directory matches: either if we are done with
+ // segment and there is next one, or if the directory is assumed
+ return !dirOnly ? true : (end > 0 && end != path.length())
+ || assumeDirectory;
+ } while (!beginning && end != path.length());
+ return false;
+ }
+
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ // faster local access, same as in string.indexOf()
+ String s = subPattern;
+ if (s.length() != (endExcl - startIncl))
+ return false;
+ for (int i = 0; i < s.length(); i++) {
+ char c1 = s.charAt(i);
+ char c2 = segment.charAt(i + startIncl);
+ if (c1 != c2)
+ return false;
+ }
+ return true;
+ }
+
+ private int getFirstNotSlash(String s, int start) {
+ int slashIdx = s.indexOf(slash, start);
+ return slashIdx == start ? start + 1 : start;
+ }
+
+ private int getFirstSlash(String s, int start) {
+ int slashIdx = s.indexOf(slash, start);
+ return slashIdx == -1 ? s.length() : slashIdx;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
new file mode 100644
index 0000000000..dcecf303c4
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/PathMatcher.java
@@ -0,0 +1,254 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+import static org.eclipse.jgit.ignore.internal.Strings.checkWildCards;
+import static org.eclipse.jgit.ignore.internal.Strings.count;
+import static org.eclipse.jgit.ignore.internal.Strings.getPathSeparator;
+import static org.eclipse.jgit.ignore.internal.Strings.isWildCard;
+import static org.eclipse.jgit.ignore.internal.Strings.split;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.ignore.FastIgnoreRule;
+import org.eclipse.jgit.ignore.internal.Strings.PatternState;
+
+/**
+ * Matcher built by patterns consists of multiple path segments.
+ * <p>
+ * This class is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public class PathMatcher extends AbstractMatcher {
+
+ private static final WildMatcher WILD = WildMatcher.INSTANCE;
+
+ private final List<IMatcher> matchers;
+
+ private final char slash;
+
+ private boolean beginning;
+
+ PathMatcher(String pattern, Character pathSeparator, boolean dirOnly)
+ throws InvalidPatternException {
+ super(pattern, dirOnly);
+ slash = getPathSeparator(pathSeparator);
+ beginning = pattern.indexOf(slash) == 0;
+ if (isSimplePathWithSegments(pattern))
+ matchers = null;
+ else
+ matchers = createMatchers(split(pattern, slash), pathSeparator,
+ dirOnly);
+ }
+
+ private boolean isSimplePathWithSegments(String path) {
+ return !isWildCard(path) && count(path, slash, true) > 0;
+ }
+
+ static private List<IMatcher> createMatchers(List<String> segments,
+ Character pathSeparator, boolean dirOnly)
+ throws InvalidPatternException {
+ List<IMatcher> matchers = new ArrayList<IMatcher>(segments.size());
+ for (int i = 0; i < segments.size(); i++) {
+ String segment = segments.get(i);
+ IMatcher matcher = createNameMatcher0(segment, pathSeparator,
+ dirOnly);
+ if (matcher == WILD && i > 0
+ && matchers.get(matchers.size() - 1) == WILD)
+ // collapse wildmatchers **/** is same as **
+ continue;
+ matchers.add(matcher);
+ }
+ return matchers;
+ }
+
+ /**
+ *
+ * @param pattern
+ * @param pathSeparator
+ * if this parameter isn't null then this character will not
+ * match at wildcards(* and ? are wildcards).
+ * @param dirOnly
+ * @return never null
+ * @throws InvalidPatternException
+ */
+ public static IMatcher createPathMatcher(String pattern,
+ Character pathSeparator, boolean dirOnly)
+ throws InvalidPatternException {
+ pattern = pattern.trim();
+ char slash = Strings.getPathSeparator(pathSeparator);
+ // ignore possible leading and trailing slash
+ int slashIdx = pattern.indexOf(slash, 1);
+ if (slashIdx > 0 && slashIdx < pattern.length() - 1)
+ return new PathMatcher(pattern, pathSeparator, dirOnly);
+ return createNameMatcher0(pattern, pathSeparator, dirOnly);
+ }
+
+ private static IMatcher createNameMatcher0(String segment,
+ Character pathSeparator, boolean dirOnly)
+ throws InvalidPatternException {
+ // check if we see /** or ** segments => double star pattern
+ if (WildMatcher.WILDMATCH.equals(segment)
+ || WildMatcher.WILDMATCH2.equals(segment))
+ return WILD;
+
+ PatternState state = checkWildCards(segment);
+ switch (state) {
+ case LEADING_ASTERISK_ONLY:
+ return new LeadingAsteriskMatcher(segment, pathSeparator, dirOnly);
+ case TRAILING_ASTERISK_ONLY:
+ return new TrailingAsteriskMatcher(segment, pathSeparator, dirOnly);
+ case COMPLEX:
+ return new WildCardMatcher(segment, pathSeparator, dirOnly);
+ default:
+ return new NameMatcher(segment, pathSeparator, dirOnly);
+ }
+ }
+
+ public boolean matches(String path, boolean assumeDirectory) {
+ if (matchers == null)
+ return simpleMatch(path, assumeDirectory);
+ return iterate(path, 0, path.length(), assumeDirectory);
+ }
+
+ /*
+ * Stupid but fast string comparison: the case where we don't have to match
+ * wildcards or single segments (mean: this is multi-segment path which must
+ * be at the beginning of the another string)
+ */
+ private boolean simpleMatch(String path, boolean assumeDirectory) {
+ boolean hasSlash = path.indexOf(slash) == 0;
+ if (beginning && !hasSlash)
+ path = slash + path;
+
+ if (!beginning && hasSlash)
+ path = path.substring(1);
+
+ if (path.equals(pattern))
+ // Exact match
+ if (dirOnly && !assumeDirectory)
+ // Directory expectations not met
+ return false;
+ else
+ // Directory expectations met
+ return true;
+
+ /*
+ * Add slashes for startsWith check. This avoids matching e.g.
+ * "/src/new" to /src/newfile" but allows "/src/new" to match
+ * "/src/new/newfile", as is the git standard
+ */
+ if (path.startsWith(pattern + FastIgnoreRule.PATH_SEPARATOR))
+ return true;
+
+ return false;
+ }
+
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ throw new UnsupportedOperationException(
+ "Path matcher works only on entire paths"); //$NON-NLS-1$
+ }
+
+ boolean iterate(final String path, final int startIncl, final int endExcl,
+ boolean assumeDirectory) {
+ int matcher = 0;
+ int right = startIncl;
+ boolean match = false;
+ int lastWildmatch = -1;
+ while (true) {
+ int left = right;
+ right = path.indexOf(slash, right);
+ if (right == -1) {
+ if (left < endExcl)
+ match = matches(matcher, path, left, endExcl,
+ assumeDirectory);
+ if (match) {
+ if (matcher == matchers.size() - 2
+ && matchers.get(matcher + 1) == WILD)
+ // ** can match *nothing*: a/b/** match also a/b
+ return true;
+ if (matcher < matchers.size() - 1
+ && matchers.get(matcher) == WILD) {
+ // ** can match *nothing*: a/**/b match also a/b
+ matcher++;
+ match = matches(matcher, path, left, endExcl,
+ assumeDirectory);
+ } else if (dirOnly)
+ return false;
+ }
+ return match && matcher + 1 == matchers.size();
+ }
+ if (right - left > 0)
+ match = matches(matcher, path, left, right, assumeDirectory);
+ else {
+ // path starts with slash???
+ right++;
+ continue;
+ }
+ if (match) {
+ if (matchers.get(matcher) == WILD) {
+ lastWildmatch = matcher;
+ // ** can match *nothing*: a/**/b match also a/b
+ right = left - 1;
+ }
+ matcher++;
+ if (matcher == matchers.size())
+ return true;
+ } else if (lastWildmatch != -1)
+ matcher = lastWildmatch + 1;
+ else
+ return false;
+ right++;
+ }
+ }
+
+ boolean matches(int matcherIdx, String path, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ IMatcher matcher = matchers.get(matcherIdx);
+ return matcher.matches(path, startIncl, endExcl, assumeDirectory);
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
new file mode 100644
index 0000000000..cd4d7536d9
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/Strings.java
@@ -0,0 +1,404 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+import static java.lang.Character.isLetter;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+import org.eclipse.jgit.ignore.FastIgnoreRule;
+
+/**
+ * Various {@link String} related utility methods, written mostly to avoid
+ * generation of new String objects (e.g. via splitting Strings etc).
+ *
+ * @since 3.6
+ */
+public class Strings {
+
+ static char getPathSeparator(Character pathSeparator) {
+ return pathSeparator == null ? FastIgnoreRule.PATH_SEPARATOR
+ : pathSeparator.charValue();
+ }
+
+ /**
+ * @param pattern
+ * non null
+ * @param c
+ * character to remove
+ * @return new string with all trailing characters removed
+ */
+ public static String stripTrailing(String pattern, char c) {
+ while (pattern.length() > 0
+ && pattern.charAt(pattern.length() - 1) == c)
+ pattern = pattern.substring(0, pattern.length() - 1);
+ return pattern;
+ }
+
+ static int count(String s, char c, boolean ignoreFirstLast) {
+ int start = 0;
+ int count = 0;
+ while (true) {
+ start = s.indexOf(c, start);
+ if (start == -1)
+ break;
+ if (!ignoreFirstLast || (start != 0 && start != s.length()))
+ count++;
+ start++;
+ }
+ return count;
+ }
+
+ /**
+ * Splits given string to substrings by given separator
+ *
+ * @param pattern
+ * non null
+ * @param slash
+ * separator char
+ * @return list of substrings
+ */
+ public static List<String> split(String pattern, char slash) {
+ int count = count(pattern, slash, true);
+ if (count < 1)
+ throw new IllegalStateException(
+ "Pattern must have at least two segments: " + pattern); //$NON-NLS-1$
+ List<String> segments = new ArrayList<String>(count);
+ int right = 0;
+ while (true) {
+ int left = right;
+ right = pattern.indexOf(slash, right);
+ if (right == -1) {
+ if (left < pattern.length())
+ segments.add(pattern.substring(left));
+ break;
+ }
+ if (right - left > 0)
+ if (left == 1)
+ // leading slash should remain by the first pattern
+ segments.add(pattern.substring(left - 1, right));
+ else if (right == pattern.length() - 1)
+ // trailing slash should remain too
+ segments.add(pattern.substring(left, right + 1));
+ else
+ segments.add(pattern.substring(left, right));
+ right++;
+ }
+ return segments;
+ }
+
+ static boolean isWildCard(String pattern) {
+ return pattern.indexOf('*') != -1 || isComplexWildcard(pattern);
+ }
+
+ private static boolean isComplexWildcard(String pattern) {
+ int idx1 = pattern.indexOf('[');
+ if (idx1 != -1) {
+ int idx2 = pattern.indexOf(']');
+ if (idx2 > idx1)
+ return true;
+ }
+ // required to match escaped backslashes '\\\\'
+ if (pattern.indexOf('?') != -1 || pattern.indexOf('\\') != -1)
+ return true;
+ return false;
+ }
+
+ static PatternState checkWildCards(String pattern) {
+ if (isComplexWildcard(pattern))
+ return PatternState.COMPLEX;
+ int startIdx = pattern.indexOf('*');
+ if (startIdx < 0)
+ return PatternState.NONE;
+
+ if (startIdx == pattern.length() - 1)
+ return PatternState.TRAILING_ASTERISK_ONLY;
+ if (pattern.lastIndexOf('*') == 0)
+ return PatternState.LEADING_ASTERISK_ONLY;
+
+ return PatternState.COMPLEX;
+ }
+
+ static enum PatternState {
+ LEADING_ASTERISK_ONLY, TRAILING_ASTERISK_ONLY, COMPLEX, NONE
+ }
+
+ final static List<String> POSIX_CHAR_CLASSES = Arrays.asList(
+ "alnum", "alpha", "blank", "cntrl", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ // [:alnum:] [:alpha:] [:blank:] [:cntrl:]
+ "digit", "graph", "lower", "print", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ // [:digit:] [:graph:] [:lower:] [:print:]
+ "punct", "space", "upper", "xdigit", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ // [:punct:] [:space:] [:upper:] [:xdigit:]
+ "word" //$NON-NLS-1$
+ // [:word:] XXX I don't see it in
+ // http://man7.org/linux/man-pages/man7/glob.7.html
+ // but this was in org.eclipse.jgit.fnmatch.GroupHead.java ???
+ );
+
+ private static final String DL = "\\p{javaDigit}\\p{javaLetter}"; //$NON-NLS-1$
+
+ final static List<String> JAVA_CHAR_CLASSES = Arrays
+ .asList("\\p{Alnum}", "\\p{javaLetter}", "\\p{Blank}", "\\p{Cntrl}", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ // [:alnum:] [:alpha:] [:blank:] [:cntrl:]
+ "\\p{javaDigit}", "[\\p{Graph}" + DL + "]", "\\p{Ll}", "[\\p{Print}" + DL + "]", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$
+ // [:digit:] [:graph:] [:lower:] [:print:]
+ "\\p{Punct}", "\\p{Space}", "\\p{Lu}", "\\p{XDigit}", //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$
+ // [:punct:] [:space:] [:upper:] [:xdigit:]
+ "[" + DL + "_]" //$NON-NLS-1$ //$NON-NLS-2$
+ // [:word:]
+ );
+
+ // Collating symbols [[.a.]] or equivalence class expressions [[=a=]] are
+ // not supported by CLI git (at least not by 1.9.1)
+ final static Pattern UNSUPPORTED = Pattern
+ .compile("\\[\\[[.=]\\w+[.=]\\]\\]"); //$NON-NLS-1$
+
+ /**
+ * Conversion from glob to Java regex following two sources: <li>
+ * http://man7.org/linux/man-pages/man7/glob.7.html <li>
+ * org.eclipse.jgit.fnmatch.FileNameMatcher.java Seems that there are
+ * various ways to define what "glob" can be.
+ *
+ * @param pattern
+ * non null pattern
+ *
+ * @return Java regex pattern corresponding to given glob pattern
+ * @throws InvalidPatternException
+ */
+ static Pattern convertGlob(String pattern) throws InvalidPatternException {
+ if (UNSUPPORTED.matcher(pattern).find())
+ throw new InvalidPatternException(
+ "Collating symbols [[.a.]] or equivalence class expressions [[=a=]] are not supported", //$NON-NLS-1$
+ pattern);
+
+ StringBuilder sb = new StringBuilder(pattern.length());
+
+ int in_brackets = 0;
+ boolean seenEscape = false;
+ boolean ignoreLastBracket = false;
+ boolean in_char_class = false;
+ // 6 is the length of the longest posix char class "xdigit"
+ char[] charClass = new char[6];
+
+ for (int i = 0; i < pattern.length(); i++) {
+ char c = pattern.charAt(i);
+ switch (c) {
+
+ case '*':
+ if (seenEscape || in_brackets > 0)
+ sb.append(c);
+ else
+ sb.append('.').append(c);
+ break;
+
+ case '.':
+ if (seenEscape)
+ sb.append(c);
+ else
+ sb.append('\\').append('.');
+ break;
+
+ case '?':
+ if (seenEscape || in_brackets > 0)
+ sb.append(c);
+ else
+ sb.append('.');
+ break;
+
+ case ':':
+ if (in_brackets > 0)
+ if (lookBehind(sb) == '['
+ && isLetter(lookAhead(pattern, i)))
+ in_char_class = true;
+ sb.append(':');
+ break;
+
+ case '-':
+ if (in_brackets > 0) {
+ if (lookAhead(pattern, i) == ']')
+ sb.append('\\').append(c);
+ else
+ sb.append(c);
+ } else
+ sb.append('-');
+ break;
+
+ case '\\':
+ if (in_brackets > 0) {
+ char lookAhead = lookAhead(pattern, i);
+ if (lookAhead == ']' || lookAhead == '[')
+ ignoreLastBracket = true;
+ }
+ sb.append(c);
+ break;
+
+ case '[':
+ if (in_brackets > 0) {
+ sb.append('\\').append('[');
+ ignoreLastBracket = true;
+ } else {
+ if (!seenEscape) {
+ in_brackets++;
+ ignoreLastBracket = false;
+ }
+ sb.append('[');
+ }
+ break;
+
+ case ']':
+ if (seenEscape) {
+ sb.append(']');
+ ignoreLastBracket = true;
+ break;
+ }
+ if (in_brackets <= 0) {
+ sb.append('\\').append(']');
+ ignoreLastBracket = true;
+ break;
+ }
+ char lookBehind = lookBehind(sb);
+ if ((lookBehind == '[' && !ignoreLastBracket)
+ || lookBehind == '^') {
+ sb.append('\\');
+ sb.append(']');
+ ignoreLastBracket = true;
+ } else {
+ ignoreLastBracket = false;
+ if (!in_char_class) {
+ in_brackets--;
+ sb.append(']');
+ } else {
+ in_char_class = false;
+ String charCl = checkPosixCharClass(charClass);
+ // delete last \[:: chars and set the pattern
+ if (charCl != null) {
+ sb.setLength(sb.length() - 4);
+ sb.append(charCl);
+ }
+ reset(charClass);
+ }
+ }
+ break;
+
+ case '!':
+ if (in_brackets > 0) {
+ if (lookBehind(sb) == '[')
+ sb.append('^');
+ else
+ sb.append(c);
+ } else
+ sb.append(c);
+ break;
+
+ default:
+ if (in_char_class)
+ setNext(charClass, c);
+ else
+ sb.append(c);
+ break;
+ } // end switch
+
+ seenEscape = c == '\\';
+
+ } // end for
+
+ if (in_brackets > 0)
+ throw new InvalidPatternException("Not closed bracket?", pattern); //$NON-NLS-1$
+ return Pattern.compile(sb.toString());
+ }
+
+ /**
+ * @param buffer
+ * @return zero of the buffer is empty, otherwise the last character from
+ * buffer
+ */
+ private static char lookBehind(StringBuilder buffer) {
+ return buffer.length() > 0 ? buffer.charAt(buffer.length() - 1) : 0;
+ }
+
+ /**
+ * @param pattern
+ * @param i
+ * current pointer in the pattern
+ * @return zero of the index is out of range, otherwise the next character
+ * from given position
+ */
+ private static char lookAhead(String pattern, int i) {
+ int idx = i + 1;
+ return idx >= pattern.length() ? 0 : pattern.charAt(idx);
+ }
+
+ private static void setNext(char[] buffer, char c) {
+ for (int i = 0; i < buffer.length; i++)
+ if (buffer[i] == 0) {
+ buffer[i] = c;
+ break;
+ }
+ }
+
+ private static void reset(char[] buffer) {
+ for (int i = 0; i < buffer.length; i++)
+ buffer[i] = 0;
+ }
+
+ private static String checkPosixCharClass(char[] buffer) {
+ for (int i = 0; i < POSIX_CHAR_CLASSES.size(); i++) {
+ String clazz = POSIX_CHAR_CLASSES.get(i);
+ boolean match = true;
+ for (int j = 0; j < clazz.length(); j++)
+ if (buffer[j] != clazz.charAt(j)) {
+ match = false;
+ break;
+ }
+ if (match)
+ return JAVA_CHAR_CLASSES.get(i);
+ }
+ return null;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
new file mode 100644
index 0000000000..4a1c780d99
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/TrailingAsteriskMatcher.java
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+/**
+ * Matcher for simple patterns ending with an asterisk, e.g. "Makefile.*"
+ *
+ * @since 3.6
+ */
+public class TrailingAsteriskMatcher extends NameMatcher {
+
+ TrailingAsteriskMatcher(String pattern, Character pathSeparator, boolean dirOnly) {
+ super(pattern, pathSeparator, dirOnly);
+
+ if (subPattern.charAt(subPattern.length() - 1) != '*')
+ throw new IllegalArgumentException(
+ "Pattern must have trailing asterisk: " + pattern); //$NON-NLS-1$
+ }
+
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ // faster local access, same as in string.indexOf()
+ String s = subPattern;
+ // we don't need to count '*' character itself
+ int subLenth = s.length() - 1;
+ // simple /*/ pattern
+ if (subLenth == 0)
+ return true;
+
+ if (subLenth > (endExcl - startIncl))
+ return false;
+
+ for (int i = 0; i < subLenth; i++) {
+ char c1 = s.charAt(i);
+ char c2 = segment.charAt(i + startIncl);
+ if (c1 != c2)
+ return false;
+ }
+ return true;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
new file mode 100644
index 0000000000..7d12b0ddce
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildCardMatcher.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+import static org.eclipse.jgit.ignore.internal.Strings.convertGlob;
+
+import java.util.regex.Pattern;
+
+import org.eclipse.jgit.errors.InvalidPatternException;
+
+/**
+ * Matcher built from path segments containing wildcards. This matcher converts
+ * glob wildcards to Java {@link Pattern}'s.
+ * <p>
+ * This class is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public class WildCardMatcher extends NameMatcher {
+
+ final Pattern p;
+
+ WildCardMatcher(String pattern, Character pathSeparator, boolean dirOnly)
+ throws InvalidPatternException {
+ super(pattern, pathSeparator, dirOnly);
+ p = convertGlob(subPattern);
+ }
+
+ @Override
+ public boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ return p.matcher(segment.substring(startIncl, endExcl)).matches();
+ }
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
new file mode 100644
index 0000000000..d578654375
--- /dev/null
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/ignore/internal/WildMatcher.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2014, Andrey Loskutov <loskutov@gmx.de>
+ * 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.ignore.internal;
+
+/**
+ * Wildmatch matcher for "double star" (<code>**</code>) pattern only. This
+ * matcher matches any path.
+ * <p>
+ * This class is immutable and thread safe.
+ *
+ * @since 3.6
+ */
+public final class WildMatcher extends AbstractMatcher {
+
+ static final String WILDMATCH = "**"; //$NON-NLS-1$
+
+ // double star for the beginning of pattern
+ static final String WILDMATCH2 = "/**"; //$NON-NLS-1$
+
+ static final WildMatcher INSTANCE = new WildMatcher();
+
+ private WildMatcher() {
+ super(WILDMATCH, false);
+ }
+
+ public final boolean matches(String path, boolean assumeDirectory) {
+ return true;
+ }
+
+ public final boolean matches(String segment, int startIncl, int endExcl,
+ boolean assumeDirectory) {
+ return true;
+ }
+
+}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
index f2a1b948cc..65272fb0bd 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
@@ -150,7 +150,7 @@ public class JGitText extends TranslationBundle {
/***/ public String commitDoesNotHaveGivenParent;
/***/ public String cantFindObjectInReversePackIndexForTheSpecifiedOffset;
/***/ public String cantPassMeATree;
- /***/ public String channelMustBeInRange0_255;
+ /***/ public String channelMustBeInRange1_255;
/***/ public String characterClassIsNotSupported;
/***/ public String checkoutConflictWithFile;
/***/ public String checkoutConflictWithFiles;
@@ -308,6 +308,8 @@ public class JGitText extends TranslationBundle {
/***/ public String indexFileIsTooLargeForJgit;
/***/ public String indexSignatureIsInvalid;
/***/ public String indexWriteException;
+ /***/ public String initFailedBareRepoDifferentDirs;
+ /***/ public String initFailedNonBareRepoSameDirs;
/***/ public String inMemoryBufferLimitExceeded;
/***/ public String inputStreamMustSupportMark;
/***/ public String integerValueOutOfRange;
@@ -556,6 +558,7 @@ public class JGitText extends TranslationBundle {
/***/ public String tagAlreadyExists;
/***/ public String tagNameInvalid;
/***/ public String tagOnRepoWithoutHEADCurrentlyNotSupported;
+ /***/ public String transactionAborted;
/***/ public String theFactoryMustNotBeNull;
/***/ public String timerAlreadyTerminated;
/***/ public String topologicalSortRequired;
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
index 9670bf10a2..995621ee3e 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileRepository.java
@@ -273,7 +273,8 @@ public class FileRepository extends Repository {
ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_HIDEDOTFILES,
HideDotFiles.DOTGITONLY);
- if (hideDotFiles != HideDotFiles.FALSE && !isBare())
+ if (hideDotFiles != HideDotFiles.FALSE && !isBare()
+ && getDirectory().getName().startsWith(".")) //$NON-NLS-1$
getFS().setHidden(getDirectory(), true);
refs.create();
objectDatabase.create();
@@ -329,6 +330,25 @@ public class FileRepository extends Repository {
// Java has no other way
cfg.setBoolean(ConfigConstants.CONFIG_CORE_SECTION, null,
ConfigConstants.CONFIG_KEY_PRECOMPOSEUNICODE, true);
+ if (!bare) {
+ File workTree = getWorkTree();
+ if (!getDirectory().getParentFile().equals(workTree)) {
+ cfg.setString(ConfigConstants.CONFIG_CORE_SECTION, null,
+ ConfigConstants.CONFIG_KEY_WORKTREE, getWorkTree()
+ .getAbsolutePath());
+ LockFile dotGitLockFile = new LockFile(new File(workTree,
+ Constants.DOT_GIT), getFS());
+ try {
+ if (dotGitLockFile.lock()) {
+ dotGitLockFile.write(Constants.encode(Constants.GITDIR
+ + getDirectory().getAbsolutePath()));
+ dotGitLockFile.commit();
+ }
+ } finally {
+ dotGitLockFile.unlock();
+ }
+ }
+ }
cfg.save();
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
index 3cc4e7b97b..48335e48c2 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GC.java
@@ -93,6 +93,7 @@ import org.eclipse.jgit.lib.ReflogEntry;
import org.eclipse.jgit.revwalk.ObjectWalk;
import org.eclipse.jgit.revwalk.RevObject;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.storage.pack.PackConfig;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.filter.TreeFilter;
import org.eclipse.jgit.util.FileUtils;
@@ -117,6 +118,8 @@ public class GC {
private Date expire;
+ private PackConfig pconfig = null;
+
/**
* the refs which existed during the last call to {@link #repack()}. This is
* needed during {@link #prune(Set)} where we can optimize by looking at the
@@ -686,7 +689,7 @@ public class GC {
}
});
- PackWriter pw = new PackWriter(repo);
+ PackWriter pw = new PackWriter((pconfig == null) ? new PackConfig(repo) : pconfig, repo.newObjectReader());
try {
// prepare the PackWriter
pw.setDeltaBaseAsOffset(true);
@@ -948,6 +951,19 @@ public class GC {
}
/**
+ * Set the PackConfig used when (re-)writing packfiles. This allows to
+ * influence how packs are written and to implement something similar to
+ * "git gc --aggressive"
+ *
+ * @since 3.6
+ * @param pconfig
+ * the {@link PackConfig} used when writing packs
+ */
+ public void setPackConfig(PackConfig pconfig) {
+ this.pconfig = pconfig;
+ }
+
+ /**
* During gc() or prune() each unreferenced, loose object which has been
* created or modified after or at <code>expire</code> will not be pruned.
* Only older objects may be pruned. If set to null then every object is a
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
index 882f5c8a45..58276051ea 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
@@ -72,6 +72,7 @@ import org.eclipse.jgit.internal.storage.pack.PackWriter;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.ObjectDatabase;
import org.eclipse.jgit.lib.ObjectId;
@@ -606,7 +607,18 @@ public class ObjectDirectory extends FileObjectDatabase {
}
private boolean searchPacksAgain(PackList old) {
- return old.snapshot.isModified(packDirectory) && old != scanPacks(old);
+ // Whether to trust the pack folder's modification time. If set
+ // to false we will always scan the .git/objects/pack folder to
+ // check for new pack files. If set to true (default) we use the
+ // lastmodified attribute of the folder and assume that no new
+ // pack files can be in this folder if his modification time has
+ // not changed.
+ boolean trustFolderStat = config.getBoolean(
+ ConfigConstants.CONFIG_CORE_SECTION,
+ ConfigConstants.CONFIG_KEY_TRUSTFOLDERSTAT, true);
+
+ return ((!trustFolderStat) || old.snapshot.isModified(packDirectory))
+ && old != scanPacks(old);
}
Config getConfig() {
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
index 2d574d80a0..ab3297ad2a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV1.java
@@ -54,6 +54,7 @@ import java.util.NoSuchElementException;
import java.util.Set;
import org.eclipse.jgit.errors.CorruptObjectException;
+import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.AbbreviatedObjectId;
import org.eclipse.jgit.lib.AnyObjectId;
import org.eclipse.jgit.lib.Constants;
@@ -88,7 +89,11 @@ class PackIndexV1 extends PackIndex {
n = (int) (idxHeader[k] - idxHeader[k - 1]);
}
if (n > 0) {
- idxdata[k] = new byte[n * (Constants.OBJECT_ID_LENGTH + 4)];
+ final long len = n * (Constants.OBJECT_ID_LENGTH + 4);
+ if (len > Integer.MAX_VALUE - 8) // http://stackoverflow.com/a/8381338
+ throw new IOException(JGitText.get().indexFileIsTooLargeForJgit);
+
+ idxdata[k] = new byte[(int) len];
IO.readFully(fd, idxdata[k], 0, idxdata[k].length);
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
index 76e0b35b35..164934704c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java
@@ -1029,43 +1029,44 @@ public class PackWriter {
stats.totalObjects = objCnt;
beginPhase(PackingPhase.WRITING, writeMonitor, objCnt);
long writeStart = System.currentTimeMillis();
+ try {
+ out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
+ out.flush();
- out.writeFileHeader(PACK_VERSION_GENERATED, objCnt);
- out.flush();
+ writeObjects(out);
+ if (!edgeObjects.isEmpty() || !cachedPacks.isEmpty()) {
+ for (Statistics.ObjectType typeStat : stats.objectTypes) {
+ if (typeStat == null)
+ continue;
+ stats.thinPackBytes += typeStat.bytes;
+ }
+ }
+
+ stats.reusedPacks = Collections.unmodifiableList(cachedPacks);
+ for (CachedPack pack : cachedPacks) {
+ long deltaCnt = pack.getDeltaCount();
+ stats.reusedObjects += pack.getObjectCount();
+ stats.reusedDeltas += deltaCnt;
+ stats.totalDeltas += deltaCnt;
+ reuseSupport.copyPackAsIs(out, pack, reuseValidate);
+ }
+ writeChecksum(out);
+ out.flush();
+ } finally {
+ stats.timeWriting = System.currentTimeMillis() - writeStart;
+ stats.depth = depth;
- writeObjects(out);
- if (!edgeObjects.isEmpty() || !cachedPacks.isEmpty()) {
for (Statistics.ObjectType typeStat : stats.objectTypes) {
if (typeStat == null)
continue;
- stats.thinPackBytes += typeStat.bytes;
+ typeStat.cntDeltas += typeStat.reusedDeltas;
+ stats.reusedObjects += typeStat.reusedObjects;
+ stats.reusedDeltas += typeStat.reusedDeltas;
+ stats.totalDeltas += typeStat.cntDeltas;
}
}
- for (CachedPack pack : cachedPacks) {
- long deltaCnt = pack.getDeltaCount();
- stats.reusedObjects += pack.getObjectCount();
- stats.reusedDeltas += deltaCnt;
- stats.totalDeltas += deltaCnt;
- reuseSupport.copyPackAsIs(out, pack, reuseValidate);
- }
- writeChecksum(out);
- out.flush();
- stats.timeWriting = System.currentTimeMillis() - writeStart;
stats.totalBytes = out.length();
- stats.reusedPacks = Collections.unmodifiableList(cachedPacks);
- stats.depth = depth;
-
- for (Statistics.ObjectType typeStat : stats.objectTypes) {
- if (typeStat == null)
- continue;
- typeStat.cntDeltas += typeStat.reusedDeltas;
-
- stats.reusedObjects += typeStat.reusedObjects;
- stats.reusedDeltas += typeStat.reusedDeltas;
- stats.totalDeltas += typeStat.cntDeltas;
- }
-
reader.release();
endPhase(writeMonitor);
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
index 7a6ddb39a1..eecbc224bc 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BaseRepositoryBuilder.java
@@ -514,13 +514,17 @@ public class BaseRepositoryBuilder<B extends BaseRepositoryBuilder, R extends Re
if (FileKey.isGitRepository(dir, tryFS)) {
setGitDir(dir);
break;
- } else if (dir.isFile())
+ } else if (dir.isFile()) {
try {
setGitDir(getSymRef(current, dir, tryFS));
break;
} catch (IOException ignored) {
// Continue searching if gitdir ref isn't found
}
+ } else if (FileKey.isGitRepository(current, tryFS)) {
+ setGitDir(current);
+ break;
+ }
current = current.getParentFile();
if (current != null && ceilingDirectories != null
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
index 378d91c58c..ccbfed720a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java
@@ -79,7 +79,6 @@ public class ConfigConstants {
/**
* The "rebase" section
- *
* @since 3.2
*/
public static final String CONFIG_REBASE_SECTION = "rebase";
@@ -92,14 +91,12 @@ public class ConfigConstants {
/**
* The "fetch" section
- *
* @since 3.3
*/
public static final String CONFIG_FETCH_SECTION = "fetch";
/**
* The "pull" section
- *
* @since 3.5
*/
public static final String CONFIG_PULL_SECTION = "pull";
@@ -139,7 +136,6 @@ public class ConfigConstants {
/**
* The "symlinks" key
- *
* @since 3.3
*/
public static final String CONFIG_KEY_SYMLINKS = "symlinks";
@@ -167,7 +163,6 @@ public class ConfigConstants {
/**
* The "autostash" key
- *
* @since 3.2
*/
public static final String CONFIG_KEY_AUTOSTASH = "autostash";
@@ -208,6 +203,12 @@ public class ConfigConstants {
/** The "update" key */
public static final String CONFIG_KEY_UPDATE = "update";
+ /**
+ * The "ignore" key
+ * @since 3.6
+ */
+ public static final String CONFIG_KEY_IGNORE = "ignore";
+
/** The "compression" key */
public static final String CONFIG_KEY_COMPRESSION = "compression";
@@ -226,6 +227,18 @@ public class ConfigConstants {
/** The "pruneexpire" key */
public static final String CONFIG_KEY_PRUNEEXPIRE = "pruneexpire";
+ /**
+ * The "aggressiveDepth" key
+ * @since 3.6
+ */
+ public static final String CONFIG_KEY_AGGRESSIVE_DEPTH = "aggressiveDepth";
+
+ /**
+ * The "aggressiveWindow" key
+ * @since 3.6
+ */
+ public static final String CONFIG_KEY_AGGRESSIVE_WINDOW = "aggressiveWindow";
+
/** The "mergeoptions" key */
public static final String CONFIG_KEY_MERGEOPTIONS = "mergeoptions";
@@ -239,38 +252,43 @@ public class ConfigConstants {
public static final String CONFIG_KEY_CHECKSTAT = "checkstat";
/**
- * The "renamelimit" key in the "diff section"
- * @since 3.0
- */
+ * The "renamelimit" key in the "diff section"
+ * @since 3.0
+ */
public static final String CONFIG_KEY_RENAMELIMIT = "renamelimit";
/**
- * The "noprefix" key in the "diff section"
- * @since 3.0
- */
+ * The "trustfolderstat" key in the "core section"
+ * @since 3.6
+ */
+ public static final String CONFIG_KEY_TRUSTFOLDERSTAT = "trustfolderstat";
+
+ /**
+ * The "noprefix" key in the "diff section"
+ * @since 3.0
+ */
public static final String CONFIG_KEY_NOPREFIX = "noprefix";
/**
- * A "renamelimit" value in the "diff section"
- * @since 3.0
- */
+ * A "renamelimit" value in the "diff section"
+ * @since 3.0
+ */
public static final String CONFIG_RENAMELIMIT_COPY = "copy";
/**
- * A "renamelimit" value in the "diff section"
- * @since 3.0
- */
+ * A "renamelimit" value in the "diff section"
+ * @since 3.0
+ */
public static final String CONFIG_RENAMELIMIT_COPIES = "copies";
/**
- * The "renames" key in the "diff section"
- * @since 3.0
- */
+ * The "renames" key in the "diff section"
+ * @since 3.0
+ */
public static final String CONFIG_KEY_RENAMES = "renames";
/**
* The "prune" key
- *
* @since 3.3
*/
public static final String CONFIG_KEY_PRUNE = "prune";
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
index d14614dc38..f149749843 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
@@ -272,7 +272,14 @@ public final class Constants {
*/
public static final String INFO_EXCLUDE = "info/exclude";
- /** The environment variable that contains the system user name */
+ /**
+ * The system property that contains the system user name
+ *
+ * @since 3.6
+ */
+ public static final String OS_USER_DIR = "user.dir";
+
+ /** The system property that contains the system user name */
public static final String OS_USER_NAME_KEY = "user.name";
/** The environment variable that contains the author's name */
@@ -359,6 +366,20 @@ public final class Constants {
public static final String SHALLOW = "shallow";
/**
+ * Prefix of the first line in a ".git" file
+ *
+ * @since 3.6
+ */
+ public static final String GITDIR = "gitdir: ";
+
+ /**
+ * Name of the folder (inside gitDir) where submodules are stored
+ *
+ * @since 3.6
+ */
+ public static final String MODULES = "modules";
+
+ /**
* Create a new digest function for objects.
*
* @return a new digest object.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
index 8eb0333550..1b049f6155 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java
@@ -3,6 +3,7 @@
* Copyright (C) 2007-2008, Robin Rosenberg <robin.rosenberg@dewire.com>
* Copyright (C) 2010, Jens Baumgart <jens.baumgart@sap.com>
* Copyright (C) 2013, Robin Stocker <robin@nibor.org>
+ * Copyright (C) 2014, Axel Richard <axel.richard@obeo.fr>
* and other copyright owners as documented in the project's IP log.
*
* This program and the accompanying materials are made available
@@ -58,13 +59,17 @@ import java.util.Set;
import org.eclipse.jgit.dircache.DirCache;
import org.eclipse.jgit.dircache.DirCacheEntry;
import org.eclipse.jgit.dircache.DirCacheIterator;
+import org.eclipse.jgit.errors.ConfigInvalidException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.StopWalkException;
import org.eclipse.jgit.revwalk.RevTree;
import org.eclipse.jgit.revwalk.RevWalk;
+import org.eclipse.jgit.submodule.SubmoduleWalk;
+import org.eclipse.jgit.submodule.SubmoduleWalk.IgnoreSubmoduleMode;
import org.eclipse.jgit.treewalk.AbstractTreeIterator;
import org.eclipse.jgit.treewalk.EmptyTreeIterator;
+import org.eclipse.jgit.treewalk.FileTreeIterator;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.treewalk.filter.AndTreeFilter;
@@ -227,7 +232,7 @@ public class IndexDiff {
@Override
public TreeFilter clone() {
throw new IllegalStateException(
- "Do not clone this kind of filter: "
+ "Do not clone this kind of filter: " //$NON-NLS-1$
+ getClass().getName());
}
}
@@ -268,6 +273,12 @@ public class IndexDiff {
private IndexDiffFilter indexDiffFilter;
+ private Map<String, IndexDiff> submoduleIndexDiffs = new HashMap<String, IndexDiff>();
+
+ private IgnoreSubmoduleMode ignoreSubmoduleMode = null;
+
+ private Map<FileMode, Set<String>> fileModes = new HashMap<FileMode, Set<String>>();
+
/**
* Construct an IndexDiff
*
@@ -281,13 +292,7 @@ public class IndexDiff {
*/
public IndexDiff(Repository repository, String revstr,
WorkingTreeIterator workingTreeIterator) throws IOException {
- this.repository = repository;
- ObjectId objectId = repository.resolve(revstr);
- if (objectId != null)
- tree = new RevWalk(repository).parseTree(objectId);
- else
- tree = null;
- this.initialWorkingTreeIterator = workingTreeIterator;
+ this(repository, repository.resolve(revstr), workingTreeIterator);
}
/**
@@ -311,6 +316,43 @@ public class IndexDiff {
}
/**
+ * @param mode
+ * defines how modifications in submodules are treated
+ * @since 3.6
+ */
+ public void setIgnoreSubmoduleMode(IgnoreSubmoduleMode mode) {
+ this.ignoreSubmoduleMode = mode;
+ }
+
+ /**
+ * A factory to producing WorkingTreeIterators
+ * @since 3.6
+ */
+ public interface WorkingTreeIteratorFactory {
+ /**
+ * @param repo
+ * @return a WorkingTreeIterator for repo
+ */
+ public WorkingTreeIterator getWorkingTreeIterator(Repository repo);
+ }
+
+ private WorkingTreeIteratorFactory wTreeIt = new WorkingTreeIteratorFactory() {
+ public WorkingTreeIterator getWorkingTreeIterator(Repository repo) {
+ return new FileTreeIterator(repo);
+ }
+ };
+
+ /**
+ * Allows higher layers to set the factory for WorkingTreeIterators.
+ *
+ * @param wTreeIt
+ * @since 3.6
+ */
+ public void setWorkingTreeItFactory(WorkingTreeIteratorFactory wTreeIt) {
+ this.wTreeIt = wTreeIt;
+ }
+
+ /**
* Sets a filter. Can be used e.g. for restricting the tree walk to a set of
* files.
*
@@ -386,6 +428,7 @@ public class IndexDiff {
indexDiffFilter = new IndexDiffFilter(INDEX, WORKDIR);
filters.add(indexDiffFilter);
treeWalk.setFilter(AndTreeFilter.create(filters));
+ fileModes.clear();
while (treeWalk.next()) {
AbstractTreeIterator treeIterator = treeWalk.getTree(TREE,
AbstractTreeIterator.class);
@@ -413,18 +456,25 @@ public class IndexDiff {
|| treeIterator.getEntryRawMode()
!= dirCacheIterator.getEntryRawMode()) {
// in repo, in index, content diff => changed
- changed.add(treeWalk.getPathString());
+ if (!isEntryGitLink(treeIterator)
+ || !isEntryGitLink(dirCacheIterator)
+ || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
+ changed.add(treeWalk.getPathString());
}
} else {
// in repo, not in index => removed
- removed.add(treeWalk.getPathString());
+ if (!isEntryGitLink(treeIterator)
+ || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
+ removed.add(treeWalk.getPathString());
if (workingTreeIterator != null)
untracked.add(treeWalk.getPathString());
}
} else {
if (dirCacheIterator != null) {
// not in repo, in index => added
- added.add(treeWalk.getPathString());
+ if (!isEntryGitLink(dirCacheIterator)
+ || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
+ added.add(treeWalk.getPathString());
} else {
// not in repo, not in index => untracked
if (workingTreeIterator != null
@@ -437,16 +487,85 @@ public class IndexDiff {
if (dirCacheIterator != null) {
if (workingTreeIterator == null) {
// in index, not in workdir => missing
- missing.add(treeWalk.getPathString());
+ if (!isEntryGitLink(dirCacheIterator)
+ || ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL)
+ missing.add(treeWalk.getPathString());
} else {
if (workingTreeIterator.isModified(
dirCacheIterator.getDirCacheEntry(), true,
treeWalk.getObjectReader())) {
// in index, in workdir, content differs => modified
- modified.add(treeWalk.getPathString());
+ if (!isEntryGitLink(dirCacheIterator) || !isEntryGitLink(workingTreeIterator)
+ || (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL && ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY))
+ modified.add(treeWalk.getPathString());
+ }
+ }
+ }
+
+ for (int i = 0; i < treeWalk.getTreeCount(); i++) {
+ Set<String> values = fileModes.get(treeWalk.getFileMode(i));
+ String path = treeWalk.getPathString();
+ if (path != null) {
+ if (values == null)
+ values = new HashSet<String>();
+ values.add(path);
+ fileModes.put(treeWalk.getFileMode(i), values);
+ }
+ }
+ }
+
+ if (ignoreSubmoduleMode != IgnoreSubmoduleMode.ALL) {
+ IgnoreSubmoduleMode localIgnoreSubmoduleMode = ignoreSubmoduleMode;
+ SubmoduleWalk smw = SubmoduleWalk.forIndex(repository);
+ while (smw.next()) {
+ try {
+ if (localIgnoreSubmoduleMode == null)
+ localIgnoreSubmoduleMode = smw.getModulesIgnore();
+ if (IgnoreSubmoduleMode.ALL
+ .equals(localIgnoreSubmoduleMode))
+ continue;
+ } catch (ConfigInvalidException e) {
+ IOException e1 = new IOException(
+ "Found invalid ignore param for submodule "
+ + smw.getPath());
+ e1.initCause(e);
+ throw e1;
+ }
+ Repository subRepo = smw.getRepository();
+ if (subRepo != null) {
+ try {
+ ObjectId subHead = subRepo.resolve("HEAD"); //$NON-NLS-1$
+ if (subHead != null
+ && !subHead.equals(smw.getObjectId()))
+ modified.add(smw.getPath());
+ else if (ignoreSubmoduleMode != IgnoreSubmoduleMode.DIRTY) {
+ IndexDiff smid = submoduleIndexDiffs.get(smw
+ .getPath());
+ if (smid == null) {
+ smid = new IndexDiff(subRepo,
+ smw.getObjectId(),
+ wTreeIt.getWorkingTreeIterator(subRepo));
+ submoduleIndexDiffs.put(smw.getPath(), smid);
+ }
+ if (smid.diff()) {
+ if (ignoreSubmoduleMode == IgnoreSubmoduleMode.UNTRACKED
+ && smid.getAdded().isEmpty()
+ && smid.getChanged().isEmpty()
+ && smid.getConflicting().isEmpty()
+ && smid.getMissing().isEmpty()
+ && smid.getModified().isEmpty()
+ && smid.getRemoved().isEmpty()) {
+ continue;
+ }
+ modified.add(smw.getPath());
+ }
+ }
+ } finally {
+ subRepo.close();
}
}
}
+
}
// consume the remaining work
@@ -462,6 +581,11 @@ public class IndexDiff {
return true;
}
+ private boolean isEntryGitLink(AbstractTreeIterator ti) {
+ return ((ti != null) && (ti.getEntryRawMode() == FileMode.GITLINK
+ .getBits()));
+ }
+
private void addConflict(String path, int stage) {
StageState existingStageStates = conflicts.get(path);
byte stageMask = 0;
@@ -578,4 +702,20 @@ public class IndexDiff {
final DirCacheEntry entry = dirCache.getEntry(path);
return entry != null ? entry.getFileMode() : FileMode.MISSING;
}
+
+ /**
+ * Get the list of paths that IndexDiff has detected to differ and have the
+ * given file mode
+ *
+ * @param mode
+ * @return the list of paths that IndexDiff has detected to differ and have
+ * the given file mode
+ * @since 3.6
+ */
+ public Set<String> getPathsWithIndexMode(final FileMode mode) {
+ Set<String> paths = fileModes.get(mode);
+ if (paths == null)
+ paths = new HashSet<String>();
+ return paths;
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
index 281bccde65..8435c9a64b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java
@@ -425,6 +425,45 @@ public class ObjectChecker {
/**
* Check tree path entry for validity.
+ * <p>
+ * Unlike {@link #checkPathSegment(byte[], int, int)}, this version
+ * scans a multi-directory path string such as {@code "src/main.c"}.
+ *
+ * @param path path string to scan.
+ * @throws CorruptObjectException path is invalid.
+ * @since 3.6
+ */
+ public void checkPath(String path) throws CorruptObjectException {
+ byte[] buf = Constants.encode(path);
+ checkPath(buf, 0, buf.length);
+ }
+
+ /**
+ * Check tree path entry for validity.
+ * <p>
+ * Unlike {@link #checkPathSegment(byte[], int, int)}, this version
+ * scans a multi-directory path string such as {@code "src/main.c"}.
+ *
+ * @param raw buffer to scan.
+ * @param ptr offset to first byte of the name.
+ * @param end offset to one past last byte of name.
+ * @throws CorruptObjectException path is invalid.
+ * @since 3.6
+ */
+ public void checkPath(byte[] raw, int ptr, int end)
+ throws CorruptObjectException {
+ int start = ptr;
+ for (; ptr < end; ptr++) {
+ if (raw[ptr] == '/') {
+ checkPathSegment(raw, start, ptr);
+ start = ptr + 1;
+ }
+ }
+ checkPathSegment(raw, start, end);
+ }
+
+ /**
+ * Check tree path entry for validity.
*
* @param raw buffer to scan.
* @param ptr offset to first byte of the name.
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
index 682cac162c..7fea880612 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java
@@ -197,6 +197,15 @@ public abstract class RefDatabase {
}
/**
+ * @return if the database performs {@code newBatchUpdate()} as an atomic
+ * transaction.
+ * @since 3.6
+ */
+ public boolean performsAtomicTransactions() {
+ return false;
+ }
+
+ /**
* Read a single reference.
* <p>
* Aside from taking advantage of {@link #SEARCH_PATH}, this method may be
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
index a9a45a2406..6353a5b5f3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java
@@ -65,8 +65,6 @@ import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jgit.dircache.DirCache;
-import org.eclipse.jgit.dircache.DirCacheCheckout;
-import org.eclipse.jgit.dircache.InvalidPathException;
import org.eclipse.jgit.errors.AmbiguousObjectException;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.IncorrectObjectTypeException;
@@ -90,6 +88,7 @@ import org.eclipse.jgit.util.FS;
import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.IO;
import org.eclipse.jgit.util.RawParseUtils;
+import org.eclipse.jgit.util.SystemReader;
import org.eclipse.jgit.util.io.SafeBufferedOutputStream;
/**
@@ -1154,11 +1153,11 @@ public abstract class Repository {
if (refName.endsWith(".lock")) //$NON-NLS-1$
return false;
- // Borrow logic for filtering out invalid paths. These
- // are also invalid ref
+ // Refs may be stored as loose files so invalid paths
+ // on the local system must also be invalid refs.
try {
- DirCacheCheckout.checkValidPath(refName);
- } catch (InvalidPathException e) {
+ SystemReader.getInstance().checkPath(refName);
+ } catch (CorruptObjectException e) {
return false;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
index fb9abf8097..8e70f57fa3 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java
@@ -77,7 +77,6 @@ import org.eclipse.jgit.errors.IncorrectObjectTypeException;
import org.eclipse.jgit.errors.IndexWriteException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
-import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.ConfigConstants;
import org.eclipse.jgit.lib.FileMode;
import org.eclipse.jgit.lib.ObjectId;
@@ -90,7 +89,6 @@ import org.eclipse.jgit.treewalk.NameConflictTreeWalk;
import org.eclipse.jgit.treewalk.TreeWalk;
import org.eclipse.jgit.treewalk.WorkingTreeIterator;
import org.eclipse.jgit.util.FS;
-import org.eclipse.jgit.util.FileUtils;
import org.eclipse.jgit.util.TemporaryBuffer;
/**
@@ -309,13 +307,6 @@ public class ResolveMerger extends ThreeWayMerger {
}
private void checkout() throws NoWorkTreeException, IOException {
- for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
- .entrySet()) {
- File f = new File(db.getWorkTree(), entry.getKey());
- createDir(f.getParentFile());
- DirCacheCheckout.checkoutEntry(db, f, entry.getValue(), reader);
- modifiedFiles.add(entry.getKey());
- }
// Iterate in reverse so that "folder/file" is deleted before
// "folder". Otherwise this could result in a failing path because
// of a non-empty directory, for which delete() would fail.
@@ -328,18 +319,10 @@ public class ResolveMerger extends ThreeWayMerger {
MergeFailureReason.COULD_NOT_DELETE);
modifiedFiles.add(fileName);
}
- }
-
- private void createDir(File f) throws IOException {
- if (!db.getFS().isDirectory(f) && !f.mkdirs()) {
- File p = f;
- while (p != null && !db.getFS().exists(p))
- p = p.getParentFile();
- if (p == null || db.getFS().isDirectory(p))
- throw new IOException(JGitText.get().cannotCreateDirectory);
- FileUtils.delete(p);
- if (!f.mkdirs())
- throw new IOException(JGitText.get().cannotCreateDirectory);
+ for (Map.Entry<String, DirCacheEntry> entry : toBeCheckedOut
+ .entrySet()) {
+ DirCacheCheckout.checkoutEntry(db, entry.getValue(), reader);
+ modifiedFiles.add(entry.getKey());
}
}
@@ -367,15 +350,8 @@ public class ResolveMerger extends ThreeWayMerger {
while(mpathsIt.hasNext()) {
String mpath=mpathsIt.next();
DirCacheEntry entry = dc.getEntry(mpath);
- if (entry == null)
- continue;
- FileOutputStream fos = new FileOutputStream(new File(
- db.getWorkTree(), mpath));
- try {
- reader.open(entry.getObjectId()).copyTo(fos);
- } finally {
- fos.close();
- }
+ if (entry != null)
+ DirCacheCheckout.checkoutEntry(db, entry, reader);
mpathsIt.remove();
}
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
index 6ba0dfed03..f88b819d4d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revplot/AbstractPlotRenderer.java
@@ -174,7 +174,7 @@ public abstract class AbstractPlotRenderer<TLane extends PlotLane, TColor> {
}
final String msg = commit.getShortMessage();
- drawText(msg, textx + dotSize, h / 2);
+ drawText(msg, textx + dotSize, h);
}
/**
@@ -276,9 +276,9 @@ public abstract class AbstractPlotRenderer<TLane extends PlotLane, TColor> {
* first pixel from the left that the text can be drawn at.
* Character data must not appear before this position.
* @param y
- * pixel coordinate of the centerline of the text.
- * Implementations must adjust this coordinate to account for the
- * way their implementation handles font rendering.
+ * pixel coordinate of the baseline of the text. Implementations
+ * must adjust this coordinate to account for the way their
+ * implementation handles font rendering.
*/
protected abstract void drawText(String msg, int x, int y);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
index 79cc42d170..d19e467c1d 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/RevWalk.java
@@ -174,6 +174,7 @@ public class RevWalk implements Iterable<RevCommit> {
private int delayFreeFlags;
+ private int retainOnReset;
int carryFlags = UNINTERESTING;
final ArrayList<RevCommit> roots;
@@ -1093,6 +1094,47 @@ public class RevWalk implements Iterable<RevCommit> {
}
/**
+ * Preserve a RevFlag during all {@code reset} methods.
+ * <p>
+ * Calling {@code retainOnReset(flag)} avoids needing to pass the flag
+ * during each {@code resetRetain()} invocation on this instance.
+ * <p>
+ * Clearing flags marked retainOnReset requires disposing of the flag with
+ * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by
+ * {@code #dispose()}.
+ *
+ * @param flag
+ * the flag to retain during all resets.
+ * @since 3.6
+ */
+ public final void retainOnReset(RevFlag flag) {
+ if ((freeFlags & flag.mask) != 0)
+ throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagIsDisposed, flag.name));
+ if (flag.walker != this)
+ throw new IllegalArgumentException(MessageFormat.format(JGitText.get().flagNotFromThis, flag.name));
+ retainOnReset |= flag.mask;
+ }
+
+ /**
+ * Preserve a set of RevFlags during all {@code reset} methods.
+ * <p>
+ * Calling {@code retainOnReset(set)} avoids needing to pass the flags
+ * during each {@code resetRetain()} invocation on this instance.
+ * <p>
+ * Clearing flags marked retainOnReset requires disposing of the flag with
+ * {@code #disposeFlag(RevFlag)} or disposing of the entire RevWalk by
+ * {@code #dispose()}.
+ *
+ * @param flags
+ * the flags to retain during all resets.
+ * @since 3.6
+ */
+ public final void retainOnReset(Collection<RevFlag> flags) {
+ for (RevFlag f : flags)
+ retainOnReset(f);
+ }
+
+ /**
* Allow a flag to be recycled for a different use.
* <p>
* Recycled flags always come back as a different Java object instance when
@@ -1110,6 +1152,7 @@ public class RevWalk implements Iterable<RevCommit> {
}
void freeFlag(final int mask) {
+ retainOnReset &= ~mask;
if (isNotStarted()) {
freeFlags |= mask;
carryFlags &= ~mask;
@@ -1158,6 +1201,9 @@ public class RevWalk implements Iterable<RevCommit> {
* Unlike {@link #dispose()} previously acquired RevObject (and RevCommit)
* instances are not invalidated. RevFlag instances are not invalidated, but
* are removed from all RevObjects.
+ * <p>
+ * See {@link #retainOnReset(RevFlag)} for an alternative that does not
+ * require passing the flags during each reset.
*
* @param retainFlags
* application flags that should <b>not</b> be cleared from
@@ -1183,7 +1229,7 @@ public class RevWalk implements Iterable<RevCommit> {
*/
protected void reset(int retainFlags) {
finishDelayedFreeFlags();
- retainFlags |= PARSED;
+ retainFlags |= PARSED | retainOnReset;
final int clearFlags = ~retainFlags;
final FIFORevQueue q = new FIFORevQueue();
@@ -1227,6 +1273,7 @@ public class RevWalk implements Iterable<RevCommit> {
reader.release();
freeFlags = APP_FLAGS;
delayFreeFlags = 0;
+ retainOnReset = 0;
carryFlags = UNINTERESTING;
objects.clear();
reader.release();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
index c31ffd1f7d..5db3378b78 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/submodule/SubmoduleWalk.java
@@ -79,6 +79,33 @@ import org.eclipse.jgit.util.FS;
public class SubmoduleWalk {
/**
+ * The values for the config param submodule.<name>.ignore
+ *
+ * @since 3.6
+ */
+ public enum IgnoreSubmoduleMode {
+ /**
+ * Ignore all modifications to submodules
+ */
+ ALL,
+
+ /**
+ * Ignore changes to the working tree of a submodule
+ */
+ DIRTY,
+
+ /**
+ * Ignore changes to untracked files in the working tree of a submodule
+ */
+ UNTRACKED,
+
+ /**
+ * Ignore nothing. That's the default
+ */
+ NONE;
+ }
+
+ /**
* Create a generator to walk over the submodule entries currently in the
* index
*
@@ -426,6 +453,29 @@ public class SubmoduleWalk {
return this;
}
+ /**
+ * Checks whether the working tree (or the index in case of a bare repo)
+ * contains a .gitmodules file. That's a hint that the repo contains
+ * submodules.
+ *
+ * @param repository
+ * the repository to check
+ * @return <code>true</code> if the repo contains a .gitmodules file
+ * @throws IOException
+ * @throws CorruptObjectException
+ * @since 3.6
+ */
+ public static boolean containsGitModulesFile(Repository repository)
+ throws IOException {
+ if (repository.isBare()) {
+ DirCache dc = repository.readDirCache();
+ return (dc.findEntry(Constants.DOT_GIT_MODULES) >= 0);
+ }
+ File modulesFile = new File(repository.getWorkTree(),
+ Constants.DOT_GIT_MODULES);
+ return (modulesFile.exists());
+ }
+
private void lazyLoadModulesConfig() throws IOException, ConfigInvalidException {
if (modulesConfig == null)
loadModulesConfig();
@@ -600,6 +650,26 @@ public class SubmoduleWalk {
}
/**
+ * Get the configured ignore field for the current entry. This will be the
+ * value from the .gitmodules file in the current repository's working tree.
+ *
+ * @return ignore value
+ * @throws ConfigInvalidException
+ * @throws IOException
+ * @since 3.6
+ */
+ public IgnoreSubmoduleMode getModulesIgnore() throws IOException,
+ ConfigInvalidException {
+ lazyLoadModulesConfig();
+ String name = modulesConfig.getString(
+ ConfigConstants.CONFIG_SUBMODULE_SECTION, path,
+ ConfigConstants.CONFIG_KEY_IGNORE);
+ if (name == null)
+ return null;
+ return IgnoreSubmoduleMode.valueOf(name.trim().toUpperCase());
+ }
+
+ /**
* Get repository for current submodule entry
*
* @return repository or null if non-existent
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
index e7e8af50a8..f907891bae 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
@@ -757,7 +757,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection
try {
PackParser parser = ins.newPackParser(input);
parser.setAllowThin(thinPack);
- parser.setObjectChecking(transport.isCheckFetchedObjects());
+ parser.setObjectChecker(transport.getObjectChecker());
parser.setLockMessage(lockMessage);
packLock = parser.parse(monitor);
ins.flush();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
index 72c1697593..0475d2792a 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_REFS;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
@@ -167,7 +168,8 @@ public abstract class BaseReceivePack {
private boolean allowCreates;
/** Should an incoming transfer permit delete requests? */
- private boolean allowDeletes;
+ private boolean allowAnyDeletes;
+ private boolean allowBranchDeletes;
/** Should an incoming transfer permit non-fast-forward requests? */
private boolean allowNonFastForwards;
@@ -257,7 +259,8 @@ public abstract class BaseReceivePack {
final ReceiveConfig cfg = db.getConfig().get(ReceiveConfig.KEY);
objectChecker = cfg.newObjectChecker();
allowCreates = cfg.allowCreates;
- allowDeletes = cfg.allowDeletes;
+ allowAnyDeletes = true;
+ allowBranchDeletes = cfg.allowDeletes;
allowNonFastForwards = cfg.allowNonFastForwards;
allowOfsDelta = cfg.allowOfsDelta;
advertiseRefsHook = AdvertiseRefsHook.DEFAULT;
@@ -540,7 +543,7 @@ public abstract class BaseReceivePack {
/** @return true if the client can request refs to be deleted. */
public boolean isAllowDeletes() {
- return allowDeletes;
+ return allowAnyDeletes;
}
/**
@@ -548,7 +551,25 @@ public abstract class BaseReceivePack {
* true to permit delete ref commands to be processed.
*/
public void setAllowDeletes(final boolean canDelete) {
- allowDeletes = canDelete;
+ allowAnyDeletes = canDelete;
+ }
+
+ /**
+ * @return true if the client can delete from {@code refs/heads/}.
+ * @since 3.6
+ */
+ public boolean isAllowBranchDeletes() {
+ return allowBranchDeletes;
+ }
+
+ /**
+ * @param canDelete
+ * true to permit deletion of branches from the
+ * {@code refs/heads/} namespace.
+ * @since 3.6
+ */
+ public void setAllowBranchDeletes(boolean canDelete) {
+ allowBranchDeletes = canDelete;
}
/**
@@ -908,6 +929,8 @@ public abstract class BaseReceivePack {
adv.advertiseCapability(CAPABILITY_SIDE_BAND_64K);
adv.advertiseCapability(CAPABILITY_DELETE_REFS);
adv.advertiseCapability(CAPABILITY_REPORT_STATUS);
+ if (db.getRefDatabase().performsAtomicTransactions())
+ adv.advertiseCapability(CAPABILITY_ATOMIC);
if (allowOfsDelta)
adv.advertiseCapability(CAPABILITY_OFS_DELTA);
adv.send(getAdvertisedOrDefaultRefs());
@@ -1140,12 +1163,18 @@ public abstract class BaseReceivePack {
if (cmd.getResult() != Result.NOT_ATTEMPTED)
continue;
- if (cmd.getType() == ReceiveCommand.Type.DELETE
- && !isAllowDeletes()) {
- // Deletes are not supported on this repository.
- //
- cmd.setResult(Result.REJECTED_NODELETE);
- continue;
+ if (cmd.getType() == ReceiveCommand.Type.DELETE) {
+ if (!isAllowDeletes()) {
+ // Deletes are not supported on this repository.
+ cmd.setResult(Result.REJECTED_NODELETE);
+ continue;
+ }
+ if (!isAllowBranchDeletes()
+ && ref.getName().startsWith(Constants.R_HEADS)) {
+ // Branches cannot be deleted, but other refs can.
+ cmd.setResult(Result.REJECTED_NODELETE);
+ continue;
+ }
}
if (cmd.getType() == ReceiveCommand.Type.CREATE) {
@@ -1252,6 +1281,29 @@ public abstract class BaseReceivePack {
}
/**
+ * @return if any commands have been rejected so far.
+ * @since 3.6
+ */
+ protected boolean anyRejects() {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() != Result.NOT_ATTEMPTED && cmd.getResult() != Result.OK)
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * Set the result to fail for any command that was not processed yet.
+ * @since 3.6
+ */
+ protected void failPendingCommands() {
+ for (ReceiveCommand cmd : commands) {
+ if (cmd.getResult() == Result.NOT_ATTEMPTED)
+ cmd.setResult(Result.REJECTED_OTHER_REASON, JGitText.get().transactionAborted);
+ }
+ }
+
+ /**
* Filter the list of commands according to result.
*
* @param want
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
index 15ff9d3627..e3cfd22adb 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java
@@ -187,7 +187,7 @@ class BundleFetchConnection extends BaseFetchConnection {
try {
PackParser parser = ins.newPackParser(bin);
parser.setAllowThin(true);
- parser.setObjectChecking(transport.isCheckFetchedObjects());
+ parser.setObjectChecker(transport.getObjectChecker());
parser.setLockMessage(lockMessage);
packLock = parser.parse(NullProgressMonitor.INSTANCE);
ins.flush();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
index 4f5cda7abd..d0f005cde8 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java
@@ -128,7 +128,8 @@ public class BundleWriter {
* object to pack. Multiple refs may point to the same object.
*/
public void include(final String name, final AnyObjectId id) {
- if (!Repository.isValidRefName(name))
+ boolean validRefName = Repository.isValidRefName(name) || Constants.HEAD.equals(name);
+ if (!validRefName)
throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidRefName, name));
if (include.containsKey(name))
throw new IllegalStateException(JGitText.get().duplicateRef + name);
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
index c0a70d0437..9ec14aade7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
@@ -130,6 +130,21 @@ public class GitProtocolConstants {
public static final String OPTION_ALLOW_TIP_SHA1_IN_WANT = "allow-tip-sha1-in-want"; //$NON-NLS-1$
/**
+ * Symbolic reference support for better negotiation.
+ *
+ * @since 3.6
+ */
+ public static final String OPTION_SYMREF = "symref"; //$NON-NLS-1$
+
+ /**
+ * The client supports atomic pushes. If this option is used, the server
+ * will update all refs within one atomic transaction.
+ *
+ * @since 3.6
+ */
+ public static final String CAPABILITY_ATOMIC = "atomic-push"; //$NON-NLS-1$
+
+ /**
* The client expects a status report after the server processes the pack.
*
* @since 3.2
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
index 4d931dd5df..e5eb822418 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java
@@ -43,6 +43,7 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC;
import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS;
import java.io.IOException;
@@ -199,8 +200,14 @@ public class ReceivePack extends BaseReceivePack {
}
if (unpackError == null) {
+ boolean atomic = isCapabilityEnabled(CAPABILITY_ATOMIC);
validateCommands();
+ if (atomic && anyRejects())
+ failPendingCommands();
+
preReceive.onPreReceive(this, filterCommands(Result.NOT_ATTEMPTED));
+ if (atomic && anyRejects())
+ failPendingCommands();
executeCommands();
}
unlockPack();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
index 581a44b1dc..76547a628b 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java
@@ -43,6 +43,8 @@
package org.eclipse.jgit.transport;
+import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SYMREF;
+
import java.io.IOException;
import java.util.HashSet;
import java.util.LinkedHashSet;
@@ -146,6 +148,27 @@ public abstract class RefAdvertiser {
}
/**
+ * Add a symbolic ref to capabilities.
+ * <p>
+ * This method must be invoked prior to any of the following:
+ * <ul>
+ * <li>{@link #send(Map)}
+ * <li>{@link #advertiseHave(AnyObjectId)}
+ * </ul>
+ *
+ * @param from
+ * The symbolic ref, e.g. "HEAD"
+ * @param to
+ * The real ref it points to, e.g. "refs/heads/master"
+ *
+ * @since 3.6
+ */
+ public void addSymref(String from, String to) {
+ String symref = String.format("%s=%s:%s", OPTION_SYMREF, from, to); //$NON-NLS-1$
+ advertiseCapability(symref);
+ }
+
+ /**
* Format an advertisement for the supplied refs.
*
* @param refs
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
index 770fcbed82..0303eed9a7 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/SideBandOutputStream.java
@@ -93,7 +93,7 @@ public class SideBandOutputStream extends OutputStream {
* @param chan
* channel number to prefix all packets with, so the remote side
* can demultiplex the stream and get back the original data.
- * Must be in the range [0, 255].
+ * Must be in the range [1, 255].
* @param sz
* maximum size of a data packet within the stream. The remote
* side needs to agree to the packet size to prevent buffer
@@ -105,7 +105,7 @@ public class SideBandOutputStream extends OutputStream {
public SideBandOutputStream(final int chan, final int sz, final OutputStream os) {
if (chan <= 0 || chan > 255)
throw new IllegalArgumentException(MessageFormat.format(
- JGitText.get().channelMustBeInRange0_255,
+ JGitText.get().channelMustBeInRange1_255,
Integer.valueOf(chan)));
if (sz <= HDR_SIZE)
throw new IllegalArgumentException(MessageFormat.format(
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
index b00d607eee..1de91a57ef 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java
@@ -48,8 +48,10 @@ import java.util.Map;
import org.eclipse.jgit.lib.Config;
import org.eclipse.jgit.lib.Config.SectionParser;
+import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
+import org.eclipse.jgit.util.SystemReader;
/**
* The standard "transfer", "fetch", "receive", and "uploadpack" configuration
@@ -63,7 +65,10 @@ public class TransferConfig {
}
};
- private final boolean fetchFsck;
+ private final boolean checkReceivedObjects;
+ private final boolean allowLeadingZeroFileMode;
+ private final boolean safeForWindows;
+ private final boolean safeForMacOS;
private final boolean allowTipSha1InWant;
private final String[] hideRefs;
@@ -72,9 +77,17 @@ public class TransferConfig {
}
private TransferConfig(final Config rc) {
- fetchFsck = rc.getBoolean(
+ checkReceivedObjects = rc.getBoolean(
"fetch", "fsckobjects", //$NON-NLS-1$ //$NON-NLS-2$
rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$
+ allowLeadingZeroFileMode = checkReceivedObjects
+ && rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$
+ safeForWindows = checkReceivedObjects
+ && rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$
+ SystemReader.getInstance().isWindows());
+ safeForMacOS = checkReceivedObjects
+ && rc.getBoolean("fsck", "safeForMacOS", //$NON-NLS-1$ //$NON-NLS-2$
+ SystemReader.getInstance().isMacOS());
allowTipSha1InWant = rc.getBoolean(
"uploadpack", "allowtipsha1inwant", false); //$NON-NLS-1$ //$NON-NLS-2$
@@ -83,9 +96,25 @@ public class TransferConfig {
/**
* @return strictly verify received objects?
+ * @deprecated use {@link #newObjectChecker()} instead.
*/
+ @Deprecated
public boolean isFsckObjects() {
- return fetchFsck;
+ return checkReceivedObjects;
+ }
+
+ /**
+ * @return checker to verify fetched objects, or null if checking is not
+ * enabled in the repository configuration.
+ * @since 3.6
+ */
+ public ObjectChecker newObjectChecker() {
+ if (!checkReceivedObjects)
+ return null;
+ return new ObjectChecker()
+ .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode)
+ .setSafeForWindows(safeForWindows)
+ .setSafeForMacOS(safeForMacOS);
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
index 3ad1db2d49..218562254c 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
@@ -75,6 +75,7 @@ import org.eclipse.jgit.errors.TransportException;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.NullProgressMonitor;
+import org.eclipse.jgit.lib.ObjectChecker;
import org.eclipse.jgit.lib.ProgressMonitor;
import org.eclipse.jgit.lib.Ref;
import org.eclipse.jgit.lib.Repository;
@@ -746,7 +747,7 @@ public abstract class Transport {
private boolean dryRun;
/** Should an incoming (fetch) transfer validate objects? */
- private boolean checkFetchedObjects;
+ private ObjectChecker objectChecker;
/** Should refs no longer on the source be pruned from the destination? */
private boolean removeDeletedRefs;
@@ -775,7 +776,7 @@ public abstract class Transport {
final TransferConfig tc = local.getConfig().get(TransferConfig.KEY);
this.local = local;
this.uri = uri;
- this.checkFetchedObjects = tc.isFsckObjects();
+ this.objectChecker = tc.newObjectChecker();
this.credentialsProvider = CredentialsProvider.getDefault();
}
@@ -787,7 +788,7 @@ public abstract class Transport {
protected Transport(final URIish uri) {
this.uri = uri;
this.local = null;
- this.checkFetchedObjects = true;
+ this.objectChecker = new ObjectChecker();
this.credentialsProvider = CredentialsProvider.getDefault();
}
@@ -873,16 +874,38 @@ public abstract class Transport {
* client side of the connection.
*/
public boolean isCheckFetchedObjects() {
- return checkFetchedObjects;
+ return getObjectChecker() != null;
}
/**
* @param check
* true to enable checking received objects; false to assume all
* received objects are valid.
+ * @see #setObjectChecker(ObjectChecker)
*/
public void setCheckFetchedObjects(final boolean check) {
- checkFetchedObjects = check;
+ if (check && objectChecker == null)
+ setObjectChecker(new ObjectChecker());
+ else if (!check && objectChecker != null)
+ setObjectChecker(null);
+ }
+
+ /**
+ * @return configured object checker for received objects, or null.
+ * @since 3.6
+ */
+ public ObjectChecker getObjectChecker() {
+ return objectChecker;
+ }
+
+ /**
+ * @param impl
+ * if non-null the object checking instance to verify each
+ * received object with; null to disable object checking.
+ * @since 3.6
+ */
+ public void setObjectChecker(ObjectChecker impl) {
+ objectChecker = impl;
}
/**
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
index 83806f129a..1a653bd2be 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java
@@ -807,7 +807,9 @@ public class UploadPack {
|| policy == null)
adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT);
adv.setDerefTags(true);
- advertised = adv.send(getAdvertisedOrDefaultRefs());
+ Map<String, Ref> refs = getAdvertisedOrDefaultRefs();
+ findSymrefs(adv, refs);
+ advertised = adv.send(refs);
if (adv.isEmpty())
adv.advertiseId(ObjectId.zeroId(), "capabilities^{}"); //$NON-NLS-1$
adv.end();
@@ -1430,4 +1432,12 @@ public class UploadPack {
if (sideband)
pckOut.end();
}
+
+ private void findSymrefs(
+ final RefAdvertiser adv, final Map<String, Ref> refs) {
+ Ref head = refs.get(Constants.HEAD);
+ if (head != null && head.isSymbolic()) {
+ adv.addSymref(Constants.HEAD, head.getLeaf().getName());
+ }
+ }
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
index 565b457acb..6b7183b759 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/WalkFetchConnection.java
@@ -193,7 +193,7 @@ class WalkFetchConnection extends BaseFetchConnection {
WalkFetchConnection(final WalkTransport t, final WalkRemoteObjectDatabase w) {
Transport wt = (Transport)t;
local = wt.local;
- objCheck = wt.isCheckFetchedObjects() ? new ObjectChecker() : null;
+ objCheck = wt.getObjectChecker();
inserter = local.newObjectInserter();
reader = local.newObjectReader();
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
index 9eb4285557..6311da6b68 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java
@@ -70,8 +70,8 @@ import org.eclipse.jgit.dircache.DirCacheIterator;
import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.errors.MissingObjectException;
import org.eclipse.jgit.errors.NoWorkTreeException;
+import org.eclipse.jgit.ignore.FastIgnoreRule;
import org.eclipse.jgit.ignore.IgnoreNode;
-import org.eclipse.jgit.ignore.IgnoreRule;
import org.eclipse.jgit.internal.JGitText;
import org.eclipse.jgit.lib.Constants;
import org.eclipse.jgit.lib.CoreConfig;
@@ -573,6 +573,23 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
* a relevant ignore rule file exists but cannot be read.
*/
protected boolean isEntryIgnored(final int pLen) throws IOException {
+ return isEntryIgnored(pLen, false);
+ }
+
+ /**
+ * Determine if the entry path is ignored by an ignore rule. Consider
+ * possible rule negation from child iterator.
+ *
+ * @param pLen
+ * the length of the path in the path buffer.
+ * @param negatePrevious
+ * true if the previous matching iterator rule was negation
+ * @return true if the entry is ignored by an ignore rule.
+ * @throws IOException
+ * a relevant ignore rule file exists but cannot be read.
+ */
+ private boolean isEntryIgnored(final int pLen, boolean negatePrevious)
+ throws IOException {
IgnoreNode rules = getIgnoreNode();
if (rules != null) {
// The ignore code wants path to start with a '/' if possible.
@@ -583,17 +600,23 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
if (0 < pOff)
pOff--;
String p = TreeWalk.pathOf(path, pOff, pLen);
- switch (rules.isIgnored(p, FileMode.TREE.equals(mode))) {
+ switch (rules.isIgnored(p, FileMode.TREE.equals(mode),
+ negatePrevious)) {
case IGNORED:
return true;
case NOT_IGNORED:
return false;
case CHECK_PARENT:
+ negatePrevious = false;
+ break;
+ case CHECK_PARENT_NEGATE_FIRST_MATCH:
+ negatePrevious = true;
break;
}
}
if (parent instanceof WorkingTreeIterator)
- return ((WorkingTreeIterator) parent).isEntryIgnored(pLen);
+ return ((WorkingTreeIterator) parent).isEntryIgnored(pLen,
+ negatePrevious);
return false;
}
@@ -668,6 +691,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
ptr = 0;
if (!eof())
parseEntry();
+ else if (pathLen == 0) // see bug 445363
+ pathLen = pathOffset;
}
/**
@@ -1130,7 +1155,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator {
final Entry entry;
PerDirectoryIgnoreNode(Entry entry) {
- super(Collections.<IgnoreRule> emptyList());
+ super(Collections.<FastIgnoreRule> emptyList());
this.entry = entry;
}
diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
index e73f100f96..3447f639d4 100644
--- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
+++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java
@@ -57,7 +57,9 @@ import java.util.Locale;
import java.util.TimeZone;
import org.eclipse.jgit.storage.file.FileBasedConfig;
+import org.eclipse.jgit.errors.CorruptObjectException;
import org.eclipse.jgit.lib.Config;
+import org.eclipse.jgit.lib.ObjectChecker;
/**
* Interface to read values from the system.
@@ -68,7 +70,14 @@ import org.eclipse.jgit.lib.Config;
* </p>
*/
public abstract class SystemReader {
- private static SystemReader DEFAULT = new SystemReader() {
+ private static final SystemReader DEFAULT;
+ static {
+ SystemReader r = new Default();
+ r.init();
+ DEFAULT = r;
+ }
+
+ private static class Default extends SystemReader {
private volatile String hostname;
public String getenv(String variable) {
@@ -126,7 +135,7 @@ public abstract class SystemReader {
public int getTimezone(long when) {
return getTimeZone().getOffset(when) / (60 * 1000);
}
- };
+ }
private static SystemReader INSTANCE = DEFAULT;
@@ -143,8 +152,22 @@ public abstract class SystemReader {
public static void setInstance(SystemReader newReader) {
if (newReader == null)
INSTANCE = DEFAULT;
- else
+ else {
+ newReader.init();
INSTANCE = newReader;
+ }
+ }
+
+ private ObjectChecker platformChecker;
+
+ private void init() {
+ // Creating ObjectChecker must be deferred. Unit tests change
+ // behavior of is{Windows,MacOS} in constructor of subclass.
+ if (platformChecker == null) {
+ platformChecker = new ObjectChecker()
+ .setSafeForWindows(isWindows())
+ .setSafeForMacOS(isMacOS());
+ }
}
/**
@@ -286,4 +309,16 @@ public abstract class SystemReader {
return "Mac OS X".equals(osDotName) || "Darwin".equals(osDotName); //$NON-NLS-1$ //$NON-NLS-2$
}
+ /**
+ * Check tree path entry for validity.
+ * <p>
+ * Scans a multi-directory path string such as {@code "src/main.c"}.
+ *
+ * @param path path string to scan.
+ * @throws CorruptObjectException path is invalid.
+ * @since 3.6
+ */
+ public void checkPath(String path) throws CorruptObjectException {
+ platformChecker.checkPath(path);
+ }
}