]> source.dussan.org Git - jgit.git/commitdiff
Fetch: add support for shallow 29/193329/14
authorRobin Müller <eclipse@mail.coder-hugo.de>
Fri, 13 May 2022 11:46:13 +0000 (13:46 +0200)
committerThomas Wolf <twolf@apache.org>
Sun, 31 Jul 2022 12:08:47 +0000 (14:08 +0200)
This adds support for shallow cloning. The CloneCommand and the
FetchCommand now have the new methods setDepth, setShallowSince and
addShallowExclude to tell the server that the client doesn't want to
download the complete history.

Bug: 475615
Change-Id: Ic80fb6efb5474543ae59be590ebe385bec21cc0d

25 files changed:
org.eclipse.jgit.http.test/tst/org/eclipse/jgit/http/test/HttpClientTests.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/api/CloneCommandTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV0ParserTest.java
org.eclipse.jgit.test/tst/org/eclipse/jgit/transport/ProtocolV2ParserTest.java
org.eclipse.jgit/.settings/.api_filters
org.eclipse.jgit/resources/org/eclipse/jgit/internal/JGitText.properties
org.eclipse.jgit/src/org/eclipse/jgit/api/CloneCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/api/FetchCommand.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/JGitText.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/dfs/InMemoryRepository.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/CachedObjectDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/FileObjectDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/internal/storage/file/ObjectDirectory.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/Constants.java
org.eclipse.jgit/src/org/eclipse/jgit/lib/ObjectDatabase.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/BasePackFetchConnection.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchProcess.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchRequest.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV0Request.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/FetchV2Request.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/GitProtocolConstants.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV0Parser.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/ProtocolV2Parser.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/Transport.java
org.eclipse.jgit/src/org/eclipse/jgit/transport/UploadPack.java

index 3438c52c8d3cf0b70efc36f29829f5ff9f3e4446..6b067273a94bf6bb04f29de9082812de9fb04b16 100644 (file)
@@ -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());
+    }
 }
index c928d2ad22d0e75668b8a2ae6f6e90ef6ac7a206..6053c8c568bee5287d28fd66a4f693b7348f19d4 100644 (file)
@@ -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(
index c0db83a8200b9bc2bd080d27f16e339e86f22730..b2a4af30ab16638891c454831f6b8e7980550ebe 100644 (file)
@@ -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;
 
@@ -132,6 +133,42 @@ public class ProtocolV0ParserTest {
                                                "f900c8326a43303685c46b279b9f70411bff1a4b"));
        }
 
+       @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 {
index 837bdce919a985dc75e4f39facb1451a843bee21..167b5b72c6bf464a54e8e97358ca526cda89f8a4 100644 (file)
@@ -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"));
        }
 
index 6eb8bd3732b7ebee03581fd23bc2f15ca4c9ad46..8aa84f3ac1b5b1db23894bb8c4e927580d867074 100644 (file)
         <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&lt;ObjectId&gt;)"/>
             </message_arguments>
         </filter>
     </resource>
             </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>
index 66adad51514e290e9bedd29ecdb8b964244d07f9..84a7a80d6258a33f078335efb2fa675dc93e3763 100644 (file)
@@ -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}
@@ -662,6 +665,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.
index 3aa711455b2ac577ad49a199ab3e1f150988fe14..1f979a938c207d22fe569729e847cf73b9151e2d 100644 (file)
@@ -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) {
index 90c1515b06340a48c054dedd5478991d10be5d6b..84bee3620478f75f36330851763e4c3aa2db709b 100644 (file)
@@ -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;
+       }
 }
index efdb8e42e398cedb58f6b769dff76f7d560d1f86..551a5a8a9d4d171cf7958a13b42e7e48796b969f 100644 (file)
@@ -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;
@@ -690,6 +693,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;
index 99da22239503925a80c045b5654c202b2da26626..5a8207ed01af0ff00efad50f1637a14f55ec3cca 100644 (file)
@@ -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());
@@ -166,6 +179,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;
index 094fdc1559ae224334ef03b4d82a4c78518d93d9..9272bf3f598b7c6c8e6bd2f3c8c643e606279c47 100644 (file)
@@ -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();
index 01dd27d9fbcf72b65e2eaae815e70a603c179fee..e97ed393a14585ae11b29ebfceecb56691ba58ee 100644 (file)
@@ -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;
 
index 06c8cad3acd2d8d4c1e8756d956a388beae5a43d..1a1d31a6321ef0b5a34b799a2fa4caa1887a59ea 100644 (file)
@@ -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,7 +561,7 @@ 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();
 
@@ -587,6 +588,43 @@ public class ObjectDirectory extends FileObjectDatabase {
                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);
+               }
+       }
+
        void closeAllPackHandles(File packFile) {
                // if the packfile already exists (because we are rewriting a
                // packfile for the same set of objects maybe with different
index cf2e69dbb52d6ba2888042d0bd3ea62eea92e2dc..30a0074195daf01d5915a81ade280317422f7966 100644 (file)
@@ -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
        }
index 70009cba352e377e3496a1bcf8cff34c3572746d..1c0f436090eeac6c5923dcfce8daec173a4ad1c0 100644 (file)
@@ -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;
@@ -71,6 +72,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.
         */
index 3f167ccce24bac858c21a5eb91b726642cb23d21..2aecf63ada6c0781968efc3a4c15aabee25d3aa7 100644 (file)
@@ -16,12 +16,14 @@ 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 +34,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 +79,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 +213,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 +255,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 +397,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 +444,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;
 
