diff options
10 files changed, 239 insertions, 27 deletions
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 d3437b7eb9..23a398f9db 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 @@ -208,6 +208,8 @@ class UploadPackServlet extends HttpServlet { log(up.getRepository(), e.getCause()); consumeRequestBody(req); out.close(); + } finally { + up.close(); } }; diff --git a/org.eclipse.jgit.packaging/pom.xml b/org.eclipse.jgit.packaging/pom.xml index 1c581eec17..56946faeca 100644 --- a/org.eclipse.jgit.packaging/pom.xml +++ b/org.eclipse.jgit.packaging/pom.xml @@ -259,6 +259,11 @@ <ws>cocoa</ws> <arch>x86_64</arch> </environment> + <environment> + <os>macosx</os> + <ws>cocoa</ws> + <arch>aarch64</arch> + </environment> </environments> </configuration> </plugin> diff --git a/org.eclipse.jgit.test/META-INF/MANIFEST.MF b/org.eclipse.jgit.test/META-INF/MANIFEST.MF index 0ba71f982d..bb155eace0 100644 --- a/org.eclipse.jgit.test/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit.test/META-INF/MANIFEST.MF @@ -86,4 +86,4 @@ Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", org.mockito.stubbing;version="[2.23.0,3.0.0)", org.objenesis;version="[2.6.0,3.0.0)", org.slf4j;version="[1.7.0,2.0.0)", - org.tukaani.xz;version="[1.6.0,2.0)" + org.tukaani.xz diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java index de25870bd0..c928d2ad22 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java @@ -22,6 +22,7 @@ import java.net.URISyntaxException; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.stream.Stream; import org.eclipse.jgit.api.ListBranchCommand.ListMode; import org.eclipse.jgit.api.errors.GitAPIException; @@ -115,6 +116,49 @@ public class CloneCommandTest extends RepositoryTestCase { } @Test + public void testCloneRepository_refLogForLocalRefs() + throws IOException, JGitInternalException, GitAPIException { + File directory = createTempDirectory("testCloneRepository"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + Git git2 = command.call(); + Repository clonedRepo = git2.getRepository(); + addRepoToClose(clonedRepo); + + List<Ref> clonedRefs = clonedRepo.getRefDatabase().getRefs(); + Stream<Ref> remoteRefs = clonedRefs.stream() + .filter(CloneCommandTest::isRemote); + Stream<Ref> localHeadsRefs = clonedRefs.stream() + .filter(CloneCommandTest::isLocalHead); + + remoteRefs.forEach(ref -> assertFalse( + "Ref " + ref.getName() + + " is remote and should not have a reflog", + hasRefLog(clonedRepo, ref))); + localHeadsRefs.forEach(ref -> assertTrue( + "Ref " + ref.getName() + + " is local head and should have a reflog", + hasRefLog(clonedRepo, ref))); + } + + private static boolean isRemote(Ref ref) { + return ref.getName().startsWith(Constants.R_REMOTES); + } + + private static boolean isLocalHead(Ref ref) { + return !isRemote(ref) && ref.getName().startsWith(Constants.R_HEADS); + } + + private static boolean hasRefLog(Repository repo, Ref ref) { + try { + return repo.getReflogReader(ref.getName()).getLastEntry() != null; + } catch (IOException ioe) { + throw new IllegalStateException(ioe); + } + } + + @Test public void testCloneRepositoryExplicitGitDir() throws IOException, JGitInternalException, GitAPIException { File directory = createTempDirectory("testCloneRepository"); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java index 0884d72235..3ec454cfc3 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/api/FetchCommandTest.java @@ -77,6 +77,26 @@ public class FetchCommandTest extends RepositoryTestCase { } @Test + public void testFetchHasRefLogForRemoteRef() throws Exception { + // create an initial commit SHA1 for the default branch + ObjectId defaultBranchSha1 = remoteGit.commit() + .setMessage("initial commit").call().getId(); + + git.fetch().setRemote("test") + .setRefSpecs("refs/heads/*:refs/remotes/origin/*").call(); + + List<Ref> allFetchedRefs = git.getRepository().getRefDatabase() + .getRefs(); + assertEquals(allFetchedRefs.size(), 1); + Ref remoteRef = allFetchedRefs.get(0); + + assertTrue(remoteRef.getName().startsWith(Constants.R_REMOTES)); + assertEquals(defaultBranchSha1, remoteRef.getObjectId()); + assertNotNull(git.getRepository().getReflogReader(remoteRef.getName()) + .getLastEntry()); + } + + @Test public void testForcedFetch() throws Exception { remoteGit.commit().setMessage("commit").call(); remoteGit.commit().setMessage("commit2").call(); diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java index ae3f05111f..083e6cd005 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/ignore/CGitIgnoreTest.java @@ -13,6 +13,7 @@ import static java.nio.charset.StandardCharsets.UTF_8; import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; import java.io.BufferedInputStream; import java.io.BufferedReader; @@ -20,6 +21,7 @@ import java.io.ByteArrayInputStream; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.nio.file.Files; import java.util.LinkedHashSet; import java.util.Set; @@ -354,4 +356,84 @@ public class CGitIgnoreTest extends RepositoryTestCase { writeTrashFile("src/.gitignore", "*\n!*.java\n!*/"); assertSameAsCGit(); } + + @Test + public void testMultipleEntriesIgnored() throws Exception { + createFiles("dir/a"); + writeTrashFile(".gitignore", "!dir/a\ndir/a"); + assertSameAsCGit(); + } + + @Test + public void testMultipleEntriesNotIgnored() throws Exception { + createFiles("dir/a"); + writeTrashFile(".gitignore", "dir/a\n!dir/a"); + assertSameAsCGit("dir/a"); + } + + @Test + public void testInfoExcludes() throws Exception { + createFiles("dir/a", "dir/b"); + File gitDir = db.getDirectory(); + File info = new File(gitDir, "info"); + assertTrue(info.mkdirs()); + File infoExclude = new File(info, "exclude"); + Files.writeString(infoExclude.toPath(), "dir/a"); + assertSameAsCGit("dir/b"); + } + + @Test + public void testInfoExcludesPrecedence() throws Exception { + createFiles("dir/a", "dir/b"); + writeTrashFile(".gitignore", "!dir/a"); + File gitDir = db.getDirectory(); + File info = new File(gitDir, "info"); + assertTrue(info.mkdirs()); + File infoExclude = new File(info, "exclude"); + Files.writeString(infoExclude.toPath(), "dir/a"); + assertSameAsCGit("dir/a", "dir/b"); + } + + @Test + public void testCoreExcludes() throws Exception { + createFiles("dir/a", "dir/b"); + writeTrashFile(".fake_git_ignore", "dir/a"); + assertSameAsCGit("dir/b"); + } + + @Test + public void testInfoCoreExcludes() throws Exception { + createFiles("dir/a", "dir/b"); + File gitDir = db.getDirectory(); + File info = new File(gitDir, "info"); + assertTrue(info.mkdirs()); + File infoExclude = new File(info, "exclude"); + Files.writeString(infoExclude.toPath(), "!a"); + writeTrashFile(".fake_git_ignore", "dir/a"); + assertSameAsCGit("dir/b"); + } + + @Test + public void testInfoCoreExcludesPrecedence() throws Exception { + createFiles("dir/a", "dir/b"); + File gitDir = db.getDirectory(); + File info = new File(gitDir, "info"); + assertTrue(info.mkdirs()); + File infoExclude = new File(info, "exclude"); + Files.writeString(infoExclude.toPath(), "!dir/a"); + writeTrashFile(".fake_git_ignore", "dir/a"); + assertSameAsCGit("dir/a", "dir/b"); + } + + @Test + public void testInfoCoreExcludesPrecedence2() throws Exception { + createFiles("dir/a", "dir/b"); + File gitDir = db.getDirectory(); + File info = new File(gitDir, "info"); + assertTrue(info.mkdirs()); + File infoExclude = new File(info, "exclude"); + Files.writeString(infoExclude.toPath(), "dir/a"); + writeTrashFile(".fake_git_ignore", "!dir/a"); + assertSameAsCGit("dir/b"); + } } diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index b919140989..d1e612593c 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -230,6 +230,7 @@ Export-Package: org.eclipse.jgit.annotations;version="6.3.0", Bundle-RequiredExecutionEnvironment: JavaSE-11 Import-Package: com.googlecode.javaewah;version="[1.1.6,2.0.0)", javax.crypto, + javax.management, javax.net.ssl, org.slf4j;version="[1.7.0,2.0.0)", org.xml.sax, 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 87e5476036..7bface49d9 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -214,8 +214,13 @@ class FetchProcess { BatchRefUpdate batch = transport.local.getRefDatabase() .newBatchUpdate() - .setAllowNonFastForwards(true) - .setRefLogMessage("fetch", true); //$NON-NLS-1$ + .setAllowNonFastForwards(true); + + // Generate reflog only when fetching updates and not at the first clone + if (initialBranch == null) { + batch.setRefLogMessage("fetch", true); //$NON-NLS-1$ + } + try (RevWalk walk = new RevWalk(transport.local)) { walk.setRetainBody(false); if (monitor instanceof BatchingProgressMonitor) { 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 8b47d846a8..dcd806a3da 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -39,6 +39,7 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2_REQUEST; import static org.eclipse.jgit.util.RefMap.toRefMap; import java.io.ByteArrayOutputStream; +import java.io.Closeable; import java.io.EOFException; import java.io.IOException; import java.io.InputStream; @@ -105,7 +106,7 @@ import org.eclipse.jgit.util.io.TimeoutOutputStream; /** * Implements the server side of a fetch connection, transmitting objects. */ -public class UploadPack { +public class UploadPack implements Closeable { /** Policy the server uses to validate client requests */ public enum RequestPolicy { /** Client may only ask for objects the server advertised a reference for. */ @@ -733,6 +734,17 @@ public class UploadPack { && clientRequestedV2; } + @Override + public void close() { + if (timer != null) { + try { + timer.terminate(); + } finally { + timer = null; + } + } + } + /** * Execute the upload task on the socket. * @@ -780,6 +792,8 @@ public class UploadPack { throw new UploadPackInternalServerErrorException(err); } throw err; + } finally { + close(); } } @@ -789,6 +803,10 @@ public class UploadPack { * <p> * If the client passed extra parameters (e.g., "version=2") through a side * channel, the caller must call setExtraParameters first to supply them. + * Callers of this method should call {@link #close()} to terminate the + * internal interrupt timer thread. If the caller fails to terminate the + * thread, it will (eventually) terminate itself when the InterruptTimer + * instance is garbage collected. * * @param input * raw input to read client commands from. Caller must ensure the @@ -845,13 +863,6 @@ public class UploadPack { } finally { msgOut = NullOutputStream.INSTANCE; walk.close(); - if (timer != null) { - try { - timer.terminate(); - } finally { - timer = null; - } - } } } @@ -1998,12 +2009,16 @@ public class UploadPack { throws IOException { ObjectReader reader = up.getRevWalk().getObjectReader(); + Set<ObjectId> directlyVisibleObjects = refIdSet(visibleRefs); + List<ObjectId> nonTipWants = notAdvertisedWants.stream() + .filter(not(directlyVisibleObjects::contains)) + .collect(Collectors.toList()); try (RevWalk walk = new RevWalk(reader)) { walk.setRetainBody(false); // Missing "wants" throw exception here List<RevObject> wantsAsObjs = objectIdsToRevObjects(walk, - notAdvertisedWants); + nonTipWants); List<RevCommit> wantsAsCommits = wantsAsObjs.stream() .filter(obj -> obj instanceof RevCommit) .map(obj -> (RevCommit) obj) @@ -2069,6 +2084,10 @@ public class UploadPack { } } + private static <T> Predicate<T> not(Predicate<T> t) { + return t.negate(); + } + static Stream<Ref> importantRefsFirst( Collection<Ref> visibleRefs) { Predicate<Ref> startsWithRefsHeads = ref -> ref.getName() diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java index 427eac5b53..b108b0a959 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/treewalk/WorkingTreeIterator.java @@ -1274,11 +1274,15 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } IgnoreNode load() throws IOException { - IgnoreNode r = new IgnoreNode(); + return load(null); + } + + IgnoreNode load(IgnoreNode parent) throws IOException { + IgnoreNodeWithParent r = new IgnoreNodeWithParent(parent); try (InputStream in = entry.openInputStream()) { r.parse(name, in); } - return r.getRules().isEmpty() ? null : r; + return r.getRules().isEmpty() && parent == null ? null : r; } } @@ -1292,29 +1296,41 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } @Override - IgnoreNode load() throws IOException { - IgnoreNode r; - if (entry != null) { - r = super.load(); - if (r == null) - r = new IgnoreNode(); - } else { - r = new IgnoreNode(); - } - + IgnoreNode load(IgnoreNode parent) throws IOException { + IgnoreNode coreExclude = new IgnoreNodeWithParent(parent); FS fs = repository.getFS(); Path path = repository.getConfig().getPath( ConfigConstants.CONFIG_CORE_SECTION, null, ConfigConstants.CONFIG_KEY_EXCLUDESFILE, fs, null, null); if (path != null) { - loadRulesFromFile(r, path.toFile()); + loadRulesFromFile(coreExclude, path.toFile()); + } + if (coreExclude.getRules().isEmpty()) { + coreExclude = parent; } + IgnoreNode infoExclude = new IgnoreNodeWithParent( + coreExclude); File exclude = fs.resolve(repository.getDirectory(), Constants.INFO_EXCLUDE); - loadRulesFromFile(r, exclude); + loadRulesFromFile(infoExclude, exclude); + if (infoExclude.getRules().isEmpty()) { + infoExclude = null; + } - return r.getRules().isEmpty() ? null : r; + IgnoreNode parentNode = infoExclude != null ? infoExclude + : coreExclude; + + IgnoreNode r; + if (entry != null) { + r = super.load(parentNode); + if (r == null) { + return null; + } + } else { + return parentNode; + } + return r.getRules().isEmpty() ? parentNode : r; } private static void loadRulesFromFile(IgnoreNode r, File exclude) @@ -1327,6 +1343,24 @@ public abstract class WorkingTreeIterator extends AbstractTreeIterator { } } + private static class IgnoreNodeWithParent extends IgnoreNode { + + private final IgnoreNode parent; + + IgnoreNodeWithParent(IgnoreNode parent) { + this.parent = parent; + } + + @Override + public Boolean checkIgnored(String path, boolean isDirectory) { + Boolean result = super.checkIgnored(path, isDirectory); + if (result == null && parent != null) { + return parent.checkIgnored(path, isDirectory); + } + return result; + } + } + /** Magic type indicating we know rules exist, but they aren't loaded. */ private static class PerDirectoryAttributesNode extends AttributesNode { final Entry entry; |