2
This code is derived from jgit (http://eclipse.org/jgit).
3
Copyright owners are documented in jgit's IP log.
5
This program and the accompanying materials are made available
6
under the terms of the Eclipse Distribution License v1.0 which
7
accompanies this distribution, is reproduced below, and is
8
available at http://www.eclipse.org/org/documents/edl-v10.php
12
Redistribution and use in source and binary forms, with or
13
without modification, are permitted provided that the following
16
- Redistributions of source code must retain the above copyright
17
notice, this list of conditions and the following disclaimer.
19
- Redistributions in binary form must reproduce the above
20
copyright notice, this list of conditions and the following
21
disclaimer in the documentation and/or other materials provided
22
with the distribution.
24
- Neither the name of the Eclipse Foundation, Inc. nor the
25
names of its contributors may be used to endorse or promote
26
products derived from this software without specific prior
29
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30
CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31
INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32
OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41
ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
50
namespace NGit.Dircache
53
/// A single file (or stage of a file) in a
54
/// <see cref="DirCache">DirCache</see>
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
62
private static readonly byte[] nullpad = new byte[8];
64
/// <summary>The standard (fully merged) stage for an entry.</summary>
65
/// <remarks>The standard (fully merged) stage for an entry.</remarks>
66
public const int STAGE_0 = 0;
68
/// <summary>The base tree revision for an entry.</summary>
69
/// <remarks>The base tree revision for an entry.</remarks>
70
public const int STAGE_1 = 1;
72
/// <summary>The first tree revision (usually called "ours").</summary>
73
/// <remarks>The first tree revision (usually called "ours").</remarks>
74
public const int STAGE_2 = 2;
76
/// <summary>The second tree revision (usually called "theirs").</summary>
77
/// <remarks>The second tree revision (usually called "theirs").</remarks>
78
public const int STAGE_3 = 3;
80
private const int P_MTIME = 8;
82
private const int P_MODE = 24;
84
private const int P_SIZE = 36;
86
private const int P_OBJECTID = 40;
88
private const int P_FLAGS = 60;
90
private const int P_FLAGS2 = 62;
93
/// Mask applied to data in
94
/// <see cref="P_FLAGS">P_FLAGS</see>
95
/// to get the name length.
97
private const int NAME_MASK = unchecked((int)(0xfff));
99
private const int INTENT_TO_ADD = unchecked((int)(0x20000000));
101
private const int SKIP_WORKTREE = unchecked((int)(0x40000000));
103
private const int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE);
105
private const int INFO_LEN = 62;
107
private const int INFO_LEN_EXTENDED = 64;
109
private const int EXTENDED = unchecked((int)(0x40));
111
private const int ASSUME_VALID = unchecked((int)(0x80));
113
/// <summary>In-core flag signaling that the entry should be considered as modified.</summary>
114
/// <remarks>In-core flag signaling that the entry should be considered as modified.</remarks>
115
private const int UPDATE_NEEDED = unchecked((int)(0x1));
117
/// <summary>(Possibly shared) header information storage.</summary>
118
/// <remarks>(Possibly shared) header information storage.</remarks>
119
private readonly byte[] info;
122
/// First location within
123
/// <see cref="info">info</see>
124
/// where our header starts.
126
private readonly int infoOffset;
128
/// <summary>Our encoded path name, from the root of the repository.</summary>
129
/// <remarks>Our encoded path name, from the root of the repository.</remarks>
130
internal readonly byte[] path;
132
/// <summary>Flags which are never stored to disk.</summary>
133
/// <remarks>Flags which are never stored to disk.</remarks>
134
private byte inCoreFlags;
136
/// <exception cref="System.IO.IOException"></exception>
137
internal DirCacheEntry(byte[] sharedInfo, MutableInteger infoAt, InputStream @in,
140
// private static final int P_CTIME = 0;
141
// private static final int P_CTIME_NSEC = 4;
142
// private static final int P_MTIME_NSEC = 12;
143
// private static final int P_DEV = 16;
144
// private static final int P_INO = 20;
145
// private static final int P_UID = 28;
146
// private static final int P_GID = 32;
148
infoOffset = infoAt.value;
149
IOUtil.ReadFully(@in, info, infoOffset, INFO_LEN);
153
len = INFO_LEN_EXTENDED;
154
IOUtil.ReadFully(@in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN);
155
if ((GetExtendedFlags() & ~EXTENDED_FLAGS) != 0)
157
throw new IOException(MessageFormat.Format(JGitText.Get().DIRCUnrecognizedExtendedFlags
158
, GetExtendedFlags().ToString()));
166
md.Update(info, infoOffset, len);
167
int pathLen = NB.DecodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
169
if (pathLen < NAME_MASK)
171
path = new byte[pathLen];
172
IOUtil.ReadFully(@in, path, 0, pathLen);
173
md.Update(path, 0, pathLen);
177
ByteArrayOutputStream tmp = new ByteArrayOutputStream();
179
byte[] buf = new byte[NAME_MASK];
180
IOUtil.ReadFully(@in, buf, 0, NAME_MASK);
188
throw new EOFException(JGitText.Get().shortReadOfBlock);
196
path = tmp.ToByteArray();
197
pathLen = path.Length;
199
// we already skipped 1 '\0' above to break the loop.
200
md.Update(path, 0, pathLen);
201
md.Update(unchecked((byte)0));
203
// Index records are padded out to the next 8 byte alignment
204
// for historical reasons related to how C Git read the files.
206
int actLen = len + pathLen;
207
int expLen = (actLen + 8) & ~7;
208
int padLen = expLen - actLen - skipped;
211
IOUtil.SkipFully(@in, padLen);
212
md.Update(nullpad, 0, padLen);
216
/// <summary>Create an empty entry at stage 0.</summary>
217
/// <remarks>Create an empty entry at stage 0.</remarks>
218
/// <param name="newPath">name of the cache entry.</param>
219
/// <exception cref="System.ArgumentException">
220
/// If the path starts or ends with "/", or contains "//" either
221
/// "\0". These sequences are not permitted in a git tree object
222
/// or DirCache file.
224
public DirCacheEntry(string newPath) : this(Constants.Encode(newPath))
228
/// <summary>Create an empty entry at the specified stage.</summary>
229
/// <remarks>Create an empty entry at the specified stage.</remarks>
230
/// <param name="newPath">name of the cache entry.</param>
231
/// <param name="stage">the stage index of the new entry.</param>
232
/// <exception cref="System.ArgumentException">
233
/// If the path starts or ends with "/", or contains "//" either
234
/// "\0". These sequences are not permitted in a git tree object
235
/// or DirCache file. Or if
236
/// <code>stage</code>
237
/// is outside of the
238
/// range 0..3, inclusive.
240
public DirCacheEntry(string newPath, int stage) : this(Constants.Encode(newPath),
245
/// <summary>Create an empty entry at stage 0.</summary>
246
/// <remarks>Create an empty entry at stage 0.</remarks>
247
/// <param name="newPath">name of the cache entry, in the standard encoding.</param>
248
/// <exception cref="System.ArgumentException">
249
/// If the path starts or ends with "/", or contains "//" either
250
/// "\0". These sequences are not permitted in a git tree object
251
/// or DirCache file.
253
public DirCacheEntry(byte[] newPath) : this(newPath, STAGE_0)
257
/// <summary>Create an empty entry at the specified stage.</summary>
258
/// <remarks>Create an empty entry at the specified stage.</remarks>
259
/// <param name="newPath">name of the cache entry, in the standard encoding.</param>
260
/// <param name="stage">the stage index of the new entry.</param>
261
/// <exception cref="System.ArgumentException">
262
/// If the path starts or ends with "/", or contains "//" either
263
/// "\0". These sequences are not permitted in a git tree object
264
/// or DirCache file. Or if
265
/// <code>stage</code>
266
/// is outside of the
267
/// range 0..3, inclusive.
269
public DirCacheEntry(byte[] newPath, int stage)
271
if (!IsValidPath(newPath))
273
throw new ArgumentException(MessageFormat.Format(JGitText.Get().invalidPath, ToString
276
if (stage < 0 || 3 < stage)
278
throw new ArgumentException(MessageFormat.Format(JGitText.Get().invalidStageForPath
279
, stage, ToString(newPath)));
281
info = new byte[INFO_LEN];
284
int flags = ((stage & unchecked((int)(0x3))) << 12);
285
if (path.Length < NAME_MASK)
287
flags |= path.Length;
293
NB.EncodeInt16(info, infoOffset + P_FLAGS, flags);
296
/// <exception cref="System.IO.IOException"></exception>
297
internal virtual void Write(OutputStream os)
299
int len = IsExtended ? INFO_LEN_EXTENDED : INFO_LEN;
300
int pathLen = path.Length;
301
os.Write(info, infoOffset, len);
302
os.Write(path, 0, pathLen);
303
// Index records are padded out to the next 8 byte alignment
304
// for historical reasons related to how C Git read the files.
306
int actLen = len + pathLen;
307
int expLen = (actLen + 8) & ~7;
308
if (actLen != expLen)
310
os.Write(nullpad, 0, expLen - actLen);
315
/// Is it possible for this entry to be accidentally assumed clean?
317
/// The "racy git" problem happens when a work file can be updated faster
318
/// than the filesystem records file modification timestamps.
321
/// Is it possible for this entry to be accidentally assumed clean?
323
/// The "racy git" problem happens when a work file can be updated faster
324
/// than the filesystem records file modification timestamps. It is possible
325
/// for an application to edit a work file, update the index, then edit it
326
/// again before the filesystem will give the work file a new modification
327
/// timestamp. This method tests to see if file was written out at the same
328
/// time as the index.
330
/// <param name="smudge_s">seconds component of the index's last modified time.</param>
331
/// <param name="smudge_ns">nanoseconds component of the index's last modified time.</param>
332
/// <returns>true if extra careful checks should be used.</returns>
333
public bool MightBeRacilyClean(int smudge_s, int smudge_ns)
335
// If the index has a modification time then it came from disk
336
// and was not generated from scratch in memory. In such cases
337
// the entry is 'racily clean' if the entry's cached modification
338
// time is equal to or later than the index modification time. In
339
// such cases the work file is too close to the index to tell if
340
// it is clean or not based on the modification time alone.
342
int @base = infoOffset + P_MTIME;
343
int mtime = NB.DecodeInt32(info, @base);
344
if (smudge_s == mtime)
346
return smudge_ns <= NB.DecodeInt32(info, @base + 4);
351
/// <summary>Force this entry to no longer match its working tree file.</summary>
353
/// Force this entry to no longer match its working tree file.
355
/// This avoids the "racy git" problem by making this index entry no longer
356
/// match the file in the working directory. Later git will be forced to
357
/// compare the file content to ensure the file matches the working tree.
359
public void SmudgeRacilyClean()
361
// To mark an entry racily clean we set its length to 0 (like native git
362
// does). Entries which are not racily clean and have zero length can be
363
// distinguished from racily clean entries by checking P_OBJECTID
364
// against the SHA1 of empty content. When length is 0 and P_OBJECTID is
365
// different from SHA1 of empty content we know the entry is marked
367
int @base = infoOffset + P_SIZE;
368
Arrays.Fill(info, @base, @base + 4, unchecked((byte)0));
372
/// Check whether this entry has been smudged or not
374
/// If a blob has length 0 we know his id see
375
/// <see cref="NGit.Constants.EMPTY_BLOB_ID">NGit.Constants.EMPTY_BLOB_ID</see>
377
/// has length 0 and an ID different from the one for empty blob we know this
378
/// entry was smudged.
381
/// <code>true</code> if the entry is smudged, <code>false</code>
384
public bool IsSmudged
388
int @base = infoOffset + P_OBJECTID;
389
return (Length == 0) && (Constants.EMPTY_BLOB_ID.CompareTo(info, @base) != 0);
393
internal byte[] IdBuffer
401
internal int IdOffset
405
return infoOffset + P_OBJECTID;
410
/// Is this entry always thought to be unmodified?
412
/// Most entries in the index do not have this flag set.
415
/// Is this entry always thought to be unmodified?
417
/// Most entries in the index do not have this flag set. Users may however
418
/// set them on if the file system stat() costs are too high on this working
419
/// directory, such as on NFS or SMB volumes.
421
/// <returns>true if we must assume the entry is unmodified.</returns>
422
/// <summary>Set the assume valid flag for this entry,</summary>
424
/// true to ignore apparent modifications; false to look at last
425
/// modified to detect file modifications.
427
public virtual bool IsAssumeValid
431
return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0;
438
info[infoOffset + P_FLAGS] |= ASSUME_VALID;
442
info[infoOffset + P_FLAGS] &= unchecked((byte)~ASSUME_VALID);
447
/// <returns>true if this entry should be checked for changes</returns>
448
/// <summary>Set whether this entry must be checked for changes</summary>
450
public virtual bool IsUpdateNeeded
454
return (inCoreFlags & UPDATE_NEEDED) != 0;
458
bool updateNeeded = value;
461
inCoreFlags |= UPDATE_NEEDED;
465
inCoreFlags &= unchecked((byte)~UPDATE_NEEDED);
470
/// <summary>Get the stage of this entry.</summary>
472
/// Get the stage of this entry.
474
/// Entries have one of 4 possible stages: 0-3.
476
/// <returns>the stage of this entry.</returns>
477
public virtual int Stage
481
return (info[infoOffset + P_FLAGS] >> 4) & unchecked((int)(0x3));
485
/// <summary>Returns whether this entry should be skipped from the working tree.</summary>
486
/// <remarks>Returns whether this entry should be skipped from the working tree.</remarks>
487
/// <returns>true if this entry should be skipepd.</returns>
488
public virtual bool IsSkipWorkTree
492
return (GetExtendedFlags() & SKIP_WORKTREE) != 0;
496
/// <summary>Returns whether this entry is intent to be added to the Index.</summary>
497
/// <remarks>Returns whether this entry is intent to be added to the Index.</remarks>
498
/// <returns>true if this entry is intent to add.</returns>
499
public virtual bool IsIntentToAdd
503
return (GetExtendedFlags() & INTENT_TO_ADD) != 0;
509
/// <see cref="NGit.FileMode">NGit.FileMode</see>
510
/// bits for this entry.
512
/// <returns>mode bits for the entry.</returns>
513
/// <seealso cref="NGit.FileMode.FromBits(int)">NGit.FileMode.FromBits(int)</seealso>
514
public virtual int RawMode
518
return NB.DecodeInt32(info, infoOffset + P_MODE);
524
/// <see cref="NGit.FileMode">NGit.FileMode</see>
527
/// <returns>the file mode singleton for this entry.</returns>
528
/// <summary>Set the file mode for this entry.</summary>
529
/// <remarks>Set the file mode for this entry.</remarks>
530
/// <value>the new mode constant.</value>
531
/// <exception cref="System.ArgumentException">
533
/// <code>mode</code>
535
/// <see cref="NGit.FileMode.MISSING">NGit.FileMode.MISSING</see>
537
/// <see cref="NGit.FileMode.TREE">NGit.FileMode.TREE</see>
538
/// , or any other type code not permitted
539
/// in a tree object.
541
public virtual NGit.FileMode FileMode
545
return NGit.FileMode.FromBits(RawMode);
549
NGit.FileMode mode = value;
550
switch (mode.GetBits() & NGit.FileMode.TYPE_MASK)
552
case NGit.FileMode.TYPE_MISSING:
553
case NGit.FileMode.TYPE_TREE:
555
throw new ArgumentException(MessageFormat.Format(JGitText.Get().invalidModeForPath
556
, mode, PathString));
559
NB.EncodeInt32(info, infoOffset + P_MODE, mode.GetBits());
563
/// <summary>Get the cached last modification date of this file, in milliseconds.</summary>
565
/// Get the cached last modification date of this file, in milliseconds.
567
/// One of the indicators that the file has been modified by an application
568
/// changing the working tree is if the last modification time for the file
569
/// differs from the time stored in this entry.
572
/// last modification time of this file, in milliseconds since the
573
/// Java epoch (midnight Jan 1, 1970 UTC).
575
/// <summary>Set the cached last modification date of this file, using milliseconds.</summary>
576
/// <remarks>Set the cached last modification date of this file, using milliseconds.</remarks>
577
/// <value>new cached modification date of the file, in milliseconds.</value>
578
public virtual long LastModified
582
return DecodeTS(P_MTIME);
587
EncodeTS(P_MTIME, when);
591
/// <summary>Get the cached size (in bytes) of this file.</summary>
593
/// Get the cached size (in bytes) of this file.
595
/// One of the indicators that the file has been modified by an application
596
/// changing the working tree is if the size of the file (in bytes) differs
597
/// from the size stored in this entry.
599
/// Note that this is the length of the file in the working directory, which
600
/// may differ from the size of the decompressed blob if work tree filters
601
/// are being used, such as LF<->CRLF conversion.
603
/// <returns>cached size of the working directory file, in bytes.</returns>
604
public virtual int Length
608
return NB.DecodeInt32(info, infoOffset + P_SIZE);
612
/// <summary>Set the cached size (in bytes) of this file.</summary>
613
/// <remarks>Set the cached size (in bytes) of this file.</remarks>
614
/// <param name="sz">new cached size of the file, as bytes.</param>
615
public virtual void SetLength(int sz)
617
NB.EncodeInt32(info, infoOffset + P_SIZE, sz);
620
/// <summary>Set the cached size (in bytes) of this file.</summary>
621
/// <remarks>Set the cached size (in bytes) of this file.</remarks>
622
/// <param name="sz">new cached size of the file, as bytes.</param>
623
/// <exception cref="System.ArgumentException">
624
/// if the size exceeds the 2 GiB barrier imposed by current file
625
/// format limitations.
627
public virtual void SetLength(long sz)
629
if (int.MaxValue <= sz)
631
throw new ArgumentException(MessageFormat.Format(JGitText.Get().sizeExceeds2GB, PathString
637
/// <summary>Obtain the ObjectId for the entry.</summary>
639
/// Obtain the ObjectId for the entry.
641
/// Using this method to compare ObjectId values between entries is
642
/// inefficient as it causes memory allocation.
644
/// <returns>object identifier for the entry.</returns>
645
public virtual ObjectId GetObjectId()
647
return ObjectId.FromRaw(IdBuffer, IdOffset);
650
/// <summary>Set the ObjectId for the entry.</summary>
651
/// <remarks>Set the ObjectId for the entry.</remarks>
652
/// <param name="id">
653
/// new object identifier for the entry. May be
654
/// <see cref="NGit.ObjectId.ZeroId()">NGit.ObjectId.ZeroId()</see>
655
/// to remove the current identifier.
657
public virtual void SetObjectId(AnyObjectId id)
659
id.CopyRawTo(IdBuffer, IdOffset);
662
/// <summary>Set the ObjectId for the entry from the raw binary representation.</summary>
663
/// <remarks>Set the ObjectId for the entry from the raw binary representation.</remarks>
664
/// <param name="bs">
665
/// the raw byte buffer to read from. At least 20 bytes after p
666
/// must be available within this byte array.
668
/// <param name="p">position to read the first byte of data from.</param>
669
public virtual void SetObjectIdFromRaw(byte[] bs, int p)
671
int n = Constants.OBJECT_ID_LENGTH;
672
System.Array.Copy(bs, p, IdBuffer, IdOffset, n);
675
/// <summary>Get the entry's complete path.</summary>
677
/// Get the entry's complete path.
679
/// This method is not very efficient and is primarily meant for debugging
680
/// and final output generation. Applications should try to avoid calling it,
681
/// and if invoked do so only once per interesting entry, where the name is
682
/// absolutely required for correct function.
685
/// complete path of the entry, from the root of the repository. If
686
/// the entry is in a subtree there will be at least one '/' in the
689
public virtual string PathString
693
return ToString(path);
697
/// <summary>Copy the ObjectId and other meta fields from an existing entry.</summary>
699
/// Copy the ObjectId and other meta fields from an existing entry.
701
/// This method copies everything except the path from one entry to another,
702
/// supporting renaming.
704
/// <param name="src">the entry to copy ObjectId and meta fields from.</param>
705
public virtual void CopyMetaData(NGit.Dircache.DirCacheEntry src)
707
int pLen = NB.DecodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK;
708
System.Array.Copy(src.info, src.infoOffset, info, infoOffset, INFO_LEN);
709
NB.EncodeInt16(info, infoOffset + P_FLAGS, pLen | NB.DecodeUInt16(info, infoOffset
710
+ P_FLAGS) & ~NAME_MASK);
713
/// <returns>true if the entry contains extended flags.</returns>
714
internal virtual bool IsExtended
718
return (info[infoOffset + P_FLAGS] & EXTENDED) != 0;
722
private long DecodeTS(int pIdx)
724
int @base = infoOffset + pIdx;
725
int sec = NB.DecodeInt32(info, @base);
726
int ms = NB.DecodeInt32(info, @base + 4) / 1000000;
727
return 1000L * sec + ms;
730
private void EncodeTS(int pIdx, long when)
732
int @base = infoOffset + pIdx;
733
NB.EncodeInt32(info, @base, (int)(when / 1000));
734
NB.EncodeInt32(info, @base + 4, ((int)(when % 1000)) * 1000000);
737
private int GetExtendedFlags()
741
return NB.DecodeUInt16(info, infoOffset + P_FLAGS2) << 16;
749
private static string ToString(byte[] path)
751
return Constants.CHARSET.Decode(ByteBuffer.Wrap(path)).ToString();
754
internal static bool IsValidPath(byte[] path)
756
if (path.Length == 0)
760
// empty path is not permitted.
761
bool componentHasChars = false;
762
foreach (byte c in path)
773
// NUL is never allowed within the path.
774
if (componentHasChars)
776
componentHasChars = false;
787
componentHasChars = true;
792
return componentHasChars;
795
internal static int GetMaximumInfoLength(bool extended)
797
return extended ? INFO_LEN_EXTENDED : INFO_LEN;