xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/CVSRepository.java (revision 52ef4877dde7e326cfa2f9465f2bbcd67dc483f8)
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.BufferedReader;
27 import java.io.File;
28 import java.io.FileReader;
29 import java.io.IOException;
30 import java.io.InputStream;
31 import java.io.OutputStream;
32 import java.util.ArrayList;
33 import java.util.Arrays;
34 import java.util.List;
35 import java.util.logging.Level;
36 import java.util.logging.Logger;
37 
38 import org.jetbrains.annotations.Nullable;
39 import org.opengrok.indexer.configuration.CommandTimeoutType;
40 import org.opengrok.indexer.configuration.RuntimeEnvironment;
41 import org.opengrok.indexer.logger.LoggerFactory;
42 import org.opengrok.indexer.util.Executor;
43 
44 /**
45  * Access to a CVS repository.
46  */
47 public class CVSRepository extends RCSRepository {
48 
49     private static final Logger LOGGER = LoggerFactory.getLogger(CVSRepository.class);
50 
51     private static final long serialVersionUID = 1L;
52     /**
53      * The property name used to obtain the client command for repository.
54      */
55     public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.cvs";
56     /**
57      * The command to use to access the repository if none was given explicitly.
58      */
59     public static final String CMD_FALLBACK = "cvs";
60 
CVSRepository()61     public CVSRepository() {
62         /*
63          * This variable is set in the ancestor to TRUE which has a side effect
64          * that this repository is always marked as working even though it does
65          * not have the binary available on the system.
66          *
67          * Setting this to null restores the default behavior (as java
68          * default for reference is null) for this repository - detecting the
69          * binary and act upon that.
70          *
71          * @see #isWorking
72          */
73         working = null;
74         setType("CVS");
75         datePatterns = new String[]{
76             "yyyy-MM-dd hh:mm:ss"
77         };
78 
79         ignoredFiles.add(".cvsignore");
80         ignoredDirs.add("CVS");
81         ignoredDirs.add("CVSROOT");
82     }
83 
84     @Override
isWorking()85     public boolean isWorking() {
86         if (working == null) {
87             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
88             working = checkCmd(RepoCommand, "--version");
89         }
90         return working;
91     }
92 
93     @Override
setDirectoryName(File directory)94     public void setDirectoryName(File directory) {
95         super.setDirectoryName(directory);
96 
97         if (isWorking()) {
98             File rootFile = new File(getDirectoryName() + File.separatorChar
99                     + "CVS" + File.separatorChar + "Root");
100             String root;
101 
102             if (!rootFile.exists()) {
103                 LOGGER.log(Level.FINE, "CVS Root file {0} does not exist", rootFile);
104                 return;
105             }
106 
107             try (BufferedReader input = new BufferedReader(new FileReader(rootFile))) {
108                 root = input.readLine();
109             } catch (IOException e) {
110                 LOGGER.log(Level.WARNING, String.format("failed to read %s", rootFile), e);
111                 return;
112             }
113 
114             if (!root.startsWith("/")) {
115                 setRemote(true);
116             }
117         }
118     }
119 
120     @Override
getRCSFile(File file)121     File getRCSFile(File file) {
122         File cvsFile
123                 = RCSHistoryParser.getCVSFile(file.getParent(), file.getName());
124         if (cvsFile != null && cvsFile.exists()) {
125             return cvsFile;
126         }
127         return null;
128     }
129 
130     @Override
isRepositoryFor(File file, CommandTimeoutType cmdType)131     public boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
132         if (file.isDirectory()) {
133             File cvsDir = new File(file, "CVS");
134             return cvsDir.isDirectory();
135         }
136         return false;
137     }
138 
139     @Override
determineBranch(CommandTimeoutType cmdType)140     String determineBranch(CommandTimeoutType cmdType) throws IOException {
141         String branch = null;
142 
143         File tagFile = new File(getDirectoryName(), "CVS/Tag");
144         if (tagFile.isFile()) {
145             try (BufferedReader br = new BufferedReader(new FileReader(tagFile))) {
146                 String line = br.readLine();
147                 if (line != null) {
148                     branch = line.substring(1);
149                 }
150             } catch (IOException ex) {
151                 LOGGER.log(Level.WARNING,
152                     "Failed to work with CVS/Tag file of {0}",
153                     getDirectoryName() + ": " + ex.getClass().toString());
154             } catch (Exception exp) {
155                 LOGGER.log(Level.WARNING,
156                     "Failed to get revision tag of {0}",
157                     getDirectoryName() + ": " + exp.getClass().toString());
158             }
159         }
160 
161         return branch;
162     }
163 
164     /**
165      * Get an executor to be used for retrieving the history log for the named
166      * file.
167      *
168      * @param file The file to retrieve history for
169      * @return An Executor ready to be started
170      */
getHistoryLogExecutor(final File file)171     Executor getHistoryLogExecutor(final File file) throws IOException {
172         String filename = getRepoRelativePath(file);
173 
174         List<String> cmd = new ArrayList<>();
175         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
176         cmd.add(RepoCommand);
177         cmd.add("log");
178 
179         if (getBranch() != null && !getBranch().isEmpty()) {
180             // Generate history on this branch and follow up to the origin.
181             cmd.add("-r1.1:" + branch);
182         } else {
183             // Get revisions on this branch only (otherwise the revisions
184             // list produced by the cvs log command would be unsorted).
185             cmd.add("-b");
186         }
187 
188         if (filename.length() > 0) {
189             cmd.add(filename);
190         }
191 
192         return new Executor(cmd, new File(getDirectoryName()));
193     }
194 
195     @Override
getHistoryGet(OutputStream out, String parent, String basename, String rev)196     boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) {
197         String revision = rev;
198         if (rev.indexOf(':') != -1) {
199             revision = rev.substring(0, rev.indexOf(':'));
200         }
201 
202         try {
203             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
204             String[] argv = {RepoCommand, "up", "-p", "-r", revision, basename};
205             Executor executor = new Executor(Arrays.asList(argv), new File(parent),
206                 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
207             executor.exec();
208 
209             byte[] buffer = new byte[32 * 1024];
210             try (InputStream in = executor.getOutputStream()) {
211                 int len;
212 
213                 while ((len = in.read(buffer)) != -1) {
214                     if (len > 0) {
215                         out.write(buffer, 0, len);
216                     }
217                 }
218             }
219         } catch (Exception exp) {
220             LOGGER.log(Level.WARNING, "Failed to get history", exp);
221             return false;
222         }
223 
224         return true;
225     }
226 
227     @Override
fileHasAnnotation(File file)228     public boolean fileHasAnnotation(File file) {
229         return true;
230     }
231 
232     @Override
fileHasHistory(File file)233     public boolean fileHasHistory(File file) {
234         // @TODO: Research how to cheaply test if a file in a given
235         // CVS repo has history.  If there is a cheap test, then this
236         // code can be refined, boosting performance.
237         return true;
238     }
239 
240     @Override
getHistory(File file)241     History getHistory(File file) throws HistoryException {
242         return new CVSHistoryParser().parse(file, this);
243     }
244 
245     @Override
annotate(File file, String revision)246     Annotation annotate(File file, String revision) throws IOException {
247         ArrayList<String> cmd = new ArrayList<>();
248         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
249         cmd.add(RepoCommand);
250         cmd.add("annotate");
251         if (revision != null) {
252             cmd.add("-r");
253             cmd.add(revision);
254         } else if (getBranch() != null && !getBranch().isEmpty()) {
255             cmd.add("-r");
256             cmd.add(getBranch());
257         }
258         cmd.add(file.getName());
259 
260         Executor exec = new Executor(cmd, file.getParentFile(),
261                 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
262         CVSAnnotationParser parser = new CVSAnnotationParser(file.getName());
263         int status = exec.exec(true, parser);
264 
265         if (status != 0) {
266             LOGGER.log(Level.WARNING,
267                     "Failed to get annotations for: \"{0}\" Exit code: {1}",
268                     new Object[]{file.getAbsolutePath(), String.valueOf(status)});
269         }
270 
271         return parser.getAnnotation();
272     }
273 
274     @Override
275     @Nullable
determineParent(CommandTimeoutType cmdType)276     String determineParent(CommandTimeoutType cmdType) throws IOException {
277         File rootFile = new File(getDirectoryName() + File.separator + "CVS"
278                 + File.separator + "Root");
279         String parent = null;
280 
281         if (rootFile.isFile()) {
282             try (BufferedReader br = new BufferedReader(new FileReader(rootFile))) {
283                 parent = br.readLine();
284             } catch (IOException ex) {
285                 LOGGER.log(Level.WARNING, String.format("Failed to read CVS/Root file %s", rootFile), ex);
286             }
287         }
288 
289         return parent;
290     }
291 }
292