diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java index dec9b59f2..362a09d64 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientDumbServerTest.java @@ -209,7 +209,7 @@ public class DumbClientDumbServerTest extends HttpTestCase { } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List loose = getRequests(loose(remoteURI, A_txt)); @@ -234,7 +234,7 @@ public class DumbClientDumbServerTest extends HttpTestCase { } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List req; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java index e385b9538..da3a09809 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/DumbClientSmartServerTest.java @@ -225,7 +225,7 @@ public class DumbClientSmartServerTest extends HttpTestCase { } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List loose = getRequests(loose(remoteURI, A_txt)); @@ -253,7 +253,7 @@ public class DumbClientSmartServerTest extends HttpTestCase { } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List req; diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java index f1056f2d6..d67c8173c 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HookMessageTest.java @@ -164,8 +164,8 @@ public class HookMessageTest extends HttpTestCase { } assertTrue(remoteRepository.hasObject(Q_txt)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); List requests = getRequests(); diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java index 1b6c552ce..9ca0789e2 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/SmartClientSmartServerTest.java @@ -296,7 +296,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } assertTrue(dst.hasObject(A_txt)); - assertEquals(B, dst.getRef(master).getObjectId()); + assertEquals(B, dst.exactRef(master).getObjectId()); fsck(dst, B); List requests = getRequests(); @@ -337,7 +337,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } finally { t.close(); } - assertEquals(B, dst.getRepository().getRef(master).getObjectId()); + assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); // Only create a few new commits. @@ -358,7 +358,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } finally { t.close(); } - assertEquals(Z, dst.getRepository().getRef(master).getObjectId()); + assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); List requests = getRequests(); requests.removeAll(cloneRequests); @@ -400,7 +400,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } finally { t.close(); } - assertEquals(B, dst.getRepository().getRef(master).getObjectId()); + assertEquals(B, dst.getRepository().exactRef(master).getObjectId()); List cloneRequests = getRequests(); // Force enough into the local client that enumeration will @@ -424,7 +424,7 @@ public class SmartClientSmartServerTest extends HttpTestCase { } finally { t.close(); } - assertEquals(Z, dst.getRepository().getRef(master).getObjectId()); + assertEquals(Z, dst.getRepository().exactRef(master).getObjectId()); List requests = getRequests(); requests.removeAll(cloneRequests); @@ -579,8 +579,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { } assertTrue(remoteRepository.hasObject(Q_txt)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); final ReflogReader log = remoteRepository.getReflogReader(dstName); @@ -657,8 +657,8 @@ public class SmartClientSmartServerTest extends HttpTestCase { } assertTrue(remoteRepository.hasObject(Q_bin)); - assertNotNull("has " + dstName, remoteRepository.getRef(dstName)); - assertEquals(Q, remoteRepository.getRef(dstName).getObjectId()); + assertNotNull("has " + dstName, remoteRepository.exactRef(dstName)); + assertEquals(Q, remoteRepository.exactRef(dstName).getObjectId()); fsck(remoteRepository, Q); List requests = getRequests(); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java index 521593ea8..2962e7192 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/JGitTestUtil.java @@ -241,6 +241,17 @@ public abstract class JGitTestUtil { FileUtils.delete(path); } + /** + * @param db + * the repository + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @return the path to the symbolic link + * @throws Exception + * @since 4.2 + */ public static Path writeLink(Repository db, String link, String target) throws Exception { return FileUtils.createSymLink(new File(db.getWorkTree(), link), diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java index bb5f9efb8..4d713b5e7 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/LocalDiskRepositoryTestCase.java @@ -91,7 +91,10 @@ public abstract class LocalDiskRepositoryTestCase { /** A fake (but stable) identity for committer fields in the test. */ protected PersonIdent committer; - /** A {@link SystemReader} used to coordinate time, envars, etc. */ + /** + * A {@link SystemReader} used to coordinate time, envars, etc. + * @since 4.2 + */ protected MockSystemReader mockSystemReader; private final List toClose = new ArrayList(); diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java index 03a2b1a58..28a95569e 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/MockSystemReader.java @@ -151,6 +151,7 @@ public class MockSystemReader extends SystemReader { * * @param secDelta * number of seconds to add to the current time. + * @since 4.2 */ public void tick(final int secDelta) { now += secDelta * 1000L; diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java index 28c61778c..c649eb908 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/RepositoryTestCase.java @@ -108,6 +108,17 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { return JGitTestUtil.writeTrashFile(db, name, data); } + /** + * Create a symbolic link + * + * @param link + * the path of the symbolic link to create + * @param target + * the target of the symbolic link + * @return the path to the symbolic link + * @throws Exception + * @since 4.2 + */ protected Path writeLink(final String link, final String target) throws Exception { return JGitTestUtil.writeLink(db, link, target); @@ -271,6 +282,19 @@ public abstract class RepositoryTestCase extends LocalDiskRepositoryTestCase { return name; } + /** + * Replaces '\' by '/' + * + * @param str + * the string in which backslashes should be replaced + * @return the resulting string with slashes + * @since 4.2 + */ + public static String slashify(String str) { + str = str.replace('\\', '/'); + return str; + } + /** * Waits until it is guaranteed that a subsequent file modification has a * younger modification timestamp than the modification timestamp of the diff --git a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java index 251e65f55..ac9685d37 100644 --- a/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java +++ b/org.eclipse.jgit.junit/src/org/eclipse/jgit/junit/TestRepository.java @@ -175,6 +175,7 @@ public class TestRepository { * the MockSystemReader to use for clock and other system * operations. * @throws IOException + * @since 4.2 */ public TestRepository(R db, RevWalk rw, MockSystemReader reader) throws IOException { @@ -203,7 +204,10 @@ public class TestRepository { return git; } - /** @return current date. */ + /** + * @return current date. + * @since 4.2 + */ public Date getDate() { return new Date(mockSystemReader.getCurrentTime()); } diff --git a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java7).launch b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java7).launch index 600ce7b72..3df0dcb64 100644 --- a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java7).launch +++ b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java7).launch @@ -15,12 +15,6 @@ - - - - - - diff --git a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8).launch b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8).launch index 1b61d3d3f..ce473ed03 100644 --- a/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8).launch +++ b/org.eclipse.jgit.pgm.test/org.eclipse.jgit.pgm--All-Tests (Java8).launch @@ -19,7 +19,6 @@ - diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java index 7bf4c26c3..939a9f6fd 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/CheckoutTest.java @@ -198,7 +198,8 @@ public class CheckoutTest extends CLIRepositoryTestCase { assertStringArrayEquals("Switched to a new branch 'new_branch'", execute("git checkout --orphan new_branch")); - assertEquals("refs/heads/new_branch", db.getRef("HEAD").getTarget().getName()); + assertEquals("refs/heads/new_branch", + db.exactRef("HEAD").getTarget().getName()); RevCommit commit = git.commit().setMessage("orphan commit").call(); assertEquals(0, commit.getParentCount()); } diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java new file mode 100644 index 000000000..58e0e19f8 --- /dev/null +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/RemoteTest.java @@ -0,0 +1,159 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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.pgm; + + +import static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.lib.CLIRepositoryTestCase; +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; +import org.junit.Before; +import org.junit.Test; + +public class RemoteTest extends CLIRepositoryTestCase { + + private StoredConfig config; + + private RemoteConfig remote; + + @Before + @Override + public void setUp() throws Exception { + super.setUp(); + + // create another repository + Repository remoteRepository = createWorkRepository(); + + // set it up as a remote to this repository + config = db.getConfig(); + remote = new RemoteConfig(config, "test"); + remote.addFetchRefSpec( + new RefSpec("+refs/heads/*:refs/remotes/test/*")); + URIish uri = new URIish( + remoteRepository.getDirectory().toURI().toURL()); + remote.addURI(uri); + remote.update(config); + config.save(); + + Git.wrap(remoteRepository).commit().setMessage("initial commit").call(); + } + + @Test + public void testList() throws Exception { + assertArrayEquals(new String[] { remote.getName(), "" }, + execute("git remote")); + } + + @Test + public void testVerboseList() throws Exception { + assertArrayEquals( + new String[] { + String.format("%s\t%s (fetch)", remote.getName(), + remote.getURIs().get(0)), + String.format("%s\t%s (push)", remote.getName(), + remote.getURIs().get(0)), + "" }, + execute("git remote -v")); + } + + @Test + public void testAdd() throws Exception { + assertArrayEquals(new String[] { "" }, + execute("git remote add second git://test.com/second")); + + List remotes = RemoteConfig.getAllRemoteConfigs(config); + assertEquals(2, remotes.size()); + assertEquals("second", remotes.get(0).getName()); + assertEquals("test", remotes.get(1).getName()); + } + + @Test + public void testRemove() throws Exception { + assertArrayEquals(new String[] { "" }, + execute("git remote remove test")); + + assertTrue(RemoteConfig.getAllRemoteConfigs(config).isEmpty()); + } + + @Test + public void testSetUrl() throws Exception { + assertArrayEquals(new String[] { "" }, + execute("git remote set-url test git://test.com/test")); + + RemoteConfig result = new RemoteConfig(config, "test"); + assertEquals("test", result.getName()); + assertArrayEquals(new URIish[] { new URIish("git://test.com/test") }, + result.getURIs().toArray()); + assertTrue(result.getPushURIs().isEmpty()); + } + + @Test + public void testSetUrlPush() throws Exception { + assertArrayEquals(new String[] { "" }, + execute("git remote set-url --push test git://test.com/test")); + + RemoteConfig result = new RemoteConfig(config, "test"); + assertEquals("test", result.getName()); + assertEquals(remote.getURIs(), result.getURIs()); + assertArrayEquals(new URIish[] { new URIish("git://test.com/test") }, + result.getPushURIs().toArray()); + } + + @Test + public void testUpdate() throws Exception { + assertArrayEquals(new String[] { + "From " + remote.getURIs().get(0).toString(), + " * [new branch] master -> test/master", "", "" }, + execute("git remote update test")); + } + +} diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java index 0785c5c5f..dae477928 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/ResetTest.java @@ -67,7 +67,7 @@ public class ResetTest extends CLIRepositoryTestCase { assertStringArrayEquals("", execute("git reset --hard " + commit.getId().name())); assertEquals(commit.getId(), - git.getRepository().getRef("HEAD").getObjectId()); + git.getRepository().exactRef("HEAD").getObjectId()); } @Test @@ -77,7 +77,7 @@ public class ResetTest extends CLIRepositoryTestCase { assertStringArrayEquals("", execute("git reset --hard " + commit.getId().name())); assertEquals(commit.getId(), - git.getRepository().getRef("HEAD").getObjectId()); + git.getRepository().exactRef("HEAD").getObjectId()); } @Test @@ -86,7 +86,7 @@ public class ResetTest extends CLIRepositoryTestCase { assertStringArrayEquals("", execute("git reset --hard " + commit.getId().name() + " --")); assertEquals(commit.getId(), - git.getRepository().getRef("HEAD").getObjectId()); + git.getRepository().exactRef("HEAD").getObjectId()); } @Test @@ -119,7 +119,7 @@ public class ResetTest extends CLIRepositoryTestCase { (useDoubleDash) ? " --" : ""); assertStringArrayEquals("", execute(cmd)); assertEquals(commit.getId(), - git.getRepository().getRef("HEAD").getObjectId()); + git.getRepository().exactRef("HEAD").getObjectId()); org.eclipse.jgit.api.Status status = git.status().call(); // assert that file a is unstaged diff --git a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java index 793fc7daf..854c52d88 100644 --- a/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java +++ b/org.eclipse.jgit.pgm.test/tst/org/eclipse/jgit/pgm/StatusTest.java @@ -42,13 +42,14 @@ */ package org.eclipse.jgit.pgm; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.R_HEADS; import java.io.IOException; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.CLIRepositoryTestCase; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.revwalk.RevCommit; import org.junit.Test; @@ -254,7 +255,7 @@ public class StatusTest extends CLIRepositoryTestCase { } private void detachHead(Git git) throws IOException, GitAPIException { - String commitId = db.getRef(Constants.MASTER).getObjectId().name(); + String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name(); git.checkout().setName(commitId).call(); } diff --git a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin index e1b05491b..c13f63e80 100644 --- a/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin +++ b/org.eclipse.jgit.pgm/META-INF/services/org.eclipse.jgit.pgm.TextBuiltin @@ -24,6 +24,7 @@ org.eclipse.jgit.pgm.MergeBase org.eclipse.jgit.pgm.Push org.eclipse.jgit.pgm.ReceivePack org.eclipse.jgit.pgm.Reflog +org.eclipse.jgit.pgm.Remote org.eclipse.jgit.pgm.Repo org.eclipse.jgit.pgm.Reset org.eclipse.jgit.pgm.RevList diff --git a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties index 64afdad51..335336da2 100644 --- a/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties +++ b/org.eclipse.jgit.pgm/resources/org/eclipse/jgit/pgm/internal/CLIText.properties @@ -128,6 +128,7 @@ metaVar_user=USER metaVar_version=VERSION mostCommonlyUsedCommandsAre=The most commonly used commands are: needApprovalToDestroyCurrentRepository=Need approval to destroy current repository +needSingleRevision=Needed a single revision noGitRepositoryConfigured=No Git repository configured. noNamesFound=No names found, cannot describe anything. noSuchFile=no such file: {0} @@ -142,6 +143,7 @@ notAJgitCommand={0} is not a jgit command notARevision=Not a revision: {0} notATree={0} is not a tree notAValidRefName={0} is not a valid ref name +notAValidCommitName={0} is not a valid commit name notAnIndexFile={0} is not an index file notAnObject={0} is not an object notFound=!! NOT FOUND !! @@ -186,6 +188,7 @@ treeIsRequired=argument tree is required tooManyRefsGiven=Too many refs given unknownIoErrorStdout=An unknown I/O error occurred on standard output unknownMergeStrategy=unknown merge strategy {0} specified +unknownSubcommand=Unknown subcommand: {0} unmergedPaths=Unmerged paths: unsupportedOperation=Unsupported operation: {0} untrackedFiles=Untracked files: @@ -220,6 +223,7 @@ usage_MergeBase=Find as good common ancestors as possible for a merge usage_MergesTwoDevelopmentHistories=Merges two development histories usage_ReadDirCache= Read the DirCache 100 times usage_RebuildCommitGraph=Recreate a repository from another one's commit graph +usage_Remote=Manage set of tracked repositories usage_RepositoryToReadFrom=Repository to read from usage_RepositoryToReceiveInto=Repository to receive into usage_RevList=List commit objects in reverse chronological order @@ -327,6 +331,7 @@ usage_performFsckStyleChecksOnReceive=perform fsck style checks on receive usage_portNumberToListenOn=port number to listen on usage_printOnlyBranchesThatContainTheCommit=print only branches that contain the commit usage_pruneStaleTrackingRefs=prune stale tracking refs +usage_pushUrls=push URLs are manipulated usage_quiet=don't show progress messages usage_recordChangesToRepository=Record changes to the repository usage_recurseIntoSubtrees=recurse into subtrees diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java index 83a1ca7e2..65aa24f35 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Branch.java @@ -154,10 +154,14 @@ class Branch extends TextBuiltin { startBranch = Constants.HEAD; Ref startRef = db.getRef(startBranch); ObjectId startAt = db.resolve(startBranch + "^0"); //$NON-NLS-1$ - if (startRef != null) + if (startRef != null) { startBranch = startRef.getName(); - else + } else if (startAt != null) { startBranch = startAt.name(); + } else { + throw die(MessageFormat.format( + CLIText.get().notAValidCommitName, startBranch)); + } startBranch = Repository.shortenRefName(startBranch); String newRefName = newHead; if (!newRefName.startsWith(Constants.R_HEADS)) @@ -249,7 +253,7 @@ class Branch extends TextBuiltin { String current = db.getBranch(); ObjectId head = db.resolve(Constants.HEAD); for (String branch : branches) { - if (current.equals(branch)) { + if (branch.equals(current)) { throw die(MessageFormat.format(CLIText.get().cannotDeleteTheBranchWhichYouAreCurrentlyOn, branch)); } RefUpdate update = db.updateRef((remote ? Constants.R_REMOTES diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java index f18242d68..38d8d70ce 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Commit.java @@ -96,6 +96,9 @@ class Commit extends TextBuiltin { commitCmd.setAmend(amend); commitCmd.setAll(all); Ref head = db.getRef(Constants.HEAD); + if (head == null) { + throw die(CLIText.get().onBranchToBeBorn); + } RevCommit commit; try { commit = commitCmd.call(); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java index e0ff0583c..cd65af954 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Merge.java @@ -120,7 +120,7 @@ class Merge extends TextBuiltin { throw die(MessageFormat.format( CLIText.get().refDoesNotExistOrNoCommit, ref)); - Ref oldHead = db.getRef(Constants.HEAD); + Ref oldHead = getOldHead(); MergeResult result; try (Git git = new Git(db)) { MergeCommand mergeCmd = git.merge().setStrategy(mergeStrategy) @@ -205,6 +205,14 @@ class Merge extends TextBuiltin { } } + private Ref getOldHead() throws IOException { + Ref oldHead = db.getRef(Constants.HEAD); + if (oldHead == null) { + throw die(CLIText.get().onBranchToBeBorn); + } + return oldHead; + } + private boolean isMergedInto(Ref oldHead, AnyObjectId src) throws IOException { try (RevWalk revWalk = new RevWalk(db)) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java index 1879ef51f..9098c1263 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Push.java @@ -82,6 +82,9 @@ class Push extends TextBuiltin { @Option(name = "--all") private boolean all; + @Option(name = "--atomic") + private boolean atomic; + @Option(name = "--tags") private boolean tags; @@ -122,6 +125,7 @@ class Push extends TextBuiltin { push.setPushTags(); push.setRemote(remote); push.setThin(thin); + push.setAtomic(atomic); push.setTimeout(timeout); Iterable results = push.call(); for (PushResult result : results) { diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java new file mode 100644 index 000000000..70868e920 --- /dev/null +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Remote.java @@ -0,0 +1,197 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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.pgm; + +import java.io.IOException; +import java.io.StringWriter; +import java.text.MessageFormat; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.RemoteAddCommand; +import org.eclipse.jgit.api.RemoteListCommand; +import org.eclipse.jgit.api.RemoteRemoveCommand; +import org.eclipse.jgit.api.RemoteSetUrlCommand; +import org.eclipse.jgit.api.errors.JGitInternalException; +import org.eclipse.jgit.pgm.internal.CLIText; +import org.eclipse.jgit.pgm.opt.CmdLineParser; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.eclipse.jgit.util.io.ThrowingPrintWriter; +import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.Option; + +@Command(common = false, usage = "usage_Remote") +class Remote extends TextBuiltin { + + @Option(name = "--verbose", aliases = { "-v" }, usage = "usage_beVerbose") + private boolean verbose = false; + + @Option(name = "--prune", aliases = { + "-p" }, usage = "usage_pruneStaleTrackingRefs") + private boolean prune; + + @Option(name = "--push", usage = "usage_pushUrls") + private boolean push; + + @Argument(index = 0, metaVar = "metaVar_command") + private String command; + + @Argument(index = 1, metaVar = "metaVar_remoteName") + private String name; + + @Argument(index = 2, metaVar = "metaVar_uriish") + private String uri; + + @Override + protected void run() throws Exception { + try (Git git = new Git(db)) { + if (command == null) { + RemoteListCommand cmd = git.remoteList(); + List remotes = cmd.call(); + print(remotes); + } else if ("add".equals(command)) { //$NON-NLS-1$ + RemoteAddCommand cmd = git.remoteAdd(); + cmd.setName(name); + cmd.setUri(new URIish(uri)); + cmd.call(); + } else if ("remove".equals(command) || "rm".equals(command)) { //$NON-NLS-1$ //$NON-NLS-2$ + RemoteRemoveCommand cmd = git.remoteRemove(); + cmd.setName(name); + cmd.call(); + } else if ("set-url".equals(command)) { //$NON-NLS-1$ + RemoteSetUrlCommand cmd = git.remoteSetUrl(); + cmd.setName(name); + cmd.setUri(new URIish(uri)); + cmd.setPush(push); + cmd.call(); + } else if ("update".equals(command)) { //$NON-NLS-1$ + // reuse fetch command for basic implementation of remote update + Fetch fetch = new Fetch(); + fetch.init(db, gitdir); + + // redirect the output stream + StringWriter osw = new StringWriter(); + fetch.outw = new ThrowingPrintWriter(osw); + // redirect the error stream + StringWriter esw = new StringWriter(); + fetch.errw = new ThrowingPrintWriter(esw); + + List fetchArgs = new ArrayList<>(); + if (verbose) { + fetchArgs.add("--verbose"); //$NON-NLS-1$ + } + if (prune) { + fetchArgs.add("--prune"); //$NON-NLS-1$ + } + if (name != null) { + fetchArgs.add(name); + } + + fetch.execute(fetchArgs.toArray(new String[fetchArgs.size()])); + + // flush the streams + fetch.outw.flush(); + fetch.errw.flush(); + outw.println(osw.toString()); + errw.println(esw.toString()); + } else { + throw new JGitInternalException(MessageFormat + .format(CLIText.get().unknownSubcommand, command)); + } + } + } + + @Override + public void printUsageAndExit(final String message, final CmdLineParser clp) + throws IOException { + errw.println(message); + errw.println("jgit remote [--verbose (-v)] [--help (-h)]"); //$NON-NLS-1$ + errw.println("jgit remote add name uri-ish [--help (-h)]"); //$NON-NLS-1$ + errw.println("jgit remote remove name [--help (-h)]"); //$NON-NLS-1$ + errw.println("jgit remote rm name [--help (-h)]"); //$NON-NLS-1$ + errw.println( + "jgit remote [--verbose (-v)] update [name] [--prune (-p)] [--help (-h)]"); //$NON-NLS-1$ + errw.println("jgit remote set-url name uri-ish [--push] [--help (-h)]"); //$NON-NLS-1$ + + errw.println(); + clp.printUsage(errw, getResourceBundle()); + errw.println(); + + errw.flush(); + throw die(true); + } + + private void print(List remotes) throws IOException { + for (RemoteConfig remote : remotes) { + String remoteName = remote.getName(); + if (verbose) { + List fetchURIs = remote.getURIs(); + List pushURIs = remote.getPushURIs(); + + String fetchURI = ""; //$NON-NLS-1$ + if (!fetchURIs.isEmpty()) { + fetchURI = fetchURIs.get(0).toString(); + } else if (!pushURIs.isEmpty()) { + fetchURI = pushURIs.get(0).toString(); + } + + String pushURI = ""; //$NON-NLS-1$ + if (!pushURIs.isEmpty()) { + pushURI = pushURIs.get(0).toString(); + } else if (!fetchURIs.isEmpty()) { + pushURI = fetchURIs.get(0).toString(); + } + + outw.println( + String.format("%s\t%s (fetch)", remoteName, fetchURI)); //$NON-NLS-1$ + outw.println( + String.format("%s\t%s (push)", remoteName, pushURI)); //$NON-NLS-1$ + } else { + outw.println(remoteName); + } + } + } + +} diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java index 5530ac5c9..4456fd534 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/RevParse.java @@ -1,6 +1,7 @@ /* * Copyright (C) 2009, Daniel Cheng (aka SDiZ) * Copyright (C) 2009, Daniel Cheng (aka SDiZ) + * Copyright (C) 2015 Thomas Meyer * and other copyright owners as documented in the project's IP log. * * This program and the accompanying materials are made available @@ -51,14 +52,19 @@ import java.util.List; import java.util.Map; import org.kohsuke.args4j.Argument; +import org.kohsuke.args4j.CmdLineException; import org.kohsuke.args4j.Option; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.Ref; +import org.eclipse.jgit.pgm.internal.CLIText;; @Command(usage = "usage_RevParse") class RevParse extends TextBuiltin { @Option(name = "--all", usage = "usage_RevParseAll") - boolean all = false; + boolean all; + + @Option(name = "--verify", usage = "usage_RevParseVerify") + boolean verify; @Argument(index = 0, metaVar = "metaVar_commitish") private final List commits = new ArrayList(); @@ -67,11 +73,17 @@ class RevParse extends TextBuiltin { protected void run() throws Exception { if (all) { Map allRefs = db.getRefDatabase().getRefs(ALL); - for (final Ref r : allRefs.values()) + for (final Ref r : allRefs.values()) { outw.println(r.getObjectId().name()); + } } else { - for (final ObjectId o : commits) + if (verify && commits.size() > 1) { + throw new CmdLineException(CLIText.get().needSingleRevision); + } + + for (final ObjectId o : commits) { outw.println(o.name()); + } } } } diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java index df7ebb78b..d85698901 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/DiffAlgorithms.java @@ -242,9 +242,10 @@ class DiffAlgorithms extends TextBuiltin { } }); - if (db.getDirectory() != null) { - String name = db.getDirectory().getName(); - File parent = db.getDirectory().getParentFile(); + File directory = db.getDirectory(); + if (directory != null) { + String name = directory.getName(); + File parent = directory.getParentFile(); if (name.equals(Constants.DOT_GIT) && parent != null) name = parent.getName(); outw.println(name + ": start at " + startId.name()); diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java index 494055a26..6260cd99f 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/RebuildCommitGraph.java @@ -117,9 +117,12 @@ class RebuildCommitGraph extends TextBuiltin { @Override protected void run() throws Exception { if (!really && !db.getRefDatabase().getRefs(ALL).isEmpty()) { + File directory = db.getDirectory(); + String absolutePath = directory == null ? "null" //$NON-NLS-1$ + : directory.getAbsolutePath(); errw.println( MessageFormat.format(CLIText.get().fatalThisProgramWillDestroyTheRepository - , db.getDirectory().getAbsolutePath(), REALLY)); + , absolutePath, REALLY)); throw die(CLIText.get().needApprovalToDestroyCurrentRepository); } if (!refList.isFile()) diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java index dcbc37bed..887ad08af 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/debug/TextHashFunctions.java @@ -341,9 +341,10 @@ class TextHashFunctions extends TextBuiltin { } } - if (db.getDirectory() != null) { - String name = db.getDirectory().getName(); - File parent = db.getDirectory().getParentFile(); + File directory = db.getDirectory(); + if (directory != null) { + String name = directory.getName(); + File parent = directory.getParentFile(); if (name.equals(Constants.DOT_GIT) && parent != null) name = parent.getName(); outw.println(name + ":"); //$NON-NLS-1$ diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java index 433ddf2b1..f5d581ad0 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/internal/CLIText.java @@ -187,6 +187,7 @@ public class CLIText extends TranslationBundle { /***/ public String metaVar_version; /***/ public String mostCommonlyUsedCommandsAre; /***/ public String needApprovalToDestroyCurrentRepository; + /***/ public String needSingleRevision; /***/ public String noGitRepositoryConfigured; /***/ public String noNamesFound; /***/ public String noSuchFile; @@ -201,6 +202,7 @@ public class CLIText extends TranslationBundle { /***/ public String notARevision; /***/ public String notATree; /***/ public String notAValidRefName; + /***/ public String notAValidCommitName; /***/ public String notAnIndexFile; /***/ public String notAnObject; /***/ public String notFound; @@ -245,6 +247,7 @@ public class CLIText extends TranslationBundle { /***/ public String treeIsRequired; /***/ public char[] unknownIoErrorStdout; /***/ public String unknownMergeStrategy; + /***/ public String unknownSubcommand; /***/ public String unmergedPaths; /***/ public String unsupportedOperation; /***/ public String untrackedFiles; diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 7).launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 7).launch index af009c0d4..a83fabb75 100644 --- a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 7).launch +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 7).launch @@ -18,7 +18,6 @@ - diff --git a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8).launch b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8).launch index 04aa3ea10..b221a11a5 100644 --- a/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8).launch +++ b/org.eclipse.jgit.test/org.eclipse.jgit.core--All-Tests (Java 8).launch @@ -19,7 +19,6 @@ - diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java new file mode 100644 index 000000000..d6a6342cb --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AbstractRemoteCommandTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.net.URISyntaxException; + +import org.eclipse.jgit.junit.RepositoryTestCase; +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; + +public class AbstractRemoteCommandTest extends RepositoryTestCase { + + protected static final String REMOTE_NAME = "test"; + + protected RemoteConfig setupRemote() + throws IOException, URISyntaxException { + // create another repository + Repository remoteRepository = createWorkRepository(); + + // set it up as a remote to this repository + final StoredConfig config = db.getConfig(); + RemoteConfig remoteConfig = new RemoteConfig(config, REMOTE_NAME); + + RefSpec refSpec = new RefSpec(); + refSpec = refSpec.setForceUpdate(true); + refSpec = refSpec.setSourceDestination(Constants.R_HEADS + "*", + Constants.R_REMOTES + REMOTE_NAME + "/*"); + remoteConfig.addFetchRefSpec(refSpec); + + URIish uri = new URIish( + remoteRepository.getDirectory().toURI().toURL()); + remoteConfig.addURI(uri); + + remoteConfig.update(config); + config.save(); + + return remoteConfig; + } + + protected void assertRemoteConfigEquals(RemoteConfig expected, + RemoteConfig actual) { + assertEquals(expected.getName(), actual.getName()); + assertEquals(expected.getURIs(), actual.getURIs()); + assertEquals(expected.getPushURIs(), actual.getPushURIs()); + assertEquals(expected.getFetchRefSpecs(), actual.getFetchRefSpecs()); + assertEquals(expected.getPushRefSpecs(), actual.getPushRefSpecs()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java index 2abed3adc..a5ad18d10 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/AddCommandTest.java @@ -45,6 +45,7 @@ package org.eclipse.jgit.api; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import java.io.File; @@ -52,11 +53,13 @@ import java.io.FileInputStream; import java.io.IOException; import java.io.PrintWriter; +import org.eclipse.jgit.api.errors.FilterFailedException; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.NoFilepatternException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheBuilder; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; @@ -111,6 +114,191 @@ public class AddCommandTest extends RepositoryTestCase { indexState(CONTENT)); } + @Test + public void testCleanFilter() throws IOException, + GitAPIException { + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .call(); + + assertEquals( + "[src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:fee\n]", + indexState(CONTENT)); + } + + @Test + public void testCleanFilterEnvironment() + throws IOException, GitAPIException { + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + writeTrashFile("src/a.txt", "foo"); + File script = writeTempFile("echo $GIT_DIR; echo 1 >xyz"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + git.add().addFilepattern("src/a.txt").call(); + + String gitDir = db.getDirectory().getAbsolutePath(); + assertEquals("[src/a.txt, mode:100644, content:" + gitDir + + "\n]", indexState(CONTENT)); + assertTrue(new File(db.getWorkTree(), "xyz").exists()); + } + + @Test + public void testMultipleCleanFilter() throws IOException, GitAPIException { + writeTrashFile(".gitattributes", + "*.txt filter=tstFilter\n*.tmp filter=tstFilter2"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.tmp", "foo\n"); + writeTrashFile("src/a.txt", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + File script2 = writeTempFile("sed s/f/x/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.setString("filter", "tstFilter2", "clean", + "sh " + slashify(script2.getPath())); + config.save(); + + git.add().addFilepattern("src/a.txt").addFilepattern("src/a.tmp") + .call(); + + assertEquals( + "[src/a.tmp, mode:100644, content:xoo\n][src/a.txt, mode:100644, content:fee\n]", + indexState(CONTENT)); + + // TODO: multiple clean filters for one file??? + } + + /** + * The path of an added file name contains ';' and afterwards malicious + * commands. Make sure when calling filter commands to properly escape the + * filenames + * + * @throws IOException + * @throws GitAPIException + */ + @Test + public void testCommandInjection() throws IOException, GitAPIException { + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("; echo virus", "foo\n"); + File script = writeTempFile("sed s/o/e/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath()) + " %f"); + writeTrashFile(".gitattributes", "* filter=tstFilter"); + + git.add().addFilepattern("; echo virus").call(); + // Without proper escaping the content would be "feovirus". The sed + // command and the "echo virus" would contribute to the content + assertEquals("[; echo virus, mode:100644, content:fee\n]", + indexState(CONTENT)); + } + + @Test + public void testBadCleanFilter() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sedfoo s/o/e/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(127, e.getReturnCode()); + } + } + + @Test + public void testBadCleanFilter2() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sed s/o/e/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "shfoo " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(127, e.getReturnCode()); + } + } + + @Test + public void testCleanFilterReturning12() throws IOException, + GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("exit 12"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(script.getPath())); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + try { + git.add().addFilepattern("a.txt").call(); + fail("Didn't received the expected exception"); + } catch (FilterFailedException e) { + assertEquals(12, e.getReturnCode()); + } + } + + @Test + public void testNotApplicableFilter() throws IOException, GitAPIException { + writeTrashFile("a.txt", "foo"); + File script = writeTempFile("sed s/o/e/g"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "something", + "sh " + script.getPath()); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + + git.add().addFilepattern("a.txt").call(); + + assertEquals("[a.txt, mode:100644, content:foo]", indexState(CONTENT)); + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("AddCommandTest_", ""); + JGitTestUtil.write(f, body); + return f; + } + @Test public void testAddExistingSingleSmallFileWithNewLine() throws IOException, GitAPIException { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java index f7a50dffc..4bfb128cb 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CheckoutCommandTest.java @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.Assert.assertEquals; @@ -70,12 +72,14 @@ import org.eclipse.jgit.api.errors.RefNotFoundException; import org.eclipse.jgit.api.errors.TransportException; import org.eclipse.jgit.dircache.DirCache; import org.eclipse.jgit.dircache.DirCacheEntry; +import org.eclipse.jgit.junit.JGitTestUtil; import org.eclipse.jgit.junit.RepositoryTestCase; import org.eclipse.jgit.lib.ConfigConstants; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.lib.Sets; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevCommit; import org.eclipse.jgit.storage.file.FileBasedConfig; @@ -84,6 +88,7 @@ import org.eclipse.jgit.transport.RemoteConfig; import org.eclipse.jgit.transport.URIish; import org.eclipse.jgit.util.FileUtils; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; public class CheckoutCommandTest extends RepositoryTestCase { @@ -134,7 +139,7 @@ public class CheckoutCommandTest extends RepositoryTestCase { @Test public void testCreateBranchOnCheckout() throws Exception { git.checkout().setCreateBranch(true).setName("test2").call(); - assertNotNull(db.getRef("test2")); + assertNotNull(db.exactRef("refs/heads/test2")); } @Test @@ -237,8 +242,8 @@ public class CheckoutCommandTest extends RepositoryTestCase { .setStartPoint("origin/test") .setUpstreamMode(SetupUpstreamMode.TRACK).call(); - assertEquals("refs/heads/test", db2.getRef(Constants.HEAD).getTarget() - .getName()); + assertEquals("refs/heads/test", + db2.exactRef(Constants.HEAD).getTarget().getName()); StoredConfig config = db2.getConfig(); assertEquals("origin", config.getString( ConfigConstants.CONFIG_BRANCH_SECTION, "test", @@ -345,7 +350,7 @@ public class CheckoutCommandTest extends RepositoryTestCase { CheckoutCommand co = git.checkout(); co.setName("master").call(); - String commitId = db.getRef(Constants.MASTER).getObjectId().name(); + String commitId = db.exactRef(R_HEADS + MASTER).getObjectId().name(); co = git.checkout(); co.setName(commitId).call(); @@ -443,7 +448,7 @@ public class CheckoutCommandTest extends RepositoryTestCase { } private void assertHeadDetached() throws IOException { - Ref head = db.getRef(Constants.HEAD); + Ref head = db.exactRef(Constants.HEAD); assertFalse(head.isSymbolic()); assertSame(head, head.getTarget()); } @@ -554,4 +559,125 @@ public class CheckoutCommandTest extends RepositoryTestCase { } org.junit.Assume.assumeTrue(foundUnsmudged); } + + @Test + public void testSmudgeFilter_modifyExisting() throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "x"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "x\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content1 = git.commit().setMessage("add content").call(); + + writeTrashFile("src/a.tmp", "foo"); + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content2 = git.commit().setMessage("changed content").call(); + + git.checkout().setName(content1.getName()).call(); + git.checkout().setName(content2.getName()).call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals(Sets.of("src/a.txt"), git.status().call().getModified()); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + public void testSmudgeFilter_createNew() + throws IOException, GitAPIException { + File script = writeTempFile("sed s/o/e/g"); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "smudge", + "sh " + slashify(script.getPath())); + config.save(); + + writeTrashFile("foo", "foo"); + git.add().addFilepattern("foo").call(); + RevCommit initial = git.commit().setMessage("initial").call(); + + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add filter").call(); + + writeTrashFile("src/a.tmp", "foo"); + // Caution: we need a trailing '\n' since sed on mac always appends + // linefeeds if missing + writeTrashFile("src/a.txt", "foo\n"); + git.add().addFilepattern("src/a.tmp").addFilepattern("src/a.txt") + .call(); + RevCommit content = git.commit().setMessage("added content").call(); + + git.checkout().setName(initial.getName()).call(); + git.checkout().setName(content.getName()).call(); + + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some change][foo, mode:100644, content:foo][src/a.tmp, mode:100644, content:foo][src/a.txt, mode:100644, content:foo\n]", + indexState(CONTENT)); + assertEquals("foo", read("src/a.tmp")); + assertEquals("fee\n", read("src/a.txt")); + } + + @Test + @Ignore + public void testSmudgeAndClean() throws IOException, GitAPIException { + // @TODO: fix this test + File clean_filter = writeTempFile("sed s/V1/@version/g -"); + File smudge_filter = writeTempFile("sed s/@version/V1/g -"); + + Git git = new Git(db); + StoredConfig config = git.getRepository().getConfig(); + config.setString("filter", "tstFilter", "smudge", + "sh " + slashify(smudge_filter.getPath())); + config.setString("filter", "tstFilter", "clean", + "sh " + slashify(clean_filter.getPath())); + config.save(); + writeTrashFile(".gitattributes", "*.txt filter=tstFilter"); + git.add().addFilepattern(".gitattributes").call(); + git.commit().setMessage("add attributes").call(); + + writeTrashFile("filterTest.txt", "hello world, V1"); + git.add().addFilepattern("filterTest.txt").call(); + git.commit().setMessage("add filterText.txt").call(); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]", + indexState(CONTENT)); + + git.checkout().setCreateBranch(true).setName("test2").call(); + writeTrashFile("filterTest.txt", "bon giorno world, V1"); + git.add().addFilepattern("filterTest.txt").call(); + git.commit().setMessage("modified filterText.txt").call(); + + assertTrue(git.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:bon giorno world, @version]", + indexState(CONTENT)); + + git.checkout().setName("refs/heads/test").call(); + assertTrue(git.status().call().isClean()); + assertEquals( + "[.gitattributes, mode:100644, content:*.txt filter=tstFilter][Test.txt, mode:100644, content:Some other change][filterTest.txt, mode:100644, content:hello world, @version]", + indexState(CONTENT)); + assertEquals("hello world, V1", read("filterTest.txt")); + } + + private File writeTempFile(String body) throws IOException { + File f = File.createTempFile("AddCommandTest_", ""); + JGitTestUtil.write(f, body); + return f; + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java index 060a5b65c..0d03047d5 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CommitCommandTest.java @@ -408,8 +408,10 @@ public class CommitCommandTest extends RepositoryTestCase { checkoutBranch("refs/heads/master"); - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java index 0c0c6e5b5..cea8393ff 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/MergeCommandTest.java @@ -43,6 +43,8 @@ */ package org.eclipse.jgit.api; +import static org.eclipse.jgit.lib.Constants.MASTER; +import static org.eclipse.jgit.lib.Constants.R_HEADS; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNull; @@ -97,7 +99,7 @@ public class MergeCommandTest extends RepositoryTestCase { Git git = new Git(db); git.commit().setMessage("initial commit").call(); - MergeResult result = git.merge().include(db.getRef(Constants.HEAD)).call(); + MergeResult result = git.merge().include(db.exactRef(Constants.HEAD)).call(); assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); // no reflog entry written by merge assertEquals("commit (initial): initial commit", @@ -115,7 +117,7 @@ public class MergeCommandTest extends RepositoryTestCase { createBranch(first, "refs/heads/branch1"); RevCommit second = git.commit().setMessage("second commit").call(); - MergeResult result = git.merge().include(db.getRef("refs/heads/branch1")).call(); + MergeResult result = git.merge().include(db.exactRef("refs/heads/branch1")).call(); assertEquals(MergeResult.MergeStatus.ALREADY_UP_TO_DATE, result.getMergeStatus()); assertEquals(second, result.getNewHead()); // no reflog entry written by merge @@ -135,7 +137,7 @@ public class MergeCommandTest extends RepositoryTestCase { checkoutBranch("refs/heads/branch1"); - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call(); assertEquals(MergeResult.MergeStatus.FAST_FORWARD, result.getMergeStatus()); assertEquals(second, result.getNewHead()); @@ -155,7 +157,7 @@ public class MergeCommandTest extends RepositoryTestCase { checkoutBranch("refs/heads/branch1"); - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)) + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)) .setCommit(false).call(); assertEquals(MergeResult.MergeStatus.FAST_FORWARD, @@ -186,7 +188,7 @@ public class MergeCommandTest extends RepositoryTestCase { checkoutBranch("refs/heads/branch1"); assertFalse(new File(db.getWorkTree(), "file2").exists()); - MergeResult result = git.merge().include(db.getRef(Constants.MASTER)).call(); + MergeResult result = git.merge().include(db.exactRef(R_HEADS + MASTER)).call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); @@ -221,7 +223,7 @@ public class MergeCommandTest extends RepositoryTestCase { MergeCommand merge = git.merge(); merge.include(second.getId()); - merge.include(db.getRef(Constants.MASTER)); + merge.include(db.exactRef(R_HEADS + MASTER)); try { merge.call(); fail("Expected exception not thrown when merging multiple heads"); @@ -248,7 +250,7 @@ public class MergeCommandTest extends RepositoryTestCase { git.commit().setMessage("third").call(); MergeResult result = git.merge().setStrategy(mergeStrategy) - .include(db.getRef(Constants.MASTER)).call(); + .include(db.exactRef(R_HEADS + MASTER)).call(); assertEquals(MergeStatus.MERGED, result.getMergeStatus()); assertEquals( "merge refs/heads/master: Merge made by " @@ -279,9 +281,9 @@ public class MergeCommandTest extends RepositoryTestCase { MergeResult result = git.merge().setStrategy(mergeStrategy) .setCommit(false) - .include(db.getRef(Constants.MASTER)).call(); + .include(db.exactRef(R_HEADS + MASTER)).call(); assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); - assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(), + assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(), thirdCommit.getId()); } @@ -378,7 +380,7 @@ public class MergeCommandTest extends RepositoryTestCase { git.add().addFilepattern("a").call(); git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); git.merge().include(sideBranch) .setStrategy(MergeStrategy.RESOLVE).call(); @@ -590,7 +592,7 @@ public class MergeCommandTest extends RepositoryTestCase { .setCommit(false) .setStrategy(MergeStrategy.RESOLVE).call(); assertEquals(MergeStatus.MERGED_NOT_COMMITTED, result.getMergeStatus()); - assertEquals(db.getRef(Constants.HEAD).getTarget().getObjectId(), + assertEquals(db.exactRef(Constants.HEAD).getTarget().getObjectId(), thirdCommit.getId()); assertEquals("1(side)\na\n3(main)\n", read(new File(db.getWorkTree(), @@ -1310,8 +1312,10 @@ public class MergeCommandTest extends RepositoryTestCase { assertFalse(new File(db.getWorkTree(), "file2").exists()); assertFalse(new File(db.getWorkTree(), "file3").exists()); - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); @@ -1375,8 +1379,10 @@ public class MergeCommandTest extends RepositoryTestCase { assertTrue(new File(db.getWorkTree(), "file2").exists()); assertFalse(new File(db.getWorkTree(), "file3").exists()); - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); @@ -1430,8 +1436,10 @@ public class MergeCommandTest extends RepositoryTestCase { assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); - MergeResult result = git.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = git.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertTrue(new File(db.getWorkTree(), "file1").exists()); assertTrue(new File(db.getWorkTree(), "file2").exists()); @@ -1468,7 +1476,7 @@ public class MergeCommandTest extends RepositoryTestCase { MergeCommand merge = git.merge(); merge.setFastForward(FastForwardMode.FF_ONLY); - merge.include(db.getRef(Constants.MASTER)); + merge.include(db.exactRef(R_HEADS + MASTER)); MergeResult result = merge.call(); assertEquals(MergeStatus.FAST_FORWARD, result.getMergeStatus()); @@ -1485,7 +1493,7 @@ public class MergeCommandTest extends RepositoryTestCase { MergeCommand merge = git.merge(); merge.setFastForward(FastForwardMode.NO_FF); - merge.include(db.getRef(Constants.MASTER)); + merge.include(db.exactRef(R_HEADS + MASTER)); MergeResult result = merge.call(); assertEquals(MergeStatus.MERGED, result.getMergeStatus()); @@ -1505,7 +1513,7 @@ public class MergeCommandTest extends RepositoryTestCase { // when MergeCommand merge = git.merge(); merge.setFastForward(FastForwardMode.NO_FF); - merge.include(db.getRef(Constants.MASTER)); + merge.include(db.exactRef(R_HEADS + MASTER)); merge.setCommit(false); MergeResult result = merge.call(); @@ -1531,7 +1539,7 @@ public class MergeCommandTest extends RepositoryTestCase { git.commit().setMessage("second commit on branch1").call(); MergeCommand merge = git.merge(); merge.setFastForward(FastForwardMode.FF_ONLY); - merge.include(db.getRef(Constants.MASTER)); + merge.include(db.exactRef(R_HEADS + MASTER)); MergeResult result = merge.call(); assertEquals(MergeStatus.ABORTED, result.getMergeStatus()); @@ -1588,7 +1596,7 @@ public class MergeCommandTest extends RepositoryTestCase { git.add().addFilepattern("c").call(); git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) .setMessage("user message").call(); @@ -1621,7 +1629,7 @@ public class MergeCommandTest extends RepositoryTestCase { git.add().addFilepattern("a").call(); git.commit().setMessage("main").call(); - Ref sideBranch = db.getRef("side"); + Ref sideBranch = db.exactRef("refs/heads/side"); git.merge().include(sideBranch).setStrategy(MergeStrategy.RESOLVE) .setMessage("user message").call(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java index 491595498..bd62200fc 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/NameRevCommandTest.java @@ -95,9 +95,9 @@ public class NameRevCommandTest extends RepositoryTestCase { tr.update("refs/heads/master", c); tr.update("refs/tags/tag", c); assertOneResult("master", - git.nameRev().addRef(db.getRef("refs/heads/master")), c); + git.nameRev().addRef(db.exactRef("refs/heads/master")), c); assertOneResult("tag", - git.nameRev().addRef(db.getRef("refs/tags/tag")), c); + git.nameRev().addRef(db.exactRef("refs/tags/tag")), c); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java index ccf1a51f1..1fcfef9c7 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PushCommandTest.java @@ -138,11 +138,9 @@ public class PushCommandTest extends RepositoryTestCase { RefSpec spec = new RefSpec("refs/heads/master:refs/heads/x"); git1.push().setRemote("test").setRefSpecs(spec).call(); - assertEquals( - "1:test, 2:file://" + db2.getDirectory().toPath() // - + "/, 3:\n" + "refs/heads/master " + commit.getName() - + " refs/heads/x " + ObjectId.zeroId().name(), - read(hookOutput)); + assertEquals("1:test, 2:" + uri + ", 3:\n" + "refs/heads/master " + + commit.getName() + " refs/heads/x " + + ObjectId.zeroId().name(), read(hookOutput)); } private File writeHookFile(final String name, final String data) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java new file mode 100644 index 000000000..ed0944676 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteAddCommandTest.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.Test; + +public class RemoteAddCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testAdd() throws Exception { + // create another repository + Repository remoteRepository = createWorkRepository(); + URIish uri = new URIish( + remoteRepository.getDirectory().toURI().toURL()); + + // execute the command to add a new remote + RemoteAddCommand cmd = Git.wrap(db).remoteAdd(); + cmd.setName(REMOTE_NAME); + cmd.setUri(uri); + RemoteConfig remote = cmd.call(); + + // assert that the added remote represents the remote repository + assertEquals(REMOTE_NAME, remote.getName()); + assertArrayEquals(new URIish[] { uri }, remote.getURIs().toArray()); + assertEquals(1, remote.getFetchRefSpecs().size()); + assertEquals( + String.format("+refs/heads/*:refs/remotes/%s/*", REMOTE_NAME), + remote.getFetchRefSpecs().get(0).toString()); + + // assert that the added remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java new file mode 100644 index 000000000..7055daff9 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteDeleteCommandTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 static org.junit.Assert.assertTrue; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.junit.Test; + +public class RemoteDeleteCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testDelete() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to remove the remote + RemoteRemoveCommand cmd = Git.wrap(db).remoteRemove(); + cmd.setName(REMOTE_NAME); + RemoteConfig remote = cmd.call(); + + // assert that the removed remote is the initial remote + assertRemoteConfigEquals(remoteConfig, remote); + // assert that there are no remotes left + assertTrue(RemoteConfig.getAllRemoteConfigs(db.getConfig()).isEmpty()); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java new file mode 100644 index 000000000..cf522ff66 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteListCommandTest.java @@ -0,0 +1,68 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 static org.junit.Assert.assertEquals; + +import java.util.List; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.junit.Test; + +public class RemoteListCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testList() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to list the remotes + List remotes = Git.wrap(db).remoteList().call(); + + // assert that there is only one remote + assertEquals(1, remotes.size()); + // assert that the available remote is the initial remote + assertRemoteConfigEquals(remoteConfig, remotes.get(0)); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java new file mode 100644 index 000000000..6969c3df6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RemoteSetUrlCommandTest.java @@ -0,0 +1,100 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 static org.junit.Assert.assertArrayEquals; +import static org.junit.Assert.assertEquals; + +import org.eclipse.jgit.transport.RemoteConfig; +import org.eclipse.jgit.transport.URIish; +import org.junit.Test; + +public class RemoteSetUrlCommandTest extends AbstractRemoteCommandTest { + + @Test + public void testSetUrl() throws Exception { + // setup an initial remote + setupRemote(); + + // execute the command to change the fetch url + RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl(); + cmd.setName(REMOTE_NAME); + URIish newUri = new URIish("git://test.com/test"); + cmd.setUri(newUri); + RemoteConfig remote = cmd.call(); + + // assert that the changed remote has the new fetch url + assertEquals(REMOTE_NAME, remote.getName()); + assertArrayEquals(new URIish[] { newUri }, remote.getURIs().toArray()); + + // assert that the changed remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + + @Test + public void testSetPushUrl() throws Exception { + // setup an initial remote + RemoteConfig remoteConfig = setupRemote(); + + // execute the command to change the push url + RemoteSetUrlCommand cmd = Git.wrap(db).remoteSetUrl(); + cmd.setName(REMOTE_NAME); + URIish newUri = new URIish("git://test.com/test"); + cmd.setUri(newUri); + cmd.setPush(true); + RemoteConfig remote = cmd.call(); + + // assert that the changed remote has the old fetch url and the new push + // url + assertEquals(REMOTE_NAME, remote.getName()); + assertEquals(remoteConfig.getURIs(), remote.getURIs()); + assertArrayEquals(new URIish[] { newUri }, + remote.getPushURIs().toArray()); + + // assert that the changed remote is available in the git configuration + assertRemoteConfigEquals(remote, + new RemoteConfig(db.getConfig(), REMOTE_NAME)); + } + +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java index 9997c8c42..a67f2b912 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/ResetCommandTest.java @@ -477,8 +477,10 @@ public class ResetCommandTest extends RepositoryTestCase { checkoutBranch("refs/heads/master"); - MergeResult result = g.merge().include(db.getRef("branch1")) - .setSquash(true).call(); + MergeResult result = g.merge() + .include(db.exactRef("refs/heads/branch1")) + .setSquash(true) + .call(); assertEquals(MergeResult.MergeStatus.FAST_FORWARD_SQUASHED, result.getMergeStatus()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java index c317e3bee..ae8551e64 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashCreateCommandTest.java @@ -110,7 +110,7 @@ public class StashCreateCommandTest extends RepositoryTestCase { int parentCount) throws IOException { assertNotNull(commit); - Ref stashRef = db.getRef(Constants.R_STASH); + Ref stashRef = db.exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(commit, stashRef.getObjectId()); assertNotNull(commit.getAuthorIdent()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java index cfad817d2..859277e93 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/StashDropCommandTest.java @@ -96,13 +96,13 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropWithInvalidLogIndex() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); - stashRef = git.getRepository().getRef(Constants.R_STASH); - assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + stashRef = git.getRepository().exactRef(Constants.R_STASH); + assertEquals(stashed, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); try { assertNull(git.stashDrop().setStashRef(100).call()); fail("Exception not thrown"); @@ -115,15 +115,15 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropSingleStashedCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit stashed = git.stashCreate().call(); assertNotNull(stashed); - stashRef = git.getRepository().getRef(Constants.R_STASH); - assertEquals(stashed, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + stashRef = git.getRepository().exactRef(Constants.R_STASH); + assertEquals(stashed, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertNull(git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); ReflogReader reader = git.getRepository().getReflogReader( @@ -134,25 +134,25 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropAll() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertNull(git.stashDrop().setAll(true).call()); - assertNull(git.getRepository().getRef(Constants.R_STASH)); + assertNull(git.getRepository().exactRef(Constants.R_STASH)); ReflogReader reader = git.getRepository().getReflogReader( Constants.R_STASH); @@ -162,25 +162,25 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropFirstStashedCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(firstStash, git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(firstStash, stashRef.getObjectId()); @@ -196,33 +196,33 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropMiddleStashCommit() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content4"); RevCommit thirdStash = git.stashCreate().call(); assertNotNull(thirdStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(thirdStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(thirdStash, git.stashDrop().setStashRef(1).call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); @@ -241,46 +241,46 @@ public class StashDropCommandTest extends RepositoryTestCase { @Test public void dropBoundaryStashedCommits() throws Exception { write(committedFile, "content2"); - Ref stashRef = git.getRepository().getRef(Constants.R_STASH); + Ref stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNull(stashRef); RevCommit firstStash = git.stashCreate().call(); assertNotNull(firstStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(firstStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(firstStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content3"); RevCommit secondStash = git.stashCreate().call(); assertNotNull(secondStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(secondStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(secondStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content4"); RevCommit thirdStash = git.stashCreate().call(); assertNotNull(thirdStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(thirdStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(thirdStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); write(committedFile, "content5"); RevCommit fourthStash = git.stashCreate().call(); assertNotNull(fourthStash); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); - assertEquals(fourthStash, git.getRepository().getRef(Constants.R_STASH) - .getObjectId()); + assertEquals(fourthStash, + git.getRepository().exactRef(Constants.R_STASH).getObjectId()); assertEquals(thirdStash, git.stashDrop().call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); assertEquals(thirdStash, git.stashDrop().setStashRef(2).call()); - stashRef = git.getRepository().getRef(Constants.R_STASH); + stashRef = git.getRepository().exactRef(Constants.R_STASH); assertNotNull(stashRef); assertEquals(thirdStash, stashRef.getObjectId()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java index 49279e6e5..0e595e61f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeDirCacheIteratorTest.java @@ -52,9 +52,7 @@ import static org.junit.Assert.assertTrue; import java.io.IOException; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.attributes.Attribute.State; @@ -243,27 +241,29 @@ public class AttributesNodeDirCacheIteratorTest extends RepositoryTestCase { DirCacheIterator itr = walk.getTree(0, DirCacheIterator.class); assertNotNull("has tree", itr); - AttributesNode attributeNode = itr.getEntryAttributesNode(db + AttributesNode attributesNode = itr.getEntryAttributesNode(db .newObjectReader()); - assertAttributeNode(pathName, attributeNode, nodeAttrs); + assertAttributesNode(pathName, attributesNode, nodeAttrs); if (D.equals(type)) walk.enterSubtree(); } - private void assertAttributeNode(String pathName, - AttributesNode attributeNode, List nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { - Map entryAttributes = new LinkedHashMap(); - attributeNode.getAttributes(pathName, false, entryAttributes); + Attributes entryAttributes = new Attributes(); + attributesNode.getAttributes(pathName, + false, entryAttributes); if (nodeAttrs != null && !nodeAttrs.isEmpty()) { for (Attribute attribute : nodeAttrs) { - assertThat(entryAttributes.values(), hasItem(attribute)); + assertThat(entryAttributes.getAll(), + hasItem(attribute)); } } else { assertTrue( diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java similarity index 86% rename from org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java rename to org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java index ea250369a..d478a7cf0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributeNodeTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeTest.java @@ -49,10 +49,6 @@ import static org.junit.Assert.assertEquals; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Set; import org.junit.After; import org.junit.Test; @@ -60,7 +56,7 @@ import org.junit.Test; /** * Test {@link AttributesNode} */ -public class AttributeNodeTest { +public class AttributesNodeTest { private static final Attribute A_SET_ATTR = new Attribute("A", SET); @@ -104,8 +100,8 @@ public class AttributeNodeTest { is = new ByteArrayInputStream(attributeFileContent.getBytes()); AttributesNode node = new AttributesNode(); node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); - assertAttribute("file.type2", node, Collections. emptySet()); + assertAttribute("file.type1", node, new Attributes()); + assertAttribute("file.type2", node, new Attributes()); } @Test @@ -115,7 +111,7 @@ public class AttributeNodeTest { is = new ByteArrayInputStream(attributeFileContent.getBytes()); AttributesNode node = new AttributesNode(); node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); + assertAttribute("file.type1", node, new Attributes()); assertAttribute("file.type2", node, asSet(A_UNSET_ATTR)); } @@ -127,8 +123,8 @@ public class AttributeNodeTest { is = new ByteArrayInputStream(attributeFileContent.getBytes()); AttributesNode node = new AttributesNode(); node.parse(is); - assertAttribute("file.type1", node, Collections. emptySet()); - assertAttribute("file.type2", node, Collections. emptySet()); + assertAttribute("file.type1", node, new Attributes()); + assertAttribute("file.type2", node, new Attributes()); assertAttribute("file.type3", node, asSet(new Attribute("attr", ""))); } @@ -166,17 +162,14 @@ public class AttributeNodeTest { } private void assertAttribute(String path, AttributesNode node, - Set attrs) { - HashMap attributes = new HashMap(); + Attributes attrs) { + Attributes attributes = new Attributes(); node.getAttributes(path, false, attributes); - assertEquals(attrs, new HashSet(attributes.values())); + assertEquals(attrs, attributes); } - static Set asSet(Attribute... attrs) { - Set result = new HashSet(); - for (Attribute attr : attrs) - result.add(attr); - return result; + static Attributes asSet(Attribute... attrs) { + return new Attributes(attrs); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java index 64b0535d6..6ad19a249 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/AttributesNodeWorkingTreeIteratorTest.java @@ -53,9 +53,7 @@ import static org.junit.Assert.assertTrue; import java.io.File; import java.io.IOException; import java.util.Collections; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import org.eclipse.jgit.attributes.Attribute.State; import org.eclipse.jgit.errors.CorruptObjectException; @@ -76,14 +74,10 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { private static final FileMode F = FileMode.REGULAR_FILE; - private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); - private static Attribute EOL_LF = new Attribute("eol", "lf"); private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); - private static Attribute CUSTOM_VALUE = new Attribute("custom", "value"); - private TreeWalk walk; @Test @@ -112,25 +106,19 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { walk = beginWalk(); assertIteration(F, ".gitattributes"); - assertIteration(F, "global.txt", asList(EOL_LF), null, - asList(CUSTOM_VALUE)); - assertIteration(F, "readme.txt", asList(EOL_LF), null, - asList(CUSTOM_VALUE)); + assertIteration(F, "global.txt", asList(EOL_LF)); + assertIteration(F, "readme.txt", asList(EOL_LF)); assertIteration(D, "src"); assertIteration(D, "src/config"); assertIteration(F, "src/config/.gitattributes"); - assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET), null, - asList(CUSTOM_VALUE)); - assertIteration(F, "src/config/windows.file", null, asList(EOL_CRLF), - null); - assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET), - asList(EOL_CRLF), asList(CUSTOM_VALUE)); + assertIteration(F, "src/config/readme.txt", asList(DELTA_UNSET)); + assertIteration(F, "src/config/windows.file", null); + assertIteration(F, "src/config/windows.txt", asList(DELTA_UNSET)); - assertIteration(F, "windows.file", null, asList(EOL_CRLF), null); - assertIteration(F, "windows.txt", asList(EOL_LF), asList(EOL_CRLF), - asList(CUSTOM_VALUE)); + assertIteration(F, "windows.file", null); + assertIteration(F, "windows.txt", asList(EOL_LF)); endWalk(); } @@ -212,14 +200,11 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { private void assertIteration(FileMode type, String pathName) throws IOException { - assertIteration(type, pathName, Collections. emptyList(), - Collections. emptyList(), - Collections. emptyList()); + assertIteration(type, pathName, Collections. emptyList()); } private void assertIteration(FileMode type, String pathName, - List nodeAttrs, List infoAttrs, - List globalAttrs) + List nodeAttrs) throws IOException { assertTrue("walk has entry", walk.next()); assertEquals(pathName, walk.getPathString()); @@ -227,29 +212,27 @@ public class AttributesNodeWorkingTreeIteratorTest extends RepositoryTestCase { WorkingTreeIterator itr = walk.getTree(0, WorkingTreeIterator.class); assertNotNull("has tree", itr); - AttributesNode attributeNode = itr.getEntryAttributesNode(); - assertAttributeNode(pathName, attributeNode, nodeAttrs); - AttributesNode infoAttributeNode = itr.getInfoAttributesNode(); - assertAttributeNode(pathName, infoAttributeNode, infoAttrs); - AttributesNode globalAttributeNode = itr.getGlobalAttributesNode(); - assertAttributeNode(pathName, globalAttributeNode, globalAttrs); + AttributesNode attributesNode = itr.getEntryAttributesNode(); + assertAttributesNode(pathName, attributesNode, nodeAttrs); if (D.equals(type)) walk.enterSubtree(); } - private void assertAttributeNode(String pathName, - AttributesNode attributeNode, List nodeAttrs) { - if (attributeNode == null) + private void assertAttributesNode(String pathName, + AttributesNode attributesNode, List nodeAttrs) { + if (attributesNode == null) assertTrue(nodeAttrs == null || nodeAttrs.isEmpty()); else { - Map entryAttributes = new LinkedHashMap(); - attributeNode.getAttributes(pathName, false, entryAttributes); + Attributes entryAttributes = new Attributes(); + attributesNode.getAttributes(pathName, + false, entryAttributes); if (nodeAttrs != null && !nodeAttrs.isEmpty()) { for (Attribute attribute : nodeAttrs) { - assertThat(entryAttributes.values(), hasItem(attribute)); + assertThat(entryAttributes.getAll(), + hasItem(attribute)); } } else { assertTrue( diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java new file mode 100644 index 000000000..b044c01db --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/attributes/TreeWalkAttributeTest.java @@ -0,0 +1,866 @@ +/* + * Copyright (C) 2014, Obeo. + * 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 static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.eclipse.jgit.api.Git; +import org.eclipse.jgit.api.errors.GitAPIException; +import org.eclipse.jgit.api.errors.NoFilepatternException; +import org.eclipse.jgit.attributes.Attribute.State; +import org.eclipse.jgit.dircache.DirCacheIterator; +import org.eclipse.jgit.errors.NoWorkTreeException; +import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.junit.RepositoryTestCase; +import org.eclipse.jgit.lib.FileMode; +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.junit.After; +import org.junit.Before; +import org.junit.Test; + +/** + * Tests the attributes are correctly computed in a {@link TreeWalk}. + * + * @see TreeWalk#getAttributes() + */ +public class TreeWalkAttributeTest extends RepositoryTestCase { + + private static final FileMode M = FileMode.MISSING; + + private static final FileMode D = FileMode.TREE; + + private static final FileMode F = FileMode.REGULAR_FILE; + + private static Attribute EOL_CRLF = new Attribute("eol", "crlf"); + + private static Attribute EOL_LF = new Attribute("eol", "lf"); + + private static Attribute TEXT_SET = new Attribute("text", State.SET); + + private static Attribute TEXT_UNSET = new Attribute("text", State.UNSET); + + private static Attribute DELTA_UNSET = new Attribute("delta", State.UNSET); + + private static Attribute DELTA_SET = new Attribute("delta", State.SET); + + private static Attribute CUSTOM_GLOBAL = new Attribute("custom", "global"); + + private static Attribute CUSTOM_INFO = new Attribute("custom", "info"); + + private static Attribute CUSTOM_ROOT = new Attribute("custom", "root"); + + private static Attribute CUSTOM_PARENT = new Attribute("custom", "parent"); + + private static Attribute CUSTOM_CURRENT = new Attribute("custom", "current"); + + private static Attribute CUSTOM2_UNSET = new Attribute("custom2", + State.UNSET); + + private static Attribute CUSTOM2_SET = new Attribute("custom2", State.SET); + + private TreeWalk walk; + + private TreeWalk ci_walk; + + private Git git; + + private File customAttributeFile; + + @Override + @Before + public void setUp() throws Exception { + super.setUp(); + git = new Git(db); + } + + @Override + @After + public void tearDown() throws Exception { + super.tearDown(); + if (customAttributeFile != null) + customAttributeFile.delete(); + } + + /** + * Checks that the attributes are computed correctly depending on the + * operation type. + *

