Support copying meta fields from one DirCacheEntry to another
[egit.git] / org.spearce.jgit / src / org / spearce / jgit / dircache / DirCacheEntry.java
blobc481e43e0e8428eab0f440f643a6548e4b54873d
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.io.InputStream;
42 import java.io.OutputStream;
43 import java.nio.ByteBuffer;
44 import java.util.Arrays;
46 import org.spearce.jgit.lib.Constants;
47 import org.spearce.jgit.lib.FileMode;
48 import org.spearce.jgit.lib.ObjectId;
49 import org.spearce.jgit.util.NB;
51 /**
52 * A single file (or stage of a file) in a {@link DirCache}.
53 * <p>
54 * An entry represents exactly one stage of a file. If a file path is unmerged
55 * then multiple DirCacheEntry instances may appear for the same path name.
57 public class DirCacheEntry {
58 private static final byte[] nullpad = new byte[8];
60 // private static final int P_CTIME = 0;
62 // private static final int P_CTIME_NSEC = 4;
64 private static final int P_MTIME = 8;
66 // private static final int P_MTIME_NSEC = 12;
68 // private static final int P_DEV = 16;
70 // private static final int P_INO = 20;
72 private static final int P_MODE = 24;
74 // private static final int P_UID = 28;
76 // private static final int P_GID = 32;
78 private static final int P_SIZE = 36;
80 private static final int P_OBJECTID = 40;
82 private static final int P_FLAGS = 60;
84 static final int INFO_LEN = 62;
86 private static final int ASSUME_VALID = 0x80;
88 /** (Possibly shared) header information storage. */
89 private final byte[] info;
91 /** First location within {@link #info} where our header starts. */
92 private final int infoOffset;
94 /** Our encoded path name, from the root of the repository. */
95 final byte[] path;
97 DirCacheEntry(final byte[] sharedInfo, final int infoAt,
98 final InputStream in) throws IOException {
99 info = sharedInfo;
100 infoOffset = infoAt;
102 NB.readFully(in, info, infoOffset, INFO_LEN);
104 int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & 0xfff;
105 path = new byte[pathLen];
106 NB.readFully(in, path, 0, pathLen);
108 // Index records are padded out to the next 8 byte alignment
109 // for historical reasons related to how C Git read the files.
111 final int actLen = INFO_LEN + pathLen;
112 final int expLen = (actLen + 8) & ~7;
113 if (actLen != expLen)
114 in.skip(expLen - actLen);
118 * Create an empty entry.
120 * @param newPath
121 * name of the cache entry.
123 public DirCacheEntry(final String newPath) {
124 this(Constants.encode(newPath));
128 * Create an empty entry.
130 * @param newPath
131 * name of the cache entry, in the standard encoding.
133 public DirCacheEntry(final byte[] newPath) {
134 info = new byte[INFO_LEN];
135 infoOffset = 0;
137 path = newPath;
138 NB.encodeInt16(info, infoOffset + P_FLAGS, path.length);
141 void write(final OutputStream os) throws IOException {
142 final int pathLen = path.length;
143 os.write(info, infoOffset, INFO_LEN);
144 os.write(path, 0, pathLen);
146 // Index records are padded out to the next 8 byte alignment
147 // for historical reasons related to how C Git read the files.
149 final int actLen = INFO_LEN + pathLen;
150 final int expLen = (actLen + 8) & ~7;
151 if (actLen != expLen)
152 os.write(nullpad, 0, expLen - actLen);
156 * Is it possible for this entry to be accidently assumed clean?
157 * <p>
158 * The "racy git" problem happens when a work file can be updated faster
159 * than the filesystem records file modification timestamps. It is possible
160 * for an application to edit a work file, update the index, then edit it
161 * again before the filesystem will give the work file a new modification
162 * timestamp. This method tests to see if file was written out at the same
163 * time as the index.
165 * @param smudge_s
166 * seconds component of the index's last modified time.
167 * @param smudge_ns
168 * nanoseconds component of the index's last modified time.
169 * @return true if extra careful checks should be used.
171 final boolean mightBeRacilyClean(final int smudge_s, final int smudge_ns) {
172 // If the index has a modification time then it came from disk
173 // and was not generated from scratch in memory. In such cases
174 // the entry is 'racily clean' if the entry's cached modification
175 // time is equal to or later than the index modification time. In
176 // such cases the work file is too close to the index to tell if
177 // it is clean or not based on the modification time alone.
179 final int base = infoOffset + P_MTIME;
180 final int mtime = NB.decodeInt32(info, base);
181 if (smudge_s < mtime)
182 return true;
183 if (smudge_s == mtime)
184 return smudge_ns <= NB.decodeInt32(info, base + 4) / 1000000;
185 return false;
189 * Force this entry to no longer match its working tree file.
190 * <p>
191 * This avoids the "racy git" problem by making this index entry no longer
192 * match the file in the working directory. Later git will be forced to
193 * compare the file content to ensure the file matches the working tree.
195 final void smudgeRacilyClean() {
196 // We don't use the same approach as C Git to smudge the entry,
197 // as we cannot compare the working tree file to our SHA-1 and
198 // thus cannot use the "size to 0" trick without accidentally
199 // thinking a zero length file is clean.
201 // Instead we force the mtime to the largest possible value, so
202 // it is certainly after the index's own modification time and
203 // on a future read will cause mightBeRacilyClean to say "yes!".
204 // It is also unlikely to match with the working tree file.
206 // I'll see you again before Jan 19, 2038, 03:14:07 AM GMT.
208 final int base = infoOffset + P_MTIME;
209 Arrays.fill(info, base, base + 8, (byte) 127);
212 final byte[] idBuffer() {
213 return info;
216 final int idOffset() {
217 return infoOffset + P_OBJECTID;
221 * Is this entry always thought to be unmodified?
222 * <p>
223 * Most entries in the index do not have this flag set. Users may however
224 * set them on if the file system stat() costs are too high on this working
225 * directory, such as on NFS or SMB volumes.
227 * @return true if we must assume the entry is unmodified.
229 public boolean isAssumeValid() {
230 return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
234 * Set the assume valid flag for this entry,
236 * @param assume
237 * true to ignore apparent modifications; false to look at last
238 * modified to detect file modifications.
240 public void setAssumeValid(final boolean assume) {
241 if (assume)
242 info[infoOffset + P_FLAGS] |= ASSUME_VALID;
243 else
244 info[infoOffset + P_FLAGS] &= ~ASSUME_VALID;
248 * Get the stage of this entry.
249 * <p>
250 * Entries have one of 4 possible stages: 0-3.
252 * @return the stage of this entry.
254 public int getStage() {
255 return (info[infoOffset + P_FLAGS] >>> 4) & 0x3;
259 * Obtain the raw {@link FileMode} bits for this entry.
261 * @return mode bits for the entry.
262 * @see FileMode#fromBits(int)
264 public int getRawMode() {
265 return NB.decodeInt32(info, infoOffset + P_MODE);
269 * Set the file mode for this entry.
271 * @param mode
272 * the new mode constant.
274 public void setFileMode(final FileMode mode) {
275 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits());
279 * Get the cached last modification date of this file, in milliseconds.
280 * <p>
281 * One of the indicators that the file has been modified by an application
282 * changing the working tree is if the last modification time for the file
283 * differs from the time stored in this entry.
285 * @return last modification time of this file, in milliseconds since the
286 * Java epoch (midnight Jan 1, 1970 UTC).
288 public long getLastModified() {
289 return decodeTS(P_MTIME);
293 * Set the cached last modification date of this file, using milliseconds.
295 * @param when
296 * new cached modification date of the file, in milliseconds.
298 public void setLastModified(final long when) {
299 encodeTS(P_MTIME, when);
303 * Get the cached size (in bytes) of this file.
304 * <p>
305 * One of the indicators that the file has been modified by an application
306 * changing the working tree is if the size of the file (in bytes) differs
307 * from the size stored in this entry.
308 * <p>
309 * Note that this is the length of the file in the working directory, which
310 * may differ from the size of the decompressed blob if work tree filters
311 * are being used, such as LF<->CRLF conversion.
313 * @return cached size of the working directory file, in bytes.
315 public int getLength() {
316 return NB.decodeInt32(info, infoOffset + P_SIZE);
320 * Set the cached size (in bytes) of this file.
322 * @param sz
323 * new cached size of the file, as bytes.
325 public void setLength(final int sz) {
326 NB.encodeInt32(info, infoOffset + P_SIZE, sz);
330 * Obtain the ObjectId for the entry.
331 * <p>
332 * Using this method to compare ObjectId values between entries is
333 * inefficient as it causes memory allocation.
335 * @return object identifier for the entry.
337 public ObjectId getObjectId() {
338 return ObjectId.fromRaw(idBuffer(), idOffset());
342 * Get the entry's complete path.
343 * <p>
344 * This method is not very efficient and is primarily meant for debugging
345 * and final output generation. Applications should try to avoid calling it,
346 * and if invoked do so only once per interesting entry, where the name is
347 * absolutely required for correct function.
349 * @return complete path of the entry, from the root of the repository. If
350 * the entry is in a subtree there will be at least one '/' in the
351 * returned string.
353 public String getPathString() {
354 return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString();
358 * Copy the ObjectId and other meta fields from an existing entry.
359 * <p>
360 * This method copies everything except the path from one entry to another,
361 * supporting renaming.
363 * @param src
364 * the entry to copy ObjectId and meta fields from.
366 public void copyMetaData(final DirCacheEntry src) {
367 final int pLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & 0xfff;
368 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
369 NB.encodeInt16(info, infoOffset + P_FLAGS, pLen
370 | NB.decodeUInt16(info, infoOffset + P_FLAGS) & ~0xfff);
373 private long decodeTS(final int pIdx) {
374 final int base = infoOffset + pIdx;
375 final int sec = NB.decodeInt32(info, base);
376 final int ms = NB.decodeInt32(info, base + 4) / 1000000;
377 return 1000L * sec + ms;
380 private void encodeTS(final int pIdx, final long when) {
381 final int base = infoOffset + pIdx;
382 NB.encodeInt32(info, base, (int) (when / 1000));
383 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000);