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 d3437b7eb..23a398f9d 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 @@ public void flush() throws IOException { log(up.getRepository(), e.getCause()); consumeRequestBody(req); out.close(); + } finally { + up.close(); } }; 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 de25870bd..c928d2ad2 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.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; @@ -114,6 +115,49 @@ public void testCloneRepository() throws IOException, assertTagOption(git2.getRepository(), TagOpt.AUTO_FOLLOW); } + @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 clonedRefs = clonedRepo.getRefDatabase().getRefs(); + Stream remoteRefs = clonedRefs.stream() + .filter(CloneCommandTest::isRemote); + Stream 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 { 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 6479d157e..b608afa5c 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 @@ -76,6 +76,26 @@ public void testFetch() throws Exception { db.resolve(tagRef.getObjectId().getName())); } + @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 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(); diff --git a/org.eclipse.jgit/META-INF/MANIFEST.MF b/org.eclipse.jgit/META-INF/MANIFEST.MF index eb8eb8bd6..808d5d37b 100644 --- a/org.eclipse.jgit/META-INF/MANIFEST.MF +++ b/org.eclipse.jgit/META-INF/MANIFEST.MF @@ -226,6 +226,7 @@ Export-Package: org.eclipse.jgit.annotations;version="6.1.1", 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 7d7b3ee0a..2b4deb32f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -204,8 +204,13 @@ else if (tagopt == TagOpt.FETCH_TAGS) 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 b5062fdd1..1617c5063 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.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 @@ /** * 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 @@ private boolean useProtocolV2() { && 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 void upload(InputStream input, OutputStream output, throw new UploadPackInternalServerErrorException(err); } throw err; + } finally { + close(); } } @@ -789,6 +803,10 @@ public void upload(InputStream input, OutputStream output, *

* 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 void uploadWithExceptionPropagation(InputStream input, } finally { msgOut = NullOutputStream.INSTANCE; walk.close(); - if (timer != null) { - try { - timer.terminate(); - } finally { - timer = null; - } - } } } @@ -1998,12 +2009,16 @@ private static void checkNotAdvertisedWants(UploadPack up, throws IOException { ObjectReader reader = up.getRevWalk().getObjectReader(); + Set directlyVisibleObjects = refIdSet(visibleRefs); + List 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 wantsAsObjs = objectIdsToRevObjects(walk, - notAdvertisedWants); + nonTipWants); List wantsAsCommits = wantsAsObjs.stream() .filter(obj -> obj instanceof RevCommit) .map(obj -> (RevCommit) obj) @@ -2063,6 +2078,10 @@ private static void checkNotAdvertisedWants(UploadPack up, } } + private static Predicate not(Predicate t) { + return t.negate(); + } + static Stream importantRefsFirst( Collection visibleRefs) { Predicate startsWithRefsHeads = ref -> ref.getName()