BaseDirCacheEditor.java

  1. /*
  2.  * Copyright (C) 2008, 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_TREE;
  13. import static org.eclipse.jgit.util.Paths.compareSameName;

  14. import java.io.IOException;

  15. import org.eclipse.jgit.errors.DirCacheNameConflictException;

  16. /**
  17.  * Generic update/editing support for {@link DirCache}.
  18.  * <p>
  19.  * The different update strategies extend this class to provide their own unique
  20.  * services to applications.
  21.  */
  22. abstract class BaseDirCacheEditor {
  23.     /** The cache instance this editor updates during {@link #finish()}. */
  24.     protected DirCache cache;

  25.     /**
  26.      * Entry table this builder will eventually replace into {@link #cache}.
  27.      * <p>
  28.      * Use {@link #fastAdd(DirCacheEntry)} or {@link #fastKeep(int, int)} to
  29.      * make additions to this table. The table is automatically expanded if it
  30.      * is too small for a new addition.
  31.      * <p>
  32.      * Typically the entries in here are sorted by their path names, just like
  33.      * they are in the DirCache instance.
  34.      */
  35.     protected DirCacheEntry[] entries;

  36.     /** Total number of valid entries in {@link #entries}. */
  37.     protected int entryCnt;

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

  51.     /**
  52.      * Get the {@code DirCache}
  53.      *
  54.      * @return the cache we will update on {@link #finish()}.
  55.      */
  56.     public DirCache getDirCache() {
  57.         return cache;
  58.     }

  59.     /**
  60.      * Append one entry into the resulting entry list.
  61.      * <p>
  62.      * The entry is placed at the end of the entry list. The caller is
  63.      * responsible for making sure the final table is correctly sorted.
  64.      * <p>
  65.      * The {@link #entries} table is automatically expanded if there is
  66.      * insufficient space for the new addition.
  67.      *
  68.      * @param newEntry
  69.      *            the new entry to add.
  70.      */
  71.     protected void fastAdd(DirCacheEntry newEntry) {
  72.         if (entries.length == entryCnt) {
  73.             final DirCacheEntry[] n = new DirCacheEntry[(entryCnt + 16) * 3 / 2];
  74.             System.arraycopy(entries, 0, n, 0, entryCnt);
  75.             entries = n;
  76.         }
  77.         entries[entryCnt++] = newEntry;
  78.     }

  79.     /**
  80.      * Add a range of existing entries from the destination cache.
  81.      * <p>
  82.      * The entries are placed at the end of the entry list, preserving their
  83.      * current order. The caller is responsible for making sure the final table
  84.      * is correctly sorted.
  85.      * <p>
  86.      * This method copies from the destination cache, which has not yet been
  87.      * updated with this editor's new table. So all offsets into the destination
  88.      * cache are not affected by any updates that may be currently taking place
  89.      * in this editor.
  90.      * <p>
  91.      * The {@link #entries} table is automatically expanded if there is
  92.      * insufficient space for the new additions.
  93.      *
  94.      * @param pos
  95.      *            first entry to copy from the destination cache.
  96.      * @param cnt
  97.      *            number of entries to copy.
  98.      */
  99.     protected void fastKeep(int pos, int cnt) {
  100.         if (entryCnt + cnt > entries.length) {
  101.             final int m1 = (entryCnt + 16) * 3 / 2;
  102.             final int m2 = entryCnt + cnt;
  103.             final DirCacheEntry[] n = new DirCacheEntry[Math.max(m1, m2)];
  104.             System.arraycopy(entries, 0, n, 0, entryCnt);
  105.             entries = n;
  106.         }

  107.         cache.toArray(pos, entries, entryCnt, cnt);
  108.         entryCnt += cnt;
  109.     }

  110.     /**
  111.      * Finish this builder and update the destination
  112.      * {@link org.eclipse.jgit.dircache.DirCache}.
  113.      * <p>
  114.      * When this method completes this builder instance is no longer usable by
  115.      * the calling application. A new builder must be created to make additional
  116.      * changes to the index entries.
  117.      * <p>
  118.      * After completion the DirCache returned by {@link #getDirCache()} will
  119.      * contain all modifications.
  120.      * <p>
  121.      * <i>Note to implementors:</i> Make sure {@link #entries} is fully sorted
  122.      * then invoke {@link #replace()} to update the DirCache with the new table.
  123.      */
  124.     public abstract void finish();

  125.     /**
  126.      * Update the DirCache with the contents of {@link #entries}.
  127.      * <p>
  128.      * This method should be invoked only during an implementation of
  129.      * {@link #finish()}, and only after {@link #entries} is sorted.
  130.      */
  131.     protected void replace() {
  132.         checkNameConflicts();
  133.         if (entryCnt < entries.length / 2) {
  134.             final DirCacheEntry[] n = new DirCacheEntry[entryCnt];
  135.             System.arraycopy(entries, 0, n, 0, entryCnt);
  136.             entries = n;
  137.         }
  138.         cache.replace(entries, entryCnt);
  139.     }

  140.     private void checkNameConflicts() {
  141.         int end = entryCnt - 1;
  142.         for (int eIdx = 0; eIdx < end; eIdx++) {
  143.             DirCacheEntry e = entries[eIdx];
  144.             if (e.getStage() != 0) {
  145.                 continue;
  146.             }

  147.             byte[] ePath = e.path;
  148.             int prefixLen = lastSlash(ePath) + 1;

  149.             for (int nIdx = eIdx + 1; nIdx < entryCnt; nIdx++) {
  150.                 DirCacheEntry n = entries[nIdx];
  151.                 if (n.getStage() != 0) {
  152.                     continue;
  153.                 }

  154.                 byte[] nPath = n.path;
  155.                 if (!startsWith(ePath, nPath, prefixLen)) {
  156.                     // Different prefix; this entry is in another directory.
  157.                     break;
  158.                 }

  159.                 int s = nextSlash(nPath, prefixLen);
  160.                 int m = s < nPath.length ? TYPE_TREE : n.getRawMode();
  161.                 int cmp = compareSameName(
  162.                         ePath, prefixLen, ePath.length,
  163.                         nPath, prefixLen, s, m);
  164.                 if (cmp < 0) {
  165.                     break;
  166.                 } else if (cmp == 0) {
  167.                     throw new DirCacheNameConflictException(
  168.                             e.getPathString(),
  169.                             n.getPathString());
  170.                 }
  171.             }
  172.         }
  173.     }

  174.     private static int lastSlash(byte[] path) {
  175.         for (int i = path.length - 1; i >= 0; i--) {
  176.             if (path[i] == '/') {
  177.                 return i;
  178.             }
  179.         }
  180.         return -1;
  181.     }

  182.     private static int nextSlash(byte[] b, int p) {
  183.         final int n = b.length;
  184.         for (; p < n; p++) {
  185.             if (b[p] == '/') {
  186.                 return p;
  187.             }
  188.         }
  189.         return n;
  190.     }

  191.     private static boolean startsWith(byte[] a, byte[] b, int n) {
  192.         if (b.length < n) {
  193.             return false;
  194.         }
  195.         for (n--; n >= 0; n--) {
  196.             if (a[n] != b[n]) {
  197.                 return false;
  198.             }
  199.         }
  200.         return true;
  201.     }

  202.     /**
  203.      * Finish, write, commit this change, and release the index lock.
  204.      * <p>
  205.      * If this method fails (returns false) the lock is still released.
  206.      * <p>
  207.      * This is a utility method for applications as the finish-write-commit
  208.      * pattern is very common after using a builder to update entries.
  209.      *
  210.      * @return true if the commit was successful and the file contains the new
  211.      *         data; false if the commit failed and the file remains with the
  212.      *         old data.
  213.      * @throws java.lang.IllegalStateException
  214.      *             the lock is not held.
  215.      * @throws java.io.IOException
  216.      *             the output file could not be created. The caller no longer
  217.      *             holds the lock.
  218.      */
  219.     public boolean commit() throws IOException {
  220.         finish();
  221.         cache.write();
  222.         return cache.commit();
  223.     }
  224. }