/* * CDDL HEADER START * * The contents of this file are subject to the terms of the * Common Development and Distribution License (the "License"). * You may not use this file except in compliance with the License. * * See LICENSE.txt included in this distribution for the specific * language governing permissions and limitations under the License. * * When distributing Covered Code, include this CDDL HEADER in each * file and include the License file at LICENSE.txt. * If applicable, add the following below this CDDL HEADER, with the * fields enclosed by brackets "[]" replaced with your own identifying * information: Portions Copyright [yyyy] [name of copyright owner] * * CDDL HEADER END */ /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ package org.opensolaris.opengrok.history; import java.io.BufferedReader; import java.io.File; import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.text.DateFormat; import java.text.ParseException; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; import org.opensolaris.opengrok.configuration.Configuration; import org.opensolaris.opengrok.configuration.RuntimeEnvironment; import org.opensolaris.opengrok.util.Executor; /** * Class used to parse the history log from Monotone * * @author Trond Norbye */ class MonotoneHistoryParser implements Executor.StreamHandler { private static final Logger logger = Logger.getLogger(MonotoneHistoryParser.class.getName()); private List entries = new ArrayList(); //NOPMD private final MonotoneRepository repository; private final String mydir; MonotoneHistoryParser(MonotoneRepository repository) { this.repository = repository; mydir = repository.getDirectoryName() + File.separator; } /** * Parse the history for the specified file or directory. If a changeset is * specified, only return the history from the changeset right after the * specified one. * * @param file the file or directory to get history for * @param changeset the changeset right before the first one to fetch, or * {@code null} if all changesets should be fetched * @return history for the specified file or directory * @throws HistoryException if an error happens when parsing the history */ History parse(File file, String changeset) throws HistoryException { try { Executor executor = repository.getHistoryLogExecutor(file, changeset); int status = executor.exec(true, this); if (status != 0) { throw new HistoryException("Failed to get history for '" + file.getAbsolutePath() + "' - Exit code " + status); } } catch (IOException e) { throw new HistoryException("Failed to get history for '" + file.getAbsolutePath() + "'", e); } return new History(entries); } private static final String LOG_SEPARATOR = "-----------------------------------------------------------------"; /** * Process the output from the hg log command and insert the HistoryEntries * into the history field. * * @param input The output from the process * @throws java.io.IOException If an error occurs while reading the stream */ @Override public void processStream(InputStream input) throws IOException { Configuration cfg = RuntimeEnvironment.getConfig(); DateFormat df = repository.getDateFormat(); BufferedReader in = new BufferedReader(new InputStreamReader(input)); String s; HistoryEntry entry = null; int state = 0; while ((s = in.readLine()) != null) { s = s.trim(); // Later versions of monotone (such as 1.0) output even more dashes // so lets require the minimum amount for maximum compatibility // between monotone versions. if (s.startsWith(LOG_SEPARATOR)) { if (entry != null && state > 2) { entries.add(entry); } entry = new HistoryEntry(); entry.setActive(true); state = 0; continue; } if (entry == null) { continue; } switch (state) { case 0: if (s.startsWith("Revision:")) { String rev = s.substring("Revision:".length()).trim(); entry.setRevision(rev); ++state; } break; case 1: if (s.startsWith("Author:")) { entry.setAuthor(s.substring("Author:".length()).trim()); ++state; } break; case 2: if (s.startsWith("Date:")) { Date date = null; try { date = df.parse(s.substring("date:".length()).trim()); } catch (ParseException pe) { logger.warning("Could not parse date " + s + ": " + pe.getMessage()); logger.log(Level.FINE, "processStream", pe); } entry.setDate(date == null ? new Date() : date); ++state; } break; case 3: if (s.startsWith("Modified ") || s.startsWith("Added ") || s.startsWith("Deleted ")) { ++state; } else if (s.equalsIgnoreCase("ChangeLog:")) { state = 5; } break; case 4: if (s.startsWith("Modified ") || s.startsWith("Added ") || s.startsWith("Deleted ")) //NOPMD { /* swallow */ } else if (s.equalsIgnoreCase("ChangeLog:")) { state = 5; } else { String files[] = s.split(" "); for (String f : files) { File file = new File(mydir, f); try { entry.addFile(cfg.getPathRelativeToSourceRoot(file, 0)); } catch (FileNotFoundException e) { // NOPMD // If the file is not located under the source // root, ignore it } } } break; case 5: entry.appendMessage(s); break; default: logger.warning("Unknown parser state " + state); break; } } if (entry != null && state > 2) { entries.add(entry); } } }