1 /* 2 * Copyright (C) 2008-2009, Google Inc. 3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> 4 * Copyright (C) 2010, Matthias Sohn <matthias.sohn@sap.com> 5 * Copyright (C) 2010, Christian Halstrick <christian.halstrick@sap.com> 6 * and other copyright owners as documented in the project's IP log. 7 * 8 * This program and the accompanying materials are made available 9 * under the terms of the Eclipse Distribution License v1.0 which 10 * accompanies this distribution, is reproduced below, and is 11 * available at http://www.eclipse.org/org/documents/edl-v10.php 12 * 13 * All rights reserved. 14 * 15 * Redistribution and use in source and binary forms, with or 16 * without modification, are permitted provided that the following 17 * conditions are met: 18 * 19 * - Redistributions of source code must retain the above copyright 20 * notice, this list of conditions and the following disclaimer. 21 * 22 * - Redistributions in binary form must reproduce the above 23 * copyright notice, this list of conditions and the following 24 * disclaimer in the documentation and/or other materials provided 25 * with the distribution. 26 * 27 * - Neither the name of the Eclipse Foundation, Inc. nor the 28 * names of its contributors may be used to endorse or promote 29 * products derived from this software without specific prior 30 * written permission. 31 * 32 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 33 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, 34 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 35 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 36 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 37 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 38 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 39 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 40 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 41 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 42 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 43 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF 44 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 45 */ 46 47 package org.eclipse.jgit.dircache; 48 49 import java.io.ByteArrayOutputStream; 50 import java.io.EOFException; 51 import java.io.IOException; 52 import java.io.InputStream; 53 import java.io.OutputStream; 54 import java.nio.ByteBuffer; 55 import java.security.MessageDigest; 56 import java.text.MessageFormat; 57 import java.util.Arrays; 58 59 import org.eclipse.jgit.errors.CorruptObjectException; 60 import org.eclipse.jgit.internal.JGitText; 61 import org.eclipse.jgit.lib.AnyObjectId; 62 import org.eclipse.jgit.lib.Constants; 63 import org.eclipse.jgit.lib.FileMode; 64 import org.eclipse.jgit.lib.ObjectId; 65 import org.eclipse.jgit.util.IO; 66 import org.eclipse.jgit.util.MutableInteger; 67 import org.eclipse.jgit.util.NB; 68 import org.eclipse.jgit.util.SystemReader; 69 70 /** 71 * A single file (or stage of a file) in a 72 * {@link org.eclipse.jgit.dircache.DirCache}. 73 * <p> 74 * An entry represents exactly one stage of a file. If a file path is unmerged 75 * then multiple DirCacheEntry instances may appear for the same path name. 76 */ 77 public class DirCacheEntry { 78 private static final byte[] nullpad = new byte[8]; 79 80 /** The standard (fully merged) stage for an entry. */ 81 public static final int STAGE_0 = 0; 82 83 /** The base tree revision for an entry. */ 84 public static final int STAGE_1 = 1; 85 86 /** The first tree revision (usually called "ours"). */ 87 public static final int STAGE_2 = 2; 88 89 /** The second tree revision (usually called "theirs"). */ 90 public static final int STAGE_3 = 3; 91 92 private static final int P_CTIME = 0; 93 94 // private static final int P_CTIME_NSEC = 4; 95 96 private static final int P_MTIME = 8; 97 98 // private static final int P_MTIME_NSEC = 12; 99 100 // private static final int P_DEV = 16; 101 102 // private static final int P_INO = 20; 103 104 private static final int P_MODE = 24; 105 106 // private static final int P_UID = 28; 107 108 // private static final int P_GID = 32; 109 110 private static final int P_SIZE = 36; 111 112 private static final int P_OBJECTID = 40; 113 114 private static final int P_FLAGS = 60; 115 private static final int P_FLAGS2 = 62; 116 117 /** Mask applied to data in {@link #P_FLAGS} to get the name length. */ 118 private static final int NAME_MASK = 0xfff; 119 120 private static final int INTENT_TO_ADD = 0x20000000; 121 private static final int SKIP_WORKTREE = 0x40000000; 122 private static final int EXTENDED_FLAGS = (INTENT_TO_ADD | SKIP_WORKTREE); 123 124 private static final int INFO_LEN = 62; 125 private static final int INFO_LEN_EXTENDED = 64; 126 127 private static final int EXTENDED = 0x40; 128 private static final int ASSUME_VALID = 0x80; 129 130 /** In-core flag signaling that the entry should be considered as modified. */ 131 private static final int UPDATE_NEEDED = 0x1; 132 133 /** (Possibly shared) header information storage. */ 134 private final byte[] info; 135 136 /** First location within {@link #info} where our header starts. */ 137 private final int infoOffset; 138 139 /** Our encoded path name, from the root of the repository. */ 140 final byte[] path; 141 142 /** Flags which are never stored to disk. */ 143 private byte inCoreFlags; 144 145 DirCacheEntry(final byte[] sharedInfo, final MutableInteger infoAt, 146 final InputStream in, final MessageDigest md, final int smudge_s, 147 final int smudge_ns) throws IOException { 148 info = sharedInfo; 149 infoOffset = infoAt.value; 150 151 IO.readFully(in, info, infoOffset, INFO_LEN); 152 153 final int len; 154 if (isExtended()) { 155 len = INFO_LEN_EXTENDED; 156 IO.readFully(in, info, infoOffset + INFO_LEN, INFO_LEN_EXTENDED - INFO_LEN); 157 158 if ((getExtendedFlags() & ~EXTENDED_FLAGS) != 0) 159 throw new IOException(MessageFormat.format(JGitText.get() 160 .DIRCUnrecognizedExtendedFlags, String.valueOf(getExtendedFlags()))); 161 } else 162 len = INFO_LEN; 163 164 infoAt.value += len; 165 md.update(info, infoOffset, len); 166 167 int pathLen = NB.decodeUInt16(info, infoOffset + P_FLAGS) & NAME_MASK; 168 int skipped = 0; 169 if (pathLen < NAME_MASK) { 170 path = new byte[pathLen]; 171 IO.readFully(in, path, 0, pathLen); 172 md.update(path, 0, pathLen); 173 } else { 174 final ByteArrayOutputStream tmp = new ByteArrayOutputStream(); 175 { 176 final byte[] buf = new byte[NAME_MASK]; 177 IO.readFully(in, buf, 0, NAME_MASK); 178 tmp.write(buf); 179 } 180 for (;;) { 181 final int c = in.read(); 182 if (c < 0) 183 throw new EOFException(JGitText.get().shortReadOfBlock); 184 if (c == 0) 185 break; 186 tmp.write(c); 187 } 188 path = tmp.toByteArray(); 189 pathLen = path.length; 190 skipped = 1; // we already skipped 1 '\0' above to break the loop. 191 md.update(path, 0, pathLen); 192 md.update((byte) 0); 193 } 194 195 try { 196 checkPath(path); 197 } catch (InvalidPathException e) { 198 CorruptObjectException p = 199 new CorruptObjectException(e.getMessage()); 200 if (e.getCause() != null) 201 p.initCause(e.getCause()); 202 throw p; 203 } 204 205 // Index records are padded out to the next 8 byte alignment 206 // for historical reasons related to how C Git read the files. 207 // 208 final int actLen = len + pathLen; 209 final int expLen = (actLen + 8) & ~7; 210 final int padLen = expLen - actLen - skipped; 211 if (padLen > 0) { 212 IO.skipFully(in, padLen); 213 md.update(nullpad, 0, padLen); 214 } 215 216 if (mightBeRacilyClean(smudge_s, smudge_ns)) 217 smudgeRacilyClean(); 218 } 219 220 /** 221 * Create an empty entry at stage 0. 222 * 223 * @param newPath 224 * name of the cache entry. 225 * @throws java.lang.IllegalArgumentException 226 * If the path starts or ends with "/", or contains "//" either 227 * "\0". These sequences are not permitted in a git tree object 228 * or DirCache file. 229 */ 230 public DirCacheEntry(String newPath) { 231 this(Constants.encode(newPath), STAGE_0); 232 } 233 234 /** 235 * Create an empty entry at the specified stage. 236 * 237 * @param newPath 238 * name of the cache entry. 239 * @param stage 240 * the stage index of the new entry. 241 * @throws java.lang.IllegalArgumentException 242 * If the path starts or ends with "/", or contains "//" either 243 * "\0". These sequences are not permitted in a git tree object 244 * or DirCache file. Or if {@code stage} is outside of the 245 * range 0..3, inclusive. 246 */ 247 public DirCacheEntry(String newPath, int stage) { 248 this(Constants.encode(newPath), stage); 249 } 250 251 /** 252 * Create an empty entry at stage 0. 253 * 254 * @param newPath 255 * name of the cache entry, in the standard encoding. 256 * @throws java.lang.IllegalArgumentException 257 * If the path starts or ends with "/", or contains "//" either 258 * "\0". These sequences are not permitted in a git tree object 259 * or DirCache file. 260 */ 261 public DirCacheEntry(byte[] newPath) { 262 this(newPath, STAGE_0); 263 } 264 265 /** 266 * Create an empty entry at the specified stage. 267 * 268 * @param path 269 * name of the cache entry, in the standard encoding. 270 * @param stage 271 * the stage index of the new entry. 272 * @throws java.lang.IllegalArgumentException 273 * If the path starts or ends with "/", or contains "//" either 274 * "\0". These sequences are not permitted in a git tree object 275 * or DirCache file. Or if {@code stage} is outside of the 276 * range 0..3, inclusive. 277 */ 278 @SuppressWarnings("boxing") 279 public DirCacheEntry(byte[] path, int stage) { 280 checkPath(path); 281 if (stage < 0 || 3 < stage) 282 throw new IllegalArgumentException(MessageFormat.format( 283 JGitText.get().invalidStageForPath, 284 stage, toString(path))); 285 286 info = new byte[INFO_LEN]; 287 infoOffset = 0; 288 this.path = path; 289 290 int flags = ((stage & 0x3) << 12); 291 if (path.length < NAME_MASK) 292 flags |= path.length; 293 else 294 flags |= NAME_MASK; 295 NB.encodeInt16(info, infoOffset + P_FLAGS, flags); 296 } 297 298 /** 299 * Duplicate DirCacheEntry with same path and copied info. 300 * <p> 301 * The same path buffer is reused (avoiding copying), however a new info 302 * buffer is created and its contents are copied. 303 * 304 * @param src 305 * entry to clone. 306 * @since 4.2 307 */ 308 public DirCacheEntry(DirCacheEntry src) { 309 path = src.path; 310 info = new byte[INFO_LEN]; 311 infoOffset = 0; 312 System.arraycopy(src.info, src.infoOffset, info, 0, INFO_LEN); 313 } 314 315 void write(OutputStream os) throws IOException { 316 final int len = isExtended() ? INFO_LEN_EXTENDED : INFO_LEN; 317 final int pathLen = path.length; 318 os.write(info, infoOffset, len); 319 os.write(path, 0, pathLen); 320 321 // Index records are padded out to the next 8 byte alignment 322 // for historical reasons related to how C Git read the files. 323 // 324 final int actLen = len + pathLen; 325 final int expLen = (actLen + 8) & ~7; 326 if (actLen != expLen) 327 os.write(nullpad, 0, expLen - actLen); 328 } 329 330 /** 331 * Is it possible for this entry to be accidentally assumed clean? 332 * <p> 333 * The "racy git" problem happens when a work file can be updated faster 334 * than the filesystem records file modification timestamps. It is possible 335 * for an application to edit a work file, update the index, then edit it 336 * again before the filesystem will give the work file a new modification 337 * timestamp. This method tests to see if file was written out at the same 338 * time as the index. 339 * 340 * @param smudge_s 341 * seconds component of the index's last modified time. 342 * @param smudge_ns 343 * nanoseconds component of the index's last modified time. 344 * @return true if extra careful checks should be used. 345 */ 346 public final boolean mightBeRacilyClean(int smudge_s, int smudge_ns) { 347 // If the index has a modification time then it came from disk 348 // and was not generated from scratch in memory. In such cases 349 // the entry is 'racily clean' if the entry's cached modification 350 // time is equal to or later than the index modification time. In 351 // such cases the work file is too close to the index to tell if 352 // it is clean or not based on the modification time alone. 353 // 354 final int base = infoOffset + P_MTIME; 355 final int mtime = NB.decodeInt32(info, base); 356 if (smudge_s == mtime) 357 return smudge_ns <= NB.decodeInt32(info, base + 4); 358 return false; 359 } 360 361 /** 362 * Force this entry to no longer match its working tree file. 363 * <p> 364 * This avoids the "racy git" problem by making this index entry no longer 365 * match the file in the working directory. Later git will be forced to 366 * compare the file content to ensure the file matches the working tree. 367 */ 368 public final void smudgeRacilyClean() { 369 // To mark an entry racily clean we set its length to 0 (like native git 370 // does). Entries which are not racily clean and have zero length can be 371 // distinguished from racily clean entries by checking P_OBJECTID 372 // against the SHA1 of empty content. When length is 0 and P_OBJECTID is 373 // different from SHA1 of empty content we know the entry is marked 374 // racily clean 375 final int base = infoOffset + P_SIZE; 376 Arrays.fill(info, base, base + 4, (byte) 0); 377 } 378 379 /** 380 * Check whether this entry has been smudged or not 381 * <p> 382 * If a blob has length 0 we know its id, see 383 * {@link org.eclipse.jgit.lib.Constants#EMPTY_BLOB_ID}. If an entry has 384 * length 0 and an ID different from the one for empty blob we know this 385 * entry was smudged. 386 * 387 * @return <code>true</code> if the entry is smudged, <code>false</code> 388 * otherwise 389 */ 390 public final boolean isSmudged() { 391 final int base = infoOffset + P_OBJECTID; 392 return (getLength() == 0) && (Constants.EMPTY_BLOB_ID.compareTo(info, base) != 0); 393 } 394 395 final byte[] idBuffer() { 396 return info; 397 } 398 399 final int idOffset() { 400 return infoOffset + P_OBJECTID; 401 } 402 403 /** 404 * Is this entry always thought to be unmodified? 405 * <p> 406 * Most entries in the index do not have this flag set. Users may however 407 * set them on if the file system stat() costs are too high on this working 408 * directory, such as on NFS or SMB volumes. 409 * 410 * @return true if we must assume the entry is unmodified. 411 */ 412 public boolean isAssumeValid() { 413 return (info[infoOffset + P_FLAGS] & ASSUME_VALID) != 0; 414 } 415 416 /** 417 * Set the assume valid flag for this entry, 418 * 419 * @param assume 420 * true to ignore apparent modifications; false to look at last 421 * modified to detect file modifications. 422 */ 423 public void setAssumeValid(boolean assume) { 424 if (assume) 425 info[infoOffset + P_FLAGS] |= ASSUME_VALID; 426 else 427 info[infoOffset + P_FLAGS] &= ~ASSUME_VALID; 428 } 429 430 /** 431 * Whether this entry should be checked for changes 432 * 433 * @return {@code true} if this entry should be checked for changes 434 */ 435 public boolean isUpdateNeeded() { 436 return (inCoreFlags & UPDATE_NEEDED) != 0; 437 } 438 439 /** 440 * Set whether this entry must be checked for changes 441 * 442 * @param updateNeeded 443 * whether this entry must be checked for changes 444 */ 445 public void setUpdateNeeded(boolean updateNeeded) { 446 if (updateNeeded) 447 inCoreFlags |= UPDATE_NEEDED; 448 else 449 inCoreFlags &= ~UPDATE_NEEDED; 450 } 451 452 /** 453 * Get the stage of this entry. 454 * <p> 455 * Entries have one of 4 possible stages: 0-3. 456 * 457 * @return the stage of this entry. 458 */ 459 public int getStage() { 460 return (info[infoOffset + P_FLAGS] >>> 4) & 0x3; 461 } 462 463 /** 464 * Returns whether this entry should be skipped from the working tree. 465 * 466 * @return true if this entry should be skipepd. 467 */ 468 public boolean isSkipWorkTree() { 469 return (getExtendedFlags() & SKIP_WORKTREE) != 0; 470 } 471 472 /** 473 * Returns whether this entry is intent to be added to the Index. 474 * 475 * @return true if this entry is intent to add. 476 */ 477 public boolean isIntentToAdd() { 478 return (getExtendedFlags() & INTENT_TO_ADD) != 0; 479 } 480 481 /** 482 * Returns whether this entry is in the fully-merged stage (0). 483 * 484 * @return true if this entry is merged 485 * @since 2.2 486 */ 487 public boolean isMerged() { 488 return getStage() == STAGE_0; 489 } 490 491 /** 492 * Obtain the raw {@link org.eclipse.jgit.lib.FileMode} bits for this entry. 493 * 494 * @return mode bits for the entry. 495 * @see FileMode#fromBits(int) 496 */ 497 public int getRawMode() { 498 return NB.decodeInt32(info, infoOffset + P_MODE); 499 } 500 501 /** 502 * Obtain the {@link org.eclipse.jgit.lib.FileMode} for this entry. 503 * 504 * @return the file mode singleton for this entry. 505 */ 506 public FileMode getFileMode() { 507 return FileMode.fromBits(getRawMode()); 508 } 509 510 /** 511 * Set the file mode for this entry. 512 * 513 * @param mode 514 * the new mode constant. 515 * @throws java.lang.IllegalArgumentException 516 * If {@code mode} is 517 * {@link org.eclipse.jgit.lib.FileMode#MISSING}, 518 * {@link org.eclipse.jgit.lib.FileMode#TREE}, or any other type 519 * code not permitted in a tree object. 520 */ 521 public void setFileMode(FileMode mode) { 522 switch (mode.getBits() & FileMode.TYPE_MASK) { 523 case FileMode.TYPE_MISSING: 524 case FileMode.TYPE_TREE: 525 throw new IllegalArgumentException(MessageFormat.format( 526 JGitText.get().invalidModeForPath, mode, getPathString())); 527 } 528 NB.encodeInt32(info, infoOffset + P_MODE, mode.getBits()); 529 } 530 531 void setFileMode(int mode) { 532 NB.encodeInt32(info, infoOffset + P_MODE, mode); 533 } 534 535 /** 536 * Get the cached creation time of this file, in milliseconds. 537 * 538 * @return cached creation time of this file, in milliseconds since the 539 * Java epoch (midnight Jan 1, 1970 UTC). 540 */ 541 public long getCreationTime() { 542 return decodeTS(P_CTIME); 543 } 544 545 /** 546 * Set the cached creation time of this file, using milliseconds. 547 * 548 * @param when 549 * new cached creation time of the file, in milliseconds. 550 */ 551 public void setCreationTime(long when) { 552 encodeTS(P_CTIME, when); 553 } 554 555 /** 556 * Get the cached last modification date of this file, in milliseconds. 557 * <p> 558 * One of the indicators that the file has been modified by an application 559 * changing the working tree is if the last modification time for the file 560 * differs from the time stored in this entry. 561 * 562 * @return last modification time of this file, in milliseconds since the 563 * Java epoch (midnight Jan 1, 1970 UTC). 564 */ 565 public long getLastModified() { 566 return decodeTS(P_MTIME); 567 } 568 569 /** 570 * Set the cached last modification date of this file, using milliseconds. 571 * 572 * @param when 573 * new cached modification date of the file, in milliseconds. 574 */ 575 public void setLastModified(long when) { 576 encodeTS(P_MTIME, when); 577 } 578 579 /** 580 * Get the cached size (mod 4 GB) (in bytes) of this file. 581 * <p> 582 * One of the indicators that the file has been modified by an application 583 * changing the working tree is if the size of the file (in bytes) differs 584 * from the size stored in this entry. 585 * <p> 586 * Note that this is the length of the file in the working directory, which 587 * may differ from the size of the decompressed blob if work tree filters 588 * are being used, such as LF<->CRLF conversion. 589 * <p> 590 * Note also that for very large files, this is the size of the on-disk file 591 * truncated to 32 bits, i.e. modulo 4294967296. If that value is larger 592 * than 2GB, it will appear negative. 593 * 594 * @return cached size of the working directory file, in bytes. 595 */ 596 public int getLength() { 597 return NB.decodeInt32(info, infoOffset + P_SIZE); 598 } 599 600 /** 601 * Set the cached size (in bytes) of this file. 602 * 603 * @param sz 604 * new cached size of the file, as bytes. If the file is larger 605 * than 2G, cast it to (int) before calling this method. 606 */ 607 public void setLength(int sz) { 608 NB.encodeInt32(info, infoOffset + P_SIZE, sz); 609 } 610 611 /** 612 * Set the cached size (in bytes) of this file. 613 * 614 * @param sz 615 * new cached size of the file, as bytes. 616 */ 617 public void setLength(long sz) { 618 setLength((int) sz); 619 } 620 621 /** 622 * Obtain the ObjectId for the entry. 623 * <p> 624 * Using this method to compare ObjectId values between entries is 625 * inefficient as it causes memory allocation. 626 * 627 * @return object identifier for the entry. 628 */ 629 public ObjectId getObjectId() { 630 return ObjectId.fromRaw(idBuffer(), idOffset()); 631 } 632 633 /** 634 * Set the ObjectId for the entry. 635 * 636 * @param id 637 * new object identifier for the entry. May be 638 * {@link org.eclipse.jgit.lib.ObjectId#zeroId()} to remove the 639 * current identifier. 640 */ 641 public void setObjectId(AnyObjectId id) { 642 id.copyRawTo(idBuffer(), idOffset()); 643 } 644 645 /** 646 * Set the ObjectId for the entry from the raw binary representation. 647 * 648 * @param bs 649 * the raw byte buffer to read from. At least 20 bytes after p 650 * must be available within this byte array. 651 * @param p 652 * position to read the first byte of data from. 653 */ 654 public void setObjectIdFromRaw(byte[] bs, int p) { 655 final int n = Constants.OBJECT_ID_LENGTH; 656 System.arraycopy(bs, p, idBuffer(), idOffset(), n); 657 } 658 659 /** 660 * Get the entry's complete path. 661 * <p> 662 * This method is not very efficient and is primarily meant for debugging 663 * and final output generation. Applications should try to avoid calling it, 664 * and if invoked do so only once per interesting entry, where the name is 665 * absolutely required for correct function. 666 * 667 * @return complete path of the entry, from the root of the repository. If 668 * the entry is in a subtree there will be at least one '/' in the 669 * returned string. 670 */ 671 public String getPathString() { 672 return toString(path); 673 } 674 675 /** 676 * Get a copy of the entry's raw path bytes. 677 * 678 * @return raw path bytes. 679 * @since 3.4 680 */ 681 public byte[] getRawPath() { 682 return path.clone(); 683 } 684 685 /** 686 * {@inheritDoc} 687 * <p> 688 * Use for debugging only ! 689 */ 690 @SuppressWarnings("nls") 691 @Override 692 public String toString() { 693 return getFileMode() + " " + getLength() + " " + getLastModified() 694 + " " + getObjectId() + " " + getStage() + " " 695 + getPathString() + "\n"; 696 } 697 698 /** 699 * Copy the ObjectId and other meta fields from an existing entry. 700 * <p> 701 * This method copies everything except the path from one entry to another, 702 * supporting renaming. 703 * 704 * @param src 705 * the entry to copy ObjectId and meta fields from. 706 */ 707 public void copyMetaData(DirCacheEntry src) { 708 copyMetaData(src, false); 709 } 710 711 /** 712 * Copy the ObjectId and other meta fields from an existing entry. 713 * <p> 714 * This method copies everything except the path and possibly stage from one 715 * entry to another, supporting renaming. 716 * 717 * @param src 718 * the entry to copy ObjectId and meta fields from. 719 * @param keepStage 720 * if true, the stage attribute will not be copied 721 */ 722 void copyMetaData(DirCacheEntry src, boolean keepStage) { 723 int origflags = NB.decodeUInt16(info, infoOffset + P_FLAGS); 724 int newflags = NB.decodeUInt16(src.info, src.infoOffset + P_FLAGS); 725 System.arraycopy(src.info, src.infoOffset, info, infoOffset, INFO_LEN); 726 final int pLen = origflags & NAME_MASK; 727 final int SHIFTED_STAGE_MASK = 0x3 << 12; 728 final int pStageShifted; 729 if (keepStage) 730 pStageShifted = origflags & SHIFTED_STAGE_MASK; 731 else 732 pStageShifted = newflags & SHIFTED_STAGE_MASK; 733 NB.encodeInt16(info, infoOffset + P_FLAGS, pStageShifted | pLen 734 | (newflags & ~NAME_MASK & ~SHIFTED_STAGE_MASK)); 735 } 736 737 /** 738 * @return true if the entry contains extended flags. 739 */ 740 boolean isExtended() { 741 return (info[infoOffset + P_FLAGS] & EXTENDED) != 0; 742 } 743 744 private long decodeTS(int pIdx) { 745 final int base = infoOffset + pIdx; 746 final int sec = NB.decodeInt32(info, base); 747 final int ms = NB.decodeInt32(info, base + 4) / 1000000; 748 return 1000L * sec + ms; 749 } 750 751 private void encodeTS(int pIdx, long when) { 752 final int base = infoOffset + pIdx; 753 NB.encodeInt32(info, base, (int) (when / 1000)); 754 NB.encodeInt32(info, base + 4, ((int) (when % 1000)) * 1000000); 755 } 756 757 private int getExtendedFlags() { 758 if (isExtended()) 759 return NB.decodeUInt16(info, infoOffset + P_FLAGS2) << 16; 760 else 761 return 0; 762 } 763 764 private static void checkPath(byte[] path) { 765 try { 766 SystemReader.getInstance().checkPath(path); 767 } catch (CorruptObjectException e) { 768 InvalidPathException p = new InvalidPathException(toString(path)); 769 p.initCause(e); 770 throw p; 771 } 772 } 773 774 static String toString(byte[] path) { 775 return Constants.CHARSET.decode(ByteBuffer.wrap(path)).toString(); 776 } 777 778 static int getMaximumInfoLength(boolean extended) { 779 return extended ? INFO_LEN_EXTENDED : INFO_LEN; 780 } 781 }