View Javadoc
1   /*
2    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 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    * https://www.eclipse.org/org/documents/edl-v10.php.
7    *
8    * SPDX-License-Identifier: BSD-3-Clause
9    */
10  
11  package org.eclipse.jgit.revwalk;
12  
13  import java.io.IOException;
14  import java.text.MessageFormat;
15  import java.util.LinkedList;
16  
17  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
18  import org.eclipse.jgit.errors.MissingObjectException;
19  import org.eclipse.jgit.internal.JGitText;
20  
21  /**
22   * Computes the merge base(s) of the starting commits.
23   * <p>
24   * This generator is selected if the RevFilter is only
25   * {@link org.eclipse.jgit.revwalk.filter.RevFilter#MERGE_BASE}.
26   * <p>
27   * To compute the merge base we assign a temporary flag to each of the starting
28   * commits. The maximum number of starting commits is bounded by the number of
29   * free flags available in the RevWalk when the generator is initialized. These
30   * flags will be automatically released on the next reset of the RevWalk, but
31   * not until then, as they are assigned to commits throughout the history.
32   * <p>
33   * Several internal flags are reused here for a different purpose, but this
34   * should not have any impact as this generator should be run alone, and without
35   * any other generators wrapped around it.
36   */
37  class MergeBaseGenerator extends Generator {
38  	private static final int PARSED = RevWalk.PARSED;
39  	private static final int IN_PENDING = RevWalk.SEEN;
40  	private static final int POPPED = RevWalk.TEMP_MARK;
41  	private static final int MERGE_BASE = RevWalk.REWRITE;
42  
43  	private final RevWalk walker;
44  	private final DateRevQueue pending;
45  
46  	private int branchMask;
47  	private int recarryTest;
48  	private int recarryMask;
49  	private int mergeBaseAncestor = -1;
50  	private LinkedList<RevCommit> ret = new LinkedList<>();
51  
52  	private CarryStack stack;
53  
54  	MergeBaseGenerator(RevWalk w) {
55  		super(w.isFirstParent());
56  		walker = w;
57  		pending = new DateRevQueue(firstParent);
58  	}
59  
60  	void init(AbstractRevQueue p) throws IOException {
61  		try {
62  			for (;;) {
63  				final RevCommit c = p.next();
64  				if (c == null)
65  					break;
66  				add(c);
67  			}
68  			// Setup the condition used by carryOntoOne to detect a late
69  			// merge base and produce it on the next round.
70  			//
71  			recarryTest = branchMask | POPPED;
72  			recarryMask = branchMask | POPPED | MERGE_BASE;
73  			mergeBaseAncestor = walker.allocFlag();
74  
75  			for (;;) {
76  				RevCommit c = _next();
77  				if (c == null) {
78  					break;
79  				}
80  				ret.add(c);
81  			}
82  		} finally {
83  			// Always free the flags immediately. This ensures the flags
84  			// will be available for reuse when the walk resets.
85  			//
86  			walker.freeFlag(branchMask | mergeBaseAncestor);
87  		}
88  	}
89  
90  	private void add(RevCommit c) {
91  		final int flag = walker.allocFlag();
92  		branchMask |= flag;
93  		if ((c.flags & branchMask) != 0) {
94  			// This should never happen. RevWalk ensures we get a
95  			// commit admitted to the initial queue only once. If
96  			// we see this marks aren't correctly erased.
97  			//
98  			throw new IllegalStateException(MessageFormat.format(JGitText.get().staleRevFlagsOn, c.name()));
99  		}
100 		c.flags |= flag;
101 		pending.add(c);
102 	}
103 
104 	@Override
105 	int outputType() {
106 		return 0;
107 	}
108 
109 	private RevCommit _next() throws MissingObjectException,
110 			IncorrectObjectTypeException, IOException {
111 		for (;;) {
112 			final RevCommit c = pending.next();
113 			if (c == null) {
114 				return null;
115 			}
116 
117 			for (RevCommit p : c.parents) {
118 				if ((p.flags & IN_PENDING) != 0)
119 					continue;
120 				if ((p.flags & PARSED) == 0)
121 					p.parseHeaders(walker);
122 				p.flags |= IN_PENDING;
123 				pending.add(p);
124 			}
125 
126 			int carry = c.flags & branchMask;
127 			boolean mb = carry == branchMask;
128 			if (mb) {
129 				// If we are a merge base make sure our ancestors are
130 				// also flagged as being popped, so that they do not
131 				// generate to the caller.
132 				//
133 				carry |= MERGE_BASE | mergeBaseAncestor;
134 			}
135 			carryOntoHistory(c, carry);
136 
137 			if ((c.flags & MERGE_BASE) != 0) {
138 				// This commit is an ancestor of a merge base we already
139 				// popped back to the caller. If everyone in pending is
140 				// that way we are done traversing; if not we just need
141 				// to move to the next available commit and try again.
142 				//
143 				if (pending.everbodyHasFlag(MERGE_BASE))
144 					return null;
145 				continue;
146 			}
147 			c.flags |= POPPED;
148 
149 			if (mb) {
150 				c.flags |= MERGE_BASE;
151 				return c;
152 			}
153 		}
154 	}
155 
156 	@Override
157 	RevCommit next() throws MissingObjectException,
158 			IncorrectObjectTypeException, IOException {
159 		while (!ret.isEmpty()) {
160 			RevCommit commit = ret.remove();
161 			if ((commit.flags & mergeBaseAncestor) == 0) {
162 				return commit;
163 			}
164 		}
165 		return null;
166 	}
167 
168 	private void carryOntoHistory(RevCommit c, int carry) {
169 		stack = null;
170 		for (;;) {
171 			carryOntoHistoryInnerLoop(c, carry);
172 			if (stack == null) {
173 				break;
174 			}
175 			c = stack.c;
176 			carry = stack.carry;
177 			stack = stack.prev;
178 		}
179 	}
180 
181 	private void carryOntoHistoryInnerLoop(RevCommit c, int carry) {
182 		for (;;) {
183 			RevCommit[] parents = c.parents;
184 			if (parents == null || parents.length == 0) {
185 				break;
186 			}
187 
188 			int e = parents.length - 1;
189 			for (int i = 0; i < e; i++) {
190 				RevCommit p = parents[i];
191 				if (carryOntoOne(p, carry) == CONTINUE) {
192 					// Walking p will be required, buffer p on stack.
193 					stack = new CarryStack(stack, p, carry);
194 				}
195 				// For other results from carryOntoOne:
196 				// HAVE_ALL: p has all bits, do nothing to skip that path.
197 				// CONTINUE_ON_STACK: callee pushed StackElement for p.
198 			}
199 
200 			c = parents[e];
201 			if (carryOntoOne(c, carry) != CONTINUE) {
202 				break;
203 			}
204 		}
205 	}
206 
207 	private static final int CONTINUE = 0;
208 	private static final int HAVE_ALL = 1;
209 	private static final int CONTINUE_ON_STACK = 2;
210 
211 	private int carryOntoOne(RevCommit p, int carry) {
212 		// If we already had all carried flags, our parents do too.
213 		// Return HAVE_ALL to stop caller from running down this leg
214 		// of the revision graph any further.
215 		//
216 		// Otherwise return CONTINUE to ask the caller to walk history.
217 		int rc = (p.flags & carry) == carry ? HAVE_ALL : CONTINUE;
218 		p.flags |= carry;
219 
220 		if ((p.flags & recarryMask) == recarryTest) {
221 			// We were popped without being a merge base, but we just got
222 			// voted to be one. Inject ourselves back at the front of the
223 			// pending queue and tell all of our ancestors they are within
224 			// the merge base now.
225 			p.flags &= ~POPPED;
226 			pending.add(p);
227 			stack = new CarryStack(stack, p, branchMask | MERGE_BASE);
228 			return CONTINUE_ON_STACK;
229 		}
230 		return rc;
231 	}
232 
233 	private static class CarryStack {
234 		final CarryStack prev;
235 		final RevCommit c;
236 		final int carry;
237 
238 		CarryStack(CarryStack prev, RevCommit c, int carry) {
239 			this.prev = prev;
240 			this.c = c;
241 			this.carry = carry;
242 		}
243 	}
244 }