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