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