Support a simplified model of editing index entries
[egit/zawir.git] / org.spearce.jgit / src / org / spearce / jgit / dircache / DirCacheEditor.java
blob10b554ea660d04bce3481c3d2f230c6087426d0f
1 /*
2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * All rights reserved.
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
8 * conditions are met:
10 * - Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
13 * - Redistributions in binary form must reproduce the above
14 * copyright notice, this list of conditions and the following
15 * disclaimer in the documentation and/or other materials provided
16 * with the distribution.
18 * - Neither the name of the Git Development Community nor the
19 * names of its contributors may be used to endorse or promote
20 * products derived from this software without specific prior
21 * written permission.
23 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
24 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
25 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
26 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
27 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
29 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
30 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
31 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
32 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
33 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
34 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
35 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
38 package org.spearce.jgit.dircache;
40 import java.io.IOException;
41 import java.util.ArrayList;
42 import java.util.Collections;
43 import java.util.Comparator;
44 import java.util.List;
46 import org.spearce.jgit.lib.Constants;
48 /**
49 * Updates a {@link DirCache} by supplying discrete edit commands.
50 * <p>
51 * An editor updates a DirCache by taking a list of {@link PathEdit} commands
52 * and executing them against the entries of the destination cache to produce a
53 * new cache. This edit style allows applications to insert a few commands and
54 * then have the editor compute the proper entry indexes necessary to perform an
55 * efficient in-order update of the index records. This can be easier to use
56 * than {@link DirCacheBuilder}.
57 * <p>
59 * @see DirCacheBuilder
61 public class DirCacheEditor extends BaseDirCacheEditor {
62 private static final Comparator<PathEdit> EDIT_CMP = new Comparator<PathEdit>() {
63 public int compare(final PathEdit o1, final PathEdit o2) {
64 final byte[] a = o1.path;
65 final byte[] b = o2.path;
66 return DirCache.cmp(a, a.length, b, b.length);
70 private final List<PathEdit> edits;
72 /**
73 * Construct a new editor.
75 * @param dc
76 * the cache this editor will eventually update.
77 * @param ecnt
78 * estimated number of entries the editor will have upon
79 * completion. This sizes the initial entry table.
81 protected DirCacheEditor(final DirCache dc, final int ecnt) {
82 super(dc, ecnt);
83 edits = new ArrayList<PathEdit>();
86 /**
87 * Append one edit command to the list of commands to be applied.
88 * <p>
89 * Edit commands may be added in any order chosen by the application. They
90 * are automatically rearranged by the builder to provide the most efficient
91 * update possible.
93 * @param edit
94 * another edit command.
96 public void add(final PathEdit edit) {
97 edits.add(edit);
100 @Override
101 public boolean commit() throws IOException {
102 if (edits.isEmpty()) {
103 // No changes? Don't rewrite the index.
105 cache.unlock();
106 return true;
108 return super.commit();
111 public void finish() {
112 if (!edits.isEmpty()) {
113 applyEdits();
114 replace();
118 private void applyEdits() {
119 Collections.sort(edits, EDIT_CMP);
121 final int maxIdx = cache.getEntryCount();
122 int lastIdx = 0;
123 for (final PathEdit e : edits) {
124 int eIdx = cache.findEntry(e.path, e.path.length);
125 final boolean missing = eIdx < 0;
126 if (eIdx < 0)
127 eIdx = -(eIdx + 1);
128 final int cnt = Math.min(eIdx, maxIdx) - lastIdx;
129 if (cnt > 0)
130 fastKeep(lastIdx, cnt);
131 lastIdx = missing ? eIdx : cache.nextEntry(eIdx);
133 if (e instanceof DeletePath)
134 continue;
135 if (e instanceof DeleteTree) {
136 lastIdx = cache.nextEntry(e.path, e.path.length, eIdx);
137 continue;
140 final DirCacheEntry ent;
141 if (missing)
142 ent = new DirCacheEntry(e.path);
143 else
144 ent = cache.getEntry(eIdx);
145 e.apply(ent);
146 fastAdd(ent);
149 final int cnt = maxIdx - lastIdx;
150 if (cnt > 0)
151 fastKeep(lastIdx, cnt);
155 * Any index record update.
156 * <p>
157 * Applications should subclass and provide their own implementation for the
158 * {@link #apply(DirCacheEntry)} method. The editor will invoke apply once
159 * for each record in the index which matches the path name. If there are
160 * multiple records (for example in stages 1, 2 and 3), the edit instance
161 * will be called multiple times, once for each stage.
163 public abstract static class PathEdit {
164 final byte[] path;
167 * Create a new update command by path name.
169 * @param entryPath
170 * path of the file within the repository.
172 public PathEdit(final String entryPath) {
173 path = Constants.encode(entryPath);
177 * Create a new update command for an existing entry instance.
179 * @param ent
180 * entry instance to match path of. Only the path of this
181 * entry is actually considered during command evaluation.
183 public PathEdit(final DirCacheEntry ent) {
184 path = ent.path;
188 * Apply the update to a single cache entry matching the path.
189 * <p>
190 * After apply is invoked the entry is added to the output table, and
191 * will be included in the new index.
193 * @param ent
194 * the entry being processed. All fields are zeroed out if
195 * the path is a new path in the index.
197 public abstract void apply(DirCacheEntry ent);
201 * Deletes a single file entry from the index.
202 * <p>
203 * This deletion command removes only a single file at the given location,
204 * but removes multiple stages (if present) for that path. To remove a
205 * complete subtree use {@link DeleteTree} instead.
207 * @see DeleteTree
209 public static final class DeletePath extends PathEdit {
211 * Create a new deletion command by path name.
213 * @param entryPath
214 * path of the file within the repository.
216 public DeletePath(final String entryPath) {
217 super(entryPath);
221 * Create a new deletion command for an existing entry instance.
223 * @param ent
224 * entry instance to remove. Only the path of this entry is
225 * actually considered during command evaluation.
227 public DeletePath(final DirCacheEntry ent) {
228 super(ent);
231 public void apply(final DirCacheEntry ent) {
232 throw new UnsupportedOperationException("No apply in delete");
237 * Recursively deletes all paths under a subtree.
238 * <p>
239 * This deletion command is more generic than {@link DeletePath} as it can
240 * remove all records which appear recursively under the same subtree.
241 * Multiple stages are removed (if present) for any deleted entry.
242 * <p>
243 * This command will not remove a single file entry. To remove a single file
244 * use {@link DeletePath}.
246 * @see DeletePath
248 public static final class DeleteTree extends PathEdit {
250 * Create a new tree deletion command by path name.
252 * @param entryPath
253 * path of the subtree within the repository. If the path
254 * does not end with "/" a "/" is implicitly added to ensure
255 * only the subtree's contents are matched by the command.
257 public DeleteTree(final String entryPath) {
258 super(entryPath.endsWith("/") ? entryPath : entryPath + "/");
261 public void apply(final DirCacheEntry ent) {
262 throw new UnsupportedOperationException("No apply in delete");