1433N/A/*
1433N/A * CDDL HEADER START
1433N/A *
1433N/A * The contents of this file are subject to the terms of the
1433N/A * Common Development and Distribution License (the "License").
1433N/A * You may not use this file except in compliance with the License.
1433N/A *
1433N/A * See LICENSE.txt included in this distribution for the specific
1433N/A * language governing permissions and limitations under the License.
1433N/A *
1433N/A * When distributing Covered Code, include this CDDL HEADER in each
1433N/A * file and include the License file at LICENSE.txt.
1433N/A * If applicable, add the following below this CDDL HEADER, with the
1433N/A * fields enclosed by brackets "[]" replaced with your own identifying
1433N/A * information: Portions Copyright [yyyy] [name of copyright owner]
1433N/A *
1433N/A * CDDL HEADER END
1433N/A */
1433N/A
1433N/A/*
1433N/A * Copyright (c) 2008, 2011, Oracle and/or its affiliates. All rights reserved.
1433N/A */
1433N/Apackage org.opensolaris.opengrok.history;
1433N/A
1433N/Aimport java.io.BufferedReader;
1433N/Aimport java.io.ByteArrayInputStream;
1433N/Aimport java.io.File;
1433N/Aimport java.io.IOException;
1433N/Aimport java.io.InputStream;
1433N/Aimport java.util.ArrayList;
1433N/Aimport java.util.logging.Level;
1461N/Aimport java.util.logging.Logger;
1433N/Aimport java.util.regex.Matcher;
1433N/Aimport java.util.regex.Pattern;
1461N/A
1462N/Aimport org.opensolaris.opengrok.configuration.Configuration;
1434N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
1433N/Aimport org.opensolaris.opengrok.util.Executor;
1433N/Aimport org.opensolaris.opengrok.util.IOUtils;
1433N/A
1433N/A/**
1433N/A * Access to an AccuRev repository (here an actual user workspace)
1433N/A *
1434N/A * AccuRev requires that a user logs into their system before it can be used. So
1434N/A * on the machine acting as the OpenGrok server, some valid user has to be
1434N/A * permanently logged in. (accurev login -n <user>)
1433N/A *
1434N/A * It appears that the file path that is given to all these methods is the
1434N/A * complete path to the file which includes the path to the root of the source
1434N/A * location. This means that when using the -P option of OpenGrok to make all
1434N/A * the directories pointed to by the source root to be seen as separate projects
1434N/A * is not all as it would seem. The History GURU always starts building the
1434N/A * history cache using the source root. Well there is NO HISTORY for anything at
1434N/A * the source root because it is not part of an actual AccuRev depot. The
1434N/A * directories within the source root directory represent the work areas of
1434N/A * AccuRev and it is those areas where history can be obtained. This
1434N/A * implementation allows those directories to be symbolic links to the actual
1434N/A * workspaces.
1433N/A *
1433N/A * Other assumptions:
1461N/A * There is only one associated AccuRev depot associated with workspaces.
1433N/A *
1433N/A * @author Steven Haehn
1433N/A */
1433N/Apublic class AccuRevRepository extends Repository {
1433N/A
1461N/A private static final Logger logger =
1461N/A Logger.getLogger(AccuRevRepository.class.getName());
1433N/A private static final long serialVersionUID = 1L;
1434N/A /**
1434N/A * The property name used to obtain the client command for this repository.
1434N/A */
1462N/A public static final String CMD_PROPERTY_KEY =
1462N/A Configuration.PROPERTY_KEY_PREFIX + "history.AccuRev";
1434N/A /**
1434N/A * The command to use to access the repository if none was given explicitly
1434N/A */
1433N/A public static final String CMD_FALLBACK = "accurev";
1461N/A private static final Pattern annotationPattern =
1461N/A Pattern.compile("^\\s+(\\d+/\\d+)\\s+(\\w+)"); // version, user
1461N/A private static final Pattern depotPattern =
1461N/A Pattern.compile("^Depot:\\s+(\\w+)");
1433N/A
1461N/A /**
1462N/A * Create a new instance of type {@code AccuRev}.
1461N/A */
1433N/A public AccuRevRepository() {
1433N/A type = "AccuRev";
1433N/A datePattern = "yyyy/MM/dd hh:mm:ss";
1434N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1433N/A }
1433N/A
1461N/A /**
1461N/A * {@inheritDoc}
1461N/A */
1433N/A @Override
1461N/A public Annotation annotate(File file, String rev) {
1434N/A Annotation a = new Annotation(file.getName());
1433N/A ArrayList<String> cmd = new ArrayList<String>();
1461N/A /* Strip off source root to get to workspace path. */
1434N/A String path = getDepotRelativePath(file);
1434N/A
1461N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1433N/A cmd.add(this.cmd);
1433N/A cmd.add("annotate");
1461N/A cmd.add("-fvu"); // version & user
1434N/A if (rev != null) {
1434N/A cmd.add("-v");
1434N/A cmd.add(rev.trim());
1433N/A }
1434N/A cmd.add(path);
1433N/A Executor executor = new Executor(cmd, file.getParentFile());
1433N/A executor.exec();
1433N/A BufferedReader reader = new BufferedReader(executor.getOutputReader());
1433N/A String line;
1433N/A int lineno = 0;
1461N/A Matcher matcher = annotationPattern.matcher("");
1433N/A try {
1433N/A while ((line = reader.readLine()) != null) {
1461N/A lineno++;
1461N/A matcher.reset(line);
1433N/A if (matcher.find()) {
1461N/A a.addLine(matcher.group(1), matcher.group(2));
1433N/A } else {
1461N/A logger.warning("Did not find annotation in line " + lineno
1461N/A + ": [" + line + "]");
1433N/A }
1433N/A }
1433N/A } catch (IOException e) {
1461N/A logger.warning("Could not read annotations for '" + file + "'");
1461N/A logger.log(Level.FINE, "annotate", e);
1433N/A }
1434N/A
1433N/A IOUtils.close(reader);
1433N/A return a;
1433N/A }
1433N/A
1433N/A /**
1434N/A * Get an executor to be used for retrieving the history log for the given
1434N/A * file. (used by AccuRevHistoryParser).
1433N/A *
1473N/A * @param file file for which history is to be retrieved (canonical path
1473N/A * incl. source root).
1433N/A * @return An Executor ready to be started
1433N/A */
1461N/A Executor getHistoryLogExecutor(File file) {
1461N/A /* Strip off source root to get to workspace path. */
1434N/A String path = getDepotRelativePath(file);
1433N/A ArrayList<String> cmd = new ArrayList<String>();
1434N/A
1461N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1434N/A cmd.add(this.cmd);
1434N/A cmd.add("hist");
1434N/A if (!file.isDirectory()) {
1434N/A cmd.add("-k");
1461N/A cmd.add("keep"); // get a list of all 'real' file versions
1433N/A }
1434N/A cmd.add(path);
1434N/A return new Executor(cmd, file.getParentFile());
1433N/A }
1433N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
1433N/A @Override
1473N/A public InputStream getHistoryGet(String parent, String basename, String rev)
1473N/A {
1433N/A ArrayList<String> cmd = new ArrayList<String>();
1433N/A InputStream inputStream = null;
1434N/A File directory = new File(parent);
1434N/A
1461N/A /* The only way to guarantee getting the contents of a file is to fire
1461N/A * off an AccuRev 'stat'us command to get the element ID number for the
1434N/A * subsequent 'cat' command. (Element ID's are unique for a file, unless
1434N/A * evil twins are present) This is because it is possible that the file
1434N/A * may have been moved to a different place in the depot. The 'stat'
1434N/A * command will produce a line with the format:
1434N/A *
1434N/A * <filePath> <elementID> <virtualVersion> (<realVersion>) (<status>)
1434N/A *
1461N/A * /./myFile e:17715 CP.73_Depot/2 (3220/2) (backed)
1434N/A */
1461N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1434N/A cmd.add(this.cmd);
1434N/A cmd.add("stat");
1434N/A cmd.add("-fe");
1434N/A cmd.add(basename);
1434N/A Executor executor = new Executor(cmd, directory);
1433N/A executor.exec();
1433N/A
1434N/A BufferedReader info = new BufferedReader(executor.getOutputReader());
1433N/A String elementID = null;
1433N/A try {
1461N/A String[] statInfo = info.readLine().split("\\s+");
1434N/A elementID = statInfo[1].substring(2); // skip over 'e:'
1434N/A } catch (IOException e) {
1461N/A logger.warning("Could not obtain status for '" + basename + "'");
1433N/A }
1434N/A if (elementID != null) {
1461N/A /* This really gets the contents of the file */
1433N/A cmd.clear();
1434N/A cmd.add(this.cmd);
1434N/A cmd.add("cat");
1434N/A cmd.add("-v");
1434N/A cmd.add(rev.trim());
1434N/A cmd.add("-e");
1434N/A cmd.add(elementID);
1434N/A
1434N/A executor = new Executor(cmd, directory);
1433N/A executor.exec();
1434N/A
1461N/A inputStream = new ByteArrayInputStream(executor.getOutputString()
1461N/A .getBytes());
1433N/A }
1433N/A return inputStream;
1433N/A }
1434N/A
1461N/A /**
1461N/A * {@inheritDoc}
1461N/A */
1433N/A @Override
1461N/A public void update() {
1434N/A throw new UnsupportedOperationException("Not supported yet.");
1433N/A }
1433N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
1433N/A @Override
1473N/A public boolean fileHasHistory(File file) {
1433N/A return true;
1433N/A }
1433N/A
1461N/A /**
1461N/A * {@inheritDoc}
1461N/A */
1433N/A @Override
1433N/A public boolean fileHasAnnotation(File file) {
1433N/A return true;
1433N/A }
1434N/A
1433N/A /**
1461N/A * Get the path for the given file relative to the opengrok source root
1461N/A * directory.
1461N/A *
1461N/A * @param file file to check
1461N/A * @return {@code /./} on error if file names the opengrok source directory,
1461N/A * the relative path prefixed with a {@code /.} otherwise.
1433N/A */
1461N/A public static String getDepotRelativePath(File file) {
1433N/A String path = "/./";
1434N/A
1433N/A try {
1470N/A path = RuntimeEnvironment.getConfig()
1461N/A .getPathRelativeToSourceRoot(file, 0);
1434N/A if (path.startsWith("/")) {
1433N/A path = "/." + path;
1434N/A } else {
1433N/A path = "/./" + path;
1433N/A }
1434N/A } catch (IOException e) {
1461N/A logger.warning("Unable to determine depot relative path for '"
1461N/A + file.getPath() + "'");
1433N/A }
1433N/A return path;
1433N/A }
1434N/A
1461N/A /**
1461N/A * Check if a given path is associated with an AccuRev workspace.
1461N/A *
1461N/A * The AccuRev 'info' command provides a Depot name when in a known
1461N/A * workspace. Otherwise, the Depot name will be missing.
1461N/A *
1473N/A * @param sourceHome The presumed path to an AccuRev workspace directory.
1461N/A * @return {@code true} if the given path is in the depot.
1461N/A */
1433N/A @Override
1473N/A public boolean isRepositoryFor(File sourceHome) {
1461N/A // TODO: oh my goodness!!! Have fun invoking this for all files in a
1461N/A // repo search scan ...
1461N/A if (isWorking()) {
1461N/A ArrayList<String> cmd = new ArrayList<String>();
1461N/A ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
1461N/A cmd.add(this.cmd);
1461N/A cmd.add("info");
1434N/A
1461N/A Executor executor = new Executor(cmd, sourceHome);
1461N/A executor.exec(true);
1461N/A
1461N/A BufferedReader info = new BufferedReader(executor.getOutputReader());
1461N/A try {
1461N/A String line;
1461N/A Matcher depotMatch = depotPattern.matcher("");
1461N/A while ((line = info.readLine()) != null) {
1461N/A depotMatch.reset(line);
1461N/A if (line.indexOf("not logged in") != -1) {
1461N/A logger.warning("Not logged into AccuRev server");
1461N/A // TODO: is it ever possible to find a match if not
1461N/A // logged in? If not, return false immediately.
1461N/A }
1461N/A if (depotMatch.find()) {
1461N/A return true;
1461N/A }
1461N/A }
1461N/A } catch (IOException e) {
1461N/A logger.warning("Could not find AccuRev repository for '"
1461N/A + sourceHome + "'");
1461N/A }
1461N/A }
1461N/A return false;
1433N/A }
1433N/A
1461N/A /**
1461N/A * {@inheritDoc}
1461N/A */
1433N/A @Override
1433N/A public boolean isWorking() {
1433N/A if (working == null) {
1434N/A working = checkCmd(cmd, "info");
1433N/A }
1433N/A return working.booleanValue();
1433N/A }
1433N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
1433N/A @Override
1473N/A public boolean hasHistoryForDirectories() {
1433N/A return true;
1433N/A }
1433N/A
1473N/A /**
1473N/A * {@inheritDoc}
1473N/A */
1433N/A @Override
1473N/A public History getHistory(File file) {
1433N/A return new AccuRevHistoryParser().parse(file, this);
1433N/A }
1433N/A}