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, 2022, 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.BufferedReader; 27 import java.io.File; 28 import java.io.FileNotFoundException; 29 import java.io.FileReader; 30 import java.io.IOException; 31 import java.io.InputStream; 32 import java.io.OutputStream; 33 import java.nio.file.Paths; 34 import java.util.ArrayList; 35 import java.util.Map; 36 import java.util.logging.Level; 37 import java.util.logging.Logger; 38 39 import org.jetbrains.annotations.VisibleForTesting; 40 import org.opengrok.indexer.configuration.CommandTimeoutType; 41 import org.opengrok.indexer.configuration.RuntimeEnvironment; 42 import org.opengrok.indexer.logger.LoggerFactory; 43 import org.opengrok.indexer.util.Executor; 44 45 /** 46 * This class gives access to repositories built on top of SCCS (including TeamWare). 47 */ 48 public class SCCSRepository extends Repository { 49 50 private static final Logger LOGGER = LoggerFactory.getLogger(SCCSRepository.class); 51 52 private static final long serialVersionUID = 1L; 53 /** 54 * The property name used to obtain the client command for this repository. 55 */ 56 public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.SCCS"; 57 /** 58 * The command to use to access the repository if none was given explicitly. 59 */ 60 public static final String CMD_FALLBACK = "sccs"; 61 62 @VisibleForTesting 63 static final String CODEMGR_WSDATA = "Codemgr_wsdata"; 64 SCCSRepository()65 public SCCSRepository() { 66 type = "SCCS"; 67 /* 68 * Originally there was only the "yy/MM/dd" pattern however the newer SCCS implementations seem 69 * to use the time as well. Some historical SCCS versions might still use that, so it is left there 70 * for potential compatibility. 71 */ 72 datePatterns = new String[]{ 73 "yy/MM/dd HH:mm:ss", 74 "yy/MM/dd" 75 }; 76 77 ignoredDirs.add("SCCS"); 78 } 79 80 @Override getHistoryGet(OutputStream out, String parent, String basename, String rev)81 boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) { 82 try { 83 File history = SCCSHistoryParser.getSCCSFile(parent, basename); 84 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 85 try (InputStream in = SCCSget.getRevision(RepoCommand, history, rev)) { 86 copyBytes(out::write, in); 87 } 88 return true; 89 } catch (FileNotFoundException ex) { 90 // continue below 91 } catch (IOException ex) { 92 LOGGER.log(Level.WARNING, "An error occurred while getting revision", ex); 93 } 94 return false; 95 } 96 getAuthors(File file)97 private Map<String, String> getAuthors(File file) throws IOException { 98 ArrayList<String> argv = new ArrayList<>(); 99 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 100 argv.add(RepoCommand); 101 argv.add("prs"); 102 argv.add("-e"); 103 argv.add("-d:I: :P:"); 104 argv.add(file.getCanonicalPath()); 105 106 Executor executor = new Executor(argv, file.getCanonicalFile().getParentFile(), 107 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 108 SCCSRepositoryAuthorParser parser = new SCCSRepositoryAuthorParser(); 109 executor.exec(true, parser); 110 111 return parser.getAuthors(); 112 } 113 114 /** 115 * Annotate the specified file/revision. 116 * 117 * @param file file to annotate 118 * @param revision revision to annotate 119 * @return file annotation 120 * @throws java.io.IOException if I/O exception occurs 121 */ 122 @Override annotate(File file, String revision)123 public Annotation annotate(File file, String revision) throws IOException { 124 Map<String, String> authors = getAuthors(file); 125 126 ArrayList<String> argv = new ArrayList<>(); 127 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 128 argv.add(RepoCommand); 129 argv.add("get"); 130 argv.add("-m"); 131 argv.add("-p"); 132 if (revision != null) { 133 argv.add("-r" + revision); 134 } 135 argv.add(file.getCanonicalPath()); 136 Executor executor = new Executor(argv, file.getCanonicalFile().getParentFile(), 137 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 138 SCCSRepositoryAnnotationParser parser = new SCCSRepositoryAnnotationParser(file, authors); 139 executor.exec(true, parser); 140 return parser.getAnnotation(); 141 } 142 143 @Override fileHasAnnotation(File file)144 public boolean fileHasAnnotation(File file) { 145 return true; 146 } 147 148 @Override fileHasHistory(File file)149 public boolean fileHasHistory(File file) { 150 String parentFile = file.getParent(); 151 String name = file.getName(); 152 File f = SCCSHistoryParser.getSCCSFile(parentFile, name); 153 if (f == null) { 154 return false; 155 } 156 return f.exists(); 157 } 158 159 @Override isRepositoryFor(File file, CommandTimeoutType cmdType)160 boolean isRepositoryFor(File file, CommandTimeoutType cmdType) { 161 if (file.isDirectory()) { 162 File f = new File(file, CODEMGR_WSDATA.toLowerCase()); // OK no ROOT 163 if (f.isDirectory()) { 164 return true; 165 } 166 f = new File(file, CODEMGR_WSDATA); 167 if (f.isDirectory()) { 168 return true; 169 } 170 return new File(file, SCCSHistoryParser.SCCS_DIR_NAME).isDirectory(); 171 } 172 return false; 173 } 174 175 @Override isWorking()176 public boolean isWorking() { 177 if (working == null) { 178 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 179 working = checkCmd(RepoCommand, "help", "help"); 180 if (!working) { 181 working = checkCmd(RepoCommand, "--version"); 182 } 183 } 184 return working; 185 } 186 187 @Override hasHistoryForDirectories()188 boolean hasHistoryForDirectories() { 189 return false; 190 } 191 192 @Override getHistory(File file)193 History getHistory(File file) throws HistoryException { 194 return new SCCSHistoryParser(this).parse(file); 195 } 196 197 @Override determineParent(CommandTimeoutType cmdType)198 String determineParent(CommandTimeoutType cmdType) throws IOException { 199 File parentFile = Paths.get(getDirectoryName(), CODEMGR_WSDATA, "parent").toFile(); 200 String parent = null; 201 202 if (parentFile.isFile()) { 203 String line; 204 try (BufferedReader in = new BufferedReader(new FileReader(parentFile))) { 205 if ((line = in.readLine()) == null) { 206 LOGGER.log(Level.WARNING, 207 "Failed to get parent for {0} (cannot read first line of {1})", 208 new Object[]{getDirectoryName(), parentFile.getName()}); 209 return null; 210 } 211 if (!line.startsWith("VERSION")) { 212 LOGGER.log(Level.WARNING, 213 "Failed to get parent for {0} (first line does not start with VERSION)", 214 getDirectoryName()); 215 } 216 if ((parent = in.readLine()) == null) { 217 LOGGER.log(Level.WARNING, 218 "Failed to get parent for {0} (cannot read second line of {1})", 219 new Object[]{getDirectoryName(), parentFile.getName()}); 220 } 221 } 222 } 223 224 return parent; 225 } 226 227 @Override determineBranch(CommandTimeoutType cmdType)228 String determineBranch(CommandTimeoutType cmdType) { 229 return null; 230 } 231 232 @Override determineCurrentVersion(CommandTimeoutType cmdType)233 String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException { 234 return null; 235 } 236 } 237