1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * See LICENSE.txt included in this distribution for the specific 9 * language governing permissions and limitations under the License. 10 * 11 * When distributing Covered Code, include this CDDL HEADER in each 12 * file and include the License file at LICENSE.txt. 13 * If applicable, add the following below this CDDL HEADER, with the 14 * fields enclosed by brackets "[]" replaced with your own identifying 15 * information: Portions Copyright [yyyy] [name of copyright owner] 16 * 17 * CDDL HEADER END 18 */ 19 20 /* 21 * Copyright (c) 2008, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2018, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.history; 25 26 import java.io.File; 27 import java.io.IOException; 28 import java.io.InputStream; 29 import java.io.OutputStream; 30 import java.util.ArrayList; 31 import java.util.List; 32 import java.util.logging.Level; 33 import java.util.logging.Logger; 34 35 import org.opengrok.indexer.configuration.CommandTimeoutType; 36 import org.opengrok.indexer.configuration.RuntimeEnvironment; 37 import org.opengrok.indexer.util.Executor; 38 import org.opengrok.indexer.logger.LoggerFactory; 39 40 /** 41 * Access to an RCS repository. 42 */ 43 public class RCSRepository extends Repository { 44 45 private static final Logger LOGGER = LoggerFactory.getLogger(RCSRepository.class); 46 47 private static final long serialVersionUID = 1L; 48 49 /** 50 * This property name is used to obtain the command to get annotation for this repository. 51 */ 52 private static final String CMD_BLAME_PROPERTY_KEY 53 = "org.opengrok.indexer.history.RCS.blame"; 54 /** 55 * The command to use to get annotation if none was given explicitly. 56 */ 57 private static final String CMD_BLAME_FALLBACK = "blame"; 58 RCSRepository()59 public RCSRepository() { 60 working = Boolean.TRUE; 61 type = "RCS"; 62 63 ignoredDirs.add("RCS"); 64 } 65 66 @Override fileHasHistory(File file)67 boolean fileHasHistory(File file) { 68 return getRCSFile(file) != null; 69 } 70 71 @Override getHistoryGet(OutputStream out, String parent, String basename, String rev)72 boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) { 73 try { 74 File file = new File(parent, basename); 75 File rcsFile = getRCSFile(file); 76 try (InputStream in = new RCSget(rcsFile.getPath(), rev)) { 77 copyBytes(out::write, in); 78 } 79 return true; 80 } catch (IOException ioe) { 81 LOGGER.log(Level.SEVERE, 82 "Failed to retrieve revision " + rev + " of " + basename, ioe); 83 return false; 84 } 85 } 86 87 @Override fileHasAnnotation(File file)88 boolean fileHasAnnotation(File file) { 89 return fileHasHistory(file); 90 } 91 92 @Override annotate(File file, String revision)93 Annotation annotate(File file, String revision) throws IOException { 94 List<String> argv = new ArrayList<>(); 95 ensureCommand(CMD_BLAME_PROPERTY_KEY, CMD_BLAME_FALLBACK); 96 97 argv.add(RepoCommand); 98 if (revision != null) { 99 argv.add("-r"); 100 argv.add(revision); 101 } 102 argv.add(file.getName()); 103 104 Executor executor = new Executor(argv, file.getParentFile(), 105 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 106 107 RCSAnnotationParser annotator = new RCSAnnotationParser(file); 108 executor.exec(true, annotator); 109 110 return annotator.getAnnotation(); 111 } 112 113 /** 114 * Wrap a {@code Throwable} in an {@code IOException} and return it. 115 */ wrapInIOException(String message, Throwable t)116 static IOException wrapInIOException(String message, Throwable t) { 117 return new IOException(message + ": " + t.getMessage(), t); 118 } 119 120 @Override isRepositoryFor(File file, CommandTimeoutType cmdType)121 boolean isRepositoryFor(File file, CommandTimeoutType cmdType) { 122 File rcsDir = new File(file, "RCS"); 123 if (!rcsDir.isDirectory()) { 124 return false; 125 } 126 127 // If there is at least one entry with the ',v' suffix, 128 // consider this a RCS repository. 129 String[] list = rcsDir.list((dir, name) -> { 130 // Technically we should check whether the entry is a file 131 // however this would incur additional I/O. The pattern 132 // should be enough. 133 return name.matches(".*,v"); 134 }); 135 136 return (list.length > 0); 137 } 138 139 /** 140 * Get a {@code File} object that points to the file that contains 141 * RCS history for the specified file. 142 * 143 * @param file the file whose corresponding RCS file should be found 144 * @return the file which contains the RCS history, or {@code null} if it 145 * cannot be found 146 */ getRCSFile(File file)147 File getRCSFile(File file) { 148 File dir = new File(file.getParentFile(), "RCS"); 149 String baseName = file.getName(); 150 File rcsFile = new File(dir, baseName + ",v"); 151 return rcsFile.exists() ? rcsFile : null; 152 } 153 154 @Override hasHistoryForDirectories()155 boolean hasHistoryForDirectories() { 156 return false; 157 } 158 159 @Override getHistory(File file)160 History getHistory(File file) throws HistoryException { 161 return new RCSHistoryParser().parse(file, this); 162 } 163 164 @Override determineParent(CommandTimeoutType cmdType)165 String determineParent(CommandTimeoutType cmdType) throws IOException { 166 return null; 167 } 168 169 @Override determineBranch(CommandTimeoutType cmdType)170 String determineBranch(CommandTimeoutType cmdType) throws IOException { 171 return null; 172 } 173 174 @Override determineCurrentVersion(CommandTimeoutType cmdType)175 String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException { 176 return null; 177 } 178 } 179