View Javadoc
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  
11  package org.eclipse.jgit.transport.internal;
12  
13  import java.io.IOException;
14  import java.util.Set;
15  
16  import org.eclipse.jgit.errors.MissingObjectException;
17  import org.eclipse.jgit.internal.JGitText;
18  import org.eclipse.jgit.lib.Constants;
19  import org.eclipse.jgit.lib.ObjectId;
20  import org.eclipse.jgit.lib.ObjectIdSubclassMap;
21  import org.eclipse.jgit.lib.ProgressMonitor;
22  import org.eclipse.jgit.revwalk.ObjectWalk;
23  import org.eclipse.jgit.revwalk.RevBlob;
24  import org.eclipse.jgit.revwalk.RevCommit;
25  import org.eclipse.jgit.revwalk.RevFlag;
26  import org.eclipse.jgit.revwalk.RevObject;
27  import org.eclipse.jgit.revwalk.RevSort;
28  import org.eclipse.jgit.revwalk.RevTree;
29  import org.eclipse.jgit.transport.ConnectivityChecker;
30  import org.eclipse.jgit.transport.ReceiveCommand;
31  import org.eclipse.jgit.transport.ReceiveCommand.Result;
32  
33  /**
34   * A connectivity checker that uses the entire reference database to perform
35   * reachability checks when checking the connectivity of objects. If
36   * info.isCheckObjects() is set it will also check that objects referenced by
37   * deltas are either provided or reachable as well.
38   */
39  public final class FullConnectivityChecker implements ConnectivityChecker {
40  	@Override
41  	public void checkConnectivity(ConnectivityCheckInfo connectivityCheckInfo,
42  			Set<ObjectId> haves, ProgressMonitor pm)
43  			throws MissingObjectException, IOException {
44  		pm.beginTask(JGitText.get().countingObjects,
45  				ProgressMonitor.UNKNOWN);
46  		try (ObjectWalkObjectWalk.html#ObjectWalk">ObjectWalk ow = new ObjectWalk(connectivityCheckInfo.getRepository())) {
47  			if (!markStartAndKnownNodes(connectivityCheckInfo, ow, haves,
48  					pm)) {
49  				return;
50  			}
51  			checkCommitTree(connectivityCheckInfo, ow, pm);
52  			checkObjects(connectivityCheckInfo, ow, pm);
53  		} finally {
54  			pm.endTask();
55  		}
56  	}
57  
58  	/**
59  	 * @param connectivityCheckInfo
60  	 *            Source for connectivity check.
61  	 * @param ow
62  	 *            Walk which can also check blobs.
63  	 * @param haves
64  	 *            Set of references known for client.
65  	 * @param pm
66  	 *            Monitor to publish progress to.
67  	 * @return true if at least one new node was marked.
68  	 * @throws IOException
69  	 *             an error occurred during connectivity checking.
70  	 */
71  	private boolean markStartAndKnownNodes(
72  			ConnectivityCheckInfo connectivityCheckInfo,
73  			ObjectWalk ow,
74  			Set<ObjectId> haves, ProgressMonitor pm)
75  			throws IOException {
76  		boolean markTrees = connectivityCheckInfo
77  				.isCheckObjects()
78  				&& !connectivityCheckInfo.getParser().getBaseObjectIds()
79  						.isEmpty();
80  		if (connectivityCheckInfo.isCheckObjects()) {
81  			ow.sort(RevSort.TOPO);
82  			if (!connectivityCheckInfo.getParser().getBaseObjectIds()
83  					.isEmpty()) {
84  				ow.sort(RevSort.BOUNDARY, true);
85  			}
86  		}
87  		boolean hasInteresting = false;
88  
89  		for (ReceiveCommand cmd : connectivityCheckInfo.getCommands()) {
90  			if (cmd.getResult() != Result.NOT_ATTEMPTED) {
91  				continue;
92  			}
93  			if (cmd.getType() == ReceiveCommand.Type.DELETE) {
94  				continue;
95  			}
96  			if (haves.contains(cmd.getNewId())) {
97  				continue;
98  			}
99  			ow.markStart(ow.parseAny(cmd.getNewId()));
100 			pm.update(1);
101 			hasInteresting = true;
102 		}
103 		if (!hasInteresting) {
104 			return false;
105 		}
106 		for (ObjectId have : haves) {
107 			RevObject o = ow.parseAny(have);
108 			ow.markUninteresting(o);
109 			pm.update(1);
110 
111 			if (markTrees) {
112 				o = ow.peel(o);
113 				if (o instanceof RevCommit) {
114 					o = ((RevCommit) o).getTree();
115 				}
116 				if (o instanceof RevTree) {
117 					ow.markUninteresting(o);
118 				}
119 			}
120 		}
121 		return true;
122 	}
123 
124 	/**
125 	 * @param connectivityCheckInfo
126 	 *            Source for connectivity check.
127 	 * @param ow
128 	 *            Walk which can also check blobs.
129 	 * @param pm
130 	 *            Monitor to publish progress to.
131 	 * @throws IOException
132 	 *             an error occurred during connectivity checking.
133 	 */
134 	private void checkCommitTree(ConnectivityCheckInfo connectivityCheckInfo,
135 			ObjectWalk ow,
136 			ProgressMonitor pm) throws IOException {
137 		RevCommit c;
138 		ObjectIdSubclassMap<ObjectId> newObjectIds = connectivityCheckInfo
139 				.getParser()
140 				.getNewObjectIds();
141 		while ((c = ow.next()) != null) {
142 			pm.update(1);
143 			if (connectivityCheckInfo.isCheckObjects()
144 					&& !c.has(RevFlag.UNINTERESTING)
145 					&& !newObjectIds.contains(c)) {
146 				throw new MissingObjectException(c, Constants.TYPE_COMMIT);
147 			}
148 		}
149 	}
150 
151 	/**
152 	 * @param connectivityCheckInfo
153 	 *            Source for connectivity check.
154 	 * @param ow
155 	 *            Walk which can also check blobs.
156 	 * @param pm
157 	 *            Monitor to publish progress to.
158 	 * @throws IOException
159 	 *             an error occurred during connectivity checking.
160 	 *
161 	 */
162 	private void checkObjects(ConnectivityCheckInfo connectivityCheckInfo,
163 			ObjectWalk ow,
164 			ProgressMonitor pm) throws IOException {
165 		RevObject o;
166 		ObjectIdSubclassMap<ObjectId> newObjectIds = connectivityCheckInfo
167 				.getParser()
168 				.getNewObjectIds();
169 
170 		while ((o = ow.nextObject()) != null) {
171 			pm.update(1);
172 			if (o.has(RevFlag.UNINTERESTING)) {
173 				continue;
174 			}
175 
176 			if (connectivityCheckInfo.isCheckObjects()) {
177 				if (newObjectIds.contains(o)) {
178 					continue;
179 				}
180 				throw new MissingObjectException(o, o.getType());
181 
182 			}
183 
184 			if (o instanceof RevBlob
185 					&& !connectivityCheckInfo.getRepository().getObjectDatabase()
186 							.has(o)) {
187 				throw new MissingObjectException(o, Constants.TYPE_BLOB);
188 			}
189 		}
190 
191 		if (connectivityCheckInfo.isCheckObjects()) {
192 			for (ObjectId id : connectivityCheckInfo.getParser()
193 					.getBaseObjectIds()) {
194 				o = ow.parseAny(id);
195 				if (!o.has(RevFlag.UNINTERESTING)) {
196 					throw new MissingObjectException(o, o.getType());
197 				}
198 			}
199 		}
200 	}
201 }