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) 2009, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, 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.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 36 import org.opengrok.indexer.configuration.CommandTimeoutType; 37 import org.opengrok.indexer.configuration.RuntimeEnvironment; 38 import org.opengrok.indexer.logger.LoggerFactory; 39 import org.opengrok.indexer.util.Executor; 40 41 /** 42 * Access to a Monotone repository. 43 * 44 * @author Trond Norbye 45 */ 46 public class MonotoneRepository extends Repository { 47 48 private static final Logger LOGGER = LoggerFactory.getLogger(MonotoneRepository.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.Monotone"; 55 /** 56 * The command to use to access the repository if none was given explicitly. 57 */ 58 public static final String CMD_FALLBACK = "mnt"; 59 MonotoneRepository()60 public MonotoneRepository() { 61 type = "Monotone"; 62 datePatterns = new String[]{ 63 "yyyy-MM-dd'T'hh:mm:ss" 64 }; 65 } 66 67 @Override getHistoryGet(OutputStream out, String parent, String basename, String rev)68 boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) { 69 70 File directory = new File(getDirectoryName()); 71 try { 72 String filename = (new File(parent, basename)).getCanonicalPath() 73 .substring(getDirectoryName().length() + 1); 74 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 75 String[] argv = {RepoCommand, "cat", "-r", rev, filename}; 76 Executor executor = new Executor(Arrays.asList(argv), directory, 77 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 78 copyBytes(out::write, executor.getOutputStream()); 79 return true; 80 } catch (Exception exp) { 81 LOGGER.log(Level.SEVERE, 82 "Failed to get history: {0}", exp.getClass().toString()); 83 } 84 85 return false; 86 } 87 88 /** 89 * Get an executor to be used for retrieving the history log for the named 90 * file or directory. 91 * 92 * @param file The file or directory to retrieve history for 93 * @param sinceRevision the oldest changeset to return from the executor, or 94 * {@code null} if all changesets should be returned 95 * @return An Executor ready to be started 96 */ getHistoryLogExecutor(File file, String sinceRevision)97 Executor getHistoryLogExecutor(File file, String sinceRevision) 98 throws IOException { 99 String filename = getRepoRelativePath(file); 100 101 List<String> cmd = new ArrayList<>(); 102 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 103 cmd.add(RepoCommand); 104 cmd.add("log"); 105 106 if (sinceRevision != null) { 107 cmd.add("--to"); 108 cmd.add(sinceRevision); 109 } 110 111 cmd.add("--no-graph"); 112 cmd.add("--no-merges"); 113 cmd.add("--no-format-dates"); 114 cmd.add(filename); 115 116 return new Executor(cmd, new File(getDirectoryName()), sinceRevision != null); 117 } 118 119 /** 120 * Annotate the specified file/revision using the {@code mnt annotate} command. 121 * 122 * @param file file to annotate 123 * @param revision revision to annotate 124 * @return file annotation 125 * @throws java.io.IOException if I/O exception occurred or the command failed 126 */ 127 @Override annotate(File file, String revision)128 public Annotation annotate(File file, String revision) throws IOException { 129 ArrayList<String> cmd = new ArrayList<>(); 130 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 131 cmd.add(RepoCommand); 132 cmd.add("annotate"); 133 cmd.add(getQuietOption()); 134 if (revision != null) { 135 cmd.add("-r"); 136 cmd.add(revision); 137 } 138 cmd.add(file.getName()); 139 File directory = new File(getDirectoryName()); 140 141 Executor executor = new Executor(cmd, directory, 142 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 143 MonotoneAnnotationParser parser = new MonotoneAnnotationParser(file); 144 int status = executor.exec(true, parser); 145 if (status != 0) { 146 LOGGER.log(Level.WARNING, 147 "Failed to get annotations for: \"{0}\" Exit code: {1}", 148 new Object[]{file.getAbsolutePath(), String.valueOf(status)}); 149 throw new IOException(executor.getErrorString()); 150 } else { 151 return parser.getAnnotation(); 152 } 153 } 154 155 @Override fileHasAnnotation(File file)156 public boolean fileHasAnnotation(File file) { 157 return true; 158 } 159 160 @Override fileHasHistory(File file)161 public boolean fileHasHistory(File file) { 162 return true; 163 } 164 165 @Override isRepositoryFor(File file, CommandTimeoutType cmdType)166 boolean isRepositoryFor(File file, CommandTimeoutType cmdType) { 167 File f = new File(file, "_MTN"); 168 return f.exists() && f.isDirectory(); 169 } 170 171 @Override isWorking()172 public boolean isWorking() { 173 if (working == null) { 174 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 175 working = checkCmd(RepoCommand, "--help"); 176 } 177 return working; 178 } 179 180 @Override hasHistoryForDirectories()181 boolean hasHistoryForDirectories() { 182 return true; 183 } 184 185 @Override getHistory(File file)186 History getHistory(File file) throws HistoryException { 187 return getHistory(file, null); 188 } 189 190 @Override getHistory(File file, String sinceRevision)191 History getHistory(File file, String sinceRevision) 192 throws HistoryException { 193 return new MonotoneHistoryParser(this).parse(file, sinceRevision); 194 } 195 getQuietOption()196 private String getQuietOption() { 197 if (useDeprecated()) { 198 return "--reallyquiet"; 199 } else { 200 return "--quiet --quiet"; 201 } 202 } 203 204 public static final String DEPRECATED_KEY 205 = "org.opengrok.indexer.history.monotone.deprecated"; 206 useDeprecated()207 private boolean useDeprecated() { 208 return Boolean.parseBoolean(System.getProperty(DEPRECATED_KEY, "false")); 209 } 210 211 @Override determineParent(CommandTimeoutType cmdType)212 String determineParent(CommandTimeoutType cmdType) throws IOException { 213 String parent = null; 214 File directory = new File(getDirectoryName()); 215 216 List<String> cmd = new ArrayList<>(); 217 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 218 cmd.add(RepoCommand); 219 cmd.add("ls"); 220 cmd.add("vars"); 221 cmd.add("database"); 222 Executor executor = new Executor(cmd, directory, 223 RuntimeEnvironment.getInstance().getCommandTimeout(cmdType)); 224 executor.exec(); 225 226 try (BufferedReader in = new BufferedReader(executor.getOutputReader())) { 227 String line; 228 while ((line = in.readLine()) != null) { 229 if (line.startsWith("database") && line.contains("default-server")) { 230 String[] parts = line.split("\\s+"); 231 if (parts.length != 3) { 232 LOGGER.log(Level.WARNING, 233 "Failed to get parent for {0}", getDirectoryName()); 234 } 235 parent = parts[2]; 236 break; 237 } 238 } 239 } 240 241 return parent; 242 } 243 244 @Override determineBranch(CommandTimeoutType cmdType)245 String determineBranch(CommandTimeoutType cmdType) { 246 return null; 247 } 248 249 @Override determineCurrentVersion(CommandTimeoutType cmdType)250 String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException { 251 return null; 252 } 253 } 254