xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/BazaarRepository.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
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