diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java index d1c258047..a8e312d3f 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/ReceivePackServlet.java @@ -76,6 +76,7 @@ import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.errors.UnpackException; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.InternalHttpServerGlue; import org.eclipse.jgit.transport.ReceivePack; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.transport.resolver.ReceivePackFactory; @@ -100,6 +101,9 @@ class ReceivePackServlet extends HttpServlet { throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { ReceivePack rp = receivePackFactory.create(req, db); + InternalHttpServerGlue.setPeerUserAgent( + rp, + req.getHeader(HDR_USER_AGENT)); req.setAttribute(ATTRIBUTE_HANDLER, rp); } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java index 481075377..51332194b 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/SmartServiceInfoRefs.java @@ -101,7 +101,7 @@ abstract class SmartServiceInfoRefs implements Filter { res.sendError(SC_UNAUTHORIZED); return; } catch (ServiceNotEnabledException e) { - sendError(req, res, SC_FORBIDDEN); + sendError(req, res, SC_FORBIDDEN, e.getMessage()); return; } diff --git a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java index c5272b55e..7aefcbd80 100644 --- a/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java +++ b/org.eclipse.jgit.http.server/src/org/eclipse/jgit/http/server/UploadPackServlet.java @@ -74,6 +74,7 @@ import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jgit.lib.Repository; +import org.eclipse.jgit.transport.InternalHttpServerGlue; import org.eclipse.jgit.transport.RefAdvertiser.PacketLineOutRefAdvertiser; import org.eclipse.jgit.transport.UploadPack; import org.eclipse.jgit.transport.UploadPackInternalServerErrorException; @@ -100,6 +101,9 @@ class UploadPackServlet extends HttpServlet { throws IOException, ServiceNotEnabledException, ServiceNotAuthorizedException { UploadPack up = uploadPackFactory.create(req, db); + InternalHttpServerGlue.setPeerUserAgent( + up, + req.getHeader(HDR_USER_AGENT)); req.setAttribute(ATTRIBUTE_HANDLER, up); } diff --git a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF index b20464e39..327a697f6 100644 --- a/org.eclipse.jgit.junit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.junit/META-INF/MANIFEST.MF @@ -14,6 +14,7 @@ Import-Package: org.eclipse.jgit.api;version="[4.0.0,4.1.0)", org.eclipse.jgit.internal.storage.file;version="[4.0.0,4.1.0)", org.eclipse.jgit.internal.storage.pack;version="[4.0.0,4.1.0)", org.eclipse.jgit.lib;version="[4.0.0,4.1.0)", + org.eclipse.jgit.merge;version="[4.0.0,4.1.0)", org.eclipse.jgit.revwalk;version="[4.0.0,4.1.0)", org.eclipse.jgit.storage.file;version="[4.0.0,4.1.0)", org.eclipse.jgit.treewalk;version="[4.0.0,4.1.0)", 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 ce33ec709..925a6b021 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 @@ -52,11 +52,13 @@ import java.io.IOException; import java.io.OutputStream; import java.security.MessageDigest; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.List; import java.util.Set; +import java.util.TimeZone; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.dircache.DirCache; @@ -88,6 +90,8 @@ import org.eclipse.jgit.lib.RefUpdate; import org.eclipse.jgit.lib.RefWriter; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.TagBuilder; +import org.eclipse.jgit.merge.MergeStrategy; +import org.eclipse.jgit.merge.ThreeWayMerger; import org.eclipse.jgit.revwalk.ObjectWalk; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; @@ -187,6 +191,11 @@ public class TestRepository { return new Date(now); } + /** @return timezone used for default identities. */ + public TimeZone getTimeZone() { + return defaultCommitter.getTimeZone(); + } + /** * Adjust the current time that will used by the next commit. * @@ -615,6 +624,59 @@ public class TestRepository { } } + /** + * Cherry-pick a commit onto HEAD. + *

+ * This differs from {@code git cherry-pick} in that it works in a bare + * repository. As a result, any merge failure results in an exception, as + * there is no way to recover. + * + * @param id + * commit-ish to cherry-pick. + * @return newly created commit, or null if no work was done due to the + * resulting tree being identical. + * @throws Exception + */ + public RevCommit cherryPick(AnyObjectId id) throws Exception { + RevCommit commit = pool.parseCommit(id); + pool.parseBody(commit); + if (commit.getParentCount() != 1) + throw new IOException(String.format( + "Expected 1 parent for %s, found: %s", + id.name(), Arrays.asList(commit.getParents()))); + RevCommit parent = commit.getParent(0); + pool.parseHeaders(parent); + + Ref headRef = db.getRef(Constants.HEAD); + if (headRef == null) + throw new IOException("Missing HEAD"); + RevCommit head = pool.parseCommit(headRef.getObjectId()); + + ThreeWayMerger merger = MergeStrategy.RECURSIVE.newMerger(db, true); + merger.setBase(parent.getTree()); + if (merger.merge(head, commit)) { + if (AnyObjectId.equals(head.getTree(), merger.getResultTreeId())) + return null; + tick(1); + org.eclipse.jgit.lib.CommitBuilder b = + new org.eclipse.jgit.lib.CommitBuilder(); + b.setParentId(head); + b.setTreeId(merger.getResultTreeId()); + b.setAuthor(commit.getAuthorIdent()); + b.setCommitter(new PersonIdent(defaultCommitter, new Date(now))); + b.setMessage(commit.getFullMessage()); + ObjectId result; + try (ObjectInserter ins = inserter) { + result = ins.insert(b); + ins.flush(); + } + update(Constants.HEAD, result); + return pool.parseCommit(result); + } else { + throw new IOException("Merge conflict"); + } + } + /** * Update the dumb client server info files. * diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml index 3f552b666..5c6d51a4d 100644 --- a/org.eclipse.jgit.packaging/pom.xml +++ b/org.eclipse.jgit.packaging/pom.xml @@ -68,6 +68,10 @@ repo.eclipse.org.cbi-releases https://repo.eclipse.org/content/repositories/cbi-releases/ + + repo.eclipse.org.cbi-snapshots + https://repo.eclipse.org/content/repositories/cbi-snapshots/ + @@ -210,7 +214,7 @@ org.eclipse.cbi.maven.plugins eclipse-jarsigner-plugin - 1.1.1 + 1.1.2-SNAPSHOT org.codehaus.mojo diff --git a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java index 38c3a2fcd..7151de794 100644 --- a/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java +++ b/org.eclipse.jgit.pgm/src/org/eclipse/jgit/pgm/Main.java @@ -47,14 +47,15 @@ package org.eclipse.jgit.pgm; import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.lang.reflect.InvocationTargetException; import java.net.MalformedURLException; import java.net.URL; import java.text.MessageFormat; import java.util.ArrayList; import java.util.List; -import org.eclipse.jgit.console.ConsoleAuthenticator; -import org.eclipse.jgit.console.ConsoleCredentialsProvider; +import org.eclipse.jgit.awtui.AwtAuthenticator; +import org.eclipse.jgit.awtui.AwtCredentialsProvider; import org.eclipse.jgit.errors.TransportException; import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.RepositoryBuilder; @@ -115,8 +116,10 @@ public class Main { */ protected void run(final String[] argv) { try { - ConsoleAuthenticator.install(); - ConsoleCredentialsProvider.install(); + if (!installConsole()) { + AwtAuthenticator.install(); + AwtCredentialsProvider.install(); + } configureHttpProxy(); execute(argv); } catch (Die err) { @@ -246,6 +249,45 @@ public class Main { return rb.build(); } + private static boolean installConsole() { + try { + install("org.eclipse.jgit.console.ConsoleAuthenticator"); //$NON-NLS-1$ + install("org.eclipse.jgit.console.ConsoleCredentialsProvider"); //$NON-NLS-1$ + return true; + } catch (ClassNotFoundException e) { + return false; + } catch (NoClassDefFoundError e) { + return false; + } catch (UnsupportedClassVersionError e) { + return false; + + } catch (IllegalArgumentException e) { + throw new RuntimeException(CLIText.get().cannotSetupConsole, e); + } catch (SecurityException e) { + throw new RuntimeException(CLIText.get().cannotSetupConsole, e); + } catch (IllegalAccessException e) { + throw new RuntimeException(CLIText.get().cannotSetupConsole, e); + } catch (InvocationTargetException e) { + throw new RuntimeException(CLIText.get().cannotSetupConsole, e); + } catch (NoSuchMethodException e) { + throw new RuntimeException(CLIText.get().cannotSetupConsole, e); + } + } + + private static void install(final String name) + throws IllegalAccessException, InvocationTargetException, + NoSuchMethodException, ClassNotFoundException { + try { + Class.forName(name).getMethod("install").invoke(null); //$NON-NLS-1$ + } catch (InvocationTargetException e) { + if (e.getCause() instanceof RuntimeException) + throw (RuntimeException) e.getCause(); + if (e.getCause() instanceof Error) + throw (Error) e.getCause(); + throw e; + } + } + /** * Configure the JRE's standard HTTP based on http_proxy. *

diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java index 25534fdda..9ad845b2a 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/PullCommandWithRebaseTest.java @@ -180,7 +180,7 @@ public class PullCommandWithRebaseTest extends RepositoryTestCase { + remoteUri + "\nSource change\n=======\nTarget change\n>>>>>>> 42453fd Target change in local\n"; assertFileContentsEqual(targetFile, result); - assertEquals(RepositoryState.REBASING_INTERACTIVE, target + assertEquals(RepositoryState.REBASING_MERGE, target .getRepository().getRepositoryState()); } @@ -225,7 +225,7 @@ public class PullCommandWithRebaseTest extends RepositoryTestCase { String result = "<<<<<<< Upstream, based on branch 'master' of local repository\n" + "Master change\n=======\nSlave change\n>>>>>>> 4049c9e Source change in based on master\n"; assertFileContentsEqual(targetFile, result); - assertEquals(RepositoryState.REBASING_INTERACTIVE, target + assertEquals(RepositoryState.REBASING_MERGE, target .getRepository().getRepositoryState()); } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java index 8e64776f7..6b641c495 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/RebaseCommandTest.java @@ -824,7 +824,7 @@ public class RebaseCommandTest extends RepositoryTestCase { "<<<<<<< Upstream, based on master\n1master\n=======\n1topic", ">>>>>>> e0d1dea change file1 in topic\n2\n3\ntopic4"); - assertEquals(RepositoryState.REBASING_INTERACTIVE, db + assertEquals(RepositoryState.REBASING_MERGE, db .getRepositoryState()); assertTrue(new File(db.getDirectory(), "rebase-merge").exists()); // the first one should be included, so we should have left two picks in @@ -887,7 +887,7 @@ public class RebaseCommandTest extends RepositoryTestCase { "<<<<<<< Upstream, based on master\n1master\n=======\n1topic", ">>>>>>> e0d1dea change file1 in topic\n2\n3\ntopic4"); - assertEquals(RepositoryState.REBASING_INTERACTIVE, + assertEquals(RepositoryState.REBASING_MERGE, db.getRepositoryState()); assertTrue(new File(db.getDirectory(), "rebase-merge").exists()); // the first one should be included, so we should have left two picks in @@ -1009,7 +1009,7 @@ public class RebaseCommandTest extends RepositoryTestCase { res = git.rebase().setOperation(Operation.CONTINUE).call(); assertNotNull(res); assertEquals(Status.NOTHING_TO_COMMIT, res.getStatus()); - assertEquals(RepositoryState.REBASING_INTERACTIVE, + assertEquals(RepositoryState.REBASING_MERGE, db.getRepositoryState()); git.rebase().setOperation(Operation.SKIP).call(); @@ -1300,7 +1300,7 @@ public class RebaseCommandTest extends RepositoryTestCase { // user can decide what to do. if he accidentally committed, reset soft, // and continue, if he really has nothing to commit, skip. assertEquals(Status.NOTHING_TO_COMMIT, res.getStatus()); - assertEquals(RepositoryState.REBASING_INTERACTIVE, + assertEquals(RepositoryState.REBASING_MERGE, db.getRepositoryState()); git.rebase().setOperation(Operation.SKIP).call(); @@ -1401,7 +1401,7 @@ public class RebaseCommandTest extends RepositoryTestCase { assertEquals(Status.STOPPED, res.getStatus()); assertEquals(conflicting, res.getCurrentCommit()); - assertEquals(RepositoryState.REBASING_INTERACTIVE, db + assertEquals(RepositoryState.REBASING_MERGE, db .getRepositoryState()); assertTrue(new File(db.getDirectory(), "rebase-merge").exists()); // the first one should be included, so we should have left two picks in 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 cefc779a2..fbb9eecdf 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 @@ -46,6 +46,7 @@ package org.eclipse.jgit.junit; import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertSame; import static org.junit.Assert.assertTrue; @@ -55,9 +56,9 @@ import java.util.regex.Pattern; import org.eclipse.jgit.internal.storage.dfs.DfsRepositoryDescription; import org.eclipse.jgit.internal.storage.dfs.InMemoryRepository; -import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Constants; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Ref; @@ -296,6 +297,83 @@ public class TestRepositoryTest { assertEquals("refs/heads/master", ref.getTarget().getName()); } + @Test + public void cherryPick() throws Exception { + repo.updateRef("HEAD").link("refs/heads/master"); + RevCommit head = tr.branch("master").commit() + .add("foo", "foo contents\n") + .create(); + rw.parseBody(head); + RevCommit toPick = tr.commit() + .parent(tr.commit().create()) // Can't cherry-pick root. + .author(new PersonIdent("Cherrypick Author", "cpa@example.com", + tr.getClock(), tr.getTimeZone())) + .author(new PersonIdent("Cherrypick Committer", "cpc@example.com", + tr.getClock(), tr.getTimeZone())) + .message("message to cherry-pick") + .add("bar", "bar contents\n") + .create(); + RevCommit result = tr.cherryPick(toPick); + rw.parseBody(result); + + Ref headRef = tr.getRepository().getRef("HEAD"); + assertEquals(result, headRef.getObjectId()); + assertTrue(headRef.isSymbolic()); + assertEquals("refs/heads/master", headRef.getLeaf().getName()); + + assertEquals(1, result.getParentCount()); + assertEquals(head, result.getParent(0)); + assertEquals(toPick.getAuthorIdent(), result.getAuthorIdent()); + + // Committer name/email matches default, and time was incremented. + assertEquals(new PersonIdent(head.getCommitterIdent(), new Date(0)), + new PersonIdent(result.getCommitterIdent(), new Date(0))); + assertTrue(toPick.getCommitTime() < result.getCommitTime()); + + assertEquals("message to cherry-pick", result.getFullMessage()); + assertEquals("foo contents\n", blobAsString(result, "foo")); + assertEquals("bar contents\n", blobAsString(result, "bar")); + } + + @Test + public void cherryPickWithContentMerge() throws Exception { + RevCommit base = tr.branch("HEAD").commit() + .add("foo", "foo contents\n\n") + .create(); + tr.branch("HEAD").commit() + .add("foo", "foo contents\n\nlast line\n") + .create(); + RevCommit toPick = tr.commit() + .message("message to cherry-pick") + .parent(base) + .add("foo", "changed foo contents\n\n") + .create(); + RevCommit result = tr.cherryPick(toPick); + rw.parseBody(result); + + assertEquals("message to cherry-pick", result.getFullMessage()); + assertEquals("changed foo contents\n\nlast line\n", + blobAsString(result, "foo")); + } + + @Test + public void cherryPickWithIdenticalContents() throws Exception { + RevCommit base = tr.branch("HEAD").commit().add("foo", "foo contents\n") + .create(); + RevCommit head = tr.branch("HEAD").commit() + .parent(base) + .add("bar", "bar contents\n") + .create(); + RevCommit toPick = tr.commit() + .parent(base) + .message("message to cherry-pick") + .add("bar", "bar contents\n") + .create(); + assertNotEquals(head, toPick); + assertNull(tr.cherryPick(toPick)); + assertEquals(head, repo.getRef("HEAD").getObjectId()); + } + private String blobAsString(AnyObjectId treeish, String path) throws Exception { RevObject obj = tr.get(rw.parseTree(treeish), path); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java index c6578ccfa..274757d95 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/lib/ObjectCheckerTest.java @@ -120,6 +120,42 @@ public class ObjectCheckerTest { checker.check(Constants.OBJ_COMMIT, data); } + @Test + public void testCommitCorruptAuthor() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n"); + b.append("author b 0 +0000\n"); + b.append("committer <> 0 +0000\n"); + + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid author", e.getMessage()); + } + checker.setAllowInvalidPersonIdent(true); + checker.checkCommit(data); + } + + @Test + public void testCommitCorruptCommitter() throws CorruptObjectException { + StringBuilder b = new StringBuilder(); + b.append("tree be9bfa841874ccc9f2ef7c48d0c76226f89b7189\n"); + b.append("author <> 0 +0000\n"); + b.append("committer b 0 +0000\n"); + + byte[] data = Constants.encodeASCII(b.toString()); + try { + checker.checkCommit(data); + fail("Did not catch corrupt object"); + } catch (CorruptObjectException e) { + assertEquals("invalid committer", e.getMessage()); + } + checker.setAllowInvalidPersonIdent(true); + checker.checkCommit(data); + } + @Test public void testValidCommit1Parent() throws CorruptObjectException { final StringBuilder b = new StringBuilder(); @@ -940,7 +976,8 @@ public class ObjectCheckerTest { } @Test - public void testInvalidTagInvalidTaggerHeader1() { + public void testInvalidTagInvalidTaggerHeader1() + throws CorruptObjectException { final StringBuilder b = new StringBuilder(); b.append("object "); @@ -958,6 +995,8 @@ public class ObjectCheckerTest { } catch (CorruptObjectException e) { assertEquals("invalid tagger", e.getMessage()); } + checker.setAllowInvalidPersonIdent(true); + checker.checkTag(data); } @Test diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java index 8a33425b1..d4a3d62da 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/MergeAlgorithmTest.java @@ -51,11 +51,25 @@ import java.io.IOException; import org.eclipse.jgit.diff.RawText; import org.eclipse.jgit.diff.RawTextComparator; import org.eclipse.jgit.lib.Constants; +import org.junit.Assume; import org.junit.Test; +import org.junit.experimental.theories.DataPoints; +import org.junit.experimental.theories.Theories; +import org.junit.runner.RunWith; +@RunWith(Theories.class) public class MergeAlgorithmTest { MergeFormatter fmt=new MergeFormatter(); + private final boolean newlineAtEnd; + + @DataPoints + public static boolean[] newlineAtEndDataPoints = { false, true }; + + public MergeAlgorithmTest(boolean newlineAtEnd) { + this.newlineAtEnd = newlineAtEnd; + } + /** * Check for a conflict where the second text was changed similar to the * first one, but the second texts modification covers one more line. @@ -174,28 +188,55 @@ public class MergeAlgorithmTest { } @Test - public void testSeperateModifications() throws IOException { + public void testSeparateModifications() throws IOException { assertEquals(t("aZcYe"), merge("abcde", "aZcde", "abcYe")); } + @Test + public void testBlankLines() throws IOException { + assertEquals(t("aZc\nYe"), merge("abc\nde", "aZc\nde", "abc\nYe")); + } + /** * Test merging two contents which do one similar modification and one - * insertion is only done by one side. Between modification and insertion is - * a block which is common between the two contents and the common base + * insertion is only done by one side, in the middle. Between modification + * and insertion is a block which is common between the two contents and the + * common base * * @throws IOException */ @Test public void testTwoSimilarModsAndOneInsert() throws IOException { - assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ")); assertEquals(t("aBcDde"), merge("abcde", "aBcde", "aBcDde")); - assertEquals(t("IAJ"), merge("iA", "IA", "IAJ")); - assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ")); assertEquals(t("IAAAJCAB"), merge("iACAB", "IACAB", "IAAAJCAB")); assertEquals(t("HIAAAJCAB"), merge("HiACAB", "HIACAB", "HIAAAJCAB")); assertEquals(t("AGADEFHIAAAJCAB"), merge("AGADEFHiACAB", "AGADEFHIACAB", "AGADEFHIAAAJCAB")); + } + /** + * Test merging two contents which do one similar modification and one + * insertion is only done by one side, at the end. Between modification and + * insertion is a block which is common between the two contents and the + * common base + * + * @throws IOException + */ + @Test + public void testTwoSimilarModsAndOneInsertAtEnd() throws IOException { + Assume.assumeTrue(newlineAtEnd); + assertEquals(t("IAAJ"), merge("iA", "IA", "IAAJ")); + assertEquals(t("IAJ"), merge("iA", "IA", "IAJ")); + assertEquals(t("IAAAJ"), merge("iA", "IA", "IAAAJ")); + } + + @Test + public void testTwoSimilarModsAndOneInsertAtEndNoNewlineAtEnd() + throws IOException { + Assume.assumeFalse(newlineAtEnd); + assertEquals(t("I"), merge("iA", "IA", "IAAJ")); + assertEquals(t("I"), merge("iA", "IA", "IAJ")); + assertEquals(t("I"), merge("iA", "IA", "IAAAJ")); } /** @@ -225,7 +266,7 @@ public class MergeAlgorithmTest { return new String(bo.toByteArray(), Constants.CHARACTER_ENCODING); } - public static String t(String text) { + public String t(String text) { StringBuilder r = new StringBuilder(); for (int i = 0; i < text.length(); i++) { char c = text.charAt(i); @@ -241,13 +282,14 @@ public class MergeAlgorithmTest { break; default: r.append(c); - r.append('\n'); + if (newlineAtEnd || i < text.length() - 1) + r.append('\n'); } } return r.toString(); } - public static RawText T(String text) { + public RawText T(String text) { return new RawText(Constants.encode(t(text))); } } diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java index 37cc88be1..19e495b40 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/merge/RecursiveMergerTest.java @@ -175,6 +175,69 @@ public class RecursiveMergerTest extends RepositoryTestCase { } } + @Theory + /** + * Merging m2,s2 from the following topology. m1 and s1 are the two root + * commits of the repo. In master and side different files are touched. + * No need to do a real content merge. + * + *

+	 * m1--m2
+	 *   \/
+	 *   /\
+	 * s1--s2
+	 * 
+ */ + public void crissCrossMerge_twoRoots(MergeStrategy strategy, + IndexState indexState, WorktreeState worktreeState) + throws Exception { + if (!validateStates(indexState, worktreeState)) + return; + // fill the repo + BranchBuilder master = db_t.branch("master"); + BranchBuilder side = db_t.branch("side"); + RevCommit m1 = master.commit().add("m", "m1").message("m1").create(); + db_t.getRevWalk().parseCommit(m1); + + RevCommit s1 = side.commit().add("s", "s1").message("s1").create(); + RevCommit s2 = side.commit().parent(m1).add("m", "m1") + .message("s2(merge)").create(); + RevCommit m2 = master.commit().parent(s1).add("s", "s1") + .message("m2(merge)").create(); + + Git git = Git.wrap(db); + git.checkout().setName("master").call(); + modifyWorktree(worktreeState, "m", "side"); + modifyWorktree(worktreeState, "s", "side"); + modifyIndex(indexState, "m", "side"); + modifyIndex(indexState, "s", "side"); + + ResolveMerger merger = (ResolveMerger) strategy.newMerger(db, + worktreeState == WorktreeState.Bare); + if (worktreeState != WorktreeState.Bare) + merger.setWorkingTreeIterator(new FileTreeIterator(db)); + try { + boolean expectSuccess = true; + if (!(indexState == IndexState.Bare + || indexState == IndexState.Missing + || indexState == IndexState.SameAsHead || indexState == IndexState.SameAsOther)) + // index is dirty + expectSuccess = false; + + assertEquals(Boolean.valueOf(expectSuccess), + Boolean.valueOf(merger.merge(new RevCommit[] { m2, s2 }))); + assertEquals(MergeStrategy.RECURSIVE, strategy); + assertEquals("m1", + contentAsString(db, merger.getResultTreeId(), "m")); + assertEquals("s1", + contentAsString(db, merger.getResultTreeId(), "s")); + } catch (NoMergeBaseException e) { + assertEquals(MergeStrategy.RESOLVE, strategy); + assertEquals(e.getReason(), + MergeBaseFailureReason.MULTIPLE_MERGE_BASES_NOT_SUPPORTED); + } + } + @Theory /** * Merging m2,s2 from the following topology. The same file is modified 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 dd06168c3..478a93b77 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 @@ -184,7 +184,7 @@ public class ResolveMergerTest extends RepositoryTestCase { MergeResult mergeRes = git.merge().setStrategy(strategy) .include(masterCommit).call(); assertEquals(MergeStatus.MERGED, mergeRes.getMergeStatus()); - assertEquals("[d/1, mode:100644, content:1master\n2\n3side\n]", + assertEquals("[d/1, mode:100644, content:1master\n2\n3side]", indexState(CONTENT)); } @@ -561,7 +561,7 @@ public class ResolveMergerTest extends RepositoryTestCase { assertEquals(MergeStrategy.RECURSIVE, strategy); assertEquals(MergeResult.MergeStatus.MERGED, mergeResult.getMergeStatus()); - assertEquals("1master2\n2\n3side2\n", read("1")); + assertEquals("1master2\n2\n3side2", read("1")); } catch (JGitInternalException e) { assertEquals(MergeStrategy.RESOLVE, strategy); assertTrue(e.getCause() instanceof NoMergeBaseException); @@ -697,7 +697,7 @@ public class ResolveMergerTest extends RepositoryTestCase { assertEquals( "[0, mode:100644, content:master]" // + "[1, mode:100644, content:side]" // - + "[2, mode:100644, content:1master\n2\n3side\n]" // + + "[2, mode:100644, content:1master\n2\n3side]" // + "[3, mode:100644, stage:1, content:orig][3, mode:100644, stage:2, content:side][3, mode:100644, stage:3, content:master]" // + "[4, mode:100644, content:orig]", // indexState(CONTENT)); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java index 9cbb1c875..dfde7fcf2 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/revwalk/ObjectWalkTest.java @@ -214,21 +214,6 @@ public class ObjectWalkTest extends RevWalkTestCase { assertNull(objw.nextObject()); } - @Test - public void testMarkUninterestingPropagation() throws Exception { - final RevBlob f = blob("1"); - final RevTree t = tree(file("f", f)); - final RevCommit c1 = commit(t); - final RevCommit c2 = commit(t); - - markUninteresting(c1); - markStart(c2); - - assertSame(c2, objw.next()); - assertNull(objw.next()); - assertNull(objw.nextObject()); - } - @Test public void testEmptyTreeCorruption() throws Exception { ObjectId bId = ObjectId diff --git a/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java new file mode 100644 index 000000000..fd26bfa7f --- /dev/null +++ b/org.eclipse.jgit.ui/src/org/eclipse/jgit/awtui/AwtCredentialsProvider.java @@ -0,0 +1,204 @@ +/* + * Copyright (C) 2010, Google Inc. + * Copyright (C) 2008, Robin Rosenberg + * Copyright (C) 2008, Shawn O. Pearce + * 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.awtui; + +import java.awt.GridBagConstraints; +import java.awt.GridBagLayout; +import java.awt.Insets; + +import javax.swing.JLabel; +import javax.swing.JOptionPane; +import javax.swing.JPanel; +import javax.swing.JPasswordField; +import javax.swing.JTextField; + +import org.eclipse.jgit.errors.UnsupportedCredentialItem; +import org.eclipse.jgit.transport.CredentialItem; +import org.eclipse.jgit.transport.CredentialsProvider; +import org.eclipse.jgit.transport.URIish; + +/** Interacts with the user during authentication by using AWT/Swing dialogs. */ +public class AwtCredentialsProvider extends CredentialsProvider { + /** Install this implementation as the default. */ + public static void install() { + CredentialsProvider.setDefault(new AwtCredentialsProvider()); + } + + @Override + public boolean isInteractive() { + return true; + } + + @Override + public boolean supports(CredentialItem... items) { + for (CredentialItem i : items) { + if (i instanceof CredentialItem.StringType) + continue; + + else if (i instanceof CredentialItem.CharArrayType) + continue; + + else if (i instanceof CredentialItem.YesNoType) + continue; + + else if (i instanceof CredentialItem.InformationalMessage) + continue; + + else + return false; + } + return true; + } + + @Override + public boolean get(URIish uri, CredentialItem... items) + throws UnsupportedCredentialItem { + if (items.length == 0) { + return true; + + } else if (items.length == 1) { + final CredentialItem item = items[0]; + + if (item instanceof CredentialItem.InformationalMessage) { + JOptionPane.showMessageDialog(null, item.getPromptText(), + UIText.get().warning, JOptionPane.INFORMATION_MESSAGE); + return true; + + } else if (item instanceof CredentialItem.YesNoType) { + CredentialItem.YesNoType v = (CredentialItem.YesNoType) item; + int r = JOptionPane.showConfirmDialog(null, v.getPromptText(), + UIText.get().warning, JOptionPane.YES_NO_OPTION); + switch (r) { + case JOptionPane.YES_OPTION: + v.setValue(true); + return true; + + case JOptionPane.NO_OPTION: + v.setValue(false); + return true; + + case JOptionPane.CANCEL_OPTION: + case JOptionPane.CLOSED_OPTION: + default: + return false; + } + + } else { + return interactive(uri, items); + } + + } else { + return interactive(uri, items); + } + } + + private static boolean interactive(URIish uri, CredentialItem[] items) { + final GridBagConstraints gbc = new GridBagConstraints(0, 0, 1, 1, 1, 1, + GridBagConstraints.NORTHWEST, GridBagConstraints.NONE, + new Insets(0, 0, 0, 0), 0, 0); + final JPanel panel = new JPanel(); + panel.setLayout(new GridBagLayout()); + + final JTextField[] texts = new JTextField[items.length]; + for (int i = 0; i < items.length; i++) { + CredentialItem item = items[i]; + + if (item instanceof CredentialItem.StringType + || item instanceof CredentialItem.CharArrayType) { + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = GridBagConstraints.RELATIVE; + gbc.gridx = 0; + panel.add(new JLabel(item.getPromptText()), gbc); + + gbc.fill = GridBagConstraints.HORIZONTAL; + gbc.gridwidth = GridBagConstraints.RELATIVE; + gbc.gridx = 1; + if (item.isValueSecure()) + texts[i] = new JPasswordField(20); + else + texts[i] = new JTextField(20); + panel.add(texts[i], gbc); + gbc.gridy++; + + } else if (item instanceof CredentialItem.InformationalMessage) { + gbc.fill = GridBagConstraints.NONE; + gbc.gridwidth = GridBagConstraints.REMAINDER; + gbc.gridx = 0; + panel.add(new JLabel(item.getPromptText()), gbc); + gbc.gridy++; + + } else { + throw new UnsupportedCredentialItem(uri, item.getPromptText()); + } + } + + if (JOptionPane.showConfirmDialog(null, panel, + UIText.get().authenticationRequired, + JOptionPane.OK_CANCEL_OPTION, JOptionPane.QUESTION_MESSAGE) != JOptionPane.OK_OPTION) + return false; // cancel + + for (int i = 0; i < items.length; i++) { + CredentialItem item = items[i]; + JTextField f = texts[i]; + + if (item instanceof CredentialItem.StringType) { + CredentialItem.StringType v = (CredentialItem.StringType) item; + if (f instanceof JPasswordField) + v.setValue(new String(((JPasswordField) f).getPassword())); + else + v.setValue(f.getText()); + + } else if (item instanceof CredentialItem.CharArrayType) { + CredentialItem.CharArrayType v = (CredentialItem.CharArrayType) item; + if (f instanceof JPasswordField) + v.setValueNoCopy(((JPasswordField) f).getPassword()); + else + v.setValueNoCopy(f.getText().toCharArray()); + } + } + return true; + } +} diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 3dfa7b452..124435a81 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -1,50 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 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 a003b02cc..602861859 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -44,6 +44,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. +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}. cannotCommitOnARepoWithState=Cannot commit on a repo with state: {0} @@ -231,6 +232,7 @@ 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} +findingGarbage=Finding garbage flagIsDisposed={0} is disposed. flagNotFromThis={0} not from this. flagsAlreadyCreated={0} flags already created. @@ -258,6 +260,7 @@ indexWriteException=Modified index could not be written initFailedBareRepoDifferentDirs=When initializing a bare repo with directory {0} and separate git-dir {1} specified both folders must point to the same location initFailedNonBareRepoSameDirs=When initializing a non-bare repo with directory {0} and separate git-dir {1} specified both folders should not point to the same location inMemoryBufferLimitExceeded=In-memory buffer limit exceeded +inputDidntMatchLength=Input did not match supplied length. {0} bytes are missing. inputStreamMustSupportMark=InputStream must support mark() integerValueOutOfRange=Integer value {0}.{1} out of range internalRevisionError=internal revision error @@ -391,6 +394,7 @@ packfileCorruptionDetected=Packfile corruption detected: {0} packFileInvalid=Pack file invalid: {0} 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 packingCancelledDuringObjectsWriting=Packing cancelled during objects writing packObjectCountMismatch=Pack object count mismatch: pack {0} index {1}: {2} @@ -502,6 +506,7 @@ statelessRPCRequiresOptionToBeEnabled=stateless RPC requires {0} to be enabled submoduleExists=Submodule ''{0}'' already exists in the index submoduleParentRemoteUrlInvalid=Cannot remove segment from remote url ''{0}'' submodulesNotSupported=Submodules are not supported +supportOnlyPackIndexVersion2=Only support index version 2 symlinkCannotBeWrittenAsTheLinkTarget=Symlink "{0}" cannot be written as the link target cannot be read from within Java. systemConfigFileInvalid=Systen wide config file {0} is invalid {1} tagAlreadyExists=tag ''{0}'' already exists @@ -536,6 +541,7 @@ truncatedHunkNewLinesMissing=Truncated hunk, at least {0} new lines is missing truncatedHunkOldLinesMissing=Truncated hunk, at least {0} old lines is missing tSizeMustBeGreaterOrEqual1=tSize must be >= 1 unableToCheckConnectivity=Unable to check connectivity. +unableToCreateNewObject=Unable to create new object: {0} unableToStore=Unable to store {0}. unableToWrite=Unable to write {0} unencodeableFile=Unencodable file: {0} 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 c23256c74..de6c32a80 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddCommand.java @@ -137,13 +137,12 @@ public class AddCommand extends GitCommand { if (filepatterns.contains(".")) //$NON-NLS-1$ addAll = true; - ObjectInserter inserter = repo.newObjectInserter(); - try { + try (ObjectInserter inserter = repo.newObjectInserter(); + final TreeWalk tw = new TreeWalk(repo)) { dc = repo.lockDirCache(); DirCacheIterator c; DirCacheBuilder builder = dc.builder(); - final TreeWalk tw = new TreeWalk(repo); tw.addTree(new DirCacheBuildIterator(builder)); if (workingTreeIterator == null) workingTreeIterator = new FileTreeIterator(repo); @@ -212,7 +211,6 @@ public class AddCommand extends GitCommand { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfAddCommand, e); } finally { - inserter.release(); if (dc != null) dc.unlock(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java index 3af86959d..9cf888195 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/AddNoteCommand.java @@ -83,11 +83,10 @@ public class AddNoteCommand extends GitCommand { public Note call() throws GitAPIException { checkCallable(); - RevWalk walk = new RevWalk(repo); - ObjectInserter inserter = repo.newObjectInserter(); NoteMap map = NoteMap.newEmptyMap(); RevCommit notesCommit = null; - try { + try (RevWalk walk = new RevWalk(repo); + ObjectInserter inserter = repo.newObjectInserter()) { Ref ref = repo.getRef(notesRef); // if we have a notes ref, use it if (ref != null) { @@ -96,13 +95,10 @@ public class AddNoteCommand extends GitCommand { } map.set(id, message, inserter); commitNoteMap(walk, map, notesCommit, inserter, - "Notes added by 'git notes add'"); + "Notes added by 'git notes add'"); //$NON-NLS-1$ return map.getNote(id); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); - } finally { - inserter.release(); - walk.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java index 9014aaf1a..713866dc5 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ArchiveCommand.java @@ -365,13 +365,11 @@ public class ArchiveCommand extends GitCommand { private OutputStream writeArchive(Format fmt) { final String pfx = prefix == null ? "" : prefix; //$NON-NLS-1$ - final TreeWalk walk = new TreeWalk(repo); - try { + try (final TreeWalk walk = new TreeWalk(repo)) { final T outa = fmt.createArchiveOutputStream(out, formatOptions); - try { + try (final RevWalk rw = new RevWalk(walk.getObjectReader())) { final MutableObjectId idBuf = new MutableObjectId(); final ObjectReader reader = walk.getObjectReader(); - final RevWalk rw = new RevWalk(walk.getObjectReader()); walk.reset(rw.parseTree(tree)); if (!paths.isEmpty()) @@ -405,8 +403,6 @@ public class ArchiveCommand extends GitCommand { // TODO(jrn): Throw finer-grained errors. throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfArchiveCommand, e); - } finally { - walk.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java index f7ce835d5..a83814eb4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/BlameCommand.java @@ -200,8 +200,7 @@ public class BlameCommand extends GitCommand { */ public BlameResult call() throws GitAPIException { checkCallable(); - BlameGenerator gen = new BlameGenerator(repo, path); - try { + try (BlameGenerator gen = new BlameGenerator(repo, path)) { if (diffAlgorithm != null) gen.setDiffAlgorithm(diffAlgorithm); if (textComparator != null) @@ -231,8 +230,6 @@ public class BlameCommand extends GitCommand { return gen.computeBlameResult(); } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); - } finally { - gen.release(); } } 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 3787ac511..8d8aada62 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CheckoutCommand.java @@ -208,16 +208,17 @@ public class CheckoutCommand extends GitCommand { } if (createBranch) { - Git git = new Git(repo); - CreateBranchCommand command = git.branchCreate(); - command.setName(name); - if (startCommit != null) - command.setStartPoint(startCommit); - else - command.setStartPoint(startPoint); - if (upstreamMode != null) - command.setUpstreamMode(upstreamMode); - command.call(); + try (Git git = new Git(repo)) { + CreateBranchCommand command = git.branchCreate(); + command.setName(name); + if (startCommit != null) + command.setStartPoint(startCommit); + else + command.setStartPoint(startPoint); + if (upstreamMode != null) + command.setUpstreamMode(upstreamMode); + command.call(); + } } Ref headRef = repo.getRef(Constants.HEAD); @@ -243,11 +244,14 @@ public class CheckoutCommand extends GitCommand { JGitText.get().refNotResolved, name)); } - RevWalk revWalk = new RevWalk(repo); - AnyObjectId headId = headRef.getObjectId(); - RevCommit headCommit = headId == null ? null : revWalk - .parseCommit(headId); - RevCommit newCommit = revWalk.parseCommit(branch); + RevCommit headCommit = null; + RevCommit newCommit = null; + try (RevWalk revWalk = new RevWalk(repo)) { + AnyObjectId headId = headRef.getObjectId(); + headCommit = headId == null ? null + : revWalk.parseCommit(headId); + newCommit = revWalk.parseCommit(branch); + } RevTree headTree = headCommit == null ? null : headCommit.getTree(); DirCacheCheckout dco; DirCache dc = repo.lockDirCache(); @@ -376,26 +380,20 @@ public class CheckoutCommand extends GitCommand { */ protected CheckoutCommand checkoutPaths() throws IOException, RefNotFoundException { - RevWalk revWalk = new RevWalk(repo); DirCache dc = repo.lockDirCache(); - try { - TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader()); + try (RevWalk revWalk = new RevWalk(repo); + TreeWalk treeWalk = new TreeWalk(revWalk.getObjectReader())) { treeWalk.setRecursive(true); if (!checkoutAllPaths) treeWalk.setFilter(PathFilterGroup.createFromStrings(paths)); - try { - if (isCheckoutIndex()) - checkoutPathsFromIndex(treeWalk, dc); - else { - RevCommit commit = revWalk.parseCommit(getStartPointObjectId()); - checkoutPathsFromCommit(treeWalk, dc, commit); - } - } finally { - treeWalk.release(); + if (isCheckoutIndex()) + checkoutPathsFromIndex(treeWalk, dc); + else { + RevCommit commit = revWalk.parseCommit(getStartPointObjectId()); + checkoutPathsFromCommit(treeWalk, dc, commit); } } finally { dc.unlock(); - revWalk.release(); } return this; } @@ -675,7 +673,6 @@ public class CheckoutCommand extends GitCommand { private void checkOptions() { if (checkoutStage != null && !isCheckoutIndex()) throw new IllegalStateException( - "Checking out ours/theirs is only possible when checking out index, " - + "not when switching branches."); + JGitText.get().cannotCheckoutOursSwitchBranch); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java index d4eb0b3b2..d6e930ada 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickCommand.java @@ -123,8 +123,7 @@ public class CherryPickCommand extends GitCommand { List cherryPickedRefs = new LinkedList(); checkCallable(); - RevWalk revWalk = new RevWalk(repo); - try { + try (RevWalk revWalk = new RevWalk(repo)) { // get the head commit Ref headRef = repo.getRef(Constants.HEAD); @@ -153,7 +152,7 @@ public class CherryPickCommand extends GitCommand { ResolveMerger merger = (ResolveMerger) strategy.newMerger(repo); merger.setWorkingTreeIterator(new FileTreeIterator(repo)); merger.setBase(srcParent.getTree()); - merger.setCommitNames(new String[] { "BASE", ourName, + merger.setCommitNames(new String[] { "BASE", ourName, //$NON-NLS-1$ cherryPickName }); if (merger.merge(newHead, srcCommit)) { if (AnyObjectId.equals(newHead.getTree().getId(), merger @@ -194,8 +193,6 @@ public class CherryPickCommand extends GitCommand { MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfCherryPickCommand, e), e); - } finally { - revWalk.release(); } return new CherryPickResult(newHead, cherryPickedRefs); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java index 5b3c5d4e7..b121291fc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CherryPickResult.java @@ -63,21 +63,21 @@ public class CherryPickResult { OK { @Override public String toString() { - return "Ok"; + return "Ok"; //$NON-NLS-1$ } }, /** */ FAILED { @Override public String toString() { - return "Failed"; + return "Failed"; //$NON-NLS-1$ } }, /** */ CONFLICTING { @Override public String toString() { - return "Conflicting"; + return "Conflicting"; //$NON-NLS-1$ } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java index f058f799d..53901f589 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -322,12 +322,9 @@ public class CloneCommand extends TransportCommand { private RevCommit parseCommit(final Repository clonedRepo, final Ref ref) throws MissingObjectException, IncorrectObjectTypeException, IOException { - final RevWalk rw = new RevWalk(clonedRepo); final RevCommit commit; - try { + try (final RevWalk rw = new RevWalk(clonedRepo)) { commit = rw.parseCommit(ref.getObjectId()); - } finally { - rw.release(); } return commit; } 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 b57cff7ea..87efd7983 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CommitCommand.java @@ -165,9 +165,7 @@ public class CommitCommand extends GitCommand { checkCallable(); Collections.sort(only); - RevWalk rw = new RevWalk(repo); - - try { + try (RevWalk rw = new RevWalk(repo)) { RepositoryState state = repo.getRepositoryState(); if (!state.canCommit()) throw new WrongRepositoryStateException(MessageFormat.format( @@ -181,8 +179,7 @@ public class CommitCommand extends GitCommand { processOptions(state, rw); if (all && !repo.isBare() && repo.getWorkTree() != null) { - Git git = new Git(repo); - try { + try (Git git = new Git(repo)) { git.add() .addFilepattern(".") //$NON-NLS-1$ .setUpdate(true).call(); @@ -221,80 +218,74 @@ public class CommitCommand extends GitCommand { // lock the index DirCache index = repo.lockDirCache(); - try { + try (ObjectInserter odi = repo.newObjectInserter()) { if (!only.isEmpty()) index = createTemporaryIndex(headId, index, rw); - ObjectInserter odi = repo.newObjectInserter(); - try { - // Write the index as tree to the object database. This may - // fail for example when the index contains unmerged paths - // (unresolved conflicts) - ObjectId indexTreeId = index.writeTree(odi); - - if (insertChangeId) - insertChangeId(indexTreeId); - - // Create a Commit object, populate it and write it - CommitBuilder commit = new CommitBuilder(); - commit.setCommitter(committer); - commit.setAuthor(author); - commit.setMessage(message); - - commit.setParentIds(parents); - commit.setTreeId(indexTreeId); - ObjectId commitId = odi.insert(commit); - odi.flush(); - - RevCommit revCommit = rw.parseCommit(commitId); - RefUpdate ru = repo.updateRef(Constants.HEAD); - ru.setNewObjectId(commitId); - if (reflogComment != null) { - ru.setRefLogMessage(reflogComment, false); - } else { - String prefix = amend ? "commit (amend): " //$NON-NLS-1$ - : parents.size() == 0 ? "commit (initial): " //$NON-NLS-1$ - : "commit: "; //$NON-NLS-1$ - ru.setRefLogMessage( - prefix + revCommit.getShortMessage(), false); - } - if (headId != null) - ru.setExpectedOldObjectId(headId); - else - ru.setExpectedOldObjectId(ObjectId.zeroId()); - Result rc = ru.forceUpdate(); - switch (rc) { - case NEW: - case FORCED: - case FAST_FORWARD: { - setCallable(false); - if (state == RepositoryState.MERGING_RESOLVED - || isMergeDuringRebase(state)) { - // Commit was successful. Now delete the files - // used for merge commits - repo.writeMergeCommitMsg(null); - repo.writeMergeHeads(null); - } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) { - repo.writeMergeCommitMsg(null); - repo.writeCherryPickHead(null); - } else if (state == RepositoryState.REVERTING_RESOLVED) { - repo.writeMergeCommitMsg(null); - repo.writeRevertHead(null); - } - return revCommit; - } - case REJECTED: - case LOCK_FAILURE: - throw new ConcurrentRefUpdateException( - JGitText.get().couldNotLockHEAD, ru.getRef(), - rc); - default: - throw new JGitInternalException(MessageFormat.format( - JGitText.get().updatingRefFailed, - Constants.HEAD, commitId.toString(), rc)); + // Write the index as tree to the object database. This may + // fail for example when the index contains unmerged paths + // (unresolved conflicts) + ObjectId indexTreeId = index.writeTree(odi); + + if (insertChangeId) + insertChangeId(indexTreeId); + + // Create a Commit object, populate it and write it + CommitBuilder commit = new CommitBuilder(); + commit.setCommitter(committer); + commit.setAuthor(author); + commit.setMessage(message); + + commit.setParentIds(parents); + commit.setTreeId(indexTreeId); + ObjectId commitId = odi.insert(commit); + odi.flush(); + + RevCommit revCommit = rw.parseCommit(commitId); + RefUpdate ru = repo.updateRef(Constants.HEAD); + ru.setNewObjectId(commitId); + if (reflogComment != null) { + ru.setRefLogMessage(reflogComment, false); + } else { + String prefix = amend ? "commit (amend): " //$NON-NLS-1$ + : parents.size() == 0 ? "commit (initial): " //$NON-NLS-1$ + : "commit: "; //$NON-NLS-1$ + ru.setRefLogMessage(prefix + revCommit.getShortMessage(), + false); + } + if (headId != null) + ru.setExpectedOldObjectId(headId); + else + ru.setExpectedOldObjectId(ObjectId.zeroId()); + Result rc = ru.forceUpdate(); + switch (rc) { + case NEW: + case FORCED: + case FAST_FORWARD: { + setCallable(false); + if (state == RepositoryState.MERGING_RESOLVED + || isMergeDuringRebase(state)) { + // Commit was successful. Now delete the files + // used for merge commits + repo.writeMergeCommitMsg(null); + repo.writeMergeHeads(null); + } else if (state == RepositoryState.CHERRY_PICKING_RESOLVED) { + repo.writeMergeCommitMsg(null); + repo.writeCherryPickHead(null); + } else if (state == RepositoryState.REVERTING_RESOLVED) { + repo.writeMergeCommitMsg(null); + repo.writeRevertHead(null); } - } finally { - odi.release(); + return revCommit; + } + case REJECTED: + case LOCK_FAILURE: + throw new ConcurrentRefUpdateException( + JGitText.get().couldNotLockHEAD, ru.getRef(), rc); + default: + throw new JGitInternalException(MessageFormat.format( + JGitText.get().updatingRefFailed, Constants.HEAD, + commitId.toString(), rc)); } } finally { index.unlock(); @@ -304,8 +295,6 @@ public class CommitCommand extends GitCommand { } catch (IOException e) { throw new JGitInternalException( JGitText.get().exceptionCaughtDuringExecutionOfCommitCommand, e); - } finally { - rw.dispose(); } } @@ -338,114 +327,120 @@ public class CommitCommand extends GitCommand { onlyProcessed = new boolean[only.size()]; boolean emptyCommit = true; - TreeWalk treeWalk = new TreeWalk(repo); - int dcIdx = treeWalk.addTree(new DirCacheBuildIterator(existingBuilder)); - int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); - int hIdx = -1; - if (headId != null) - hIdx = treeWalk.addTree(rw.parseTree(headId)); - treeWalk.setRecursive(true); - - String lastAddedFile = null; - while (treeWalk.next()) { - String path = treeWalk.getPathString(); - // check if current entry's path matches a specified path - int pos = lookupOnly(path); - - CanonicalTreeParser hTree = null; - if (hIdx != -1) - hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); - - DirCacheIterator dcTree = treeWalk.getTree(dcIdx, - DirCacheIterator.class); - - if (pos >= 0) { - // include entry in commit - - FileTreeIterator fTree = treeWalk.getTree(fIdx, - FileTreeIterator.class); - - // check if entry refers to a tracked file - boolean tracked = dcTree != null || hTree != null; - if (!tracked) - break; - - // for an unmerged path, DirCacheBuildIterator will yield 3 - // entries, we only want to add one - if (path.equals(lastAddedFile)) - continue; - - lastAddedFile = path; - - if (fTree != null) { - // create a new DirCacheEntry with data retrieved from disk - final DirCacheEntry dcEntry = new DirCacheEntry(path); - long entryLength = fTree.getEntryLength(); - dcEntry.setLength(entryLength); - dcEntry.setLastModified(fTree.getEntryLastModified()); - dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); - - boolean objectExists = (dcTree != null && fTree - .idEqual(dcTree)) - || (hTree != null && fTree.idEqual(hTree)); - if (objectExists) { - dcEntry.setObjectId(fTree.getEntryObjectId()); - } else { - if (FileMode.GITLINK.equals(dcEntry.getFileMode())) + try (TreeWalk treeWalk = new TreeWalk(repo)) { + int dcIdx = treeWalk + .addTree(new DirCacheBuildIterator(existingBuilder)); + int fIdx = treeWalk.addTree(new FileTreeIterator(repo)); + int hIdx = -1; + if (headId != null) + hIdx = treeWalk.addTree(rw.parseTree(headId)); + treeWalk.setRecursive(true); + + String lastAddedFile = null; + while (treeWalk.next()) { + String path = treeWalk.getPathString(); + // check if current entry's path matches a specified path + int pos = lookupOnly(path); + + CanonicalTreeParser hTree = null; + if (hIdx != -1) + hTree = treeWalk.getTree(hIdx, CanonicalTreeParser.class); + + DirCacheIterator dcTree = treeWalk.getTree(dcIdx, + DirCacheIterator.class); + + if (pos >= 0) { + // include entry in commit + + FileTreeIterator fTree = treeWalk.getTree(fIdx, + FileTreeIterator.class); + + // check if entry refers to a tracked file + boolean tracked = dcTree != null || hTree != null; + if (!tracked) + break; + + // for an unmerged path, DirCacheBuildIterator will yield 3 + // entries, we only want to add one + if (path.equals(lastAddedFile)) + continue; + + lastAddedFile = path; + + if (fTree != null) { + // create a new DirCacheEntry with data retrieved from + // disk + final DirCacheEntry dcEntry = new DirCacheEntry(path); + long entryLength = fTree.getEntryLength(); + dcEntry.setLength(entryLength); + dcEntry.setLastModified(fTree.getEntryLastModified()); + dcEntry.setFileMode(fTree.getIndexFileMode(dcTree)); + + boolean objectExists = (dcTree != null + && fTree.idEqual(dcTree)) + || (hTree != null && fTree.idEqual(hTree)); + if (objectExists) { dcEntry.setObjectId(fTree.getEntryObjectId()); - else { - // insert object - if (inserter == null) - inserter = repo.newObjectInserter(); - long contentLength = fTree.getEntryContentLength(); - InputStream inputStream = fTree.openEntryStream(); - try { - dcEntry.setObjectId(inserter.insert( - Constants.OBJ_BLOB, contentLength, - inputStream)); - } finally { - inputStream.close(); + } else { + if (FileMode.GITLINK.equals(dcEntry.getFileMode())) + dcEntry.setObjectId(fTree.getEntryObjectId()); + else { + // insert object + if (inserter == null) + inserter = repo.newObjectInserter(); + long contentLength = fTree + .getEntryContentLength(); + InputStream inputStream = fTree + .openEntryStream(); + try { + dcEntry.setObjectId(inserter.insert( + Constants.OBJ_BLOB, contentLength, + inputStream)); + } finally { + inputStream.close(); + } } } + + // add to existing index + existingBuilder.add(dcEntry); + // add to temporary in-core index + tempBuilder.add(dcEntry); + + if (emptyCommit + && (hTree == null || !hTree.idEqual(fTree) + || hTree.getEntryRawMode() != fTree + .getEntryRawMode())) + // this is a change + emptyCommit = false; + } else { + // if no file exists on disk, neither add it to + // index nor to temporary in-core index + + if (emptyCommit && hTree != null) + // this is a change + emptyCommit = false; } - // add to existing index - existingBuilder.add(dcEntry); - // add to temporary in-core index - tempBuilder.add(dcEntry); - - if (emptyCommit - && (hTree == null || !hTree.idEqual(fTree) || hTree - .getEntryRawMode() != fTree - .getEntryRawMode())) - // this is a change - emptyCommit = false; + // keep track of processed path + onlyProcessed[pos] = true; } else { - // if no file exists on disk, neither add it to - // index nor to temporary in-core index - - if (emptyCommit && hTree != null) - // this is a change - emptyCommit = false; - } + // add entries from HEAD for all other paths + if (hTree != null) { + // create a new DirCacheEntry with data retrieved from + // HEAD + final DirCacheEntry dcEntry = new DirCacheEntry(path); + dcEntry.setObjectId(hTree.getEntryObjectId()); + dcEntry.setFileMode(hTree.getEntryFileMode()); + + // add to temporary in-core index + tempBuilder.add(dcEntry); + } - // keep track of processed path - onlyProcessed[pos] = true; - } else { - // add entries from HEAD for all other paths - if (hTree != null) { - // create a new DirCacheEntry with data retrieved from HEAD - final DirCacheEntry dcEntry = new DirCacheEntry(path); - dcEntry.setObjectId(hTree.getEntryObjectId()); - dcEntry.setFileMode(hTree.getEntryFileMode()); - - // add to temporary in-core index - tempBuilder.add(dcEntry); + // preserve existing entry in index + if (dcTree != null) + existingBuilder.add(dcTree.getDirCacheEntry()); } - - // preserve existing entry in index - if (dcTree != null) - existingBuilder.add(dcTree.getDirCacheEntry()); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java index bf6da45ca..be456662d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DescribeCommand.java @@ -315,7 +315,7 @@ public class DescribeCommand extends GitCommand { throw new JGitInternalException(e.getMessage(), e); } finally { setCallable(false); - w.release(); + w.close(); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java index f31198f94..527daef81 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/DiffCommand.java @@ -124,11 +124,8 @@ public class DiffCommand extends GitCommand> { if (head == null) throw new NoHeadException(JGitText.get().cannotReadTree); CanonicalTreeParser p = new CanonicalTreeParser(); - ObjectReader reader = repo.newObjectReader(); - try { + try (ObjectReader reader = repo.newObjectReader()) { p.reset(reader, head); - } finally { - reader.release(); } oldTree = p; } @@ -159,7 +156,7 @@ public class DiffCommand extends GitCommand> { } catch (IOException e) { throw new JGitInternalException(e.getMessage(), e); } finally { - diffFmt.release(); + diffFmt.close(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java index cce42fc51..fd28d0ec4 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/NameRevCommand.java @@ -185,11 +185,11 @@ public class NameRevCommand extends GitCommand> { } setCallable(false); - walk.release(); return result; } catch (IOException e) { - walk.reset(); throw new JGitInternalException(e.getMessage(), e); + } finally { + walk.close(); } } 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 7d3e82318..62001d0a7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/RebaseCommand.java @@ -1088,7 +1088,9 @@ public class RebaseCommand extends GitCommand { rebaseState.createFile(HEAD_NAME, headName); rebaseState.createFile(ONTO, upstreamCommit.name()); rebaseState.createFile(ONTO_NAME, upstreamCommitName); - rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$ + if (isInteractive()) { + rebaseState.createFile(INTERACTIVE, ""); //$NON-NLS-1$ + } rebaseState.createFile(QUIET, ""); //$NON-NLS-1$ ArrayList toDoSteps = new ArrayList(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java index 17b124230..ac67037b8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/ResetCommand.java @@ -227,9 +227,9 @@ public class ResetCommand extends GitCommand { setCallable(false); return result; } catch (IOException e) { - throw new JGitInternalException( + throw new JGitInternalException(MessageFormat.format( JGitText.get().exceptionCaughtDuringExecutionOfResetCommand, - e); + e.getMessage()), e); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java index 735eef7ed..e34db38fd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/blame/BlameResult.java @@ -85,7 +85,7 @@ public class BlameResult { String path = gen.getResultPath(); RawText contents = gen.getResultContents(); if (contents == null) { - gen.release(); + gen.close(); return null; } return new BlameResult(gen, path, contents); @@ -239,7 +239,7 @@ public class BlameResult { while (gen.next()) loadFrom(gen); } finally { - gen.release(); + gen.close(); generator = null; } } @@ -265,7 +265,7 @@ public class BlameResult { lastLength = gen.getRegionLength(); return gen.getResultStart(); } else { - gen.release(); + gen.close(); generator = null; return -1; } @@ -300,7 +300,7 @@ public class BlameResult { return; if (!gen.next()) { - gen.release(); + gen.close(); generator = null; return; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java index 1aab51e9c..b71e9902c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/diff/DiffFormatter.java @@ -173,7 +173,7 @@ public class DiffFormatter implements AutoCloseable { */ public void setRepository(Repository repository) { if (reader != null) - reader.release(); + reader.close(); db = repository; reader = db.newObjectReader(); @@ -422,10 +422,11 @@ public class DiffFormatter implements AutoCloseable { throws IOException { assertHaveRepository(); - RevWalk rw = new RevWalk(reader); - RevTree aTree = a != null ? rw.parseTree(a) : null; - RevTree bTree = b != null ? rw.parseTree(b) : null; - return scan(aTree, bTree); + try (RevWalk rw = new RevWalk(reader)) { + RevTree aTree = a != null ? rw.parseTree(a) : null; + RevTree bTree = b != null ? rw.parseTree(b) : null; + return scan(aTree, bTree); + } } /** 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 98a1c8ca4..6d9a32db9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCache.java @@ -961,9 +961,8 @@ public class DirCache { * @throws IOException */ private void updateSmudgedEntries() throws IOException { - TreeWalk walk = new TreeWalk(repository); List paths = new ArrayList(128); - try { + try (TreeWalk walk = new TreeWalk(repository)) { for (int i = 0; i < entryCnt; i++) if (sortedEntries[i].isSmudged()) paths.add(sortedEntries[i].getPathString()); @@ -989,8 +988,6 @@ public class DirCache { entry.setLastModified(fIter.getEntryLastModified()); } } - } finally { - walk.release(); } } } 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 015d9d6a8..99e022bfe 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/dircache/DirCacheCheckout.java @@ -1284,10 +1284,8 @@ public class DirCacheCheckout { * @throws InvalidPathException * if the path is invalid * @since 3.3 - * @deprecated Use {@link SystemReader#checkPath(String)}. */ - @Deprecated - public static void checkValidPath(String path) throws InvalidPathException { + static void checkValidPath(String path) throws InvalidPathException { try { SystemReader.getInstance().checkPath(path); } catch (CorruptObjectException e) { 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 63b788f6b..3077e18c1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -103,6 +103,7 @@ public class JGitText extends TranslationBundle { /***/ public String cannotBeRecursiveWhenTreesAreIncluded; /***/ public String cannotChangeActionOnComment; /***/ public String cannotChangeToComment; + /***/ public String cannotCheckoutOursSwitchBranch; /***/ public String cannotCombineSquashWithNoff; /***/ public String cannotCombineTreeFilterWithRevFilter; /***/ public String cannotCommitOnARepoWithState; @@ -290,6 +291,7 @@ public class JGitText extends TranslationBundle { /***/ public String fileIsTooBigForThisConvenienceMethod; /***/ public String fileIsTooLarge; /***/ public String fileModeNotSetForPath; + /***/ public String findingGarbage; /***/ public String flagIsDisposed; /***/ public String flagNotFromThis; /***/ public String flagsAlreadyCreated; @@ -317,6 +319,7 @@ public class JGitText extends TranslationBundle { /***/ public String initFailedBareRepoDifferentDirs; /***/ public String initFailedNonBareRepoSameDirs; /***/ public String inMemoryBufferLimitExceeded; + /***/ public String inputDidntMatchLength; /***/ public String inputStreamMustSupportMark; /***/ public String integerValueOutOfRange; /***/ public String internalRevisionError; @@ -450,6 +453,7 @@ public class JGitText extends TranslationBundle { /***/ public String packFileInvalid; /***/ public String packfileIsTruncated; /***/ public String packfileIsTruncatedNoParam; + /***/ public String packHandleIsStale; /***/ public String packHasUnresolvedDeltas; /***/ public String packingCancelledDuringObjectsWriting; /***/ public String packObjectCountMismatch; @@ -561,6 +565,7 @@ public class JGitText extends TranslationBundle { /***/ public String submoduleExists; /***/ public String submodulesNotSupported; /***/ public String submoduleParentRemoteUrlInvalid; + /***/ public String supportOnlyPackIndexVersion2; /***/ public String symlinkCannotBeWrittenAsTheLinkTarget; /***/ public String systemConfigFileInvalid; /***/ public String tagAlreadyExists; @@ -595,6 +600,7 @@ public class JGitText extends TranslationBundle { /***/ public String truncatedHunkOldLinesMissing; /***/ public String tSizeMustBeGreaterOrEqual1; /***/ public String unableToCheckConnectivity; + /***/ public String unableToCreateNewObject; /***/ public String unableToStore; /***/ public String unableToWrite; /***/ public String unencodeableFile; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java index c1853327f..79265363e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlock.java @@ -46,7 +46,6 @@ package org.eclipse.jgit.internal.storage.dfs; import java.io.IOException; -import java.security.MessageDigest; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -101,12 +100,9 @@ final class DfsBlock { out.update(block, ptr, cnt); } - void write(PackOutputStream out, long pos, int cnt, MessageDigest digest) + void write(PackOutputStream out, long pos, int cnt) throws IOException { - int ptr = (int) (pos - start); - out.write(block, ptr, cnt); - if (digest != null) - digest.update(block, ptr, cnt); + out.write(block, (int) (pos - start), cnt); } void check(Inflater inf, byte[] tmp, long pos, int cnt) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java index 748a4a38e..2e170a5d0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCache.java @@ -135,6 +135,9 @@ public final class DfsBlockCache { /** Maximum number of bytes the cache should hold. */ private final long maxBytes; + /** Pack files smaller than this size can be copied through the cache. */ + private final long maxStreamThroughCache; + /** * Suggested block size to read from pack files in. *

@@ -191,6 +194,7 @@ public final class DfsBlockCache { eb = tableSize; maxBytes = cfg.getBlockLimit(); + maxStreamThroughCache = (long) (maxBytes * cfg.getStreamRatio()); blockSize = cfg.getBlockSize(); blockSizeShift = Integer.numberOfTrailingZeros(blockSize); @@ -206,6 +210,10 @@ public final class DfsBlockCache { statMiss = new AtomicLong(); } + boolean shouldCopyThroughCache(long length) { + return length <= maxStreamThroughCache; + } + /** @return total number of bytes in the cache. */ public long getCurrentSize() { return liveBytes; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java index ca1451a2b..a7d13defd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsBlockCacheConfig.java @@ -47,7 +47,11 @@ import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_LIMIT; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_BLOCK_SIZE; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_RATIO; +import java.text.MessageFormat; + +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; /** Configuration parameters for {@link DfsBlockCache}. */ @@ -59,13 +63,14 @@ public class DfsBlockCacheConfig { public static final int MB = 1024 * KB; private long blockLimit; - private int blockSize; + private double streamRatio; /** Create a default configuration. */ public DfsBlockCacheConfig() { setBlockLimit(32 * MB); setBlockSize(64 * KB); + setStreamRatio(0.30); } /** @@ -105,6 +110,27 @@ public class DfsBlockCacheConfig { return this; } + /** + * @return highest percentage of {@link #getBlockLimit()} a single pack can + * occupy while being copied by the pack reuse strategy. Default + * is 0.30, or 30%. + * @since 4.0 + */ + public double getStreamRatio() { + return streamRatio; + } + + /** + * @param ratio + * percentage of cache to occupy with a copied pack. + * @return {@code this} + * @since 4.0 + */ + public DfsBlockCacheConfig setStreamRatio(double ratio) { + streamRatio = Math.max(0, Math.min(ratio, 1.0)); + return this; + } + /** * Update properties by setting fields from the configuration. *

@@ -127,6 +153,22 @@ public class DfsBlockCacheConfig { CONFIG_DFS_SECTION, CONFIG_KEY_BLOCK_SIZE, getBlockSize())); + + String v = rc.getString( + CONFIG_CORE_SECTION, + CONFIG_DFS_SECTION, + CONFIG_KEY_STREAM_RATIO); + if (v != null) { + try { + setStreamRatio(Double.parseDouble(v)); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(MessageFormat.format( + JGitText.get().enumValueNotSupported3, + CONFIG_CORE_SECTION, + CONFIG_DFS_SECTION, + CONFIG_KEY_STREAM_RATIO, v)); + } + } return this; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java index 3da5184e0..a5308f617 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsCachedPack.java @@ -78,8 +78,7 @@ public class DfsCachedPack extends CachedPack { return ((DfsObjectRepresentation) rep).pack == pack; } - void copyAsIs(PackOutputStream out, boolean validate, DfsReader ctx) - throws IOException { - pack.copyPackAsIs(out, validate, ctx); + void copyAsIs(PackOutputStream out, DfsReader ctx) throws IOException { + pack.copyPackAsIs(out, ctx); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java index deb6b7ff4..fed533880 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsGarbageCollector.java @@ -58,6 +58,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.file.PackIndex; import org.eclipse.jgit.internal.storage.pack.PackExt; @@ -188,7 +189,8 @@ public class DfsGarbageCollector { if (pm == null) pm = NullProgressMonitor.INSTANCE; if (packConfig.getIndexVersion() != 2) - throw new IllegalStateException("Only index version 2"); + throw new IllegalStateException( + JGitText.get().supportOnlyPackIndexVersion2); ctx = (DfsReader) objdb.newReader(); try { @@ -272,14 +274,11 @@ public class DfsGarbageCollector { if (allHeads.isEmpty()) return; - PackWriter pw = newPackWriter(); - try { + try (PackWriter pw = newPackWriter()) { pw.setTagTargets(tagTargets); pw.preparePack(pm, allHeads, Collections. emptySet()); if (0 < pw.getObjectCount()) writePack(GC, pw, pm); - } finally { - pw.release(); } } @@ -287,15 +286,12 @@ public class DfsGarbageCollector { if (nonHeads.isEmpty()) return; - PackWriter pw = newPackWriter(); - try { + try (PackWriter pw = newPackWriter()) { for (PackWriter.ObjectIdSet packedObjs : newPackObj) pw.excludeObjects(packedObjs); pw.preparePack(pm, nonHeads, allHeads); if (0 < pw.getObjectCount()) writePack(GC, pw, pm); - } finally { - pw.release(); } } @@ -307,12 +303,11 @@ public class DfsGarbageCollector { cfg.setDeltaCompress(false); cfg.setBuildBitmaps(false); - PackWriter pw = new PackWriter(cfg, ctx); - pw.setDeltaBaseAsOffset(true); - pw.setReuseDeltaCommits(true); - try { - RevWalk pool = new RevWalk(ctx); - pm.beginTask("Finding garbage", objectsBefore()); + try (PackWriter pw = new PackWriter(cfg, ctx); + RevWalk pool = new RevWalk(ctx)) { + pw.setDeltaBaseAsOffset(true); + pw.setReuseDeltaCommits(true); + pm.beginTask(JGitText.get().findingGarbage, objectsBefore()); for (DfsPackFile oldPack : packsBefore) { PackIndex oldIdx = oldPack.getPackIndex(ctx); for (PackIndex.MutableEntry ent : oldIdx) { @@ -328,8 +323,6 @@ public class DfsGarbageCollector { pm.endTask(); if (0 < pw.getObjectCount()) writePack(UNREACHABLE_GARBAGE, pw, pm); - } finally { - pw.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java index 58df895b4..e03488b3c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsPackFile.java @@ -54,6 +54,7 @@ import java.io.BufferedInputStream; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; +import java.nio.ByteBuffer; import java.nio.channels.Channels; import java.text.MessageFormat; import java.util.Set; @@ -80,7 +81,6 @@ import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; import org.eclipse.jgit.lib.Repository; -import org.eclipse.jgit.util.IO; import org.eclipse.jgit.util.LongList; /** @@ -464,11 +464,73 @@ public final class DfsPackFile { return dstbuf; } - void copyPackAsIs(PackOutputStream out, boolean validate, DfsReader ctx) + void copyPackAsIs(PackOutputStream out, DfsReader ctx) throws IOException { - // Pin the first window, this ensures the length is accurate. - ctx.pin(this, 0); - ctx.copyPackAsIs(this, length, validate, out); + // If the length hasn't been determined yet, pin to set it. + if (length == -1) { + ctx.pin(this, 0); + ctx.unpin(); + } + if (cache.shouldCopyThroughCache(length)) + copyPackThroughCache(out, ctx); + else + copyPackBypassCache(out, ctx); + } + + private void copyPackThroughCache(PackOutputStream out, DfsReader ctx) + throws IOException { + long position = 12; + long remaining = length - (12 + 20); + while (0 < remaining) { + DfsBlock b = cache.getOrLoad(this, position, ctx); + int ptr = (int) (position - b.start); + int n = (int) Math.min(b.size() - ptr, remaining); + b.write(out, position, n); + position += n; + remaining -= n; + } + } + + private long copyPackBypassCache(PackOutputStream out, DfsReader ctx) + throws IOException { + try (ReadableChannel rc = ctx.db.openFile(packDesc, PACK)) { + ByteBuffer buf = newCopyBuffer(out, rc); + if (ctx.getOptions().getStreamPackBufferSize() > 0) + rc.setReadAheadBytes(ctx.getOptions().getStreamPackBufferSize()); + long position = 12; + long remaining = length - (12 + 20); + while (0 < remaining) { + DfsBlock b = cache.get(key, alignToBlock(position)); + if (b != null) { + int ptr = (int) (position - b.start); + int n = (int) Math.min(b.size() - ptr, remaining); + b.write(out, position, n); + position += n; + remaining -= n; + rc.position(position); + continue; + } + + buf.position(0); + int n = read(rc, buf); + if (n <= 0) + throw packfileIsTruncated(); + else if (n > remaining) + n = (int) remaining; + out.write(buf.array(), 0, n); + position += n; + remaining -= n; + } + return position; + } + } + + private ByteBuffer newCopyBuffer(PackOutputStream out, ReadableChannel rc) { + int bs = blockSize(rc); + byte[] copyBuf = out.getCopyBuffer(); + if (bs > copyBuf.length) + copyBuf = new byte[bs]; + return ByteBuffer.wrap(copyBuf, 0, bs); } void copyAsIs(PackOutputStream out, DfsObjectToPack src, @@ -617,7 +679,7 @@ public final class DfsPackFile { // and we have it pinned. Write this out without copying. // out.writeHeader(src, inflatedLength); - quickCopy.write(out, dataOffset, (int) dataLength, null); + quickCopy.write(out, dataOffset, (int) dataLength); } else if (dataLength <= buf.length) { // Tiny optimization: Lots of objects are very small deltas or @@ -668,6 +730,12 @@ public final class DfsPackFile { invalid = true; } + private IOException packfileIsTruncated() { + invalid = true; + return new IOException(MessageFormat.format( + JGitText.get().packfileIsTruncated, getPackName())); + } + private void readFully(long position, byte[] dstbuf, int dstoff, int cnt, DfsReader ctx) throws IOException { if (ctx.copy(this, position, dstbuf, dstoff, cnt) != cnt) @@ -692,18 +760,8 @@ public final class DfsPackFile { ReadableChannel rc = ctx.db.openFile(packDesc, PACK); try { - // If the block alignment is not yet known, discover it. Prefer the - // larger size from either the cache or the file itself. - int size = blockSize; - if (size == 0) { - size = rc.blockSize(); - if (size <= 0) - size = cache.getBlockSize(); - else if (size < cache.getBlockSize()) - size = (cache.getBlockSize() / size) * size; - blockSize = size; - pos = (pos / size) * size; - } + int size = blockSize(rc); + pos = (pos / size) * size; // If the size of the file is not yet known, try to discover it. // Channels may choose to return -1 to indicate they don't @@ -725,7 +783,7 @@ public final class DfsPackFile { byte[] buf = new byte[size]; rc.position(pos); - int cnt = IO.read(rc, buf, 0, size); + int cnt = read(rc, ByteBuffer.wrap(buf, 0, size)); if (cnt != size) { if (0 <= len) { throw new EOFException(MessageFormat.format( @@ -754,6 +812,30 @@ public final class DfsPackFile { } } + private int blockSize(ReadableChannel rc) { + // If the block alignment is not yet known, discover it. Prefer the + // larger size from either the cache or the file itself. + int size = blockSize; + if (size == 0) { + size = rc.blockSize(); + if (size <= 0) + size = cache.getBlockSize(); + else if (size < cache.getBlockSize()) + size = (cache.getBlockSize() / size) * size; + blockSize = size; + } + return size; + } + + private static int read(ReadableChannel rc, ByteBuffer buf) + throws IOException { + int n; + do { + n = rc.read(buf); + } while (0 < n && buf.hasRemaining()); + return buf.position(); + } + ObjectLoader load(DfsReader ctx, long pos) throws IOException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java index 4cf7cbefc..f5f3375fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReader.java @@ -44,14 +44,10 @@ package org.eclipse.jgit.internal.storage.dfs; -import static org.eclipse.jgit.internal.storage.pack.PackExt.PACK; import static org.eclipse.jgit.lib.Constants.OBJECT_ID_LENGTH; import java.io.IOException; -import java.security.MessageDigest; -import java.text.MessageFormat; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Comparator; @@ -65,7 +61,6 @@ import java.util.zip.Inflater; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.file.BitmapIndexImpl; import org.eclipse.jgit.internal.storage.file.PackBitmapIndex; import org.eclipse.jgit.internal.storage.file.PackIndex; @@ -81,7 +76,6 @@ import org.eclipse.jgit.lib.AsyncObjectLoaderQueue; import org.eclipse.jgit.lib.AsyncObjectSizeQueue; import org.eclipse.jgit.lib.BitmapIndex; import org.eclipse.jgit.lib.BitmapIndex.BitmapBuilder; -import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.InflaterCache; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectLoader; @@ -498,9 +492,9 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { out.writeObject(otp); } - public void copyPackAsIs(PackOutputStream out, CachedPack pack, - boolean validate) throws IOException { - ((DfsCachedPack) pack).copyAsIs(out, validate, this); + public void copyPackAsIs(PackOutputStream out, CachedPack pack) + throws IOException { + ((DfsCachedPack) pack).copyAsIs(out, this); } /** @@ -547,52 +541,6 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { return cnt - need; } - void copyPackAsIs(DfsPackFile pack, long length, boolean validate, - PackOutputStream out) throws IOException { - MessageDigest md = null; - if (validate) { - md = Constants.newMessageDigest(); - byte[] buf = out.getCopyBuffer(); - pin(pack, 0); - if (block.copy(0, buf, 0, 12) != 12) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileIsTruncated, pack.getPackName())); - } - md.update(buf, 0, 12); - } - - long position = 12; - long remaining = length - (12 + 20); - while (0 < remaining) { - pin(pack, position); - - int ptr = (int) (position - block.start); - int n = (int) Math.min(block.size() - ptr, remaining); - block.write(out, position, n, md); - position += n; - remaining -= n; - } - - if (md != null) { - byte[] buf = new byte[20]; - byte[] actHash = md.digest(); - - pin(pack, position); - if (block.copy(position, buf, 0, 20) != 20) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileIsTruncated, pack.getPackName())); - } - if (!Arrays.equals(actHash, buf)) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileCorruptionDetected, - pack.getPackDescription().getFileName(PACK))); - } - } - } - /** * Inflate a region of the pack starting at {@code position}. * @@ -664,6 +612,10 @@ public final class DfsReader extends ObjectReader implements ObjectReuseAsIs { } } + void unpin() { + block = null; + } + /** Release the current window cursor. */ @Override public void release() { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java index 2a625473b..84198077e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/DfsReaderOptions.java @@ -46,6 +46,7 @@ package org.eclipse.jgit.internal.storage.dfs; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_CORE_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_DFS_SECTION; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_DELTA_BASE_CACHE_LIMIT; +import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_BUFFER; import static org.eclipse.jgit.lib.ConfigConstants.CONFIG_KEY_STREAM_FILE_TRESHOLD; import org.eclipse.jgit.lib.Config; @@ -60,9 +61,10 @@ public class DfsReaderOptions { public static final int MiB = 1024 * KiB; private int deltaBaseCacheLimit; - private int streamFileThreshold; + private int streamPackBufferSize; + /** Create a default reader configuration. */ public DfsReaderOptions() { setDeltaBaseCacheLimit(10 * MiB); @@ -104,6 +106,27 @@ public class DfsReaderOptions { return this; } + /** + * @return number of bytes to use for buffering when streaming a pack file + * during copying. If 0 the block size of the pack is used. + * @since 4.0 + */ + public int getStreamPackBufferSize() { + return streamPackBufferSize; + } + + /** + * @param bufsz + * new buffer size in bytes for buffers used when streaming pack + * files during copying. + * @return {@code this} + * @since 4.0 + */ + public DfsReaderOptions setStreamPackBufferSize(int bufsz) { + streamPackBufferSize = Math.max(0, bufsz); + return this; + } + /** * Update properties by setting fields from the configuration. *

@@ -130,6 +153,12 @@ public class DfsReaderOptions { sft = Math.min(sft, maxMem / 4); // don't use more than 1/4 of the heap sft = Math.min(sft, Integer.MAX_VALUE); // cannot exceed array length setStreamFileThreshold((int) sft); + + setStreamPackBufferSize(rc.getInt( + CONFIG_CORE_SECTION, + CONFIG_DFS_SECTION, + CONFIG_KEY_STREAM_BUFFER, + getStreamPackBufferSize())); return this; } } 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 ae05536de..8e7af0d29 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 @@ -227,6 +227,10 @@ public class InMemoryRepository extends DfsRepository { public int blockSize() { return 0; } + + public void setReadAheadBytes(int b) { + // Unnecessary on a byte array. + } } private class MemRefDatabase extends DfsRefDatabase { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java index 5ec7079a8..240d552aa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/ReadableChannel.java @@ -100,4 +100,33 @@ public interface ReadableChannel extends ReadableByteChannel { * not need to be a power of 2. */ public int blockSize(); + + /** + * Recommend the channel maintain a read-ahead buffer. + *

+ * A read-ahead buffer of approximately {@code bufferSize} in bytes may be + * allocated and used by the channel to smooth out latency for read. + *

+ * Callers can continue to read in smaller than {@code bufferSize} chunks. + * With read-ahead buffering enabled read latency may fluctuate in a pattern + * of one slower read followed by {@code (bufferSize / readSize) - 1} fast + * reads satisfied by the read-ahead buffer. When summed up overall time to + * read the same contiguous range should be lower than if read-ahead was not + * enabled, as the implementation can combine reads to increase throughput. + *

+ * To avoid unnecessary IO callers should only enable read-ahead if the + * majority of the channel will be accessed in order. + *

+ * Implementations may chose to read-ahead using asynchronous APIs or + * background threads, or may simply aggregate reads using a buffer. + *

+ * This read ahead stays in effect until the channel is closed or the buffer + * size is set to 0. + * + * @param bufferSize + * requested size of the read ahead buffer, in bytes. + * @throws IOException + * if the read ahead cannot be adjusted. + */ + public void setReadAheadBytes(int bufferSize) throws IOException; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java index 863c553b3..dc720bc62 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteArrayWindow.java @@ -46,7 +46,6 @@ package org.eclipse.jgit.internal.storage.file; import java.io.IOException; -import java.security.MessageDigest; import java.util.zip.CRC32; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -84,12 +83,10 @@ final class ByteArrayWindow extends ByteWindow { } @Override - void write(PackOutputStream out, long pos, int cnt, MessageDigest digest) + void write(PackOutputStream out, long pos, int cnt) throws IOException { int ptr = (int) (pos - start); out.write(array, ptr, cnt); - if (digest != null) - digest.update(array, ptr, cnt); } void check(Inflater inf, byte[] tmp, long pos, int cnt) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java index 31925d28e..05ddd69b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteBufferWindow.java @@ -47,7 +47,6 @@ package org.eclipse.jgit.internal.storage.file; import java.io.IOException; import java.nio.ByteBuffer; -import java.security.MessageDigest; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -76,7 +75,7 @@ final class ByteBufferWindow extends ByteWindow { } @Override - void write(PackOutputStream out, long pos, int cnt, MessageDigest digest) + void write(PackOutputStream out, long pos, int cnt) throws IOException { final ByteBuffer s = buffer.slice(); s.position((int) (pos - start)); @@ -86,8 +85,6 @@ final class ByteBufferWindow extends ByteWindow { int n = Math.min(cnt, buf.length); s.get(buf, 0, n); out.write(buf, 0, n); - if (digest != null) - digest.update(buf, 0, n); cnt -= n; } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java index ab5eb7c90..e774a1464 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ByteWindow.java @@ -45,7 +45,6 @@ package org.eclipse.jgit.internal.storage.file; import java.io.IOException; -import java.security.MessageDigest; import java.util.zip.DataFormatException; import java.util.zip.Inflater; @@ -121,8 +120,8 @@ abstract class ByteWindow { */ protected abstract int copy(int pos, byte[] dstbuf, int dstoff, int cnt); - abstract void write(PackOutputStream out, long pos, int cnt, - MessageDigest md) throws IOException; + abstract void write(PackOutputStream out, long pos, int cnt) + throws IOException; final int setInput(long pos, Inflater inf) throws DataFormatException { return setInput((int) (pos - start), inf); 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 48335e48c..338106f8e 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 @@ -618,22 +618,19 @@ public class GC { */ private Set listNonHEADIndexObjects() throws CorruptObjectException, IOException { - RevWalk revWalk = null; try { if (repo.getIndexFile() == null) return Collections.emptySet(); } catch (NoWorkTreeException e) { return Collections.emptySet(); } - TreeWalk treeWalk = new TreeWalk(repo); - try { + try (TreeWalk treeWalk = new TreeWalk(repo)) { treeWalk.addTree(new DirCacheIterator(repo.readDirCache())); ObjectId headID = repo.resolve(Constants.HEAD); if (headID != null) { - revWalk = new RevWalk(repo); - treeWalk.addTree(revWalk.parseTree(headID)); - revWalk.dispose(); - revWalk = null; + try (RevWalk revWalk = new RevWalk(repo)) { + treeWalk.addTree(revWalk.parseTree(headID)); + } } treeWalk.setFilter(TreeFilter.ANY_DIFF); @@ -662,10 +659,6 @@ public class GC { } } return ret; - } finally { - if (revWalk != null) - revWalk.dispose(); - treeWalk.release(); } } @@ -689,8 +682,9 @@ public class GC { } }); - PackWriter pw = new PackWriter((pconfig == null) ? new PackConfig(repo) : pconfig, repo.newObjectReader()); - try { + try (PackWriter pw = new PackWriter( + (pconfig == null) ? new PackConfig(repo) : pconfig, + repo.newObjectReader())) { // prepare the PackWriter pw.setDeltaBaseAsOffset(true); pw.setReuseDeltaCommits(false); @@ -810,7 +804,6 @@ public class GC { } return repo.getObjectDatabase().openPack(realPack); } finally { - pw.release(); if (tmpPack != null && tmpPack.exists()) tmpPack.delete(); for (File tmpExt : tmpExts.values()) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java index b70ebcf9e..fd9dcdafa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/LocalCachedPack.java @@ -79,10 +79,10 @@ class LocalCachedPack extends CachedPack { return cnt; } - void copyAsIs(PackOutputStream out, boolean validate, WindowCursor wc) + void copyAsIs(PackOutputStream out, WindowCursor wc) throws IOException { for (PackFile pack : getPacks()) - pack.copyPackAsIs(out, validate, wc); + pack.copyPackAsIs(out, wc); } @Override 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 687408e19..796109aee 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 @@ -114,6 +114,8 @@ public class ObjectDirectory extends FileObjectDatabase { /** Maximum number of candidates offered as resolutions of abbreviation. */ private static final int RESOLVE_ABBREV_LIMIT = 256; + private static final String STALE_FILE_HANDLE_MSG = "stale file handle"; //$NON-NLS-1$ + private final Config config; private final File objects; @@ -554,22 +556,35 @@ public class ObjectDirectory extends FileObjectDatabase { } private void handlePackError(IOException e, PackFile p) { - String tmpl; + String warnTmpl = null; if ((e instanceof CorruptObjectException) || (e instanceof PackInvalidException)) { - tmpl = JGitText.get().corruptPack; + warnTmpl = JGitText.get().corruptPack; // Assume the pack is corrupted, and remove it from the list. removePack(p); } else if (e instanceof FileNotFoundException) { - tmpl = JGitText.get().packWasDeleted; + warnTmpl = JGitText.get().packWasDeleted; + removePack(p); + } else if (e.getMessage() != null + && e.getMessage().toLowerCase().contains(STALE_FILE_HANDLE_MSG)) { + warnTmpl = JGitText.get().packHandleIsStale; removePack(p); + } + if (warnTmpl != null) { + if (LOG.isDebugEnabled()) { + LOG.debug(MessageFormat.format(warnTmpl, + p.getPackFile().getAbsolutePath()), e); + } else { + LOG.warn(MessageFormat.format(warnTmpl, + p.getPackFile().getAbsolutePath())); + } } else { - tmpl = JGitText.get().exceptionWhileReadingPack; // Don't remove the pack from the list, as the error may be // transient. + LOG.error(MessageFormat.format( + JGitText.get().exceptionWhileReadingPack, p.getPackFile() + .getAbsolutePath()), e); } - LOG.error(MessageFormat.format(tmpl, - p.getPackFile().getAbsolutePath()), e); } @Override diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java index 812c899a8..eb87460d8 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectoryInserter.java @@ -55,10 +55,12 @@ import java.io.OutputStream; import java.nio.channels.Channels; import java.security.DigestOutputStream; import java.security.MessageDigest; +import java.text.MessageFormat; import java.util.zip.Deflater; import java.util.zip.DeflaterOutputStream; import org.eclipse.jgit.errors.ObjectWritingException; +import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.Constants; import org.eclipse.jgit.lib.ObjectId; @@ -123,7 +125,8 @@ class ObjectDirectoryInserter extends ObjectInserter { } final File dst = db.fileFor(id); - throw new ObjectWritingException("Unable to create new object: " + dst); + throw new ObjectWritingException(MessageFormat + .format(JGitText.get().unableToCreateNewObject, dst)); } @Override @@ -242,7 +245,7 @@ class ObjectDirectoryInserter extends ObjectInserter { } private static EOFException shortInput(long missing) { - return new EOFException("Input did not match supplied length. " - + missing + " bytes are missing."); + return new EOFException(MessageFormat.format( + JGitText.get().inputDidntMatchLength, Long.valueOf(missing))); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java index eb2293874..75c361e10 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackFile.java @@ -344,11 +344,11 @@ public class PackFile implements Iterable { return dstbuf; } - void copyPackAsIs(PackOutputStream out, boolean validate, WindowCursor curs) + void copyPackAsIs(PackOutputStream out, WindowCursor curs) throws IOException { // Pin the first window, this ensures the length is accurate. curs.pin(this, 0); - curs.copyPackAsIs(this, length, validate, out); + curs.copyPackAsIs(this, length, out); } final void copyAsIs(PackOutputStream out, LocalObjectToPack src, @@ -362,6 +362,7 @@ public class PackFile implements Iterable { } } + @SuppressWarnings("null") private void copyAsIs2(PackOutputStream out, LocalObjectToPack src, boolean validate, WindowCursor curs) throws IOException, StoredObjectRepresentationNotAvailableException { @@ -501,7 +502,7 @@ public class PackFile implements Iterable { // and we have it pinned. Write this out without copying. // out.writeHeader(src, inflatedLength); - quickCopy.write(out, dataOffset, (int) dataLength, null); + quickCopy.write(out, dataOffset, (int) dataLength); } else if (dataLength <= buf.length) { // Tiny optimization: Lots of objects are very small deltas or @@ -703,6 +704,7 @@ public class PackFile implements Iterable { , getPackFile())); } + @SuppressWarnings("null") ObjectLoader load(final WindowCursor curs, long pos) throws IOException, LargeObjectException { try { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java index 70cf20fb9..cb8c91a5a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/PackIndexV2.java @@ -232,7 +232,7 @@ class PackIndexV2 extends PackIndex { final int levelOne = objId.getFirstByte(); final int levelTwo = binarySearchLevelTwo(objId, levelOne); if (levelTwo == -1) - throw new MissingObjectException(objId.copy(), "unknown"); + throw new MissingObjectException(objId.copy(), "unknown"); //$NON-NLS-1$ return NB.decodeUInt32(crc32[levelOne], levelTwo << 2); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java index 85c3c7425..21d6cd2ca 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/WindowCursor.java @@ -45,9 +45,6 @@ package org.eclipse.jgit.internal.storage.file; import java.io.IOException; -import java.security.MessageDigest; -import java.text.MessageFormat; -import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -59,7 +56,6 @@ import java.util.zip.Inflater; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; import org.eclipse.jgit.errors.StoredObjectRepresentationNotAvailableException; -import org.eclipse.jgit.internal.JGitText; import org.eclipse.jgit.internal.storage.pack.CachedPack; import org.eclipse.jgit.internal.storage.pack.ObjectReuseAsIs; import org.eclipse.jgit.internal.storage.pack.ObjectToPack; @@ -232,27 +228,13 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { return cnt - need; } - public void copyPackAsIs(PackOutputStream out, CachedPack pack, - boolean validate) throws IOException { - ((LocalCachedPack) pack).copyAsIs(out, validate, this); + public void copyPackAsIs(PackOutputStream out, CachedPack pack) + throws IOException { + ((LocalCachedPack) pack).copyAsIs(out, this); } - void copyPackAsIs(final PackFile pack, final long length, boolean validate, + void copyPackAsIs(final PackFile pack, final long length, final PackOutputStream out) throws IOException { - MessageDigest md = null; - if (validate) { - md = Constants.newMessageDigest(); - byte[] buf = out.getCopyBuffer(); - pin(pack, 0); - if (window.copy(0, buf, 0, 12) != 12) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileIsTruncated, pack.getPackFile() - .getPath())); - } - md.update(buf, 0, 12); - } - long position = 12; long remaining = length - (12 + 20); while (0 < remaining) { @@ -260,29 +242,10 @@ final class WindowCursor extends ObjectReader implements ObjectReuseAsIs { int ptr = (int) (position - window.start); int n = (int) Math.min(window.size() - ptr, remaining); - window.write(out, position, n, md); + window.write(out, position, n); position += n; remaining -= n; } - - if (md != null) { - byte[] buf = new byte[20]; - byte[] actHash = md.digest(); - - pin(pack, position); - if (window.copy(position, buf, 0, 20) != 20) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileIsTruncated, pack.getPackFile() - .getPath())); - } - if (!Arrays.equals(actHash, buf)) { - pack.setInvalid(); - throw new IOException(MessageFormat.format( - JGitText.get().packfileCorruptionDetected, pack - .getPackFile().getPath())); - } - } } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java index 00b6b6536..2e5d59960 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/ObjectReuseAsIs.java @@ -209,16 +209,11 @@ public interface ObjectReuseAsIs { * stream to append the pack onto. * @param pack * the cached pack to send. - * @param validate - * if true the representation must be validated and not be - * corrupt before being reused. If false, validation may be - * skipped as it will be performed elsewhere in the processing - * pipeline. * @throws IOException * the pack cannot be read, or stream did not accept a write. */ - public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack, - boolean validate) throws IOException; + public abstract void copyPackAsIs(PackOutputStream out, CachedPack pack) + throws IOException; /** * Obtain the available cached packs that match the bitmap and update diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java index 510538d86..6d0c8e6cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/pack/PackWriter.java @@ -1048,7 +1048,7 @@ public class PackWriter implements AutoCloseable { stats.reusedObjects += pack.getObjectCount(); stats.reusedDeltas += deltaCnt; stats.totalDeltas += deltaCnt; - reuseSupport.copyPackAsIs(out, pack, reuseValidate); + reuseSupport.copyPackAsIs(out, pack); } writeChecksum(out); out.flush(); @@ -1866,7 +1866,7 @@ public class PackWriter implements AutoCloseable { false); BitmapBuilder needBitmap = wantBitmap.andNot(haveBitmap); - if (useCachedPacks && reuseSupport != null + if (useCachedPacks && reuseSupport != null && !reuseValidate && (excludeInPacks == null || excludeInPacks.length == 0)) cachedPacks.addAll( reuseSupport.getCachedPacksAndUpdate(needBitmap)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java index a0197d022..cbb2f5b85 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/BlobBasedConfig.java @@ -104,11 +104,8 @@ public class BlobBasedConfig extends Config { private static byte[] read(Repository db, AnyObjectId blobId) throws MissingObjectException, IncorrectObjectTypeException, IOException { - ObjectReader or = db.newObjectReader(); - try { + try (ObjectReader or = db.newObjectReader()) { return read(or, blobId); - } finally { - or.release(); } } @@ -146,15 +143,12 @@ public class BlobBasedConfig extends Config { private static byte[] read(Repository db, AnyObjectId treeish, String path) throws MissingObjectException, IncorrectObjectTypeException, IOException { - ObjectReader or = db.newObjectReader(); - try { + try (ObjectReader or = db.newObjectReader()) { TreeWalk tree = TreeWalk.forPath(or, path, asTree(or, treeish)); if (tree == null) throw new FileNotFoundException(MessageFormat.format(JGitText .get().entryNotFoundByPath, path)); return read(or, tree.getObjectId(0)); - } finally { - or.release(); } } @@ -168,6 +162,8 @@ public class BlobBasedConfig extends Config { && ((RevCommit) treeish).getTree() != null) return ((RevCommit) treeish).getTree(); - return new RevWalk(or).parseTree(treeish).getId(); + try (RevWalk rw = new RevWalk(or)) { + return rw.parseTree(treeish).getId(); + } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java index 8a2080bac..a89bcee73 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ConfigConstants.java @@ -299,4 +299,16 @@ public class ConfigConstants { * @since 3.3 */ public static final String CONFIG_KEY_PRUNE = "prune"; + + /** + * The "streamBuffer" key + * @since 4.0 + */ + public static final String CONFIG_KEY_STREAM_BUFFER = "streamBuffer"; + + /** + * The "streamRatio" key + * @since 4.0 + */ + public static final String CONFIG_KEY_STREAM_RATIO = "streamRatio"; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java index 8435c9a64..359b59295 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectChecker.java @@ -104,6 +104,8 @@ public class ObjectChecker { private final MutableInteger ptrout = new MutableInteger(); private boolean allowZeroMode; + + private boolean allowInvalidPersonIdent; private boolean windows; private boolean macosx; @@ -124,6 +126,22 @@ public class ObjectChecker { return this; } + /** + * Enable accepting invalid author, committer and tagger identities. + *

+ * Some broken Git versions/libraries allowed users to create commits and + * tags with invalid formatting between the name, email and timestamp. + * + * @param allow + * if true accept invalid person identity strings. + * @return {@code this}. + * @since 4.0 + */ + public ObjectChecker setAllowInvalidPersonIdent(boolean allow) { + allowInvalidPersonIdent = allow; + return this; + } + /** * Restrict trees to only names legal on Windows platforms. *

@@ -198,6 +216,9 @@ public class ObjectChecker { } private int personIdent(final byte[] raw, int ptr) { + if (allowInvalidPersonIdent) + return nextLF(raw, ptr) - 1; + final int emailB = nextLF(raw, ptr, '<'); if (emailB == ptr || raw[emailB - 1] != '<') return -1; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java index 0cc51d1a5..2abd6dae6 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -120,11 +120,8 @@ public abstract class ObjectDatabase { * the object store cannot be accessed. */ public boolean has(final AnyObjectId objectId) throws IOException { - final ObjectReader or = newReader(); - try { + try (final ObjectReader or = newReader()) { return or.has(objectId); - } finally { - or.release(); } } @@ -172,11 +169,8 @@ public abstract class ObjectDatabase { public ObjectLoader open(AnyObjectId objectId, int typeHint) throws MissingObjectException, IncorrectObjectTypeException, IOException { - final ObjectReader or = newReader(); - try { + try (final ObjectReader or = newReader()) { return or.open(objectId, typeHint); - } finally { - or.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java new file mode 100644 index 000000000..1ddac18e5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/EolAwareOutputStream.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2014, André de Oliveira + * + * 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.merge; + +import java.io.IOException; +import java.io.OutputStream; + +/** + * An output stream which is aware of newlines and can be asked to begin a new + * line if not already in one. + */ +class EolAwareOutputStream extends OutputStream { + private final OutputStream out; + + private boolean bol = true; + + /** + * Initialize a new EOL aware stream. + * + * @param out + * stream to output all writes to. + */ + EolAwareOutputStream(OutputStream out) { + this.out = out; + } + + /** + * Begin a new line if not already in one. + * + * @exception IOException + * if an I/O error occurs. + */ + void beginln() throws IOException { + if (!bol) + write('\n'); + } + + /** @return true if a new line has just begun. */ + boolean isBeginln() { + return bol; + } + + @Override + public void write(int val) throws IOException { + out.write(val); + bol = (val == '\n'); + } + + @Override + public void write(byte[] buf, int pos, int cnt) throws IOException { + if (cnt > 0) { + out.write(buf, pos, cnt); + bol = (buf[pos + (cnt - 1)] == '\n'); + } + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java index eed1dcfa8..977f95341 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatter.java @@ -49,7 +49,6 @@ import java.util.ArrayList; import java.util.List; import org.eclipse.jgit.diff.RawText; -import org.eclipse.jgit.merge.MergeChunk.ConflictState; /** * A class to convert merge results into a Git conformant textual presentation @@ -78,47 +77,7 @@ public class MergeFormatter { */ public void formatMerge(OutputStream out, MergeResult res, List seqName, String charsetName) throws IOException { - String lastConflictingName = null; // is set to non-null whenever we are - // in a conflict - boolean threeWayMerge = (res.getSequences().size() == 3); - for (MergeChunk chunk : res) { - RawText seq = res.getSequences().get(chunk.getSequenceIndex()); - if (lastConflictingName != null - && chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) { - // found the end of an conflict - out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$ - lastConflictingName = null; - } - if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) { - // found the start of an conflict - out.write(("<<<<<<< " + seqName.get(chunk.getSequenceIndex()) + //$NON-NLS-1$ - "\n").getBytes(charsetName)); //$NON-NLS-1$ - lastConflictingName = seqName.get(chunk.getSequenceIndex()); - } else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) { - // found another conflicting chunk - - /* - * In case of a non-three-way merge I'll add the name of the - * conflicting chunk behind the equal signs. I also append the - * name of the last conflicting chunk after the ending - * greater-than signs. If somebody knows a better notation to - * present non-three-way merges - feel free to correct here. - */ - lastConflictingName = seqName.get(chunk.getSequenceIndex()); - out.write((threeWayMerge ? "=======\n" : "======= " //$NON-NLS-1$ //$NON-NLS-2$ - + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ - } - // the lines with conflict-metadata are written. Now write the chunk - for (int i = chunk.getBegin(); i < chunk.getEnd(); i++) { - seq.writeLine(out, i); - out.write('\n'); - } - } - // one possible leftover: if the merge result ended with a conflict we - // have to close the last conflict here - if (lastConflictingName != null) { - out.write((">>>>>>> " + lastConflictingName + "\n").getBytes(charsetName)); //$NON-NLS-1$ //$NON-NLS-2$ - } + new MergeFormatterPass(out, res, seqName, charsetName).formatMerge(); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java new file mode 100644 index 000000000..0345921bd --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/MergeFormatterPass.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2009, Christian Halstrick + * Copyright (C) 2014, André de Oliveira + * 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.merge; + +import java.io.IOException; +import java.io.OutputStream; +import java.util.List; + +import org.eclipse.jgit.diff.RawText; +import org.eclipse.jgit.merge.MergeChunk.ConflictState; + +class MergeFormatterPass { + + private final EolAwareOutputStream out; + + private final MergeResult res; + + private final List seqName; + + private final String charsetName; + + private final boolean threeWayMerge; + + private String lastConflictingName; // is set to non-null whenever we are in + // a conflict + + MergeFormatterPass(OutputStream out, MergeResult res, List seqName, + String charsetName) { + this.out = new EolAwareOutputStream(out); + this.res = res; + this.seqName = seqName; + this.charsetName = charsetName; + this.threeWayMerge = (res.getSequences().size() == 3); + } + + void formatMerge() throws IOException { + boolean missingNewlineAtEnd = false; + for (MergeChunk chunk : res) { + RawText seq = res.getSequences().get(chunk.getSequenceIndex()); + writeConflictMetadata(chunk); + // the lines with conflict-metadata are written. Now write the chunk + for (int i = chunk.getBegin(); i < chunk.getEnd(); i++) + writeLine(seq, i); + missingNewlineAtEnd = seq.isMissingNewlineAtEnd(); + } + // one possible leftover: if the merge result ended with a conflict we + // have to close the last conflict here + if (lastConflictingName != null) + writeConflictEnd(); + if (!missingNewlineAtEnd) + out.beginln(); + } + + private void writeConflictMetadata(MergeChunk chunk) throws IOException { + if (lastConflictingName != null + && chunk.getConflictState() != ConflictState.NEXT_CONFLICTING_RANGE) { + // found the end of an conflict + writeConflictEnd(); + } + if (chunk.getConflictState() == ConflictState.FIRST_CONFLICTING_RANGE) { + // found the start of an conflict + writeConflictStart(chunk); + } else if (chunk.getConflictState() == ConflictState.NEXT_CONFLICTING_RANGE) { + // found another conflicting chunk + writeConflictChange(chunk); + } + } + + private void writeConflictEnd() throws IOException { + writeln(">>>>>>> " + lastConflictingName); //$NON-NLS-1$ + lastConflictingName = null; + } + + private void writeConflictStart(MergeChunk chunk) throws IOException { + lastConflictingName = seqName.get(chunk.getSequenceIndex()); + writeln("<<<<<<< " + lastConflictingName); //$NON-NLS-1$ + } + + private void writeConflictChange(MergeChunk chunk) throws IOException { + /* + * In case of a non-three-way merge I'll add the name of the conflicting + * chunk behind the equal signs. I also append the name of the last + * conflicting chunk after the ending greater-than signs. If somebody + * knows a better notation to present non-three-way merges - feel free + * to correct here. + */ + lastConflictingName = seqName.get(chunk.getSequenceIndex()); + writeln(threeWayMerge ? "=======" : "======= " //$NON-NLS-1$ //$NON-NLS-2$ + + lastConflictingName); + } + + private void writeln(String s) throws IOException { + out.beginln(); + out.write((s + "\n").getBytes(charsetName)); //$NON-NLS-1$ + } + + private void writeLine(RawText seq, int i) throws IOException { + out.beginln(); + seq.writeLine(out, i); + // still BOL? It was a blank line. But writeLine won't lf, so we do. + if (out.isBeginln()) + out.write('\n'); + } +} \ No newline at end of file diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java index d786a18ff..a76dd350d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/Merger.java @@ -125,9 +125,9 @@ public abstract class Merger { * repository instance returned by {@link #getRepository()}. */ public void setObjectInserter(ObjectInserter oi) { - walk.release(); - reader.release(); - inserter.release(); + walk.close(); + reader.close(); + inserter.close(); inserter = oi; reader = oi.newReader(); walk = new RevWalk(reader); @@ -206,8 +206,8 @@ public abstract class Merger { return ok; } finally { if (flush) - inserter.release(); - reader.release(); + inserter.close(); + reader.close(); } } 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 85cdb7684..36ffe7a63 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/RecursiveMerger.java @@ -68,6 +68,8 @@ import org.eclipse.jgit.lib.PersonIdent; import org.eclipse.jgit.lib.Repository; 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; @@ -194,10 +196,12 @@ public class RecursiveMerger extends ResolveMerger { Integer.valueOf(MAX_BASES), a.name(), b.name(), Integer.valueOf(baseCommits.size()))); parents.add(nextBase); - if (mergeTrees( - openTree(getBaseCommit(currentBase, nextBase, - callDepth + 1).getTree()), - currentBase.getTree(), nextBase.getTree(), true)) + RevCommit bc = getBaseCommit(currentBase, nextBase, + callDepth + 1); + AbstractTreeIterator bcTree = (bc == null) ? new EmptyTreeIterator() + : openTree(bc.getTree()); + if (mergeTrees(bcTree, currentBase.getTree(), + nextBase.getTree(), true)) currentBase = createCommitForTree(resultTree, parents); else throw new NoMergeBaseException( 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 8e70f57fa..953d3a2cd 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/merge/ResolveMerger.java @@ -47,12 +47,14 @@ package org.eclipse.jgit.merge; import static org.eclipse.jgit.lib.Constants.CHARACTER_ENCODING; import static org.eclipse.jgit.lib.Constants.OBJ_BLOB; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStream; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -791,25 +793,25 @@ public class ResolveMerger extends ThreeWayMerger { File parentFolder = of.getParentFile(); if (!fs.exists(parentFolder)) parentFolder.mkdirs(); - FileOutputStream fos = new FileOutputStream(of); - try { - new MergeFormatter().formatMerge(fos, result, + try (OutputStream os = new BufferedOutputStream( + new FileOutputStream(of))) { + new MergeFormatter().formatMerge(os, result, Arrays.asList(commitNames), CHARACTER_ENCODING); - } finally { - fos.close(); } return of; } private ObjectId insertMergeResult(MergeResult result) throws IOException { - TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile(10 << 20); + TemporaryBuffer.LocalFile buf = new TemporaryBuffer.LocalFile( + db.getDirectory(), 10 << 20); try { new MergeFormatter().formatMerge(buf, result, Arrays.asList(commitNames), CHARACTER_ENCODING); buf.close(); - return getObjectInserter().insert(OBJ_BLOB, buf.length(), - buf.openInputStream()); + try (InputStream in = buf.openInputStream()) { + return getObjectInserter().insert(OBJ_BLOB, buf.length(), in); + } } finally { buf.destroy(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java index 061447666..19ec1a13d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/notes/NoteMapMerger.java @@ -135,8 +135,8 @@ public class NoteMapMerger { inserter.flush(); return NoteMap.newMap(mergedBucket, reader); } finally { - reader.release(); - inserter.release(); + reader.close(); + inserter.close(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java index b73ccb197..a0af067dc 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/revwalk/ObjectWalk.java @@ -232,7 +232,7 @@ public class ObjectWalk extends RevWalk { } if (o instanceof RevCommit) - markUninteresting((RevCommit) o); + super.markUninteresting((RevCommit) o); else if (o instanceof RevTree) markTreeUninteresting((RevTree) o); else @@ -242,18 +242,6 @@ public class ObjectWalk extends RevWalk { addObject(o); } - @Override - public void markUninteresting(RevCommit c) throws MissingObjectException, - IncorrectObjectTypeException, IOException { - super.markUninteresting(c); - try { - markTreeUninteresting(c.getTree()); - } catch (MissingObjectException e) { - // we don't care if the tree of the commit does not exist locally - } - } - - @Override public void sort(RevSort s) { super.sort(s); boundary = hasRevSort(RevSort.BOUNDARY); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java index 1072d58ad..59ff1bd99 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseConnection.java @@ -65,6 +65,8 @@ import org.eclipse.jgit.lib.Ref; public abstract class BaseConnection implements Connection { private Map advertisedRefs = Collections.emptyMap(); + private String peerUserAgent; + private boolean startedOperation; private Writer messageWriter; @@ -85,6 +87,28 @@ public abstract class BaseConnection implements Connection { return messageWriter != null ? messageWriter.toString() : ""; //$NON-NLS-1$ } + /** + * User agent advertised by the remote server. + * + * @return agent (version of Git) running on the remote server. Null if the + * server does not advertise this version. + * @since 4.0 + */ + public String getPeerUserAgent() { + return peerUserAgent; + } + + /** + * Remember the remote peer's agent. + * + * @param agent + * remote peer agent string. + * @since 4.0 + */ + protected void setPeerUserAgent(String agent) { + peerUserAgent = agent; + } + public abstract void close(); /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java index 8f825ea14..7f9cec734 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackConnection.java @@ -46,6 +46,8 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; + import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -275,6 +277,18 @@ abstract class BasePackConnection extends BaseConnection { return true; } + protected void addUserAgentCapability(StringBuilder b) { + String a = UserAgent.get(); + if (a != null && UserAgent.hasAgent(remoteCapablities)) { + b.append(' ').append(OPTION_AGENT).append('=').append(a); + } + } + + @Override + public String getPeerUserAgent() { + return UserAgent.getAgent(remoteCapablities, super.getPeerUserAgent()); + } + private PackProtocolException duplicateAdvertisement(final String name) { return new PackProtocolException(uri, MessageFormat.format(JGitText.get().duplicateAdvertisementsOf, name)); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java index f907891ba..a6fc63359 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -377,7 +377,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection @Override public void close() { if (walk != null) - walk.release(); + walk.close(); super.close(); } @@ -521,6 +521,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection OPTION_MULTI_ACK_DETAILED)); } + addUserAgentCapability(line); return line.toString(); } @@ -753,16 +754,13 @@ public abstract class BasePackFetchConnection extends BasePackConnection input = new SideBandInputStream(input, monitor, getMessageWriter(), outputStream); - ObjectInserter ins = local.newObjectInserter(); - try { + try (ObjectInserter ins = local.newObjectInserter()) { PackParser parser = ins.newPackParser(input); parser.setAllowThin(thinPack); parser.setObjectChecker(transport.getObjectChecker()); parser.setLockMessage(lockMessage); packLock = parser.parse(monitor); ins.flush(); - } finally { - ins.release(); } } 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 e367ab44c..1e5b8e8ad 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackPushConnection.java @@ -268,6 +268,7 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen outputStream); pckIn = new PacketLineIn(in); } + addUserAgentCapability(line); if (line.length() > 0) line.setCharAt(0, '\0'); @@ -279,9 +280,8 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen Set remoteObjects = new HashSet(); Set newObjects = new HashSet(); - final PackWriter writer = new PackWriter(transport.getPackConfig(), - local.newObjectReader()); - try { + try (final PackWriter writer = new PackWriter(transport.getPackConfig(), + local.newObjectReader())) { for (final Ref r : getRefs()) { // only add objects that we actually have @@ -303,10 +303,9 @@ public abstract class BasePackPushConnection extends BasePackConnection implemen writer.setDeltaBaseAsOffset(capableOfsDelta); writer.preparePack(monitor, newObjects, remoteObjects); writer.writePack(monitor, monitor, out); - } finally { - writer.release(); + + packTransferTime = writer.getStatistics().getTimeWriting(); } - packTransferTime = writer.getStatistics().getTimeWriting(); } private void readStatusReport(final Map refUpdates) diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java index dfb8ca93a..cf6b2fd3d 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BaseReceivePack.java @@ -48,6 +48,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_DELETE_ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_OFS_DELTA; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_REPORT_STATUS; import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_SIDE_BAND_64K; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; import static org.eclipse.jgit.transport.SideBandOutputStream.MAX_BUF; @@ -224,6 +225,7 @@ public abstract class BaseReceivePack { /** Capabilities requested by the client. */ private Set enabledCapabilities; + String userAgent; private Set clientShallowCommits; private List commands; @@ -289,6 +291,7 @@ public abstract class BaseReceivePack { final boolean checkReceivedObjects; final boolean allowLeadingZeroFileMode; + final boolean allowInvalidPersonIdent; final boolean safeForWindows; final boolean safeForMacOS; @@ -306,6 +309,8 @@ public abstract class BaseReceivePack { config.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ allowLeadingZeroFileMode = checkReceivedObjects && config.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ + allowInvalidPersonIdent = checkReceivedObjects + && config.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ safeForWindows = checkReceivedObjects && config.getBoolean("fsck", "safeForWindows", false); //$NON-NLS-1$ //$NON-NLS-2$ safeForMacOS = checkReceivedObjects @@ -317,7 +322,7 @@ public abstract class BaseReceivePack { "denynonfastforwards", false); //$NON-NLS-1$ allowOfsDelta = config.getBoolean("repack", "usedeltabaseoffset", //$NON-NLS-1$ //$NON-NLS-2$ true); - certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ + certNonceSeed = config.getString("receive", null, "certnonceseed"); //$NON-NLS-1$ //$NON-NLS-2$ certNonceSlopLimit = config.getInt("receive", "certnonceslop", 0); //$NON-NLS-1$ //$NON-NLS-2$ } @@ -326,6 +331,7 @@ public abstract class BaseReceivePack { return null; return new ObjectChecker() .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) + .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) .setSafeForMacOS(safeForMacOS); } @@ -734,6 +740,25 @@ public abstract class BaseReceivePack { return enabledCapabilities.contains(CAPABILITY_SIDE_BAND_64K); } + /** + * Get the user agent of the client. + *

+ * If the client is new enough to use {@code agent=} capability that value + * will be returned. Older HTTP clients may also supply their version using + * the HTTP {@code User-Agent} header. The capability overrides the HTTP + * header if both are available. + *

+ * When an HTTP request has been received this method returns the HTTP + * {@code User-Agent} header value until capabilities have been parsed. + * + * @return user agent supplied by the client. Available only if the client + * is new enough to advertise its user agent. + * @since 4.0 + */ + public String getPeerUserAgent() { + return UserAgent.getAgent(enabledCapabilities, userAgent); + } + /** @return all of the command received by the current request. */ public List getAllCommands() { return Collections.unmodifiableList(commands); @@ -951,6 +976,7 @@ public abstract class BaseReceivePack { adv.advertiseCapability(CAPABILITY_ATOMIC); if (allowOfsDelta) adv.advertiseCapability(CAPABILITY_OFS_DELTA); + adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.send(getAdvertisedOrDefaultRefs()); for (ObjectId obj : advertisedHaves) adv.advertiseHave(obj); @@ -1070,8 +1096,7 @@ public abstract class BaseReceivePack { if (sideBand) resolving = new SideBandProgressMonitor(msgOut); - ObjectInserter ins = db.newObjectInserter(); - try { + try (ObjectInserter ins = db.newObjectInserter()) { String lockMsg = "jgit receive-pack"; //$NON-NLS-1$ if (getRefLogIdent() != null) lockMsg += " from " + getRefLogIdent().toExternalString(); //$NON-NLS-1$ @@ -1089,8 +1114,6 @@ public abstract class BaseReceivePack { packLock = parser.parse(receiving, resolving); packSize = Long.valueOf(parser.getPackSize()); ins.flush(); - } finally { - ins.release(); } if (timeoutIn != null) @@ -1119,67 +1142,69 @@ public abstract class BaseReceivePack { } parser = null; - final ObjectWalk ow = new ObjectWalk(db); - ow.setRetainBody(false); - if (baseObjects != null) { - ow.sort(RevSort.TOPO); - if (!baseObjects.isEmpty()) - ow.sort(RevSort.BOUNDARY, true); - } - - for (final ReceiveCommand cmd : commands) { - if (cmd.getResult() != Result.NOT_ATTEMPTED) - continue; - if (cmd.getType() == ReceiveCommand.Type.DELETE) - continue; - ow.markStart(ow.parseAny(cmd.getNewId())); - } - for (final ObjectId have : advertisedHaves) { - RevObject o = ow.parseAny(have); - ow.markUninteresting(o); - - if (baseObjects != null && !baseObjects.isEmpty()) { - o = ow.peel(o); - if (o instanceof RevCommit) - o = ((RevCommit) o).getTree(); - if (o instanceof RevTree) - ow.markUninteresting(o); + try (final ObjectWalk ow = new ObjectWalk(db)) { + ow.setRetainBody(false); + if (baseObjects != null) { + ow.sort(RevSort.TOPO); + if (!baseObjects.isEmpty()) + ow.sort(RevSort.BOUNDARY, true); } - } - checking.beginTask(JGitText.get().countingObjects, ProgressMonitor.UNKNOWN); - RevCommit c; - while ((c = ow.next()) != null) { - checking.update(1); - if (providedObjects != null // - && !c.has(RevFlag.UNINTERESTING) // - && !providedObjects.contains(c)) - throw new MissingObjectException(c, Constants.TYPE_COMMIT); - } + for (final ReceiveCommand cmd : commands) { + if (cmd.getResult() != Result.NOT_ATTEMPTED) + continue; + if (cmd.getType() == ReceiveCommand.Type.DELETE) + continue; + ow.markStart(ow.parseAny(cmd.getNewId())); + } + for (final ObjectId have : advertisedHaves) { + RevObject o = ow.parseAny(have); + ow.markUninteresting(o); + + if (baseObjects != null && !baseObjects.isEmpty()) { + o = ow.peel(o); + if (o instanceof RevCommit) + o = ((RevCommit) o).getTree(); + if (o instanceof RevTree) + ow.markUninteresting(o); + } + } - RevObject o; - while ((o = ow.nextObject()) != null) { - checking.update(1); - if (o.has(RevFlag.UNINTERESTING)) - continue; + checking.beginTask(JGitText.get().countingObjects, + ProgressMonitor.UNKNOWN); + RevCommit c; + while ((c = ow.next()) != null) { + checking.update(1); + if (providedObjects != null // + && !c.has(RevFlag.UNINTERESTING) // + && !providedObjects.contains(c)) + throw new MissingObjectException(c, Constants.TYPE_COMMIT); + } - if (providedObjects != null) { - if (providedObjects.contains(o)) + RevObject o; + while ((o = ow.nextObject()) != null) { + checking.update(1); + if (o.has(RevFlag.UNINTERESTING)) continue; - else - throw new MissingObjectException(o, o.getType()); - } - if (o instanceof RevBlob && !db.hasObject(o)) - throw new MissingObjectException(o, Constants.TYPE_BLOB); - } - checking.endTask(); + if (providedObjects != null) { + if (providedObjects.contains(o)) + continue; + else + throw new MissingObjectException(o, o.getType()); + } - if (baseObjects != null) { - for (ObjectId id : baseObjects) { - o = ow.parseAny(id); - if (!o.has(RevFlag.UNINTERESTING)) - throw new MissingObjectException(o, o.getType()); + if (o instanceof RevBlob && !db.hasObject(o)) + throw new MissingObjectException(o, Constants.TYPE_BLOB); + } + checking.endTask(); + + if (baseObjects != null) { + for (ObjectId id : baseObjects) { + o = ow.parseAny(id); + if (!o.has(RevFlag.UNINTERESTING)) + throw new MissingObjectException(o, o.getType()); + } } } } @@ -1502,7 +1527,7 @@ public abstract class BaseReceivePack { * the pack could not be unlocked. */ protected void release() throws IOException { - walk.release(); + walk.close(); unlockPack(); timeoutIn = null; rawIn = null; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java index e3cfd22ad..e53c04b53 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleFetchConnection.java @@ -183,16 +183,13 @@ class BundleFetchConnection extends BaseFetchConnection { throws TransportException { verifyPrerequisites(); try { - ObjectInserter ins = transport.local.newObjectInserter(); - try { + try (ObjectInserter ins = transport.local.newObjectInserter()) { PackParser parser = ins.newPackParser(bin); parser.setAllowThin(true); parser.setObjectChecker(transport.getObjectChecker()); parser.setLockMessage(lockMessage); packLock = parser.parse(NullProgressMonitor.INSTANCE); ins.flush(); - } finally { - ins.release(); } } catch (IOException err) { close(); @@ -217,8 +214,7 @@ class BundleFetchConnection extends BaseFetchConnection { if (prereqs.isEmpty()) return; - final RevWalk rw = new RevWalk(transport.local); - try { + try (final RevWalk rw = new RevWalk(transport.local)) { final RevFlag PREREQ = rw.newFlag("PREREQ"); //$NON-NLS-1$ final RevFlag SEEN = rw.newFlag("SEEN"); //$NON-NLS-1$ @@ -281,8 +277,6 @@ class BundleFetchConnection extends BaseFetchConnection { throw new MissingBundlePrerequisiteException(transport.uri, missing); } - } finally { - rw.release(); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java index d0f005cde..81ad98191 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BundleWriter.java @@ -194,8 +194,7 @@ public class BundleWriter { PackConfig pc = packConfig; if (pc == null) pc = new PackConfig(db); - PackWriter packWriter = new PackWriter(pc, db.newObjectReader()); - try { + try (PackWriter packWriter = new PackWriter(pc, db.newObjectReader())) { final HashSet inc = new HashSet(); final HashSet exc = new HashSet(); inc.addAll(include.values()); @@ -233,8 +232,6 @@ public class BundleWriter { w.write('\n'); w.flush(); packWriter.writePack(monitor, monitor, os); - } finally { - packWriter.release(); } } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java index e386c26c1..0ff9fcea7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Connection.java @@ -127,4 +127,13 @@ public interface Connection { * remote produced no additional messages. */ public String getMessages(); + + /** + * User agent advertised by the remote server. + * + * @return agent (version of Git) running on the remote server. Null if the + * server does not advertise this version. + * @since 4.0 + */ + public String getPeerUserAgent(); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java index 52a9bab4b..9aae1c37a 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -136,6 +136,7 @@ class FetchProcess { conn = transport.openFetch(); try { result.setAdvertisedRefs(transport.getURI(), conn.getRefsMap()); + result.peerUserAgent = conn.getPeerUserAgent(); final Set matched = new HashSet(); for (final RefSpec spec : toFetch) { if (spec.getSource() == null) @@ -196,8 +197,7 @@ class FetchProcess { .newBatchUpdate() .setAllowNonFastForwards(true) .setRefLogMessage("fetch", true); //$NON-NLS-1$ - final RevWalk walk = new RevWalk(transport.local); - try { + try (final RevWalk walk = new RevWalk(transport.local)) { if (monitor instanceof BatchingProgressMonitor) { ((BatchingProgressMonitor) monitor).setDelayStart( 250, TimeUnit.MILLISECONDS); @@ -226,8 +226,6 @@ class FetchProcess { throw new TransportException(MessageFormat.format( JGitText.get().failureUpdatingTrackingRef, getFirstFailedRefName(batch), err.getMessage()), err); - } finally { - walk.release(); } if (!fetchHeadUpdates.isEmpty()) { @@ -338,15 +336,12 @@ class FetchProcess { private boolean askForIsComplete() throws TransportException { try { - final ObjectWalk ow = new ObjectWalk(transport.local); - try { + try (final ObjectWalk ow = new ObjectWalk(transport.local)) { for (final ObjectId want : askFor.keySet()) ow.markStart(ow.parseAny(want)); for (final Ref ref : localRefs().values()) ow.markUninteresting(ow.parseAny(ref.getObjectId())); ow.checkConnectivity(); - } finally { - ow.release(); } return true; } catch (MissingObjectException e) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java index 27052db67..8d9d2b718 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -186,6 +186,13 @@ public class GitProtocolConstants { */ public static final String CAPABILITY_PUSH_CERT = "push-cert"; //$NON-NLS-1$ + /** + * Implementation name and version of the client or server. + * + * @since 4.0 + */ + public static final String OPTION_AGENT = "agent"; //$NON-NLS-1$ + static enum MultiAck { OFF, CONTINUE, DETAILED; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java new file mode 100644 index 000000000..fe7aaf769 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/InternalHttpServerGlue.java @@ -0,0 +1,80 @@ +/* + * 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; + +/** + * Internal API to to assist {@code org.eclipse.jgit.http.server}. + *

+ * Do not call. + * + * @since 4.0 + */ +public class InternalHttpServerGlue { + /** + * Apply a default user agent for a request. + * + * @param up + * current UploadPack instance. + * @param agent + * user agent string from the HTTP headers. + */ + public static void setPeerUserAgent(UploadPack up, String agent) { + up.userAgent = agent; + } + + /** + * Apply a default user agent for a request. + * + * @param rp + * current ReceivePack instance. + * @param agent + * user agent string from the HTTP headers. + */ + public static void setPeerUserAgent(ReceivePack rp, String agent) { + rp.userAgent = agent; + } + + private InternalHttpServerGlue() { + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java index b4a48b016..ad51f3e70 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/OperationResult.java @@ -68,6 +68,8 @@ public abstract class OperationResult { StringBuilder messageBuffer; + String peerUserAgent; + /** * Get the URI this result came from. *

@@ -165,4 +167,15 @@ public abstract class OperationResult { messageBuffer.append('\n'); } } + + /** + * Get the user agent advertised by the peer server, if available. + * + * @return advertised user agent, e.g. {@code "JGit/4.0"}. Null if the peer + * did not advertise version information. + * @since 4.0 + */ + public String getPeerUserAgent() { + return peerUserAgent; + } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java index 5b54891cd..04abe2232 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PackParser.java @@ -529,7 +529,7 @@ public abstract class PackParser { } finally { try { if (readCurs != null) - readCurs.release(); + readCurs.close(); } finally { readCurs = null; } @@ -812,7 +812,7 @@ public abstract class PackParser { for (final DeltaChain base : missing) { if (base.head != null) - throw new MissingObjectException(base, "delta base"); + throw new MissingObjectException(base, "delta base"); //$NON-NLS-1$ } onEndThinPack(); 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 53fba5557..00f84f70e 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/PushProcess.java @@ -155,6 +155,7 @@ class PushProcess { try { res.setAdvertisedRefs(transport.getURI(), connection .getRefsMap()); + res.peerUserAgent = connection.getPeerUserAgent(); res.setRemoteUpdates(toPush); monitor.endTask(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java index 76547a628..f72a4b2b3 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/RefAdvertiser.java @@ -147,6 +147,21 @@ public abstract class RefAdvertiser { capablities.add(name); } + /** + * Add one protocol capability with a value ({@code "name=value"}). + * + * @param name + * name of the capability. + * @param value + * value. If null the capability will not be added. + * @since 4.0 + */ + public void advertiseCapability(String name, String value) { + if (value != null) { + capablities.add(name + '=' + value); + } + } + /** * Add a symbolic ref to capabilities. *

@@ -164,8 +179,7 @@ public abstract class RefAdvertiser { * @since 3.6 */ public void addSymref(String from, String to) { - String symref = String.format("%s=%s:%s", OPTION_SYMREF, from, to); //$NON-NLS-1$ - advertiseCapability(symref); + advertiseCapability(OPTION_SYMREF, from + ':' + to); } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java index 1de91a57e..60043fff7 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransferConfig.java @@ -67,6 +67,7 @@ public class TransferConfig { private final boolean checkReceivedObjects; private final boolean allowLeadingZeroFileMode; + private final boolean allowInvalidPersonIdent; private final boolean safeForWindows; private final boolean safeForMacOS; private final boolean allowTipSha1InWant; @@ -82,6 +83,8 @@ public class TransferConfig { rc.getBoolean("transfer", "fsckobjects", false)); //$NON-NLS-1$ //$NON-NLS-2$ allowLeadingZeroFileMode = checkReceivedObjects && rc.getBoolean("fsck", "allowLeadingZeroFileMode", false); //$NON-NLS-1$ //$NON-NLS-2$ + allowInvalidPersonIdent = checkReceivedObjects + && rc.getBoolean("fsck", "allowInvalidPersonIdent", false); //$NON-NLS-1$ //$NON-NLS-2$ safeForWindows = checkReceivedObjects && rc.getBoolean("fsck", "safeForWindows", //$NON-NLS-1$ //$NON-NLS-2$ SystemReader.getInstance().isWindows()); @@ -113,6 +116,7 @@ public class TransferConfig { return null; return new ObjectChecker() .setAllowLeadingZeroFileMode(allowLeadingZeroFileMode) + .setAllowInvalidPersonIdent(allowInvalidPersonIdent) .setSafeForWindows(safeForWindows) .setSafeForMacOS(safeForMacOS); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java index 82d1737b9..b23771e95 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/TransportHttp.java @@ -134,8 +134,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport, private static final String SVC_RECEIVE_PACK = "git-receive-pack"; //$NON-NLS-1$ - private static final String userAgent = computeUserAgent(); - static final TransportProtocol PROTO_HTTP = new TransportProtocol() { private final String[] schemeNames = { "http", "https" }; //$NON-NLS-1$ //$NON-NLS-2$ @@ -204,17 +202,6 @@ public class TransportHttp extends HttpTransport implements WalkTransport, } }; - private static String computeUserAgent() { - String version; - final Package pkg = TransportHttp.class.getPackage(); - if (pkg != null && pkg.getImplementationVersion() != null) { - version = pkg.getImplementationVersion(); - } else { - version = "unknown"; //$NON-NLS-1$ - } - return "JGit/" + version; //$NON-NLS-1$ - } - private static final Config.SectionParser HTTP_KEY = new SectionParser() { public HttpConfig parse(final Config cfg) { return new HttpConfig(cfg); @@ -309,16 +296,17 @@ public class TransportHttp extends HttpTransport implements WalkTransport, final HttpConnection c = connect(service); final InputStream in = openInputStream(c); try { + BaseConnection f; if (isSmartHttp(c, service)) { readSmartHeaders(in, service); - return new SmartHttpFetchConnection(in); - + f = new SmartHttpFetchConnection(in); } else { // Assume this server doesn't support smart HTTP fetch // and fall back on dumb object walking. - // - return newDumbConnection(in); + f = newDumbConnection(in); } + f.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER)); + return (FetchConnection) f; } finally { in.close(); } @@ -331,7 +319,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, } } - private FetchConnection newDumbConnection(InputStream in) + private WalkFetchConnection newDumbConnection(InputStream in) throws IOException, PackProtocolException { HttpObjectDB d = new HttpObjectDB(objectsUrl); BufferedReader br = toBufferedReader(in); @@ -400,9 +388,7 @@ public class TransportHttp extends HttpTransport implements WalkTransport, final InputStream in = openInputStream(c); try { if (isSmartHttp(c, service)) { - readSmartHeaders(in, service); - return new SmartHttpPushConnection(in); - + return smartPush(service, c, in); } else if (!useSmartHttp) { final String msg = JGitText.get().smartHTTPPushDisabled; throw new NotSupportedException(msg); @@ -423,6 +409,14 @@ public class TransportHttp extends HttpTransport implements WalkTransport, } } + private PushConnection smartPush(String service, HttpConnection c, + InputStream in) throws IOException, TransportException { + readSmartHeaders(in, service); + SmartHttpPushConnection p = new SmartHttpPushConnection(in); + p.setPeerUserAgent(c.getHeaderField(HttpSupport.HDR_SERVER)); + return p; + } + @Override public void close() { // No explicit connections are maintained. @@ -551,7 +545,9 @@ public class TransportHttp extends HttpTransport implements WalkTransport, conn.setUseCaches(false); conn.setRequestProperty(HDR_ACCEPT_ENCODING, ENCODING_GZIP); conn.setRequestProperty(HDR_PRAGMA, "no-cache"); //$NON-NLS-1$ - conn.setRequestProperty(HDR_USER_AGENT, userAgent); + if (UserAgent.get() != null) { + conn.setRequestProperty(HDR_USER_AGENT, UserAgent.get()); + } int timeOut = getTimeout(); if (timeOut != -1) { int effTimeOut = timeOut * 1000; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java index 1a653bd2b..3afdb6114 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.transport; import static org.eclipse.jgit.lib.RefDatabase.ALL; +import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_AGENT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_ALLOW_TIP_SHA1_IN_WANT; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_INCLUDE_TAG; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_MULTI_ACK; @@ -253,6 +254,7 @@ public class UploadPack { /** Capabilities requested by the client. */ private Set options; + String userAgent; /** Raw ObjectIds the client has asked for, before validating them. */ private final Set wantIds = new HashSet(); @@ -806,6 +808,7 @@ public class UploadPack { || policy == RequestPolicy.REACHABLE_COMMIT_TIP || policy == null) adv.advertiseCapability(OPTION_ALLOW_TIP_SHA1_IN_WANT); + adv.advertiseCapability(OPTION_AGENT, UserAgent.get()); adv.setDerefTags(true); Map refs = getAdvertisedOrDefaultRefs(); findSymrefs(adv, refs); @@ -884,6 +887,37 @@ public class UploadPack { } } + /** + * Returns the clone/fetch depth. Valid only after calling recvWants(). + * + * @return the depth requested by the client, or 0 if unbounded. + * @since 4.0 + */ + public int getDepth() { + if (options == null) + throw new RequestNotYetReadException(); + return depth; + } + + /** + * Get the user agent of the client. + *

+ * If the client is new enough to use {@code agent=} capability that value + * will be returned. Older HTTP clients may also supply their version using + * the HTTP {@code User-Agent} header. The capability overrides the HTTP + * header if both are available. + *

+ * When an HTTP request has been received this method returns the HTTP + * {@code User-Agent} header value until capabilities have been parsed. + * + * @return user agent supplied by the client. Available only if the client + * is new enough to advertise its user agent. + * @since 4.0 + */ + public String getPeerUserAgent() { + return UserAgent.getAgent(options, userAgent); + } + private boolean negotiate() throws IOException { okToGiveUp = Boolean.FALSE; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java new file mode 100644 index 000000000..eadb92dd5 --- /dev/null +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UserAgent.java @@ -0,0 +1,147 @@ +/* + * 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.GitProtocolConstants.OPTION_AGENT; + +import java.util.Set; + +import org.eclipse.jgit.util.StringUtils; + +/** + * User agent to be reported by this JGit client and server on the network. + *

+ * On HTTP transports this user agent string is always supplied by the JGit + * client in the {@code User-Agent} HTTP header. + *

+ * On native transports this user agent string is always sent when JGit is a + * server. When JGit is a client the user agent string will be supplied to the + * remote server only if the remote server advertises its own agent identity. + * + * @since 4.0 + */ +public class UserAgent { + private static volatile String userAgent = computeUserAgent(); + + private static String computeUserAgent() { + return clean("JGit/" + computeVersion()); //$NON-NLS-1$ + } + + private static String computeVersion() { + Package pkg = UserAgent.class.getPackage(); + if (pkg != null) { + String ver = pkg.getImplementationVersion(); + if (!StringUtils.isEmptyOrNull(ver)) { + return ver; + } + } + return "unknown"; //$NON-NLS-1$ + } + + private static String clean(String s) { + s = s.trim(); + StringBuilder b = new StringBuilder(s.length()); + for (int i = 0; i < s.length(); i++) { + char c = s.charAt(i); + if (c <= 32 || c >= 127) { + if (b.length() > 0 && b.charAt(b.length() - 1) == '.') + continue; + c = '.'; + } + b.append(c); + } + return b.length() > 0 ? b.toString() : null; + } + + /** + * Get the user agent string advertised by JGit. + * + * @return a string similar to {@code "JGit/4.0"}; null if the agent has + * been cleared and should not be shared with a peer. + */ + public static String get() { + return userAgent; + } + + /** + * Change the user agent string advertised by JGit. + *

+ * The new string should start with {@code "JGit/"} (for example + * {@code "JGit/4.0"}) to advertise the implementation as JGit based. + *

+ * Spaces and other whitespace should be avoided as these will be + * automatically converted to {@code "."}. + *

+ * User agent strings are restricted to printable ASCII. + * + * @param agent + * new user agent string for this running JGit library. Setting + * to null or empty string will avoid sending any identification + * to the peer. + */ + public static void set(String agent) { + userAgent = StringUtils.isEmptyOrNull(agent) ? null : clean(agent); + } + + static String getAgent(Set options, String transportAgent) { + if (options == null || options.isEmpty()) { + return transportAgent; + } + for (String o : options) { + if (o.startsWith(OPTION_AGENT) + && o.length() > OPTION_AGENT.length() + && o.charAt(OPTION_AGENT.length()) == '=') { + return o.substring(OPTION_AGENT.length() + 1); + } + } + return transportAgent; + } + + static boolean hasAgent(Set options) { + return getAgent(options, null) != null; + } + + private UserAgent() { + } +} diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java index 35850dcaa..35fc99e54 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/ChangeIdUtil.java @@ -115,8 +115,10 @@ public class ChangeIdUtil { b.append(committer.toExternalString()); b.append("\n\n"); //$NON-NLS-1$ b.append(cleanMessage); - return new ObjectInserter.Formatter().idFor(Constants.OBJ_COMMIT, // - b.toString().getBytes(Constants.CHARACTER_ENCODING)); + try (ObjectInserter f = new ObjectInserter.Formatter()) { + return f.idFor(Constants.OBJ_COMMIT, // + b.toString().getBytes(Constants.CHARACTER_ENCODING)); + } } private static final Pattern issuePattern = Pattern 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 706b54e16..d11b03e67 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/FS.java @@ -481,7 +481,7 @@ public abstract class FS { } } } catch (IOException e) { - LOG.error("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ + LOG.debug("Caught exception in FS.readPipe()", e); //$NON-NLS-1$ } if (debug) { LOG.debug("readpipe returns null"); //$NON-NLS-1$ diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java index 37c9f7b8a..8b4ad0aa2 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/HttpSupport.java @@ -74,6 +74,12 @@ public class HttpSupport { /** The {@code User-Agent} header. */ public static final String HDR_USER_AGENT = "User-Agent"; //$NON-NLS-1$ + /** + * The {@code Server} header. + * @since 4.0 + */ + public static final String HDR_SERVER = "Server"; //$NON-NLS-1$ + /** The {@code Date} header. */ public static final String HDR_DATE = "Date"; //$NON-NLS-1$ 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 006c3c0a0..3719bf794 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/util/TemporaryBuffer.java @@ -44,6 +44,7 @@ package org.eclipse.jgit.util; +import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; @@ -435,7 +436,7 @@ public abstract class TemporaryBuffer extends OutputStream { protected OutputStream overflow() throws IOException { onDiskFile = File.createTempFile("jgit_", ".buf", directory); //$NON-NLS-1$ //$NON-NLS-2$ - return new FileOutputStream(onDiskFile); + return new BufferedOutputStream(new FileOutputStream(onDiskFile)); } public long length() { diff --git a/pom.xml b/pom.xml index 4b8ac43eb..1e00f2457 100644 --- a/pom.xml +++ b/pom.xml @@ -210,6 +210,10 @@ repo.eclipse.org.cbi-releases https://repo.eclipse.org/content/repositories/cbi-releases/ + + repo.eclipse.org.cbi-snapshots + https://repo.eclipse.org/content/repositories/cbi-snapshots/ + @@ -346,7 +350,7 @@ org.eclipse.cbi.maven.plugins eclipse-jarsigner-plugin - 1.1.1 + 1.1.2-SNAPSHOT org.eclipse.tycho.extras