private void logEntries(Collection<HistoryEntry> entries) {
   LocalHistory.LOG.log(Level.FINE, "LocalHistory returns {0} entries", entries.size()); // NOI18N
   if (LocalHistory.LOG.isLoggable(Level.FINEST)) {
     StringBuilder sb = new StringBuilder();
     Iterator<HistoryEntry> it = entries.iterator();
     while (it.hasNext()) {
       HistoryEntry entry = it.next();
       sb.append("["); // NOI18N
       sb.append(DateFormat.getDateTimeInstance().format(entry.getDateTime()));
       sb.append(",["); // NOI18N
       sb.append(toString(entry.getFiles()));
       sb.append("]]"); // NOI18N
       if (it.hasNext()) sb.append(","); // NOI18N
     }
     LocalHistory.LOG.finest(sb.toString());
   }
 }
  /**
   * Build maps from directory names and file names to their respective identifiers in the database.
   * The directories and files that are not already in the database, are added to it.
   *
   * @param conn the connection to the database
   * @param history the history to get the file and directory names from
   * @param reposId the id of the repository
   * @param dirMap a map which will be filled with directory names and ids
   * @param fileMap a map which will be filled with file names and ids
   */
  private void getFilesAndDirectories(
      ConnectionResource conn,
      History history,
      int reposId,
      Map<String, Integer> dirMap,
      Map<String, Integer> fileMap)
      throws SQLException {

    populateFileOrDirMap(conn.getStatement(GET_DIRS), reposId, dirMap);
    populateFileOrDirMap(conn.getStatement(GET_FILES), reposId, fileMap);

    int insertCount = 0;

    PreparedStatement insDir = conn.getStatement(INSERT_DIR);
    PreparedStatement insFile = conn.getStatement(INSERT_FILE);
    for (HistoryEntry entry : history.getHistoryEntries()) {
      for (String file : entry.getFiles()) {
        String fullPath = toUnixPath(file);
        // Add the file to the database and to the map if it isn't
        // there already. Assumption: If the file is in the database,
        // all its parent directories are also there.
        if (!fileMap.containsKey(fullPath)) {
          // Get the dir id for this file, potentially adding the
          // parent directories to the db and to dirMap.
          int dir = addAllDirs(insDir, reposId, fullPath, dirMap);
          int fileId = nextFileId.getAndIncrement();
          insFile.setInt(1, dir);
          insFile.setString(2, getBaseName(fullPath));
          insFile.setInt(3, fileId);
          insFile.executeUpdate();
          fileMap.put(fullPath, fileId);

          // Commit every now and then to allow the database to free
          // resources (like locks and transaction log), but not too
          // frequently, since that may kill the performance. It is
          // OK not to commit for every file added, since the worst
          // thing that could happen is that we need to re-insert
          // the files added since the last commit in case of a crash.
          insertCount++;
          if (insertCount % 30 == 0) {
            conn.commit();
          }
        }
      }
    }
  }
  /** Test of parse method, of class ClearCaseHistoryParser. */
  @Test
  public void parseFileHistory() throws Exception {
    String author1 = "First Last (username)";
    String author2 = "First2 Last2 (username2)";
    String output =
        "create version\n"
            + "20020618.212343\n"
            + author1
            + "\n"
            + "/main/3\n"
            + "Merge from eeeee for ffffff\n"
            + ".\n"
            + "create version\n"
            + "20020222.164239\n"
            + author2
            + "\n"
            + "/main/2\n"
            + "Merge from projper branch.\n"
            + "Adding aaaaaaa to the yyyyy.\n"
            + ".\n"
            + "create version\n"
            + "20020208.150825\n"
            + author2
            + "\n"
            + "/main/1\n"
            + "Target for javac set to 1.3.\n"
            + "Fixed handling of "
            + " res directory.\n"
            + ".\n"
            + "create version\n"
            + "20010924.095104\n"
            + author2
            + "\n"
            + "/main/0\n"
            + "\n"
            + ".\n"
            + "create branch\n"
            + "20010924.095104\n"
            + author2
            + "\n"
            + "/main\n"
            + "\n"
            + ".\n"
            + "create file element\n"
            + "20010924.095104\n"
            + author1
            + "\n"
            + "\n"
            + "\n"
            + ".";

    History result = instance.parse(output);
    assertNotNull(result);
    assertEquals(4, result.getHistoryEntries().size());

    HistoryEntry e1 = result.getHistoryEntries().get(0);
    assertEquals("/main/3", e1.getRevision());
    assertEquals(author1, e1.getAuthor());
    assertEquals(0, e1.getFiles().size());
    assertTrue(e1.getMessage().contains("eeeee"));

    HistoryEntry e4 = result.getHistoryEntries().get(3);
    assertEquals("/main/0", e4.getRevision());
    assertEquals(author2, e4.getAuthor());
    assertEquals(0, e4.getFiles().size());
  }
  private void storeHistory(ConnectionResource conn, History history, Repository repository)
      throws SQLException {

    Integer reposId = null;
    Map<String, Integer> authors = null;
    Map<String, Integer> files = null;
    Map<String, Integer> directories = null;
    PreparedStatement addChangeset = null;
    PreparedStatement addDirchange = null;
    PreparedStatement addFilechange = null;
    PreparedStatement addFilemove = null;
    RuntimeEnvironment env = RuntimeEnvironment.getInstance();

    // return immediately when there is nothing to do
    List<HistoryEntry> entries = history.getHistoryEntries();
    if (entries.isEmpty()) {
      return;
    }

    for (int i = 0; ; i++) {
      try {
        if (reposId == null) {
          reposId = getRepositoryId(conn, repository);
          conn.commit();
        }

        if (authors == null) {
          authors = getAuthors(conn, history, reposId);
          conn.commit();
        }

        if (directories == null || files == null) {
          Map<String, Integer> dirs = new HashMap<String, Integer>();
          Map<String, Integer> fls = new HashMap<String, Integer>();
          getFilesAndDirectories(conn, history, reposId, dirs, fls);
          conn.commit();
          directories = dirs;
          files = fls;
        }

        if (addChangeset == null) {
          addChangeset = conn.getStatement(ADD_CHANGESET);
        }

        if (addDirchange == null) {
          addDirchange = conn.getStatement(ADD_DIRCHANGE);
        }

        if (addFilechange == null) {
          addFilechange = conn.getStatement(ADD_FILECHANGE);
        }

        if (addFilemove == null) {
          addFilemove = conn.getStatement(ADD_FILEMOVE);
        }

        // Success! Break out of the loop.
        break;

      } catch (SQLException sqle) {
        handleSQLException(sqle, i);
        conn.rollback();
      }
    }

    addChangeset.setInt(1, reposId);

    // getHistoryEntries() returns the entries in reverse chronological
    // order, but we want to insert them in chronological order so that
    // their auto-generated identity column can be used as a chronological
    // ordering column. Otherwise, incremental updates will make the
    // identity column unusable for chronological ordering. So therefore
    // we walk the list backwards.
    for (ListIterator<HistoryEntry> it = entries.listIterator(entries.size()); it.hasPrevious(); ) {
      HistoryEntry entry = it.previous();
      retry:
      for (int i = 0; ; i++) {
        try {
          addChangeset.setString(2, entry.getRevision());
          addChangeset.setInt(3, authors.get(entry.getAuthor()));
          addChangeset.setTimestamp(4, new Timestamp(entry.getDate().getTime()));
          String msg = entry.getMessage();
          // Truncate the message if it can't fit in a VARCHAR
          // (bug #11663).
          if (msg.length() > MAX_MESSAGE_LENGTH) {
            msg = truncate(msg, MAX_MESSAGE_LENGTH);
          }
          addChangeset.setString(5, msg);
          int changesetId = nextChangesetId.getAndIncrement();
          addChangeset.setInt(6, changesetId);
          addChangeset.executeUpdate();

          // Add one row for each file in FILECHANGES, and one row
          // for each path element of the directories in DIRCHANGES.
          Set<String> addedDirs = new HashSet<String>();
          addDirchange.setInt(1, changesetId);
          addFilechange.setInt(1, changesetId);
          for (String file : entry.getFiles()) {
            // ignore ignored files
            String repodir = "";
            try {
              repodir = env.getPathRelativeToSourceRoot(new File(repository.getDirectoryName()), 0);
            } catch (IOException ex) {
              Logger.getLogger(JDBCHistoryCache.class.getName()).log(Level.SEVERE, null, ex);
            }

            String fullPath = toUnixPath(file);
            if (!history.isIgnored(file.substring(repodir.length() + 1))) {
              int fileId = files.get(fullPath);
              addFilechange.setInt(2, fileId);
              addFilechange.executeUpdate();
            }
            String[] pathElts = splitPath(fullPath);
            for (int j = 0; j < pathElts.length; j++) {
              String dir = unsplitPath(pathElts, j);
              // Only add to DIRCHANGES if we haven't already
              // added this dir/changeset combination.
              if (!addedDirs.contains(dir)) {
                addDirchange.setInt(2, directories.get(dir));
                addDirchange.executeUpdate();
                addedDirs.add(dir);
              }
            }
          }

          conn.commit();

          // Successfully added the entry. Break out of retry loop.
          break retry;

        } catch (SQLException sqle) {
          handleSQLException(sqle, i);
          conn.rollback();
        }
      }
    }

    /*
     * Special handling for certain files - this is mainly for files which
     * have been renamed in Mercurial repository.
     * This ensures that their complete history (follow) will be saved.
     */
    for (String filename : history.getIgnoredFiles()) {
      String file_path = repository.getDirectoryName() + File.separatorChar + filename;
      File file = new File(file_path);
      String repo_path = file_path.substring(env.getSourceRootPath().length());
      History hist;
      try {
        hist = repository.getHistory(file);
      } catch (HistoryException ex) {
        Logger.getLogger(JDBCHistoryCache.class.getName()).log(Level.SEVERE, null, ex);
        continue;
      }

      int fileId = files.get(repo_path);
      for (HistoryEntry entry : hist.getHistoryEntries()) {
        retry:
        for (int i = 0; ; i++) {
          try {
            int changesetId = getIdForRevision(entry.getRevision());

            /*
             * If the file exists in the changeset, store it in
             * the table tracking moves of the file when it had
             * one of its precedent names so it can be found
             * when performing historyget on directory.
             */
            if (entry.getFiles().contains(repo_path)) {
              addFilechange.setInt(1, changesetId);
              addFilechange.setInt(2, fileId);
              addFilechange.executeUpdate();
            } else {
              addFilemove.setInt(1, changesetId);
              addFilemove.setInt(2, fileId);
              addFilemove.executeUpdate();
            }

            conn.commit();
            break retry;
          } catch (SQLException sqle) {
            handleSQLException(sqle, i);
            conn.rollback();
          }
        }
      }
    }
  }
  /** Test of parse method, of class ClearCaseHistoryParser. */
  @Test
  public void parseDirHistory() throws Exception {
    String author1 = "First Last (username)";
    String author2 = "First2 Last2 (username2)";
    String output =
        "create directory version\n"
            + "20050401.162902\n"
            + author1
            + "\n"
            + "/main/3\n"
            + "Removed directory element \"prototype\".\n"
            + ".\n"
            + "create directory version\n"
            + "20020618.215917\n"
            + author2
            + "\n"
            + "/main/2\n"
            + "Merge from wwwww for dddddd\n"
            + ".\n"
            + "create directory version\n"
            + "20010228.174617\n"
            + author1
            + "\n"
            + "/main/1\n"
            + "New structure.\n"
            + "\n"
            + "Added directory element \"generic\".\n"
            + "Added directory element \"prototype\".\n"
            + "Added directory element \"install\".\n"
            + "Added directory element \"service\".\n"
            + ".\n"
            + "create directory version\n"
            + "20001215.092522\n"
            + author2
            + "\n"
            + "/main/0\n"
            + "\n"
            + ".\n"
            + "create branch\n"
            + "20001215.092522\n"
            + author1
            + "\n"
            + "/main\n"
            + "\n"
            + ".\n"
            + "create directory element\n"
            + "20001215.092522\n"
            + author1
            + "\n"
            + "\n"
            + "\n"
            + ".";
    History result = instance.parse(output);
    assertNotNull(result);
    assertEquals(4, result.getHistoryEntries().size());

    HistoryEntry e1 = result.getHistoryEntries().get(0);
    assertEquals("/main/3", e1.getRevision());
    assertEquals(author1, e1.getAuthor());
    assertEquals(0, e1.getFiles().size());
    assertTrue(e1.getMessage().contains("prototype"));

    HistoryEntry e4 = result.getHistoryEntries().get(3);
    assertEquals("/main/0", e4.getRevision());
    assertEquals(author2, e4.getAuthor());
    assertEquals(0, e4.getFiles().size());
  }