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