View Javadoc
1   /*
2    * Copyright (C) 2008-2009, Google Inc.
3    * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 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.dircache;
13  
14  import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
15  import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;
16  
17  import java.io.IOException;
18  import java.text.MessageFormat;
19  import java.util.Arrays;
20  
21  import org.eclipse.jgit.internal.JGitText;
22  import org.eclipse.jgit.lib.AnyObjectId;
23  import org.eclipse.jgit.lib.ObjectReader;
24  import org.eclipse.jgit.treewalk.CanonicalTreeParser;
25  
26  /**
27   * Updates a {@link org.eclipse.jgit.dircache.DirCache} by adding individual
28   * {@link org.eclipse.jgit.dircache.DirCacheEntry}s.
29   * <p>
30   * A builder always starts from a clean slate and appends in every single
31   * <code>DirCacheEntry</code> which the final updated index must have to reflect
32   * its new content.
33   * <p>
34   * For maximum performance applications should add entries in path name order.
35   * Adding entries out of order is permitted, however a final sorting pass will
36   * be implicitly performed during {@link #finish()} to correct any out-of-order
37   * entries. Duplicate detection is also delayed until the sorting is complete.
38   *
39   * @see DirCacheEditor
40   */
41  public class DirCacheBuilder extends BaseDirCacheEditor {
42  	private boolean sorted;
43  
44  	/**
45  	 * Construct a new builder.
46  	 *
47  	 * @param dc
48  	 *            the cache this builder will eventually update.
49  	 * @param ecnt
50  	 *            estimated number of entries the builder will have upon
51  	 *            completion. This sizes the initial entry table.
52  	 */
53  	protected DirCacheBuilder(DirCache dc, int ecnt) {
54  		super(dc, ecnt);
55  	}
56  
57  	/**
58  	 * Append one entry into the resulting entry list.
59  	 * <p>
60  	 * The entry is placed at the end of the entry list. If the entry causes the
61  	 * list to now be incorrectly sorted a final sorting phase will be
62  	 * automatically enabled within {@link #finish()}.
63  	 * <p>
64  	 * The internal entry table is automatically expanded if there is
65  	 * insufficient space for the new addition.
66  	 *
67  	 * @param newEntry
68  	 *            the new entry to add.
69  	 * @throws java.lang.IllegalArgumentException
70  	 *             If the FileMode of the entry was not set by the caller.
71  	 */
72  	public void add(DirCacheEntry newEntry) {
73  		if (newEntry.getRawMode() == 0)
74  			throw new IllegalArgumentException(MessageFormat.format(
75  					JGitText.get().fileModeNotSetForPath,
76  					newEntry.getPathString()));
77  		beforeAdd(newEntry);
78  		fastAdd(newEntry);
79  	}
80  
81  	/**
82  	 * Add a range of existing entries from the destination cache.
83  	 * <p>
84  	 * The entries are placed at the end of the entry list. If any of the
85  	 * entries causes the list to now be incorrectly sorted a final sorting
86  	 * phase will be automatically enabled within {@link #finish()}.
87  	 * <p>
88  	 * This method copies from the destination cache, which has not yet been
89  	 * updated with this editor's new table. So all offsets into the destination
90  	 * cache are not affected by any updates that may be currently taking place
91  	 * in this editor.
92  	 * <p>
93  	 * The internal entry table is automatically expanded if there is
94  	 * insufficient space for the new additions.
95  	 *
96  	 * @param pos
97  	 *            first entry to copy from the destination cache.
98  	 * @param cnt
99  	 *            number of entries to copy.
100 	 */
101 	public void keep(int pos, int cnt) {
102 		beforeAdd(cache.getEntry(pos));
103 		fastKeep(pos, cnt);
104 	}
105 
106 	/**
107 	 * Recursively add an entire tree into this builder.
108 	 * <p>
109 	 * If pathPrefix is "a/b" and the tree contains file "c" then the resulting
110 	 * DirCacheEntry will have the path "a/b/c".
111 	 * <p>
112 	 * All entries are inserted at stage 0, therefore assuming that the
113 	 * application will not insert any other paths with the same pathPrefix.
114 	 *
115 	 * @param pathPrefix
116 	 *            UTF-8 encoded prefix to mount the tree's entries at. If the
117 	 *            path does not end with '/' one will be automatically inserted
118 	 *            as necessary.
119 	 * @param stage
120 	 *            stage of the entries when adding them.
121 	 * @param reader
122 	 *            reader the tree(s) will be read from during recursive
123 	 *            traversal. This must be the same repository that the resulting
124 	 *            DirCache would be written out to (or used in) otherwise the
125 	 *            caller is simply asking for deferred MissingObjectExceptions.
126 	 *            Caller is responsible for releasing this reader when done.
127 	 * @param tree
128 	 *            the tree to recursively add. This tree's contents will appear
129 	 *            under <code>pathPrefix</code>. The ObjectId must be that of a
130 	 *            tree; the caller is responsible for dereferencing a tag or
131 	 *            commit (if necessary).
132 	 * @throws java.io.IOException
133 	 *             a tree cannot be read to iterate through its entries.
134 	 */
135 	public void addTree(byte[] pathPrefix, int stage, ObjectReader reader,
136 			AnyObjectId tree) throws IOException {
137 		CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree);
138 		while (!p.eof()) {
139 			if (isTree(p)) {
140 				p = enterTree(p, reader);
141 				continue;
142 			}
143 
144 			DirCacheEntry first = toEntry(stage, p);
145 			beforeAdd(first);
146 			fastAdd(first);
147 			p = p.next();
148 			break;
149 		}
150 
151 		// Rest of tree entries are correctly sorted; use fastAdd().
152 		while (!p.eof()) {
153 			if (isTree(p)) {
154 				p = enterTree(p, reader);
155 			} else {
156 				fastAdd(toEntry(stage, p));
157 				p = p.next();
158 			}
159 		}
160 	}
161 
162 	private static CanonicalTreeParser createTreeParser(byte[] pathPrefix,
163 			ObjectReader reader, AnyObjectId tree) throws IOException {
164 		return new CanonicalTreeParser(pathPrefix, reader, tree);
165 	}
166 
167 	private static boolean isTree(CanonicalTreeParser p) {
168 		return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE;
169 	}
170 
171 	private static CanonicalTreeParserrg/eclipse/jgit/treewalk/CanonicalTreeParser.html#CanonicalTreeParser">CanonicalTreeParser enterTree(CanonicalTreeParser p,
172 			ObjectReader reader) throws IOException {
173 		p = p.createSubtreeIterator(reader);
174 		return p.eof() ? p.next() : p;
175 	}
176 
177 	private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
178 		byte[] buf = i.getEntryPathBuffer();
179 		int len = i.getEntryPathLength();
180 		byte[] path = new byte[len];
181 		System.arraycopy(buf, 0, path, 0, len);
182 
183 		DirCacheEntry e = new DirCacheEntry(path, stage);
184 		e.setFileMode(i.getEntryRawMode());
185 		e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
186 		return e;
187 	}
188 
189 	/** {@inheritDoc} */
190 	@Override
191 	public void finish() {
192 		if (!sorted)
193 			resort();
194 		replace();
195 	}
196 
197 	private void beforeAdd(DirCacheEntry newEntry) {
198 		if (sorted && entryCnt > 0) {
199 			final DirCacheEntry lastEntry = entries[entryCnt - 1];
200 			final int cr = DirCache.cmp(lastEntry, newEntry);
201 			if (cr > 0) {
202 				// The new entry sorts before the old entry; we are
203 				// no longer sorted correctly. We'll need to redo
204 				// the sorting before we can close out the build.
205 				//
206 				sorted = false;
207 			} else if (cr == 0) {
208 				// Same file path; we can only insert this if the
209 				// stages won't be violated.
210 				//
211 				final int peStage = lastEntry.getStage();
212 				final int dceStage = newEntry.getStage();
213 				if (peStage == dceStage)
214 					throw bad(newEntry, JGitText.get().duplicateStagesNotAllowed);
215 				if (peStage == 0 || dceStage == 0)
216 					throw bad(newEntry, JGitText.get().mixedStagesNotAllowed);
217 				if (peStage > dceStage)
218 					sorted = false;
219 			}
220 		}
221 	}
222 
223 	private void resort() {
224 		Arrays.sort(entries, 0, entryCnt, DirCache.ENT_CMP);
225 
226 		for (int entryIdx = 1; entryIdx < entryCnt; entryIdx++) {
227 			final DirCacheEntry pe = entries[entryIdx - 1];
228 			final DirCacheEntry ce = entries[entryIdx];
229 			final int cr = DirCache.cmp(pe, ce);
230 			if (cr == 0) {
231 				// Same file path; we can only allow this if the stages
232 				// are 1-3 and no 0 exists.
233 				//
234 				final int peStage = pe.getStage();
235 				final int ceStage = ce.getStage();
236 				if (peStage == ceStage)
237 					throw bad(ce, JGitText.get().duplicateStagesNotAllowed);
238 				if (peStage == 0 || ceStage == 0)
239 					throw bad(ce, JGitText.get().mixedStagesNotAllowed);
240 			}
241 		}
242 
243 		sorted = true;
244 	}
245 
246 	private static IllegalStateException bad(DirCacheEntry a, String msg) {
247 		return new IllegalStateException(String.format(
248 				"%s: %d %s", //$NON-NLS-1$
249 				msg, Integer.valueOf(a.getStage()), a.getPathString()));
250 	}
251 }