View Javadoc
1   /*
2    * Copyright (C) 2010, Garmin International
3    * Copyright (C) 2010, Matt Fischer <matt.fischer@garmin.com> and others
4    *
5    * This program and the accompanying materials are made available under the
6    * terms of the Eclipse Distribution License v. 1.0 which is available at
7    * https://www.eclipse.org/org/documents/edl-v10.php.
8    *
9    * SPDX-License-Identifier: BSD-3-Clause
10   */
11  
12  package org.eclipse.jgit.revwalk;
13  
14  import java.io.IOException;
15  
16  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
17  import org.eclipse.jgit.errors.MissingObjectException;
18  import org.eclipse.jgit.lib.ObjectId;
19  
20  /**
21   * Only produce commits which are below a specified depth.
22   *
23   * @see DepthWalk
24   */
25  class DepthGenerator extends Generator {
26  	private final FIFORevQueue pending;
27  
28  	private final int depth;
29  
30  	private final int deepenSince;
31  
32  	private final RevWalk walk;
33  
34  	/**
35  	 * Commits which used to be shallow in the client, but which are
36  	 * being extended as part of this fetch.  These commits should be
37  	 * returned to the caller as UNINTERESTING so that their blobs/trees
38  	 * can be marked appropriately in the pack writer.
39  	 */
40  	private final RevFlag UNSHALLOW;
41  
42  	/**
43  	 * Commits which the normal framework has marked as UNINTERESTING,
44  	 * but which we now care about again.  This happens if a client is
45  	 * extending a shallow checkout to become deeper--the new commits at
46  	 * the bottom of the graph need to be sent, even though they are
47  	 * below other commits which the client already has.
48  	 */
49  	private final RevFlag REINTERESTING;
50  
51  	/**
52  	 * Commits reachable from commits that the client specified using --shallow-exclude.
53  	 */
54  	private final RevFlag DEEPEN_NOT;
55  
56  	/**
57  	 * @param w
58  	 * @param s Parent generator
59  	 * @throws MissingObjectException
60  	 * @throws IncorrectObjectTypeException
61  	 * @throws IOException
62  	 */
63  	DepthGenerator(DepthWalk w, Generator s) throws MissingObjectException,
64  			IncorrectObjectTypeException, IOException {
65  		super(s.firstParent);
66  		pending = new FIFORevQueue(firstParent);
67  		walk = (RevWalk)w;
68  
69  		this.depth = w.getDepth();
70  		this.deepenSince = w.getDeepenSince();
71  		this.UNSHALLOW = w.getUnshallowFlag();
72  		this.REINTERESTING = w.getReinterestingFlag();
73  		this.DEEPEN_NOT = w.getDeepenNotFlag();
74  
75  		s.shareFreeList(pending);
76  
77  		// Begin by sucking out all of the source's commits, and
78  		// adding them to the pending queue
79  		FIFORevQueue unshallowCommits = new FIFORevQueue();
80  		for (;;) {
81  			RevCommit c = s.next();
82  			if (c == null)
83  				break;
84  			if (c.has(UNSHALLOW)) {
85  				unshallowCommits.add(c);
86  			} else if (((DepthWalk.Commit) c).getDepth() == 0) {
87  				pending.add(c);
88  			}
89  		}
90  		// Move unshallow commits to the front so that the REINTERESTING flag
91  		// carry over code is executed first.
92  		for (;;) {
93  			RevCommit c = unshallowCommits.next();
94  			if (c == null) {
95  				break;
96  			}
97  			pending.unpop(c);
98  		}
99  
100 		// Mark DEEPEN_NOT on all deepen-not commits and their ancestors.
101 		// TODO(jonathantanmy): This implementation is somewhat
102 		// inefficient in that any "deepen-not <ref>" in the request
103 		// results in all commits reachable from that ref being parsed
104 		// and marked, even if the commit topology is such that it is
105 		// not necessary.
106 		for (ObjectId oid : w.getDeepenNots()) {
107 			RevCommit c;
108 			try {
109 				c = walk.parseCommit(oid);
110 			} catch (IncorrectObjectTypeException notCommit) {
111 				// The C Git implementation silently tolerates
112 				// non-commits, so do the same here.
113 				continue;
114 			}
115 
116 			FIFORevQueue queue = new FIFORevQueue();
117 			queue.add(c);
118 			while ((c = queue.next()) != null) {
119 				if (c.has(DEEPEN_NOT)) {
120 					continue;
121 				}
122 
123 				walk.parseHeaders(c);
124 				c.add(DEEPEN_NOT);
125 				for (RevCommit p : c.getParents()) {
126 					queue.add(p);
127 				}
128 			}
129 		}
130 	}
131 
132 	@Override
133 	int outputType() {
134 		return pending.outputType() | HAS_UNINTERESTING;
135 	}
136 
137 	@Override
138 	void shareFreeList(BlockRevQueue q) {
139 		pending.shareFreeList(q);
140 	}
141 
142 	@Override
143 	RevCommit next() throws MissingObjectException,
144 			IncorrectObjectTypeException, IOException {
145 		// Perform a breadth-first descent into the commit graph,
146 		// marking depths as we go.  This means that if a commit is
147 		// reachable by more than one route, we are guaranteed to
148 		// arrive by the shortest route first.
149 		for (;;) {
150 			final DepthWalk.Commit c = (DepthWalk.Commit) pending.next();
151 			if (c == null)
152 				return null;
153 
154 			if ((c.flags & RevWalk.PARSED) == 0)
155 				c.parseHeaders(walk);
156 
157 			if (c.getCommitTime() < deepenSince) {
158 				continue;
159 			}
160 
161 			if (c.has(DEEPEN_NOT)) {
162 				continue;
163 			}
164 
165 			int newDepth = c.depth + 1;
166 
167 			for (int i = 0; i < c.parents.length; i++) {
168 				if (firstParent && i > 0) {
169 					break;
170 				}
171 				RevCommit p = c.parents[i];
172 				DepthWalk.Commit dp = (DepthWalk.Commit) p;
173 
174 				// If no depth has been assigned to this commit, assign
175 				// it now.  Since we arrive by the shortest route first,
176 				// this depth is guaranteed to be the smallest value that
177 				// any path could produce.
178 				if (dp.depth == -1) {
179 					boolean failsDeepenSince = false;
180 					if (deepenSince != 0) {
181 						if ((p.flags & RevWalk.PARSED) == 0) {
182 							p.parseHeaders(walk);
183 						}
184 						failsDeepenSince =
185 							p.getCommitTime() < deepenSince;
186 					}
187 
188 					dp.depth = newDepth;
189 
190 					// If the parent is not too deep and was not excluded, add
191 					// it to the queue so that we can produce it later
192 					if (newDepth <= depth && !failsDeepenSince &&
193 							!p.has(DEEPEN_NOT)) {
194 						pending.add(p);
195 					} else {
196 						dp.makesChildBoundary = true;
197 					}
198 				}
199 
200 				if (dp.makesChildBoundary) {
201 					c.isBoundary = true;
202 				}
203 
204 				// If the current commit has become unshallowed, everything
205 				// below us is new to the client.  Mark its parent as
206 				// re-interesting, and carry that flag downward to all
207 				// of its ancestors.
208 				if(c.has(UNSHALLOW) || c.has(REINTERESTING)) {
209 					p.add(REINTERESTING);
210 					p.flags &= ~RevWalk.UNINTERESTING;
211 				}
212 			}
213 
214 			boolean produce = true;
215 
216 			// Unshallow commits are uninteresting, but still need to be sent
217 			// up to the PackWriter so that it will exclude objects correctly.
218 			// All other uninteresting commits should be omitted.
219 			if ((c.flags & RevWalk.UNINTERESTING) != 0 && !c.has(UNSHALLOW))
220 				produce = false;
221 
222 			if (c.getCommitTime() < deepenSince) {
223 				produce = false;
224 			}
225 
226 			if (produce)
227 				return c;
228 		}
229 	}
230 }