DirCacheBuilder.java

  1. /*
  2.  * Copyright (C) 2008-2009, Google Inc.
  3.  * Copyright (C) 2008, Shawn O. Pearce <spearce@spearce.org> and others
  4.  *
  5.  * This program and the accompanying materials are made available under the
  6.  * terms of the Eclipse Distribution License v. 1.0 which is available at
  7.  * https://www.eclipse.org/org/documents/edl-v10.php.
  8.  *
  9.  * SPDX-License-Identifier: BSD-3-Clause
  10.  */

  11. package org.eclipse.jgit.dircache;

  12. import static org.eclipse.jgit.lib.FileMode.TYPE_MASK;
  13. import static org.eclipse.jgit.lib.FileMode.TYPE_TREE;

  14. import java.io.IOException;
  15. import java.text.MessageFormat;
  16. import java.util.Arrays;

  17. import org.eclipse.jgit.internal.JGitText;
  18. import org.eclipse.jgit.lib.AnyObjectId;
  19. import org.eclipse.jgit.lib.ObjectReader;
  20. import org.eclipse.jgit.treewalk.CanonicalTreeParser;

  21. /**
  22.  * Updates a {@link org.eclipse.jgit.dircache.DirCache} by adding individual
  23.  * {@link org.eclipse.jgit.dircache.DirCacheEntry}s.
  24.  * <p>
  25.  * A builder always starts from a clean slate and appends in every single
  26.  * <code>DirCacheEntry</code> which the final updated index must have to reflect
  27.  * its new content.
  28.  * <p>
  29.  * For maximum performance applications should add entries in path name order.
  30.  * Adding entries out of order is permitted, however a final sorting pass will
  31.  * be implicitly performed during {@link #finish()} to correct any out-of-order
  32.  * entries. Duplicate detection is also delayed until the sorting is complete.
  33.  *
  34.  * @see DirCacheEditor
  35.  */
  36. public class DirCacheBuilder extends BaseDirCacheEditor {
  37.     private boolean sorted;

  38.     /**
  39.      * Construct a new builder.
  40.      *
  41.      * @param dc
  42.      *            the cache this builder will eventually update.
  43.      * @param ecnt
  44.      *            estimated number of entries the builder will have upon
  45.      *            completion. This sizes the initial entry table.
  46.      */
  47.     protected DirCacheBuilder(DirCache dc, int ecnt) {
  48.         super(dc, ecnt);
  49.     }

  50.     /**
  51.      * Append one entry into the resulting entry list.
  52.      * <p>
  53.      * The entry is placed at the end of the entry list. If the entry causes the
  54.      * list to now be incorrectly sorted a final sorting phase will be
  55.      * automatically enabled within {@link #finish()}.
  56.      * <p>
  57.      * The internal entry table is automatically expanded if there is
  58.      * insufficient space for the new addition.
  59.      *
  60.      * @param newEntry
  61.      *            the new entry to add.
  62.      * @throws java.lang.IllegalArgumentException
  63.      *             If the FileMode of the entry was not set by the caller.
  64.      */
  65.     public void add(DirCacheEntry newEntry) {
  66.         if (newEntry.getRawMode() == 0)
  67.             throw new IllegalArgumentException(MessageFormat.format(
  68.                     JGitText.get().fileModeNotSetForPath,
  69.                     newEntry.getPathString()));
  70.         beforeAdd(newEntry);
  71.         fastAdd(newEntry);
  72.     }

  73.     /**
  74.      * Add a range of existing entries from the destination cache.
  75.      * <p>
  76.      * The entries are placed at the end of the entry list. If any of the
  77.      * entries causes the list to now be incorrectly sorted a final sorting
  78.      * phase will be automatically enabled within {@link #finish()}.
  79.      * <p>
  80.      * This method copies from the destination cache, which has not yet been
  81.      * updated with this editor's new table. So all offsets into the destination
  82.      * cache are not affected by any updates that may be currently taking place
  83.      * in this editor.
  84.      * <p>
  85.      * The internal entry table is automatically expanded if there is
  86.      * insufficient space for the new additions.
  87.      *
  88.      * @param pos
  89.      *            first entry to copy from the destination cache.
  90.      * @param cnt
  91.      *            number of entries to copy.
  92.      */
  93.     public void keep(int pos, int cnt) {
  94.         beforeAdd(cache.getEntry(pos));
  95.         fastKeep(pos, cnt);
  96.     }

  97.     /**
  98.      * Recursively add an entire tree into this builder.
  99.      * <p>
  100.      * If pathPrefix is "a/b" and the tree contains file "c" then the resulting
  101.      * DirCacheEntry will have the path "a/b/c".
  102.      * <p>
  103.      * All entries are inserted at stage 0, therefore assuming that the
  104.      * application will not insert any other paths with the same pathPrefix.
  105.      *
  106.      * @param pathPrefix
  107.      *            UTF-8 encoded prefix to mount the tree's entries at. If the
  108.      *            path does not end with '/' one will be automatically inserted
  109.      *            as necessary.
  110.      * @param stage
  111.      *            stage of the entries when adding them.
  112.      * @param reader
  113.      *            reader the tree(s) will be read from during recursive
  114.      *            traversal. This must be the same repository that the resulting
  115.      *            DirCache would be written out to (or used in) otherwise the
  116.      *            caller is simply asking for deferred MissingObjectExceptions.
  117.      *            Caller is responsible for releasing this reader when done.
  118.      * @param tree
  119.      *            the tree to recursively add. This tree's contents will appear
  120.      *            under <code>pathPrefix</code>. The ObjectId must be that of a
  121.      *            tree; the caller is responsible for dereferencing a tag or
  122.      *            commit (if necessary).
  123.      * @throws java.io.IOException
  124.      *             a tree cannot be read to iterate through its entries.
  125.      */
  126.     public void addTree(byte[] pathPrefix, int stage, ObjectReader reader,
  127.             AnyObjectId tree) throws IOException {
  128.         CanonicalTreeParser p = createTreeParser(pathPrefix, reader, tree);
  129.         while (!p.eof()) {
  130.             if (isTree(p)) {
  131.                 p = enterTree(p, reader);
  132.                 continue;
  133.             }

  134.             DirCacheEntry first = toEntry(stage, p);
  135.             beforeAdd(first);
  136.             fastAdd(first);
  137.             p = p.next();
  138.             break;
  139.         }

  140.         // Rest of tree entries are correctly sorted; use fastAdd().
  141.         while (!p.eof()) {
  142.             if (isTree(p)) {
  143.                 p = enterTree(p, reader);
  144.             } else {
  145.                 fastAdd(toEntry(stage, p));
  146.                 p = p.next();
  147.             }
  148.         }
  149.     }

  150.     private static CanonicalTreeParser createTreeParser(byte[] pathPrefix,
  151.             ObjectReader reader, AnyObjectId tree) throws IOException {
  152.         return new CanonicalTreeParser(pathPrefix, reader, tree);
  153.     }

  154.     private static boolean isTree(CanonicalTreeParser p) {
  155.         return (p.getEntryRawMode() & TYPE_MASK) == TYPE_TREE;
  156.     }

  157.     private static CanonicalTreeParser enterTree(CanonicalTreeParser p,
  158.             ObjectReader reader) throws IOException {
  159.         p = p.createSubtreeIterator(reader);
  160.         return p.eof() ? p.next() : p;
  161.     }

  162.     private static DirCacheEntry toEntry(int stage, CanonicalTreeParser i) {
  163.         byte[] buf = i.getEntryPathBuffer();
  164.         int len = i.getEntryPathLength();
  165.         byte[] path = new byte[len];
  166.         System.arraycopy(buf, 0, path, 0, len);

  167.         DirCacheEntry e = new DirCacheEntry(path, stage);
  168.         e.setFileMode(i.getEntryRawMode());
  169.         e.setObjectIdFromRaw(i.idBuffer(), i.idOffset());
  170.         return e;
  171.     }

  172.     /** {@inheritDoc} */
  173.     @Override
  174.     public void finish() {
  175.         if (!sorted)
  176.             resort();
  177.         replace();
  178.     }

  179.     private void beforeAdd(DirCacheEntry newEntry) {
  180.         if (sorted && entryCnt > 0) {
  181.             final DirCacheEntry lastEntry = entries[entryCnt - 1];
  182.             final int cr = DirCache.cmp(lastEntry, newEntry);
  183.             if (cr > 0) {
  184.                 // The new entry sorts before the old entry; we are
  185.                 // no longer sorted correctly. We'll need to redo
  186.                 // the sorting before we can close out the build.
  187.                 //
  188.                 sorted = false;
  189.             } else if (cr == 0) {
  190.                 // Same file path; we can only insert this if the
  191.                 // stages won't be violated.
  192.                 //
  193.                 final int peStage = lastEntry.getStage();
  194.                 final int dceStage = newEntry.getStage();
  195.                 if (peStage == dceStage)
  196.                     throw bad(newEntry, JGitText.get().duplicateStagesNotAllowed);
  197.                 if (peStage == 0 || dceStage == 0)
  198.                     throw bad(newEntry, JGitText.get().mixedStagesNotAllowed);
  199.                 if (peStage > dceStage)
  200.                     sorted = false;
  201.             }
  202.         }
  203.     }

  204.     private void resort() {
  205.         Arrays.sort(entries, 0, entryCnt, DirCache.ENT_CMP);

  206.         for (int entryIdx = 1; entryIdx < entryCnt; entryIdx++) {
  207.             final DirCacheEntry pe = entries[entryIdx - 1];
  208.             final DirCacheEntry ce = entries[entryIdx];
  209.             final int cr = DirCache.cmp(pe, ce);
  210.             if (cr == 0) {
  211.                 // Same file path; we can only allow this if the stages
  212.                 // are 1-3 and no 0 exists.
  213.                 //
  214.                 final int peStage = pe.getStage();
  215.                 final int ceStage = ce.getStage();
  216.                 if (peStage == ceStage)
  217.                     throw bad(ce, JGitText.get().duplicateStagesNotAllowed);
  218.                 if (peStage == 0 || ceStage == 0)
  219.                     throw bad(ce, JGitText.get().mixedStagesNotAllowed);
  220.             }
  221.         }

  222.         sorted = true;
  223.     }

  224.     private static IllegalStateException bad(DirCacheEntry a, String msg) {
  225.         return new IllegalStateException(String.format(
  226.                 "%s: %d %s", //$NON-NLS-1$
  227.                 msg, Integer.valueOf(a.getStage()), a.getPathString()));
  228.     }
  229. }