/*
* 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 (c) 2008, 2014, Oracle and/or its affiliates. All rights reserved.
*/
package org.opensolaris.opengrok.history;
import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.opensolaris.opengrok.OpenGrokLogger;
import org.opensolaris.opengrok.util.Executor;
/**
* Access to a CVS repository.
*/
public class CVSRepository extends RCSRepository {
private static final long serialVersionUID = 1L;
/** The property name used to obtain the client command for repository. */
public static final String CMD_PROPERTY_KEY =
"org.opensolaris.opengrok.history.cvs";
/** The command to use to access the repository if none was given explicitly */
public static final String CMD_FALLBACK = "cvs";
private Boolean isBranch = null;
private String branch = null;
/** Pattern used to extract author/revision from cvs annotate. */
private static final Pattern ANNOTATE_PATTERN =
Pattern.compile("([\\.\\d]+)\\W+\\((\\w+)");
public CVSRepository() {
setType("CVS");
setDatePattern("yyyy-MM-dd hh:mm:ss");
}
@Override
public boolean isWorking() {
if (working == null) {
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
working = checkCmd(cmd , "--version");
}
return working.booleanValue();
}
@Override
public void setDirectoryName(String directoryName) {
super.setDirectoryName(directoryName);
if (isWorking()) {
Logger logger = OpenGrokLogger.getLogger();
File rootFile = new File(getDirectoryName() + File.separatorChar
+ "CVS" + File.separatorChar + "Root");
BufferedReader input;
String root;
try {
input = new BufferedReader(new FileReader(rootFile));
try {
root = input.readLine();
} catch (java.io.IOException e) {
logger.warning("failed to load: " + e);
return;
} finally {
try {
input.close();
} catch (java.io.IOException e) {
logger.info("failed to close: " + e);
}
}
} catch (java.io.FileNotFoundException e) {
logger.fine("not loading CVS Root file: " + e);
return;
}
if (!root.startsWith("/")) {
setRemote(true);
}
}
}
@Override
File getRCSFile(File file) {
File cvsFile =
RCSHistoryParser.getCVSFile(file.getParent(), file.getName());
if (cvsFile != null && cvsFile.exists()) {
return cvsFile;
}
return null;
}
@Override
public boolean isRepositoryFor(File file) {
if (file.isDirectory()) {
File cvsDir = new File(file, "CVS");
return cvsDir.isDirectory();
}
return false;
}
@Override
public void update() throws IOException {
File directory = new File(getDirectoryName());
List<String> cmd = new ArrayList<String>();
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
cmd.add(this.cmd);
cmd.add("update");
Executor executor = new Executor(cmd, directory);
if (executor.exec() != 0) {
throw new IOException(executor.getErrorString());
}
}
/**
* Get an executor to be used for retrieving the history log for the
* named file.
*
* @param file The file to retrieve history for
* @return An Executor ready to be started
*/
Executor getHistoryLogExecutor(final File file) throws IOException {
String abs = file.getCanonicalPath();
String filename = "";
if (abs.length() > directoryName.length()) {
filename = abs.substring(directoryName.length() + 1);
}
List<String> cmd = new ArrayList<String>();
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
cmd.add(this.cmd);
cmd.add("log");
if (isBranch == null) {
File tagFile = new File(getDirectoryName(), "CVS/Tag");
if (tagFile.isFile()) {
isBranch = Boolean.TRUE;
try (BufferedReader br = new BufferedReader(new FileReader(tagFile))) {
String line = br.readLine();
if (line != null) {
branch = line.substring(1);
}
} catch (IOException ex) {
OpenGrokLogger.getLogger().log(Level.WARNING,
"Failed to work with CVS/Tag file of {0}",
getDirectoryName() + ": " + ex.getClass().toString());
} catch (Exception exp) {
OpenGrokLogger.getLogger().log(Level.WARNING,
"Failed to get revision tag of {0}",
getDirectoryName() + ": " + exp.getClass().toString());
}
} else {
isBranch = Boolean.FALSE;
}
}
if (isBranch.equals(Boolean.TRUE) && branch != null && !branch.isEmpty()) {
// Just generate THIS branch history, we don't care about the other
// branches which are not checked out.
cmd.add("-r"+branch);
} else {
// Get revisions on this branch only (otherwise the revisions
// list produced by the cvs log command would be unsorted).
cmd.add("-b");
}
if (filename.length() > 0) {
cmd.add(filename);
}
return new Executor(cmd, new File(getDirectoryName()));
}
@Override
public InputStream getHistoryGet(String parent, String basename, String rev)
{
InputStream ret = null;
Process process = null;
String revision = rev;
if (rev.indexOf(':') != -1) {
revision = rev.substring(0, rev.indexOf(':'));
}
try {
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
String argv[] = {cmd, "up", "-p", "-r", revision, basename};
process = Runtime.getRuntime().exec(argv, null, new File(parent));
ByteArrayOutputStream out = new ByteArrayOutputStream();
byte[] buffer = new byte[32 * 1024];
try (InputStream in = process.getInputStream()) {
int len;
while ((len = in.read(buffer)) != -1) {
if (len > 0) {
out.write(buffer, 0, len);
}
}
}
ret = new ByteArrayInputStream(out.toByteArray());
} catch (Exception exp) {
OpenGrokLogger.getLogger().log(Level.SEVERE,
"Failed to get history: {0}", exp.getClass().toString());
} finally {
// Clean up zombie-processes...
if (process != null) {
try {
process.exitValue();
} catch (IllegalThreadStateException exp) {
// the process is still running??? just kill it..
process.destroy();
}
}
}
return ret;
}
@Override
public boolean fileHasAnnotation(File file) {
return true;
}
@Override
public boolean fileHasHistory(File file) {
// @TODO: Research how to cheaply test if a file in a given
// CVS repo has history. If there is a cheap test, then this
// code can be refined, boosting performance.
return true;
}
@Override
History getHistory(File file) throws HistoryException {
return new CVSHistoryParser().parse(file, this);
}
@Override
Annotation annotate(File file, String revision) throws IOException {
ArrayList<String> cmd = new ArrayList<String>();
ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
cmd.add(this.cmd);
cmd.add("annotate");
if (revision != null) {
cmd.add("-r");
cmd.add(revision);
}
cmd.add(file.getName());
Executor exec = new Executor(cmd, file.getParentFile());
int status = exec.exec();
if (status != 0) {
OpenGrokLogger.getLogger().log(Level.WARNING,
"Failed to get annotations for: \"{0}\" Exit code: {1}",
new Object[]{file.getAbsolutePath(), String.valueOf(status)});
}
return parseAnnotation(exec.getOutputReader(), file.getName());
}
protected Annotation parseAnnotation(Reader input, String fileName)
throws IOException
{
BufferedReader in = new BufferedReader(input);
Annotation ret = new Annotation(fileName);
String line = "";
int lineno = 0;
boolean hasStarted = false;
Matcher matcher = ANNOTATE_PATTERN.matcher(line);
while ((line = in.readLine()) != null) {
// Skip header
if (!hasStarted && (line.length() == 0
|| !Character.isDigit(line.charAt(0))))
{
continue;
}
hasStarted = true;
// Start parsing
++lineno;
matcher.reset(line);
if (matcher.find()) {
String rev = matcher.group(1);
String author = matcher.group(2).trim();
ret.addLine(rev, author, true);
} else {
OpenGrokLogger.getLogger().log(Level.SEVERE,
"Error: did not find annotation in line {0}: [{1}]",
new Object[]{String.valueOf(lineno), line});
}
}
return ret;
}
}