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