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  
15  import org.eclipse.jgit.errors.IncorrectObjectTypeException;
16  import org.eclipse.jgit.errors.MissingObjectException;
17  
18  /**
19   * Replaces a RevCommit's parents until not colored with REWRITE.
20   * <p>
21   * Before a RevCommit is returned to the caller its parents are updated to
22   * create a dense DAG. Instead of reporting the actual parents as recorded when
23   * the commit was created the returned commit will reflect the next closest
24   * commit that matched the revision walker's filters.
25   * <p>
26   * This generator is the second phase of a path limited revision walk and
27   * assumes it is receiving RevCommits from {@link TreeRevFilter}.
28   *
29   * @see TreeRevFilter
30   */
31  class RewriteGenerator extends Generator {
32  	private static final int REWRITE = RevWalk.REWRITE;
33  
34  	/** For {@link #cleanup(RevCommit[])} to remove duplicate parents. */
35  	private static final int DUPLICATE = RevWalk.TEMP_MARK;
36  
37  	private final Generator source;
38  
39  	private final FIFORevQueue pending;
40  
41  	RewriteGenerator(Generator s) {
42  		super(s.firstParent);
43  		source = s;
44  		pending = new FIFORevQueue(s.firstParent);
45  	}
46  
47  	@Override
48  	void shareFreeList(BlockRevQueue q) {
49  		source.shareFreeList(q);
50  	}
51  
52  	@Override
53  	int outputType() {
54  		return source.outputType() & ~NEEDS_REWRITE;
55  	}
56  
57  	@SuppressWarnings("ReferenceEquality")
58  	@Override
59  	RevCommit next() throws MissingObjectException,
60  			IncorrectObjectTypeException, IOException {
61  		RevCommit c = pending.next();
62  
63  		if (c == null) {
64  			c = source.next();
65  			if (c == null) {
66  				// We are done: Both the source generator and our internal list
67  				// are completely exhausted.
68  				return null;
69  			}
70  		}
71  
72  		applyFilterToParents(c);
73  
74  		boolean rewrote = false;
75  		final RevCommit[] pList = c.getParents();
76  		final int nParents = pList.length;
77  		for (int i = 0; i < nParents; i++) {
78  			final RevCommit oldp = pList[i];
79  			final RevCommit newp = rewrite(oldp);
80  			if (firstParent) {
81  				if (newp == null) {
82  					c.parents = RevCommit.NO_PARENTS;
83  				} else {
84  					c.parents = new RevCommit[] { newp };
85  				}
86  				return c;
87  			}
88  			if (oldp != newp) {
89  				pList[i] = newp;
90  				rewrote = true;
91  			}
92  		}
93  		if (rewrote) {
94  			c.parents = cleanup(pList);
95  		}
96  		return c;
97  	}
98  
99  	/**
100 	 * Makes sure that the {@link TreeRevFilter} has been applied to all parents
101 	 * of this commit by the previous {@link PendingGenerator}.
102 	 *
103 	 * @param c
104 	 * @throws MissingObjectException
105 	 * @throws IncorrectObjectTypeException
106 	 * @throws IOException
107 	 */
108 	private void applyFilterToParents(RevCommit c)
109 			throws MissingObjectException, IncorrectObjectTypeException,
110 			IOException {
111 		for (RevCommit parent : c.getParents()) {
112 			while ((parent.flags & RevWalk.TREE_REV_FILTER_APPLIED) == 0) {
113 
114 				RevCommit n = source.next();
115 
116 				if (n != null) {
117 					pending.add(n);
118 				} else {
119 					// Source generator is exhausted; filter has been applied to
120 					// all commits
121 					return;
122 				}
123 
124 			}
125 
126 		}
127 	}
128 
129 	private RevCommit rewrite(RevCommit p) throws MissingObjectException,
130 			IncorrectObjectTypeException, IOException {
131 		for (;;) {
132 
133 			if (p.getParentCount() > 1) {
134 				// This parent is a merge, so keep it.
135 				//
136 				return p;
137 			}
138 
139 			if ((p.flags & RevWalk.UNINTERESTING) != 0) {
140 				// Retain uninteresting parents. They show where the
141 				// DAG was cut off because it wasn't interesting.
142 				//
143 				return p;
144 			}
145 
146 			if ((p.flags & REWRITE) == 0) {
147 				// This parent was not eligible for rewriting. We
148 				// need to keep it in the DAG.
149 				//
150 				return p;
151 			}
152 
153 			if (p.getParentCount() == 0) {
154 				// We can't go back any further, other than to
155 				// just delete the parent entirely.
156 				//
157 				return null;
158 			}
159 
160 			applyFilterToParents(p.getParent(0));
161 			p = p.getParent(0);
162 
163 		}
164 	}
165 
166 	private RevCommit[] cleanup(RevCommit[] oldList) {
167 		// Remove any duplicate parents caused due to rewrites (e.g. a merge
168 		// with two sides that both simplified back into the merge base).
169 		// We also may have deleted a parent by marking it null.
170 		//
171 		int newCnt = 0;
172 		for (int o = 0; o < oldList.length; o++) {
173 			final RevCommit p = oldList[o];
174 			if (p == null)
175 				continue;
176 			if ((p.flags & DUPLICATE) != 0) {
177 				oldList[o] = null;
178 				continue;
179 			}
180 			p.flags |= DUPLICATE;
181 			newCnt++;
182 		}
183 
184 		if (newCnt == oldList.length) {
185 			for (RevCommit p : oldList)
186 				p.flags &= ~DUPLICATE;
187 			return oldList;
188 		}
189 
190 		final RevCommit[] newList = new RevCommit[newCnt];
191 		newCnt = 0;
192 		for (RevCommit p : oldList) {
193 			if (p != null) {
194 				newList[newCnt++] = p;
195 				p.flags &= ~DUPLICATE;
196 			}
197 		}
198 
199 		return newList;
200 	}
201 }