@@ -458,7 +486,17 @@ public abstract class BasePackFetchConnection extends BasePackConnection
                if (sentDone && line.startsWith("ERR ")) { //$NON-NLS-1$
                        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, "0001", line)); //$NON-NLS-1$
+                       }
+                       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(
@@ -672,16 +710,19 @@ 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);
@@ -773,8 +814,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;
@@ -911,6 +952,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, "0000", line)); //$NON-NLS-1$
+                       }
+               }
+
                READ_RESULT: while (resultsPending > 0 || multiAck != MultiAck.OFF) {
                        final AckNackResult anr = pckIn.readACK(ackId);
                        resultsPending--;
@@ -1025,6 +1074,50 @@ public abstract class BasePackFetchConnection extends BasePackConnection
                }
        }
 
+       private void sendShallow(Set<ObjectId> shallowCommits, PacketLineOut output) throws IOException {
+               for (ObjectId shallowCommit : shallowCommits) {
+                       output.writeString("shallow " + shallowCommit.name()); //$NON-NLS-1$
+               }
+
+               if (depth != null) {
+                       output.writeString("deepen " + depth); //$NON-NLS-1$
+               }
+
+               if (deepenSince != null) {
+                       output.writeString("deepen-since " + deepenSince.getEpochSecond()); //$NON-NLS-1$
+               }
+
+               if (deepenNots != null) {
+                       for (String deepenNotRef : deepenNots) {
+                               output.writeString("deepen-not " + deepenNotRef); //$NON-NLS-1$
+                       }
+               }
+       }
+
+       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("shallow ")) { //$NON-NLS-1$
+                               newShallowCommits.add(ObjectId
+                                               .fromString(line.substring("shallow ".length()))); //$NON-NLS-1$
+                       } else if (line.startsWith("unshallow ")) { //$NON-NLS-1$
+                               ObjectId unshallow = ObjectId
+                                               .fromString(line.substring("unshallow ".length())); //$NON-NLS-1$
+                               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
index 7bface49d9d03add74ef776c43f715f1f8ef469f..28c3b6a0fa4479802a968a9295b1efc34f19d268 100644 (file)
@@ -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);
                }
 
index 9ebc722ffe892cff07fd60b9daf72311730870bd..0663c5141cab3ed7f55c201bfc27892afefbb808 100644 (file)
@@ -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;
        }
 
        /**
index 91adb5e6ac4c9fd17f9d2a9972d80cdb8f4b01be..4decb7951356b44a974757642d7b16bf48ff4ccd 100644 (file)
@@ -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<>();
@@ -67,6 +73,50 @@ final class FetchV0Request extends FetchRequest {
                        return this;
                }
 
+               /**
+                * @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
@@ -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);
                }
 
        }
index 50fb9d22620786203d5d9fc11931f192f904d36a..446a3bcc798d3c3f945b7bc05580bf0307e2da75 100644 (file)
@@ -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,
index aaa9308ac3c04481f66e0193281702ae91870be1..24ea552ba6ebf8c7e7b3bfb71570ee3eeaf6a86c 100644 (file)
@@ -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
@@ -233,6 +233,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.
         *
@@ -307,6 +314,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.
index f8c51c180f63918b55dd1a0b49d9df1be58ed52a..4ddcb994198e9895c81b8e5f850b226bff2601f9 100644 (file)
@@ -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
@@ -77,10 +77,42 @@ final class ProtocolV0Parser {
                                                        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("deepen-not ")) { //$NON-NLS-1$
+                               reqBuilder.addDeepenNot(line.substring(11));
+                               if (reqBuilder.getDepth() != 0) {
+                                       throw new PackProtocolException(
+                                                       JGitText.get().deepenNotWithDeepen);
+                               }
+                               continue;
+                       }
+
+                       if (line.startsWith("deepen-since ")) { //$NON-NLS-1$
+                               // TODO: timestamps should be long
+                               int ts = Integer.parseInt(line.substring(13));
+                               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("shallow ")) { //$NON-NLS-1$
                                reqBuilder.addClientShallowCommit(
                                                ObjectId.fromString(line.substring(8)));
index 6cec4b9a3fae068ee9ee37ae75fdbef79d456bb6..e502831a2bb639ef1833c5850ec19bc1857f685f 100644 (file)
@@ -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
@@ -149,13 +149,13 @@ 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));
+                               reqBuilder.addDeepenNot(line2.substring(11));
                                if (reqBuilder.getDepth() != 0) {
                                        throw new PackProtocolException(
                                                        JGitText.get().deepenNotWithDeepen);
index 3222d6330c05d75d8165cf605f57ea10a45beed0..7cea99847448ecba0b7a3ee1cbe19e620c579d3a 100644 (file)
@@ -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
index dcd806a3da04919fbabc7b2ac308c4611d6ff037..409161d58b40a7bc89d3dab7369cd26319b0673d 100644 (file)
@@ -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;
@@ -1033,6 +1034,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 +1073,14 @@ 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$
                                }, unshallow -> {
                                        pckOut.writeString("unshallow " + unshallow.name() + '\n'); //$NON-NLS-1$
                                        unshallowCommits.add(unshallow);
-                               }, Collections.emptyList());
+                               }, deepenNots);
                                pckOut.end();
                        }
 
@@ -1109,7 +1112,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 +1191,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 +1201,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<>();
 
@@ -2476,6 +2471,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;