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) 2017, 2020, Chris Fraire <cfraire@me.com>. 23 */ 24 package org.opengrok.indexer.history; 25 26 import java.io.File; 27 import java.io.FileInputStream; 28 import java.io.IOException; 29 import java.io.OutputStream; 30 import java.util.ArrayList; 31 import java.util.Arrays; 32 import java.util.List; 33 import java.util.logging.Level; 34 import java.util.logging.Logger; 35 import java.util.regex.Pattern; 36 37 import org.opengrok.indexer.configuration.CommandTimeoutType; 38 import org.opengrok.indexer.configuration.RuntimeEnvironment; 39 import org.opengrok.indexer.logger.LoggerFactory; 40 import org.opengrok.indexer.util.Executor; 41 42 /** 43 * Access to a ClearCase repository. 44 * 45 */ 46 public class ClearCaseRepository extends Repository { 47 48 private static final Logger LOGGER = LoggerFactory.getLogger(ClearCaseRepository.class); 49 50 private static final long serialVersionUID = 1L; 51 /** 52 * The property name used to obtain the client command for this repository. 53 */ 54 public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.ClearCase"; 55 /** 56 * The command to use to access the repository if none was given explicitly. 57 */ 58 public static final String CMD_FALLBACK = "cleartool"; 59 ClearCaseRepository()60 public ClearCaseRepository() { 61 type = "ClearCase"; 62 datePatterns = new String[]{ 63 "yyyyMMdd.HHmmss" 64 }; 65 } 66 67 /** 68 * Get an executor to be used for retrieving the history log for the named 69 * file. 70 * 71 * @param file The file to retrieve history for 72 * @return An Executor ready to be started 73 */ getHistoryLogExecutor(final File file)74 Executor getHistoryLogExecutor(final File file) throws IOException { 75 String filename = getRepoRelativePath(file); 76 77 List<String> cmd = new ArrayList<>(); 78 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 79 cmd.add(RepoCommand); 80 cmd.add("lshistory"); 81 if (file.isDirectory()) { 82 cmd.add("-dir"); 83 } 84 cmd.add("-fmt"); 85 cmd.add("%e\n%Nd\n%Fu (%u)\n%Vn\n%Nc\n.\n"); 86 cmd.add(filename); 87 88 return new Executor(cmd, new File(getDirectoryName())); 89 } 90 91 @Override getHistoryGet(OutputStream out, String parent, String basename, String rev)92 boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) { 93 94 File directory = new File(getDirectoryName()); 95 96 try { 97 String filename = (new File(parent, basename)).getCanonicalPath() 98 .substring(getDirectoryName().length() + 1); 99 final File tmp = File.createTempFile("opengrok", "tmp"); 100 String tmpName = tmp.getCanonicalPath(); 101 102 // cleartool can't get to a previously existing file 103 if (tmp.exists() && !tmp.delete()) { 104 LOGGER.log(Level.WARNING, 105 "Failed to remove temporary file used by history cache"); 106 } 107 108 String decorated = filename + "@@" + rev; 109 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 110 String[] argv = {RepoCommand, "get", "-to", tmpName, decorated}; 111 Executor executor = new Executor(Arrays.asList(argv), directory, 112 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 113 int status = executor.exec(); 114 if (status != 0) { 115 LOGGER.log(Level.SEVERE, "Failed to get history: {0}", 116 executor.getErrorString()); 117 return false; 118 } 119 120 try (FileInputStream in = new FileInputStream(tmp)) { 121 copyBytes(out::write, in); 122 } finally { 123 // delete the temporary file on close 124 if (!tmp.delete()) { 125 // failed, lets do the next best thing then .. 126 // delete it on JVM exit 127 tmp.deleteOnExit(); 128 } 129 } 130 return true; 131 } catch (Exception exp) { 132 LOGGER.log(Level.WARNING, 133 "Failed to get history: " + exp.getClass().toString(), exp); 134 } 135 136 return false; 137 } 138 139 /** 140 * Annotate the specified file/revision. 141 * 142 * @param file file to annotate 143 * @param revision revision to annotate 144 * @return file annotation 145 * @throws java.io.IOException if I/O exception occurred 146 */ 147 @Override annotate(File file, String revision)148 public Annotation annotate(File file, String revision) throws IOException { 149 ArrayList<String> argv = new ArrayList<>(); 150 151 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 152 argv.add(RepoCommand); 153 argv.add("annotate"); 154 argv.add("-nheader"); 155 argv.add("-out"); 156 argv.add("-"); 157 argv.add("-f"); 158 argv.add("-fmt"); 159 argv.add("%u|%Vn|"); 160 161 if (revision != null) { 162 argv.add(revision); 163 } 164 argv.add(file.getName()); 165 166 Executor executor = new Executor(argv, file.getParentFile(), 167 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 168 ClearCaseAnnotationParser parser = new ClearCaseAnnotationParser(file.getName()); 169 executor.exec(true, parser); 170 171 return parser.getAnnotation(); 172 } 173 174 @Override fileHasAnnotation(File file)175 public boolean fileHasAnnotation(File file) { 176 return true; 177 } 178 179 @Override fileHasHistory(File file)180 public boolean fileHasHistory(File file) { 181 // Todo: is there a cheap test for whether ClearCase has history 182 // available for a file? 183 // Otherwise, this is harmless, since ClearCase's commands will just 184 // print nothing if there is no history. 185 return true; 186 } 187 188 @Override isWorking()189 public boolean isWorking() { 190 if (working == null) { 191 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 192 working = checkCmd(RepoCommand, "-version"); 193 } 194 return working; 195 } 196 197 @Override isRepositoryFor(File file, CommandTimeoutType cmdType)198 boolean isRepositoryFor(File file, CommandTimeoutType cmdType) { 199 // if the parent contains a file named "[.]view.dat" or 200 // the parent is named "vobs" or the canonical path 201 // is found in "cleartool lsvob -s" 202 File fWindows = new File(file, "view.dat"); 203 File fUnix = new File(file, ".view.dat"); 204 if (fWindows.exists() || fUnix.exists()) { 205 return true; 206 } else if (file.isDirectory() && file.getName().equalsIgnoreCase("vobs")) { 207 return true; 208 } else if (isWorking()) { 209 try { 210 String canonicalPath = file.getCanonicalPath(); 211 for (String vob : getAllVobs()) { 212 if (canonicalPath.equalsIgnoreCase(vob)) { 213 return true; 214 } 215 } 216 } catch (IOException e) { 217 LOGGER.log(Level.WARNING, 218 "Could not get canonical path for \"" + file + "\"", e); 219 } 220 } 221 return false; 222 } 223 224 @Override determineCurrentVersion(CommandTimeoutType cmdType)225 String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException { 226 return null; 227 } 228 229 private static class VobsHolder { 230 static String[] vobs = runLsvob(); 231 } 232 getAllVobs()233 private static String[] getAllVobs() { 234 return VobsHolder.vobs; 235 } 236 237 private static final ClearCaseRepository testRepo 238 = new ClearCaseRepository(); 239 runLsvob()240 private static String[] runLsvob() { 241 if (testRepo.isWorking()) { 242 Executor exec = new Executor( 243 new String[]{testRepo.RepoCommand, "lsvob", "-s"}); 244 int rc; 245 if ((rc = exec.exec(true)) == 0) { 246 String output = exec.getOutputString(); 247 248 if (output == null) { 249 LOGGER.log(Level.SEVERE, 250 "\"cleartool lsvob -s\" output was null"); 251 return new String[0]; 252 } 253 String sep = System.getProperty("line.separator"); 254 String[] vobs = output.split(Pattern.quote(sep)); 255 LOGGER.log(Level.CONFIG, "Found VOBs: {0}", 256 Arrays.asList(vobs)); 257 return vobs; 258 } 259 LOGGER.log(Level.SEVERE, 260 "\"cleartool lsvob -s\" returned non-zero status: {0}", rc); 261 } 262 return new String[0]; 263 } 264 265 @Override hasHistoryForDirectories()266 boolean hasHistoryForDirectories() { 267 return true; 268 } 269 270 @Override getHistory(File file)271 History getHistory(File file) throws HistoryException { 272 return new ClearCaseHistoryParser().parse(file, this); 273 } 274 275 @Override determineParent(CommandTimeoutType cmdType)276 String determineParent(CommandTimeoutType cmdType) throws IOException { 277 return null; 278 } 279 280 @Override determineBranch(CommandTimeoutType cmdType)281 String determineBranch(CommandTimeoutType cmdType) { 282 return null; 283 } 284 } 285