You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IterativeConnectivityChecker.java 4.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152
  1. /*
  2. * Copyright (c) 2019, Google LLC and others
  3. *
  4. * This program and the accompanying materials are made available under the
  5. * terms of the Eclipse Distribution License v. 1.0 which is available at
  6. * http://www.eclipse.org/org/documents/edl-v10.php.
  7. *
  8. * SPDX-License-Identifier: BSD-3-Clause
  9. */
  10. package org.eclipse.jgit.internal.transport.connectivity;
  11. import static java.util.stream.Collectors.toList;
  12. import java.io.IOException;
  13. import java.util.ArrayDeque;
  14. import java.util.Arrays;
  15. import java.util.Collections;
  16. import java.util.HashSet;
  17. import java.util.List;
  18. import java.util.Queue;
  19. import java.util.Set;
  20. import java.util.stream.Stream;
  21. import org.eclipse.jgit.errors.MissingObjectException;
  22. import org.eclipse.jgit.lib.ObjectId;
  23. import org.eclipse.jgit.lib.ProgressMonitor;
  24. import org.eclipse.jgit.revwalk.RevCommit;
  25. import org.eclipse.jgit.revwalk.RevObject;
  26. import org.eclipse.jgit.revwalk.RevWalk;
  27. import org.eclipse.jgit.transport.ConnectivityChecker;
  28. import org.eclipse.jgit.transport.ReceiveCommand;
  29. /**
  30. * Implementation of connectivity checker which tries to do check with smaller
  31. * set of references first and if it fails will fall back to check against all
  32. * advertised references.
  33. *
  34. * This is useful for big repos with enormous number of references.
  35. */
  36. public class IterativeConnectivityChecker implements ConnectivityChecker {
  37. private static final int MAXIMUM_PARENTS_TO_CHECK = 128;
  38. private final ConnectivityChecker delegate;
  39. private Set<ObjectId> forcedHaves = Collections.emptySet();
  40. /**
  41. * @param delegate
  42. * Delegate checker which will be called for actual checks.
  43. */
  44. public IterativeConnectivityChecker(ConnectivityChecker delegate) {
  45. this.delegate = delegate;
  46. }
  47. @Override
  48. public void checkConnectivity(ConnectivityCheckInfo connectivityCheckInfo,
  49. Set<ObjectId> advertisedHaves, ProgressMonitor pm)
  50. throws MissingObjectException, IOException {
  51. try {
  52. Set<ObjectId> newRefs = new HashSet<>();
  53. Set<ObjectId> expectedParents = new HashSet<>();
  54. getAllObjectIds(connectivityCheckInfo.getCommands())
  55. .forEach(oid -> {
  56. if (advertisedHaves.contains(oid)) {
  57. expectedParents.add(oid);
  58. } else {
  59. newRefs.add(oid);
  60. }
  61. });
  62. if (!newRefs.isEmpty()) {
  63. expectedParents.addAll(extractAdvertisedParentCommits(newRefs,
  64. advertisedHaves, connectivityCheckInfo.getWalk()));
  65. }
  66. expectedParents.addAll(forcedHaves);
  67. if (!expectedParents.isEmpty()) {
  68. delegate.checkConnectivity(connectivityCheckInfo,
  69. expectedParents, pm);
  70. return;
  71. }
  72. } catch (MissingObjectException e) {
  73. // This is fine, retry with all haves.
  74. }
  75. delegate.checkConnectivity(connectivityCheckInfo, advertisedHaves, pm);
  76. }
  77. private static Stream<ObjectId> getAllObjectIds(
  78. List<ReceiveCommand> commands) {
  79. return commands.stream().flatMap(cmd -> {
  80. if (cmd.getType() == ReceiveCommand.Type.UPDATE || cmd
  81. .getType() == ReceiveCommand.Type.UPDATE_NONFASTFORWARD) {
  82. return Stream.of(cmd.getOldId(), cmd.getNewId());
  83. } else if (cmd.getType() == ReceiveCommand.Type.CREATE) {
  84. return Stream.of(cmd.getNewId());
  85. }
  86. return Stream.of();
  87. });
  88. }
  89. /**
  90. * Sets additional haves that client can depend on (e.g. gerrit changes).
  91. *
  92. * @param forcedHaves
  93. * Haves server expects client to depend on.
  94. */
  95. public void setForcedHaves(Set<ObjectId> forcedHaves) {
  96. this.forcedHaves = Collections.unmodifiableSet(forcedHaves);
  97. }
  98. private static Set<ObjectId> extractAdvertisedParentCommits(
  99. Set<ObjectId> newRefs, Set<ObjectId> advertisedHaves, RevWalk rw)
  100. throws MissingObjectException, IOException {
  101. Set<ObjectId> advertisedParents = new HashSet<>();
  102. for (ObjectId newRef : newRefs) {
  103. RevObject object = rw.parseAny(newRef);
  104. if (object instanceof RevCommit) {
  105. int numberOfParentsToCheck = 0;
  106. Queue<RevCommit> parents = new ArrayDeque<>(
  107. MAXIMUM_PARENTS_TO_CHECK);
  108. parents.addAll(
  109. parseParents(((RevCommit) object).getParents(), rw));
  110. // Looking through a chain of ancestors handles the case where a
  111. // series of commits is sent in a single push for a new branch.
  112. while (!parents.isEmpty()) {
  113. RevCommit parentCommit = parents.poll();
  114. if (advertisedHaves.contains(parentCommit.getId())) {
  115. advertisedParents.add(parentCommit.getId());
  116. } else if (numberOfParentsToCheck < MAXIMUM_PARENTS_TO_CHECK) {
  117. RevCommit[] grandParents = parentCommit.getParents();
  118. numberOfParentsToCheck += grandParents.length;
  119. parents.addAll(parseParents(grandParents, rw));
  120. }
  121. }
  122. }
  123. }
  124. return advertisedParents;
  125. }
  126. private static List<RevCommit> parseParents(RevCommit[] parents,
  127. RevWalk rw) {
  128. return Arrays.stream(parents).map((commit) -> {
  129. try {
  130. return rw.parseCommit(commit);
  131. } catch (Exception e) {
  132. throw new RuntimeException(e);
  133. }
  134. }).collect(toList());
  135. }
  136. }