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, 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.OutputStream; 29 import java.util.ArrayList; 30 import java.util.List; 31 import java.util.TreeSet; 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.logger.LoggerFactory; 38 import org.opengrok.indexer.util.Executor; 39 40 /** 41 * Access to a Bazaar repository. 42 */ 43 public class BazaarRepository extends Repository { 44 45 private static final Logger LOGGER = LoggerFactory.getLogger(BazaarRepository.class); 46 47 private static final long serialVersionUID = 1L; 48 /** 49 * The property name used to obtain the client command for this repository. 50 */ 51 public static final String CMD_PROPERTY_KEY 52 = "org.opengrok.indexer.history.Bazaar"; 53 /** 54 * The command to use to access the repository if none was given explicitly. 55 */ 56 public static final String CMD_FALLBACK = "bzr"; 57 BazaarRepository()58 public BazaarRepository() { 59 type = "Bazaar"; 60 datePatterns = new String[]{ 61 "EEE yyyy-MM-dd hh:mm:ss ZZZZ" 62 }; 63 64 ignoredDirs.add(".bzr"); 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 * @param sinceRevision the oldest changeset to return from the executor, or 73 * {@code null} if all changesets should be returned 74 * @return An Executor ready to be started 75 */ getHistoryLogExecutor(final File file, final String sinceRevision)76 Executor getHistoryLogExecutor(final File file, final String sinceRevision) 77 throws IOException { 78 String filename = getRepoRelativePath(file); 79 80 List<String> cmd = new ArrayList<>(); 81 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 82 cmd.add(RepoCommand); 83 cmd.add("log"); 84 85 if (file.isDirectory()) { 86 cmd.add("-v"); 87 } 88 cmd.add(filename); 89 90 if (sinceRevision != null) { 91 cmd.add("-r"); 92 cmd.add(sinceRevision + "..-1"); 93 } 94 95 return new Executor(cmd, new File(getDirectoryName()), sinceRevision != null); 96 } 97 98 @Override getHistoryGet(OutputStream out, String parent, String basename, String rev)99 boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) { 100 101 File directory = new File(getDirectoryName()); 102 Process process = null; 103 try { 104 String filename = (new File(parent, basename)).getCanonicalPath() 105 .substring(getDirectoryName().length() + 1); 106 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 107 String[] argv = {RepoCommand, "cat", "-r", rev, filename}; 108 process = Runtime.getRuntime().exec(argv, null, directory); 109 copyBytes(out::write, process.getInputStream()); 110 return true; 111 } catch (Exception exp) { 112 LOGGER.log(Level.SEVERE, 113 "Failed to get history: " + exp.getClass().toString(), exp); 114 } finally { 115 // Clean up zombie-processes... 116 if (process != null) { 117 try { 118 process.exitValue(); 119 } catch (IllegalThreadStateException exp) { 120 // the process is still running??? just kill it.. 121 process.destroy(); 122 } 123 } 124 } 125 126 return false; 127 } 128 129 /** 130 * Annotate the specified file/revision. 131 * 132 * @param file file to annotate 133 * @param revision revision to annotate 134 * @return file annotation 135 * @throws java.io.IOException if I/O exception occurred 136 */ 137 @Override annotate(File file, String revision)138 public Annotation annotate(File file, String revision) throws IOException { 139 List<String> cmd = new ArrayList<>(); 140 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 141 cmd.add(RepoCommand); 142 cmd.add("blame"); 143 cmd.add("--all"); 144 cmd.add("--long"); 145 if (revision != null) { 146 cmd.add("-r"); 147 cmd.add(revision); 148 } 149 cmd.add(file.getName()); 150 151 Executor executor = new Executor(cmd, file.getParentFile(), 152 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout()); 153 BazaarAnnotationParser parser = new BazaarAnnotationParser(file.getName()); 154 int status = executor.exec(true, parser); 155 156 if (status != 0) { 157 LOGGER.log(Level.WARNING, 158 "Failed to get annotations for: \"{0}\" Exit code: {1}", 159 new Object[]{file.getAbsolutePath(), String.valueOf(status)}); 160 throw new IOException(executor.getErrorString()); 161 } else { 162 return parser.getAnnotation(); 163 } 164 } 165 166 @Override fileHasAnnotation(File file)167 public boolean fileHasAnnotation(File file) { 168 return true; 169 } 170 171 @Override fileHasHistory(File file)172 public boolean fileHasHistory(File file) { 173 // Todo: is there a cheap test for whether Bazaar has history 174 // available for a file? 175 // Otherwise, this is harmless, since Bazaar's commands will just 176 // print nothing if there is no history. 177 return true; 178 } 179 180 @Override isRepositoryFor(File file, CommandTimeoutType cmdType)181 boolean isRepositoryFor(File file, CommandTimeoutType cmdType) { 182 if (file.isDirectory()) { 183 File f = new File(file, ".bzr"); 184 return f.exists() && f.isDirectory(); 185 } 186 return false; 187 } 188 189 @Override isWorking()190 public boolean isWorking() { 191 if (working == null) { 192 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 193 working = checkCmd(RepoCommand, "--help"); 194 } 195 return working; 196 } 197 198 @Override hasHistoryForDirectories()199 boolean hasHistoryForDirectories() { 200 return true; 201 } 202 203 @Override getHistory(File file, String sinceRevision)204 History getHistory(File file, String sinceRevision) throws HistoryException { 205 RuntimeEnvironment env = RuntimeEnvironment.getInstance(); 206 History result = new BazaarHistoryParser(this).parse(file, sinceRevision); 207 // Assign tags to changesets they represent 208 // We don't need to check if this repository supports tags, because we know it:-) 209 if (env.isTagsEnabled()) { 210 assignTagsInHistory(result); 211 } 212 return result; 213 } 214 215 @Override getHistory(File file)216 History getHistory(File file) throws HistoryException { 217 return getHistory(file, null); 218 } 219 220 @Override hasFileBasedTags()221 boolean hasFileBasedTags() { 222 return true; 223 } 224 225 /** 226 * @param directory Directory where we list tags 227 */ 228 @Override buildTagList(File directory, CommandTimeoutType cmdType)229 protected void buildTagList(File directory, CommandTimeoutType cmdType) { 230 this.tagList = new TreeSet<>(); 231 ArrayList<String> argv = new ArrayList<>(); 232 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 233 argv.add(RepoCommand); 234 argv.add("tags"); 235 236 Executor executor = new Executor(argv, directory, 237 RuntimeEnvironment.getInstance().getCommandTimeout(cmdType)); 238 final BazaarTagParser parser = new BazaarTagParser(); 239 int status = executor.exec(true, parser); 240 if (status != 0) { 241 LOGGER.log(Level.WARNING, 242 "Failed to get tags for: \"{0}\" Exit code: {1}", 243 new Object[]{directory.getAbsolutePath(), String.valueOf(status)}); 244 } else { 245 tagList = parser.getEntries(); 246 } 247 } 248 249 @Override determineParent(CommandTimeoutType cmdType)250 String determineParent(CommandTimeoutType cmdType) throws IOException { 251 File directory = new File(getDirectoryName()); 252 253 List<String> cmd = new ArrayList<>(); 254 ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK); 255 cmd.add(RepoCommand); 256 cmd.add("config"); 257 cmd.add("parent_location"); 258 Executor executor = new Executor(cmd, directory, 259 RuntimeEnvironment.getInstance().getCommandTimeout(cmdType)); 260 if (executor.exec(false) != 0) { 261 throw new IOException(executor.getErrorString()); 262 } 263 264 return executor.getOutputString().trim(); 265 } 266 267 @Override determineBranch(CommandTimeoutType cmdType)268 String determineBranch(CommandTimeoutType cmdType) { 269 return null; 270 } 271 272 @Override determineCurrentVersion(CommandTimeoutType cmdType)273 String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException { 274 return null; 275 } 276 } 277