diff options
Diffstat (limited to 'org.eclipse.jgit')
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); + } } |