diff options
Diffstat (limited to 'org.eclipse.jgit')
76 files changed, 3100 insertions, 445 deletions
diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index ed330b1517..b2a8f677f3 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,5 +1,13 @@ <?xml version="1.0" encoding="UTF-8" standalone="no"?> <component id="org.eclipse.jgit" version="2"> + <resource path="src/org/eclipse/jgit/attributes/AttributesNode.java" type="org.eclipse.jgit.attributes.AttributesNode"> + <filter comment="attributes weren't really usable in earlier versions" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.attributes.AttributesNode"/> + <message_argument value="getAttributes(String, boolean, Map<String,Attribute>)"/> + </message_arguments> + </filter> + </resource> <resource path="src/org/eclipse/jgit/lib/BitmapIndex.java" type="org.eclipse.jgit.lib.BitmapIndex$BitmapBuilder"> <filter comment="interface is implemented by extenders but not clients of the API" id="403804204"> <message_arguments> @@ -14,6 +22,14 @@ </message_arguments> </filter> </resource> + <resource path="src/org/eclipse/jgit/lib/Repository.java" type="org.eclipse.jgit.lib.Repository"> + <filter comment="Only implementors of Repository are affected. That should be allowed" id="336695337"> + <message_arguments> + <message_argument value="org.eclipse.jgit.lib.Repository"/> + <message_argument value="createAttributesNodeProvider()"/> + </message_arguments> + </filter> + </resource> <resource path="src/org/eclipse/jgit/transport/PushCertificate.java" type="org.eclipse.jgit.transport.PushCertificate"> <filter comment="PushCertificate wasn't really usable in 4.0" id="338722907"> <message_arguments> @@ -35,6 +51,20 @@ </message_arguments> </filter> </resource> + <resource path="src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java" type="org.eclipse.jgit.treewalk.WorkingTreeIterator"> + <filter comment="attributes weren't really usable in earlier versions" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/> + <message_argument value="getGlobalAttributesNode()"/> + </message_arguments> + </filter> + <filter comment="attributes weren't really usable in earlier versions" id="338792546"> + <message_arguments> + <message_argument value="org.eclipse.jgit.treewalk.WorkingTreeIterator"/> + <message_argument value="getInfoAttributesNode()"/> + </message_arguments> + </filter> + </resource> <resource path="src/org/eclipse/jgit/util/FileUtils.java" type="org.eclipse.jgit.util.FileUtils"> <filter id="338792546"> <message_arguments> diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs index 195987db64..45d6d2c4c0 100644 --- a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs +++ b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs @@ -2,7 +2,7 @@ eclipse.preferences.version=1 org.eclipse.jdt.core.compiler.annotation.inheritNullAnnotations=enabled org.eclipse.jdt.core.compiler.annotation.missingNonNullByDefaultAnnotation=ignore org.eclipse.jdt.core.compiler.annotation.nonnull=org.eclipse.jgit.annotations.NonNull -org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonByDefault +org.eclipse.jdt.core.compiler.annotation.nonnullbydefault=org.eclipse.jgit.annotations.NonNullByDefault org.eclipse.jdt.core.compiler.annotation.nullable=org.eclipse.jgit.annotations.Nullable org.eclipse.jdt.core.compiler.annotation.nullanalysis=enabled org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index b6899b2801..2a953b559d 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -6,7 +6,8 @@ Bundle-Version: 4.2.0.qualifier Bundle-Localization: plugin Bundle-Vendor: %provider_name Bundle-ActivationPolicy: lazy -Export-Package: org.eclipse.jgit.api;version="4.2.0"; +Export-Package: org.eclipse.jgit.annotations;version="4.2.0", + org.eclipse.jgit.api;version="4.2.0"; uses:="org.eclipse.jgit.revwalk, org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.diff, @@ -20,9 +21,7 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.eclipse.jgit.submodule, org.eclipse.jgit.transport, org.eclipse.jgit.merge", - org.eclipse.jgit.api.errors;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.errors", + org.eclipse.jgit.api.errors;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.errors", org.eclipse.jgit.attributes;version="4.2.0", org.eclipse.jgit.blame;version="4.2.0"; uses:="org.eclipse.jgit.lib, @@ -47,8 +46,7 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.eclipse.jgit.internal.storage.pack, org.eclipse.jgit.transport, org.eclipse.jgit.dircache", - org.eclipse.jgit.events;version="4.2.0"; - uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.events;version="4.2.0";uses:="org.eclipse.jgit.lib", org.eclipse.jgit.fnmatch;version="4.2.0", org.eclipse.jgit.gitrepo;version="4.2.0"; uses:="org.eclipse.jgit.api, @@ -57,14 +55,11 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.xml.sax.helpers, org.xml.sax", org.eclipse.jgit.gitrepo.internal;version="4.2.0";x-internal:=true, - org.eclipse.jgit.hooks;version="4.2.0"; - uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.hooks;version="4.2.0";uses:="org.eclipse.jgit.lib", org.eclipse.jgit.ignore;version="4.2.0", org.eclipse.jgit.ignore.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test", org.eclipse.jgit.internal;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.test", - org.eclipse.jgit.internal.storage.dfs;version="4.2.0"; - x-friends:="org.eclipse.jgit.test, - org.eclipse.jgit.http.server", + org.eclipse.jgit.internal.storage.dfs;version="4.2.0";x-friends:="org.eclipse.jgit.test,org.eclipse.jgit.http.server", org.eclipse.jgit.internal.storage.file;version="4.2.0"; x-friends:="org.eclipse.jgit.test, org.eclipse.jgit.junit, @@ -96,31 +91,18 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.eclipse.jgit.treewalk, org.eclipse.jgit.revwalk, org.eclipse.jgit.merge", - org.eclipse.jgit.patch;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.diff", - org.eclipse.jgit.revplot;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.revwalk", + org.eclipse.jgit.patch;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.diff", + org.eclipse.jgit.revplot;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.revwalk", org.eclipse.jgit.revwalk;version="4.2.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="4.2.0"; - uses:="org.eclipse.jgit.revwalk, - org.eclipse.jgit.lib, - org.eclipse.jgit.util", - org.eclipse.jgit.storage.file;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.util", - org.eclipse.jgit.storage.pack;version="4.2.0"; - uses:="org.eclipse.jgit.lib", - org.eclipse.jgit.submodule;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.treewalk.filter, - org.eclipse.jgit.treewalk", + org.eclipse.jgit.revwalk.filter;version="4.2.0";uses:="org.eclipse.jgit.revwalk,org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.file;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.util", + org.eclipse.jgit.storage.pack;version="4.2.0";uses:="org.eclipse.jgit.lib", + org.eclipse.jgit.submodule;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.treewalk.filter,org.eclipse.jgit.treewalk", org.eclipse.jgit.transport;version="4.2.0"; uses:="org.eclipse.jgit.transport.resolver, org.eclipse.jgit.revwalk, @@ -133,11 +115,8 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.eclipse.jgit.transport.http, org.eclipse.jgit.errors, org.eclipse.jgit.storage.pack", - org.eclipse.jgit.transport.http;version="4.2.0"; - uses:="javax.net.ssl", - org.eclipse.jgit.transport.resolver;version="4.2.0"; - uses:="org.eclipse.jgit.lib, - org.eclipse.jgit.transport", + org.eclipse.jgit.transport.http;version="4.2.0";uses:="javax.net.ssl", + org.eclipse.jgit.transport.resolver;version="4.2.0";uses:="org.eclipse.jgit.lib,org.eclipse.jgit.transport", org.eclipse.jgit.treewalk;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.revwalk, @@ -145,8 +124,7 @@ Export-Package: org.eclipse.jgit.api;version="4.2.0"; org.eclipse.jgit.treewalk.filter, org.eclipse.jgit.util, org.eclipse.jgit.dircache", - org.eclipse.jgit.treewalk.filter;version="4.2.0"; - uses:="org.eclipse.jgit.treewalk", + org.eclipse.jgit.treewalk.filter;version="4.2.0";uses:="org.eclipse.jgit.treewalk", org.eclipse.jgit.util;version="4.2.0"; uses:="org.eclipse.jgit.lib, org.eclipse.jgit.transport.http, 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 51e44fd778..0e9b0b59e6 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -20,6 +20,7 @@ argumentIsNotAValidCommentString=Invalid comment: {0} atLeastOnePathIsRequired=At least one path is required. atLeastOnePatternIsRequired=At least one pattern is required. atLeastTwoFiltersNeeded=At least two filters needed. +atomicPushNotSupported=Atomic push not supported. authenticationNotSupported=authentication not supported badBase64InputCharacterAt=Bad Base64 input character at {0} : {1} (decimal) badEntryDelimiter=Bad entry delimiter @@ -45,6 +46,7 @@ cannotBeCombined=Cannot be combined. cannotBeRecursiveWhenTreesAreIncluded=TreeWalk shouldn't be recursive when tree objects are included. cannotChangeActionOnComment=Cannot change action on comment line in git-rebase-todo file, old action: {0}, new action: {1}. cannotChangeToComment=Cannot change a non-comment line to a comment line. +cannotCheckoutFromUnbornBranch=Cannot checkout from unborn branch cannotCheckoutOursSwitchBranch=Checking out ours/theirs is only possible when checking out index, not when switching branches. cannotCombineSquashWithNoff=Cannot combine --squash with --no-ff. cannotCombineTreeFilterWithRevFilter=Cannot combine TreeFilter {0} with RevFilter {1}. @@ -87,6 +89,7 @@ cannotReadBlob=Cannot read blob {0} cannotReadCommit=Cannot read commit {0} cannotReadFile=Cannot read file {0} cannotReadHEAD=cannot read HEAD: {0} {1} +cannotReadIndex=The index file {0} exists but cannot be read cannotReadObject=Cannot read object cannotReadObjectsPath=Cannot read {0}/{1}: {2} cannotReadTree=Cannot read tree {0} @@ -279,6 +282,8 @@ fileCannotBeDeleted=File cannot be deleted: {0} fileIsTooBigForThisConvenienceMethod=File is too big for this convenience method ({0} bytes). fileIsTooLarge=File is too large: {0} fileModeNotSetForPath=FileMode not set for path {0} +filterExecutionFailed=Execution of filter command ''{0}'' on file ''{1}'' failed +filterExecutionFailedRc=Execution of filter command ''{0}'' on file ''{1}'' failed with return code ''{2}'', message on stderr: ''{3}'' findingGarbage=Finding garbage flagIsDisposed={0} is disposed. flagNotFromThis={0} not from this. @@ -344,6 +349,7 @@ invalidPathReservedOnWindows=Invalid path (''{0}'' is reserved on Windows): {1} invalidReflogRevision=Invalid reflog revision: {0} invalidRefName=Invalid ref name: {0} invalidRemote=Invalid remote: {0} +invalidRepositoryStateNoHead=Invalid repository --- cannot read HEAD invalidShallowObject=invalid shallow object {0}, expected commit invalidStageForPath=Invalid stage {0} for path {1} invalidTagOption=Invalid tag option: {0} @@ -450,6 +456,7 @@ packfileIsTruncated=Packfile {0} is truncated. packfileIsTruncatedNoParam=Packfile is truncated. packHandleIsStale=Pack file {0} handle is stale, removing it from pack list packHasUnresolvedDeltas=pack has unresolved deltas +packInaccessible=Pack file {0} now inaccessible; removing it from pack list packingCancelledDuringObjectsWriting=Packing cancelled during objects writing packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2} packRefs=Pack refs diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java new file mode 100644 index 0000000000..08f81cb9f8 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015, 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.annotations; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.ElementType.LOCAL_VARIABLE; +import static java.lang.annotation.ElementType.METHOD; +import static java.lang.annotation.ElementType.PARAMETER; + +import java.lang.annotation.Documented; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * JGit's replacement for the {@code javax.annotation.Nonnull}. + * <p> + * Denotes that a local variable, parameter, field, method return value expected + * to be non {@code null}. + * + * @since 4.2 + */ +@Documented +@Retention(RetentionPolicy.CLASS) +@Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) +public @interface NonNull { + // marker annotation with no members +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java index 254920e7a3..7b9156710f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/Nullable.java @@ -54,13 +54,46 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; /** - * JGit's replacement for the {@code javax.annotations.Nullable}. + * Marks types that can hold the value {@code null} at run time. * <p> - * Denotes that a local variable, parameter, field, method return value can be - * {@code null}. + * Unlike {@code org.eclipse.jdt.annotation.Nullable}, this has run-time + * retention, allowing the annotation to be recognized by + * <a href="https://github.com/google/guice/wiki/UseNullable">Guice</a>. Unlike + * {@code javax.annotation.Nullable}, this does not involve importing new classes + * to a standard (Java EE) package, so it can be deployed in an OSGi container + * without running into + * <a href="http://wiki.osgi.org/wiki/Split_Packages">split-package</a> + * <a href="https://gerrit-review.googlesource.com/50112">problems</a>. + * <p> + * You can use this annotation to qualify a type in a method signature or local + * variable declaration. The entity whose type has this annotation is allowed to + * hold the value {@code null} at run time. This allows annotation based null + * analysis to infer that + * <ul> + * <li>Binding a {@code null} value to the entity is legal. + * <li>Dereferencing the entity is unsafe and can trigger a + * {@code NullPointerException}. + * </ul> + * <p> + * To avoid a dependency on Java 8, this annotation does not use + * {@link Target @Target} {@code TYPE_USE}. That may change when JGit starts + * requiring Java 8. + * <p> + * <b>Warning:</b> Please do not use this annotation on arrays. Different + * annotation processors treat {@code @Nullable Object[]} differently: some + * treat it as an array of nullable objects, for consistency with versions of + * {@code Nullable} defined with {@code @Target} {@code TYPE_USE}, while others + * treat it as a nullable array of objects. JGit therefore avoids using this + * annotation on arrays altogether. + * + * @see <a href= + * "http://types.cs.washington.edu/checker-framework/current/checker-framework-manual.html#faq-array-syntax-meaning"> + * The checker-framework manual</a> + * + * @since 4.2 */ @Documented -@Retention(RetentionPolicy.CLASS) +@Retention(RetentionPolicy.RUNTIME) @Target({ FIELD, METHOD, PARAMETER, LOCAL_VARIABLE }) public @interface Nullable { // marker annotation with no members diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java index de6c32a808..67fb342fe2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -48,6 +48,7 @@ import java.io.InputStream; import java.util.Collection; import java.util.LinkedList; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.JGitInternalException; import org.eclipse.jgit.api.errors.NoFilepatternException; @@ -63,6 +64,7 @@ import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; @@ -139,6 +141,7 @@ public class AddCommand extends GitCommand<DirCache> { try (ObjectInserter inserter = repo.newObjectInserter(); final TreeWalk tw = new TreeWalk(repo)) { + tw.setOperationType(OperationType.CHECKIN_OP); dc = repo.lockDirCache(); DirCacheIterator c; @@ -146,6 +149,7 @@ public class AddCommand extends GitCommand<DirCache> { tw.addTree(new DirCacheBuildIterator(builder)); if (workingTreeIterator == null) workingTreeIterator = new FileTreeIterator(repo); + workingTreeIterator.setDirCacheIterator(tw, 0); tw.addTree(workingTreeIterator); tw.setRecursive(true); if (!addAll) @@ -208,6 +212,9 @@ public class AddCommand extends GitCommand<DirCache> { builder.commit(); setCallable(false); } catch (IOException e) { + Throwable cause = e.getCause(); + if (cause != null && cause instanceof FilterFailedException) + throw (FilterFailedException) cause; throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e); } finally { 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 8d85bfcb15..8743ea9ac7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -222,6 +222,12 @@ public class CheckoutCommand extends GitCommand<Ref> { } Ref headRef = repo.getRef(Constants.HEAD); + if (headRef == null) { + // TODO Git CLI supports checkout from unborn branch, we should + // also allow this + throw new UnsupportedOperationException( + JGitText.get().cannotCheckoutFromUnbornBranch); + } String shortHeadRef = getShortBranchName(headRef); String refLogMessage = "checkout: moving from " + shortHeadRef; //$NON-NLS-1$ ObjectId branch; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java index 6174d48d3a..9466dab74e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -86,6 +86,7 @@ import org.eclipse.jgit.revwalk.RevWalk; import org.eclipse.jgit.treewalk.CanonicalTreeParser; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.util.ChangeIdUtil; /** @@ -328,9 +329,12 @@ public class CommitCommand extends GitCommand<RevCommit> { boolean emptyCommit = true; try (TreeWalk treeWalk = new TreeWalk(repo)) { + treeWalk.setOperationType(OperationType.CHECKIN_OP); int dcIdx = treeWalk .addTree(new DirCacheBuildIterator(existingBuilder)); - int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); + FileTreeIterator fti = new FileTreeIterator(repo); + fti.setDirCacheIterator(treeWalk, 0); + int fIdx = treeWalk.addTree(fti); int hIdx = -1; if (headId != null) hIdx = treeWalk.addTree(rw.parseTree(headId)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java index addca4c469..2cd5f59a71 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/Git.java @@ -713,8 +713,48 @@ public class Git implements AutoCloseable { } /** - * @return the git repository this class is interacting with; see {@link - * #close()} for notes on closing this repository. + * Return a command used to list the available remotes. + * + * @return a {@link RemoteListCommand} + * @since 4.2 + */ + public RemoteListCommand remoteList() { + return new RemoteListCommand(repo); + } + + /** + * Return a command used to add a new remote. + * + * @return a {@link RemoteAddCommand} + * @since 4.2 + */ + public RemoteAddCommand remoteAdd() { + return new RemoteAddCommand(repo); + } + + /** + * Return a command used to remove an existing remote. + * + * @return a {@link RemoteRemoveCommand} + * @since 4.2 + */ + public RemoteRemoveCommand remoteRemove() { + return new RemoteRemoveCommand(repo); + } + + /** + * Return a command used to change the URL of an existing remote. + * + * @return a {@link RemoteSetUrlCommand} + * @since 4.2 + */ + public RemoteSetUrlCommand remoteSetUrl() { + return new RemoteSetUrlCommand(repo); + } + + /** + * @return the git repository this class is interacting with; see + * {@link #close()} for notes on closing this repository. */ public Repository getRepository() { return repo; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java index 227e32236d..f5b82bdd7d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/PushCommand.java @@ -89,9 +89,8 @@ public class PushCommand extends private String receivePack = RemoteConfig.DEFAULT_RECEIVE_PACK; private boolean dryRun; - + private boolean atomic; private boolean force; - private boolean thin = Transport.DEFAULT_PUSH_THIN; private OutputStream out; @@ -145,6 +144,7 @@ public class PushCommand extends transports = Transport.openAll(repo, remote, Transport.Operation.PUSH); for (final Transport transport : transports) { transport.setPushThin(thin); + transport.setPushAtomic(atomic); if (receivePack != null) transport.setOptionReceivePack(receivePack); transport.setDryRun(dryRun); @@ -397,6 +397,29 @@ public class PushCommand extends } /** + * @return true if all-or-nothing behavior is requested. + * @since 4.2 + */ + public boolean isAtomic() { + return atomic; + } + + /** + * Requests atomic push (all references updated, or no updates). + * + * Default setting is false. + * + * @param atomic + * @return {@code this} + * @since 4.2 + */ + public PushCommand setAtomic(boolean atomic) { + checkCallable(); + this.atomic = atomic; + return this; + } + + /** * @return the force preference for push operation */ public boolean isForce() { 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 ff29008420..8582bbb0dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -668,12 +668,13 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } private void writeRewrittenHashes() throws RevisionSyntaxException, - IOException { + IOException, RefNotFoundException { File currentCommitFile = rebaseState.getFile(CURRENT_COMMIT); if (!currentCommitFile.exists()) return; - String head = repo.resolve(Constants.HEAD).getName(); + ObjectId headId = getHead().getObjectId(); + String head = headId.getName(); String currentCommits = rebaseState.readFile(CURRENT_COMMIT); for (String current : currentCommits.split("\n")) //$NON-NLS-1$ RebaseState @@ -743,8 +744,8 @@ public class RebaseCommand extends GitCommand<RebaseResult> { private void resetSoftToParent() throws IOException, GitAPIException, CheckoutConflictException { - Ref orig_head = repo.getRef(Constants.ORIG_HEAD); - ObjectId orig_headId = orig_head.getObjectId(); + Ref ref = repo.getRef(Constants.ORIG_HEAD); + ObjectId orig_head = ref == null ? null : ref.getObjectId(); try { // we have already commited the cherry-picked commit. // what we need is to have changes introduced by this @@ -755,7 +756,7 @@ public class RebaseCommand extends GitCommand<RebaseResult> { } finally { // set ORIG_HEAD back to where we started because soft // reset moved it - repo.writeOrigHead(orig_headId); + repo.writeOrigHead(orig_head); } } @@ -980,6 +981,9 @@ public class RebaseCommand extends GitCommand<RebaseResult> { try { raw = IO.readFully(authorScriptFile); } catch (FileNotFoundException notFound) { + if (authorScriptFile.exists()) { + throw notFound; + } return null; } return parseAuthor(raw); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java new file mode 100644 index 0000000000..679566903f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RefSpec; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +/** + * Used to add a new remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see <a href= + * "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git + * documentation about Remote</a> + * + * @since 4.2 + */ +public class RemoteAddCommand extends GitCommand<RemoteConfig> { + + private String name; + + private URIish uri; + + /** + * @param repo + */ + protected RemoteAddCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to add. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * The URL of the repository for the new remote. + * + * @param uri + * an URL for the remote + */ + public void setUri(URIish uri) { + this.uri = uri; + } + + /** + * Executes the {@code remote add} command with all the options and + * parameters collected by the setter methods of this class. + * + * @return the {@link RemoteConfig} object of the added remote + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + + RefSpec refSpec = new RefSpec(); + refSpec = refSpec.setForceUpdate(true); + refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", //$NON-NLS-1$ + Constants.R_REMOTES + name + "/*"); //$NON-NLS-1$ + remote.addFetchRefSpec(refSpec); + + remote.addURI(uri); + + remote.update(config); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java new file mode 100644 index 0000000000..f778eaa28c --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.net.URISyntaxException; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteConfig; + +/** + * Used to obtain the list of remotes. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see <a href= + * "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git + * documentation about Remote</a> + * + * @since 4.2 + */ +public class RemoteListCommand extends GitCommand<List<RemoteConfig>> { + + /** + * @param repo + */ + protected RemoteListCommand(Repository repo) { + super(repo); + } + + /** + * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + * + * @return a list of {@link RemoteConfig} objects. + */ + @Override + public List<RemoteConfig> call() throws GitAPIException { + checkCallable(); + + try { + return RemoteConfig.getAllRemoteConfigs(repo.getConfig()); + } catch (URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java new file mode 100644 index 0000000000..5782bf61b5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.ConfigConstants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RemoteConfig; + +/** + * Used to remove an existing remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see <a href= + * "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git + * documentation about Remote</a> + * + * @since 4.2 + */ +public class RemoteRemoveCommand extends GitCommand<RemoteConfig> { + + private String name; + + /** + * @param repo + */ + protected RemoteRemoveCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to remove. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + * + * @return the {@link RemoteConfig} object of the removed remote + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + config.unsetSection(ConfigConstants.CONFIG_KEY_REMOTE, name); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java new file mode 100644 index 0000000000..6bd2ac7993 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015, Kaloyan Raev <kaloyan.r@zend.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.api; + +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.List; + +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.StoredConfig; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; + +/** + * Used to to change the URL of a remote. + * + * This class has setters for all supported options and arguments of this + * command and a {@link #call()} method to finally execute the command. + * + * @see <a href= + * "http://www.kernel.org/pub/software/scm/git/docs/git-remote.html" > Git + * documentation about Remote</a> + * + * @since 4.2 + */ +public class RemoteSetUrlCommand extends GitCommand<RemoteConfig> { + + private String name; + + private URIish uri; + + private boolean push; + + /** + * @param repo + */ + protected RemoteSetUrlCommand(Repository repo) { + super(repo); + } + + /** + * The name of the remote to change the URL for. + * + * @param name + * a remote name + */ + public void setName(String name) { + this.name = name; + } + + /** + * The new URL for the remote. + * + * @param uri + * an URL for the remote + */ + public void setUri(URIish uri) { + this.uri = uri; + } + + /** + * Whether to change the push URL of the remote instead of the fetch URL. + * + * @param push + * <code>true</code> to set the push url, <code>false</code> to + * set the fetch url + */ + public void setPush(boolean push) { + this.push = push; + } + + /** + * Executes the {@code remote} command with all the options and parameters + * collected by the setter methods of this class. + * + * @return the {@link RemoteConfig} object of the modified remote + */ + @Override + public RemoteConfig call() throws GitAPIException { + checkCallable(); + + try { + StoredConfig config = repo.getConfig(); + RemoteConfig remote = new RemoteConfig(config, name); + if (push) { + List<URIish> uris = remote.getPushURIs(); + if (uris.size() > 1) { + throw new JGitInternalException( + "remote.newtest.pushurl has multiple values"); //$NON-NLS-1$ + } else if (uris.size() == 1) { + remote.removePushURI(uris.get(0)); + } + remote.addPushURI(uri); + } else { + List<URIish> uris = remote.getURIs(); + if (uris.size() > 1) { + throw new JGitInternalException( + "remote.newtest.url has multiple values"); //$NON-NLS-1$ + } else if (uris.size() == 1) { + remote.removeURI(uris.get(0)); + } + remote.addURI(uri); + } + + remote.update(config); + config.save(); + return remote; + } catch (IOException | URISyntaxException e) { + throw new JGitInternalException(e.getMessage(), e); + } + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java index 607253b76d..0731dd45ec 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RenameBranchCommand.java @@ -51,6 +51,7 @@ import org.eclipse.jgit.api.errors.DetachedHeadException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRefNameException; import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.api.errors.NoHeadException; import org.eclipse.jgit.api.errors.RefAlreadyExistsException; import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.internal.JGitText; @@ -121,6 +122,10 @@ public class RenameBranchCommand extends GitCommand<Ref> { fullOldName = ref.getName(); } else { fullOldName = repo.getFullBranch(); + if (fullOldName == null) { + throw new NoHeadException( + JGitText.get().invalidRepositoryStateNoHead); + } if (ObjectId.isId(fullOldName)) throw new DetachedHeadException(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java new file mode 100644 index 0000000000..fbc30ef162 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> and + * other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ + +package org.eclipse.jgit.api.errors; + +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Exception thrown when the execution of a filter command failed + * + * @since 4.2 + */ +public class FilterFailedException extends GitAPIException { + private static final long serialVersionUID = 1L; + + private String filterCommand; + + private String path; + + private byte[] stdout; + + private String stderr; + + private int rc; + + /** + * Thrown if during execution of filter command an exception occurred + * + * @param cause + * the exception + * @param filterCommand + * the command which failed + * @param path + * the path processed by the filter + */ + public FilterFailedException(Exception cause, String filterCommand, + String path) { + super(MessageFormat.format(JGitText.get().filterExecutionFailed, + filterCommand, path), cause); + this.filterCommand = filterCommand; + this.path = path; + } + + /** + * Thrown if a filter command returns a non-zero return code + * + * @param rc + * the return code + * @param filterCommand + * the command which failed + * @param path + * the path processed by the filter + * @param stdout + * the output the filter generated so far. This should be limited + * to reasonable size. + * @param stderr + * the stderr output of the filter + */ + @SuppressWarnings("boxing") + public FilterFailedException(int rc, String filterCommand, String path, + byte[] stdout, String stderr) { + super(MessageFormat.format(JGitText.get().filterExecutionFailedRc, + filterCommand, path, rc, stderr)); + this.rc = rc; + this.filterCommand = filterCommand; + this.path = path; + this.stdout = stdout; + this.stderr = stderr; + } + + /** + * @return the filterCommand + */ + public String getFilterCommand() { + return filterCommand; + } + + /** + * @return the path of the file processed by the filter command + */ + public String getPath() { + return path; + } + + /** + * @return the output generated by the filter command. Might be truncated to + * limit memory consumption. + */ + public byte[] getOutput() { + return stdout; + } + + /** + * @return the error output returned by the filter command + */ + public String getError() { + return stderr; + } + + /** + * @return the return code returned by the filter command + */ + public int getReturnCode() { + return rc; + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java index d3ce685187..905ad76929 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attribute.java @@ -50,8 +50,10 @@ package org.eclipse.jgit.attributes; * <li>Set - represented by {@link State#SET}</li> * <li>Unset - represented by {@link State#UNSET}</li> * <li>Set to a value - represented by {@link State#CUSTOM}</li> - * <li>Unspecified - <code>null</code> is used instead of an instance of this - * class</li> + * <li>Unspecified - used to revert an attribute . This is crucial in order to + * mark an attribute as unspecified in the attributes map and thus preventing + * following (with lower priority) nodes from setting the attribute to a value + * at all</li> * </ul> * </p> * @@ -61,6 +63,7 @@ public final class Attribute { /** * The attribute value state + * see also https://www.kernel.org/pub/software/scm/git/docs/gitattributes.html */ public static enum State { /** the attribute is set */ @@ -69,6 +72,13 @@ public final class Attribute { /** the attribute is unset */ UNSET, + /** + * the attribute appears as if it would not be defined at all + * + * @since 4.2 + */ + UNSPECIFIED, + /** the attribute is set to a custom value */ CUSTOM } @@ -176,6 +186,8 @@ public final class Attribute { return key; case UNSET: return "-" + key; //$NON-NLS-1$ + case UNSPECIFIED: + return "!" + key; //$NON-NLS-1$ case CUSTOM: default: return key + "=" + value; //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java new file mode 100644 index 0000000000..0810e31682 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015, Ivan Motsch <ivan.motsch@bsiag.com> + * + * 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.attributes; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.LinkedHashMap; +import java.util.Map; + +import org.eclipse.jgit.attributes.Attribute.State; + +/** + * Represents a set of attributes for a path + * <p> + * + * @since 4.2 + */ +public final class Attributes { + private final Map<String, Attribute> map = new LinkedHashMap<>(); + + /** + * Creates a new instance + * + * @param attributes + */ + public Attributes(Attribute... attributes) { + if (attributes != null) { + for (Attribute a : attributes) { + put(a); + } + } + } + + /** + * @return true if the set does not contain any attributes + */ + public boolean isEmpty() { + return map.isEmpty(); + } + + /** + * @param key + * @return the attribute or null + */ + public Attribute get(String key) { + return map.get(key); + } + + /** + * @return all attributes + */ + public Collection<Attribute> getAll() { + return new ArrayList<>(map.values()); + } + + /** + * @param a + */ + public void put(Attribute a) { + map.put(a.getKey(), a); + } + + /** + * @param key + */ + public void remove(String key) { + map.remove(key); + } + + /** + * @param key + * @return true if the {@link Attributes} contains this key + */ + public boolean containsKey(String key) { + return map.containsKey(key); + } + + /** + * Returns the state. + * + * @param key + * + * @return the state (never returns <code>null</code>) + */ + public Attribute.State getState(String key) { + Attribute a = map.get(key); + return a != null ? a.getState() : Attribute.State.UNSPECIFIED; + } + + /** + * @param key + * @return true if the key is {@link State#SET}, false in all other cases + */ + public boolean isSet(String key) { + return (getState(key) == State.SET); + } + + /** + * @param key + * @return true if the key is {@link State#UNSET}, false in all other cases + */ + public boolean isUnset(String key) { + return (getState(key) == State.UNSET); + } + + /** + * @param key + * @return true if the key is {@link State#UNSPECIFIED}, false in all other + * cases + */ + public boolean isUnspecified(String key) { + return (getState(key) == State.UNSPECIFIED); + } + + /** + * @param key + * @return true if the key is {@link State#CUSTOM}, false in all other cases + * see {@link #getValue(String)} for the value of the key + */ + public boolean isCustom(String key) { + return (getState(key) == State.CUSTOM); + } + + /** + * @param key + * @return the attribute value (may be <code>null</code>) + */ + public String getValue(String key) { + Attribute a = map.get(key); + return a != null ? a.getValue() : null; + } + + @Override + public String toString() { + StringBuilder buf = new StringBuilder(); + buf.append(getClass().getSimpleName()); + buf.append("["); //$NON-NLS-1$ + buf.append(" "); //$NON-NLS-1$ + for (Attribute a : map.values()) { + buf.append(a.toString()); + buf.append(" "); //$NON-NLS-1$ + } + buf.append("]"); //$NON-NLS-1$ + return buf.toString(); + } + + @Override + public int hashCode() { + return map.hashCode(); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (!(obj instanceof Attributes)) + return false; + Attributes other = (Attributes) obj; + return this.map.equals(other.map); + } + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java index 70f56ff964..5c0aba2e0e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNode.java @@ -50,7 +50,6 @@ import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.ListIterator; -import java.util.Map; import org.eclipse.jgit.lib.Constants; @@ -134,11 +133,12 @@ public class AttributesNode { * true if the target item is a directory. * @param attributes * Map that will hold the attributes matching this entry path. If - * it is not empty, this method will NOT override any - * existing entry. + * it is not empty, this method will NOT override any existing + * entry. + * @since 4.2 */ - public void getAttributes(String entryPath, boolean isDirectory, - Map<String, Attribute> attributes) { + public void getAttributes(String entryPath, + boolean isDirectory, Attributes attributes) { // Parse rules in the reverse order that they were read since the last // entry should be used ListIterator<AttributesRule> ruleIterator = rules.listIterator(rules @@ -153,7 +153,7 @@ public class AttributesNode { while (attributeIte.hasPrevious()) { Attribute attr = attributeIte.previous(); if (!attributes.containsKey(attr.getKey())) - attributes.put(attr.getKey(), attr); + attributes.put(attr); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java new file mode 100644 index 0000000000..6f2ebad677 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> + * 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.attributes; + +import java.io.IOException; + +import org.eclipse.jgit.lib.CoreConfig; + +/** + * An interface used to retrieve the global and info {@link AttributesNode}s. + * + * @since 4.2 + * + */ +public interface AttributesNodeProvider { + + /** + * Retrieve the {@link AttributesNode} that holds the information located + * in $GIT_DIR/info/attributes file. + * + * @return the {@link AttributesNode} that holds the information located in + * $GIT_DIR/info/attributes file. + * @throws IOException + * if an error is raised while parsing the attributes file + */ + public AttributesNode getInfoAttributesNode() throws IOException; + + /** + * Retrieve the {@link AttributesNode} that holds the information located + * in the global gitattributes file. + * + * @return the {@link AttributesNode} that holds the information located in + * the global gitattributes file. + * @throws IOException + * IOException if an error is raised while parsing the + * attributes file + * @see CoreConfig#getAttributesFile() + */ + public AttributesNode getGlobalAttributesNode() throws IOException; + +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java new file mode 100644 index 0000000000..1037f697d4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.attributes; + +/** + * Interface for classes which provide git attributes + * + * @since 4.2 + */ +public interface AttributesProvider { + /** + * @return the currently active attributes + */ + public Attributes getAttributes(); +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java index bcac14b5ff..35d18c4b2a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesRule.java @@ -84,6 +84,13 @@ public class AttributesRule { continue; } + if (attribute.startsWith("!")) {//$NON-NLS-1$ + if (attribute.length() > 1) + result.add(new Attribute(attribute.substring(1), + State.UNSPECIFIED)); + continue; + } + final int equalsIndex = attribute.indexOf("="); //$NON-NLS-1$ if (equalsIndex == -1) result.add(new Attribute(attribute, State.SET)); @@ -200,4 +207,16 @@ public class AttributesRule { boolean match = matcher.matches(relativeTarget, isDirectory); return match; } + + @Override + public String toString() { + StringBuilder sb = new StringBuilder(); + sb.append(pattern); + for (Attribute a : attributes) { + sb.append(" "); //$NON-NLS-1$ + sb.append(a); + } + return sb.toString(); + + } }
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java index 0dc4b05787..444ab1cb83 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/ContentSource.java @@ -132,7 +132,11 @@ public abstract class ContentSource { @Override public long size(String path, ObjectId id) throws IOException { - return reader.getObjectSize(id, Constants.OBJ_BLOB); + try { + return reader.getObjectSize(id, Constants.OBJ_BLOB); + } catch (MissingObjectException ignore) { + return 0; + } } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java index 6d9a32db92..fa0339544f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -63,6 +63,7 @@ import java.util.Comparator; import java.util.List; import org.eclipse.jgit.errors.CorruptObjectException; +import org.eclipse.jgit.errors.IndexReadException; import org.eclipse.jgit.errors.LockFailedException; import org.eclipse.jgit.errors.UnmergedPathException; import org.eclipse.jgit.events.IndexChangedEvent; @@ -70,12 +71,15 @@ import org.eclipse.jgit.events.IndexChangedListener; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.FileSnapshot; import org.eclipse.jgit.internal.storage.file.LockFile; +import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; +import org.eclipse.jgit.treewalk.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.filter.PathFilterGroup; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.IO; @@ -145,6 +149,28 @@ public class DirCache { } /** + * Create a new in memory index read from the contents of a tree. + * + * @param reader + * reader to access the tree objects from a repository. + * @param treeId + * tree to read. Must identify a tree, not a tree-ish. + * @return a new cache which has no backing store file, but contains the + * contents of {@code treeId}. + * @throws IOException + * one or more trees not available from the ObjectReader. + * @since 4.2 + */ + public static DirCache read(ObjectReader reader, AnyObjectId treeId) + throws IOException { + DirCache d = newInCore(); + DirCacheBuilder b = d.builder(); + b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId); + b.finish(); + return d; + } + + /** * Create a new in-core index representation and read an index from disk. * <p> * The new index will be read before it is returned to the caller. Read @@ -417,6 +443,12 @@ public class DirCache { } } } catch (FileNotFoundException fnfe) { + if (liveFile.exists()) { + // Panic: the index file exists but we can't read it + throw new IndexReadException( + MessageFormat.format(JGitText.get().cannotReadIndex, + liveFile.getAbsolutePath(), fnfe)); + } // Someone must have deleted it between our exists test // and actually opening the path. That's fine, its empty. // @@ -869,8 +901,8 @@ public class DirCache { */ public DirCacheEntry[] getEntriesWithin(String path) { if (path.length() == 0) { - final DirCacheEntry[] r = new DirCacheEntry[sortedEntries.length]; - System.arraycopy(sortedEntries, 0, r, 0, sortedEntries.length); + DirCacheEntry[] r = new DirCacheEntry[entryCnt]; + System.arraycopy(sortedEntries, 0, r, 0, entryCnt); return r; } if (!path.endsWith("/")) //$NON-NLS-1$ @@ -963,6 +995,7 @@ public class DirCache { private void updateSmudgedEntries() throws IOException { List<String> paths = new ArrayList<String>(128); try (TreeWalk walk = new TreeWalk(repository)) { + walk.setOperationType(OperationType.CHECKIN_OP); for (int i = 0; i < entryCnt; i++) if (sortedEntries[i].isSmudged()) paths.add(sortedEntries[i].getPathString()); @@ -974,6 +1007,7 @@ public class DirCache { FileTreeIterator fIter = new FileTreeIterator(repository); walk.addTree(iIter); walk.addTree(fIter); + fIter.setDirCacheIterator(walk, 0); walk.setRecursive(true); while (walk.next()) { iIter = walk.getTree(0, DirCacheIterator.class); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java index 6c7a70c52e..cfebe2d073 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheBuilder.java @@ -44,6 +44,9 @@ package org.eclipse.jgit.dircache; +import static org.eclipse.jgit.lib.FileMode.TYPE_MASK; +import static org.eclipse.jgit.lib.FileMode.TYPE_TREE; + import java.io.IOException; import java.text.MessageFormat; import java.util.Arrays; @@ -51,9 +54,7 @@ import java.util.Arrays; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.ObjectReader; -import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.CanonicalTreeParser; -import org.eclipse.jgit.treewalk.TreeWalk; /** * Updates a {@link DirCache} by adding individual {@link DirCacheEntry}s. @@ -102,8 +103,9 @@ public class DirCacheBuilder extends BaseDirCacheEditor { */ public void add(final DirCacheEntry newEntry) { if (newEntry.getRawMode() == 0) - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().fileModeNotSetForPath - , newEntry.getPathString())); + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().fileModeNotSetForPath, + newEntry.getPathString())); beforeAdd(newEntry); fastAdd(newEntry); } @@ -162,27 +164,56 @@ public class DirCacheBuilder extends BaseDirCacheEditor { * @throws IOException * a tree cannot be read to iterate through its entries. */ - public void addTree(final byte[] pathPrefix, final int stage, - final ObjectReader reader, final AnyObjectId tree) throws IOException { - final TreeWalk tw = new TreeWalk(reader); - tw.addTree(new CanonicalTreeParser(pathPrefix, reader, tree - .toObjectId())); - tw.setRecursive(true); - if (tw.next()) { - final DirCacheEntry newEntry = toEntry(stage, tw); - beforeAdd(newEntry); - fastAdd(newEntry); - while (tw.next()) - fastAdd(toEntry(stage, tw)); + public void addTree(byte[] pathPrefix, int stage, ObjectReader reader, + AnyObjectId tree) throws IOException { + CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree); + while (!p.eof()) { + if (isTree(p)) { + p = enterTree(p, reader); + continue; + } + + DirCacheEntry first = toEntry(stage, p); + beforeAdd(first); + fastAdd(first); + p = p.next(); + break; + } + + // Rest of tree entries are correctly sorted; use fastAdd(). + while (!p.eof()) { + if (isTree(p)) { + p = enterTree(p, reader); + } else { + fastAdd(toEntry(stage, p)); + p = p.next(); + } } } - private DirCacheEntry toEntry(final int stage, final TreeWalk tw) { - final DirCacheEntry e = new DirCacheEntry(tw.getRawPath(), stage); - final AbstractTreeIterator i; + private static CanonicalTreeParser createTreeParser(byte[] pathPrefix, + ObjectReader reader, AnyObjectId tree) throws IOException { + return new CanonicalTreeParser(pathPrefix, reader, tree); + } + + private static boolean isTree(CanonicalTreeParser p) { + return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE; + } + + private static CanonicalTreeParser enterTree(CanonicalTreeParser p, + ObjectReader reader) throws IOException { + p = p.createSubtreeIterator(reader); + return p.eof() ? p.next() : p; + } + + private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) { + byte[] buf = i.getEntryPathBuffer(); + int len = i.getEntryPathLength(); + byte[] path = new byte[len]; + System.arraycopy(buf, 0, path, 0, len); - i = tw.getTree(0, AbstractTreeIterator.class); - e.setFileMode(tw.getFileMode(0)); + DirCacheEntry e = new DirCacheEntry(path, stage); + e.setFileMode(i.getEntryRawMode()); e.setObjectIdFromRaw(i.idBuffer(), i.idOffset()); return e; } @@ -242,9 +273,9 @@ public class DirCacheBuilder extends BaseDirCacheEditor { sorted = true; } - private static IllegalStateException bad(final DirCacheEntry a, - final String msg) { - return new IllegalStateException(msg + ": " + a.getStage() + " " //$NON-NLS-1$ //$NON-NLS-2$ - + a.getPathString()); + private static IllegalStateException bad(DirCacheEntry a, String msg) { + return new IllegalStateException(String.format( + "%s: %d %s", //$NON-NLS-1$ + msg, Integer.valueOf(a.getStage()), a.getPathString())); } } 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 0036ab5089..4eb688170c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -52,15 +52,18 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.errors.CheckoutConflictException; import org.eclipse.jgit.errors.CorruptObjectException; 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; +import org.eclipse.jgit.lib.NullProgressMonitor; import org.eclipse.jgit.lib.ObjectChecker; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; @@ -76,6 +79,7 @@ import org.eclipse.jgit.treewalk.WorkingTreeIterator; import org.eclipse.jgit.treewalk.WorkingTreeOptions; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.FileUtils; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.SystemReader; @@ -85,9 +89,10 @@ import org.eclipse.jgit.util.io.AutoCRLFOutputStream; * This class handles checking out one or two trees merging with the index. */ public class DirCacheCheckout { + private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024; private Repository repo; - private HashMap<String, ObjectId> updated = new HashMap<String, ObjectId>(); + private HashMap<String, String> updated = new HashMap<String, String>(); private ArrayList<String> conflicts = new ArrayList<String>(); @@ -112,9 +117,9 @@ public class DirCacheCheckout { private boolean emptyDirCache; /** - * @return a list of updated paths and objectIds + * @return a list of updated paths and smudgeFilterCommands */ - public Map<String, ObjectId> getUpdated() { + public Map<String, String> getUpdated() { return updated; } @@ -447,7 +452,8 @@ public class DirCacheCheckout { for (String path : updated.keySet()) { DirCacheEntry entry = dc.getEntry(path); if (!FileMode.GITLINK.equals(entry.getRawMode())) - checkoutEntry(repo, entry, objectReader, false); + checkoutEntry(repo, entry, objectReader, false, + updated.get(path)); } // commit the index builder - a new index is persisted @@ -996,9 +1002,12 @@ public class DirCacheCheckout { removed.add(path); } - private void update(String path, ObjectId mId, FileMode mode) { + private void update(String path, ObjectId mId, FileMode mode) + throws IOException { if (!FileMode.TREE.equals(mode)) { - updated.put(path, mId); + updated.put(path, + walk.getFilterCommand(Constants.ATTR_FILTER_TYPE_SMUDGE)); + DirCacheEntry entry = new DirCacheEntry(path, DirCacheEntry.STAGE_0); entry.setObjectId(mId); entry.setFileMode(mode); @@ -1150,7 +1159,7 @@ public class DirCacheCheckout { */ public static void checkoutEntry(Repository repo, DirCacheEntry entry, ObjectReader or) throws IOException { - checkoutEntry(repo, entry, or, false); + checkoutEntry(repo, entry, or, false, null); } /** @@ -1186,6 +1195,46 @@ public class DirCacheCheckout { */ public static void checkoutEntry(Repository repo, DirCacheEntry entry, ObjectReader or, boolean deleteRecursive) throws IOException { + checkoutEntry(repo, entry, or, deleteRecursive, null); + } + + /** + * 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> + * <b>Note:</b> if the entry path on local file system exists as a file, it + * will be deleted and if it exists as a directory, it will be deleted + * recursively, independently if has any content. + * </p> + * + * <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 + * @param deleteRecursive + * true to recursively delete final path if it exists on the file + * system + * @param smudgeFilterCommand + * the filter command to be run for smudging the entry to be + * checked out + * + * @throws IOException + * @since 4.2 + */ + public static void checkoutEntry(Repository repo, DirCacheEntry entry, + ObjectReader or, boolean deleteRecursive, + String smudgeFilterCommand) throws IOException { ObjectLoader ol = or.open(entry.getObjectId()); File f = new File(repo.getWorkTree(), entry.getPathString()); File parentDir = f.getParentFile(); @@ -1210,14 +1259,52 @@ public class DirCacheCheckout { OutputStream channel = new FileOutputStream(tmpFile); if (opt.getAutoCRLF() == AutoCRLF.TRUE) channel = new AutoCRLFOutputStream(channel); - try { - ol.copyTo(channel); - } finally { - channel.close(); + if (smudgeFilterCommand != null) { + ProcessBuilder filterProcessBuilder = fs + .runInShell(smudgeFilterCommand, new String[0]); + filterProcessBuilder.directory(repo.getWorkTree()); + filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, + repo.getDirectory().getAbsolutePath()); + ExecutionResult result; + int rc; + try { + // TODO: wire correctly with AUTOCRLF + result = fs.execute(filterProcessBuilder, ol.openStream()); + rc = result.getRc(); + if (rc == 0) { + result.getStdout().writeTo(channel, + NullProgressMonitor.INSTANCE); + } + } catch (IOException | InterruptedException e) { + throw new IOException(new FilterFailedException(e, + smudgeFilterCommand, entry.getPathString())); + + } finally { + channel.close(); + } + if (rc != 0) { + throw new IOException(new FilterFailedException(rc, + smudgeFilterCommand, entry.getPathString(), + result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), + RawParseUtils.decode(result.getStderr() + .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + } + } else { + try { + ol.copyTo(channel); + } finally { + channel.close(); + } + } + // The entry needs to correspond to the on-disk filesize. If the content + // was filtered (either by autocrlf handling or smudge filters) ask the + // filesystem again for the length. Otherwise the objectloader knows the + // size + if (opt.getAutoCRLF() == AutoCRLF.TRUE || smudgeFilterCommand != null) { + entry.setLength(tmpFile.length()); + } else { + entry.setLength(ol.getSize()); } - 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())) { @@ -1255,24 +1342,6 @@ public class DirCacheCheckout { checkValidPathSegment(chk, i); } - /** - * Check if path is a valid path for a checked out file name or ref name. - * - * @param path - * @throws InvalidPathException - * if the path is invalid - * @since 3.3 - */ - static void checkValidPath(String path) throws InvalidPathException { - try { - SystemReader.getInstance().checkPath(path); - } catch (CorruptObjectException e) { - InvalidPathException p = new InvalidPathException(path); - p.initCause(e); - throw p; - } - } - private static void checkValidPathSegment(ObjectChecker chk, CanonicalTreeParser t) throws InvalidPathException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java index eef2e6d3c3..c8bc0960f4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheEntry.java @@ -65,6 +65,7 @@ import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.MutableInteger; import org.eclipse.jgit.util.NB; +import org.eclipse.jgit.util.SystemReader; /** * A single file (or stage of a file) in a {@link DirCache}. @@ -191,7 +192,7 @@ public class DirCacheEntry { } try { - DirCacheCheckout.checkValidPath(toString(path)); + checkPath(path); } catch (InvalidPathException e) { CorruptObjectException p = new CorruptObjectException(e.getMessage()); @@ -263,7 +264,7 @@ public class DirCacheEntry { /** * Create an empty entry at the specified stage. * - * @param newPath + * @param path * name of the cache entry, in the standard encoding. * @param stage * the stage index of the new entry. @@ -274,16 +275,16 @@ public class DirCacheEntry { * range 0..3, inclusive. */ @SuppressWarnings("boxing") - public DirCacheEntry(final byte[] newPath, final int stage) { - DirCacheCheckout.checkValidPath(toString(newPath)); + public DirCacheEntry(byte[] path, final int stage) { + checkPath(path); if (stage < 0 || 3 < stage) throw new IllegalArgumentException(MessageFormat.format( JGitText.get().invalidStageForPath, - stage, toString(newPath))); + stage, toString(path))); info = new byte[INFO_LEN]; infoOffset = 0; - path = newPath; + this.path = path; int flags = ((stage & 0x3) << 12); if (path.length < NAME_MASK) @@ -498,12 +499,16 @@ public class DirCacheEntry { switch (mode.getBits() & FileMode.TYPE_MASK) { case FileMode.TYPE_MISSING: case FileMode.TYPE_TREE: - throw new IllegalArgumentException(MessageFormat.format(JGitText.get().invalidModeForPath - , mode, getPathString())); + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().invalidModeForPath, mode, getPathString())); } NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits()); } + void setFileMode(int mode) { + NB.encodeInt32(info, infoOffset + P_MODE, mode); + } + /** * Get the cached creation time of this file, in milliseconds. * @@ -730,6 +735,16 @@ public class DirCacheEntry { return 0; } + private static void checkPath(byte[] path) { + try { + SystemReader.getInstance().checkPath(path); + } catch (CorruptObjectException e) { + InvalidPathException p = new InvalidPathException(toString(path)); + p.initCause(e); + throw p; + } + } + private static String toString(final byte[] path) { return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java index 354a07439a..ad93f7213f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheIterator.java @@ -103,9 +103,6 @@ public class DirCacheIterator extends AbstractTreeIterator { /** The subtree containing {@link #currentEntry} if this is first entry. */ protected DirCacheTree currentSubtree; - /** Holds an {@link AttributesNode} for the current entry */ - private AttributesNode attributesNode; - /** * Create a new iterator for an already loaded DirCache instance. * <p> diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java new file mode 100644 index 0000000000..70f650dde6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> and + * other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v1.0 which accompanies this + * distribution, is reproduced below, and is available at + * http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * - Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the names of its + * contributors may be used to endorse or promote products derived from this + * software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.errors; + +import java.io.IOException; + +import org.eclipse.jgit.internal.JGitText; + +/** + * Cannot read the index. This is a serious error that users need to be made + * aware of. + * + * @since 4.2 + */ +public class IndexReadException extends IOException { + private static final long serialVersionUID = 1L; + + /** + * Constructs an IndexReadException with the default message. + */ + public IndexReadException() { + super(JGitText.get().indexWriteException); + } + + /** + * Constructs an IndexReadException with the specified detail message. + * + * @param s + * message + */ + public IndexReadException(final String s) { + super(s); + } + + /** + * Constructs an IndexReadException with the specified detail message. + * + * @param s + * message + * @param cause + * root cause exception + */ + public IndexReadException(final String s, final Throwable cause) { + super(s); + initCause(cause); + } +} 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 1d2d3bfaaf..ff9f233aa5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/gitrepo/RepoCommand.java @@ -42,6 +42,9 @@ */ package org.eclipse.jgit.gitrepo; +import static org.eclipse.jgit.lib.Constants.DEFAULT_REMOTE_NAME; +import static org.eclipse.jgit.lib.Constants.R_REMOTES; + import java.io.File; import java.io.FileInputStream; import java.io.IOException; @@ -574,7 +577,7 @@ public class RepoCommand extends GitCommand<RevCommit> { private static String findRef(String ref, Repository repo) throws IOException { if (!ObjectId.isId(ref)) { - Ref r = repo.getRef(Constants.DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ + Ref r = repo.exactRef(R_REMOTES + DEFAULT_REMOTE_NAME + "/" + ref); //$NON-NLS-1$ if (r != null) return r.getName(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java index 2e6582819f..a501fee901 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/hooks/PrePushHook.java @@ -113,6 +113,9 @@ public class PrePushHook extends GitHook<String> { */ @Override protected String[] getParameters() { + if (remoteName == null) { + remoteName = remoteLocation; + } return new String[] { remoteName, remoteLocation }; } 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 e39469bd8c..796eaaebf5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -79,6 +79,7 @@ public class JGitText extends TranslationBundle { /***/ public String atLeastOnePathIsRequired; /***/ public String atLeastOnePatternIsRequired; /***/ public String atLeastTwoFiltersNeeded; + /***/ public String atomicPushNotSupported; /***/ public String authenticationNotSupported; /***/ public String badBase64InputCharacterAt; /***/ public String badEntryDelimiter; @@ -104,6 +105,7 @@ public class JGitText extends TranslationBundle { /***/ public String cannotBeRecursiveWhenTreesAreIncluded; /***/ public String cannotChangeActionOnComment; /***/ public String cannotChangeToComment; + /***/ public String cannotCheckoutFromUnbornBranch; /***/ public String cannotCheckoutOursSwitchBranch; /***/ public String cannotCombineSquashWithNoff; /***/ public String cannotCombineTreeFilterWithRevFilter; @@ -146,6 +148,7 @@ public class JGitText extends TranslationBundle { /***/ public String cannotReadCommit; /***/ public String cannotReadFile; /***/ public String cannotReadHEAD; + /***/ public String cannotReadIndex; /***/ public String cannotReadObject; /***/ public String cannotReadObjectsPath; /***/ public String cannotReadTree; @@ -338,6 +341,8 @@ public class JGitText extends TranslationBundle { /***/ public String fileIsTooBigForThisConvenienceMethod; /***/ public String fileIsTooLarge; /***/ public String fileModeNotSetForPath; + /***/ public String filterExecutionFailed; + /***/ public String filterExecutionFailedRc; /***/ public String findingGarbage; /***/ public String flagIsDisposed; /***/ public String flagNotFromThis; @@ -410,6 +415,7 @@ public class JGitText extends TranslationBundle { /***/ public String invalidURL; /***/ public String invalidWildcards; /***/ public String invalidRefSpec; + /***/ public String invalidRepositoryStateNoHead; /***/ public String invalidWindowSize; /***/ public String isAStaticFlagAndHasNorevWalkInstance; /***/ public String JRELacksMD5Implementation; @@ -509,6 +515,7 @@ public class JGitText extends TranslationBundle { /***/ public String packfileIsTruncatedNoParam; /***/ public String packHandleIsStale; /***/ public String packHasUnresolvedDeltas; + /***/ public String packInaccessible; /***/ public String packingCancelledDuringObjectsWriting; /***/ public String packObjectCountMismatch; /***/ public String packRefs; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java index 122f6d3d19..0d5fd0f859 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsRepository.java @@ -44,8 +44,13 @@ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; +import java.io.InputStream; import java.text.MessageFormat; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesNodeProvider; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.RefUpdate; @@ -126,4 +131,36 @@ public abstract class DfsRepository extends Repository { public ReflogReader getReflogReader(String refName) throws IOException { throw new UnsupportedOperationException(); } + + @Override + public AttributesNodeProvider createAttributesNodeProvider() { + // TODO Check if the implementation used in FileRepository can be used + // for this kind of repository + return new EmptyAttributesNodeProvider(); + } + + private static class EmptyAttributesNodeProvider implements + AttributesNodeProvider { + private EmptyAttributesNode emptyAttributesNode = new EmptyAttributesNode(); + + public AttributesNode getInfoAttributesNode() throws IOException { + return emptyAttributesNode; + } + + public AttributesNode getGlobalAttributesNode() throws IOException { + return emptyAttributesNode; + } + + private static class EmptyAttributesNode extends AttributesNode { + + public EmptyAttributesNode() { + super(Collections.<AttributesRule> emptyList()); + } + + @Override + public void parse(InputStream in) throws IOException { + // Do nothing + } + } + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java index 832e4fb6a8..1c664b4097 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java @@ -13,14 +13,22 @@ import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.locks.ReadWriteLock; +import java.util.concurrent.locks.ReentrantReadWriteLock; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.PackExt; +import org.eclipse.jgit.lib.BatchRefUpdate; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectIdRef; +import org.eclipse.jgit.lib.ProgressMonitor; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.SymbolicRef; +import org.eclipse.jgit.revwalk.RevObject; +import org.eclipse.jgit.revwalk.RevTag; import org.eclipse.jgit.revwalk.RevWalk; +import org.eclipse.jgit.transport.ReceiveCommand; import org.eclipse.jgit.util.RefList; /** @@ -46,8 +54,8 @@ public class InMemoryRepository extends DfsRepository { static final AtomicInteger packId = new AtomicInteger(); private final DfsObjDatabase objdb; - private final DfsRefDatabase refdb; + private boolean performsAtomicTransactions = true; /** * Initialize a new in-memory repository. @@ -76,6 +84,17 @@ public class InMemoryRepository extends DfsRepository { return refdb; } + /** + * Enable (or disable) the atomic reference transaction support. + * <p> + * Useful for testing atomic support enabled or disabled. + * + * @param atomic + */ + public void setPerformsAtomicTransactions(boolean atomic) { + performsAtomicTransactions = atomic; + } + private class MemObjDatabase extends DfsObjDatabase { private List<DfsPackDescription> packs = new ArrayList<DfsPackDescription>(); @@ -235,41 +254,143 @@ public class InMemoryRepository extends DfsRepository { private class MemRefDatabase extends DfsRefDatabase { private final ConcurrentMap<String, Ref> refs = new ConcurrentHashMap<String, Ref>(); + private final ReadWriteLock lock = new ReentrantReadWriteLock(true /* fair */); MemRefDatabase() { super(InMemoryRepository.this); } @Override + public boolean performsAtomicTransactions() { + return performsAtomicTransactions; + } + + @Override + public BatchRefUpdate newBatchUpdate() { + return new BatchRefUpdate(this) { + @Override + public void execute(RevWalk walk, ProgressMonitor monitor) + throws IOException { + if (performsAtomicTransactions()) { + try { + lock.writeLock().lock(); + batch(walk, getCommands()); + } finally { + lock.writeLock().unlock(); + } + } else { + super.execute(walk, monitor); + } + } + }; + } + + @Override protected RefCache scanAllRefs() throws IOException { RefList.Builder<Ref> ids = new RefList.Builder<Ref>(); RefList.Builder<Ref> sym = new RefList.Builder<Ref>(); - for (Ref ref : refs.values()) { - if (ref.isSymbolic()) - sym.add(ref); - ids.add(ref); + try { + lock.readLock().lock(); + for (Ref ref : refs.values()) { + if (ref.isSymbolic()) + sym.add(ref); + ids.add(ref); + } + } finally { + lock.readLock().unlock(); } ids.sort(); sym.sort(); return new RefCache(ids.toRefList(), sym.toRefList()); } + private void batch(RevWalk walk, List<ReceiveCommand> cmds) { + // Validate that the target exists in a new RevWalk, as the RevWalk + // from the RefUpdate might be reading back unflushed objects. + Map<ObjectId, ObjectId> peeled = new HashMap<>(); + try (RevWalk rw = new RevWalk(getRepository())) { + for (ReceiveCommand c : cmds) { + if (!ObjectId.zeroId().equals(c.getNewId())) { + try { + RevObject o = rw.parseAny(c.getNewId()); + if (o instanceof RevTag) { + peeled.put(o, rw.peel(o).copy()); + } + } catch (IOException e) { + c.setResult(ReceiveCommand.Result.REJECTED_MISSING_OBJECT); + reject(cmds); + return; + } + } + } + } + + // Check all references conform to expected old value. + for (ReceiveCommand c : cmds) { + Ref r = refs.get(c.getRefName()); + if (r == null) { + if (c.getType() != ReceiveCommand.Type.CREATE) { + c.setResult(ReceiveCommand.Result.LOCK_FAILURE); + reject(cmds); + return; + } + } else if (r.isSymbolic() || r.getObjectId() == null + || !r.getObjectId().equals(c.getOldId())) { + c.setResult(ReceiveCommand.Result.LOCK_FAILURE); + reject(cmds); + return; + } + } + + // Write references. + for (ReceiveCommand c : cmds) { + if (c.getType() == ReceiveCommand.Type.DELETE) { + refs.remove(c.getRefName()); + c.setResult(ReceiveCommand.Result.OK); + continue; + } + + ObjectId p = peeled.get(c.getNewId()); + Ref r; + if (p != null) { + r = new ObjectIdRef.PeeledTag(Storage.PACKED, + c.getRefName(), c.getNewId(), p); + } else { + r = new ObjectIdRef.PeeledNonTag(Storage.PACKED, + c.getRefName(), c.getNewId()); + } + refs.put(r.getName(), r); + c.setResult(ReceiveCommand.Result.OK); + } + clearCache(); + } + + private void reject(List<ReceiveCommand> cmds) { + for (ReceiveCommand c : cmds) { + if (c.getResult() == ReceiveCommand.Result.NOT_ATTEMPTED) { + c.setResult(ReceiveCommand.Result.REJECTED_OTHER_REASON, + JGitText.get().transactionAborted); + } + } + } + @Override protected boolean compareAndPut(Ref oldRef, Ref newRef) throws IOException { - ObjectId id = newRef.getObjectId(); - if (id != null) { - try (RevWalk rw = new RevWalk(getRepository())) { - // Validate that the target exists in a new RevWalk, as the RevWalk - // from the RefUpdate might be reading back unflushed objects. - rw.parseAny(id); + try { + lock.writeLock().lock(); + ObjectId id = newRef.getObjectId(); + if (id != null) { + try (RevWalk rw = new RevWalk(getRepository())) { + // Validate that the target exists in a new RevWalk, as the RevWalk + // from the RefUpdate might be reading back unflushed objects. + rw.parseAny(id); + } } - } - String name = newRef.getName(); - if (oldRef == null) - return refs.putIfAbsent(name, newRef) == null; + String name = newRef.getName(); + if (oldRef == null) + return refs.putIfAbsent(name, newRef) == null; - synchronized (refs) { Ref cur = refs.get(name); Ref toCompare = cur; if (toCompare != null) { @@ -294,22 +415,29 @@ public class InMemoryRepository extends DfsRepository { if (eq(toCompare, oldRef)) return refs.replace(name, cur, newRef); } - } - if (oldRef.getStorage() == Storage.NEW) - return refs.putIfAbsent(name, newRef) == null; + if (oldRef.getStorage() == Storage.NEW) + return refs.putIfAbsent(name, newRef) == null; - return false; + return false; + } finally { + lock.writeLock().unlock(); + } } @Override protected boolean compareAndRemove(Ref oldRef) throws IOException { - String name = oldRef.getName(); - Ref cur = refs.get(name); - if (cur != null && eq(cur, oldRef)) - return refs.remove(name, cur); - else - return false; + try { + lock.writeLock().lock(); + String name = oldRef.getName(); + Ref cur = refs.get(name); + if (cur != null && eq(cur, oldRef)) + return refs.remove(name, cur); + else + return false; + } finally { + lock.writeLock().unlock(); + } } private boolean eq(Ref a, Ref b) { 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 995621ee3e..490cbcaa81 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 @@ -49,11 +49,15 @@ package org.eclipse.jgit.internal.storage.file; import static org.eclipse.jgit.lib.RefDatabase.ALL; import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.text.MessageFormat; import java.util.HashSet; import java.util.Set; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.errors.ConfigInvalidException; import org.eclipse.jgit.events.ConfigChangedEvent; import org.eclipse.jgit.events.ConfigChangedListener; @@ -479,4 +483,63 @@ public class FileRepository extends Repository { return new ReflogReaderImpl(this, ref.getName()); return null; } + + @Override + public AttributesNodeProvider createAttributesNodeProvider() { + return new AttributesNodeProviderImpl(this); + } + + /** + * Implementation a {@link AttributesNodeProvider} for a + * {@link FileRepository}. + * + * @author <a href="mailto:arthur.daussy@obeo.fr">Arthur Daussy</a> + * + */ + static class AttributesNodeProviderImpl implements + AttributesNodeProvider { + + private AttributesNode infoAttributesNode; + + private AttributesNode globalAttributesNode; + + /** + * Constructor. + * + * @param repo + * {@link Repository} that will provide the attribute nodes. + */ + protected AttributesNodeProviderImpl(Repository repo) { + infoAttributesNode = new InfoAttributesNode(repo); + globalAttributesNode = new GlobalAttributesNode(repo); + } + + public AttributesNode getInfoAttributesNode() throws IOException { + if (infoAttributesNode instanceof InfoAttributesNode) + infoAttributesNode = ((InfoAttributesNode) infoAttributesNode) + .load(); + return infoAttributesNode; + } + + public AttributesNode getGlobalAttributesNode() throws IOException { + if (globalAttributesNode instanceof GlobalAttributesNode) + globalAttributesNode = ((GlobalAttributesNode) globalAttributesNode) + .load(); + return globalAttributesNode; + } + + static void loadRulesFromFile(AttributesNode r, File attrs) + throws FileNotFoundException, IOException { + if (attrs.exists()) { + FileInputStream in = new FileInputStream(attrs); + try { + r.parse(in); + } finally { + in.close(); + } + } + } + + } + } 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 e7005c247e..4c40538b6a 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 @@ -90,6 +90,7 @@ import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.Ref.Storage; import org.eclipse.jgit.lib.RefDatabase; import org.eclipse.jgit.lib.ReflogEntry; +import org.eclipse.jgit.lib.ReflogReader; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.revwalk.RevWalk; @@ -592,7 +593,11 @@ public class GC { * @throws IOException */ private Set<ObjectId> listRefLogObjects(Ref ref, long minTime) throws IOException { - List<ReflogEntry> rlEntries = repo.getReflogReader(ref.getName()) + ReflogReader reflogReader = repo.getReflogReader(ref.getName()); + if (reflogReader == null) { + return Collections.emptySet(); + } + List<ReflogEntry> rlEntries = reflogReader .getReverseEntries(); if (rlEntries == null || rlEntries.isEmpty()) return Collections.<ObjectId> emptySet(); @@ -635,10 +640,7 @@ public class GC { */ private Set<ObjectId> listNonHEADIndexObjects() throws CorruptObjectException, IOException { - try { - if (repo.getIndexFile() == null) - return Collections.emptySet(); - } catch (NoWorkTreeException e) { + if (repo.isBare()) { return Collections.emptySet(); } try (TreeWalk treeWalk = new TreeWalk(repo)) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java new file mode 100644 index 0000000000..454d3bff69 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; + +/** Attribute node loaded from global system-wide file. */ +public class GlobalAttributesNode extends AttributesNode { + final Repository repository; + + /** + * @param repository + */ + public GlobalAttributesNode(Repository repository) { + this.repository = repository; + } + + /** + * @return the attributes node + * @throws IOException + */ + public AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + String path = repository.getConfig().get(CoreConfig.KEY) + .getAttributesFile(); + if (path != null) { + File attributesFile; + if (path.startsWith("~/")) { //$NON-NLS-1$ + attributesFile = fs.resolve(fs.userHome(), + path.substring(2)); + } else { + attributesFile = fs.resolve(null, path); + } + FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributesFile); + } + return r.getRules().isEmpty() ? null : r; + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java new file mode 100644 index 0000000000..bda5cbeba4 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy <arthur.daussy@obeo.fr> + * Copyright (C) 2015, Christian Halstrick <christian.halstrick@sap.com> + * and other copyright owners as documented in the project's IP log. + * + * This program and the accompanying materials are made available + * under the terms of the Eclipse Distribution License v1.0 which + * accompanies this distribution, is reproduced below, and is + * available at http://www.eclipse.org/org/documents/edl-v10.php + * + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or + * without modification, are permitted provided that the following + * conditions are met: + * + * - Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * - Redistributions in binary form must reproduce the above + * copyright notice, this list of conditions and the following + * disclaimer in the documentation and/or other materials provided + * with the distribution. + * + * - Neither the name of the Eclipse Foundation, Inc. nor the + * names of its contributors may be used to endorse or promote + * products derived from this software without specific prior + * written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND + * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, + * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF + * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.eclipse.jgit.internal.storage.file; + +import java.io.File; +import java.io.IOException; + +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.util.FS; + +/** Attribute node loaded from the $GIT_DIR/info/attributes file. */ +public class InfoAttributesNode extends AttributesNode { + final Repository repository; + + /** + * @param repository + */ + public InfoAttributesNode(Repository repository) { + this.repository = repository; + } + + /** + * @return the attributes node + * @throws IOException + */ + public AttributesNode load() throws IOException { + AttributesNode r = new AttributesNode(); + + FS fs = repository.getFS(); + + File attributes = fs.resolve(repository.getDirectory(), + Constants.INFO_ATTRIBUTES); + FileRepository.AttributesNodeProviderImpl.loadRulesFromFile(r, attributes); + + return r.getRules().isEmpty() ? null : r; + } + +}
\ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java index 50297a97a0..e23ca741b8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LockFile.java @@ -227,6 +227,10 @@ public class LockFile { fis.close(); } } catch (FileNotFoundException fnfe) { + if (ref.exists()) { + unlock(); + throw fnfe; + } // Don't worry about a file that doesn't exist yet, it // conceptually has no current content to copy. // 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 e7ef127dd7..bd1d488d94 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 @@ -433,16 +433,14 @@ public class ObjectDirectory extends FileObjectDatabase { ObjectLoader openLooseObject(WindowCursor curs, AnyObjectId id) throws IOException { - try { - File path = fileFor(id); - FileInputStream in = new FileInputStream(path); - try { - unpackedObjectCache.add(id); - return UnpackedObject.open(in, path, id, curs); - } finally { - in.close(); - } + File path = fileFor(id); + try (FileInputStream in = new FileInputStream(path)) { + unpackedObjectCache.add(id); + return UnpackedObject.open(in, path, id, curs); } catch (FileNotFoundException noFile) { + if (path.exists()) { + throw noFile; + } unpackedObjectCache.remove(id); return null; } @@ -513,15 +511,14 @@ public class ObjectDirectory extends FileObjectDatabase { private long getLooseObjectSize(WindowCursor curs, AnyObjectId id) throws IOException { - try { - FileInputStream in = new FileInputStream(fileFor(id)); - try { - unpackedObjectCache.add(id); - return UnpackedObject.getSize(in, id, curs); - } finally { - in.close(); - } + File f = fileFor(id); + try (FileInputStream in = new FileInputStream(f)) { + unpackedObjectCache.add(id); + return UnpackedObject.getSize(in, id, curs); } catch (FileNotFoundException noFile) { + if (f.exists()) { + throw noFile; + } unpackedObjectCache.remove(id); return -1; } @@ -561,7 +558,11 @@ public class ObjectDirectory extends FileObjectDatabase { // Assume the pack is corrupted, and remove it from the list. removePack(p); } else if (e instanceof FileNotFoundException) { - warnTmpl = JGitText.get().packWasDeleted; + if (p.getPackFile().exists()) { + warnTmpl = JGitText.get().packInaccessible; + } else { + warnTmpl = JGitText.get().packWasDeleted; + } removePack(p); } else if (FileUtils.isStaleFileHandle(e)) { warnTmpl = JGitText.get().packHandleIsStale; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java index 731bbd3839..69f7e97071 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/RefDirectory.java @@ -262,6 +262,30 @@ public class RefDirectory extends RefDatabase { } @Override + public Ref exactRef(String name) throws IOException { + RefList<Ref> packed = getPackedRefs(); + Ref ref; + try { + ref = readRef(name, packed); + if (ref != null) { + ref = resolve(ref, 0, null, null, packed); + } + } catch (IOException e) { + if (name.contains("/") //$NON-NLS-1$ + || !(e.getCause() instanceof InvalidObjectIdException)) { + throw e; + } + + // While looking for a ref outside of refs/ (e.g., 'config'), we + // found a non-ref file (e.g., a config file) instead. Treat this + // as a ref-not-found condition. + ref = null; + } + fireRefsChanged(); + return ref; + } + + @Override public Ref getRef(final String needle) throws IOException { final RefList<Ref> packed = getPackedRefs(); Ref ref = null; @@ -270,6 +294,8 @@ public class RefDirectory extends RefDatabase { ref = readRef(prefix + needle, packed); if (ref != null) { ref = resolve(ref, 0, null, null, packed); + } + if (ref != null) { break; } } catch (IOException e) { @@ -762,6 +788,9 @@ public class RefDirectory extends RefDatabase { new DigestInputStream(new FileInputStream(packedRefsFile), digest), CHARSET)); } catch (FileNotFoundException noPackedRefs) { + if (packedRefsFile.exists()) { + throw noPackedRefs; + } // Ignore it and leave the new list empty. return PackedRefList.NO_PACKED_REFS; } @@ -918,7 +947,10 @@ public class RefDirectory extends RefDatabase { try { buf = IO.readSome(path, limit); } catch (FileNotFoundException noFile) { - return null; // doesn't exist; not a reference. + if (path.exists() && path.isFile()) { + throw noFile; + } + return null; // doesn't exist or no file; not a reference. } int n = buf.length; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java index dadc631194..2f583b275a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ReflogReaderImpl.java @@ -96,6 +96,9 @@ class ReflogReaderImpl implements ReflogReader { try { log = IO.readFully(logName); } catch (FileNotFoundException e) { + if (logName.exists()) { + throw e; + } return null; } @@ -118,6 +121,9 @@ class ReflogReaderImpl implements ReflogReader { try { log = IO.readFully(logName); } catch (FileNotFoundException e) { + if (logName.exists()) { + throw e; + } return Collections.emptyList(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java index 2cc2563962..a02743720d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/UnpackedObject.java @@ -399,6 +399,9 @@ public class UnpackedObject { try { in = buffer(new FileInputStream(path)); } catch (FileNotFoundException gone) { + if (path.exists()) { + throw gone; + } // If the loose file no longer exists, it may have been // moved into a pack file in the mean time. Try again // to locate the object. 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 535a6ee175..d30edaf41b 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -273,6 +273,13 @@ public final class Constants { public static final String INFO_EXCLUDE = "info/exclude"; /** + * Attributes-override-file + * + * @since 4.2 + */ + public static final String INFO_ATTRIBUTES = "info/attributes"; + + /** * The system property that contains the system user name * * @since 3.6 @@ -363,6 +370,27 @@ public final class Constants { */ public static final String DOT_GIT_ATTRIBUTES = ".gitattributes"; + /** + * Key for filters in .gitattributes + * + * @since 4.2 + */ + public static final String ATTR_FILTER = "filter"; + + /** + * clean command name, used to call filter driver + * + * @since 4.2 + */ + public static final String ATTR_FILTER_TYPE_CLEAN = "clean"; + + /** + * smudge command name, used to call filter driver + * + * @since 4.2 + */ + public static final String ATTR_FILTER_TYPE_SMUDGE = "smudge"; + /** Name of the ignore file */ public static final String DOT_GIT_IGNORE = ".gitignore"; 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 3fde2f919b..9e474f86a8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/IndexDiff.java @@ -74,6 +74,7 @@ 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.TreeWalk.OperationType; import org.eclipse.jgit.treewalk.filter.AndTreeFilter; import org.eclipse.jgit.treewalk.filter.IndexDiffFilter; import org.eclipse.jgit.treewalk.filter.SkipWorkTreeFilter; @@ -403,6 +404,7 @@ public class IndexDiff { dirCache = repository.readDirCache(); try (TreeWalk treeWalk = new TreeWalk(repository)) { + treeWalk.setOperationType(OperationType.CHECKIN_OP); treeWalk.setRecursive(true); // add the trees (tree, dirchache, workdir) if (tree != null) @@ -411,6 +413,7 @@ public class IndexDiff { treeWalk.addTree(new EmptyTreeIterator()); treeWalk.addTree(new DirCacheIterator(dirCache)); treeWalk.addTree(initialWorkingTreeIterator); + initialWorkingTreeIterator.setDirCacheIterator(treeWalk, 1); Collection<TreeFilter> filters = new ArrayList<TreeFilter>(4); if (monitor != null) { 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 ef22fb90fe..986666f2f4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefDatabase.java @@ -51,6 +51,9 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; + /** * Abstraction of name to {@link ObjectId} mapping. * <p> @@ -132,6 +135,7 @@ public abstract class RefDatabase { * @since 2.3 * @see #isNameConflicting(String) */ + @NonNull public Collection<String> getConflictingNames(String name) throws IOException { Map<String, Ref> allRefs = getRefs(ALL); @@ -169,6 +173,7 @@ public abstract class RefDatabase { * @throws IOException * the reference space cannot be accessed. */ + @NonNull public abstract RefUpdate newUpdate(String name, boolean detach) throws IOException; @@ -183,6 +188,7 @@ public abstract class RefDatabase { * @throws IOException * the reference space cannot be accessed. */ + @NonNull public abstract RefRename newRename(String fromName, String toName) throws IOException; @@ -193,6 +199,7 @@ public abstract class RefDatabase { * * @return a new batch update object. */ + @NonNull public BatchRefUpdate newBatchUpdate() { return new BatchRefUpdate(this); } @@ -223,6 +230,7 @@ public abstract class RefDatabase { * @throws IOException * the reference space cannot be accessed. */ + @Nullable public abstract Ref getRef(String name) throws IOException; /** @@ -238,21 +246,13 @@ public abstract class RefDatabase { * the reference space cannot be accessed. * @since 4.1 */ + @Nullable public Ref exactRef(String name) throws IOException { - int slash = name.lastIndexOf('/'); - String prefix = name.substring(0, slash + 1); - String rest = name.substring(slash + 1); - Ref result = getRefs(prefix).get(rest); - if (result != null || slash != -1) { - return result; - } - - for (Ref ref : getAdditionalRefs()) { - if (name.equals(ref.getName())) { - return ref; - } + Ref ref = getRef(name); + if (ref == null || !name.equals(ref.getName())) { + return null; } - return null; + return ref; } /** @@ -270,6 +270,7 @@ public abstract class RefDatabase { * the reference space cannot be accessed. * @since 4.1 */ + @NonNull public Map<String, Ref> exactRef(String... refs) throws IOException { Map<String, Ref> result = new HashMap<>(refs.length); for (String name : refs) { @@ -294,6 +295,7 @@ public abstract class RefDatabase { * the reference space cannot be accessed. * @since 4.1 */ + @Nullable public Ref firstExactRef(String... refs) throws IOException { for (String name : refs) { Ref ref = exactRef(name); @@ -317,6 +319,7 @@ public abstract class RefDatabase { * @throws IOException * the reference space cannot be accessed. */ + @NonNull public abstract Map<String, Ref> getRefs(String prefix) throws IOException; /** @@ -331,6 +334,7 @@ public abstract class RefDatabase { * @throws IOException * the reference space cannot be accessed. */ + @NonNull public abstract List<Ref> getAdditionalRefs() throws IOException; /** @@ -347,10 +351,11 @@ public abstract class RefDatabase { * @return {@code ref} if {@code ref.isPeeled()} is true; otherwise a new * Ref object representing the same data as Ref, but isPeeled() will * be true and getPeeledObjectId() will contain the peeled object - * (or null). + * (or {@code null}). * @throws IOException * the reference space or object space cannot be accessed. */ + @NonNull public abstract Ref peel(Ref ref) throws IOException; /** @@ -374,9 +379,10 @@ public abstract class RefDatabase { * @param name * short name of ref to find, e.g. "master" to find * "refs/heads/master" in map. - * @return The first ref matching the name, or null if not found. + * @return The first ref matching the name, or {@code null} if not found. * @since 3.4 */ + @Nullable public static Ref findRef(Map<String, Ref> map, String name) { for (String prefix : SEARCH_PATH) { String fullname = prefix + name; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java index 05eb31183d..59f852b8c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/RefRename.java @@ -170,7 +170,7 @@ public abstract class RefRename { */ protected boolean needToUpdateHEAD() throws IOException { Ref head = source.getRefDatabase().getRef(Constants.HEAD); - if (head.isSymbolic()) { + if (head != null && head.isSymbolic()) { head = head.getTarget(); return head.getName().equals(source.getName()); } 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 d4c72cb9cb..49a970d03a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Repository.java @@ -64,6 +64,9 @@ import java.util.Map; import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; +import org.eclipse.jgit.annotations.NonNull; +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.attributes.AttributesNodeProvider; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.errors.AmbiguousObjectException; import org.eclipse.jgit.errors.CorruptObjectException; @@ -137,6 +140,7 @@ public abstract class Repository implements AutoCloseable { } /** @return listeners observing only events on this repository. */ + @NonNull public ListenerList getListenerList() { return myListeners; } @@ -181,7 +185,16 @@ public abstract class Repository implements AutoCloseable { */ public abstract void create(boolean bare) throws IOException; - /** @return local metadata directory; null if repository isn't local. */ + /** + * @return local metadata directory; {@code null} if repository isn't local. + */ + /* + * TODO This method should be annotated as Nullable, because in some + * specific configurations metadata is not located in the local file system + * (for example in memory databases). In "usual" repositories this + * annotation would only cause compiler errors at places where the actual + * directory can never be null. + */ public File getDirectory() { return gitDir; } @@ -189,28 +202,52 @@ public abstract class Repository implements AutoCloseable { /** * @return the object database which stores this repository's data. */ + @NonNull public abstract ObjectDatabase getObjectDatabase(); /** @return a new inserter to create objects in {@link #getObjectDatabase()} */ + @NonNull public ObjectInserter newObjectInserter() { return getObjectDatabase().newInserter(); } /** @return a new reader to read objects from {@link #getObjectDatabase()} */ + @NonNull public ObjectReader newObjectReader() { return getObjectDatabase().newReader(); } /** @return the reference database which stores the reference namespace. */ + @NonNull public abstract RefDatabase getRefDatabase(); /** * @return the configuration of this repository */ + @NonNull public abstract StoredConfig getConfig(); /** - * @return the used file system abstraction + * @return a new {@link AttributesNodeProvider}. This + * {@link AttributesNodeProvider} is lazy loaded only once. It means + * that it will not be updated after loading. Prefer creating new + * instance for each use. + * @since 4.2 + */ + @NonNull + public abstract AttributesNodeProvider createAttributesNodeProvider(); + + + /** + * @return the used file system abstraction, or or {@code null} if + * repository isn't local. + */ + /* + * TODO This method should be annotated as Nullable, because in some + * specific configurations metadata is not located in the local file system + * (for example in memory databases). In "usual" repositories this + * annotation would only cause compiler errors at places where the actual + * directory can never be null. */ public FS getFS() { return fs; @@ -244,6 +281,7 @@ public abstract class Repository implements AutoCloseable { * @throws IOException * the object store cannot be accessed. */ + @NonNull public ObjectLoader open(final AnyObjectId objectId) throws MissingObjectException, IOException { return getObjectDatabase().open(objectId); @@ -271,6 +309,7 @@ public abstract class Repository implements AutoCloseable { * @throws IOException * the object store cannot be accessed. */ + @NonNull public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { @@ -289,6 +328,7 @@ public abstract class Repository implements AutoCloseable { * a symbolic ref was passed in and could not be resolved back * to the base ref, as the symbolic ref could not be read. */ + @NonNull public RefUpdate updateRef(final String ref) throws IOException { return updateRef(ref, false); } @@ -307,6 +347,7 @@ public abstract class Repository implements AutoCloseable { * a symbolic ref was passed in and could not be resolved back * to the base ref, as the symbolic ref could not be read. */ + @NonNull public RefUpdate updateRef(final String ref, final boolean detach) throws IOException { return getRefDatabase().newUpdate(ref, detach); } @@ -323,6 +364,7 @@ public abstract class Repository implements AutoCloseable { * the rename could not be performed. * */ + @NonNull public RefRename renameRef(final String fromRef, final String toRef) throws IOException { return getRefDatabase().newRename(fromRef, toRef); } @@ -362,7 +404,8 @@ public abstract class Repository implements AutoCloseable { * * @param revstr * A git object references expression - * @return an ObjectId or null if revstr can't be resolved to any ObjectId + * @return an ObjectId or {@code null} if revstr can't be resolved to any + * ObjectId * @throws AmbiguousObjectException * {@code revstr} contains an abbreviated ObjectId and this * repository contains more than one object which match to the @@ -376,6 +419,7 @@ public abstract class Repository implements AutoCloseable { * @throws IOException * on serious errors */ + @Nullable public ObjectId resolve(final String revstr) throws AmbiguousObjectException, IncorrectObjectTypeException, RevisionSyntaxException, IOException { @@ -397,10 +441,12 @@ public abstract class Repository implements AutoCloseable { * expects a branch or revision id. * * @param revstr - * @return object id or ref name from resolved expression + * @return object id or ref name from resolved expression or {@code null} if + * given expression cannot be resolved * @throws AmbiguousObjectException * @throws IOException */ + @Nullable public String simplify(final String revstr) throws AmbiguousObjectException, IOException { try (RevWalk rw = new RevWalk(this)) { @@ -414,6 +460,7 @@ public abstract class Repository implements AutoCloseable { } } + @Nullable private Object resolve(final RevWalk rw, final String revstr) throws IOException { char[] revChars = revstr.toCharArray(); @@ -717,11 +764,13 @@ public abstract class Repository implements AutoCloseable { return true; } + @Nullable private RevObject parseSimple(RevWalk rw, String revstr) throws IOException { ObjectId id = resolveSimple(revstr); return id != null ? rw.parseAny(id) : null; } + @Nullable private ObjectId resolveSimple(final String revstr) throws IOException { if (ObjectId.isId(revstr)) return ObjectId.fromString(revstr); @@ -749,6 +798,7 @@ public abstract class Repository implements AutoCloseable { return null; } + @Nullable private String resolveReflogCheckout(int checkoutNo) throws IOException { ReflogReader reader = getReflogReader(Constants.HEAD); @@ -790,6 +840,7 @@ public abstract class Repository implements AutoCloseable { return rw.parseCommit(entry.getNewId()); } + @Nullable private ObjectId resolveAbbreviation(final String revstr) throws IOException, AmbiguousObjectException { AbbreviatedObjectId id = AbbreviatedObjectId.fromString(revstr); @@ -826,11 +877,13 @@ public abstract class Repository implements AutoCloseable { getRefDatabase().close(); } + @NonNull @SuppressWarnings("nls") public String toString() { String desc; - if (getDirectory() != null) - desc = getDirectory().getPath(); + File directory = getDirectory(); + if (directory != null) + desc = directory.getPath(); else desc = getClass().getSimpleName() + "-" //$NON-NLS-1$ + System.identityHashCode(this); @@ -850,10 +903,12 @@ public abstract class Repository implements AutoCloseable { * current ObjectId in hexadecimal string format. * * @return name of current branch (for example {@code refs/heads/master}), - * an ObjectId in hex format if the current branch is detached, - * or null if the repository is corrupt and has no HEAD reference. + * an ObjectId in hex format if the current branch is detached, or + * {@code null} if the repository is corrupt and has no HEAD + * reference. * @throws IOException */ + @Nullable public String getFullBranch() throws IOException { Ref head = getRef(Constants.HEAD); if (head == null) @@ -872,16 +927,17 @@ public abstract class Repository implements AutoCloseable { * leading prefix {@code refs/heads/} is removed from the reference before * it is returned to the caller. * - * @return name of current branch (for example {@code master}), an - * ObjectId in hex format if the current branch is detached, - * or null if the repository is corrupt and has no HEAD reference. + * @return name of current branch (for example {@code master}), an ObjectId + * in hex format if the current branch is detached, or {@code null} + * if the repository is corrupt and has no HEAD reference. * @throws IOException */ + @Nullable public String getBranch() throws IOException { String name = getFullBranch(); if (name != null) return shortenRefName(name); - return name; + return null; } /** @@ -894,6 +950,7 @@ public abstract class Repository implements AutoCloseable { * * @return unmodifiable collection of other known objects. */ + @NonNull public Set<ObjectId> getAdditionalHaves() { return Collections.emptySet(); } @@ -905,16 +962,53 @@ public abstract class Repository implements AutoCloseable { * the name of the ref to lookup. May be a short-hand form, e.g. * "master" which is is automatically expanded to * "refs/heads/master" if "refs/heads/master" already exists. - * @return the Ref with the given name, or null if it does not exist + * @return the Ref with the given name, or {@code null} if it does not exist * @throws IOException + * @deprecated Use {@link #exactRef(String)} or {@link #findRef(String)} + * instead. */ + @Deprecated + @Nullable public Ref getRef(final String name) throws IOException { + return findRef(name); + } + + /** + * Get a ref by name. + * + * @param name + * the name of the ref to lookup. Must not be a short-hand + * form; e.g., "master" is not automatically expanded to + * "refs/heads/master". + * @return the Ref with the given name, or {@code null} if it does not exist + * @throws IOException + * @since 4.2 + */ + @Nullable + public Ref exactRef(String name) throws IOException { + return getRefDatabase().exactRef(name); + } + + /** + * Search for a ref by (possibly abbreviated) name. + * + * @param name + * the name of the ref to lookup. May be a short-hand form, e.g. + * "master" which is is automatically expanded to + * "refs/heads/master" if "refs/heads/master" already exists. + * @return the Ref with the given name, or {@code null} if it does not exist + * @throws IOException + * @since 4.2 + */ + @Nullable + public Ref findRef(String name) throws IOException { return getRefDatabase().getRef(name); } /** * @return mutable map of all known refs (heads, tags, remotes). */ + @NonNull public Map<String, Ref> getAllRefs() { try { return getRefDatabase().getRefs(RefDatabase.ALL); @@ -928,6 +1022,7 @@ public abstract class Repository implements AutoCloseable { * of the entry contains the ref with the full tag name * ("refs/tags/v1.0"). */ + @NonNull public Map<String, Ref> getTags() { try { return getRefDatabase().getRefs(Constants.R_TAGS); @@ -949,6 +1044,7 @@ public abstract class Repository implements AutoCloseable { * will be true and getPeeledObjectId will contain the peeled object * (or null). */ + @NonNull public Ref peel(final Ref ref) { try { return getRefDatabase().peel(ref); @@ -963,6 +1059,7 @@ public abstract class Repository implements AutoCloseable { /** * @return a map with all objects referenced by a peeled ref. */ + @NonNull public Map<AnyObjectId, Set<Ref>> getAllRefsByPeeledObjectId() { Map<String, Ref> allRefs = getAllRefs(); Map<AnyObjectId, Set<Ref>> ret = new HashMap<AnyObjectId, Set<Ref>>(allRefs.size()); @@ -987,11 +1084,13 @@ public abstract class Repository implements AutoCloseable { } /** - * @return the index file location + * @return the index file location or {@code null} if repository isn't + * local. * @throws NoWorkTreeException * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @NonNull public File getIndexFile() throws NoWorkTreeException { if (isBare()) throw new NoWorkTreeException(); @@ -1016,6 +1115,7 @@ public abstract class Repository implements AutoCloseable { * the index file is using a format or extension that this * library does not support. */ + @NonNull public DirCache readDirCache() throws NoWorkTreeException, CorruptObjectException, IOException { return DirCache.read(this); @@ -1040,6 +1140,7 @@ public abstract class Repository implements AutoCloseable { * the index file is using a format or extension that this * library does not support. */ + @NonNull public DirCache lockDirCache() throws NoWorkTreeException, CorruptObjectException, IOException { // we want DirCache to inform us so that we can inform registered @@ -1065,6 +1166,7 @@ public abstract class Repository implements AutoCloseable { /** * @return an important state */ + @NonNull public RepositoryState getRepositoryState() { if (isBare() || getDirectory() == null) return RepositoryState.BARE; @@ -1207,6 +1309,7 @@ public abstract class Repository implements AutoCloseable { * @return normalized repository relative path or the empty * string if the file is not relative to the work directory. */ + @NonNull public static String stripWorkDir(File workDir, File file) { final String filePath = file.getPath(); final String workDirPath = workDir.getPath(); @@ -1241,6 +1344,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @NonNull public File getWorkTree() throws NoWorkTreeException { if (isBare()) throw new NoWorkTreeException(); @@ -1264,6 +1368,7 @@ public abstract class Repository implements AutoCloseable { * * @return a more user friendly ref name */ + @NonNull public static String shortenRefName(String refName) { if (refName.startsWith(Constants.R_HEADS)) return refName.substring(Constants.R_HEADS.length()); @@ -1279,9 +1384,10 @@ public abstract class Repository implements AutoCloseable { * @return the remote branch name part of <code>refName</code>, i.e. without * the <code>refs/remotes/<remote></code> prefix, if * <code>refName</code> represents a remote tracking branch; - * otherwise null. + * otherwise {@code null}. * @since 3.4 */ + @Nullable public String shortenRemoteBranchName(String refName) { for (String remote : getRemoteNames()) { String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$ @@ -1296,9 +1402,10 @@ public abstract class Repository implements AutoCloseable { * @return the remote name part of <code>refName</code>, i.e. without the * <code>refs/remotes/<remote></code> prefix, if * <code>refName</code> represents a remote tracking branch; - * otherwise null. + * otherwise {@code null}. * @since 3.4 */ + @Nullable public String getRemoteName(String refName) { for (String remote : getRemoteNames()) { String remotePrefix = Constants.R_REMOTES + remote + "/"; //$NON-NLS-1$ @@ -1310,12 +1417,13 @@ public abstract class Repository implements AutoCloseable { /** * @param refName - * @return a {@link ReflogReader} for the supplied refname, or null if the - * named ref does not exist. + * @return a {@link ReflogReader} for the supplied refname, or {@code null} + * if the named ref does not exist. * @throws IOException * the ref could not be accessed. * @since 3.0 */ + @Nullable public abstract ReflogReader getReflogReader(String refName) throws IOException; @@ -1331,6 +1439,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public String readMergeCommitMsg() throws IOException, NoWorkTreeException { return readCommitMsgFile(Constants.MERGE_MSG); } @@ -1365,6 +1474,7 @@ public abstract class Repository implements AutoCloseable { * See {@link #isBare()}. * @since 4.0 */ + @Nullable public String readCommitEditMsg() throws IOException, NoWorkTreeException { return readCommitMsgFile(Constants.COMMIT_EDITMSG); } @@ -1399,6 +1509,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public List<ObjectId> readMergeHeads() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1442,6 +1553,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readCherryPickHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) @@ -1465,6 +1577,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readRevertHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1530,6 +1643,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public ObjectId readOrigHead() throws IOException, NoWorkTreeException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1550,6 +1664,7 @@ public abstract class Repository implements AutoCloseable { * if this is bare, which implies it has no working directory. * See {@link #isBare()}. */ + @Nullable public String readSquashCommitMsg() throws IOException { return readCommitMsgFile(Constants.SQUASH_MSG); } @@ -1571,6 +1686,7 @@ public abstract class Repository implements AutoCloseable { writeCommitMsg(squashMsgFile, msg); } + @Nullable private String readCommitMsgFile(String msgFilename) throws IOException { if (isBare() || getDirectory() == null) throw new NoWorkTreeException(); @@ -1579,6 +1695,9 @@ public abstract class Repository implements AutoCloseable { try { return RawParseUtils.decode(IO.readFully(mergeMsgFile)); } catch (FileNotFoundException e) { + if (mergeMsgFile.exists()) { + throw e; + } // the file has disappeared in the meantime ignore it return null; } @@ -1601,15 +1720,20 @@ public abstract class Repository implements AutoCloseable { * Read a file from the git directory. * * @param filename - * @return the raw contents or null if the file doesn't exist or is empty + * @return the raw contents or {@code null} if the file doesn't exist or is + * empty * @throws IOException */ + @Nullable private byte[] readGitDirectoryFile(String filename) throws IOException { File file = new File(getDirectory(), filename); try { byte[] raw = IO.readFully(file); return raw.length > 0 ? raw : null; } catch (FileNotFoundException notFound) { + if (file.exists()) { + throw notFound; + } return null; } } @@ -1657,6 +1781,7 @@ public abstract class Repository implements AutoCloseable { * @throws IOException * @since 3.2 */ + @NonNull public List<RebaseTodoLine> readRebaseTodo(String path, boolean includeComments) throws IOException { @@ -1686,6 +1811,7 @@ public abstract class Repository implements AutoCloseable { * @return the names of all known remotes * @since 3.4 */ + @NonNull public Set<String> getRemoteNames() { return getConfig() .getSubsections(ConfigConstants.CONFIG_REMOTE_SECTION); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java index aef47c58cf..e0556447ce 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java @@ -57,8 +57,6 @@ import java.util.List; import java.util.TimeZone; import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheBuilder; -import org.eclipse.jgit.dircache.DirCacheEntry; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.NoMergeBaseException; import org.eclipse.jgit.internal.JGitText; @@ -70,7 +68,6 @@ import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.revwalk.filter.RevFilter; import org.eclipse.jgit.treewalk.AbstractTreeIterator; import org.eclipse.jgit.treewalk.EmptyTreeIterator; -import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.treewalk.WorkingTreeIterator; /** @@ -181,7 +178,7 @@ public class RecursiveMerger extends ResolveMerger { WorkingTreeIterator oldWTreeIt = workingTreeIterator; workingTreeIterator = null; try { - dircache = dircacheFromTree(currentBase.getTree()); + dircache = DirCache.read(reader, currentBase.getTree()); inCore = true; List<RevCommit> parents = new ArrayList<RevCommit>(); @@ -256,30 +253,4 @@ public class RecursiveMerger extends ResolveMerger { new Date((time + 1) * 1000L), TimeZone.getTimeZone("GMT+0000")); //$NON-NLS-1$ } - - /** - * Create a new in memory dircache which has the same content as a given - * tree. - * - * @param treeId - * the tree which should be used to fill the dircache - * @return a new in memory dircache - * @throws IOException - */ - private DirCache dircacheFromTree(ObjectId treeId) throws IOException { - DirCache ret = DirCache.newInCore(); - DirCacheBuilder aBuilder = ret.builder(); - try (TreeWalk atw = new TreeWalk(reader)) { - atw.addTree(treeId); - atw.setRecursive(true); - while (atw.next()) { - DirCacheEntry e = new DirCacheEntry(atw.getRawPath()); - e.setFileMode(atw.getFileMode(0)); - e.setObjectId(atw.getObjectId(0)); - aBuilder.add(e); - } - } - aBuilder.finish(); - return ret; - } } 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 8a6343c3cc..de08e4b6a0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -786,11 +786,6 @@ public class ResolveMerger extends ThreeWayMerger { private File writeMergedFile(MergeResult<RawText> result) throws FileNotFoundException, IOException { File workTree = db.getWorkTree(); - if (workTree == null) - // TODO: This should be handled by WorkingTreeIterators which - // support write operations - throw new UnsupportedOperationException(); - FS fs = db.getFS(); File of = new File(workTree, tw.getPathString()); File parentFolder = of.getParentFile(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java index 5509fc6a70..29c7f98411 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/storage/file/FileBasedConfig.java @@ -163,6 +163,9 @@ public class FileBasedConfig extends StoredConfig { hash = newHash; } } catch (FileNotFoundException noFile) { + if (configFile.exists()) { + throw noFile; + } clear(); snapshot = newSnapshot; } catch (IOException e) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java index 24fb3be64c..0834c359aa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -44,6 +44,8 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_ATOMIC; + import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; @@ -110,17 +112,15 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen public static final String CAPABILITY_SIDE_BAND_64K = GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; private final boolean thinPack; + private final boolean atomic; + private boolean capableAtomic; private boolean capableDeleteRefs; - private boolean capableReport; - private boolean capableSideBand; - private boolean capableOfsDelta; private boolean sentCommand; - private boolean writePack; /** Time in milliseconds spent transferring the pack data. */ @@ -135,6 +135,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen public BasePackPushConnection(final PackTransport packTransport) { super(packTransport); thinPack = transport.isPushThin(); + atomic = transport.isPushAtomic(); } public void push(final ProgressMonitor monitor, @@ -224,6 +225,11 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen private void writeCommands(final Collection<RemoteRefUpdate> refUpdates, final ProgressMonitor monitor, OutputStream outputStream) throws IOException { final String capabilities = enableCapabilities(monitor, outputStream); + if (atomic && !capableAtomic) { + throw new TransportException(uri, + JGitText.get().atomicPushNotSupported); + } + for (final RemoteRefUpdate rru : refUpdates) { if (!capableDeleteRefs && rru.isDelete()) { rru.setStatus(Status.REJECTED_NODELETE); @@ -231,9 +237,11 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen } final StringBuilder sb = new StringBuilder(); - final Ref advertisedRef = getRef(rru.getRemoteName()); - final ObjectId oldId = (advertisedRef == null ? ObjectId.zeroId() - : advertisedRef.getObjectId()); + ObjectId oldId = rru.getExpectedOldObjectId(); + if (oldId == null) { + Ref adv = getRef(rru.getRemoteName()); + oldId = adv != null ? adv.getObjectId() : ObjectId.zeroId(); + } sb.append(oldId.name()); sb.append(' '); sb.append(rru.getNewObjectId().name()); @@ -259,6 +267,8 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen private String enableCapabilities(final ProgressMonitor monitor, OutputStream outputStream) { final StringBuilder line = new StringBuilder(); + if (atomic) + capableAtomic = wantCapability(line, CAPABILITY_ATOMIC); capableReport = wantCapability(line, CAPABILITY_REPORT_STATUS); capableDeleteRefs = wantCapability(line, CAPABILITY_DELETE_REFS); capableOfsDelta = wantCapability(line, CAPABILITY_OFS_DELTA); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java index 7e9434a0f0..622680a27f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HMACSHA1NonceGenerator.java @@ -42,6 +42,7 @@ */ package org.eclipse.jgit.transport; +import java.io.File; import java.io.UnsupportedEncodingException; import java.security.InvalidKeyException; import java.security.NoSuchAlgorithmException; @@ -85,12 +86,16 @@ public class HMACSHA1NonceGenerator implements NonceGenerator { public synchronized String createNonce(Repository repo, long timestamp) throws IllegalStateException { String path; - if (repo instanceof DfsRepository) + if (repo instanceof DfsRepository) { path = ((DfsRepository) repo).getDescription().getRepositoryName(); - else if (repo.getDirectory() != null) - path = repo.getDirectory().getPath(); - else - throw new IllegalStateException(); + } else { + File directory = repo.getDirectory(); + if (directory != null) { + path = directory.getPath(); + } else { + throw new IllegalStateException(); + } + } String input = path + ":" + String.valueOf(timestamp); //$NON-NLS-1$ byte[] rawHmac; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java index 3594ea91b4..998f280014 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/HttpAuthMethod.java @@ -219,7 +219,8 @@ abstract class HttpAuthMethod { if (credentialsProvider.supports(u, p) && credentialsProvider.get(uri, u, p)) { username = u.getValue(); - password = new String(p.getValue()); + char[] v = p.getValue(); + password = (v == null) ? null : new String(p.getValue()); p.clear(); } else return false; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java index 8947f2779d..d436e08df1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushCertificateStore.java @@ -65,7 +65,6 @@ import java.util.Map; import java.util.NoSuchElementException; import org.eclipse.jgit.dircache.DirCache; -import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEditor; import org.eclipse.jgit.dircache.DirCacheEditor.PathEdit; import org.eclipse.jgit.dircache.DirCacheEntry; @@ -448,13 +447,10 @@ public class PushCertificateStore implements AutoCloseable { } private DirCache newDirCache() throws IOException { - DirCache dc = DirCache.newInCore(); if (commit != null) { - DirCacheBuilder b = dc.builder(); - b.addTree(new byte[0], DirCacheEntry.STAGE_0, reader, commit.getTree()); - b.finish(); + return DirCache.read(reader, commit.getTree()); } - return dc; + return DirCache.newInCore(); } private ObjectId saveCert(ObjectInserter inserter, DirCache dc, diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java index 9721ee9eb0..4fd192dbb2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java @@ -47,6 +47,7 @@ import java.io.IOException; import java.io.OutputStream; import java.text.MessageFormat; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.Map; @@ -183,6 +184,7 @@ class PushProcess { private Map<String, RemoteRefUpdate> prepareRemoteUpdates() throws TransportException { + boolean atomic = transport.isPushAtomic(); final Map<String, RemoteRefUpdate> result = new HashMap<String, RemoteRefUpdate>(); for (final RemoteRefUpdate rru : toPush.values()) { final Ref advertisedRef = connection.getRef(rru.getRemoteName()); @@ -205,8 +207,14 @@ class PushProcess { if (rru.isExpectingOldObjectId() && !rru.getExpectedOldObjectId().equals(advertisedOld)) { rru.setStatus(Status.REJECTED_REMOTE_CHANGED); + if (atomic) { + return rejectAll(); + } continue; } + if (!rru.isExpectingOldObjectId()) { + rru.setExpectedOldObjectId(advertisedOld); + } // create ref (hasn't existed on remote side) and delete ref // are always fast-forward commands, feasible at this level @@ -236,14 +244,28 @@ class PushProcess { JGitText.get().readingObjectsFromLocalRepositoryFailed, x.getMessage()), x); } rru.setFastForward(fastForward); - if (!fastForward && !rru.isForceUpdate()) + if (!fastForward && !rru.isForceUpdate()) { rru.setStatus(Status.REJECTED_NONFASTFORWARD); - else + if (atomic) { + return rejectAll(); + } + } else { result.put(rru.getRemoteName(), rru); + } } return result; } + private Map<String, RemoteRefUpdate> rejectAll() { + for (RemoteRefUpdate rru : toPush.values()) { + if (rru.getStatus() == Status.NOT_ATTEMPTED) { + rru.setStatus(RemoteRefUpdate.Status.REJECTED_OTHER_REASON); + rru.setMessage(JGitText.get().transactionAborted); + } + } + return Collections.emptyMap(); + } + private void modifyUpdatesForDryRun() { for (final RemoteRefUpdate rru : toPush.values()) if (rru.getStatus() == Status.NOT_ATTEMPTED) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java index 7c44dba4a2..5702b6d7b9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceiveCommand.java @@ -46,6 +46,7 @@ package org.eclipse.jgit.transport; import java.io.IOException; import java.text.MessageFormat; import java.util.ArrayList; +import java.util.Collection; import java.util.List; import org.eclipse.jgit.internal.JGitText; @@ -127,26 +128,46 @@ public class ReceiveCommand { } /** - * Filter a list of commands according to result. + * Filter a collection of commands according to result. * - * @param commands + * @param in * commands to filter. * @param want * desired status to filter by. * @return a copy of the command list containing only those commands with * the desired status. - * @since 2.0 + * @since 4.2 */ - public static List<ReceiveCommand> filter(List<ReceiveCommand> commands, - final Result want) { - List<ReceiveCommand> r = new ArrayList<ReceiveCommand>(commands.size()); - for (final ReceiveCommand cmd : commands) { + public static List<ReceiveCommand> filter(Iterable<ReceiveCommand> in, + Result want) { + List<ReceiveCommand> r; + if (in instanceof Collection) + r = new ArrayList<>(((Collection<?>) in).size()); + else + r = new ArrayList<>(); + for (ReceiveCommand cmd : in) { if (cmd.getResult() == want) r.add(cmd); } return r; } + /** + * Filter a list of commands according to result. + * + * @param commands + * commands to filter. + * @param want + * desired status to filter by. + * @return a copy of the command list containing only those commands with + * the desired status. + * @since 2.0 + */ + public static List<ReceiveCommand> filter(List<ReceiveCommand> commands, + Result want) { + return filter((Iterable<ReceiveCommand>) commands, want); + } + private final ObjectId oldId; private final ObjectId newId; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java index 1b82a36105..5c58346189 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RemoteRefUpdate.java @@ -125,7 +125,7 @@ public class RemoteRefUpdate { OK; } - private final ObjectId expectedOldObjectId; + private ObjectId expectedOldObjectId; private final ObjectId newObjectId; @@ -440,6 +440,10 @@ public class RemoteRefUpdate { return message; } + void setExpectedOldObjectId(ObjectId id) { + expectedOldObjectId = id; + } + void setStatus(final Status status) { this.status = status; } 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 cc7db47df5..6af153cbc9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -752,6 +752,9 @@ public abstract class Transport { /** Should push produce thin-pack when sending objects to remote repository. */ private boolean pushThin = DEFAULT_PUSH_THIN; + /** Should push be all-or-nothing atomic behavior? */ + private boolean pushAtomic; + /** Should push just check for operation result, not really push. */ private boolean dryRun; @@ -970,6 +973,31 @@ public abstract class Transport { } /** + * Default setting is false. + * + * @return true if push requires all-or-nothing atomic behavior. + * @since 4.2 + */ + public boolean isPushAtomic() { + return pushAtomic; + } + + /** + * Request atomic push (all references succeed, or none do). + * <p> + * Server must also support atomic push. If the server does not support the + * feature the push will abort without making changes. + * + * @param atomic + * true when push should be an all-or-nothing operation. + * @see PackTransport + * @since 4.2 + */ + public void setPushAtomic(final boolean atomic) { + this.pushAtomic = atomic; + } + + /** * @return true if destination refs should be removed if they no longer * exist at the source repository. */ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java index 7729c11ff9..23c506b128 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportAmazonS3.java @@ -148,8 +148,9 @@ public class TransportAmazonS3 extends HttpTransport implements WalkTransport { super(local, uri); Properties props = loadProperties(); - if (!props.containsKey("tmpdir") && local.getDirectory() != null) //$NON-NLS-1$ - props.put("tmpdir", local.getDirectory().getPath()); //$NON-NLS-1$ + File directory = local.getDirectory(); + if (!props.containsKey("tmpdir") && directory != null) //$NON-NLS-1$ + props.put("tmpdir", directory.getPath()); //$NON-NLS-1$ s3 = new AmazonS3(props); bucket = uri.getHost(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java index b27fa0d6b2..52f0f04562 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportGitSsh.java @@ -46,6 +46,7 @@ package org.eclipse.jgit.transport; +import java.io.File; import java.io.IOException; import java.io.InputStream; import java.text.MessageFormat; @@ -235,9 +236,10 @@ public class TransportGitSsh extends SshTransport implements PackTransport { ProcessBuilder pb = new ProcessBuilder(); pb.command(args); - if (local.getDirectory() != null) + File directory = local.getDirectory(); + if (directory != null) pb.environment().put(Constants.GIT_DIR_KEY, - local.getDirectory().getPath()); + directory.getPath()); try { return pb.start(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java index 3700b49555..9aeb840ebe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/URIish.java @@ -137,7 +137,11 @@ public class URIish implements Serializable { + OPT_PORT_P // + "(" // open a group capturing the user-home-dir-part //$NON-NLS-1$ + (USER_HOME_P + "?") //$NON-NLS-1$ - + "[\\\\/])" //$NON-NLS-1$ + + "(?:" // start non capturing group for host //$NON-NLS-1$ + // separator or end of line + + "[\\\\/])|$" //$NON-NLS-1$ + + ")" // close non capturing group for the host//$NON-NLS-1$ + // separator or end of line + ")?" // close the optional group containing hostname //$NON-NLS-1$ + "(.+)?" //$NON-NLS-1$ + "$"); //$NON-NLS-1$ @@ -593,6 +597,8 @@ public class URIish implements Serializable { private static boolean eq(final String a, final String b) { if (a == b) return true; + if (StringUtils.isEmptyOrNull(a) && StringUtils.isEmptyOrNull(b)) + return true; if (a == null || b == null) return false; return a.equals(b); @@ -638,7 +644,7 @@ public class URIish implements Serializable { if (getPath() != null) { if (getScheme() != null) { - if (!getPath().startsWith("/")) //$NON-NLS-1$ + if (!getPath().startsWith("/") && !getPath().isEmpty()) //$NON-NLS-1$ r.append('/'); } else if (getHost() != null) r.append(':'); @@ -709,9 +715,9 @@ public class URIish implements Serializable { */ public String getHumanishName() throws IllegalArgumentException { String s = getPath(); - if ("/".equals(s)) //$NON-NLS-1$ + if ("/".equals(s) || "".equals(s)) //$NON-NLS-1$ s = getHost(); - if ("".equals(s) || s == null) //$NON-NLS-1$ + if (s == null) // $NON-NLS-1$ throw new IllegalArgumentException(); String[] elements; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java index 41e593eaa2..5e71889574 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/AbstractTreeIterator.java @@ -49,6 +49,7 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.nio.CharBuffer; +import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.dircache.DirCacheCheckout; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; @@ -93,6 +94,13 @@ public abstract class AbstractTreeIterator { AbstractTreeIterator matches; /** + * Parsed rules of .gitattributes file if it exists. + * + * @since 4.2 + */ + protected AttributesNode attributesNode; + + /** * Number of entries we moved forward to force a D/F conflict match. * * @see NameConflictTreeWalk @@ -320,6 +328,42 @@ public abstract class AbstractTreeIterator { } /** + * Seek the iterator on a file, if present. + * + * @param name + * file name to find (will not find a directory). + * @return true if the file exists in this tree; false otherwise. + * @throws CorruptObjectException + * tree is invalid. + * @since 4.2 + */ + public boolean findFile(String name) throws CorruptObjectException { + return findFile(Constants.encode(name)); + } + + /** + * Seek the iterator on a file, if present. + * + * @param name + * file name to find (will not find a directory). + * @return true if the file exists in this tree; false otherwise. + * @throws CorruptObjectException + * tree is invalid. + * @since 4.2 + */ + public boolean findFile(byte[] name) throws CorruptObjectException { + for (; !eof(); next(1)) { + int cmp = pathCompare(name, 0, name.length, 0, pathOffset); + if (cmp == 0) { + return true; + } else if (cmp > 0) { + return false; + } + } + return false; + } + + /** * Compare the path of this current entry to a raw buffer. * * @param buf diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java index 0805e5068c..c038f07725 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/CanonicalTreeParser.java @@ -44,13 +44,23 @@ package org.eclipse.jgit.treewalk; +import static org.eclipse.jgit.lib.Constants.DOT_GIT_ATTRIBUTES; +import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; +import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import static org.eclipse.jgit.lib.Constants.OBJ_TREE; +import static org.eclipse.jgit.lib.Constants.TYPE_TREE; +import static org.eclipse.jgit.lib.Constants.encode; + import java.io.IOException; +import java.io.InputStream; import java.util.Arrays; +import java.util.Collections; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.lib.AnyObjectId; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.ObjectId; @@ -59,6 +69,7 @@ import org.eclipse.jgit.lib.ObjectReader; /** Parses raw Git trees from the canonical semi-text/semi-binary format. */ public class CanonicalTreeParser extends AbstractTreeIterator { private static final byte[] EMPTY = {}; + private static final byte[] ATTRS = encode(DOT_GIT_ATTRIBUTES); private byte[] raw; @@ -124,6 +135,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { * the raw tree content. */ public void reset(final byte[] treeData) { + attributesNode = null; raw = treeData; prevPtr = -1; currPtr = 0; @@ -199,7 +211,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { */ public void reset(final ObjectReader reader, final AnyObjectId id) throws IncorrectObjectTypeException, IOException { - reset(reader.open(id, Constants.OBJ_TREE).getCachedBytes()); + reset(reader.open(id, OBJ_TREE).getCachedBytes()); } @Override @@ -209,7 +221,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { idBuffer.fromRaw(idBuffer(), idOffset()); if (!FileMode.TREE.equals(mode)) { final ObjectId me = idBuffer.toObjectId(); - throw new IncorrectObjectTypeException(me, Constants.TYPE_TREE); + throw new IncorrectObjectTypeException(me, TYPE_TREE); } return createSubtreeIterator0(reader, idBuffer); } @@ -254,7 +266,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { @Override public int idOffset() { - return nextPtr - Constants.OBJECT_ID_LENGTH; + return nextPtr - OBJECT_ID_LENGTH; } @Override @@ -292,7 +304,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { prevPtr = ptr; while (raw[ptr] != 0) ptr++; - ptr += Constants.OBJECT_ID_LENGTH + 1; + ptr += OBJECT_ID_LENGTH + 1; } if (delta != 0) throw new ArrayIndexOutOfBoundsException(delta); @@ -328,7 +340,7 @@ public class CanonicalTreeParser extends AbstractTreeIterator { trace[delta] = ptr; while (raw[ptr] != 0) ptr++; - ptr += Constants.OBJECT_ID_LENGTH + 1; + ptr += OBJECT_ID_LENGTH + 1; } if (trace[1] == -1) throw new ArrayIndexOutOfBoundsException(delta); @@ -363,6 +375,46 @@ public class CanonicalTreeParser extends AbstractTreeIterator { } } pathLen = tmp; - nextPtr = ptr + Constants.OBJECT_ID_LENGTH; + nextPtr = ptr + OBJECT_ID_LENGTH; + } + + /** + * Retrieve the {@link AttributesNode} for the current entry. + * + * @param reader + * {@link ObjectReader} used to parse the .gitattributes entry. + * @return {@link AttributesNode} for the current entry. + * @throws IOException + * @since 4.2 + */ + public AttributesNode getEntryAttributesNode(ObjectReader reader) + throws IOException { + if (attributesNode == null) { + attributesNode = findAttributes(reader); + } + return attributesNode.getRules().isEmpty() ? null : attributesNode; + } + + private AttributesNode findAttributes(ObjectReader reader) + throws IOException { + CanonicalTreeParser itr = new CanonicalTreeParser(); + itr.reset(raw); + if (itr.findFile(ATTRS)) { + return loadAttributes(reader, itr.getEntryObjectId()); + } + return noAttributes(); + } + + private static AttributesNode loadAttributes(ObjectReader reader, + AnyObjectId id) throws IOException { + AttributesNode r = new AttributesNode(); + try (InputStream in = reader.open(id, OBJ_BLOB).openStream()) { + r.parse(in); + } + return r.getRules().isEmpty() ? noAttributes() : r; + } + + private static AttributesNode noAttributes() { + return new AttributesNode(Collections.<AttributesRule> emptyList()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java index 8d2cb1d8cd..accf4956f6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/FileTreeIterator.java @@ -67,8 +67,8 @@ import org.eclipse.jgit.util.FS; */ public class FileTreeIterator extends WorkingTreeIterator { /** - * the starting directory. This directory should correspond to the root of - * the repository. + * the starting directory of this Iterator. All entries are located directly + * in this directory. */ protected final File directory; @@ -238,8 +238,6 @@ public class FileTreeIterator extends WorkingTreeIterator { @Override protected byte[] idSubmodule(final Entry e) { - if (repository == null) - return idSubmodule(getDirectory(), e); - return super.idSubmodule(e); + return idSubmodule(getDirectory(), e); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java index 2d6acbddf0..350f563964 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/NameConflictTreeWalk.java @@ -96,7 +96,7 @@ public class NameConflictTreeWalk extends TreeWalk { * the repository the walker will obtain data from. */ public NameConflictTreeWalk(final Repository repo) { - this(repo.newObjectReader()); + super(repo); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java index 06e828419d..06dc0bf6d0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/TreeWalk.java @@ -45,12 +45,25 @@ package org.eclipse.jgit.treewalk; import java.io.IOException; - +import java.util.HashMap; +import java.util.Map; +import java.util.Set; + +import org.eclipse.jgit.annotations.Nullable; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.attributes.Attribute; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.attributes.Attributes; +import org.eclipse.jgit.attributes.AttributesNode; +import org.eclipse.jgit.attributes.AttributesNodeProvider; +import org.eclipse.jgit.attributes.AttributesProvider; +import org.eclipse.jgit.dircache.DirCacheIterator; import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StopWalkException; import org.eclipse.jgit.lib.AnyObjectId; +import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.MutableObjectId; @@ -60,6 +73,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.revwalk.RevTree; import org.eclipse.jgit.treewalk.filter.PathFilter; import org.eclipse.jgit.treewalk.filter.TreeFilter; +import org.eclipse.jgit.util.QuotedString; import org.eclipse.jgit.util.RawParseUtils; /** @@ -82,10 +96,45 @@ import org.eclipse.jgit.util.RawParseUtils; * Multiple simultaneous TreeWalk instances per {@link Repository} are * permitted, even from concurrent threads. */ -public class TreeWalk implements AutoCloseable { +public class TreeWalk implements AutoCloseable, AttributesProvider { private static final AbstractTreeIterator[] NO_TREES = {}; /** + * @since 4.2 + */ + public static enum OperationType { + /** + * Represents a checkout operation (for example a checkout or reset + * operation). + */ + CHECKOUT_OP, + + /** + * Represents a checkin operation (for example an add operation) + */ + CHECKIN_OP + } + + /** + * Type of operation you want to retrieve the git attributes for. + */ + private OperationType operationType = OperationType.CHECKOUT_OP; + + /** + * The filter command as defined in gitattributes. The keys are + * filterName+"."+filterCommandType. E.g. "lfs.clean" + */ + private Map<String, String> filterCommandsByNameDotType = new HashMap<String, String>(); + + /** + * @param operationType + * @since 4.2 + */ + public void setOperationType(OperationType operationType) { + this.operationType = operationType; + } + + /** * Open a tree walk and filter to exactly one path. * <p> * The returned tree walk is already positioned on the requested path, so @@ -213,8 +262,15 @@ public class TreeWalk implements AutoCloseable { private boolean postChildren; + private AttributesNodeProvider attributesNodeProvider; + AbstractTreeIterator currentHead; + /** Cached attribute for the current entry */ + private Attributes attrs = null; + + private Config config; + /** * Create a new tree walker for a given repository. * @@ -225,6 +281,8 @@ public class TreeWalk implements AutoCloseable { */ public TreeWalk(final Repository repo) { this(repo.newObjectReader(), true); + config = repo.getConfig(); + attributesNodeProvider = repo.createAttributesNodeProvider(); } /** @@ -356,8 +414,29 @@ public class TreeWalk implements AutoCloseable { postOrderTraversal = b; } + /** + * Sets the {@link AttributesNodeProvider} for this {@link TreeWalk}. + * <p> + * This is a requirement for a correct computation of the git attributes. + * If this {@link TreeWalk} has been built using + * {@link #TreeWalk(Repository)} constructor, the + * {@link AttributesNodeProvider} has already been set. Indeed,the + * {@link Repository} can provide an {@link AttributesNodeProvider} using + * {@link Repository#createAttributesNodeProvider()} method. Otherwise you + * should provide one. + * </p> + * + * @see Repository#createAttributesNodeProvider() + * @param provider + * @since 4.2 + */ + public void setAttributesNodeProvider(AttributesNodeProvider provider) { + attributesNodeProvider = provider; + } + /** Reset this walker so new tree iterators can be added to it. */ public void reset() { + attrs = null; trees = NO_TREES; advance = false; depth = 0; @@ -401,6 +480,7 @@ public class TreeWalk implements AutoCloseable { advance = false; depth = 0; + attrs = null; } /** @@ -450,6 +530,7 @@ public class TreeWalk implements AutoCloseable { trees = r; advance = false; depth = 0; + attrs = null; } /** @@ -546,6 +627,7 @@ public class TreeWalk implements AutoCloseable { public boolean next() throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { try { + attrs = null; if (advance) { advance = false; postChildren = false; @@ -915,6 +997,7 @@ public class TreeWalk implements AutoCloseable { */ public void enterSubtree() throws MissingObjectException, IncorrectObjectTypeException, CorruptObjectException, IOException { + attrs = null; final AbstractTreeIterator ch = currentHead; final AbstractTreeIterator[] tmp = new AbstractTreeIterator[trees.length]; for (int i = 0; i < trees.length; i++) { @@ -1008,4 +1091,296 @@ public class TreeWalk implements AutoCloseable { static String pathOf(final byte[] buf, int pos, int end) { return RawParseUtils.decode(Constants.CHARSET, buf, pos, end); } + + /** + * Retrieve the git attributes for the current entry. + * + * <h4>Git attribute computation</h4> + * + * <ul> + * <li>Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).</li> + * <li>Completes the list of attributes using the .gitattributes files + * located on the current path (the further the directory that contains + * .gitattributes is from the path in question, the lower its precedence). + * For a checkin operation, it will look first on the working tree (if any). + * If there is no attributes file, it will fallback on the index. For a + * checkout operation, it will first use the index entry and then fallback + * on the working tree if none.</li> + * <li>In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})</li> + * + * </ul> + * + * + * <h4>Iterator constraints</h4> + * + * <p> + * In order to have a correct list of attributes for the current entry, this + * {@link TreeWalk} requires to have at least one + * {@link AttributesNodeProvider} and a {@link DirCacheIterator} set up. An + * {@link AttributesNodeProvider} is used to retrieve the attributes from + * the info attributes file and the global attributes file. The + * {@link DirCacheIterator} is used to retrieve the .gitattributes files + * stored in the index. A {@link WorkingTreeIterator} can also be provided + * to access the local version of the .gitattributes files. If none is + * provided it will fallback on the {@link DirCacheIterator}. + * </p> + * + * @return a {@link Set} of {@link Attribute}s that match the current entry. + * @since 4.2 + */ + public Attributes getAttributes() { + if (attrs != null) + return attrs; + + if (attributesNodeProvider == null) { + // The work tree should have a AttributesNodeProvider to be able to + // retrieve the info and global attributes node + throw new IllegalStateException( + "The tree walk should have one AttributesNodeProvider set in order to compute the git attributes."); //$NON-NLS-1$ + } + + WorkingTreeIterator workingTreeIterator = getTree(WorkingTreeIterator.class); + DirCacheIterator dirCacheIterator = getTree(DirCacheIterator.class); + CanonicalTreeParser other = getTree(CanonicalTreeParser.class); + + if (workingTreeIterator == null && dirCacheIterator == null + && other == null) { + // Can not retrieve the attributes without at least one of the above + // iterators. + return new Attributes(); + } + + String path = currentHead.getEntryPathString(); + final boolean isDir = FileMode.TREE.equals(currentHead.mode); + Attributes attributes = new Attributes(); + try { + // Gets the global attributes node + AttributesNode globalNodeAttr = attributesNodeProvider + .getGlobalAttributesNode(); + // Gets the info attributes node + AttributesNode infoNodeAttr = attributesNodeProvider + .getInfoAttributesNode(); + + // Gets the info attributes + if (infoNodeAttr != null) { + infoNodeAttr.getAttributes(path, isDir, attributes); + } + + // Gets the attributes located on the current entry path + getPerDirectoryEntryAttributes(path, isDir, operationType, + workingTreeIterator, dirCacheIterator, other, attributes); + + // Gets the attributes located in the global attribute file + if (globalNodeAttr != null) { + globalNodeAttr.getAttributes(path, isDir, attributes); + } + } catch (IOException e) { + throw new JGitInternalException("Error while parsing attributes", e); //$NON-NLS-1$ + } + // now after all attributes are collected - in the correct hierarchy + // order - remove all unspecified entries (the ! marker) + for (Attribute a : attributes.getAll()) { + if (a.getState() == State.UNSPECIFIED) + attributes.remove(a.getKey()); + } + return attributes; + } + + /** + * Get the attributes located on the current entry path. + * + * @param path + * current entry path + * @param isDir + * holds true if the current entry is a directory + * @param opType + * type of operation + * @param workingTreeIterator + * a {@link WorkingTreeIterator} matching the current entry + * @param dirCacheIterator + * a {@link DirCacheIterator} matching the current entry + * @param other + * a {@link CanonicalTreeParser} matching the current entry + * @param attributes + * Non null map holding the existing attributes. This map will be + * augmented with new entry. None entry will be overrided. + * @throws IOException + * It raises an {@link IOException} if a problem appears while + * parsing one on the attributes file. + */ + private void getPerDirectoryEntryAttributes(String path, boolean isDir, + OperationType opType, WorkingTreeIterator workingTreeIterator, + DirCacheIterator dirCacheIterator, CanonicalTreeParser other, + Attributes attributes) + throws IOException { + // Prevents infinite recurrence + if (workingTreeIterator != null || dirCacheIterator != null + || other != null) { + AttributesNode currentAttributesNode = getCurrentAttributesNode( + opType, workingTreeIterator, dirCacheIterator, other); + if (currentAttributesNode != null) { + currentAttributesNode.getAttributes(path, isDir, attributes); + } + getPerDirectoryEntryAttributes(path, isDir, opType, + getParent(workingTreeIterator, WorkingTreeIterator.class), + getParent(dirCacheIterator, DirCacheIterator.class), + getParent(other, CanonicalTreeParser.class), attributes); + } + } + + private static <T extends AbstractTreeIterator> T getParent(T current, + Class<T> type) { + if (current != null) { + AbstractTreeIterator parent = current.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + } + return null; + } + + private <T extends AbstractTreeIterator> T getTree(Class<T> type) { + for (int i = 0; i < trees.length; i++) { + AbstractTreeIterator tree = trees[i]; + if (type.isInstance(tree)) { + return type.cast(tree); + } + } + return null; + } + + /** + * Get the {@link AttributesNode} for the current entry. + * <p> + * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + * </p> + * + * @param opType + * @param workingTreeIterator + * @param dirCacheIterator + * @param other + * @return a {@link AttributesNode} of the current entry, + * {@link NullPointerException} otherwise. + * @throws IOException + * It raises an {@link IOException} if a problem appears while + * parsing one on the attributes file. + */ + private AttributesNode getCurrentAttributesNode(OperationType opType, + @Nullable WorkingTreeIterator workingTreeIterator, + @Nullable DirCacheIterator dirCacheIterator, + @Nullable CanonicalTreeParser other) + throws IOException { + AttributesNode attributesNode = null; + switch (opType) { + case CHECKIN_OP: + if (workingTreeIterator != null) { + attributesNode = workingTreeIterator.getEntryAttributesNode(); + } + if (attributesNode == null && dirCacheIterator != null) { + attributesNode = getAttributesNode(dirCacheIterator + .getEntryAttributesNode(getObjectReader()), + attributesNode); + } + if (attributesNode == null && other != null) { + attributesNode = getAttributesNode( + other.getEntryAttributesNode(getObjectReader()), + attributesNode); + } + break; + case CHECKOUT_OP: + if (other != null) { + attributesNode = other + .getEntryAttributesNode(getObjectReader()); + } + if (dirCacheIterator != null) { + attributesNode = getAttributesNode(dirCacheIterator + .getEntryAttributesNode(getObjectReader()), + attributesNode); + } + if (attributesNode == null && workingTreeIterator != null) { + attributesNode = getAttributesNode( + workingTreeIterator.getEntryAttributesNode(), + attributesNode); + } + break; + default: + throw new IllegalStateException( + "The only supported operation types are:" //$NON-NLS-1$ + + OperationType.CHECKIN_OP + "," //$NON-NLS-1$ + + OperationType.CHECKOUT_OP); + } + + return attributesNode; + } + + private static AttributesNode getAttributesNode(AttributesNode value, + AttributesNode defaultValue) { + return (value == null) ? defaultValue : value; + } + + /** + * Inspect config and attributes to return a filtercommand applicable for + * the current path + * + * @param filterCommandType + * which type of filterCommand should be executed. E.g. "clean", + * "smudge" + * @return a filter command + * @throws IOException + * @since 4.2 + */ + public String getFilterCommand(String filterCommandType) + throws IOException { + Attributes attributes = getAttributes(); + + Attribute f = attributes.get(Constants.ATTR_FILTER); + if (f == null) { + return null; + } + String filterValue = f.getValue(); + if (filterValue == null) { + return null; + } + + String filterCommand = getFilterCommandDefinition(filterValue, + filterCommandType); + if (filterCommand == null) { + return null; + } + return filterCommand.replaceAll("%f", //$NON-NLS-1$ + QuotedString.BOURNE.quote((getPathString()))); + } + + /** + * Get the filter command how it is defined in gitconfig. The returned + * string may contain "%f" which needs to be replaced by the current path + * before executing the filter command. These filter definitions are cached + * for better performance. + * + * @param filterDriverName + * The name of the filter driver as it is referenced in the + * gitattributes file. E.g. "lfs". For each filter driver there + * may be many commands defined in the .gitconfig + * @param filterCommandType + * The type of the filter command for a specific filter driver. + * May be "clean" or "smudge". + * @return the definition of the command to be executed for this filter + * driver and filter command + */ + private String getFilterCommandDefinition(String filterDriverName, + String filterCommandType) { + String key = filterDriverName + "." + filterCommandType; //$NON-NLS-1$ + String filterCommand = filterCommandsByNameDotType.get(key); + if (filterCommand != null) + return filterCommand; + filterCommand = config.getString(Constants.ATTR_FILTER, + filterDriverName, filterCommandType); + if (filterCommand != null) + filterCommandsByNameDotType.put(key, filterCommand); + return filterCommand; + } } 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 73ab04f9cb..94beeeb56f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -62,6 +62,7 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.attributes.AttributesNode; import org.eclipse.jgit.attributes.AttributesRule; import org.eclipse.jgit.diff.RawText; @@ -76,6 +77,7 @@ import org.eclipse.jgit.ignore.IgnoreNode; import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.CoreConfig; +import org.eclipse.jgit.lib.CoreConfig.AutoCRLF; import org.eclipse.jgit.lib.CoreConfig.CheckStat; import org.eclipse.jgit.lib.CoreConfig.SymLinks; import org.eclipse.jgit.lib.FileMode; @@ -85,6 +87,7 @@ import org.eclipse.jgit.lib.ObjectReader; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.submodule.SubmoduleWalk; import org.eclipse.jgit.util.FS; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.RawParseUtils; import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; @@ -101,6 +104,8 @@ import org.eclipse.jgit.util.io.EolCanonicalizingInputStream; * @see FileTreeIterator */ public abstract class WorkingTreeIterator extends AbstractTreeIterator { + private static final int MAX_EXCEPTION_TEXT_SIZE = 10 * 1024; + /** An empty entry array, suitable for {@link #init(Entry[])}. */ protected static final Entry[] EOF = {}; @@ -134,8 +139,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { /** If there is a .gitignore file present, the parsed rules from it. */ private IgnoreNode ignoreNode; - /** If there is a .gitattributes file present, the parsed rules from it. */ - private AttributesNode attributesNode; + private String cleanFilterCommand; /** Repository that is the root level being iterated over */ protected Repository repository; @@ -147,19 +151,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { private int contentIdOffset; /** - * Holds the {@link AttributesNode} that is stored in - * $GIT_DIR/info/attributes file. - */ - private AttributesNode infoAttributeNode; - - /** - * Holds the {@link AttributesNode} that is stored in global attribute file. - * - * @see CoreConfig#getAttributesFile() - */ - private AttributesNode globalAttributeNode; - - /** * Create a new iterator with no parent. * * @param options @@ -202,8 +193,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { protected WorkingTreeIterator(final WorkingTreeIterator p) { super(p); state = p.state; - infoAttributeNode = p.infoAttributeNode; - globalAttributeNode = p.globalAttributeNode; + repository = p.repository; } /** @@ -223,10 +213,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { else entry = null; ignoreNode = new RootIgnoreNode(entry, repo); - - infoAttributeNode = new InfoAttributesNode(repo); - - globalAttributeNode = new GlobalAttributesNode(repo); } /** @@ -370,7 +356,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { private InputStream possiblyFilteredInputStream(final Entry e, final InputStream is, final long len) throws IOException { - if (!mightNeedCleaning()) { + boolean mightNeedCleaning = mightNeedCleaning(); + if (!mightNeedCleaning) { canonLen = len; return is; } @@ -388,7 +375,8 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { return new ByteArrayInputStream(raw, 0, n); } - if (isBinary(e)) { + // TODO: fix autocrlf causing mightneedcleaning + if (!mightNeedCleaning && isBinary(e)) { canonLen = len; return is; } @@ -412,10 +400,12 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } - private boolean mightNeedCleaning() { + private boolean mightNeedCleaning() throws IOException { switch (getOptions().getAutoCRLF()) { case FALSE: default: + if (getCleanFilterCommand() != null) + return true; return false; case TRUE: @@ -437,8 +427,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } - private static ByteBuffer filterClean(byte[] src, int n) - throws IOException { + private ByteBuffer filterClean(byte[] src, int n) throws IOException { InputStream in = new ByteArrayInputStream(src); try { return IO.readWholeStream(filterClean(in), n); @@ -447,8 +436,42 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } - private static InputStream filterClean(InputStream in) { - return new EolCanonicalizingInputStream(in, true); + private InputStream filterClean(InputStream in) throws IOException { + in = handleAutoCRLF(in); + String filterCommand = getCleanFilterCommand(); + if (filterCommand != null) { + FS fs = repository.getFS(); + ProcessBuilder filterProcessBuilder = fs.runInShell(filterCommand, + new String[0]); + filterProcessBuilder.directory(repository.getWorkTree()); + filterProcessBuilder.environment().put(Constants.GIT_DIR_KEY, + repository.getDirectory().getAbsolutePath()); + ExecutionResult result; + try { + result = fs.execute(filterProcessBuilder, in); + } catch (IOException | InterruptedException e) { + throw new IOException(new FilterFailedException(e, + filterCommand, getEntryPathString())); + } + int rc = result.getRc(); + if (rc != 0) { + throw new IOException(new FilterFailedException(rc, + filterCommand, getEntryPathString(), + result.getStdout().toByteArray(MAX_EXCEPTION_TEXT_SIZE), + RawParseUtils.decode(result.getStderr() + .toByteArray(MAX_EXCEPTION_TEXT_SIZE)))); + } + return result.getStdout().openInputStream(); + } + return in; + } + + private InputStream handleAutoCRLF(InputStream in) { + AutoCRLF autoCRLF = getOptions().getAutoCRLF(); + if (autoCRLF == AutoCRLF.TRUE || autoCRLF == AutoCRLF.INPUT) { + in = new EolCanonicalizingInputStream(in, true); + } + return in; } /** @@ -507,6 +530,7 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { System.arraycopy(e.encodedName, 0, path, pathOffset, nameLen); pathLen = pathOffset + nameLen; canonLen = -1; + cleanFilterCommand = null; } /** @@ -667,41 +691,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { return attributesNode; } - /** - * Retrieves the {@link AttributesNode} that holds the information located - * in $GIT_DIR/info/attributes file. - * - * @return the {@link AttributesNode} that holds the information located in - * $GIT_DIR/info/attributes file. - * @throws IOException - * if an error is raised while parsing the attributes file - * @since 3.7 - */ - public AttributesNode getInfoAttributesNode() throws IOException { - if (infoAttributeNode instanceof InfoAttributesNode) - infoAttributeNode = ((InfoAttributesNode) infoAttributeNode).load(); - return infoAttributeNode; - } - - /** - * Retrieves the {@link AttributesNode} that holds the information located - * in system-wide file. - * - * @return the {@link AttributesNode} that holds the information located in - * system-wide file. - * @throws IOException - * IOException if an error is raised while parsing the - * attributes file - * @see CoreConfig#getAttributesFile() - * @since 3.7 - */ - public AttributesNode getGlobalAttributesNode() throws IOException { - if (globalAttributeNode instanceof GlobalAttributesNode) - globalAttributeNode = ((GlobalAttributesNode) globalAttributeNode) - .load(); - return globalAttributeNode; - } - private static final Comparator<Entry> ENTRY_CMP = new Comparator<Entry>() { public int compare(final Entry o1, final Entry o2) { final byte[] a = o1.encodedName; @@ -1296,68 +1285,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } - /** - * Attributes node loaded from global system-wide file. - */ - private static class GlobalAttributesNode extends AttributesNode { - final Repository repository; - - GlobalAttributesNode(Repository repository) { - this.repository = repository; - } - - AttributesNode load() throws IOException { - AttributesNode r = new AttributesNode(); - - FS fs = repository.getFS(); - String path = repository.getConfig().get(CoreConfig.KEY) - .getAttributesFile(); - if (path != null) { - File attributesFile; - if (path.startsWith("~/")) //$NON-NLS-1$ - attributesFile = fs.resolve(fs.userHome(), - path.substring(2)); - else - attributesFile = fs.resolve(null, path); - loadRulesFromFile(r, attributesFile); - } - return r.getRules().isEmpty() ? null : r; - } - } - - /** Magic type indicating there may be rules for the top level. */ - private static class InfoAttributesNode extends AttributesNode { - final Repository repository; - - InfoAttributesNode(Repository repository) { - this.repository = repository; - } - - AttributesNode load() throws IOException { - AttributesNode r = new AttributesNode(); - - FS fs = repository.getFS(); - - File attributes = fs.resolve(repository.getDirectory(), - "info/attributes"); //$NON-NLS-1$ - loadRulesFromFile(r, attributes); - - return r.getRules().isEmpty() ? null : r; - } - - } - - private static void loadRulesFromFile(AttributesNode r, File attrs) - throws FileNotFoundException, IOException { - if (attrs.exists()) { - FileInputStream in = new FileInputStream(attrs); - try { - r.parse(in); - } finally { - in.close(); - } - } - } private static final class IteratorState { /** Options used to process the working tree. */ @@ -1390,4 +1317,18 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } } + + /** + * @return the clean filter command for the current entry or + * <code>null</code> if no such command is defined + * @throws IOException + * @since 4.2 + */ + public String getCleanFilterCommand() throws IOException { + if (cleanFilterCommand == null && state.walk != null) { + cleanFilterCommand = state.walk + .getFilterCommand(Constants.ATTR_FILTER_TYPE_CLEAN); + } + return cleanFilterCommand; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java index e407dd8be9..253acbb76a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -110,7 +110,54 @@ public abstract class FS { } } - final static Logger LOG = LoggerFactory.getLogger(FS.class); + /** + * Result of an executed process. The caller is responsible to close the + * contained {@link TemporaryBuffer}s + * + * @since 4.2 + */ + public static class ExecutionResult { + private TemporaryBuffer stdout; + + private TemporaryBuffer stderr; + + private int rc; + + /** + * @param stdout + * @param stderr + * @param rc + */ + public ExecutionResult(TemporaryBuffer stdout, TemporaryBuffer stderr, + int rc) { + this.stdout = stdout; + this.stderr = stderr; + this.rc = rc; + } + + /** + * @return buffered standard output stream + */ + public TemporaryBuffer getStdout() { + return stdout; + } + + /** + * @return buffered standard error stream + */ + public TemporaryBuffer getStderr() { + return stderr; + } + + /** + * @return the return code of the process + */ + public int getRc() { + return rc; + } + } + + private final static Logger LOG = LoggerFactory.getLogger(FS.class); /** The auto-detected implementation selected for this operating system and JRE. */ public static final FS DETECTED = detect(); @@ -902,9 +949,7 @@ public abstract class FS { * @param outRedirect * An OutputStream on which to redirect the processes stdout. Can * be <code>null</code>, in which case the processes standard - * output will be lost. If binary is set to <code>false</code> - * then it is expected that the process emits text data which - * should be processed line by line. + * output will be lost. * @param errRedirect * An OutputStream on which to redirect the processes stderr. Can * be <code>null</code>, in which case the processes standard @@ -912,9 +957,9 @@ public abstract class FS { * @param inRedirect * An InputStream from which to redirect the processes stdin. Can * be <code>null</code>, in which case the process doesn't get - * any data over stdin. If binary is set to - * <code>false</code> then it is expected that the process - * expects text data which should be processed line by line. + * any data over stdin. It is assumed that the whole InputStream + * will be consumed by the process. The method will close the + * inputstream after all bytes are read. * @return the return code of this process. * @throws IOException * if an I/O error occurs while executing this process. @@ -964,6 +1009,9 @@ public abstract class FS { // A process doesn't clean its own resources even when destroyed // Explicitly try and close all three streams, preserving the // outer I/O exception if any. + if (inRedirect != null) { + inRedirect.close(); + } try { process.getErrorStream().close(); } catch (IOException e) { @@ -1004,10 +1052,10 @@ public abstract class FS { pool.shutdown(); // Disable new tasks from being submitted try { // Wait a while for existing tasks to terminate - if (!pool.awaitTermination(5, TimeUnit.SECONDS)) { + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) { pool.shutdownNow(); // Cancel currently executing tasks // Wait a while for tasks to respond to being canceled - if (!pool.awaitTermination(5, TimeUnit.SECONDS)) + if (!pool.awaitTermination(60, TimeUnit.SECONDS)) hasShutdown = false; } } catch (InterruptedException ie) { @@ -1034,6 +1082,31 @@ public abstract class FS { */ public abstract ProcessBuilder runInShell(String cmd, String[] args); + /** + * Execute a command defined by a {@link ProcessBuilder}. + * + * @param pb + * The command to be executed + * @param in + * The standard input stream passed to the process + * @return The result of the executed command + * @throws InterruptedException + * @throws IOException + * @since 4.2 + */ + public ExecutionResult execute(ProcessBuilder pb, InputStream in) + throws IOException, InterruptedException { + TemporaryBuffer stdout = new TemporaryBuffer.LocalFile(null); + TemporaryBuffer stderr = new TemporaryBuffer.Heap(1024, 1024 * 1024); + try { + int rc = runProcess(pb, stdout, stderr, in); + return new ExecutionResult(stdout, stderr, rc); + } finally { + stdout.close(); + stderr.close(); + } + } + private static class Holder<V> { final V value; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java index f0a2e721a6..defe14f0ed 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32.java @@ -171,7 +171,8 @@ public class FS_Win32 extends FS { createSymLink(linkName, tempFile.getPath()); supportSymlinks = Boolean.TRUE; linkName.delete(); - } catch (IOException | UnsupportedOperationException e) { + } catch (IOException | UnsupportedOperationException + | InternalError e) { supportSymlinks = Boolean.FALSE; } finally { if (tempFile != null) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java index 2450be4c17..ec581b397a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS_Win32_Cygwin.java @@ -162,17 +162,15 @@ public class FS_Win32_Cygwin extends FS_Win32 { errRedirect, stdinArgs); } - @Override - public boolean supportsSymlinks() { - return true; - } - /** * @since 3.7 */ @Override public File findHook(Repository repository, String hookName) { final File gitdir = repository.getDirectory(); + if (gitdir == null) { + return null; + } final Path hookPath = gitdir.toPath().resolve(Constants.HOOKS) .resolve(hookName); if (Files.isExecutable(hookPath)) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java index 6d0318c029..727ea79cc9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FileUtils.java @@ -398,8 +398,10 @@ public class FileUtils { * Create a symbolic link * * @param path + * the path of the symbolic link to create * @param target - * @return path to the created link + * the target of the symbolic link + * @return the path to the symbolic link * @throws IOException * @since 4.2 */ 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 4795c89e73..9860ef070f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/SystemReader.java @@ -339,4 +339,19 @@ public abstract class SystemReader { public void checkPath(String path) throws CorruptObjectException { platformChecker.checkPath(path); } + + /** + * 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 4.2 + */ + public void checkPath(byte[] path) throws CorruptObjectException { + platformChecker.checkPath(path, 0, path.length); + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java index ca47f50fd9..3cd5929c7f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -247,6 +247,37 @@ public abstract class TemporaryBuffer extends OutputStream { } /** + * Convert this buffer's contents into a contiguous byte array. If this size + * of the buffer exceeds the limit only return the first {@code limit} bytes + * <p> + * The buffer is only complete after {@link #close()} has been invoked. + * + * @param limit + * the maximum number of bytes to be returned + * + * @return the byte array limited to {@code limit} bytes. + * @throws IOException + * an error occurred reading from a local temporary file + * @throws OutOfMemoryError + * the buffer cannot fit in memory + * + * @since 4.2 + */ + public byte[] toByteArray(int limit) throws IOException { + final long len = Math.min(length(), limit); + if (Integer.MAX_VALUE < len) + throw new OutOfMemoryError( + JGitText.get().lengthExceedsMaximumArraySize); + final byte[] out = new byte[(int) len]; + int outPtr = 0; + for (final Block b : blocks) { + System.arraycopy(b.buffer, 0, out, outPtr, b.count); + outPtr += b.count; + } + return out; + } + + /** * Send this buffer to an output stream. * <p> * This method may only be invoked after {@link #close()} has completed |