2 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
6 * Redistribution and use in source and binary forms, with or
7 * without modification, are permitted provided that the following
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
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
;
55 * A single file (or stage of a file) in a {@link DirCache}.
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 // private static final int P_CTIME = 0;
65 // private static final int P_CTIME_NSEC = 4;
67 private static final int P_MTIME
= 8;
69 // private static final int P_MTIME_NSEC = 12;
71 // private static final int P_DEV = 16;
73 // private static final int P_INO = 20;
75 private static final int P_MODE
= 24;
77 // private static final int P_UID = 28;
79 // private static final int P_GID = 32;
81 private static final int P_SIZE
= 36;
83 private static final int P_OBJECTID
= 40;
85 private static final int P_FLAGS
= 60;
87 /** Mask applied to data in {@link #P_FLAGS} to get the name length. */
88 private static final int NAME_MASK
= 0xfff;
90 static final int INFO_LEN
= 62;
92 private static final int ASSUME_VALID
= 0x80;
94 /** (Possibly shared) header information storage. */
95 private final byte[] info
;
97 /** First location within {@link #info} where our header starts. */
98 private final int infoOffset
;
100 /** Our encoded path name, from the root of the repository. */
103 DirCacheEntry(final byte[] sharedInfo
, final int infoAt
,
104 final InputStream in
) throws IOException
{
108 NB
.readFully(in
, info
, infoOffset
, INFO_LEN
);
110 int pathLen
= NB
.decodeUInt16(info
, infoOffset
+ P_FLAGS
) & NAME_MASK
;
112 if (pathLen
< NAME_MASK
) {
113 path
= new byte[pathLen
];
114 NB
.readFully(in
, path
, 0, pathLen
);
116 final ByteArrayOutputStream tmp
= new ByteArrayOutputStream();
118 final byte[] buf
= new byte[NAME_MASK
];
119 NB
.readFully(in
, buf
, 0, NAME_MASK
);
123 final int c
= in
.read();
125 throw new EOFException("Short read of block.");
130 path
= tmp
.toByteArray();
131 pathLen
= path
.length
;
132 skipped
= 1; // we already skipped 1 '\0' above to break the loop.
135 // Index records are padded out to the next 8 byte alignment
136 // for historical reasons related to how C Git read the files.
138 final int actLen
= INFO_LEN
+ pathLen
;
139 final int expLen
= (actLen
+ 8) & ~
7;
140 NB
.skipFully(in
, expLen
- actLen
- skipped
);
144 * Create an empty entry.
147 * name of the cache entry.
149 public DirCacheEntry(final String newPath
) {
150 this(Constants
.encode(newPath
));
154 * Create an empty entry.
157 * name of the cache entry, in the standard encoding.
159 public DirCacheEntry(final byte[] newPath
) {
160 info
= new byte[INFO_LEN
];
164 if (path
.length
< NAME_MASK
)
165 NB
.encodeInt16(info
, infoOffset
+ P_FLAGS
, path
.length
);
167 NB
.encodeInt16(info
, infoOffset
+ P_FLAGS
, NAME_MASK
);
170 void write(final OutputStream os
) throws IOException
{
171 final int pathLen
= path
.length
;
172 os
.write(info
, infoOffset
, INFO_LEN
);
173 os
.write(path
, 0, pathLen
);
175 // Index records are padded out to the next 8 byte alignment
176 // for historical reasons related to how C Git read the files.
178 final int actLen
= INFO_LEN
+ pathLen
;
179 final int expLen
= (actLen
+ 8) & ~
7;
180 if (actLen
!= expLen
)
181 os
.write(nullpad
, 0, expLen
- actLen
);
185 * Is it possible for this entry to be accidentally assumed clean?
187 * The "racy git" problem happens when a work file can be updated faster
188 * than the filesystem records file modification timestamps. It is possible
189 * for an application to edit a work file, update the index, then edit it
190 * again before the filesystem will give the work file a new modification
191 * timestamp. This method tests to see if file was written out at the same
195 * seconds component of the index's last modified time.
197 * nanoseconds component of the index's last modified time.
198 * @return true if extra careful checks should be used.
200 final boolean mightBeRacilyClean(final int smudge_s
, final int smudge_ns
) {
201 // If the index has a modification time then it came from disk
202 // and was not generated from scratch in memory. In such cases
203 // the entry is 'racily clean' if the entry's cached modification
204 // time is equal to or later than the index modification time. In
205 // such cases the work file is too close to the index to tell if
206 // it is clean or not based on the modification time alone.
208 final int base
= infoOffset
+ P_MTIME
;
209 final int mtime
= NB
.decodeInt32(info
, base
);
210 if (smudge_s
< mtime
)
212 if (smudge_s
== mtime
)
213 return smudge_ns
<= NB
.decodeInt32(info
, base
+ 4) / 1000000;
218 * Force this entry to no longer match its working tree file.
220 * This avoids the "racy git" problem by making this index entry no longer
221 * match the file in the working directory. Later git will be forced to
222 * compare the file content to ensure the file matches the working tree.
224 final void smudgeRacilyClean() {
225 // We don't use the same approach as C Git to smudge the entry,
226 // as we cannot compare the working tree file to our SHA-1 and
227 // thus cannot use the "size to 0" trick without accidentally
228 // thinking a zero length file is clean.
230 // Instead we force the mtime to the largest possible value, so
231 // it is certainly after the index's own modification time and
232 // on a future read will cause mightBeRacilyClean to say "yes!".
233 // It is also unlikely to match with the working tree file.
235 // I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
237 final int base
= infoOffset
+ P_MTIME
;
238 Arrays
.fill(info
, base
, base
+ 8, (byte) 127);
241 final byte[] idBuffer() {
245 final int idOffset() {
246 return infoOffset
+ P_OBJECTID
;
250 * Is this entry always thought to be unmodified?
252 * Most entries in the index do not have this flag set. Users may however
253 * set them on if the file system stat() costs are too high on this working
254 * directory, such as on NFS or SMB volumes.
256 * @return true if we must assume the entry is unmodified.
258 public boolean isAssumeValid() {
259 return (info
[infoOffset
+ P_FLAGS
] & ASSUME_VALID
) != 0;
263 * Set the assume valid flag for this entry,
266 * true to ignore apparent modifications; false to look at last
267 * modified to detect file modifications.
269 public void setAssumeValid(final boolean assume
) {
271 info
[infoOffset
+ P_FLAGS
] |= ASSUME_VALID
;
273 info
[infoOffset
+ P_FLAGS
] &= ~ASSUME_VALID
;
277 * Get the stage of this entry.
279 * Entries have one of 4 possible stages: 0-3.
281 * @return the stage of this entry.
283 public int getStage() {
284 return (info
[infoOffset
+ P_FLAGS
] >>> 4) & 0x3;
288 * Obtain the raw {@link FileMode} bits for this entry.
290 * @return mode bits for the entry.
291 * @see FileMode#fromBits(int)
293 public int getRawMode() {
294 return NB
.decodeInt32(info
, infoOffset
+ P_MODE
);
298 * Obtain the {@link FileMode} for this entry.
300 * @return the file mode singleton for this entry.
302 public FileMode
getFileMode() {
303 return FileMode
.fromBits(getRawMode());
307 * Set the file mode for this entry.
310 * the new mode constant.
312 public void setFileMode(final FileMode mode
) {
313 NB
.encodeInt32(info
, infoOffset
+ P_MODE
, mode
.getBits());
317 * Get the cached last modification date of this file, in milliseconds.
319 * One of the indicators that the file has been modified by an application
320 * changing the working tree is if the last modification time for the file
321 * differs from the time stored in this entry.
323 * @return last modification time of this file, in milliseconds since the
324 * Java epoch (midnight Jan 1, 1970 UTC).
326 public long getLastModified() {
327 return decodeTS(P_MTIME
);
331 * Set the cached last modification date of this file, using milliseconds.
334 * new cached modification date of the file, in milliseconds.
336 public void setLastModified(final long when
) {
337 encodeTS(P_MTIME
, when
);
341 * Get the cached size (in bytes) of this file.
343 * One of the indicators that the file has been modified by an application
344 * changing the working tree is if the size of the file (in bytes) differs
345 * from the size stored in this entry.
347 * Note that this is the length of the file in the working directory, which
348 * may differ from the size of the decompressed blob if work tree filters
349 * are being used, such as LF<->CRLF conversion.
351 * @return cached size of the working directory file, in bytes.
353 public int getLength() {
354 return NB
.decodeInt32(info
, infoOffset
+ P_SIZE
);
358 * Set the cached size (in bytes) of this file.
361 * new cached size of the file, as bytes.
363 public void setLength(final int sz
) {
364 NB
.encodeInt32(info
, infoOffset
+ P_SIZE
, sz
);
368 * Obtain the ObjectId for the entry.
370 * Using this method to compare ObjectId values between entries is
371 * inefficient as it causes memory allocation.
373 * @return object identifier for the entry.
375 public ObjectId
getObjectId() {
376 return ObjectId
.fromRaw(idBuffer(), idOffset());
380 * Set the ObjectId for the entry.
383 * new object identifier for the entry. May be
384 * {@link ObjectId#zeroId()} to remove the current identifier.
386 public void setObjectId(final AnyObjectId id
) {
387 id
.copyRawTo(idBuffer(), idOffset());
391 * Set the ObjectId for the entry from the raw binary representation.
394 * the raw byte buffer to read from. At least 20 bytes after p
395 * must be available within this byte array.
397 * position to read the first byte of data from.
399 public void setObjectIdFromRaw(final byte[] bs
, final int p
) {
400 final int n
= Constants
.OBJECT_ID_LENGTH
;
401 System
.arraycopy(bs
, p
, idBuffer(), idOffset(), n
);
405 * Get the entry's complete path.
407 * This method is not very efficient and is primarily meant for debugging
408 * and final output generation. Applications should try to avoid calling it,
409 * and if invoked do so only once per interesting entry, where the name is
410 * absolutely required for correct function.
412 * @return complete path of the entry, from the root of the repository. If
413 * the entry is in a subtree there will be at least one '/' in the
416 public String
getPathString() {
417 return Constants
.CHARSET
.decode(ByteBuffer
.wrap(path
)).toString();
421 * Copy the ObjectId and other meta fields from an existing entry.
423 * This method copies everything except the path from one entry to another,
424 * supporting renaming.
427 * the entry to copy ObjectId and meta fields from.
429 public void copyMetaData(final DirCacheEntry src
) {
430 final int pLen
= NB
.decodeUInt16(info
, infoOffset
+ P_FLAGS
) & NAME_MASK
;
431 System
.arraycopy(src
.info
, src
.infoOffset
, info
, infoOffset
, INFO_LEN
);
432 NB
.encodeInt16(info
, infoOffset
+ P_FLAGS
, pLen
433 | NB
.decodeUInt16(info
, infoOffset
+ P_FLAGS
) & ~NAME_MASK
);
436 private long decodeTS(final int pIdx
) {
437 final int base
= infoOffset
+ pIdx
;
438 final int sec
= NB
.decodeInt32(info
, base
);
439 final int ms
= NB
.decodeInt32(info
, base
+ 4) / 1000000;
440 return 1000L * sec
+ ms
;
443 private void encodeTS(final int pIdx
, final long when
) {
444 final int base
= infoOffset
+ pIdx
;
445 NB
.encodeInt32(info
, base
, (int) (when
/ 1000));
446 NB
.encodeInt32(info
, base
+ 4, ((int) (when
% 1000)) * 1000000);