+ * In this test we changed the content of the attribute files in the working + * tree compared to the one in the index. + *

+ * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testCheckinCheckoutDifferences() throws IOException, + NoFilepatternException, GitAPIException { + + writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + git.add().addFilepattern(".").call(); + + beginWalk(); + + // Modify all attributes + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=lf"); + writeAttributesFile(".gitattributes", "*.txt custom=info"); + writeAttributesFile("level1/.gitattributes", "*.txt -text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt delta"); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.txt", + asSet(EOL_LF, CUSTOM_INFO, CUSTOM2_SET, TEXT_UNSET, DELTA_SET), + asSet(EOL_LF, CUSTOM_ROOT, CUSTOM2_SET, TEXT_SET, DELTA_UNSET)); + + endWalk(); + } + + /** + * Checks that the index is used as fallback when the git attributes file + * are missing in the working tree. + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testIndexOnly() throws IOException, NoFilepatternException, + GitAPIException { + List attrFiles = new ArrayList(); + attrFiles.add(writeGlobalAttributeFile("globalAttributesFile", + "*.txt -custom2")); + attrFiles.add(writeAttributesFile(".git/info/attributes", + "*.txt eol=crlf")); + attrFiles + .add(writeAttributesFile(".gitattributes", "*.txt custom=root")); + attrFiles + .add(writeAttributesFile("level1/.gitattributes", "*.txt text")); + attrFiles.add(writeAttributesFile("level1/level2/.gitattributes", + "*.txt -delta")); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + git.add().addFilepattern(".").call(); + + // Modify all attributes + for (File attrFile : attrFiles) + attrFile.delete(); + + beginWalk(); + + assertEntry(M, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(CUSTOM_ROOT)); + + assertEntry(D, "level1"); + assertEntry(M, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + + asSet(CUSTOM_ROOT, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(M, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.txt", + + asSet(CUSTOM_ROOT, TEXT_SET, DELTA_UNSET)); + + endWalk(); + } + + /** + * Check that we search in the working tree for attributes although the file + * we are currently inspecting does not exist anymore in the working tree. + * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testIndexOnly2() + throws IOException, NoFilepatternException, GitAPIException { + File l2 = writeTrashFile("level1/level2/l2.txt", ""); + writeTrashFile("level1/level2/l1.txt", ""); + + git.add().addFilepattern(".").call(); + + writeAttributesFile(".gitattributes", "*.txt custom=root"); + assertTrue(l2.delete()); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(D, "level1"); + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l1.txt", asSet(CUSTOM_ROOT)); + assertEntry(M, "level1/level2/l2.txt", asSet(CUSTOM_ROOT)); + + endWalk(); + } + + /** + * Basic test for git attributes. + *

+ * In this use case files are present in both the working tree and the index + *

+ * + * @throws IOException + * @throws NoFilepatternException + * @throws GitAPIException + */ + @Test + public void testRules() throws IOException, NoFilepatternException, + GitAPIException { + writeAttributesFile(".git/info/attributes", "windows* eol=crlf"); + + writeAttributesFile(".gitattributes", "*.txt eol=lf"); + writeTrashFile("windows.file", ""); + writeTrashFile("windows.txt", ""); + writeTrashFile("readme.txt", ""); + + writeAttributesFile("src/config/.gitattributes", "*.txt -delta"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + writeTrashFile("src/config/windows.txt", ""); + + beginWalk(); + + git.add().addFilepattern(".").call(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "readme.txt", asSet(EOL_LF)); + + assertEntry(D, "src"); + assertEntry(D, "src/config"); + assertEntry(F, "src/config/.gitattributes"); + assertEntry(F, "src/config/readme.txt", asSet(DELTA_UNSET, EOL_LF)); + assertEntry(F, "src/config/windows.file", asSet(EOL_CRLF)); + assertEntry(F, "src/config/windows.txt", asSet(DELTA_UNSET, EOL_CRLF)); + + assertEntry(F, "windows.file", asSet(EOL_CRLF)); + assertEntry(F, "windows.txt", asSet(EOL_CRLF)); + + endWalk(); + } + + /** + * Checks that if there is no .gitattributes file in the repository + * everything still work fine. + * + * @throws IOException + */ + @Test + public void testNoAttributes() throws IOException { + writeTrashFile("l0.txt", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, "l0.txt"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/l1.txt"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l2.txt"); + + endWalk(); + } + + /** + * Checks that an empty .gitattribute file does not return incorrect value. + * + * @throws IOException + */ + @Test + public void testEmptyGitAttributeFile() throws IOException { + writeAttributesFile(".git/info/attributes", ""); + writeTrashFile("l0.txt", ""); + writeAttributesFile(".gitattributes", ""); + writeTrashFile("level1/l1.txt", ""); + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/l1.txt"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/l2.txt"); + + endWalk(); + } + + @Test + public void testNoMatchingAttributes() throws IOException { + writeAttributesFile(".git/info/attributes", "*.java delta"); + writeAttributesFile(".gitattributes", "*.java -delta"); + writeAttributesFile("levelA/.gitattributes", "*.java eol=lf"); + writeAttributesFile("levelB/.gitattributes", "*.txt eol=lf"); + + writeTrashFile("levelA/lA.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "levelA"); + assertEntry(F, "levelA/.gitattributes"); + assertEntry(F, "levelA/lA.txt"); + + assertEntry(D, "levelB"); + assertEntry(F, "levelB/.gitattributes"); + + endWalk(); + } + + /** + * Checks that $GIT_DIR/info/attributes file has the highest precedence. + * + * @throws IOException + */ + @Test + public void testPrecedenceInfo() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".git/info/attributes", "*.txt custom=info"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + writeAttributesFile("level1/level2/.gitattributes", + "*.txt custom=current"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_INFO)); + + endWalk(); + } + + /** + * Checks that a subfolder ".gitattributes" file has precedence over its + * parent. + * + * @throws IOException + */ + @Test + public void testPrecedenceCurrent() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + writeAttributesFile("level1/level2/.gitattributes", + "*.txt custom=current"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_CURRENT)); + + endWalk(); + } + + /** + * Checks that the parent ".gitattributes" file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceParent() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt custom=parent"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_PARENT)); + + endWalk(); + } + + /** + * Checks that the grand parent ".gitattributes" file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceRoot() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + + assertEntry(D, "level1"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_ROOT)); + + endWalk(); + } + + /** + * Checks that the global attribute file is used as fallback. + * + * @throws IOException + */ + @Test + public void testPrecedenceGlobal() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt custom=global"); + + writeTrashFile("level1/level2/file.txt", ""); + + beginWalk(); + + assertEntry(D, "level1"); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/file.txt", asSet(CUSTOM_GLOBAL)); + + endWalk(); + } + + /** + * Checks the precedence on a hierarchy with multiple attributes. + *

+ * In this test all file are present in both the working tree and the index. + *

+ * + * @throws IOException + * @throws GitAPIException + * @throws NoFilepatternException + */ + @Test + public void testHierarchyBothIterator() throws IOException, + NoFilepatternException, GitAPIException { + writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); + writeAttributesFile(".gitattributes", "*.local eol=lf"); + writeAttributesFile("level1/.gitattributes", "*.local text"); + writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); + + writeTrashFile("l0.global", ""); + writeTrashFile("l0.local", ""); + + writeTrashFile("level1/l1.global", ""); + writeTrashFile("level1/l1.local", ""); + + writeTrashFile("level1/level2/l2.global", ""); + writeTrashFile("level1/level2/l2.local", ""); + + beginWalk(); + + git.add().addFilepattern(".").call(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.global", asSet(EOL_CRLF)); + assertEntry(F, "l0.local", asSet(EOL_LF)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); + + endWalk(); + + } + + /** + * Checks the precedence on a hierarchy with multiple attributes. + *

+ * In this test all file are present only in the working tree. + *

+ * + * @throws IOException + * @throws GitAPIException + * @throws NoFilepatternException + */ + @Test + public void testHierarchyWorktreeOnly() + throws IOException, NoFilepatternException, GitAPIException { + writeAttributesFile(".git/info/attributes", "*.global eol=crlf"); + writeAttributesFile(".gitattributes", "*.local eol=lf"); + writeAttributesFile("level1/.gitattributes", "*.local text"); + writeAttributesFile("level1/level2/.gitattributes", "*.local -text"); + + writeTrashFile("l0.global", ""); + writeTrashFile("l0.local", ""); + + writeTrashFile("level1/l1.global", ""); + writeTrashFile("level1/l1.local", ""); + + writeTrashFile("level1/level2/l2.global", ""); + writeTrashFile("level1/level2/l2.local", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.global", asSet(EOL_CRLF)); + assertEntry(F, "l0.local", asSet(EOL_LF)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/l1.local", asSet(EOL_LF, TEXT_SET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry(F, "level1/level2/l2.global", asSet(EOL_CRLF)); + assertEntry(F, "level1/level2/l2.local", asSet(EOL_LF, TEXT_UNSET)); + + endWalk(); + + } + + /** + * Checks that the list of attributes is an aggregation of all the + * attributes from the attributes files hierarchy. + * + * @throws IOException + */ + @Test + public void testAggregation() throws IOException { + writeGlobalAttributeFile("globalAttributesFile", "*.txt -custom2"); + writeAttributesFile(".git/info/attributes", "*.txt eol=crlf"); + writeAttributesFile(".gitattributes", "*.txt custom=root"); + writeAttributesFile("level1/.gitattributes", "*.txt text"); + writeAttributesFile("level1/level2/.gitattributes", "*.txt -delta"); + + writeTrashFile("l0.txt", ""); + + writeTrashFile("level1/l1.txt", ""); + + writeTrashFile("level1/level2/l2.txt", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(F, "l0.txt", asSet(EOL_CRLF, CUSTOM_ROOT, CUSTOM2_UNSET)); + + assertEntry(D, "level1"); + assertEntry(F, "level1/.gitattributes"); + assertEntry(F, "level1/l1.txt", + asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, CUSTOM2_UNSET)); + + assertEntry(D, "level1/level2"); + assertEntry(F, "level1/level2/.gitattributes"); + assertEntry( + F, + "level1/level2/l2.txt", + asSet(EOL_CRLF, CUSTOM_ROOT, TEXT_SET, DELTA_UNSET, + CUSTOM2_UNSET)); + + endWalk(); + + } + + /** + * Checks that the last entry in .gitattributes is used if 2 lines match the + * same attribute + * + * @throws IOException + */ + @Test + public void testOverriding() throws IOException { + writeAttributesFile(".git/info/attributes",// + // + "*.txt custom=current",// + "*.txt custom=parent",// + "*.txt custom=root",// + "*.txt custom=info", + // + "*.txt delta",// + "*.txt -delta", + // + "*.txt eol=lf",// + "*.txt eol=crlf", + // + "*.txt text",// + "*.txt -text"); + + writeTrashFile("l0.txt", ""); + beginWalk(); + + assertEntry(F, "l0.txt", + asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); + + endWalk(); + } + + /** + * Checks that the last value of an attribute is used if in the same line an + * attribute is defined several time. + * + * @throws IOException + */ + @Test + public void testOverriding2() throws IOException { + writeAttributesFile(".git/info/attributes", + "*.txt custom=current custom=parent custom=root custom=info",// + "*.txt delta -delta",// + "*.txt eol=lf eol=crlf",// + "*.txt text -text"); + writeTrashFile("l0.txt", ""); + beginWalk(); + + assertEntry(F, "l0.txt", + asSet(TEXT_UNSET, EOL_CRLF, DELTA_UNSET, CUSTOM_INFO)); + + endWalk(); + } + + @Test + public void testRulesInherited() throws Exception { + writeAttributesFile(".gitattributes", "**/*.txt eol=lf"); + writeTrashFile("src/config/readme.txt", ""); + writeTrashFile("src/config/windows.file", ""); + + beginWalk(); + + assertEntry(F, ".gitattributes"); + assertEntry(D, "src"); + assertEntry(D, "src/config"); + + assertEntry(F, "src/config/readme.txt", asSet(EOL_LF)); + assertEntry(F, "src/config/windows.file", + Collections. emptySet()); + + endWalk(); + } + + private void beginWalk() throws NoWorkTreeException, IOException { + walk = new TreeWalk(db); + walk.addTree(new FileTreeIterator(db)); + walk.addTree(new DirCacheIterator(db.readDirCache())); + + ci_walk = new TreeWalk(db); + ci_walk.setOperationType(OperationType.CHECKIN_OP); + ci_walk.addTree(new FileTreeIterator(db)); + ci_walk.addTree(new DirCacheIterator(db.readDirCache())); + } + + /** + * Assert an entry in which checkin and checkout attributes are expected to + * be the same. + * + * @param type + * @param pathName + * @param forBothOperaiton + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName, + Set forBothOperaiton) throws IOException { + assertEntry(type, pathName, forBothOperaiton, forBothOperaiton); + } + + /** + * Assert an entry with no attribute expected. + * + * @param type + * @param pathName + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName) throws IOException { + assertEntry(type, pathName, Collections. emptySet(), + Collections. emptySet()); + } + + /** + * Assert that an entry; + *
    + *
  • Has the correct type
  • + *
  • Exist in the tree walk
  • + *
  • Has the expected attributes on a checkin operation
  • + *
  • Has the expected attributes on a checkout operation
  • + *
+ * + * @param type + * @param pathName + * @param checkinAttributes + * @param checkoutAttributes + * @throws IOException + */ + private void assertEntry(FileMode type, String pathName, + Set checkinAttributes, Set checkoutAttributes) + throws IOException { + assertTrue("walk has entry", walk.next()); + assertTrue("walk has entry", ci_walk.next()); + assertEquals(pathName, walk.getPathString()); + assertEquals(type, walk.getFileMode(0)); + + assertEquals(checkinAttributes, + asSet(ci_walk.getAttributes().getAll())); + assertEquals(checkoutAttributes, + asSet(walk.getAttributes().getAll())); + + if (D.equals(type)) { + walk.enterSubtree(); + ci_walk.enterSubtree(); + } + } + + private static Set asSet(Collection attributes) { + Set ret = new HashSet(); + for (Attribute a : attributes) { + ret.add(a); + } + return (ret); + } + + private File writeAttributesFile(String name, String... rules) + throws IOException { + StringBuilder data = new StringBuilder(); + for (String line : rules) + data.append(line + "\n"); + return writeTrashFile(name, data.toString()); + } + + /** + * Creates an attributes file and set its location in the git configuration. + * + * @param fileName + * @param attributes + * @return The attribute file + * @throws IOException + * @see Repository#getConfig() + */ + private File writeGlobalAttributeFile(String fileName, String... attributes) + throws IOException { + customAttributeFile = File.createTempFile("tmp_", fileName, null); + customAttributeFile.deleteOnExit(); + StringBuilder attributesFileContent = new StringBuilder(); + for (String attr : attributes) { + attributesFileContent.append(attr).append("\n"); + } + JGitTestUtil.write(customAttributeFile, + attributesFileContent.toString()); + db.getConfig().setString("core", null, "attributesfile", + customAttributeFile.getAbsolutePath()); + return customAttributeFile; + } + + static Set asSet(Attribute... attrs) { + HashSet result = new HashSet(); + for (Attribute attr : attrs) + result.add(attr); + return result; + } + + private void endWalk() throws IOException { + assertFalse("Not all files tested", walk.next()); + assertFalse("Not all files tested", ci_walk.next()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java index e83ef772f..4315be9e4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/diff/RenameDetectorTest.java @@ -48,6 +48,7 @@ import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.List; import org.eclipse.jgit.diff.DiffEntry.ChangeType; @@ -226,6 +227,19 @@ public class RenameDetectorTest extends RepositoryTestCase { assertCopy(d, b, 100, entries.get(2)); } + @Test + public void testExactRename_UnstagedFile() throws Exception { + ObjectId aId = blob("foo"); + DiffEntry a = DiffEntry.delete(PATH_A, aId); + DiffEntry b = DiffEntry.add(PATH_B, aId); + + rd.addAll(Arrays.asList(a, b)); + List entries = rd.compute(); + + assertEquals(1, entries.size()); + assertRename(a, b, 100, entries.get(0)); + } + @Test public void testInexactRename_OnePair() throws Exception { ObjectId aId = blob("foo\nbar\nbaz\nblarg\n"); @@ -429,6 +443,23 @@ public class RenameDetectorTest extends RepositoryTestCase { assertSame(b, entries.get(1)); } + @Test + public void testNoRenames_UntrackedFile() throws Exception { + ObjectId aId = blob("foo"); + ObjectId bId = ObjectId + .fromString("3049eb6eee7e1318f4e78e799bf33f1e54af9cbf"); + + DiffEntry a = DiffEntry.delete(PATH_A, aId); + DiffEntry b = DiffEntry.add(PATH_B, bId); + + rd.addAll(Arrays.asList(a, b)); + List entries = rd.compute(); + + assertEquals(2, entries.size()); + assertSame(a, entries.get(0)); + assertSame(b, entries.get(1)); + } + @Test public void testBreakModify_BreakAll() throws Exception { ObjectId aId = blob("foo"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java index e159ed939..dc7181aec 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/dircache/DirCacheEntryTest.java @@ -69,9 +69,9 @@ public class DirCacheEntryTest { assertFalse(isValidPath("a\u0000b")); } - private static boolean isValidPath(final String path) { + private static boolean isValidPath(String path) { try { - DirCacheCheckout.checkValidPath(path); + new DirCacheEntry(path); return true; } catch (InvalidPathException e) { return false; diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java index c7336da40..fa40458f3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/GcPackRefsTest.java @@ -77,7 +77,7 @@ public class GcPackRefsTest extends GcTestCase { tr.lightweightTag("t", a); gc.packRefs(); - assertSame(repo.getRef("t").getStorage(), Storage.PACKED); + assertSame(repo.exactRef("refs/tags/t").getStorage(), Storage.PACKED); } @Test @@ -126,8 +126,8 @@ public class GcPackRefsTest extends GcTestCase { refLock.unlock(); } - assertSame(repo.getRef("refs/tags/t1").getStorage(), Storage.LOOSE); - assertSame(repo.getRef("refs/tags/t2").getStorage(), Storage.PACKED); + assertSame(repo.exactRef("refs/tags/t1").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("refs/tags/t2").getStorage(), Storage.PACKED); } @Test @@ -146,7 +146,7 @@ public class GcPackRefsTest extends GcTestCase { public Result call() throws Exception { RefUpdate update = new RefDirectoryUpdate( (RefDirectory) repo.getRefDatabase(), - repo.getRef("refs/tags/t")) { + repo.exactRef("refs/tags/t")) { @Override public boolean isForceUpdate() { try { @@ -182,7 +182,7 @@ public class GcPackRefsTest extends GcTestCase { pool.awaitTermination(Long.MAX_VALUE, TimeUnit.SECONDS); } - assertEquals(repo.getRef("refs/tags/t").getObjectId(), b); + assertEquals(repo.exactRef("refs/tags/t").getObjectId(), b); } @Test @@ -194,23 +194,23 @@ public class GcPackRefsTest extends GcTestCase { // check for the unborn branch master. HEAD should point to master and // master doesn't exist. - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); git.checkout().setName("refs/heads/side").call(); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); // check for detached HEAD git.checkout().setName(first.getName()).call(); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); } @Test @@ -229,20 +229,20 @@ public class GcPackRefsTest extends GcTestCase { // check for the unborn branch master. HEAD should point to master and // master doesn't exist. - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getName(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getName(), "refs/heads/master"); - assertNull(repo.getRef("HEAD").getTarget().getObjectId()); + assertNull(repo.exactRef("HEAD").getTarget().getObjectId()); // check for non-detached HEAD repo.updateRef(Constants.HEAD).link("refs/heads/side"); gc.packRefs(); - assertSame(repo.getRef("HEAD").getStorage(), Storage.LOOSE); - assertEquals(repo.getRef("HEAD").getTarget().getObjectId(), + assertSame(repo.exactRef("HEAD").getStorage(), Storage.LOOSE); + assertEquals(repo.exactRef("HEAD").getTarget().getObjectId(), second.getId()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java index 099159892..52e181bc8 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefDirectoryTest.java @@ -857,6 +857,36 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { assertNull("mising 1 due to cycle", r); } + @Test + public void testGetRef_CycleInSymbolicRef() throws IOException { + Ref r; + + writeLooseRef("refs/1", "ref: refs/2\n"); + writeLooseRef("refs/2", "ref: refs/3\n"); + writeLooseRef("refs/3", "ref: refs/4\n"); + writeLooseRef("refs/4", "ref: refs/5\n"); + writeLooseRef("refs/5", "ref: refs/end\n"); + writeLooseRef("refs/end", A); + + r = refdir.getRef("1"); + assertEquals("refs/1", r.getName()); + assertEquals(A, r.getObjectId()); + assertTrue(r.isSymbolic()); + + writeLooseRef("refs/5", "ref: refs/6\n"); + writeLooseRef("refs/6", "ref: refs/end\n"); + + r = refdir.getRef("1"); + assertNull("missing 1 due to cycle", r); + + writeLooseRef("refs/heads/1", B); + + r = refdir.getRef("1"); + assertEquals("refs/heads/1", r.getName()); + assertEquals(B, r.getObjectId()); + assertFalse(r.isSymbolic()); + } + @Test public void testGetRefs_PackedNotPeeled_Sorted() throws IOException { Map all; @@ -1024,6 +1054,25 @@ public class RefDirectoryTest extends LocalDiskRepositoryTestCase { assertNull(refdir.getRef("v1.0")); } + @Test + public void testExactRef_EmptyDatabase() throws IOException { + Ref r; + + r = refdir.exactRef(HEAD); + assertTrue(r.isSymbolic()); + assertSame(LOOSE, r.getStorage()); + assertEquals("refs/heads/master", r.getTarget().getName()); + assertSame(NEW, r.getTarget().getStorage()); + assertNull(r.getTarget().getObjectId()); + + assertNull(refdir.exactRef("refs/heads/master")); + assertNull(refdir.exactRef("refs/tags/v1.0")); + assertNull(refdir.exactRef("FETCH_HEAD")); + assertNull(refdir.exactRef("NOT.A.REF.NAME")); + assertNull(refdir.exactRef("master")); + assertNull(refdir.exactRef("v1.0")); + } + @Test public void testGetRef_FetchHead() throws IOException { // This is an odd special case where we need to make sure we read diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java index 098b31fcc..8744ed1bd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/internal/storage/file/RefUpdateTest.java @@ -347,7 +347,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { Result update = updateRef.update(); assertEquals(Result.FORCED, update); assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals("HEAD", ref.getName()); assertTrue("is detached", !ref.isSymbolic()); @@ -377,7 +377,7 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { Result update = updateRef.update(); assertEquals(Result.NEW, update); assertEquals(ppid, db.resolve("HEAD")); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals("HEAD", ref.getName()); assertTrue("is detached", !ref.isSymbolic()); @@ -681,13 +681,13 @@ public class RefUpdateTest extends SampleDataRepositoryTestCase { public void testRenameBranchAlsoInPack() throws IOException { ObjectId rb = db.resolve("refs/heads/b"); ObjectId rb2 = db.resolve("refs/heads/b~1"); - assertEquals(Ref.Storage.PACKED, db.getRef("refs/heads/b").getStorage()); + assertEquals(Ref.Storage.PACKED, db.exactRef("refs/heads/b").getStorage()); RefUpdate updateRef = db.updateRef("refs/heads/b"); updateRef.setNewObjectId(rb2); updateRef.setForceUpdate(true); Result update = updateRef.update(); assertEquals("internal check new ref is loose", Result.FORCED, update); - assertEquals(Ref.Storage.LOOSE, db.getRef("refs/heads/b").getStorage()); + assertEquals(Ref.Storage.LOOSE, db.exactRef("refs/heads/b").getStorage()); writeReflog(db, rb, "Just a message", "refs/heads/b"); assertTrue("log on old branch", new File(db.getDirectory(), "logs/refs/heads/b").exists()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java index b69b8e01e..8b54dabce 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/junit/TestRepositoryTest.java @@ -127,25 +127,25 @@ public class TestRepositoryTest { @Test public void resetFromSymref() throws Exception { repo.updateRef("HEAD").link("refs/heads/master"); - Ref head = repo.getRef("HEAD"); + Ref head = repo.exactRef("HEAD"); RevCommit master = tr.branch("master").commit().create(); RevCommit branch = tr.branch("branch").commit().create(); RevCommit detached = tr.commit().create(); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); - assertEquals(master, repo.getRef("refs/heads/master").getObjectId()); - assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId()); + assertEquals(master, repo.exactRef("refs/heads/master").getObjectId()); + assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId()); // Reset to branches preserves symref. tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(master, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); tr.reset("branch"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(branch, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); @@ -153,50 +153,50 @@ public class TestRepositoryTest { // Reset to a SHA-1 detaches. tr.reset(detached); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached.name()); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); // Reset back to a branch remains detached. tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(lastHeadBeforeDetach, head.getObjectId()); assertFalse(head.isSymbolic()); } @Test public void resetFromDetachedHead() throws Exception { - Ref head = repo.getRef("HEAD"); + Ref head = repo.exactRef("HEAD"); RevCommit master = tr.branch("master").commit().create(); RevCommit branch = tr.branch("branch").commit().create(); RevCommit detached = tr.commit().create(); assertNull(head); - assertEquals(master, repo.getRef("refs/heads/master").getObjectId()); - assertEquals(branch, repo.getRef("refs/heads/branch").getObjectId()); + assertEquals(master, repo.exactRef("refs/heads/master").getObjectId()); + assertEquals(branch, repo.exactRef("refs/heads/branch").getObjectId()); tr.reset("master"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(master, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset("branch"); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(branch, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); tr.reset(detached.name()); - head = repo.getRef("HEAD"); + head = repo.exactRef("HEAD"); assertEquals(detached, head.getObjectId()); assertFalse(head.isSymbolic()); } @@ -222,7 +222,7 @@ public class TestRepositoryTest { .tick(3) .add("bar", "fixed bar contents") .create(); - assertEquals(amended, repo.getRef("refs/heads/master").getObjectId()); + assertEquals(amended, repo.exactRef("refs/heads/master").getObjectId()); rw.parseBody(amended); assertEquals(1, amended.getParentCount()); @@ -257,7 +257,7 @@ public class TestRepositoryTest { .add("foo", "fixed foo contents") .create(); - Ref head = repo.getRef(Constants.HEAD); + Ref head = repo.exactRef(Constants.HEAD); assertEquals(amended, head.getObjectId()); assertTrue(head.isSymbolic()); assertEquals("refs/heads/master", head.getTarget().getName()); @@ -291,7 +291,7 @@ public class TestRepositoryTest { public void commitToUnbornHead() throws Exception { repo.updateRef("HEAD").link("refs/heads/master"); RevCommit root = tr.branch("HEAD").commit().create(); - Ref ref = repo.getRef(Constants.HEAD); + Ref ref = repo.exactRef(Constants.HEAD); assertEquals(root, ref.getObjectId()); assertTrue(ref.isSymbolic()); assertEquals("refs/heads/master", ref.getTarget().getName()); @@ -316,7 +316,7 @@ public class TestRepositoryTest { RevCommit result = tr.cherryPick(toPick); rw.parseBody(result); - Ref headRef = tr.getRepository().getRef("HEAD"); + Ref headRef = tr.getRepository().exactRef("HEAD"); assertEquals(result, headRef.getObjectId()); assertTrue(headRef.isSymbolic()); assertEquals("refs/heads/master", headRef.getLeaf().getName()); @@ -371,7 +371,7 @@ public class TestRepositoryTest { .create(); assertNotEquals(head, toPick); assertNull(tr.cherryPick(toPick)); - assertEquals(head, repo.getRef("HEAD").getObjectId()); + assertEquals(head, repo.exactRef("HEAD").getObjectId()); } private String blobAsString(AnyObjectId treeish, String path) diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java index 6d62528f8..d768e0fa0 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/DirCacheCheckoutTest.java @@ -79,6 +79,7 @@ import org.eclipse.jgit.treewalk.FileTreeIterator; import org.eclipse.jgit.treewalk.TreeWalk; import org.eclipse.jgit.util.FS; import org.eclipse.jgit.util.FileUtils; +import org.junit.Assume; import org.junit.Test; public class DirCacheCheckoutTest extends RepositoryTestCase { @@ -112,7 +113,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { return dco.getRemoved(); } - private Map getUpdated() { + private Map getUpdated() { return dco.getUpdated(); } @@ -267,8 +268,6 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testRules1thru3_NoIndexEntry() throws IOException { ObjectId head = buildTree(mk("foo")); - TreeWalk tw = TreeWalk.forPath(db, "foo", head); - ObjectId objectId = tw.getObjectId(0); ObjectId merge = db.newObjectInserter().insert(Constants.OBJ_TREE, new byte[0]); @@ -278,10 +277,9 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { prescanTwoTrees(merge, head); - assertEquals(objectId, getUpdated().get("foo")); + assertTrue(getUpdated().containsKey("foo")); merge = buildTree(mkmap("foo", "a")); - tw = TreeWalk.forPath(db, "foo", merge); prescanTwoTrees(head, merge); @@ -925,6 +923,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testCheckoutChangeLinkToEmptyDir() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "was_file"; Git git = Git.wrap(db); @@ -961,6 +960,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testCheckoutChangeLinkToEmptyDirs() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "was_file"; Git git = Git.wrap(db); @@ -999,6 +999,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testCheckoutChangeLinkToNonEmptyDirs() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file"; Git git = Git.wrap(db); @@ -1043,6 +1044,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testCheckoutChangeLinkToNonEmptyDirsAndNewIndexEntry() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file"; Git git = Git.wrap(db); @@ -1364,6 +1366,7 @@ public class DirCacheCheckoutTest extends RepositoryTestCase { @Test public void testOverwriteUntrackedLinkModeChange() throws Exception { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); String fname = "file.txt"; Git git = Git.wrap(db); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java index 863d79dde..3259f622f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/IndexDiffSubmoduleTest.java @@ -87,7 +87,7 @@ public class IndexDiffSubmoduleTest extends RepositoryTestCase { .call(); submodule_db = (FileRepository) Git.wrap(db).submoduleAdd() - .setPath("submodule") + .setPath("modules/submodule") .setURI(submoduleStandalone.getDirectory().toURI().toString()) .call(); submoduleStandalone.close(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java index 109f40189..707757b34 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/RefTest.java @@ -163,7 +163,7 @@ public class RefTest extends SampleDataRepositoryTestCase { @Test public void testReadSymRefToPacked() throws IOException { writeSymref("HEAD", "refs/heads/b"); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals(Ref.Storage.LOOSE, ref.getStorage()); assertTrue("is symref", ref.isSymbolic()); ref = ref.getTarget(); @@ -181,7 +181,7 @@ public class RefTest extends SampleDataRepositoryTestCase { assertEquals(Result.FORCED, update); // internal writeSymref("HEAD", "refs/heads/master"); - Ref ref = db.getRef("HEAD"); + Ref ref = db.exactRef("HEAD"); assertEquals(Ref.Storage.LOOSE, ref.getStorage()); ref = ref.getTarget(); assertEquals("refs/heads/master", ref.getName()); @@ -194,13 +194,13 @@ public class RefTest extends SampleDataRepositoryTestCase { updateRef.setNewObjectId(db.resolve("refs/heads/master")); Result update = updateRef.update(); assertEquals(Result.NEW, update); - Ref ref = db.getRef("ref/heads/new"); + Ref ref = db.exactRef("ref/heads/new"); assertEquals(Storage.LOOSE, ref.getStorage()); } @Test public void testGetShortRef() throws IOException { - Ref ref = db.getRef("master"); + Ref ref = db.exactRef("refs/heads/master"); assertEquals("refs/heads/master", ref.getName()); assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); } @@ -222,7 +222,7 @@ public class RefTest extends SampleDataRepositoryTestCase { assertNull(db.getRefDatabase().exactRef("refs/foo/bar")); - Ref ref = db.getRef("refs/foo/bar"); + Ref ref = db.findRef("refs/foo/bar"); assertEquals("refs/heads/refs/foo/bar", ref.getName()); assertEquals(db.resolve("refs/heads/master"), ref.getObjectId()); } @@ -237,7 +237,7 @@ public class RefTest extends SampleDataRepositoryTestCase { assertEquals("refs/foo/bar", exactRef.getName()); assertEquals(masterId, exactRef.getObjectId()); - Ref ref = db.getRef("refs/foo/bar"); + Ref ref = db.findRef("refs/foo/bar"); assertEquals("refs/foo/bar", ref.getName()); assertEquals(masterId, ref.getObjectId()); } @@ -251,7 +251,7 @@ public class RefTest extends SampleDataRepositoryTestCase { @Test public void testReadLoosePackedRef() throws IOException, InterruptedException { - Ref ref = db.getRef("refs/heads/master"); + Ref ref = db.exactRef("refs/heads/master"); assertEquals(Storage.PACKED, ref.getStorage()); FileOutputStream os = new FileOutputStream(new File(db.getDirectory(), "refs/heads/master")); @@ -259,7 +259,7 @@ public class RefTest extends SampleDataRepositoryTestCase { os.write('\n'); os.close(); - ref = db.getRef("refs/heads/master"); + ref = db.exactRef("refs/heads/master"); assertEquals(Storage.LOOSE, ref.getStorage()); } @@ -271,7 +271,7 @@ public class RefTest extends SampleDataRepositoryTestCase { */ @Test public void testReadSimplePackedRefSameRepo() throws IOException { - Ref ref = db.getRef("refs/heads/master"); + Ref ref = db.exactRef("refs/heads/master"); ObjectId pid = db.resolve("refs/heads/master^"); assertEquals(Storage.PACKED, ref.getStorage()); RefUpdate updateRef = db.updateRef("refs/heads/master"); @@ -280,19 +280,19 @@ public class RefTest extends SampleDataRepositoryTestCase { Result update = updateRef.update(); assertEquals(Result.FORCED, update); - ref = db.getRef("refs/heads/master"); + ref = db.exactRef("refs/heads/master"); assertEquals(Storage.LOOSE, ref.getStorage()); } @Test public void testResolvedNamesBranch() throws IOException { - Ref ref = db.getRef("a"); + Ref ref = db.findRef("a"); assertEquals("refs/heads/a", ref.getName()); } @Test public void testResolvedSymRef() throws IOException { - Ref ref = db.getRef(Constants.HEAD); + Ref ref = db.exactRef(Constants.HEAD); assertEquals(Constants.HEAD, ref.getName()); assertTrue("is symbolic ref", ref.isSymbolic()); assertSame(Ref.Storage.LOOSE, ref.getStorage()); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java index 21ef7479a..6c90f7d44 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeMessageFormatterTest.java @@ -84,44 +84,44 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { @Test public void testOneBranch() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(a), master); assertEquals("Merge branch 'a'", message); } @Test public void testTwoBranches() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(a, b), master); assertEquals("Merge branches 'a' and 'b'", message); } @Test public void testThreeBranches() throws IOException { - Ref c = db.getRef("refs/heads/c"); - Ref b = db.getRef("refs/heads/b"); - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref c = db.exactRef("refs/heads/c"); + Ref b = db.exactRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(c, b, a), master); assertEquals("Merge branches 'c', 'b' and 'a'", message); } @Test public void testRemoteBranch() throws Exception { - Ref remoteA = db.getRef("refs/remotes/origin/remote-a"); - Ref master = db.getRef("refs/heads/master"); + Ref remoteA = db.exactRef("refs/remotes/origin/remote-a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(remoteA), master); assertEquals("Merge remote-tracking branch 'origin/remote-a'", message); } @Test public void testMixed() throws IOException { - Ref c = db.getRef("refs/heads/c"); - Ref remoteA = db.getRef("refs/remotes/origin/remote-a"); - Ref master = db.getRef("refs/heads/master"); + Ref c = db.exactRef("refs/heads/c"); + Ref remoteA = db.exactRef("refs/remotes/origin/remote-a"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(c, remoteA), master); assertEquals("Merge branch 'c', remote-tracking branch 'origin/remote-a'", message); @@ -129,8 +129,8 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { @Test public void testTag() throws IOException { - Ref tagA = db.getRef("refs/tags/A"); - Ref master = db.getRef("refs/heads/master"); + Ref tagA = db.exactRef("refs/tags/A"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(tagA), master); assertEquals("Merge tag 'A'", message); } @@ -141,7 +141,7 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { .fromString("6db9c2ebf75590eef973081736730a9ea169a0c4"); Ref commit = new ObjectIdRef.Unpeeled(Storage.LOOSE, objectId.getName(), objectId); - Ref master = db.getRef("refs/heads/master"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(commit), master); assertEquals("Merge commit '6db9c2ebf75590eef973081736730a9ea169a0c4'", message); @@ -154,7 +154,7 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { .fromString("6db9c2ebf75590eef973081736730a9ea169a0c4"); Ref remoteBranch = new ObjectIdRef.Unpeeled(Storage.LOOSE, name, objectId); - Ref master = db.getRef("refs/heads/master"); + Ref master = db.exactRef("refs/heads/master"); String message = formatter.format(Arrays.asList(remoteBranch), master); assertEquals("Merge branch 'test' of http://egit.eclipse.org/jgit.git", message); @@ -162,16 +162,16 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { @Test public void testIntoOtherThanMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); String message = formatter.format(Arrays.asList(a), b); assertEquals("Merge branch 'a' into b", message); } @Test public void testIntoHeadOtherThanMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref b = db.getRef("refs/heads/b"); + Ref a = db.exactRef("refs/heads/a"); + Ref b = db.exactRef("refs/heads/b"); SymbolicRef head = new SymbolicRef("HEAD", b); String message = formatter.format(Arrays.asList(a), head); assertEquals("Merge branch 'a' into b", message); @@ -179,8 +179,8 @@ public class MergeMessageFormatterTest extends SampleDataRepositoryTestCase { @Test public void testIntoSymbolicRefHeadPointingToMaster() throws IOException { - Ref a = db.getRef("refs/heads/a"); - Ref master = db.getRef("refs/heads/master"); + Ref a = db.exactRef("refs/heads/a"); + Ref master = db.exactRef("refs/heads/master"); SymbolicRef head = new SymbolicRef("HEAD", master); String message = formatter.format(Arrays.asList(a), head); assertEquals("Merge branch 'a'", message); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java index cd6a4bea2..674619f0d 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/ResolveMergerTest.java @@ -630,7 +630,7 @@ public class ResolveMergerTest extends RepositoryTestCase { // ResolveMerge try { MergeResult mergeResult = git.merge().setStrategy(strategy) - .include(git.getRepository().getRef("refs/heads/side")) + .include(git.getRepository().exactRef("refs/heads/side")) .call(); assertEquals(MergeStrategy.RECURSIVE, strategy); assertEquals(MergeResult.MergeStatus.MERGED, diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java index b7b229142..cddbbd528 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/SquashMessageFormatterTest.java @@ -76,7 +76,7 @@ public class SquashMessageFormatterTest extends SampleDataRepositoryTestCase { Git git = new Git(db); revCommit = git.commit().setMessage("squash_me").call(); - Ref master = db.getRef("refs/heads/master"); + Ref master = db.exactRef("refs/heads/master"); String message = msgFormatter.format(Arrays.asList(revCommit), master); assertEquals( "Squashed commit of the following:\n\ncommit " diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java new file mode 100644 index 000000000..782e414b6 --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/AtomicPushTest.java @@ -0,0 +1,200 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.transport; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.jgit.errors.TransportException; +import org.eclipse.jgit.internal.JGitText; +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class AtomicPushTest { + private URIish uri; + private TestProtocol testProtocol; + private Object ctx = new Object(); + private InMemoryRepository server; + private InMemoryRepository client; + private ObjectId obj1; + private ObjectId obj2; + + @Before + public void setUp() throws Exception { + server = newRepo("server"); + client = newRepo("client"); + testProtocol = new TestProtocol<>( + null, + new ReceivePackFactory() { + @Override + public ReceivePack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + return new ReceivePack(db); + } + }); + uri = testProtocol.register(ctx, server); + + try (ObjectInserter ins = client.newObjectInserter()) { + obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test")); + obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file")); + ins.flush(); + } + } + + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + @Test + public void pushNonAtomic() throws Exception { + PushResult r; + server.setPerformsAtomicTransactions(false); + Transport tn = testProtocol.open(uri, client, "server"); + try { + tn.setPushAtomic(false); + r = tn.push(NullProgressMonitor.INSTANCE, commands()); + } finally { + tn.close(); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + assertSame(RemoteRefUpdate.Status.OK, one.getStatus()); + assertSame( + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + } + + @Test + public void pushAtomicClientGivesUpEarly() throws Exception { + PushResult r; + Transport tn = testProtocol.open(uri, client, "server"); + try { + tn.setPushAtomic(true); + r = tn.push(NullProgressMonitor.INSTANCE, commands()); + } finally { + tn.close(); + } + + RemoteRefUpdate one = r.getRemoteUpdate("refs/heads/one"); + RemoteRefUpdate two = r.getRemoteUpdate("refs/heads/two"); + assertSame( + RemoteRefUpdate.Status.REJECTED_OTHER_REASON, + one.getStatus()); + assertSame( + RemoteRefUpdate.Status.REJECTED_REMOTE_CHANGED, + two.getStatus()); + assertEquals(JGitText.get().transactionAborted, one.getMessage()); + } + + @Test + public void pushAtomicDisabled() throws Exception { + List cmds = new ArrayList<>(); + cmds.add(new RemoteRefUpdate( + null, null, + obj1, "refs/heads/one", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + cmds.add(new RemoteRefUpdate( + null, null, + obj2, "refs/heads/two", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + + server.setPerformsAtomicTransactions(false); + Transport tn = testProtocol.open(uri, client, "server"); + try { + tn.setPushAtomic(true); + tn.push(NullProgressMonitor.INSTANCE, cmds); + fail("did not throw TransportException"); + } catch (TransportException e) { + assertEquals( + uri + ": " + JGitText.get().atomicPushNotSupported, + e.getMessage()); + } finally { + tn.close(); + } + } + + private List commands() throws IOException { + List cmds = new ArrayList<>(); + cmds.add(new RemoteRefUpdate( + null, null, + obj1, "refs/heads/one", + true /* force update */, + null /* no local tracking ref */, + ObjectId.zeroId())); + cmds.add(new RemoteRefUpdate( + null, null, + obj2, "refs/heads/two", + true /* force update */, + null /* no local tracking ref */, + obj1)); + return cmds; + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java new file mode 100644 index 000000000..a3b4134ae --- /dev/null +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/PushConnectionTest.java @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2015, Google Inc. + * 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.transport; + +import static org.eclipse.jgit.transport.RemoteRefUpdate.Status.REJECTED_OTHER_REASON; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.util.HashMap; +import java.util.Map; + +import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; +import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; +import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.ObjectInserter; +import org.eclipse.jgit.lib.RefUpdate; +import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.resolver.ReceivePackFactory; +import org.eclipse.jgit.transport.resolver.ServiceNotAuthorizedException; +import org.eclipse.jgit.transport.resolver.ServiceNotEnabledException; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +public class PushConnectionTest { + private URIish uri; + private TestProtocol testProtocol; + private Object ctx = new Object(); + private InMemoryRepository server; + private InMemoryRepository client; + private ObjectId obj1; + private ObjectId obj2; + private ObjectId obj3; + private String refName = "refs/tags/blob"; + + @Before + public void setUp() throws Exception { + server = newRepo("server"); + client = newRepo("client"); + testProtocol = new TestProtocol<>( + null, + new ReceivePackFactory() { + @Override + public ReceivePack create(Object req, Repository db) + throws ServiceNotEnabledException, + ServiceNotAuthorizedException { + return new ReceivePack(db); + } + }); + uri = testProtocol.register(ctx, server); + + try (ObjectInserter ins = server.newObjectInserter()) { + obj1 = ins.insert(Constants.OBJ_BLOB, Constants.encode("test")); + obj3 = ins.insert(Constants.OBJ_BLOB, Constants.encode("not")); + ins.flush(); + + RefUpdate u = server.updateRef(refName); + u.setNewObjectId(obj1); + assertEquals(RefUpdate.Result.NEW, u.update()); + } + + try (ObjectInserter ins = client.newObjectInserter()) { + obj2 = ins.insert(Constants.OBJ_BLOB, Constants.encode("file")); + ins.flush(); + } + } + + @After + public void tearDown() { + Transport.unregister(testProtocol); + } + + private static InMemoryRepository newRepo(String name) { + return new InMemoryRepository(new DfsRepositoryDescription(name)); + } + + @Test + public void testWrongOldIdDoesNotReplace() throws IOException { + RemoteRefUpdate rru = new RemoteRefUpdate(null, null, obj2, refName, + false, null, obj3); + + Map updates = new HashMap<>(); + updates.put(rru.getRemoteName(), rru); + + Transport tn = testProtocol.open(uri, client, "server"); + try { + PushConnection connection = tn.openPush(); + try { + connection.push(NullProgressMonitor.INSTANCE, updates); + } finally { + connection.close(); + } + } finally { + tn.close(); + } + + assertEquals(REJECTED_OTHER_REASON, rru.getStatus()); + assertEquals("invalid old id sent", rru.getMessage()); + } +} diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java index 7f865263d..31b64187c 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/TestProtocolTest.java @@ -125,7 +125,7 @@ public class TestProtocolTest { .setRefSpecs(HEADS) .call(); assertEquals(master, - local.getRepository().getRef("master").getObjectId()); + local.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -142,7 +142,7 @@ public class TestProtocolTest { .setRefSpecs(HEADS) .call(); assertEquals(master, - remote.getRepository().getRef("master").getObjectId()); + remote.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -177,7 +177,7 @@ public class TestProtocolTest { // Expected. } assertEquals(1, rejected.get()); - assertNull(local.getRepository().getRef("master")); + assertNull(local.getRepository().exactRef("refs/heads/master")); git.fetch() .setRemote(user2Uri.toString()) @@ -185,7 +185,7 @@ public class TestProtocolTest { .call(); assertEquals(1, rejected.get()); assertEquals(master, - local.getRepository().getRef("master").getObjectId()); + local.getRepository().exactRef("refs/heads/master").getObjectId()); } } @@ -222,7 +222,7 @@ public class TestProtocolTest { JGitText.get().pushNotPermitted)); } assertEquals(1, rejected.get()); - assertNull(remote.getRepository().getRef("master")); + assertNull(remote.getRepository().exactRef("refs/heads/master")); git.push() .setRemote(user2Uri.toString()) @@ -230,7 +230,7 @@ public class TestProtocolTest { .call(); assertEquals(1, rejected.get()); assertEquals(master, - remote.getRepository().getRef("master").getObjectId()); + remote.getRepository().exactRef("refs/heads/master").getObjectId()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java index 745c32201..76eb18afd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/URIishTest.java @@ -928,4 +928,19 @@ public class URIishTest { } } } + + @Test + public void testStringConstructor() throws Exception { + String str = "http://example.com/"; + URIish u = new URIish(str); + assertEquals("example.com", u.getHost()); + assertEquals("/", u.getPath()); + assertEquals(str, u.toString()); + + str = "http://example.com"; + u = new URIish(str); + assertEquals("example.com", u.getHost()); + assertEquals("", u.getPath()); + assertEquals(str, u.toString()); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java index 90d78e4b6..ac2bfd12f 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/WalkEncryptionTest.java @@ -43,6 +43,20 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.UTF_8; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListPBE; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.cryptoCipherListTrans; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.folderDelete; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.permitLongTests; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.policySetup; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.product; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.proxySetup; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.publicAddress; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.reportPolicy; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.securityProviderName; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.textWrite; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.transferStream; +import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.verifyFileContent; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotNull; @@ -59,7 +73,10 @@ import java.io.InputStream; import java.io.InputStreamReader; import java.io.OutputStream; import java.io.PrintWriter; +import java.net.SocketTimeoutException; import java.net.URL; +import java.net.URLConnection; +import java.net.UnknownHostException; import java.nio.charset.Charset; import java.nio.file.Files; import java.security.GeneralSecurityException; @@ -94,8 +111,6 @@ import org.junit.runners.Suite; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import static org.eclipse.jgit.transport.WalkEncryptionTest.Util.*; - /** * Amazon S3 encryption pipeline test. * @@ -401,14 +416,22 @@ public class WalkEncryptionTest { * @throws Exception */ static String publicAddress() throws Exception { - String service = "http://checkip.amazonaws.com"; - URL url = new URL(service); - BufferedReader reader = new BufferedReader( - new InputStreamReader(url.openStream())); try { - return reader.readLine(); - } finally { - reader.close(); + String service = "http://checkip.amazonaws.com"; + URL url = new URL(service); + URLConnection c = url.openConnection(); + c.setConnectTimeout(500); + c.setReadTimeout(500); + BufferedReader reader = new BufferedReader( + new InputStreamReader(c.getInputStream())); + try { + return reader.readLine(); + } finally { + reader.close(); + } + } catch (UnknownHostException | SocketTimeoutException e) { + return "Can't reach http://checkip.amazonaws.com to" + + " determine public address"; } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java index 52da69ef5..f5e97c2dc 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/CanonicalTreeParserTest.java @@ -43,6 +43,8 @@ package org.eclipse.jgit.treewalk; +import static org.eclipse.jgit.lib.FileMode.REGULAR_FILE; +import static org.eclipse.jgit.lib.FileMode.SYMLINK; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertSame; @@ -50,9 +52,11 @@ import static org.junit.Assert.assertTrue; import java.io.ByteArrayOutputStream; +import org.eclipse.jgit.errors.CorruptObjectException; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.FileMode; import org.eclipse.jgit.lib.ObjectId; +import org.eclipse.jgit.lib.TreeFormatter; import org.eclipse.jgit.util.RawParseUtils; import org.junit.Before; import org.junit.Test; @@ -369,4 +373,41 @@ public class CanonicalTreeParserTest { assertEquals(name, RawParseUtils.decode(Constants.CHARSET, ctp.path, ctp.pathOffset, ctp.pathLen)); } + + @Test + public void testFindAttributesWhenFirst() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append(".gitattributes", REGULAR_FILE, hash_a); + ctp.reset(tree.toByteArray()); + + assertTrue(ctp.findFile(".gitattributes")); + assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode()); + assertEquals(".gitattributes", ctp.getEntryPathString()); + assertEquals(hash_a, ctp.getEntryObjectId()); + } + + @Test + public void testFindAttributesWhenSecond() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append(".config", SYMLINK, hash_a); + tree.append(".gitattributes", REGULAR_FILE, hash_foo); + ctp.reset(tree.toByteArray()); + + assertTrue(ctp.findFile(".gitattributes")); + assertEquals(REGULAR_FILE.getBits(), ctp.getEntryRawMode()); + assertEquals(".gitattributes", ctp.getEntryPathString()); + assertEquals(hash_foo, ctp.getEntryObjectId()); + } + + @Test + public void testFindAttributesWhenMissing() throws CorruptObjectException { + TreeFormatter tree = new TreeFormatter(); + tree.append("src", REGULAR_FILE, hash_a); + tree.append("zoo", REGULAR_FILE, hash_foo); + ctp.reset(tree.toByteArray()); + + assertFalse(ctp.findFile(".gitattributes")); + assertEquals(11, ctp.idOffset()); // Did not walk the entire tree. + assertEquals("src", ctp.getEntryPathString()); + } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java index 88754109f..429f35ea4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/FileTreeIteratorJava7Test.java @@ -67,6 +67,7 @@ import org.junit.Test; public class FileTreeIteratorJava7Test extends RepositoryTestCase { @Test public void testFileModeSymLinkIsNotATree() throws IOException { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); FS fs = db.getFS(); // mål = target in swedish, just to get som unicode in here writeTrashFile("mål/data", "targetdata"); @@ -163,6 +164,7 @@ public class FileTreeIteratorJava7Test extends RepositoryTestCase { */ @Test public void testSymlinkActuallyModified() throws Exception { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); final String NORMALIZED = "target"; final byte[] NORMALIZED_BYTES = Constants.encode(NORMALIZED); try (ObjectInserter oi = db.newObjectInserter()) { diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java index bb1f2a639..1328b38e6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/treewalk/TreeWalkJava7Test.java @@ -55,6 +55,7 @@ import org.junit.Test; public class TreeWalkJava7Test extends RepositoryTestCase { @Test public void testSymlinkToDirNotRecursingViaSymlink() throws Exception { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); FS fs = db.getFS(); assertTrue(fs.supportsSymlinks()); writeTrashFile("target/data", "targetdata"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java index e6a244e14..53b6fecfd 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FSJava7Test.java @@ -57,6 +57,7 @@ import java.util.Set; import org.eclipse.jgit.junit.RepositoryTestCase; import org.junit.After; +import org.junit.Assume; import org.junit.Before; import org.junit.Test; @@ -87,6 +88,7 @@ public class FSJava7Test { */ @Test public void testSymlinkAttributes() throws IOException, InterruptedException { + Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); FS fs = FS.DETECTED; File link = new File(trash, "ä"); File target = new File(trash, "å"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java index 4625f3068..cc1fdc21b 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/FileUtils7Test.java @@ -73,6 +73,7 @@ public class FileUtils7Test { @Test public void testDeleteSymlinkToDirectoryDoesNotDeleteTarget() throws IOException { + org.junit.Assume.assumeTrue(FS.DETECTED.supportsSymlinks()); FS fs = FS.DETECTED; File dir = new File(trash, "dir"); File file = new File(dir, "file"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java index 8aa14c521..e07076e32 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/HookTest.java @@ -92,11 +92,9 @@ public class HookTest extends RepositoryTestCase { fail("expected commit-msg hook to abort commit"); } catch (AbortedByHookException e) { assertEquals("unexpected error message from commit-msg hook", - "Rejected by \"commit-msg\" hook.\nstderr" - + System.lineSeparator(), + "Rejected by \"commit-msg\" hook.\nstderr\n", e.getMessage()); - assertEquals("unexpected output from commit-msg hook", - "test" + System.lineSeparator(), + assertEquals("unexpected output from commit-msg hook", "test\n", out.toString()); } } @@ -114,7 +112,7 @@ public class HookTest extends RepositoryTestCase { ByteArrayOutputStream out = new ByteArrayOutputStream(); git.commit().setMessage("commit") .setHookOutputStream(new PrintStream(out)).call(); - assertEquals(".git/COMMIT_EDITMSG" + System.lineSeparator(), + assertEquals(".git/COMMIT_EDITMSG\n", out.toString("UTF-8")); } @@ -147,11 +145,10 @@ public class HookTest extends RepositoryTestCase { new String[] { "arg1", "arg2" }, new PrintStream(out), new PrintStream(err), "stdin"); - assertEquals("unexpected hook output", "test arg1 arg2" - + System.lineSeparator() + "stdin" + System.lineSeparator(), + + assertEquals("unexpected hook output", "test arg1 arg2\nstdin\n", out.toString("UTF-8")); - assertEquals("unexpected output on stderr stream", - "stderr" + System.lineSeparator(), + assertEquals("unexpected output on stderr stream", "stderr\n", err.toString("UTF-8")); assertEquals("unexpected exit code", 0, res.getExitCode()); assertEquals("unexpected process status", ProcessResult.Status.OK, @@ -175,11 +172,9 @@ public class HookTest extends RepositoryTestCase { fail("expected pre-commit hook to abort commit"); } catch (AbortedByHookException e) { assertEquals("unexpected error message from pre-commit hook", - "Rejected by \"pre-commit\" hook.\nstderr" - + System.lineSeparator(), + "Rejected by \"pre-commit\" hook.\nstderr\n", e.getMessage()); - assertEquals("unexpected output from pre-commit hook", - "test" + System.lineSeparator(), + assertEquals("unexpected output from pre-commit hook", "test\n", out.toString()); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java index 82beab2dc..7c0985ef4 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/util/RunExternalScriptTest.java @@ -52,16 +52,17 @@ import java.io.IOException; import java.io.InputStream; import org.eclipse.jgit.junit.JGitTestUtil; +import org.eclipse.jgit.util.FS.ExecutionResult; import org.junit.Before; import org.junit.Test; public class RunExternalScriptTest { + private static final String LF = "\n"; + private ByteArrayOutputStream out; private ByteArrayOutputStream err; - private String sep = System.getProperty("line.separator"); - @Before public void setUp() throws Exception { out = new ByteArrayOutputStream(); @@ -73,7 +74,7 @@ public class RunExternalScriptTest { String inputStr = "a\nb\rc\r\nd"; File script = writeTempFile("cat -"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath()), out, err, + new ProcessBuilder("sh", script.getPath()), out, err, new ByteArrayInputStream(inputStr.getBytes())); assertEquals(0, rc); assertEquals(inputStr, new String(out.toByteArray())); @@ -84,7 +85,7 @@ public class RunExternalScriptTest { public void testCopyNullStdIn() throws IOException, InterruptedException { File script = writeTempFile("cat -"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath()), out, err, + new ProcessBuilder("sh", script.getPath()), out, err, (InputStream) null); assertEquals(0, rc); assertEquals("", new String(out.toByteArray())); @@ -94,7 +95,8 @@ public class RunExternalScriptTest { @Test public void testArguments() throws IOException, InterruptedException { File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6"); - int rc = FS.DETECTED.runProcess(new ProcessBuilder("/bin/bash", + int rc = FS.DETECTED.runProcess( + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), out, err, (InputStream) null); assertEquals(0, rc); assertEquals("3,a,b,c,,,\n", new String(out.toByteArray())); @@ -105,7 +107,7 @@ public class RunExternalScriptTest { public void testRc() throws IOException, InterruptedException { File script = writeTempFile("exit 3"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), out, err, (InputStream) null); assertEquals(3, rc); assertEquals("", new String(out.toByteArray())); @@ -116,7 +118,7 @@ public class RunExternalScriptTest { public void testNullStdout() throws IOException, InterruptedException { File script = writeTempFile("echo hi"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath()), null, err, + new ProcessBuilder("sh", script.getPath()), null, err, (InputStream) null); assertEquals(0, rc); assertEquals("", new String(out.toByteArray())); @@ -127,11 +129,11 @@ public class RunExternalScriptTest { public void testStdErr() throws IOException, InterruptedException { File script = writeTempFile("echo hi >&2"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath()), null, err, + new ProcessBuilder("sh", script.getPath()), null, err, (InputStream) null); assertEquals(0, rc); assertEquals("", new String(out.toByteArray())); - assertEquals("hi" + sep, new String(err.toByteArray())); + assertEquals("hi" + LF, new String(err.toByteArray())); } @Test @@ -139,11 +141,11 @@ public class RunExternalScriptTest { String inputStr = "a\nb\rc\r\nd"; File script = writeTempFile("echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), out, err, new ByteArrayInputStream(inputStr.getBytes())); assertEquals(5, rc); assertEquals(inputStr, new String(out.toByteArray())); - assertEquals("3,a,b,c,,," + sep, new String(err.toByteArray())); + assertEquals("3,a,b,c,,," + LF, new String(err.toByteArray())); } @Test(expected = IOException.class) @@ -158,11 +160,50 @@ public class RunExternalScriptTest { public void testWrongScript() throws IOException, InterruptedException { File script = writeTempFile("cat-foo -"); int rc = FS.DETECTED.runProcess( - new ProcessBuilder("/bin/sh", script.getPath(), "a", "b", "c"), + new ProcessBuilder("sh", script.getPath(), "a", "b", "c"), out, err, (InputStream) null); assertEquals(127, rc); } + @Test + public void testCopyStdInExecute() + throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile("cat -"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath()); + ExecutionResult res = FS.DETECTED.execute(pb, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(0, res.getRc()); + assertEquals(inputStr, new String(res.getStdout().toByteArray())); + assertEquals("", new String(res.getStderr().toByteArray())); + } + + @Test + public void testStdErrExecute() throws IOException, InterruptedException { + File script = writeTempFile("echo hi >&2"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath()); + ExecutionResult res = FS.DETECTED.execute(pb, null); + assertEquals(0, res.getRc()); + assertEquals("", new String(res.getStdout().toByteArray())); + assertEquals("hi" + LF, new String(res.getStderr().toByteArray())); + } + + @Test + public void testAllTogetherBinExecute() + throws IOException, InterruptedException { + String inputStr = "a\nb\rc\r\nd"; + File script = writeTempFile( + "echo $#,$1,$2,$3,$4,$5,$6 >&2 ; cat -; exit 5"); + ProcessBuilder pb = new ProcessBuilder("sh", script.getPath(), "a", + "b", "c"); + ExecutionResult res = FS.DETECTED.execute(pb, + new ByteArrayInputStream(inputStr.getBytes())); + assertEquals(5, res.getRc()); + assertEquals(inputStr, new String(res.getStdout().toByteArray())); + assertEquals("3,a,b,c,,," + LF, + new String(res.getStderr().toByteArray())); + } + private File writeTempFile(String body) throws IOException { File f = File.createTempFile("RunProcessTestScript_", ""); JGitTestUtil.write(f, body); diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index ed330b151..b2a8f677f 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,5 +1,13 @@ + + + + + + + + @@ -14,6 +22,14 @@ + + + + + + + + @@ -35,6 +51,20 @@ + + + + + + + + + + + + + + diff --git a/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs b/org.eclipse.jgit/.settings/org.eclipse.jdt.core.prefs index 195987db6..45d6d2c4c 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 b6899b280..2a953b559 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 51e44fd77..0e9b0b59e 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 000000000..08f81cb9f --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/annotations/NonNull.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2015, Andrey Loskutov + * 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}. + *

+ * 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 254920e7a..7b9156710 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. *

- * 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 + * Guice. 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 + * split-package + * problems. + *

+ * 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 + *

    + *
  • Binding a {@code null} value to the entity is legal. + *
  • Dereferencing the entity is unsafe and can trigger a + * {@code NullPointerException}. + *
+ *

+ * 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. + *

+ * Warning: 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 + * The checker-framework manual + * + * @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 de6c32a80..67fb342fe 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 { 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 { 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 { 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 8d85bfcb1..8743ea9ac 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 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 6174d48d3..9466dab74 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 { 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 addca4c46..2cd5f59a7 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 227e32236..f5b82bdd7 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); @@ -396,6 +396,29 @@ public class PushCommand extends return this; } + /** + * @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 */ 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 ff2900842..8582bbb0d 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 { } 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 { 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 { } 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 { 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 000000000..679566903 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteAddCommand.java @@ -0,0 +1,133 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 Git + * documentation about Remote + * + * @since 4.2 + */ +public class RemoteAddCommand extends GitCommand { + + 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 000000000..f778eaa28 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteListCommand.java @@ -0,0 +1,91 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 Git + * documentation about Remote + * + * @since 4.2 + */ +public class RemoteListCommand extends GitCommand> { + + /** + * @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 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 000000000..5782bf61b --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteRemoveCommand.java @@ -0,0 +1,110 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 Git + * documentation about Remote + * + * @since 4.2 + */ +public class RemoteRemoveCommand extends GitCommand { + + 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 000000000..6bd2ac799 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RemoteSetUrlCommand.java @@ -0,0 +1,155 @@ +/* + * Copyright (C) 2015, Kaloyan Raev + * 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 Git + * documentation about Remote + * + * @since 4.2 + */ +public class RemoteSetUrlCommand extends GitCommand { + + 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 + * true to set the push url, false 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 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 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 607253b76..0731dd45e 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 { 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 000000000..fbc30ef16 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/errors/FilterFailedException.java @@ -0,0 +1,144 @@ +/* + * Copyright (C) 2015, Christian Halstrick 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 d3ce68518..905ad7692 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; *

  • Set - represented by {@link State#SET}
  • *
  • Unset - represented by {@link State#UNSET}
  • *
  • Set to a value - represented by {@link State#CUSTOM}
  • - *
  • Unspecified - null is used instead of an instance of this - * class
  • + *
  • 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
  • * *

    * @@ -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 000000000..0810e3168 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/Attributes.java @@ -0,0 +1,202 @@ +/* + * Copyright (C) 2015, Ivan Motsch + * + * 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 + *

    + * + * @since 4.2 + */ +public final class Attributes { + private final Map 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 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 null) + */ + 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 null) + */ + 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 70f56ff96..5c0aba2e0 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 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 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 000000000..6f2ebad67 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesNodeProvider.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * 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 000000000..1037f697d --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/attributes/AttributesProvider.java @@ -0,0 +1,55 @@ +/* + * Copyright (C) 2015, Christian Halstrick + * 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 bcac14b5f..35d18c4b2 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 0dc4b0578..444ab1cb8 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 6d9a32db9..fa0339544 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; @@ -144,6 +148,28 @@ public class DirCache { return new DirCache(null, null); } + /** + * 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. *

    @@ -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 paths = new ArrayList(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 6c7a70c52..cfebe2d07 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 0036ab508..4eb688170 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 updated = new HashMap(); + private HashMap updated = new HashMap(); private ArrayList conflicts = new ArrayList(); @@ -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 getUpdated() { + public Map 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. + * + *

    + * Note: 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. + *

    + * + *

    + * 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 + *

    + * + * @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 eef2e6d3c..c8bc0960f 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 354a07439..ad93f7213 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. *

    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 000000000..70f650dde --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/errors/IndexReadException.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2015, Christian Halstrick 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 1d2d3bfaa..ff9f233aa 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 { 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 2e6582819..a501fee90 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 { */ @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 e39469bd8..796eaaebf 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 122f6d3d1..0d5fd0f85 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. 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 832e4fb6a..1c664b409 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. + *

    + * Useful for testing atomic support enabled or disabled. + * + * @param atomic + */ + public void setPerformsAtomicTransactions(boolean atomic) { + performsAtomicTransactions = atomic; + } + private class MemObjDatabase extends DfsObjDatabase { private List packs = new ArrayList(); @@ -235,41 +254,143 @@ public class InMemoryRepository extends DfsRepository { private class MemRefDatabase extends DfsRefDatabase { private final ConcurrentMap refs = new ConcurrentHashMap(); + 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 ids = new RefList.Builder(); RefList.Builder sym = new RefList.Builder(); - 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 cmds) { + // Validate that the target exists in a new RevWalk, as the RevWalk + // from the RefUpdate might be reading back unflushed objects. + Map 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 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 995621ee3..490cbcaa8 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 Arthur Daussy + * + */ + 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 e7005c247..4c40538b6 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 listRefLogObjects(Ref ref, long minTime) throws IOException { - List rlEntries = repo.getReflogReader(ref.getName()) + ReflogReader reflogReader = repo.getReflogReader(ref.getName()); + if (reflogReader == null) { + return Collections.emptySet(); + } + List rlEntries = reflogReader .getReverseEntries(); if (rlEntries == null || rlEntries.isEmpty()) return Collections. emptySet(); @@ -635,10 +640,7 @@ public class GC { */ private Set 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 000000000..454d3bff6 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/GlobalAttributesNode.java @@ -0,0 +1,87 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * 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 000000000..bda5cbeba --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/InfoAttributesNode.java @@ -0,0 +1,81 @@ +/* + * Copyright (C) 2014, Arthur Daussy + * Copyright (C) 2015, Christian Halstrick + * 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 50297a97a..e23ca741b 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 e7ef127dd..bd1d488d9 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 731bbd383..69f7e9707 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 @@ -261,6 +261,30 @@ public class RefDirectory extends RefDatabase { return loose; } + @Override + public Ref exactRef(String name) throws IOException { + RefList 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 packed = getPackedRefs(); @@ -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 dadc63119..2f583b275 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 2cc256396..a02743720 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 535a6ee17..d30edaf41 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -272,6 +272,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 * @@ -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 3fde2f919..9e474f86a 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 filters = new ArrayList(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 ef22fb90f..986666f2f 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. *

    @@ -132,6 +135,7 @@ public abstract class RefDatabase { * @since 2.3 * @see #isNameConflicting(String) */ + @NonNull public Collection getConflictingNames(String name) throws IOException { Map 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 exactRef(String... refs) throws IOException { Map 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 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 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 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 05eb31183..59f852b8c 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 d4c72cb9c..49a970d03 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 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 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 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> getAllRefsByPeeledObjectId() { Map allRefs = getAllRefs(); Map> ret = new HashMap>(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 refName, i.e. without * the refs/remotes/<remote> prefix, if * refName 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 refName, i.e. without the * refs/remotes/<remote> prefix, if * refName 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 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 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 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 aef47c58c..e0556447c 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 parents = new ArrayList(); @@ -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 8a6343c3c..de08e4b6a 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 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 5509fc6a7..29c7f9841 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 24fb3be64..0834c359a 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 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 7e9434a0f..622680a27 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 3594ea91b..998f28001 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 8947f2779..d436e08df 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 9721ee9eb..4fd192dbb 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 prepareRemoteUpdates() throws TransportException { + boolean atomic = transport.isPushAtomic(); final Map result = new HashMap(); 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 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 7c44dba4a..5702b6d7b 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 filter(List commands, - final Result want) { - List r = new ArrayList(commands.size()); - for (final ReceiveCommand cmd : commands) { + public static List filter(Iterable in, + Result want) { + List 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 filter(List commands, + Result want) { + return filter((Iterable) 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 1b82a3610..5c5834618 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 cc7db47df..6af153cbc 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; @@ -969,6 +972,31 @@ public abstract class Transport { this.pushThin = pushThin; } + /** + * 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). + *

    + * 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 7729c11ff..23c506b12 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 b27fa0d6b..52f0f0456 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 3700b4955..9aeb840eb 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 41e593eaa..5e7188957 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; @@ -92,6 +93,13 @@ public abstract class AbstractTreeIterator { /** The iterator this current entry is path equal to. */ 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. * @@ -319,6 +327,42 @@ public abstract class AbstractTreeIterator { return pathCompare(p.path, cPos, p.pathLen, pMode, cPos); } + /** + * 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. * 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 0805e5068..c038f0772 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. 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 8d2cb1d8c..accf4956f 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 2d6acbddf..350f56396 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 06e828419..06dc0bf6d 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,9 +96,44 @@ 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 filterCommandsByNameDotType = new HashMap(); + + /** + * @param operationType + * @since 4.2 + */ + public void setOperationType(OperationType operationType) { + this.operationType = operationType; + } + /** * Open a tree walk and filter to exactly one path. *

    @@ -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}. + *

    + * 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. + *

    + * + * @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. + * + *

    Git attribute computation

    + * + *
      + *
    • Get the attributes matching the current path entry from the info file + * (see {@link AttributesNodeProvider#getInfoAttributesNode()}).
    • + *
    • 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.
    • + *
    • In the end, completes the list of matching attributes using the + * global attribute file define in the configuration (see + * {@link AttributesNodeProvider#getGlobalAttributesNode()})
    • + * + *
    + * + * + *

    Iterator constraints

    + * + *

    + * 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}. + *

    + * + * @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 getParent(T current, + Class type) { + if (current != null) { + AbstractTreeIterator parent = current.parent; + if (type.isInstance(parent)) { + return type.cast(parent); + } + } + return null; + } + + private T getTree(Class 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. + *

    + * This method implements the fallback mechanism between the index and the + * working tree depending on the operation type + *

    + * + * @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 73ab04f9c..94beeeb56 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; @@ -146,19 +150,6 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { /** The offset of the content id in {@link #idBuffer()} */ 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. * @@ -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_CMP = new Comparator() { 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 + * null 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 e407dd8be..253acbb76 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 null, in which case the processes standard - * output will be lost. If binary is set to false - * 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 null, 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 null, in which case the process doesn't get - * any data over stdin. If binary is set to - * false 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 { 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 f0a2e721a..defe14f0e 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 2450be4c1..ec581b397 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 6d0318c02..727ea79cc 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 4795c89e7..9860ef070 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. + *

    + * 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 ca47f50fd..3cd5929c7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -246,6 +246,37 @@ public abstract class TemporaryBuffer extends OutputStream { return out; } + /** + * 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 + *

    + * 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. *