diff options
29 files changed, 1333 insertions, 174 deletions
diff --git a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java index 3438c52c8d..6b067273a9 100644 --- a/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java +++ b/org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java @@ -24,13 +24,16 @@ import java.io.OutputStream; import java.net.URI; import java.net.URL; import java.text.MessageFormat; +import java.time.Instant; import java.util.List; +import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.eclipse.jetty.servlet.DefaultServlet; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.eclipse.jgit.api.Git; import org.eclipse.jgit.errors.NoRemoteRepositoryException; import org.eclipse.jgit.errors.RepositoryNotFoundException; import org.eclipse.jgit.errors.TransportException; @@ -408,4 +411,110 @@ public class HttpClientTests extends AllFactoriesHttpTestCase { assertEquals(200, c.getResponseCode()); } + + @Test + public void testCloneWithDepth() throws Exception { + remoteRepository.getRepository().getConfig().setInt( + "protocol", null, "version", 0); + File directory = createTempDirectory("testCloneWithDepth"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .setDepth(1) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } + + @Test + public void testCloneWithDeepenSince() throws Exception { + remoteRepository.getRepository().getConfig().setInt( + "protocol", null, "version", 0); + RevCommit commit = remoteRepository.commit() + .parent(remoteRepository.git().log().call().iterator().next()) + .message("Test") + .add("test.txt", "Hello world") + .create(); + remoteRepository.update(master, commit); + + File directory = createTempDirectory("testCloneWithDeepenSince"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .setShallowSince(Instant.ofEpochSecond(commit.getCommitTime())) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } + + @Test + public void testCloneWithDeepenNot() throws Exception { + remoteRepository.getRepository().getConfig().setInt( + "protocol", null, "version", 0); + RevCommit commit = remoteRepository.git().log().call().iterator().next(); + remoteRepository.update(master, remoteRepository.commit() + .parent(commit) + .message("Test") + .add("test.txt", "Hello world") + .create()); + + File directory = createTempDirectory("testCloneWithDeepenNot"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .addShallowExclude(commit.getId()) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } + + @Test + public void testV2CloneWithDepth() throws Exception { + File directory = createTempDirectory("testV2CloneWithDepth"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .setDepth(1) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } + + @Test + public void testV2CloneWithDeepenSince() throws Exception { + RevCommit commit = remoteRepository.commit() + .parent(remoteRepository.git().log().call().iterator().next()) + .message("Test") + .add("test.txt", "Hello world") + .create(); + remoteRepository.update(master, commit); + + File directory = createTempDirectory("testV2CloneWithDeepenSince"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .setShallowSince(Instant.ofEpochSecond(commit.getCommitTime())) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } + + @Test + public void testV2CloneWithDeepenNot() throws Exception { + RevCommit commit = remoteRepository.git().log().call().iterator().next(); + remoteRepository.update(master, remoteRepository.commit() + .parent(commit) + .message("Test") + .add("test.txt", "Hello world") + .create()); + + File directory = createTempDirectory("testV2CloneWithDeepenNot"); + Git git = Git.cloneRepository() + .setDirectory(directory) + .addShallowExclude(commit.getId()) + .setURI(smartAuthNoneURI.toString()) + .call(); + + assertEquals(Set.of(git.getRepository().resolve(Constants.HEAD)), git.getRepository().getObjectDatabase().getShallowCommits()); + } } diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java index 22e19979e6..873d430675 100644 --- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java +++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/AccessEvent.java @@ -30,16 +30,18 @@ public class AccessEvent { private final Map<String, String[]> parameters; - private final int status; + private int status; - private final Map<String, String> responseHeaders; + private Map<String, String> responseHeaders; - AccessEvent(Request req, Response rsp) { + AccessEvent(Request req) { method = req.getMethod(); uri = req.getRequestURI(); requestHeaders = cloneHeaders(req); parameters = clone(req.getParameterMap()); + } + void setResponse(Response rsp) { status = rsp.getStatus(); responseHeaders = cloneHeaders(rsp); } @@ -141,7 +143,7 @@ public class AccessEvent { * @return first value of the response header; null if not sent. */ public String getResponseHeader(String name) { - return responseHeaders.get(name); + return responseHeaders != null ? responseHeaders.get(name) : null; } /** {@inheritDoc} */ diff --git a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java index a86edd2f39..04cb2428a2 100644 --- a/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java +++ b/org.eclipse.jgit.junit.http/src/org/eclipse/jgit/junit/http/TestRequestLog.java @@ -87,19 +87,23 @@ class TestRequestLog extends HandlerWrapper { } } + AccessEvent event = null; + if (DispatcherType.REQUEST + .equals(baseRequest.getDispatcherType())) { + event = new AccessEvent((Request) request); + synchronized (events) { + events.add(event); + } + } + super.handle(target, baseRequest, request, response); - if (DispatcherType.REQUEST.equals(baseRequest.getDispatcherType())) - log((Request) request, (Response) response); + if (event != null) { + event.setResponse((Response) response); + } } finally { active.release(); } } - - private void log(Request request, Response response) { - synchronized (events) { - events.add(new AccessEvent(request, response)); - } - } } 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 c928d2ad22..6053c8c568 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2013 Chris Aniszczyk <caniszczyk@gmail.com> and others + * Copyright (C) 2011, 2022 Chris Aniszczyk <caniszczyk@gmail.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -19,10 +19,14 @@ import static org.junit.Assert.fail; import java.io.File; import java.io.IOException; import java.net.URISyntaxException; +import java.time.Instant; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.stream.Collectors; import java.util.stream.Stream; +import java.util.stream.StreamSupport; import org.eclipse.jgit.api.ListBranchCommand.ListMode; import org.eclipse.jgit.api.errors.GitAPIException; @@ -40,6 +44,7 @@ import org.eclipse.jgit.lib.Repository; import org.eclipse.jgit.lib.StoredConfig; import org.eclipse.jgit.revwalk.RevBlob; import org.eclipse.jgit.revwalk.RevCommit; +import org.eclipse.jgit.revwalk.RevObject; import org.eclipse.jgit.submodule.SubmoduleStatus; import org.eclipse.jgit.submodule.SubmoduleStatusType; import org.eclipse.jgit.submodule.SubmoduleWalk; @@ -895,6 +900,234 @@ public class CloneCommandTest extends RepositoryTestCase { assertEquals("refs/heads/test-copy", git2.getRepository().getFullBranch()); } + @Test + public void testCloneRepositoryWithDepth() throws IOException, JGitInternalException, GitAPIException { + File directory = createTempDirectory("testCloneRepositoryWithDepth"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(1); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + List<RevCommit> log = StreamSupport.stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(1, log.size()); + RevCommit commit = log.get(0); + assertEquals(Set.of(commit.getId()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals("Second commit", commit.getFullMessage()); + assertEquals(0, commit.getParentCount()); + } + + @Test + public void testCloneRepositoryWithDepthAndAllBranches() throws IOException, JGitInternalException, GitAPIException { + File directory = createTempDirectory("testCloneRepositoryWithDepthAndAllBranches"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(1); + command.setCloneAllBranches(true); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + List<RevCommit> log = StreamSupport.stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(2, log.size()); + assertEquals(log.stream().map(RevCommit::getId).collect(Collectors.toSet()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals(List.of("Second commit", "Initial commit"), + log.stream().map(RevCommit::getFullMessage).collect(Collectors.toList())); + for (RevCommit commit : log) { + assertEquals(0, commit.getParentCount()); + } + } + + @Test + public void testCloneRepositoryWithDepth2() throws Exception { + RevCommit parent = tr.git().log().call().iterator().next(); + RevCommit commit = tr.commit() + .parent(parent) + .message("Third commit") + .add("test.txt", "Hello world") + .create(); + tr.update("refs/heads/test", commit); + + File directory = createTempDirectory("testCloneRepositoryWithDepth2"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(2); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(2, log.size()); + assertEquals(Set.of(parent.getId()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals(List.of("Third commit", "Second commit"), log.stream() + .map(RevCommit::getFullMessage).collect(Collectors.toList())); + assertEquals(List.of(Integer.valueOf(1), Integer.valueOf(0)), + log.stream().map(RevCommit::getParentCount) + .collect(Collectors.toList())); + } + + @Test + public void testCloneRepositoryWithDepthAndFetch() throws Exception { + File directory = createTempDirectory("testCloneRepositoryWithDepthAndFetch"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(1); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + RevCommit parent = tr.git().log().call().iterator().next(); + RevCommit commit = tr.commit() + .parent(parent) + .message("Third commit") + .add("test.txt", "Hello world") + .create(); + tr.update("refs/heads/test", commit); + + git2.fetch().call(); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(2, log.size()); + assertEquals(Set.of(parent.getId()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals(List.of("Third commit", "Second commit"), log.stream() + .map(RevCommit::getFullMessage).collect(Collectors.toList())); + assertEquals(List.of(Integer.valueOf(1), Integer.valueOf(0)), + log.stream().map(RevCommit::getParentCount) + .collect(Collectors.toList())); + } + + @Test + public void testCloneRepositoryWithDepthAndFetchWithDepth() throws Exception { + File directory = createTempDirectory("testCloneRepositoryWithDepthAndFetchWithDepth"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(1); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + RevCommit parent = tr.git().log().call().iterator().next(); + RevCommit commit = tr.commit() + .parent(parent) + .message("Third commit") + .add("test.txt", "Hello world") + .create(); + tr.update("refs/heads/test", commit); + + git2.fetch().setDepth(1).call(); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(2, log.size()); + assertEquals( + log.stream().map(RevObject::getId).collect(Collectors.toSet()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals(List.of("Third commit", "Second commit"), log.stream() + .map(RevCommit::getFullMessage).collect(Collectors.toList())); + assertEquals(List.of(Integer.valueOf(0), Integer.valueOf(0)), + log.stream().map(RevCommit::getParentCount) + .collect(Collectors.toList())); + } + + @Test + public void testCloneRepositoryWithDepthAndFetchUnshallow() throws Exception { + File directory = createTempDirectory("testCloneRepositoryWithDepthAndFetchUnshallow"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setDepth(1); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + git2.fetch().setUnshallow(true).call(); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(2, log.size()); + assertEquals(Set.of(), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals(List.of("Second commit", "Initial commit"), log.stream() + .map(RevCommit::getFullMessage).collect(Collectors.toList())); + assertEquals(List.of(Integer.valueOf(1), Integer.valueOf(0)), + log.stream().map(RevCommit::getParentCount) + .collect(Collectors.toList())); + } + + @Test + public void testCloneRepositoryWithShallowSince() throws Exception { + RevCommit commit = tr.commit() + .parent(tr.git().log().call().iterator().next()) + .message("Third commit").add("test.txt", "Hello world") + .create(); + tr.update("refs/heads/test", commit); + + File directory = createTempDirectory("testCloneRepositoryWithShallowSince"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.setShallowSince(Instant.ofEpochSecond(commit.getCommitTime())); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(1, log.size()); + assertEquals(Set.of(commit.getId()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals("Third commit", log.get(0).getFullMessage()); + assertEquals(0, log.get(0).getParentCount()); + } + + @Test + public void testCloneRepositoryWithShallowExclude() throws Exception { + RevCommit parent = tr.git().log().call().iterator().next(); + tr.update("refs/heads/test", + tr.commit() + .parent(parent) + .message("Third commit") + .add("test.txt", "Hello world") + .create()); + + File directory = createTempDirectory("testCloneRepositoryWithShallowExclude"); + CloneCommand command = Git.cloneRepository(); + command.setDirectory(directory); + command.setURI(fileUri()); + command.addShallowExclude(parent.getId()); + command.setBranchesToClone(Set.of("refs/heads/test")); + Git git2 = command.call(); + addRepoToClose(git2.getRepository()); + + List<RevCommit> log = StreamSupport + .stream(git2.log().all().call().spliterator(), false) + .collect(Collectors.toList()); + assertEquals(1, log.size()); + RevCommit commit = log.get(0); + assertEquals(Set.of(commit.getId()), + git2.getRepository().getObjectDatabase().getShallowCommits()); + assertEquals("Third commit", commit.getFullMessage()); + assertEquals(0, commit.getParentCount()); + } + private void assertTagOption(Repository repo, TagOpt expectedTagOption) throws URISyntaxException { RemoteConfig remoteConfig = new RemoteConfig( diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java index c0db83a820..b2a4af30ab 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -15,6 +15,7 @@ import static org.eclipse.jgit.lib.Constants.OBJ_TAG; import static org.eclipse.jgit.lib.Constants.OBJ_TREE; import static org.eclipse.jgit.transport.ObjectIdMatcher.hasOnlyObjectIds; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; @@ -133,6 +134,42 @@ public class ProtocolV0ParserTest { } @Test + public void testRecvWantsDeepenSince() + throws PackProtocolException, IOException { + PacketLineIn pckIn = formatAsPacketLine( + "want 4624442d68ee402a94364191085b77137618633e\n", + "want f900c8326a43303685c46b279b9f70411bff1a4b\n", + "deepen-since 1652773020\n", + PacketLineIn.end()); + ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig()); + FetchV0Request request = parser.recvWants(pckIn); + assertTrue(request.getClientCapabilities().isEmpty()); + assertEquals(1652773020, request.getDeepenSince()); + assertThat(request.getWantIds(), + hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e", + "f900c8326a43303685c46b279b9f70411bff1a4b")); + } + + @Test + public void testRecvWantsDeepenNots() + throws PackProtocolException, IOException { + PacketLineIn pckIn = formatAsPacketLine( + "want 4624442d68ee402a94364191085b77137618633e\n", + "want f900c8326a43303685c46b279b9f70411bff1a4b\n", + "deepen-not 856d5138d7269a483efe276d4a6b5c25b4fbb1a4\n", + "deepen-not heads/refs/test\n", + PacketLineIn.end()); + ProtocolV0Parser parser = new ProtocolV0Parser(defaultConfig()); + FetchV0Request request = parser.recvWants(pckIn); + assertTrue(request.getClientCapabilities().isEmpty()); + assertThat(request.getDeepenNots(), contains("856d5138d7269a483efe276d4a6b5c25b4fbb1a4", + "heads/refs/test")); + assertThat(request.getWantIds(), + hasOnlyObjectIds("4624442d68ee402a94364191085b77137618633e", + "f900c8326a43303685c46b279b9f70411bff1a4b")); + } + + @Test public void testRecvWantsShallow() throws PackProtocolException, IOException { PacketLineIn pckIn = formatAsPacketLine( diff --git a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java index 837bdce919..167b5b72c6 100644 --- a/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java +++ b/org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -152,7 +152,7 @@ public class ProtocolV2ParserTest { assertThat(request.getClientShallowCommits(), hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0", "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); - assertTrue(request.getDeepenNotRefs().isEmpty()); + assertTrue(request.getDeepenNots().isEmpty()); assertEquals(15, request.getDepth()); assertTrue(request.getClientCapabilities() .contains(GitProtocolConstants.OPTION_DEEPEN_RELATIVE)); @@ -171,7 +171,7 @@ public class ProtocolV2ParserTest { assertThat(request.getClientShallowCommits(), hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0", "145e683b229dcab9d0e2ccb01b386f9ecc17d29d")); - assertThat(request.getDeepenNotRefs(), + assertThat(request.getDeepenNots(), hasItems("a08595f76159b09d57553e37a5123f1091bb13e7")); } diff --git a/org.eclipse.jgit/.settings/.api_filters b/org.eclipse.jgit/.settings/.api_filters index 6eb8bd3732..8aa84f3ac1 100644 --- a/org.eclipse.jgit/.settings/.api_filters +++ b/org.eclipse.jgit/.settings/.api_filters @@ -13,7 +13,13 @@ <filter id="336695337"> <message_arguments> <message_argument value="org.eclipse.jgit.lib.ObjectDatabase"/> - <message_argument value="getApproximateObjectCount()"/> + <message_argument value="getShallowCommits()"/> + </message_arguments> + </filter> + <filter id="336695337"> + <message_arguments> + <message_argument value="org.eclipse.jgit.lib.ObjectDatabase"/> + <message_argument value="setShallowCommits(Set<ObjectId>)"/> </message_arguments> </filter> </resource> @@ -60,14 +66,6 @@ </message_arguments> </filter> </resource> - <resource path="src/org/eclipse/jgit/transport/BasePackPushConnection.java" type="org.eclipse.jgit.transport.BasePackPushConnection"> - <filter id="338792546"> - <message_arguments> - <message_argument value="org.eclipse.jgit.transport.BasePackPushConnection"/> - <message_argument value="noRepository()"/> - </message_arguments> - </filter> - </resource> <resource path="src/org/eclipse/jgit/transport/PushConfig.java" type="org.eclipse.jgit.transport.PushConfig"> <filter id="338722907"> <message_arguments> diff --git a/org.eclipse.jgit/BUILD b/org.eclipse.jgit/BUILD index 04873b0c72..e806e7d6d0 100644 --- a/org.eclipse.jgit/BUILD +++ b/org.eclipse.jgit/BUILD @@ -23,6 +23,12 @@ java_library( "//lib:javaewah", "//lib:slf4j-api", ], + javacopts = [ + "-Xep:ReferenceEquality:OFF", + "-Xep:StringEquality:OFF", + "-Xep:TypeParameterUnusedInFormals:OFF", + "-Xep:DefaultCharset:OFF", + ] ) genrule( 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 66adad5151..f3ecadd6e5 100644 --- a/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties +++ b/org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties @@ -38,7 +38,7 @@ badIgnorePatternFull=File {0} line {1}: cannot parse pattern ''{2}'': {3} badObjectType=Bad object type: {0} badRef=Bad ref: {0}: {1} badSectionEntry=Bad section entry: {0} -badShallowLine=Bad shallow line: {0} +badShallowLine=Shallow file ''{0}'' has bad line: {1} bareRepositoryNoWorkdirAndIndex=Bare Repository has neither a working tree, nor an index base85invalidChar=Invalid base-85 character: 0x{0} base85length=Base-85 encoded data must have a length that is a multiple of 5 @@ -237,6 +237,8 @@ deletedOrphanInPackDir=Deleted orphaned file {} deleteRequiresZeroNewId=Delete requires new ID to be zero deleteTagUnexpectedResult=Delete tag returned unexpected result {0} deletingNotSupported=Deleting {0} not supported. +depthMustBeAt1=Depth must be >= 1 +depthWithUnshallow=Depth and unshallow can\'t be used together destinationIsNotAWildcard=Destination is not a wildcard. detachedHeadDetected=HEAD is detached diffToolNotGivenError=No diff tool provided and no defaults configured. @@ -518,6 +520,7 @@ notFound=not found. nothingToFetch=Nothing to fetch. nothingToPush=Nothing to push. notMergedExceptionMessage=Branch was not deleted as it has not been merged yet; use the force option to delete it anyway +notShallowedUnshallow=The server sent a unshallow for a commit that wasn''t marked as shallow: {0} noXMLParserAvailable=No XML parser available. objectAtHasBadZlibStream=Object at {0} in {1} has bad zlib stream objectIsCorrupt=Object {0} is corrupt: {1} @@ -587,6 +590,7 @@ pushNotPermitted=push not permitted pushOptionsNotSupported=Push options not supported; received {0} rawLogMessageDoesNotParseAsLogEntry=Raw log message does not parse as log entry readConfigFailed=Reading config file ''{0}'' failed +readShallowFailed=Reading shallow file ''{0}'' failed readFileStoreAttributesFailed=Reading FileStore attributes from user config failed readerIsRequired=Reader is required readingObjectsFromLocalRepositoryFailed=reading objects from local repository failed: {0} @@ -662,6 +666,7 @@ serviceNotEnabledNoName=Service not enabled serviceNotPermitted={1} not permitted on ''{0}'' sha1CollisionDetected=SHA-1 collision detected on {0} shallowCommitsAlreadyInitialized=Shallow commits have already been initialized +shallowNotSupported=The server does not support shallow shallowPacksRequireDepthWalk=Shallow packs require a DepthWalk shortCompressedStreamAt=Short compressed stream at {0} shortReadOfBlock=Short read of block. 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 3aa711455b..1f979a938c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2011, 2017 Chris Aniszczyk <caniszczyk@gmail.com> and others + * Copyright (C) 2011, 2022 Chris Aniszczyk <caniszczyk@gmail.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -13,10 +13,13 @@ import java.io.File; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; +import java.time.Instant; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Collection; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidRemoteException; @@ -91,6 +94,12 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { private TagOpt tagOption; + private Integer depth; + + private Instant shallowSince; + + private List<String> shallowExcludes = new ArrayList<>(); + private enum FETCH_TYPE { MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR } @@ -306,6 +315,11 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { fetchAll ? TagOpt.FETCH_TAGS : TagOpt.AUTO_FOLLOW); } command.setInitialBranch(branch); + if (depth != null) { + command.setDepth(depth.intValue()); + } + command.setShallowSince(shallowSince); + command.setShallowExcludes(shallowExcludes); configure(command); return command.call(); @@ -737,6 +751,82 @@ public class CloneCommand extends TransportCommand<CloneCommand, Git> { return this; } + /** + * Creates a shallow clone with a history truncated to the specified number + * of commits. + * + * @param depth + * the depth + * @return {@code this} + * + * @since 6.3 + */ + public CloneCommand setDepth(int depth) { + if (depth < 1) { + throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); + } + this.depth = Integer.valueOf(depth); + return this; + } + + /** + * Creates a shallow clone with a history after the specified time. + * + * @param shallowSince + * the timestammp; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public CloneCommand setShallowSince(@NonNull OffsetDateTime shallowSince) { + this.shallowSince = shallowSince.toInstant(); + return this; + } + + /** + * Creates a shallow clone with a history after the specified time. + * + * @param shallowSince + * the timestammp; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public CloneCommand setShallowSince(@NonNull Instant shallowSince) { + this.shallowSince = shallowSince; + return this; + } + + /** + * Creates a shallow clone with a history, excluding commits reachable from + * a specified remote branch or tag. + * + * @param shallowExclude + * the ref or commit; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public CloneCommand addShallowExclude(@NonNull String shallowExclude) { + shallowExcludes.add(shallowExclude); + return this; + } + + /** + * Creates a shallow clone with a history, excluding commits reachable from + * a specified remote branch or tag. + * + * @param shallowExclude + * the commit; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public CloneCommand addShallowExclude(@NonNull ObjectId shallowExclude) { + shallowExcludes.add(shallowExclude.name()); + return this; + } + private static void validateDirs(File directory, File gitDir, boolean bare) throws IllegalStateException { if (directory != null) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java index 90c1515b06..84bee36204 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Chris Aniszczyk <caniszczyk@gmail.com> and others + * Copyright (C) 2010, 2022 Chris Aniszczyk <caniszczyk@gmail.com> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -14,10 +14,13 @@ import static java.util.stream.Collectors.toList; import java.io.IOException; import java.net.URISyntaxException; import java.text.MessageFormat; +import java.time.Instant; +import java.time.OffsetDateTime; import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import org.eclipse.jgit.annotations.NonNull; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.api.errors.InvalidConfigurationException; @@ -76,6 +79,14 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { private String initialBranch; + private Integer depth; + + private Instant deepenSince; + + private List<String> shallowExcludes = new ArrayList<>(); + + private boolean unshallow; + /** * Callback for status of fetch operation. * @@ -156,11 +167,9 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { walk.getPath()); // When the fetch mode is "yes" we always fetch. When the - // mode - // is "on demand", we only fetch if the submodule's revision - // was - // updated to an object that is not currently present in the - // submodule. + // mode is "on demand", we only fetch if the submodule's + // revision was updated to an object that is not currently + // present in the submodule. if ((recurseMode == FetchRecurseSubmodulesMode.ON_DEMAND && !submoduleRepo.getObjectDatabase() .has(walk.getObjectId())) @@ -209,6 +218,17 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { if (tagOption != null) transport.setTagOpt(tagOption); transport.setFetchThin(thin); + if (depth != null) { + transport.setDepth(depth); + } + if (unshallow) { + if (depth != null) { + throw new IllegalStateException(JGitText.get().depthWithUnshallow); + } + transport.setDepth(Constants.INFINITE_DEPTH); + } + transport.setDeepenSince(deepenSince); + transport.setDeepenNots(shallowExcludes); configure(transport); FetchResult result = transport.fetch(monitor, applyOptions(refSpecs), initialBranch); @@ -542,4 +562,105 @@ public class FetchCommand extends TransportCommand<FetchCommand, FetchResult> { this.isForceUpdate = force; return this; } + + /** + * Limits fetching to the specified number of commits from the tip of each + * remote branch history. + * + * @param depth + * the depth + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand setDepth(int depth) { + if (depth < 1) { + throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); + } + this.depth = Integer.valueOf(depth); + return this; + } + + /** + * Deepens or shortens the history of a shallow repository to include all + * reachable commits after a specified time. + * + * @param shallowSince + * the timestammp; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand setShallowSince(@NonNull OffsetDateTime shallowSince) { + this.deepenSince = shallowSince.toInstant(); + return this; + } + + /** + * Deepens or shortens the history of a shallow repository to include all + * reachable commits after a specified time. + * + * @param shallowSince + * the timestammp; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand setShallowSince(@NonNull Instant shallowSince) { + this.deepenSince = shallowSince; + return this; + } + + /** + * Deepens or shortens the history of a shallow repository to exclude + * commits reachable from a specified remote branch or tag. + * + * @param shallowExclude + * the ref or commit; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand addShallowExclude(@NonNull String shallowExclude) { + shallowExcludes.add(shallowExclude); + return this; + } + + /** + * Creates a shallow clone with a history, excluding commits reachable from + * a specified remote branch or tag. + * + * @param shallowExclude + * the commit; must not be {@code null} + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand addShallowExclude(@NonNull ObjectId shallowExclude) { + shallowExcludes.add(shallowExclude.name()); + return this; + } + + /** + * If the source repository is complete, converts a shallow repository to a + * complete one, removing all the limitations imposed by shallow + * repositories. + * + * If the source repository is shallow, fetches as much as possible so that + * the current repository has the same history as the source repository. + * + * @param unshallow + * whether to unshallow or not + * @return {@code this} + * + * @since 6.3 + */ + public FetchCommand setUnshallow(boolean unshallow) { + this.unshallow = unshallow; + return this; + } + + void setShallowExcludes(List<String> shallowExcludes) { + this.shallowExcludes = shallowExcludes; + } } 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 efdb8e42e3..964debcccb 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java @@ -265,6 +265,8 @@ public class JGitText extends TranslationBundle { /***/ public String deleteRequiresZeroNewId; /***/ public String deleteTagUnexpectedResult; /***/ public String deletingNotSupported; + /***/ public String depthMustBeAt1; + /***/ public String depthWithUnshallow; /***/ public String destinationIsNotAWildcard; /***/ public String detachedHeadDetected; /***/ public String diffToolNotGivenError; @@ -546,6 +548,7 @@ public class JGitText extends TranslationBundle { /***/ public String nothingToFetch; /***/ public String nothingToPush; /***/ public String notMergedExceptionMessage; + /***/ public String notShallowedUnshallow; /***/ public String noXMLParserAvailable; /***/ public String objectAtHasBadZlibStream; /***/ public String objectIsCorrupt; @@ -615,6 +618,7 @@ public class JGitText extends TranslationBundle { /***/ public String pushOptionsNotSupported; /***/ public String rawLogMessageDoesNotParseAsLogEntry; /***/ public String readConfigFailed; + /***/ public String readShallowFailed; /***/ public String readFileStoreAttributesFailed; /***/ public String readerIsRequired; /***/ public String readingObjectsFromLocalRepositoryFailed; @@ -690,6 +694,7 @@ public class JGitText extends TranslationBundle { /***/ public String serviceNotPermitted; /***/ public String sha1CollisionDetected; /***/ public String shallowCommitsAlreadyInitialized; + /***/ public String shallowNotSupported; /***/ public String shallowPacksRequireDepthWalk; /***/ public String shortCompressedStreamAt; /***/ public String shortReadOfBlock; 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 99da222395..5a8207ed01 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 @@ -1,3 +1,12 @@ +/* + * Copyright (C) 2011, 2022 Google Inc. and others + * + * This program and the accompanying materials are made available under the + * terms of the Eclipse Distribution License v. 1.0 which is available at + * https://www.eclipse.org/org/documents/edl-v10.php. + * + * SPDX-License-Identifier: BSD-3-Clause + */ package org.eclipse.jgit.internal.storage.dfs; import java.io.ByteArrayOutputStream; @@ -6,13 +15,16 @@ import java.io.IOException; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; +import java.util.Collections; import java.util.List; +import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.annotations.Nullable; import org.eclipse.jgit.internal.storage.dfs.DfsObjDatabase.PackSource; import org.eclipse.jgit.internal.storage.pack.PackExt; import org.eclipse.jgit.internal.storage.reftable.ReftableConfig; +import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.RefDatabase; /** @@ -98,6 +110,7 @@ public class InMemoryRepository extends DfsRepository { public static class MemObjDatabase extends DfsObjDatabase { private List<DfsPackDescription> packs = new ArrayList<>(); private int blockSize; + private Set<ObjectId> shallowCommits = Collections.emptySet(); MemObjDatabase(DfsRepository repo) { super(repo, new DfsReaderOptions()); @@ -167,6 +180,16 @@ public class InMemoryRepository extends DfsRepository { } @Override + public Set<ObjectId> getShallowCommits() throws IOException { + return shallowCommits; + } + + @Override + public void setShallowCommits(Set<ObjectId> shallowCommits) { + this.shallowCommits = shallowCommits; + } + + @Override public long getApproximateObjectCount() { long count = 0; for (DfsPackDescription p : packs) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java index 094fdc1559..9272bf3f59 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2010, Constantine Plotnikov <constantine.plotnikov@gmail.com> - * Copyright (C) 2010, JetBrains s.r.o. and others + * Copyright (C) 2010, 2022 JetBrains s.r.o. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -117,10 +117,15 @@ class CachedObjectDirectory extends FileObjectDatabase { } @Override - Set<ObjectId> getShallowCommits() throws IOException { + public Set<ObjectId> getShallowCommits() throws IOException { return wrapped.getShallowCommits(); } + @Override + public void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException { + wrapped.setShallowCommits(shallowCommits); + } + private CachedObjectDirectory[] myAlternates() { if (alts == null) { ObjectDirectory.AlternateHandle[] src = wrapped.myAlternates(); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java index 01dd27d9fb..e97ed393a1 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2010, Google Inc. and others + * Copyright (C) 2010, 2022 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -50,8 +50,6 @@ abstract class FileObjectDatabase extends ObjectDatabase { abstract FS getFS(); - abstract Set<ObjectId> getShallowCommits() throws IOException; - abstract void selectObjectRepresentation(PackWriter packer, ObjectToPack otp, WindowCursor curs) throws IOException; 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 06c8cad3ac..7699439128 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 @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009, Google Inc. and others + * Copyright (C) 2009, 2022 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -19,6 +19,7 @@ import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; +import java.io.OutputStream; import java.nio.file.Files; import java.text.MessageFormat; import java.util.ArrayList; @@ -560,31 +561,82 @@ public class ObjectDirectory extends FileObjectDatabase { } @Override - Set<ObjectId> getShallowCommits() throws IOException { + public Set<ObjectId> getShallowCommits() throws IOException { if (shallowFile == null || !shallowFile.isFile()) return Collections.emptySet(); if (shallowFileSnapshot == null || shallowFileSnapshot.isModified(shallowFile)) { - shallowCommitsIds = new HashSet<>(); + try { + shallowCommitsIds = FileUtils.readWithRetries(shallowFile, + f -> { + FileSnapshot newSnapshot = FileSnapshot.save(f); + HashSet<ObjectId> result = new HashSet<>(); + try (BufferedReader reader = open(f)) { + String line; + while ((line = reader.readLine()) != null) { + if (!ObjectId.isId(line)) { + throw new IOException( + MessageFormat.format(JGitText + .get().badShallowLine, + f.getAbsolutePath(), + line)); + + } + result.add(ObjectId.fromString(line)); + } + } + shallowFileSnapshot = newSnapshot; + return result; + }); + } catch (IOException e) { + throw e; + } catch (Exception e) { + throw new IOException( + MessageFormat.format(JGitText.get().readShallowFailed, + shallowFile.getAbsolutePath()), + e); + } + } - try (BufferedReader reader = open(shallowFile)) { - String line; - while ((line = reader.readLine()) != null) { - try { - shallowCommitsIds.add(ObjectId.fromString(line)); - } catch (IllegalArgumentException ex) { - throw new IOException(MessageFormat - .format(JGitText.get().badShallowLine, line), - ex); + return shallowCommitsIds; + } + + @Override + public void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException { + this.shallowCommitsIds = shallowCommits; + LockFile lock = new LockFile(shallowFile); + if (!lock.lock()) { + throw new IOException(MessageFormat.format(JGitText.get().lockError, + shallowFile.getAbsolutePath())); + } + + try { + if (shallowCommits.isEmpty()) { + if (shallowFile.isFile()) { + shallowFile.delete(); + } + } else { + try (OutputStream out = lock.getOutputStream()) { + for (ObjectId shallowCommit : shallowCommits) { + byte[] buf = new byte[Constants.OBJECT_ID_STRING_LENGTH + 1]; + shallowCommit.copyTo(buf, 0); + buf[Constants.OBJECT_ID_STRING_LENGTH] = '\n'; + out.write(buf); } + } finally { + lock.commit(); } } + } finally { + lock.unlock(); + } + if (shallowCommits.isEmpty()) { + shallowFileSnapshot = FileSnapshot.DIRTY; + } else { shallowFileSnapshot = FileSnapshot.save(shallowFile); } - - return shallowCommitsIds; } void closeAllPackHandles(File packFile) { diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java index cf2e69dbb5..30a0074195 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2008, Google Inc. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2006-2017, Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2006, 2022, Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -747,6 +747,13 @@ public final class Constants { */ public static final String LOCK_SUFFIX = ".lock"; //$NON-NLS-1$ + /** + * Depth used to unshallow a repository + * + * @since 6.3 + */ + public static final int INFINITE_DEPTH = 0x7fffffff; + private Constants() { // Hide the default constructor } 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 70009cba35..1c0f436090 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2009, Google Inc. and others + * Copyright (C) 2009, 2022 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -11,6 +11,7 @@ package org.eclipse.jgit.lib; import java.io.IOException; +import java.util.Set; import org.eclipse.jgit.errors.IncorrectObjectTypeException; import org.eclipse.jgit.errors.MissingObjectException; @@ -72,6 +73,26 @@ public abstract class ObjectDatabase implements AutoCloseable { public abstract ObjectReader newReader(); /** + * @return the shallow commits of the current repository + * + * @throws IOException the database could not be read + * + * @since 6.3 + */ + public abstract Set<ObjectId> getShallowCommits() throws IOException; + + /** + * Update the shallow commits of the current repository + * + * @param shallowCommits the new shallow commits + * + * @throws IOException the database could not be updated + * + * @since 6.3 + */ + public abstract void setShallowCommits(Set<ObjectId> shallowCommits) throws IOException; + + /** * Close any resources held by this database. */ @Override 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 3f167ccce2..be36d2b834 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java @@ -12,16 +12,30 @@ package org.eclipse.jgit.transport; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DELIM; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_END; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; + import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.text.MessageFormat; +import java.time.Instant; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.HashSet; import java.util.LinkedHashSet; +import java.util.List; import java.util.Set; import org.eclipse.jgit.errors.PackProtocolException; @@ -32,6 +46,7 @@ import org.eclipse.jgit.lib.AnyObjectId; import org.eclipse.jgit.lib.Config; import org.eclipse.jgit.lib.MutableObjectId; import org.eclipse.jgit.lib.NullProgressMonitor; +import org.eclipse.jgit.lib.ObjectDatabase; import org.eclipse.jgit.lib.ObjectId; import org.eclipse.jgit.lib.ObjectInserter; import org.eclipse.jgit.lib.ProgressMonitor; @@ -76,7 +91,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection /** * Maximum number of 'have' lines to send before giving up. * <p> - * During {@link #negotiate(ProgressMonitor)} we send at most this many + * During {@link #negotiate(ProgressMonitor, boolean, Set)} we send at most this many * commits to the remote peer as 'have' lines without an ACK response before * we give up. */ @@ -210,6 +225,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection private int maxHaves; + private Integer depth; + + private Instant deepenSince; + + private List<String> deepenNots; + /** * RPC state, if {@link BasePackConnection#statelessRPC} is true or protocol * V2 is used. @@ -246,6 +267,9 @@ public abstract class BasePackFetchConnection extends BasePackConnection includeTags = transport.getTagOpt() != TagOpt.NO_TAGS; thinPack = transport.isFetchThin(); filterSpec = transport.getFilterSpec(); + depth = transport.getDepth(); + deepenSince = transport.getDeepenSince(); + deepenNots = transport.getDeepenNots(); if (local != null) { walk = new RevWalk(local); @@ -385,9 +409,17 @@ public abstract class BasePackFetchConnection extends BasePackConnection } PacketLineOut output = statelessRPC ? pckState : pckOut; if (sendWants(want, output)) { + boolean mayHaveShallow = depth != null || deepenSince != null || !deepenNots.isEmpty(); + Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits(); + if (isCapableOf(GitProtocolConstants.CAPABILITY_SHALLOW)) { + sendShallow(shallowCommits, output); + } else if (mayHaveShallow) { + throw new PackProtocolException(JGitText.get().shallowNotSupported); + } output.end(); outNeedsEnd = false; - negotiate(monitor); + + negotiate(monitor, mayHaveShallow, shallowCommits); clearState(); @@ -424,10 +456,18 @@ public abstract class BasePackFetchConnection extends BasePackConnection for (String capability : getCapabilitiesV2(capabilities)) { pckState.writeString(capability); } + if (!sendWants(want, pckState)) { // We already have everything we wanted. return; } + + Set<ObjectId> shallowCommits = local.getObjectDatabase().getShallowCommits(); + if (capabilities.contains(GitProtocolConstants.CAPABILITY_SHALLOW)) { + sendShallow(shallowCommits, pckState); + } else if (depth != null || deepenSince != null || !deepenNots.isEmpty()) { + throw new PackProtocolException(JGitText.get().shallowNotSupported); + } // If we send something, we always close it properly ourselves. outNeedsEnd = false; @@ -455,10 +495,21 @@ public abstract class BasePackFetchConnection extends BasePackConnection clearState(); String line = pckIn.readString(); // If we sent a done, we may have an error reply here. - if (sentDone && line.startsWith("ERR ")) { //$NON-NLS-1$ + if (sentDone && line.startsWith(PACKET_ERR)) { throw new RemoteRepositoryException(uri, line.substring(4)); } - // "shallow-info", "wanted-refs", and "packfile-uris" would have to be + + if (GitProtocolConstants.SECTION_SHALLOW_INFO.equals(line)) { + line = handleShallowUnshallow(shallowCommits, pckIn); + if (!PacketLineIn.isDelimiter(line)) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().expectedGot, PACKET_DELIM, + line)); + } + line = pckIn.readString(); + } + + // "wanted-refs" and "packfile-uris" would have to be // handled here in that order. if (!GitProtocolConstants.SECTION_PACKFILE.equals(line)) { throw new PackProtocolException( @@ -494,7 +545,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection if (c == null) { break; } - output.writeString("have " + c.getId().name() + '\n'); //$NON-NLS-1$ + output.writeString(PACKET_HAVE + c.getId().name() + '\n'); n++; if (n % 10 == 0 && monitor.isCancelled()) { throw new CancelledException(); @@ -505,7 +556,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection || (fetchState.hadAcks && fetchState.havesWithoutAck > MAX_HAVES) || fetchState.havesTotal > maxHaves) { - output.writeString("done\n"); //$NON-NLS-1$ + output.writeString(PACKET_DONE + '\n'); output.end(); return true; } @@ -572,11 +623,12 @@ public abstract class BasePackFetchConnection extends BasePackConnection if (gotReady) { if (!PacketLineIn.isDelimiter(line)) { throw new PackProtocolException(MessageFormat - .format(JGitText.get().expectedGot, "0001", line)); //$NON-NLS-1$ + .format(JGitText.get().expectedGot, PACKET_DELIM, + line)); } } else if (!PacketLineIn.isEnd(line)) { throw new PackProtocolException(MessageFormat - .format(JGitText.get().expectedGot, "0000", line)); //$NON-NLS-1$ + .format(JGitText.get().expectedGot, PACKET_END, line)); } return gotReady; } @@ -672,21 +724,23 @@ public abstract class BasePackFetchConnection extends BasePackConnection if (objectId == null) { continue; } - try { - if (walk.parseAny(objectId).has(REACHABLE)) { - // We already have this object. Asking for it is - // not a very good idea. - // - continue; + // if depth is set we need to fetch the objects even if they are already available + if (transport.getDepth() == null) { + try { + if (walk.parseAny(objectId).has(REACHABLE)) { + // We already have this object. Asking for it is + // not a very good idea. + // + continue; + } + } catch (IOException err) { + // Its OK, we don't have it, but we want to fix that + // by fetching the object from the other side. } - } catch (IOException err) { - // Its OK, we don't have it, but we want to fix that - // by fetching the object from the other side. } final StringBuilder line = new StringBuilder(46); - line.append("want "); //$NON-NLS-1$ - line.append(objectId.name()); + line.append(PACKET_WANT).append(objectId.name()); if (first && TransferConfig.ProtocolVersion.V0 .equals(getProtocolVersion())) { line.append(enableCapabilities()); @@ -773,8 +827,8 @@ public abstract class BasePackFetchConnection extends BasePackConnection return line.toString(); } - private void negotiate(ProgressMonitor monitor) throws IOException, - CancelledException { + private void negotiate(ProgressMonitor monitor, boolean mayHaveShallow, Set<ObjectId> shallowCommits) + throws IOException, CancelledException { final MutableObjectId ackId = new MutableObjectId(); int resultsPending = 0; int havesSent = 0; @@ -795,7 +849,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection } ObjectId o = c.getId(); - pckOut.writeString("have " + o.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + pckOut.writeString(PACKET_HAVE + o.name() + '\n'); havesSent++; havesSinceLastContinue++; @@ -898,7 +952,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection // loop above while in the middle of a request. This allows us // to just write done immediately. // - pckOut.writeString("done\n"); //$NON-NLS-1$ + pckOut.writeString(PACKET_DONE + '\n'); pckOut.flush(); } @@ -911,6 +965,14 @@ public abstract class BasePackFetchConnection extends BasePackConnection resultsPending++; } + if (mayHaveShallow) { + String line = handleShallowUnshallow(shallowCommits, pckIn); + if (!PacketLineIn.isEnd(line)) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().expectedGot, PACKET_END, line)); + } + } + READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) { final AckNackResult anr = pckIn.readACK(ackId); resultsPending--; @@ -992,7 +1054,7 @@ public abstract class BasePackFetchConnection extends BasePackConnection private void markCommon(RevObject obj, AckNackResult anr, boolean useState) throws IOException { if (useState && anr == AckNackResult.ACK_COMMON && !obj.has(STATE)) { - pckState.writeString("have " + obj.name() + '\n'); //$NON-NLS-1$ + pckState.writeString(PACKET_HAVE + obj.name() + '\n'); obj.add(STATE); } obj.add(COMMON); @@ -1025,6 +1087,55 @@ public abstract class BasePackFetchConnection extends BasePackConnection } } + private void sendShallow(Set<ObjectId> shallowCommits, PacketLineOut output) + throws IOException { + for (ObjectId shallowCommit : shallowCommits) { + output.writeString(PACKET_SHALLOW + shallowCommit.name()); + } + + if (depth != null) { + output.writeString(PACKET_DEEPEN + depth); + } + + if (deepenSince != null) { + output.writeString( + PACKET_DEEPEN_SINCE + deepenSince.getEpochSecond()); + } + + if (deepenNots != null) { + for (String deepenNotRef : deepenNots) { + output.writeString(PACKET_DEEPEN_NOT + deepenNotRef); + } + } + } + + private String handleShallowUnshallow( + Set<ObjectId> advertisedShallowCommits, PacketLineIn input) + throws IOException { + String line = input.readString(); + ObjectDatabase objectDatabase = local.getObjectDatabase(); + HashSet<ObjectId> newShallowCommits = new HashSet<>( + advertisedShallowCommits); + while (!PacketLineIn.isDelimiter(line) && !PacketLineIn.isEnd(line)) { + if (line.startsWith(PACKET_SHALLOW)) { + newShallowCommits.add(ObjectId + .fromString(line.substring(PACKET_SHALLOW.length()))); + } else if (line.startsWith(PACKET_UNSHALLOW)) { + ObjectId unshallow = ObjectId + .fromString(line.substring(PACKET_UNSHALLOW.length())); + if (!advertisedShallowCommits.contains(unshallow)) { + throw new PackProtocolException(MessageFormat.format( + JGitText.get().notShallowedUnshallow, + unshallow.name())); + } + newShallowCommits.remove(unshallow); + } + line = input.readString(); + } + objectDatabase.setShallowCommits(newShallowCommits); + return line; + } + /** * Notification event delivered just before the pack is received from the * network. This event can be used by RPC such as {@link org.eclipse.jgit.transport.TransportHttp} to 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 7bface49d9..28c3b6a0fa 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java @@ -1,6 +1,6 @@ /* * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -393,7 +393,7 @@ class FetchProcess { ow.markUninteresting(ow.parseAny(ref.getObjectId())); ow.checkConnectivity(); } - return true; + return transport.getDepth() == null; // if depth is set we need to request objects that are already available } catch (MissingObjectException e) { return false; } catch (IOException e) { @@ -516,8 +516,10 @@ class FetchProcess { } if (spec.getDestination() != null) { final TrackingRefUpdate tru = createUpdate(spec, newId); - if (newId.equals(tru.getOldObjectId())) + // if depth is set we need to update the ref + if (newId.equals(tru.getOldObjectId()) && transport.getDepth() == null) { return; + } localUpdates.add(tru); } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java index 9ebc722ffe..0663c5141c 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -35,7 +35,7 @@ abstract class FetchRequest { final int deepenSince; - final List<String> deepenNotRefs; + final List<String> deepenNots; @Nullable final String agent; @@ -53,7 +53,7 @@ abstract class FetchRequest { * the filter spec * @param clientCapabilities * capabilities sent in the request - * @param deepenNotRefs + * @param deepenNots * Requests that the shallow clone/fetch should be cut at these * specific revisions instead of a depth. * @param deepenSince @@ -66,14 +66,14 @@ abstract class FetchRequest { @NonNull Set<ObjectId> clientShallowCommits, @NonNull FilterSpec filterSpec, @NonNull Set<String> clientCapabilities, int deepenSince, - @NonNull List<String> deepenNotRefs, @Nullable String agent) { + @NonNull List<String> deepenNots, @Nullable String agent) { this.wantIds = requireNonNull(wantIds); this.depth = depth; this.clientShallowCommits = requireNonNull(clientShallowCommits); this.filterSpec = requireNonNull(filterSpec); this.clientCapabilities = requireNonNull(clientCapabilities); this.deepenSince = deepenSince; - this.deepenNotRefs = requireNonNull(deepenNotRefs); + this.deepenNots = requireNonNull(deepenNots); this.agent = agent; } @@ -148,8 +148,8 @@ abstract class FetchRequest { * @return refs received in "deepen-not" lines. */ @NonNull - List<String> getDeepenNotRefs() { - return deepenNotRefs; + List<String> getDeepenNots() { + return deepenNots; } /** diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java index 91adb5e6ac..4decb79513 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -11,9 +11,10 @@ package org.eclipse.jgit.transport; import static java.util.Objects.requireNonNull; +import java.util.ArrayList; import java.util.Collection; -import java.util.Collections; import java.util.HashSet; +import java.util.List; import java.util.Set; import org.eclipse.jgit.annotations.NonNull; @@ -28,15 +29,20 @@ final class FetchV0Request extends FetchRequest { FetchV0Request(@NonNull Set<ObjectId> wantIds, int depth, @NonNull Set<ObjectId> clientShallowCommits, @NonNull FilterSpec filterSpec, - @NonNull Set<String> clientCapabilities, @Nullable String agent) { + @NonNull Set<String> clientCapabilities, int deepenSince, + @NonNull List<String> deepenNotRefs, @Nullable String agent) { super(wantIds, depth, clientShallowCommits, filterSpec, - clientCapabilities, 0, Collections.emptyList(), agent); + clientCapabilities, deepenSince, deepenNotRefs, agent); } static final class Builder { int depth; + final List<String> deepenNots = new ArrayList<>(); + + int deepenSince; + final Set<ObjectId> wantIds = new HashSet<>(); final Set<ObjectId> clientShallowCommits = new HashSet<>(); @@ -68,6 +74,50 @@ final class FetchV0Request extends FetchRequest { } /** + * @return depth set in the request (via a "deepen" line). Defaulting to + * 0 if not set. + */ + int getDepth() { + return depth; + } + + /** + * @return true if there has been at least one "deepen not" line in the + * request so far + */ + boolean hasDeepenNots() { + return !deepenNots.isEmpty(); + } + + /** + * @param deepenNot + * reference received in a "deepen not" line + * @return this builder + */ + Builder addDeepenNot(String deepenNot) { + deepenNots.add(deepenNot); + return this; + } + + /** + * @param value + * Unix timestamp received in a "deepen since" line + * @return this builder + */ + Builder setDeepenSince(int value) { + deepenSince = value; + return this; + } + + /** + * @return shallow since value, sent before in a "deepen since" line. 0 + * by default. + */ + int getDeepenSince() { + return deepenSince; + } + + /** * @param shallowOid * object id received in a "shallow" line * @return this builder @@ -110,7 +160,7 @@ final class FetchV0Request extends FetchRequest { FetchV0Request build() { return new FetchV0Request(wantIds, depth, clientShallowCommits, - filterSpec, clientCaps, agent); + filterSpec, clientCaps, deepenSince, deepenNots, agent); } } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java index 50fb9d2262..446a3bcc79 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -50,7 +50,7 @@ public final class FetchV2Request extends FetchRequest { @NonNull List<String> wantedRefs, @NonNull Set<ObjectId> wantIds, @NonNull Set<ObjectId> clientShallowCommits, int deepenSince, - @NonNull List<String> deepenNotRefs, int depth, + @NonNull List<String> deepenNots, int depth, @NonNull FilterSpec filterSpec, boolean doneReceived, boolean waitForDone, @NonNull Set<String> clientCapabilities, @@ -58,7 +58,7 @@ public final class FetchV2Request extends FetchRequest { boolean sidebandAll, @NonNull List<String> packfileUriProtocols) { super(wantIds, depth, clientShallowCommits, filterSpec, clientCapabilities, deepenSince, - deepenNotRefs, agent); + deepenNots, agent); this.peerHas = requireNonNull(peerHas); this.wantedRefs = requireNonNull(wantedRefs); this.doneReceived = doneReceived; @@ -140,7 +140,7 @@ public final class FetchV2Request extends FetchRequest { final Set<ObjectId> clientShallowCommits = new HashSet<>(); - final List<String> deepenNotRefs = new ArrayList<>(); + final List<String> deepenNots = new ArrayList<>(); final Set<String> clientCapabilities = new HashSet<>(); @@ -240,17 +240,17 @@ public final class FetchV2Request extends FetchRequest { * @return true if there has been at least one "deepen not" line in the * request so far */ - boolean hasDeepenNotRefs() { - return !deepenNotRefs.isEmpty(); + boolean hasDeepenNots() { + return !deepenNots.isEmpty(); } /** - * @param deepenNotRef + * @param deepenNot * reference received in a "deepen not" line * @return this builder */ - Builder addDeepenNotRef(String deepenNotRef) { - deepenNotRefs.add(deepenNotRef); + Builder addDeepenNot(String deepenNot) { + deepenNots.add(deepenNot); return this; } @@ -350,7 +350,7 @@ public final class FetchV2Request extends FetchRequest { */ FetchV2Request build() { return new FetchV2Request(peerHas, wantedRefs, wantIds, - clientShallowCommits, deepenSince, deepenNotRefs, + clientShallowCommits, deepenSince, deepenNots, depth, filterSpec, doneReceived, waitForDone, clientCapabilities, agent, Collections.unmodifiableList(serverOptions), sidebandAll, 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 aaa9308ac3..be14e92d07 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java @@ -1,7 +1,7 @@ /* * Copyright (C) 2008, 2013 Google Inc. * Copyright (C) 2008, Robin Rosenberg <robin.rosenberg@dewire.com> - * Copyright (C) 2008, 2020 Shawn O. Pearce <spearce@spearce.org> and others + * Copyright (C) 2008, 2022 Shawn O. Pearce <spearce@spearce.org> and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -234,6 +234,13 @@ public final class GitProtocolConstants { public static final String CAPABILITY_SERVER_OPTION = "server-option"; //$NON-NLS-1$ /** + * The server supports the receiving of shallow options. + * + * @since 6.3 + */ + public static final String CAPABILITY_SHALLOW = "shallow"; //$NON-NLS-1$ + + /** * Option for passing application-specific options to the server. * * @since 5.2 @@ -308,6 +315,13 @@ public final class GitProtocolConstants { public static final String SECTION_PACKFILE = "packfile"; //$NON-NLS-1$ /** + * Protocol V2 shallow-info section header. + * + * @since 6.3 + */ + public static final String SECTION_SHALLOW_INFO = "shallow-info"; //$NON-NLS-1$ + + /** * Protocol announcement for protocol version 1. This is the same as V0, * except for this initial line. * @@ -329,6 +343,106 @@ public final class GitProtocolConstants { */ public static final String VERSION_2_REQUEST = "version=2"; //$NON-NLS-1$ + /** + * The flush packet. + * + * @since 6.3 + */ + public static final String PACKET_FLUSH = "0000"; //$NON-NLS-1$ + + /** + * An alias for {@link #PACKET_FLUSH}. "Flush" is the name used in the C git + * documentation; the Java implementation calls this "end" in several + * places. + * + * @since 6.3 + */ + public static final String PACKET_END = PACKET_FLUSH; + + /** + * The delimiter packet in protocol V2. + * + * @since 6.3 + */ + public static final String PACKET_DELIM = "0001"; //$NON-NLS-1$ + + /** + * A "deepen" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_DEEPEN = "deepen "; //$NON-NLS-1$ + + /** + * A "deepen-not" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_DEEPEN_NOT = "deepen-not "; //$NON-NLS-1$ + + /** + * A "deepen-since" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_DEEPEN_SINCE = "deepen-since "; //$NON-NLS-1$ + + /** + * An "ACK" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_ACK = "ACK "; //$NON-NLS-1$ + + /** + * A "done" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_DONE = "done"; //$NON-NLS-1$ + + /** + * A "ERR" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_ERR = "ERR "; //$NON-NLS-1$ + + /** + * A "have" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_HAVE = "have "; //$NON-NLS-1$ + + /** + * A "shallow" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_SHALLOW = OPTION_SHALLOW + ' '; + + /** + * A "shallow" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_UNSHALLOW = "unshallow "; //$NON-NLS-1$ + + /** + * A "want" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_WANT = "want "; //$NON-NLS-1$ + + /** + * A "want-ref" packet beginning. + * + * @since 6.3 + */ + public static final String PACKET_WANT_REF = OPTION_WANT_REF + ' '; + enum MultiAck { OFF, CONTINUE, DETAILED; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java index f8c51c180f..21a492577f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -10,6 +10,11 @@ package org.eclipse.jgit.transport; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_FILTER; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; import java.io.EOFException; import java.io.IOException; @@ -70,20 +75,56 @@ final class ProtocolV0Parser { break; } - if (line.startsWith("deepen ")) { //$NON-NLS-1$ - int depth = Integer.parseInt(line.substring(7)); + if (line.startsWith(PACKET_DEEPEN)) { + int depth = Integer + .parseInt(line.substring(PACKET_DEEPEN.length())); if (depth <= 0) { throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidDepth, Integer.valueOf(depth))); } + if (reqBuilder.getDeepenSince() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + if (reqBuilder.hasDeepenNots()) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } reqBuilder.setDepth(depth); continue; } - if (line.startsWith("shallow ")) { //$NON-NLS-1$ + if (line.startsWith(PACKET_DEEPEN_NOT)) { + reqBuilder.addDeepenNot( + line.substring(PACKET_DEEPEN_NOT.length())); + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenNotWithDeepen); + } + continue; + } + + if (line.startsWith(PACKET_DEEPEN_SINCE)) { + // TODO: timestamps should be long + int ts = Integer + .parseInt(line.substring(PACKET_DEEPEN_SINCE.length())); + if (ts <= 0) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidTimestamp, line)); + } + if (reqBuilder.getDepth() != 0) { + throw new PackProtocolException( + JGitText.get().deepenSinceWithDeepen); + } + reqBuilder.setDeepenSince(ts); + continue; + } + + if (line.startsWith(PACKET_SHALLOW)) { reqBuilder.addClientShallowCommit( - ObjectId.fromString(line.substring(8))); + ObjectId.fromString( + line.substring(PACKET_SHALLOW.length()))); continue; } @@ -101,7 +142,7 @@ final class ProtocolV0Parser { continue; } - if (!line.startsWith("want ") || line.length() < 45) { //$NON-NLS-1$ + if (!line.startsWith(PACKET_WANT) || line.length() < 45) { throw new PackProtocolException(MessageFormat .format(JGitText.get().expectedGot, "want", line)); //$NON-NLS-1$ } @@ -115,7 +156,8 @@ final class ProtocolV0Parser { } } - reqBuilder.addWantId(ObjectId.fromString(line.substring(5))); + reqBuilder.addWantId( + ObjectId.fromString(line.substring(PACKET_WANT.length()))); isFirst = false; } diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java index 6cec4b9a3f..b38deb69c0 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2018, Google LLC. and others + * Copyright (C) 2018, 2022 Google LLC. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -20,7 +20,14 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDEBAND_AL import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; -import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WANT_REF; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_NOT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DEEPEN_SINCE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_WANT_REF; import java.io.IOException; import java.text.MessageFormat; @@ -115,15 +122,17 @@ final class ProtocolV2Parser { boolean filterReceived = false; for (String line2 : pckIn.readStrings()) { - if (line2.startsWith("want ")) { //$NON-NLS-1$ - reqBuilder.addWantId(ObjectId.fromString(line2.substring(5))); + if (line2.startsWith(PACKET_WANT)) { + reqBuilder.addWantId(ObjectId + .fromString(line2.substring(PACKET_WANT.length()))); } else if (transferConfig.isAllowRefInWant() - && line2.startsWith(OPTION_WANT_REF + " ")) { //$NON-NLS-1$ + && line2.startsWith(PACKET_WANT_REF)) { reqBuilder.addWantedRef( - line2.substring(OPTION_WANT_REF.length() + 1)); - } else if (line2.startsWith("have ")) { //$NON-NLS-1$ - reqBuilder.addPeerHas(ObjectId.fromString(line2.substring(5))); - } else if (line2.equals("done")) { //$NON-NLS-1$ + line2.substring(PACKET_WANT_REF.length())); + } else if (line2.startsWith(PACKET_HAVE)) { + reqBuilder.addPeerHas(ObjectId + .fromString(line2.substring(PACKET_HAVE.length()))); + } else if (line2.equals(PACKET_DONE)) { reqBuilder.setDoneReceived(); } else if (line2.equals(OPTION_WAIT_FOR_DONE)) { reqBuilder.setWaitForDone(); @@ -135,11 +144,13 @@ final class ProtocolV2Parser { reqBuilder.addClientCapability(OPTION_INCLUDE_TAG); } else if (line2.equals(OPTION_OFS_DELTA)) { reqBuilder.addClientCapability(OPTION_OFS_DELTA); - } else if (line2.startsWith("shallow ")) { //$NON-NLS-1$ + } else if (line2.startsWith(PACKET_SHALLOW)) { reqBuilder.addClientShallowCommit( - ObjectId.fromString(line2.substring(8))); - } else if (line2.startsWith("deepen ")) { //$NON-NLS-1$ - int parsedDepth = Integer.parseInt(line2.substring(7)); + ObjectId.fromString( + line2.substring(PACKET_SHALLOW.length()))); + } else if (line2.startsWith(PACKET_DEEPEN)) { + int parsedDepth = Integer + .parseInt(line2.substring(PACKET_DEEPEN.length())); if (parsedDepth <= 0) { throw new PackProtocolException( MessageFormat.format(JGitText.get().invalidDepth, @@ -149,21 +160,23 @@ final class ProtocolV2Parser { throw new PackProtocolException( JGitText.get().deepenSinceWithDeepen); } - if (reqBuilder.hasDeepenNotRefs()) { + if (reqBuilder.hasDeepenNots()) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } reqBuilder.setDepth(parsedDepth); - } else if (line2.startsWith("deepen-not ")) { //$NON-NLS-1$ - reqBuilder.addDeepenNotRef(line2.substring(11)); + } else if (line2.startsWith(PACKET_DEEPEN_NOT)) { + reqBuilder.addDeepenNot( + line2.substring(PACKET_DEEPEN_NOT.length())); if (reqBuilder.getDepth() != 0) { throw new PackProtocolException( JGitText.get().deepenNotWithDeepen); } } else if (line2.equals(OPTION_DEEPEN_RELATIVE)) { reqBuilder.addClientCapability(OPTION_DEEPEN_RELATIVE); - } else if (line2.startsWith("deepen-since ")) { //$NON-NLS-1$ - int ts = Integer.parseInt(line2.substring(13)); + } else if (line2.startsWith(PACKET_DEEPEN_SINCE)) { + int ts = Integer.parseInt( + line2.substring(PACKET_DEEPEN_SINCE.length())); if (ts <= 0) { throw new PackProtocolException(MessageFormat .format(JGitText.get().invalidTimestamp, line2)); diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java index 2542105c07..b70eedca63 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/ReceivePack.java @@ -20,6 +20,8 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.CAPABILITY_QUIET; 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.GitProtocolConstants.PACKET_ERR; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_DATA; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_ERROR; import static org.eclipse.jgit.transport.SideBandOutputStream.CH_PROGRESS; @@ -1247,7 +1249,7 @@ public class ReceivePack { public void sendAdvertisedRefs(RefAdvertiser adv) throws IOException, ServiceMayNotContinueException { if (advertiseError != null) { - adv.writeOne("ERR " + advertiseError); //$NON-NLS-1$ + adv.writeOne(PACKET_ERR + advertiseError); return; } @@ -1255,7 +1257,7 @@ public class ReceivePack { advertiseRefsHook.advertiseRefs(this); } catch (ServiceMayNotContinueException fail) { if (fail.getMessage() != null) { - adv.writeOne("ERR " + fail.getMessage()); //$NON-NLS-1$ + adv.writeOne(PACKET_ERR + fail.getMessage()); fail.setOutput(); } throw fail; @@ -1339,8 +1341,9 @@ public class ReceivePack { break; } - if (line.length() >= 48 && line.startsWith("shallow ")) { //$NON-NLS-1$ - parseShallow(line.substring(8, 48)); + int len = PACKET_SHALLOW.length() + 40; + if (line.length() >= len && line.startsWith(PACKET_SHALLOW)) { + parseShallow(line.substring(PACKET_SHALLOW.length(), len)); continue; } @@ -1795,9 +1798,9 @@ public class ReceivePack { @Override void sendString(String s) throws IOException { if (reportStatus) { - pckOut.writeString(s + "\n"); //$NON-NLS-1$ + pckOut.writeString(s + '\n'); } else if (msgOut != null) { - msgOut.write(Constants.encode(s + "\n")); //$NON-NLS-1$ + msgOut.write(Constants.encode(s + '\n')); } } }; diff --git a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java index 3222d6330c..7cea998474 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java @@ -27,6 +27,7 @@ import java.lang.reflect.Modifier; import java.net.URISyntaxException; import java.net.URL; import java.text.MessageFormat; +import java.time.Instant; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -784,6 +785,12 @@ public abstract class Transport implements AutoCloseable { private PrePushHook prePush; + private Integer depth; + + private Instant deepenSince; + + private List<String> deepenNots = new ArrayList<>(); + @Nullable TransferConfig.ProtocolVersion protocol; @@ -1086,6 +1093,83 @@ public abstract class Transport implements AutoCloseable { filterSpec = requireNonNull(filter); } + + /** + * Retrieves the depth for a shallow clone. + * + * @return the depth, or {@code null} if none set + * @since 6.3 + */ + public final Integer getDepth() { + return depth; + } + + /** + * Limits fetching to the specified number of commits from the tip of each + * remote branch history. + * + * @param depth + * the depth + * @since 6.3 + */ + public final void setDepth(int depth) { + if (depth < 1) { + throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); + } + this.depth = Integer.valueOf(depth); + } + + /** + * Limits fetching to the specified number of commits from the tip of each + * remote branch history. + * + * @param depth + * the depth, or {@code null} to unset the depth + * @since 6.3 + */ + public final void setDepth(Integer depth) { + if (depth != null && depth.intValue() < 1) { + throw new IllegalArgumentException(JGitText.get().depthMustBeAt1); + } + this.depth = depth; + } + + /** + * @return the deepen-since for a shallow clone + * @since 6.3 + */ + public final Instant getDeepenSince() { + return deepenSince; + } + + /** + * Deepen or shorten the history of a shallow repository to include all reachable commits after a specified time. + * + * @param deepenSince the deepen-since. Must not be {@code null} + * @since 6.3 + */ + public final void setDeepenSince(@NonNull Instant deepenSince) { + this.deepenSince = deepenSince; + } + + /** + * @return the deepen-not for a shallow clone + * @since 6.3 + */ + public final List<String> getDeepenNots() { + return deepenNots; + } + + /** + * Deepen or shorten the history of a shallow repository to exclude commits reachable from a specified remote branch or tag. + * + * @param deepenNots the deepen-not. Must not be {@code null} + * @since 6.3 + */ + public final void setDeepenNots(@NonNull List<String> deepenNots) { + this.deepenNots = deepenNots; + } + /** * Apply provided remote configuration on this transport. * @@ -1230,7 +1314,7 @@ public abstract class Transport implements AutoCloseable { * @param toFetch * specification of refs to fetch locally. May be null or the * empty collection to use the specifications from the - * RemoteConfig. May contains regular and negative + * RemoteConfig. May contains regular and negative * {@link RefSpec}s. Source for each regular RefSpec can't * be null. * @return information describing the tracking refs updated. @@ -1266,7 +1350,7 @@ public abstract class Transport implements AutoCloseable { * @param toFetch * specification of refs to fetch locally. May be null or the * empty collection to use the specifications from the - * RemoteConfig. May contains regular and negative + * RemoteConfig. May contain regular and negative * {@link RefSpec}s. Source for each regular RefSpec can't * be null. * @param branch 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 dcd806a3da..65dbf12b2f 100644 --- a/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java +++ b/org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java @@ -1,5 +1,5 @@ /* - * Copyright (C) 2008, 2020 Google Inc. and others + * Copyright (C) 2008, 2022 Google Inc. and others * * This program and the accompanying materials are made available under the * terms of the Eclipse Distribution License v. 1.0 which is available at @@ -10,6 +10,7 @@ package org.eclipse.jgit.transport; +import static java.util.Collections.emptyList; import static java.util.Collections.unmodifiableMap; import static java.util.Objects.requireNonNull; import static org.eclipse.jgit.lib.Constants.R_TAGS; @@ -35,6 +36,12 @@ import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_SIDE_BAND_64K; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_THIN_PACK; import static org.eclipse.jgit.transport.GitProtocolConstants.OPTION_WAIT_FOR_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ACK; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_DONE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_ERR; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_HAVE; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_SHALLOW; +import static org.eclipse.jgit.transport.GitProtocolConstants.PACKET_UNSHALLOW; import static org.eclipse.jgit.transport.GitProtocolConstants.VERSION_2_REQUEST; import static org.eclipse.jgit.util.RefMap.toRefMap; @@ -1033,6 +1040,7 @@ public class UploadPack implements Closeable { // writing a response. Buffer the response until then. PackStatistics.Accumulator accumulator = new PackStatistics.Accumulator(); List<ObjectId> unshallowCommits = new ArrayList<>(); + List<ObjectId> deepenNots = emptyList(); FetchRequest req; try { if (biDirectionalPipe) @@ -1071,13 +1079,15 @@ public class UploadPack implements Closeable { verifyClientShallow(req.getClientShallowCommits()); } - if (req.getDepth() != 0 || req.getDeepenSince() != 0) { + deepenNots = parseDeepenNots(req.getDeepenNots()); + if (req.getDepth() != 0 || req.getDeepenSince() != 0 || !req.getDeepenNots().isEmpty()) { computeShallowsAndUnshallows(req, shallow -> { - pckOut.writeString("shallow " + shallow.name() + '\n'); //$NON-NLS-1$ + pckOut.writeString(PACKET_SHALLOW + shallow.name() + '\n'); }, unshallow -> { - pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$ + pckOut.writeString( + PACKET_UNSHALLOW + unshallow.name() + '\n'); unshallowCommits.add(unshallow); - }, Collections.emptyList()); + }, deepenNots); pckOut.end(); } @@ -1109,7 +1119,7 @@ public class UploadPack implements Closeable { if (sendPack) { sendPack(accumulator, req, refs == null ? null : refs.values(), - unshallowCommits, Collections.emptyList(), pckOut); + unshallowCommits, deepenNots, pckOut); } } @@ -1188,15 +1198,7 @@ public class UploadPack implements Closeable { // TODO(ifrade): Refactor to pass around the Request object, instead of // copying data back to class fields - List<ObjectId> deepenNots = new ArrayList<>(); - for (String s : req.getDeepenNotRefs()) { - Ref ref = findRef(s); - if (ref == null) { - throw new PackProtocolException(MessageFormat - .format(JGitText.get().invalidRefName, s)); - } - deepenNots.add(ref.getObjectId()); - } + List<ObjectId> deepenNots = parseDeepenNots(req.getDeepenNots()); Map<String, ObjectId> wantedRefs = wantedRefs(req); // TODO(ifrade): Avoid mutating the parsed request. @@ -1206,7 +1208,7 @@ public class UploadPack implements Closeable { boolean sectionSent = false; boolean mayHaveShallow = req.getDepth() != 0 || req.getDeepenSince() != 0 - || !req.getDeepenNotRefs().isEmpty(); + || !req.getDeepenNots().isEmpty(); List<ObjectId> shallowCommits = new ArrayList<>(); List<ObjectId> unshallowCommits = new ArrayList<>(); @@ -1232,7 +1234,7 @@ public class UploadPack implements Closeable { GitProtocolConstants.SECTION_ACKNOWLEDGMENTS + '\n'); for (ObjectId id : req.getPeerHas()) { if (walk.getObjectReader().has(id)) { - pckOut.writeString("ACK " + id.getName() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + pckOut.writeString(PACKET_ACK + id.getName() + '\n'); } } processHaveLines(req.getPeerHas(), ObjectId.zeroId(), @@ -1250,12 +1252,13 @@ public class UploadPack implements Closeable { if (mayHaveShallow) { if (sectionSent) pckOut.writeDelim(); - pckOut.writeString("shallow-info\n"); //$NON-NLS-1$ + pckOut.writeString( + GitProtocolConstants.SECTION_SHALLOW_INFO + '\n'); for (ObjectId o : shallowCommits) { - pckOut.writeString("shallow " + o.getName() + '\n'); //$NON-NLS-1$ + pckOut.writeString(PACKET_SHALLOW + o.getName() + '\n'); } for (ObjectId o : unshallowCommits) { - pckOut.writeString("unshallow " + o.getName() + '\n'); //$NON-NLS-1$ + pckOut.writeString(PACKET_UNSHALLOW + o.getName() + '\n'); } sectionSent = true; } @@ -1319,7 +1322,7 @@ public class UploadPack implements Closeable { .format(JGitText.get().missingObject, oid.name()), e); } - pckOut.writeString(oid.getName() + " " + size); //$NON-NLS-1$ + pckOut.writeString(oid.getName() + ' ' + size); } pckOut.end(); @@ -1391,7 +1394,7 @@ public class UploadPack implements Closeable { protocolV2Hook .onCapabilities(CapabilitiesV2Request.builder().build()); for (String s : getV2CapabilityAdvertisement()) { - pckOut.writeString(s + "\n"); //$NON-NLS-1$ + pckOut.writeString(s + '\n'); } pckOut.end(); @@ -1618,7 +1621,7 @@ public class UploadPack implements Closeable { */ public void sendMessage(String what) { try { - msgOut.write(Constants.encode(what + "\n")); //$NON-NLS-1$ + msgOut.write(Constants.encode(what + '\n')); } catch (IOException e) { // Ignore write failures. } @@ -1725,24 +1728,26 @@ public class UploadPack implements Closeable { if (commonBase.isEmpty() || multiAck != MultiAck.OFF) pckOut.writeString("NAK\n"); //$NON-NLS-1$ if (noDone && sentReady) { - pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + pckOut.writeString(PACKET_ACK + last.name() + '\n'); return true; } if (!biDirectionalPipe) return false; pckOut.flush(); - } else if (line.startsWith("have ") && line.length() == 45) { //$NON-NLS-1$ - peerHas.add(ObjectId.fromString(line.substring(5))); + } else if (line.startsWith(PACKET_HAVE) + && line.length() == PACKET_HAVE.length() + 40) { + peerHas.add(ObjectId + .fromString(line.substring(PACKET_HAVE.length()))); accumulator.haves++; - } else if (line.equals("done")) { //$NON-NLS-1$ + } else if (line.equals(PACKET_DONE)) { last = processHaveLines(peerHas, last, pckOut, accumulator, Option.NONE); if (commonBase.isEmpty()) pckOut.writeString("NAK\n"); //$NON-NLS-1$ else if (multiAck != MultiAck.OFF) - pckOut.writeString("ACK " + last.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + pckOut.writeString(PACKET_ACK + last.name() + '\n'); return true; @@ -1803,14 +1808,15 @@ public class UploadPack implements Closeable { // switch (multiAck) { case OFF: - if (commonBase.size() == 1) - out.writeString("ACK " + obj.name() + "\n"); //$NON-NLS-1$ //$NON-NLS-2$ + if (commonBase.size() == 1) { + out.writeString(PACKET_ACK + obj.name() + '\n'); + } break; case CONTINUE: - out.writeString("ACK " + obj.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$ + out.writeString(PACKET_ACK + obj.name() + " continue\n"); //$NON-NLS-1$ break; case DETAILED: - out.writeString("ACK " + obj.name() + " common\n"); //$NON-NLS-1$ //$NON-NLS-2$ + out.writeString(PACKET_ACK + obj.name() + " common\n"); //$NON-NLS-1$ break; } } @@ -1849,11 +1855,11 @@ public class UploadPack implements Closeable { break; case CONTINUE: out.writeString( - "ACK " + id.name() + " continue\n"); //$NON-NLS-1$ //$NON-NLS-2$ + PACKET_ACK + id.name() + " continue\n"); //$NON-NLS-1$ break; case DETAILED: out.writeString( - "ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ + PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$ readySent = true; break; } @@ -1866,7 +1872,7 @@ public class UploadPack implements Closeable { if (multiAck == MultiAck.DETAILED && !didOkToGiveUp && okToGiveUp()) { ObjectId id = peerHas.get(peerHas.size() - 1); - out.writeString("ACK " + id.name() + " ready\n"); //$NON-NLS-1$ //$NON-NLS-2$ + out.writeString(PACKET_ACK + id.name() + " ready\n"); //$NON-NLS-1$ readySent = true; } @@ -2476,6 +2482,24 @@ public class UploadPack implements Closeable { } } + private List<ObjectId> parseDeepenNots(List<String> deepenNots) + throws IOException { + List<ObjectId> result = new ArrayList<>(); + for (String s : deepenNots) { + if (ObjectId.isId(s)) { + result.add(ObjectId.fromString(s)); + } else { + Ref ref = findRef(s); + if (ref == null) { + throw new PackProtocolException(MessageFormat + .format(JGitText.get().invalidRefName, s)); + } + result.add(ref.getObjectId()); + } + } + return result; + } + private static class ResponseBufferedOutputStream extends OutputStream { private final OutputStream rawOut; @@ -2539,7 +2563,7 @@ public class UploadPack implements Closeable { @Override public void writeError(String message) throws IOException { new PacketLineOut(requireNonNull(rawOut)) - .writeString("ERR " + message + '\n'); //$NON-NLS-1$ + .writeString(PACKET_ERR + message + '\n'); } } } |