/** * Get the history after a specified revision. * * <p>The default implementation first fetches the full history and then throws away the oldest * revisions. This is not efficient, so subclasses should override it in order to get good * performance. Once every subclass has implemented a more efficient method, the default * implementation should be removed and made abstract. * * @param file the file to get the history for * @param sinceRevision the revision right before the first one to return, or {@code null} to * return the full history * @return partial history for file * @throws HistoryException on error accessing the history */ History getHistory(File file, String sinceRevision) throws HistoryException { // If we want an incremental history update and get here, warn that // it may be slow. if (sinceRevision != null) { Logger logger = OpenGrokLogger.getLogger(); logger.log( Level.WARNING, "Incremental history retrieval is not implemented for {0}.", getClass().getSimpleName()); logger.log(Level.WARNING, "Falling back to slower full history retrieval."); } History history = getHistory(file); if (sinceRevision == null) { return history; } List<HistoryEntry> partial = new ArrayList<>(); for (HistoryEntry entry : history.getHistoryEntries()) { partial.add(entry); if (sinceRevision.equals(entry.getRevision())) { // Found revision right before the first one to return. break; } } removeAndVerifyOldestChangeset(partial, sinceRevision); history.setHistoryEntries(partial); return history; }
/** * Remove the oldest changeset from a list (assuming sorted with most recent changeset first) and * verify that it is the changeset we expected to find there. * * @param entries a list of {@code HistoryEntry} objects * @param revision the revision we expect the oldest entry to have * @throws HistoryException if the oldest entry was not the one we expected */ void removeAndVerifyOldestChangeset(List<HistoryEntry> entries, String revision) throws HistoryException { HistoryEntry entry = entries.isEmpty() ? null : entries.remove(entries.size() - 1); // TODO We should check more thoroughly that the changeset is the one // we expected it to be, since some SCMs may change the revision // numbers so that identical revision numbers does not always mean // identical changesets. We could for example get the cached changeset // and compare more fields, like author and date. if (entry == null || !revision.equals(entry.getRevision())) { throw new HistoryException( "Cached revision '" + revision + "' not found in the repository " + getDirectoryName()); } }
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 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()); }
/** 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()); }