Allow DirCacheEntry instances to be created with stage > 0
[egit/torarne.git] / org.spearce.jgit / src / org / spearce / jgit / dircache / DirCacheEntry.java
blob6d46648e202a309d7705f63bd1e57fd4d7cc85c5
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.ByteArrayOutputStream;
41 import java.io.EOFException;
42 import java.io.IOException;
43 import java.io.InputStream;
44 import java.io.OutputStream;
45 import java.nio.ByteBuffer;
46 import java.util.Arrays;
48 import org.spearce.jgit.lib.AnyObjectId;
49 import org.spearce.jgit.lib.Constants;
50 import org.spearce.jgit.lib.FileMode;
51 import org.spearce.jgit.lib.ObjectId;
52 import org.spearce.jgit.util.NB;
54 /**
55 * A single file (or stage of a file) in a {@link DirCache}.
56 * <p>
57 * An entry represents exactly one stage of a file. If a file path is unmerged
58 * then multiple DirCacheEntry instances may appear for the same path name.
60 public class DirCacheEntry {
61 private static final byte[] nullpad = new byte[8];
63 /** The standard (fully merged) stage for an entry. */
64 public static final int STAGE_0 = 0;
66 /** The base tree revision for an entry. */
67 public static final int STAGE_1 = 1;
69 /** The first tree revision (usually called "ours"). */
70 public static final int STAGE_2 = 2;
72 /** The second tree revision (usually called "theirs"). */
73 public static final int STAGE_3 = 3;
75 // private static final int P_CTIME = 0;
77 // private static final int P_CTIME_NSEC = 4;
79 private static final int P_MTIME = 8;
81 // private static final int P_MTIME_NSEC = 12;
83 // private static final int P_DEV = 16;
85 // private static final int P_INO = 20;
87 private static final int P_MODE = 24;
89 // private static final int P_UID = 28;
91 // private static final int P_GID = 32;
93 private static final int P_SIZE = 36;
95 private static final int P_OBJECTID = 40;
97 private static final int P_FLAGS = 60;
99 /** Mask applied to data in {@link #P_FLAGS} to get the name length. */
100 private static final int NAME_MASK = 0xfff;
102 static final int INFO_LEN = 62;
104 private static final int ASSUME_VALID = 0x80;
106 /** (Possibly shared) header information storage. */
107 private final byte[] info;
109 /** First location within {@link #info} where our header starts. */
110 private final int infoOffset;
112 /** Our encoded path name, from the root of the repository. */
113 final byte[] path;
115 DirCacheEntry(final byte[] sharedInfo, final int infoAt,
116 final InputStream in) throws IOException {
117 info = sharedInfo;
118 infoOffset = infoAt;
120 NB.readFully(in, info, infoOffset, INFO_LEN);
122 int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
123 int skipped = 0;
124 if (pathLen < NAME_MASK) {
125 path = new byte[pathLen];
126 NB.readFully(in, path, 0, pathLen);
127 } else {
128 final ByteArrayOutputStream tmp = new ByteArrayOutputStream();
130 final byte[] buf = new byte[NAME_MASK];
131 NB.readFully(in, buf, 0, NAME_MASK);
132 tmp.write(buf);
134 for (;;) {
135 final int c = in.read();
136 if (c < 0)
137 throw new EOFException("Short read of block.");
138 if (c == 0)
139 break;
140 tmp.write(c);
142 path = tmp.toByteArray();
143 pathLen = path.length;
144 skipped = 1; // we already skipped 1 '\0' above to break the loop.
147 // Index records are padded out to the next 8 byte alignment
148 // for historical reasons related to how C Git read the files.
150 final int actLen = INFO_LEN + pathLen;
151 final int expLen = (actLen + 8) & ~7;
152 NB.skipFully(in, expLen - actLen - skipped);
156 * Create an empty entry at stage 0.
158 * @param newPath
159 * name of the cache entry.
161 public DirCacheEntry(final String newPath) {
162 this(Constants.encode(newPath));
166 * Create an empty entry at the specified stage.
168 * @param newPath
169 * name of the cache entry.
170 * @param stage
171 * the stage index of the new entry.
173 public DirCacheEntry(final String newPath, final int stage) {
174 this(Constants.encode(newPath), stage);
178 * Create an empty entry at stage 0.
180 * @param newPath
181 * name of the cache entry, in the standard encoding.
183 public DirCacheEntry(final byte[] newPath) {
184 this(newPath, STAGE_0);
188 * Create an empty entry at the specified stage.
190 * @param newPath
191 * name of the cache entry, in the standard encoding.
192 * @param stage
193 * the stage index of the new entry.
195 public DirCacheEntry(final byte[] newPath, final int stage) {
196 info = new byte[INFO_LEN];
197 infoOffset = 0;
198 path = newPath;
200 int flags = ((stage & 0x3) << 12);
201 if (path.length < NAME_MASK)
202 flags |= path.length;
203 else
204 flags |= NAME_MASK;
205 NB.encodeInt16(info, infoOffset + P_FLAGS, flags);
208 void write(final OutputStream os) throws IOException {
209 final int pathLen = path.length;
210 os.write(info, infoOffset, INFO_LEN);
211 os.write(path, 0, pathLen);
213 // Index records are padded out to the next 8 byte alignment
214 // for historical reasons related to how C Git read the files.
216 final int actLen = INFO_LEN + pathLen;
217 final int expLen = (actLen + 8) & ~7;
218 if (actLen != expLen)
219 os.write(nullpad, 0, expLen - actLen);
223 * Is it possible for this entry to be accidentally assumed clean?
224 * <p>
225 * The "racy git" problem happens when a work file can be updated faster
226 * than the filesystem records file modification timestamps. It is possible
227 * for an application to edit a work file, update the index, then edit it
228 * again before the filesystem will give the work file a new modification
229 * timestamp. This method tests to see if file was written out at the same
230 * time as the index.
232 * @param smudge_s
233 * seconds component of the index's last modified time.
234 * @param smudge_ns
235 * nanoseconds component of the index's last modified time.
236 * @return true if extra careful checks should be used.
238 final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
239 // If the index has a modification time then it came from disk
240 // and was not generated from scratch in memory. In such cases
241 // the entry is 'racily clean' if the entry's cached modification
242 // time is equal to or later than the index modification time. In
243 // such cases the work file is too close to the index to tell if
244 // it is clean or not based on the modification time alone.
246 final int base = infoOffset + P_MTIME;
247 final int mtime = NB.decodeInt32(info, base);
248 if (smudge_s < mtime)
249 return true;
250 if (smudge_s == mtime)
251 return smudge_ns <= NB.decodeInt32(info, base + 4) / 1000000;
252 return false;
256 * Force this entry to no longer match its working tree file.
257 * <p>
258 * This avoids the "racy git" problem by making this index entry no longer
259 * match the file in the working directory. Later git will be forced to
260 * compare the file content to ensure the file matches the working tree.
262 final void smudgeRacilyClean() {
263 // We don't use the same approach as C Git to smudge the entry,
264 // as we cannot compare the working tree file to our SHA-1 and
265 // thus cannot use the "size to 0" trick without accidentally
266 // thinking a zero length file is clean.
268 // Instead we force the mtime to the largest possible value, so
269 // it is certainly after the index's own modification time and
270 // on a future read will cause mightBeRacilyClean to say "yes!".
271 // It is also unlikely to match with the working tree file.
273 // I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
275 final int base = infoOffset + P_MTIME;
276 Arrays.fill(info, base, base + 8, (byte) 127);
279 final byte[] idBuffer() {
280 return info;
283 final int idOffset() {
284 return infoOffset + P_OBJECTID;
288 * Is this entry always thought to be unmodified?
289 * <p>
290 * Most entries in the index do not have this flag set. Users may however
291 * set them on if the file system stat() costs are too high on this working
292 * directory, such as on NFS or SMB volumes.
294 * @return true if we must assume the entry is unmodified.
296 public boolean isAssumeValid() {
297 return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
301 * Set the assume valid flag for this entry,
303 * @param assume
304 * true to ignore apparent modifications; false to look at last
305 * modified to detect file modifications.
307 public void setAssumeValid(final boolean assume) {
308 if (assume)
309 info[infoOffset + P_FLAGS] |= ASSUME_VALID;
310 else
311 info[infoOffset + P_FLAGS] &= ~ASSUME_VALID;
315 * Get the stage of this entry.
316 * <p>
317 * Entries have one of 4 possible stages: 0-3.
319 * @return the stage of this entry.
321 public int getStage() {
322 return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
326 * Obtain the raw {@link FileMode} bits for this entry.
328 * @return mode bits for the entry.
329 * @see FileMode#fromBits(int)
331 public int getRawMode() {
332 return NB.decodeInt32(info, infoOffset + P_MODE);
336 * Obtain the {@link FileMode} for this entry.
338 * @return the file mode singleton for this entry.
340 public FileMode getFileMode() {
341 return FileMode.fromBits(getRawMode());
345 * Set the file mode for this entry.
347 * @param mode
348 * the new mode constant.
350 public void setFileMode(final FileMode mode) {
351 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
355 * Get the cached last modification date of this file, in milliseconds.
356 * <p>
357 * One of the indicators that the file has been modified by an application
358 * changing the working tree is if the last modification time for the file
359 * differs from the time stored in this entry.
361 * @return last modification time of this file, in milliseconds since the
362 * Java epoch (midnight Jan 1, 1970 UTC).
364 public long getLastModified() {
365 return decodeTS(P_MTIME);
369 * Set the cached last modification date of this file, using milliseconds.
371 * @param when
372 * new cached modification date of the file, in milliseconds.
374 public void setLastModified(final long when) {
375 encodeTS(P_MTIME, when);
379 * Get the cached size (in bytes) of this file.
380 * <p>
381 * One of the indicators that the file has been modified by an application
382 * changing the working tree is if the size of the file (in bytes) differs
383 * from the size stored in this entry.
384 * <p>
385 * Note that this is the length of the file in the working directory, which
386 * may differ from the size of the decompressed blob if work tree filters
387 * are being used, such as LF<->CRLF conversion.
389 * @return cached size of the working directory file, in bytes.
391 public int getLength() {
392 return NB.decodeInt32(info, infoOffset + P_SIZE);
396 * Set the cached size (in bytes) of this file.
398 * @param sz
399 * new cached size of the file, as bytes.
401 public void setLength(final int sz) {
402 NB.encodeInt32(info, infoOffset + P_SIZE, sz);
406 * Obtain the ObjectId for the entry.
407 * <p>
408 * Using this method to compare ObjectId values between entries is
409 * inefficient as it causes memory allocation.
411 * @return object identifier for the entry.
413 public ObjectId getObjectId() {
414 return ObjectId.fromRaw(idBuffer(), idOffset());
418 * Set the ObjectId for the entry.
420 * @param id
421 * new object identifier for the entry. May be
422 * {@link ObjectId#zeroId()} to remove the current identifier.
424 public void setObjectId(final AnyObjectId id) {
425 id.copyRawTo(idBuffer(), idOffset());
429 * Set the ObjectId for the entry from the raw binary representation.
431 * @param bs
432 * the raw byte buffer to read from. At least 20 bytes after p
433 * must be available within this byte array.
434 * @param p
435 * position to read the first byte of data from.
437 public void setObjectIdFromRaw(final byte[] bs, final int p) {
438 final int n = Constants.OBJECT_ID_LENGTH;
439 System.arraycopy(bs, p, idBuffer(), idOffset(), n);
443 * Get the entry's complete path.
444 * <p>
445 * This method is not very efficient and is primarily meant for debugging
446 * and final output generation. Applications should try to avoid calling it,
447 * and if invoked do so only once per interesting entry, where the name is
448 * absolutely required for correct function.
450 * @return complete path of the entry, from the root of the repository. If
451 * the entry is in a subtree there will be at least one '/' in the
452 * returned string.
454 public String getPathString() {
455 return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
459 * Copy the ObjectId and other meta fields from an existing entry.
460 * <p>
461 * This method copies everything except the path from one entry to another,
462 * supporting renaming.
464 * @param src
465 * the entry to copy ObjectId and meta fields from.
467 public void copyMetaData(final DirCacheEntry src) {
468 final int pLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
469 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
470 NB.encodeInt16(info, infoOffset + P_FLAGS, pLen
471 | NB.decodeUInt16(info, infoOffset + P_FLAGS) & ~NAME_MASK);
474 private long decodeTS(final int pIdx) {
475 final int base = infoOffset + pIdx;
476 final int sec = NB.decodeInt32(info, base);
477 final int ms = NB.decodeInt32(info, base + 4) / 1000000;
478 return 1000L * sec + ms;
481 private void encodeTS(final int pIdx, final long when) {
482 final int base = infoOffset + pIdx;
483 NB.encodeInt32(info, base, (int) (when / 1000));
484 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);