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