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;
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());
+ }
}
/*
- * 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
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;
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;
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(
/*
- * 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
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;
"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 {
/*
- * 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
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));
assertThat(request.getClientShallowCommits(),
hasOnlyObjectIds("28274d02c489f4c7e68153056e9061a46f62d7a0",
"145e683b229dcab9d0e2ccb01b386f9ecc17d29d"));
- assertThat(request.getDeepenNotRefs(),
+ assertThat(request.getDeepenNots(),
hasItems("a08595f76159b09d57553e37a5123f1091bb13e7"));
}
<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>
</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>
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.
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}
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.
/*
- * 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
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;
private TagOpt tagOption;
+ private Integer depth;
+
+ private Instant shallowSince;
+
+ private List<String> shallowExcludes = new ArrayList<>();
+
private enum FETCH_TYPE {
MULTIPLE_BRANCHES, ALL_BRANCHES, MIRROR
}
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();
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) {
/*
- * 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
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;
private String initialBranch;
+ private Integer depth;
+
+ private Instant deepenSince;
+
+ private List<String> shallowExcludes = new ArrayList<>();
+
+ private boolean unshallow;
+
/**
* Callback for status of fetch operation.
*
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()))
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);
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;
+ }
}
/***/ public String deleteRequiresZeroNewId;
/***/ public String deleteTagUnexpectedResult;
/***/ public String deletingNotSupported;
+ /***/ public String depthMustBeAt1;
+ /***/ public String depthWithUnshallow;
/***/ public String destinationIsNotAWildcard;
/***/ public String detachedHeadDetected;
/***/ public String diffToolNotGivenError;
/***/ public String nothingToFetch;
/***/ public String nothingToPush;
/***/ public String notMergedExceptionMessage;
+ /***/ public String notShallowedUnshallow;
/***/ public String noXMLParserAvailable;
/***/ public String objectAtHasBadZlibStream;
/***/ public String objectIsCorrupt;
/***/ public String serviceNotPermitted;
/***/ public String sha1CollisionDetected;
/***/ public String shallowCommitsAlreadyInitialized;
+ /***/ public String shallowNotSupported;
/***/ public String shallowPacksRequireDepthWalk;
/***/ public String shortCompressedStreamAt;
/***/ public String shortReadOfBlock;
+/*
+ * 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;
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;
/**
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());
};
}
+ @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;
/*
* 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
}
@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();
/*
- * 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
abstract FS getFS();
- abstract Set<ObjectId> getShallowCommits() throws IOException;
-
abstract void selectObjectRepresentation(PackWriter packer,
ObjectToPack otp, WindowCursor curs) throws IOException;
/*
- * 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
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;
}
@Override
- Set<ObjectId> getShallowCommits() throws IOException {
+ public Set<ObjectId> getShallowCommits() throws IOException {
if (shallowFile == null || !shallowFile.isFile())
return Collections.emptySet();
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
/*
* 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
*/
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
}
/*
- * 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
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;
*/
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.
*/
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;
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;
/**
* 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.
*/
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.
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);
}
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();
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;
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(
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);
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;
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--;
}
}
+ 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
/*
* 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
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) {
}
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);
}
/*
- * 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
final int deepenSince;
- final List<String> deepenNotRefs;
+ final List<String> deepenNots;
@Nullable
final String agent;
* 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
@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;
}
* @return refs received in "deepen-not" lines.
*/
@NonNull
- List<String> getDeepenNotRefs() {
- return deepenNotRefs;
+ List<String> getDeepenNots() {
+ return deepenNots;
}
/**
/*
- * 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
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;
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<>();
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
FetchV0Request build() {
return new FetchV0Request(wantIds, depth, clientShallowCommits,
- filterSpec, clientCaps, agent);
+ filterSpec, clientCaps, deepenSince, deepenNots, agent);
}
}
/*
- * 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
@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,
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;
final Set<ObjectId> clientShallowCommits = new HashSet<>();
- final List<String> deepenNotRefs = new ArrayList<>();
+ final List<String> deepenNots = new ArrayList<>();
final Set<String> clientCapabilities = new HashSet<>();
* @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;
}
*/
FetchV2Request build() {
return new FetchV2Request(peerHas, wantedRefs, wantIds,
- clientShallowCommits, deepenSince, deepenNotRefs,
+ clientShallowCommits, deepenSince, deepenNots,
depth, filterSpec, doneReceived, waitForDone, clientCapabilities,
agent, Collections.unmodifiableList(serverOptions),
sidebandAll,
/*
* 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
*/
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.
*
*/
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.
/*
- * 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
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)));
/*
- * 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
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);
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;
private PrePushHook prePush;
+ private Integer depth;
+
+ private Instant deepenSince;
+
+ private List<String> deepenNots = new ArrayList<>();
+
@Nullable
TransferConfig.ProtocolVersion protocol;
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.
*
* @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.
* @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
/*
- * 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
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;
// 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)
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();
}
if (sendPack) {
sendPack(accumulator, req, refs == null ? null : refs.values(),
- unshallowCommits, Collections.emptyList(), pckOut);
+ unshallowCommits, deepenNots, pckOut);
}
}
// 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.
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<>();
}
}
+ 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;