1 /*
2 * Copyright (C) 2008-2010, Google Inc.
3 * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org>
4 * Copyright (C) 2011, Matthias Sohn <matthias.sohn@sap.com>
5 * and other copyright owners as documented in the project's IP log.
6 *
7 * This program and the accompanying materials are made available
8 * under the terms of the Eclipse Distribution License v1.0 which
9 * accompanies this distribution, is reproduced below, and is
10 * available at http://www.eclipse.org/org/documents/edl-v10.php
11 *
12 * All rights reserved.
13 *
14 * Redistribution and use in source and binary forms, with or
15 * without modification, are permitted provided that the following
16 * conditions are met:
17 *
18 * - Redistributions of source code must retain the above copyright
19 * notice, this list of conditions and the following disclaimer.
20 *
21 * - Redistributions in binary form must reproduce the above
22 * copyright notice, this list of conditions and the following
23 * disclaimer in the documentation and/or other materials provided
24 * with the distribution.
25 *
26 * - Neither the name of the Eclipse Foundation, Inc. nor the
27 * names of its contributors may be used to endorse or promote
28 * products derived from this software without specific prior
29 * written permission.
30 *
31 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
32 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
33 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
34 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
35 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
36 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
37 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
38 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
39 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
40 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
41 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
42 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
43 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
44 */
45
46 package org.eclipse.jgit.dircache;
47
48 import static java.nio.charset.StandardCharsets.ISO_8859_1;
49
50 import java.io.BufferedInputStream;
51 import java.io.BufferedOutputStream;
52 import java.io.EOFException;
53 import java.io.File;
54 import java.io.FileNotFoundException;
55 import java.io.IOException;
56 import java.io.InputStream;
57 import java.io.OutputStream;
58 import java.security.DigestOutputStream;
59 import java.security.MessageDigest;
60 import java.text.MessageFormat;
61 import java.time.Instant;
62 import java.util.ArrayList;
63 import java.util.Arrays;
64 import java.util.Comparator;
65 import java.util.List;
66
67 import org.eclipse.jgit.errors.CorruptObjectException;
68 import org.eclipse.jgit.errors.IndexReadException;
69 import org.eclipse.jgit.errors.LockFailedException;
70 import org.eclipse.jgit.errors.UnmergedPathException;
71 import org.eclipse.jgit.events.IndexChangedEvent;
72 import org.eclipse.jgit.events.IndexChangedListener;
73 import org.eclipse.jgit.internal.JGitText;
74 import org.eclipse.jgit.internal.storage.file.FileSnapshot;
75 import org.eclipse.jgit.internal.storage.file.LockFile;
76 import org.eclipse.jgit.lib.AnyObjectId;
77 import org.eclipse.jgit.lib.Constants;
78 import org.eclipse.jgit.lib.ObjectId;
79 import org.eclipse.jgit.lib.ObjectInserter;
80 import org.eclipse.jgit.lib.ObjectReader;
81 import org.eclipse.jgit.lib.Repository;
82 import org.eclipse.jgit.treewalk.FileTreeIterator;
83 import org.eclipse.jgit.treewalk.TreeWalk;
84 import org.eclipse.jgit.treewalk.TreeWalk.OperationType;
85 import org.eclipse.jgit.treewalk.filter.PathFilterGroup;
86 import org.eclipse.jgit.util.FS;
87 import org.eclipse.jgit.util.IO;
88 import org.eclipse.jgit.util.MutableInteger;
89 import org.eclipse.jgit.util.NB;
90 import org.eclipse.jgit.util.TemporaryBuffer;
91 import org.eclipse.jgit.util.io.SilentFileInputStream;
92
93 /**
94 * Support for the Git dircache (aka index file).
95 * <p>
96 * The index file keeps track of which objects are currently checked out in the
97 * working directory, and the last modified time of those working files. Changes
98 * in the working directory can be detected by comparing the modification times
99 * to the cached modification time within the index file.
100 * <p>
101 * Index files are also used during merges, where the merge happens within the
102 * index file first, and the working directory is updated as a post-merge step.
103 * Conflicts are stored in the index file to allow tool (and human) based
104 * resolutions to be easily performed.
105 */
106 public class DirCache {
107 private static final byte[] SIG_DIRC = { 'D', 'I', 'R', 'C' };
108
109 private static final int EXT_TREE = 0x54524545 /* 'TREE' */;
110
111 private static final DirCacheEntry[] NO_ENTRIES = {};
112
113 private static final byte[] NO_CHECKSUM = {};
114
115 static final Comparator<DirCacheEntry> ENT_CMP = (DirCacheEntry o1,
116 DirCacheEntry o2) -> {
117 final int cr = cmp(o1, o2);
118 if (cr != 0)
119 return cr;
120 return o1.getStage() - o2.getStage();
121 };
122
123 static int cmp(DirCacheEntry../../../../org/eclipse/jgit/dircache/DirCacheEntry.html#DirCacheEntry">DirCacheEntry a, DirCacheEntry b) {
124 return cmp(a.path, a.path.length, b);
125 }
126
127 static int cmp(byte[] aPath, int aLen, DirCacheEntry b) {
128 return cmp(aPath, aLen, b.path, b.path.length);
129 }
130
131 static int cmp(final byte[] aPath, final int aLen, final byte[] bPath,
132 final int bLen) {
133 for (int cPos = 0; cPos < aLen && cPos < bLen; cPos++) {
134 final int cmp = (aPath[cPos] & 0xff) - (bPath[cPos] & 0xff);
135 if (cmp != 0)
136 return cmp;
137 }
138 return aLen - bLen;
139 }
140
141 /**
142 * Create a new empty index which is never stored on disk.
143 *
144 * @return an empty cache which has no backing store file. The cache may not
145 * be read or written, but it may be queried and updated (in
146 * memory).
147 */
148 public static DirCache newInCore() {
149 return new DirCache(null, null);
150 }
151
152 /**
153 * Create a new in memory index read from the contents of a tree.
154 *
155 * @param reader
156 * reader to access the tree objects from a repository.
157 * @param treeId
158 * tree to read. Must identify a tree, not a tree-ish.
159 * @return a new cache which has no backing store file, but contains the
160 * contents of {@code treeId}.
161 * @throws java.io.IOException
162 * one or more trees not available from the ObjectReader.
163 * @since 4.2
164 */
165 public static DirCache read(ObjectReader reader, AnyObjectId treeId)
166 throws IOException {
167 DirCache d = newInCore();
168 DirCacheBuilder b = d.builder();
169 b.addTree(null, DirCacheEntry.STAGE_0, reader, treeId);
170 b.finish();
171 return d;
172 }
173
174 /**
175 * Create a new in-core index representation and read an index from disk.
176 * <p>
177 * The new index will be read before it is returned to the caller. Read
178 * failures are reported as exceptions and therefore prevent the method from
179 * returning a partially populated index.
180 *
181 * @param repository
182 * repository containing the index to read
183 * @return a cache representing the contents of the specified index file (if
184 * it exists) or an empty cache if the file does not exist.
185 * @throws java.io.IOException
186 * the index file is present but could not be read.
187 * @throws org.eclipse.jgit.errors.CorruptObjectException
188 * the index file is using a format or extension that this
189 * library does not support.
190 */
191 public static DirCache read(Repository repository)
192 throws CorruptObjectException, IOException {
193 final DirCache c = read(repository.getIndexFile(), repository.getFS());
194 c.repository = repository;
195 return c;
196 }
197
198 /**
199 * Create a new in-core index representation and read an index from disk.
200 * <p>
201 * The new index will be read before it is returned to the caller. Read
202 * failures are reported as exceptions and therefore prevent the method from
203 * returning a partially populated index.
204 *
205 * @param indexLocation
206 * location of the index file on disk.
207 * @param fs
208 * the file system abstraction which will be necessary to perform
209 * certain file system operations.
210 * @return a cache representing the contents of the specified index file (if
211 * it exists) or an empty cache if the file does not exist.
212 * @throws java.io.IOException
213 * the index file is present but could not be read.
214 * @throws org.eclipse.jgit.errors.CorruptObjectException
215 * the index file is using a format or extension that this
216 * library does not support.
217 */
218 public static DirCache read(File indexLocation, FS fs)
219 throws CorruptObjectException, IOException {
220 final DirCache/DirCache.html#DirCache">DirCache c = new DirCache(indexLocation, fs);
221 c.read();
222 return c;
223 }
224
225 /**
226 * Create a new in-core index representation, lock it, and read from disk.
227 * <p>
228 * The new index will be locked and then read before it is returned to the
229 * caller. Read failures are reported as exceptions and therefore prevent
230 * the method from returning a partially populated index. On read failure,
231 * the lock is released.
232 *
233 * @param indexLocation
234 * location of the index file on disk.
235 * @param fs
236 * the file system abstraction which will be necessary to perform
237 * certain file system operations.
238 * @return a cache representing the contents of the specified index file (if
239 * it exists) or an empty cache if the file does not exist.
240 * @throws java.io.IOException
241 * the index file is present but could not be read, or the lock
242 * could not be obtained.
243 * @throws org.eclipse.jgit.errors.CorruptObjectException
244 * the index file is using a format or extension that this
245 * library does not support.
246 */
247 public static DirCache lock(File indexLocation, FS fs)
248 throws CorruptObjectException, IOException {
249 final DirCache/DirCache.html#DirCache">DirCache c = new DirCache(indexLocation, fs);
250 if (!c.lock())
251 throw new LockFailedException(indexLocation);
252
253 try {
254 c.read();
255 } catch (IOException | RuntimeException | Error e) {
256 c.unlock();
257 throw e;
258 }
259
260 return c;
261 }
262
263 /**
264 * Create a new in-core index representation, lock it, and read from disk.
265 * <p>
266 * The new index will be locked and then read before it is returned to the
267 * caller. Read failures are reported as exceptions and therefore prevent
268 * the method from returning a partially populated index. On read failure,
269 * the lock is released.
270 *
271 * @param repository
272 * repository containing the index to lock and read
273 * @param indexChangedListener
274 * listener to be informed when DirCache is committed
275 * @return a cache representing the contents of the specified index file (if
276 * it exists) or an empty cache if the file does not exist.
277 * @throws java.io.IOException
278 * the index file is present but could not be read, or the lock
279 * could not be obtained.
280 * @throws org.eclipse.jgit.errors.CorruptObjectException
281 * the index file is using a format or extension that this
282 * library does not support.
283 * @since 2.0
284 */
285 public static DirCache lock(final Repository repository,
286 final IndexChangedListener indexChangedListener)
287 throws CorruptObjectException, IOException {
288 DirCache c = lock(repository.getIndexFile(), repository.getFS(),
289 indexChangedListener);
290 c.repository = repository;
291 return c;
292 }
293
294 /**
295 * Create a new in-core index representation, lock it, and read from disk.
296 * <p>
297 * The new index will be locked and then read before it is returned to the
298 * caller. Read failures are reported as exceptions and therefore prevent
299 * the method from returning a partially populated index. On read failure,
300 * the lock is released.
301 *
302 * @param indexLocation
303 * location of the index file on disk.
304 * @param fs
305 * the file system abstraction which will be necessary to perform
306 * certain file system operations.
307 * @param indexChangedListener
308 * listener to be informed when DirCache is committed
309 * @return a cache representing the contents of the specified index file (if
310 * it exists) or an empty cache if the file does not exist.
311 * @throws java.io.IOException
312 * the index file is present but could not be read, or the lock
313 * could not be obtained.
314 * @throws org.eclipse.jgit.errors.CorruptObjectException
315 * the index file is using a format or extension that this
316 * library does not support.
317 */
318 public static DirCache lock(final File indexLocation, final FS fs,
319 IndexChangedListener indexChangedListener)
320 throws CorruptObjectException,
321 IOException {
322 DirCache c = lock(indexLocation, fs);
323 c.registerIndexChangedListener(indexChangedListener);
324 return c;
325 }
326
327 /** Location of the current version of the index file. */
328 private final File liveFile;
329
330 /** Individual file index entries, sorted by path name. */
331 private DirCacheEntry[] sortedEntries;
332
333 /** Number of positions within {@link #sortedEntries} that are valid. */
334 private int entryCnt;
335
336 /** Cache tree for this index; null if the cache tree is not available. */
337 private DirCacheTree tree;
338
339 /** Our active lock (if we hold it); null if we don't have it locked. */
340 private LockFile myLock;
341
342 /** Keep track of whether the index has changed or not */
343 private FileSnapshot snapshot;
344
345 /** index checksum when index was read from disk */
346 private byte[] readIndexChecksum;
347
348 /** index checksum when index was written to disk */
349 private byte[] writeIndexChecksum;
350
351 /** listener to be informed on commit */
352 private IndexChangedListener indexChangedListener;
353
354 /** Repository containing this index */
355 private Repository repository;
356
357 /**
358 * Create a new in-core index representation.
359 * <p>
360 * The new index will be empty. Callers may wish to read from the on disk
361 * file first with {@link #read()}.
362 *
363 * @param indexLocation
364 * location of the index file on disk.
365 * @param fs
366 * the file system abstraction which will be necessary to perform
367 * certain file system operations.
368 */
369 public DirCache(File indexLocation, FS fs) {
370 liveFile = indexLocation;
371 clear();
372 }
373
374 /**
375 * Create a new builder to update this cache.
376 * <p>
377 * Callers should add all entries to the builder, then use
378 * {@link org.eclipse.jgit.dircache.DirCacheBuilder#finish()} to update this
379 * instance.
380 *
381 * @return a new builder instance for this cache.
382 */
383 public DirCacheBuilder builder() {
384 return new DirCacheBuilder(this, entryCnt + 16);
385 }
386
387 /**
388 * Create a new editor to recreate this cache.
389 * <p>
390 * Callers should add commands to the editor, then use
391 * {@link org.eclipse.jgit.dircache.DirCacheEditor#finish()} to update this
392 * instance.
393 *
394 * @return a new builder instance for this cache.
395 */
396 public DirCacheEditor editor() {
397 return new DirCacheEditor(this, entryCnt + 16);
398 }
399
400 void replace(DirCacheEntry[] e, int cnt) {
401 sortedEntries = e;
402 entryCnt = cnt;
403 tree = null;
404 }
405
406 /**
407 * Read the index from disk, if it has changed on disk.
408 * <p>
409 * This method tries to avoid loading the index if it has not changed since
410 * the last time we consulted it. A missing index file will be treated as
411 * though it were present but had no file entries in it.
412 *
413 * @throws java.io.IOException
414 * the index file is present but could not be read. This
415 * DirCache instance may not be populated correctly.
416 * @throws org.eclipse.jgit.errors.CorruptObjectException
417 * the index file is using a format or extension that this
418 * library does not support.
419 */
420 public void read() throws IOException, CorruptObjectException {
421 if (liveFile == null)
422 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
423 if (!liveFile.exists())
424 clear();
425 else if (snapshot == null || snapshot.isModified(liveFile)) {
426 try (SilentFileInputStreamm.html#SilentFileInputStream">SilentFileInputStream inStream = new SilentFileInputStream(
427 liveFile)) {
428 clear();
429 readFrom(inStream);
430 } catch (FileNotFoundException fnfe) {
431 if (liveFile.exists()) {
432 // Panic: the index file exists but we can't read it
433 throw new IndexReadException(
434 MessageFormat.format(JGitText.get().cannotReadIndex,
435 liveFile.getAbsolutePath(), fnfe));
436 }
437 // Someone must have deleted it between our exists test
438 // and actually opening the path. That's fine, its empty.
439 //
440 clear();
441 }
442 snapshot = FileSnapshot.save(liveFile);
443 }
444 }
445
446 /**
447 * Whether the memory state differs from the index file
448 *
449 * @return {@code true} if the memory state differs from the index file
450 * @throws java.io.IOException
451 */
452 public boolean isOutdated() throws IOException {
453 if (liveFile == null || !liveFile.exists())
454 return false;
455 return snapshot == null || snapshot.isModified(liveFile);
456 }
457
458 /**
459 * Empty this index, removing all entries.
460 */
461 public void clear() {
462 snapshot = null;
463 sortedEntries = NO_ENTRIES;
464 entryCnt = 0;
465 tree = null;
466 readIndexChecksum = NO_CHECKSUM;
467 }
468
469 private void readFrom(InputStream inStream) throws IOException,
470 CorruptObjectException {
471 final BufferedInputStream in = new BufferedInputStream(inStream);
472 final MessageDigest md = Constants.newMessageDigest();
473
474 // Read the index header and verify we understand it.
475 //
476 final byte[] hdr = new byte[20];
477 IO.readFully(in, hdr, 0, 12);
478 md.update(hdr, 0, 12);
479 if (!is_DIRC(hdr))
480 throw new CorruptObjectException(JGitText.get().notADIRCFile);
481 final int ver = NB.decodeInt32(hdr, 4);
482 boolean extended = false;
483 if (ver == 3)
484 extended = true;
485 else if (ver != 2)
486 throw new CorruptObjectException(MessageFormat.format(
487 JGitText.get().unknownDIRCVersion, Integer.valueOf(ver)));
488 entryCnt = NB.decodeInt32(hdr, 8);
489 if (entryCnt < 0)
490 throw new CorruptObjectException(JGitText.get().DIRCHasTooManyEntries);
491
492 snapshot = FileSnapshot.save(liveFile);
493 Instant smudge = snapshot.lastModifiedInstant();
494
495 // Load the individual file entries.
496 //
497 final int infoLength = DirCacheEntry.getMaximumInfoLength(extended);
498 final byte[] infos = new byte[infoLength * entryCnt];
499 sortedEntries = new DirCacheEntry[entryCnt];
500
501 final MutableInteger.html#MutableInteger">MutableInteger infoAt = new MutableInteger();
502 for (int i = 0; i < entryCnt; i++) {
503 sortedEntries[i] = new DirCacheEntry(infos, infoAt, in, md, smudge);
504 }
505
506 // After the file entries are index extensions, and then a footer.
507 //
508 for (;;) {
509 in.mark(21);
510 IO.readFully(in, hdr, 0, 20);
511 if (in.read() < 0) {
512 // No extensions present; the file ended where we expected.
513 //
514 break;
515 }
516
517 in.reset();
518 md.update(hdr, 0, 8);
519 IO.skipFully(in, 8);
520
521 long sz = NB.decodeUInt32(hdr, 4);
522 switch (NB.decodeInt32(hdr, 0)) {
523 case EXT_TREE: {
524 if (Integer.MAX_VALUE < sz) {
525 throw new CorruptObjectException(MessageFormat.format(
526 JGitText.get().DIRCExtensionIsTooLargeAt,
527 formatExtensionName(hdr), Long.valueOf(sz)));
528 }
529 final byte[] raw = new byte[(int) sz];
530 IO.readFully(in, raw, 0, raw.length);
531 md.update(raw, 0, raw.length);
532 tree = new DirCacheTree(raw, new MutableInteger(), null);
533 break;
534 }
535 default:
536 if (hdr[0] >= 'A' && hdr[0] <= 'Z') {
537 // The extension is optional and is here only as
538 // a performance optimization. Since we do not
539 // understand it, we can safely skip past it, after
540 // we include its data in our checksum.
541 //
542 skipOptionalExtension(in, md, hdr, sz);
543 } else {
544 // The extension is not an optimization and is
545 // _required_ to understand this index format.
546 // Since we did not trap it above we must abort.
547 //
548 throw new CorruptObjectException(MessageFormat.format(JGitText.get().DIRCExtensionNotSupportedByThisVersion
549 , formatExtensionName(hdr)));
550 }
551 }
552 }
553
554 readIndexChecksum = md.digest();
555 if (!Arrays.equals(readIndexChecksum, hdr)) {
556 throw new CorruptObjectException(JGitText.get().DIRCChecksumMismatch);
557 }
558 }
559
560 private void skipOptionalExtension(final InputStream in,
561 final MessageDigest md, final byte[] hdr, long sz)
562 throws IOException {
563 final byte[] b = new byte[4096];
564 while (0 < sz) {
565 int n = in.read(b, 0, (int) Math.min(b.length, sz));
566 if (n < 0) {
567 throw new EOFException(
568 MessageFormat.format(
569 JGitText.get().shortReadOfOptionalDIRCExtensionExpectedAnotherBytes,
570 formatExtensionName(hdr), Long.valueOf(sz)));
571 }
572 md.update(b, 0, n);
573 sz -= n;
574 }
575 }
576
577 private static String formatExtensionName(byte[] hdr) {
578 return "'" + new String(hdr, 0, 4, ISO_8859_1) + "'"; //$NON-NLS-1$ //$NON-NLS-2$
579 }
580
581 private static boolean is_DIRC(byte[] hdr) {
582 if (hdr.length < SIG_DIRC.length)
583 return false;
584 for (int i = 0; i < SIG_DIRC.length; i++)
585 if (hdr[i] != SIG_DIRC[i])
586 return false;
587 return true;
588 }
589
590 /**
591 * Try to establish an update lock on the cache file.
592 *
593 * @return true if the lock is now held by the caller; false if it is held
594 * by someone else.
595 * @throws java.io.IOException
596 * the output file could not be created. The caller does not
597 * hold the lock.
598 */
599 public boolean lock() throws IOException {
600 if (liveFile == null)
601 throw new IOException(JGitText.get().dirCacheDoesNotHaveABackingFile);
602 final LockFiletorage/file/LockFile.html#LockFile">LockFile tmp = new LockFile(liveFile);
603 if (tmp.lock()) {
604 tmp.setNeedStatInformation(true);
605 myLock = tmp;
606 return true;
607 }
608 return false;
609 }
610
611 /**
612 * Write the entry records from memory to disk.
613 * <p>
614 * The cache must be locked first by calling {@link #lock()} and receiving
615 * true as the return value. Applications are encouraged to lock the index,
616 * then invoke {@link #read()} to ensure the in-memory data is current,
617 * prior to updating the in-memory entries.
618 * <p>
619 * Once written the lock is closed and must be either committed with
620 * {@link #commit()} or rolled back with {@link #unlock()}.
621 *
622 * @throws java.io.IOException
623 * the output file could not be created. The caller no longer
624 * holds the lock.
625 */
626 public void write() throws IOException {
627 final LockFile tmp = myLock;
628 requireLocked(tmp);
629 try (OutputStream o = tmp.getOutputStream();
630 OutputStream bo = new BufferedOutputStream(o)) {
631 writeTo(liveFile.getParentFile(), bo);
632 } catch (IOException | RuntimeException | Error err) {
633 tmp.unlock();
634 throw err;
635 }
636 }
637
638 void writeTo(File dir, OutputStream os) throws IOException {
639 final MessageDigest foot = Constants.newMessageDigest();
640 final DigestOutputStream dos = new DigestOutputStream(os, foot);
641
642 boolean extended = false;
643 for (int i = 0; i < entryCnt; i++) {
644 if (sortedEntries[i].isExtended()) {
645 extended = true;
646 break;
647 }
648 }
649
650 // Write the header.
651 //
652 final byte[] tmp = new byte[128];
653 System.arraycopy(SIG_DIRC, 0, tmp, 0, SIG_DIRC.length);
654 NB.encodeInt32(tmp, 4, extended ? 3 : 2);
655 NB.encodeInt32(tmp, 8, entryCnt);
656 dos.write(tmp, 0, 12);
657
658 // Write the individual file entries.
659
660 Instant smudge;
661 if (myLock != null) {
662 // For new files we need to smudge the index entry
663 // if they have been modified "now". Ideally we'd
664 // want the timestamp when we're done writing the index,
665 // so we use the current timestamp as a approximation.
666 myLock.createCommitSnapshot();
667 snapshot = myLock.getCommitSnapshot();
668 smudge = snapshot.lastModifiedInstant();
669 } else {
670 // Used in unit tests only
671 smudge = Instant.EPOCH;
672 }
673
674 // Check if tree is non-null here since calling updateSmudgedEntries
675 // will automatically build it via creating a DirCacheIterator
676 final boolean writeTree = tree != null;
677
678 if (repository != null && entryCnt > 0)
679 updateSmudgedEntries();
680
681 for (int i = 0; i < entryCnt; i++) {
682 final DirCacheEntry e = sortedEntries[i];
683 if (e.mightBeRacilyClean(smudge)) {
684 e.smudgeRacilyClean();
685 }
686 e.write(dos);
687 }
688
689 if (writeTree) {
690 @SuppressWarnings("resource") // Explicitly closed in try block, and
691 // destroyed in finally
692 TemporaryBuffer bb = new TemporaryBuffer.LocalFile(dir, 5 << 20);
693 try {
694 tree.write(tmp, bb);
695 bb.close();
696
697 NB.encodeInt32(tmp, 0, EXT_TREE);
698 NB.encodeInt32(tmp, 4, (int) bb.length());
699 dos.write(tmp, 0, 8);
700 bb.writeTo(dos, null);
701 } finally {
702 bb.destroy();
703 }
704 }
705 writeIndexChecksum = foot.digest();
706 os.write(writeIndexChecksum);
707 os.close();
708 }
709
710 /**
711 * Commit this change and release the lock.
712 * <p>
713 * If this method fails (returns false) the lock is still released.
714 *
715 * @return true if the commit was successful and the file contains the new
716 * data; false if the commit failed and the file remains with the
717 * old data.
718 * @throws java.lang.IllegalStateException
719 * the lock is not held.
720 */
721 public boolean commit() {
722 final LockFile tmp = myLock;
723 requireLocked(tmp);
724 myLock = null;
725 if (!tmp.commit()) {
726 return false;
727 }
728 snapshot = tmp.getCommitSnapshot();
729 if (indexChangedListener != null
730 && !Arrays.equals(readIndexChecksum, writeIndexChecksum)) {
731 indexChangedListener.onIndexChanged(new IndexChangedEvent(true));
732 }
733 return true;
734 }
735
736 private void requireLocked(LockFile tmp) {
737 if (liveFile == null)
738 throw new IllegalStateException(JGitText.get().dirCacheIsNotLocked);
739 if (tmp == null)
740 throw new IllegalStateException(MessageFormat.format(JGitText.get().dirCacheFileIsNotLocked
741 , liveFile.getAbsolutePath()));
742 }
743
744 /**
745 * Unlock this file and abort this change.
746 * <p>
747 * The temporary file (if created) is deleted before returning.
748 */
749 public void unlock() {
750 final LockFile tmp = myLock;
751 if (tmp != null) {
752 myLock = null;
753 tmp.unlock();
754 }
755 }
756
757 /**
758 * Locate the position a path's entry is at in the index. For details refer
759 * to #findEntry(byte[], int).
760 *
761 * @param path
762 * the path to search for.
763 * @return if >= 0 then the return value is the position of the entry in
764 * the index; pass to {@link #getEntry(int)} to obtain the entry
765 * information. If < 0 the entry does not exist in the index.
766 */
767 public int findEntry(String path) {
768 final byte[] p = Constants.encode(path);
769 return findEntry(p, p.length);
770 }
771
772 /**
773 * Locate the position a path's entry is at in the index.
774 * <p>
775 * If there is at least one entry in the index for this path the position of
776 * the lowest stage is returned. Subsequent stages can be identified by
777 * testing consecutive entries until the path differs.
778 * <p>
779 * If no path matches the entry -(position+1) is returned, where position is
780 * the location it would have gone within the index.
781 *
782 * @param p
783 * the byte array starting with the path to search for.
784 * @param pLen
785 * the length of the path in bytes
786 * @return if >= 0 then the return value is the position of the entry in
787 * the index; pass to {@link #getEntry(int)} to obtain the entry
788 * information. If < 0 the entry does not exist in the index.
789 * @since 3.4
790 */
791 public int findEntry(byte[] p, int pLen) {
792 return findEntry(0, p, pLen);
793 }
794
795 int findEntry(int low, byte[] p, int pLen) {
796 int high = entryCnt;
797 while (low < high) {
798 int mid = (low + high) >>> 1;
799 final int cmp = cmp(p, pLen, sortedEntries[mid]);
800 if (cmp < 0)
801 high = mid;
802 else if (cmp == 0) {
803 while (mid > 0 && cmp(p, pLen, sortedEntries[mid - 1]) == 0)
804 mid--;
805 return mid;
806 } else
807 low = mid + 1;
808 }
809 return -(low + 1);
810 }
811
812 /**
813 * Determine the next index position past all entries with the same name.
814 * <p>
815 * As index entries are sorted by path name, then stage number, this method
816 * advances the supplied position to the first position in the index whose
817 * path name does not match the path name of the supplied position's entry.
818 *
819 * @param position
820 * entry position of the path that should be skipped.
821 * @return position of the next entry whose path is after the input.
822 */
823 public int nextEntry(int position) {
824 DirCacheEntry last = sortedEntries[position];
825 int nextIdx = position + 1;
826 while (nextIdx < entryCnt) {
827 final DirCacheEntry next = sortedEntries[nextIdx];
828 if (cmp(last, next) != 0)
829 break;
830 last = next;
831 nextIdx++;
832 }
833 return nextIdx;
834 }
835
836 int nextEntry(byte[] p, int pLen, int nextIdx) {
837 while (nextIdx < entryCnt) {
838 final DirCacheEntry next = sortedEntries[nextIdx];
839 if (!DirCacheTree.peq(p, next.path, pLen))
840 break;
841 nextIdx++;
842 }
843 return nextIdx;
844 }
845
846 /**
847 * Total number of file entries stored in the index.
848 * <p>
849 * This count includes unmerged stages for a file entry if the file is
850 * currently conflicted in a merge. This means the total number of entries
851 * in the index may be up to 3 times larger than the number of files in the
852 * working directory.
853 * <p>
854 * Note that this value counts only <i>files</i>.
855 *
856 * @return number of entries available.
857 * @see #getEntry(int)
858 */
859 public int getEntryCount() {
860 return entryCnt;
861 }
862
863 /**
864 * Get a specific entry.
865 *
866 * @param i
867 * position of the entry to get.
868 * @return the entry at position <code>i</code>.
869 */
870 public DirCacheEntry getEntry(int i) {
871 return sortedEntries[i];
872 }
873
874 /**
875 * Get a specific entry.
876 *
877 * @param path
878 * the path to search for.
879 * @return the entry for the given <code>path</code>.
880 */
881 public DirCacheEntry getEntry(String path) {
882 final int i = findEntry(path);
883 return i < 0 ? null : sortedEntries[i];
884 }
885
886 /**
887 * Recursively get all entries within a subtree.
888 *
889 * @param path
890 * the subtree path to get all entries within.
891 * @return all entries recursively contained within the subtree.
892 */
893 public DirCacheEntry[] getEntriesWithin(String path) {
894 if (path.length() == 0) {
895 DirCacheEntry[] r = new DirCacheEntry[entryCnt];
896 System.arraycopy(sortedEntries, 0, r, 0, entryCnt);
897 return r;
898 }
899 if (!path.endsWith("/")) //$NON-NLS-1$
900 path += "/"; //$NON-NLS-1$
901 final byte[] p = Constants.encode(path);
902 final int pLen = p.length;
903
904 int eIdx = findEntry(p, pLen);
905 if (eIdx < 0)
906 eIdx = -(eIdx + 1);
907 final int lastIdx = nextEntry(p, pLen, eIdx);
908 final DirCacheEntryheEntry.html#DirCacheEntry">DirCacheEntry[] r = new DirCacheEntry[lastIdx - eIdx];
909 System.arraycopy(sortedEntries, eIdx, r, 0, r.length);
910 return r;
911 }
912
913 void toArray(final int i, final DirCacheEntry[] dst, final int off,
914 final int cnt) {
915 System.arraycopy(sortedEntries, i, dst, off, cnt);
916 }
917
918 /**
919 * Obtain (or build) the current cache tree structure.
920 * <p>
921 * This method can optionally recreate the cache tree, without flushing the
922 * tree objects themselves to disk.
923 *
924 * @param build
925 * if true and the cache tree is not present in the index it will
926 * be generated and returned to the caller.
927 * @return the cache tree; null if there is no current cache tree available
928 * and <code>build</code> was false.
929 */
930 public DirCacheTree getCacheTree(boolean build) {
931 if (build) {
932 if (tree == null)
933 tree = new DirCacheTree();
934 tree.validate(sortedEntries, entryCnt, 0, 0);
935 }
936 return tree;
937 }
938
939 /**
940 * Write all index trees to the object store, returning the root tree.
941 *
942 * @param ow
943 * the writer to use when serializing to the store. The caller is
944 * responsible for flushing the inserter before trying to use the
945 * returned tree identity.
946 * @return identity for the root tree.
947 * @throws org.eclipse.jgit.errors.UnmergedPathException
948 * one or more paths contain higher-order stages (stage > 0),
949 * which cannot be stored in a tree object.
950 * @throws java.lang.IllegalStateException
951 * one or more paths contain an invalid mode which should never
952 * appear in a tree object.
953 * @throws java.io.IOException
954 * an unexpected error occurred writing to the object store.
955 */
956 public ObjectId writeTree(ObjectInserter ow)
957 throws UnmergedPathException, IOException {
958 return getCacheTree(true).writeTree(sortedEntries, 0, 0, ow);
959 }
960
961 /**
962 * Tells whether this index contains unmerged paths.
963 *
964 * @return {@code true} if this index contains unmerged paths. Means: at
965 * least one entry is of a stage different from 0. {@code false}
966 * will be returned if all entries are of stage 0.
967 */
968 public boolean hasUnmergedPaths() {
969 for (int i = 0; i < entryCnt; i++) {
970 if (sortedEntries[i].getStage() > 0) {
971 return true;
972 }
973 }
974 return false;
975 }
976
977 private void registerIndexChangedListener(IndexChangedListener listener) {
978 this.indexChangedListener = listener;
979 }
980
981 /**
982 * Update any smudged entries with information from the working tree.
983 *
984 * @throws IOException
985 */
986 private void updateSmudgedEntries() throws IOException {
987 List<String> paths = new ArrayList<>(128);
988 try (TreeWalkeeWalk.html#TreeWalk">TreeWalk walk = new TreeWalk(repository)) {
989 walk.setOperationType(OperationType.CHECKIN_OP);
990 for (int i = 0; i < entryCnt; i++)
991 if (sortedEntries[i].isSmudged())
992 paths.add(sortedEntries[i].getPathString());
993 if (paths.isEmpty())
994 return;
995 walk.setFilter(PathFilterGroup.createFromStrings(paths));
996
997 DirCacheIterator iIter = new DirCacheIterator(this);
998 FileTreeIterator fIter = new FileTreeIterator(repository);
999 walk.addTree(iIter);
1000 walk.addTree(fIter);
1001 fIter.setDirCacheIterator(walk, 0);
1002 walk.setRecursive(true);
1003 while (walk.next()) {
1004 iIter = walk.getTree(0, DirCacheIterator.class);
1005 if (iIter == null)
1006 continue;
1007 fIter = walk.getTree(1, FileTreeIterator.class);
1008 if (fIter == null)
1009 continue;
1010 DirCacheEntry entry = iIter.getDirCacheEntry();
1011 if (entry.isSmudged() && iIter.idEqual(fIter)) {
1012 entry.setLength(fIter.getEntryLength());
1013 entry.setLastModified(fIter.getEntryLastModifiedInstant());
1014 }
1015 }
1016 }
1017 }
1018 }