323N/A/*
323N/A * CDDL HEADER START
323N/A *
323N/A * The contents of this file are subject to the terms of the
323N/A * Common Development and Distribution License (the "License").
323N/A * You may not use this file except in compliance with the License.
323N/A *
323N/A * See LICENSE.txt included in this distribution for the specific
323N/A * language governing permissions and limitations under the License.
323N/A *
323N/A * When distributing Covered Code, include this CDDL HEADER in each
323N/A * file and include the License file at LICENSE.txt.
323N/A * If applicable, add the following below this CDDL HEADER, with the
323N/A * fields enclosed by brackets "[]" replaced with your own identifying
323N/A * information: Portions Copyright [yyyy] [name of copyright owner]
323N/A *
323N/A * CDDL HEADER END
323N/A */
323N/A
323N/A/*
1248N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
323N/A */
323N/A
323N/Apackage org.opensolaris.opengrok.history;
323N/A
710N/Aimport java.io.BufferedReader;
752N/Aimport java.io.ByteArrayInputStream;
752N/Aimport java.io.ByteArrayOutputStream;
323N/Aimport java.io.File;
999N/Aimport java.io.FileReader;
643N/Aimport java.io.IOException;
752N/Aimport java.io.InputStream;
710N/Aimport java.io.Reader;
643N/Aimport java.util.ArrayList;
643N/Aimport java.util.List;
710N/Aimport java.util.logging.Level;
1327N/Aimport java.util.logging.Logger;
710N/Aimport java.util.regex.Matcher;
710N/Aimport java.util.regex.Pattern;
1327N/A
1462N/Aimport org.opensolaris.opengrok.configuration.Configuration;
643N/Aimport org.opensolaris.opengrok.util.Executor;
1195N/Aimport org.opensolaris.opengrok.util.IOUtils;
323N/A
323N/A/**
323N/A * Access to a local CVS repository.
323N/A */
323N/Apublic class CVSRepository extends RCSRepository {
1473N/A private static final Logger logger =
1473N/A Logger.getLogger(CVSRepository.class.getName());
815N/A private static final long serialVersionUID = 1L;
1182N/A /** The property name used to obtain the client command for repository. */
1190N/A public static final String CMD_PROPERTY_KEY =
1462N/A Configuration.PROPERTY_KEY_PREFIX + "history.cvs";
1182N/A /** The command to use to access the repository if none was given explicitly */
1182N/A public static final String CMD_FALLBACK = "cvs";
710N/A
1462N/A /**
1462N/A * Create a new instance of type {@code CVS}.
1462N/A */
664N/A public CVSRepository() {
664N/A type = "CVS";
753N/A datePattern = "yyyy-MM-dd hh:mm:ss";
664N/A }
1190N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
710N/A @Override
710N/A public boolean isWorking() {
1182N/A if (working == null) {
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1248N/A working = checkCmd(cmd , "--version");
1182N/A }
1182N/A return working.booleanValue();
710N/A }
710N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
323N/A @Override
1473N/A protected File getRCSFile(File file) {
323N/A File cvsFile =
1473N/A RCSHistoryParser.getCVSFile(file.getParent(), file.getName());
323N/A if (cvsFile != null && cvsFile.exists()) {
323N/A return cvsFile;
323N/A }
1183N/A return null;
323N/A }
323N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
323N/A @Override
323N/A public boolean isRepositoryFor(File file) {
1473N/A if (file.isDirectory()) {
1473N/A File cvsDir = new File(file, "CVS");
1473N/A return cvsDir.isDirectory();
1473N/A }
1473N/A return false;
323N/A }
1190N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
643N/A @Override
643N/A public void update() throws IOException {
643N/A File directory = new File(getDirectoryName());
643N/A
643N/A List<String> cmd = new ArrayList<String>();
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A cmd.add(this.cmd);
643N/A cmd.add("update");
643N/A Executor executor = new Executor(cmd, directory);
643N/A if (executor.exec() != 0) {
643N/A throw new IOException(executor.getErrorString());
643N/A }
643N/A }
710N/A
990N/A private Boolean isBranch=null;
999N/A private String branch=null;
710N/A /**
710N/A * Get an executor to be used for retrieving the history log for the
710N/A * named file.
710N/A *
1473N/A * @param file The file to retrieve history for (canonical path incl. source
1473N/A * root).
710N/A * @return An Executor ready to be started
710N/A */
1016N/A Executor getHistoryLogExecutor(final File file) throws IOException {
1016N/A String abs = file.getCanonicalPath();
710N/A String filename = "";
710N/A if (abs.length() > directoryName.length()) {
710N/A filename = abs.substring(directoryName.length() + 1);
710N/A }
710N/A
710N/A List<String> cmd = new ArrayList<String>();
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A cmd.add(this.cmd);
710N/A cmd.add("log");
959N/A cmd.add("-N"); //don't display tags
990N/A
1327N/A if (isBranch == null) {
990N/A File tagFile = new File(getDirectoryName(), "CVS/Tag");
1327N/A if (tagFile.isFile()) {
1327N/A isBranch = Boolean.TRUE;
999N/A try {
1462N/A @SuppressWarnings("resource")
1327N/A BufferedReader br = new BufferedReader(new FileReader(tagFile));
1327N/A try {
1327N/A String line = br.readLine();
1327N/A if (line != null) {
1327N/A branch = line.substring(1);
1327N/A }
1327N/A } catch (Exception exp) {
1327N/A logger.warning("Failed to get revision tag of '"
1327N/A + getDirectoryName() + "': " + exp.getMessage());
1327N/A } finally {
1327N/A IOUtils.close(br);
1327N/A }
1327N/A } catch (IOException ex) {
1327N/A logger.warning("Failed to work with CVS/Tag file of '"
1327N/A + getDirectoryName() + "': " + ex.getMessage());
999N/A }
1327N/A } else {
1327N/A isBranch = Boolean.FALSE;
999N/A }
960N/A }
1190N/A if (isBranch.equals(Boolean.TRUE) && branch!=null && !branch.isEmpty())
1183N/A {
1190N/A //just generate THIS branch history, we don't care about the other
1183N/A // branches which are not checked out
1183N/A cmd.add("-r"+branch);
990N/A }
1190N/A
710N/A if (filename.length() > 0) {
1473N/A cmd.add(filename);
710N/A }
710N/A return new Executor(cmd, new File(getDirectoryName()));
710N/A }
710N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
1462N/A @SuppressWarnings("resource")
710N/A @Override
1183N/A public InputStream getHistoryGet(String parent, String basename, String rev)
1183N/A {
752N/A InputStream ret = null;
752N/A
752N/A Process process = null;
752N/A InputStream in = null;
752N/A String revision = rev;
752N/A
752N/A if (rev.indexOf(':') != -1) {
752N/A revision = rev.substring(0, rev.indexOf(':'));
752N/A }
752N/A try {
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A String argv[] = {cmd, "up", "-p", "-r", revision, basename};
752N/A process = Runtime.getRuntime().exec(argv, null, new File(parent));
752N/A
752N/A ByteArrayOutputStream out = new ByteArrayOutputStream();
752N/A byte[] buffer = new byte[32 * 1024];
752N/A in = process.getInputStream();
752N/A int len;
752N/A
752N/A while ((len = in.read(buffer)) != -1) {
752N/A if (len > 0) {
752N/A out.write(buffer, 0, len);
752N/A }
752N/A }
752N/A
1183N/A ret = new ByteArrayInputStream(out.toByteArray());
752N/A } catch (Exception exp) {
1327N/A logger.warning("Failed to get history: " + exp.getMessage());
752N/A } finally {
1195N/A IOUtils.close(in);
752N/A // Clean up zombie-processes...
752N/A if (process != null) {
752N/A try {
752N/A process.exitValue();
752N/A } catch (IllegalThreadStateException exp) {
752N/A // the process is still running??? just kill it..
752N/A process.destroy();
752N/A }
752N/A }
752N/A }
752N/A
752N/A return ret;
752N/A }
752N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
752N/A @Override
710N/A public boolean fileHasAnnotation(File file) {
710N/A return true;
710N/A }
710N/A
1462N/A /**
1462N/A * {@inheritDoc}
1462N/A */
710N/A @Override
710N/A public boolean fileHasHistory(File file) {
710N/A // @TODO: Research how to cheaply test if a file in a given
710N/A // CVS repo has history. If there is a cheap test, then this
710N/A // code can be refined, boosting performance.
710N/A return true;
710N/A }
710N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
710N/A @Override
1473N/A public History getHistory(File file) throws HistoryException {
765N/A return new CVSHistoryParser().parse(file, this);
765N/A }
765N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
765N/A @Override
1473N/A protected Annotation annotate(File file, String revision) throws IOException {
710N/A ArrayList<String> cmd = new ArrayList<String>();
1182N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1182N/A cmd.add(this.cmd);
710N/A cmd.add("annotate");
710N/A if (revision != null) {
710N/A cmd.add("-r");
710N/A cmd.add(revision);
710N/A }
710N/A cmd.add(file.getName());
710N/A
710N/A Executor exec = new Executor(cmd, file.getParentFile());
710N/A int status = exec.exec();
710N/A
710N/A if (status != 0) {
1327N/A logger.warning("Failed to get annotations for '"
1327N/A + file.getAbsolutePath() + "' - Exit code " + status);
710N/A }
710N/A
710N/A return parseAnnotation(exec.getOutputReader(), file.getName());
710N/A }
710N/A
710N/A /** Pattern used to extract author/revision from cvs annotate. */
1238N/A private static final Pattern ANNOTATE_PATTERN =
710N/A Pattern.compile("([\\.\\d]+)\\W+\\((\\w+)");
710N/A
1462N/A /**
1462N/A * Read the given input and create an annotation from its content.
1462N/A * @param input data to read
1473N/A * @param fileName the name of the associated file.
1462N/A * @return a annotation which may or may not have the required information.
1462N/A * @throws IOException
1462N/A */
1462N/A @SuppressWarnings("static-method")
1190N/A protected Annotation parseAnnotation(Reader input, String fileName)
1190N/A throws IOException
1183N/A {
710N/A BufferedReader in = new BufferedReader(input);
710N/A Annotation ret = new Annotation(fileName);
710N/A String line = "";
710N/A int lineno = 0;
710N/A boolean hasStarted = false;
710N/A Matcher matcher = ANNOTATE_PATTERN.matcher(line);
710N/A while ((line = in.readLine()) != null) {
710N/A // Skip header
1190N/A if (!hasStarted && (line.length() == 0
1190N/A || !Character.isDigit(line.charAt(0))))
1183N/A {
710N/A continue;
710N/A }
710N/A hasStarted = true;
1190N/A
710N/A // Start parsing
710N/A ++lineno;
710N/A matcher.reset(line);
710N/A if (matcher.find()) {
710N/A String rev = matcher.group(1);
710N/A String author = matcher.group(2).trim();
1384N/A ret.addLine(rev, author);
710N/A } else {
1327N/A logger.log(Level.WARNING, "Did not find annotation in line {0} [{1}]",
1183N/A new Object[]{String.valueOf(lineno), line});
710N/A }
710N/A }
710N/A return ret;
710N/A }
323N/A}