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