xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/RCSRepository.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) 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.InputStream;
29 import java.io.OutputStream;
30 import java.util.ArrayList;
31 import java.util.List;
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.util.Executor;
38 import org.opengrok.indexer.logger.LoggerFactory;
39 
40 /**
41  * Access to an RCS repository.
42  */
43 public class RCSRepository extends Repository {
44 
45     private static final Logger LOGGER = LoggerFactory.getLogger(RCSRepository.class);
46 
47     private static final long serialVersionUID = 1L;
48 
49     /**
50      * This property name is used to obtain the command to get annotation for this repository.
51      */
52     private static final String CMD_BLAME_PROPERTY_KEY
53             = "org.opengrok.indexer.history.RCS.blame";
54     /**
55      * The command to use to get annotation if none was given explicitly.
56      */
57     private static final String CMD_BLAME_FALLBACK = "blame";
58 
RCSRepository()59     public RCSRepository() {
60         working = Boolean.TRUE;
61         type = "RCS";
62 
63         ignoredDirs.add("RCS");
64     }
65 
66     @Override
fileHasHistory(File file)67     boolean fileHasHistory(File file) {
68         return getRCSFile(file) != null;
69     }
70 
71     @Override
getHistoryGet(OutputStream out, String parent, String basename, String rev)72     boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) {
73         try {
74             File file = new File(parent, basename);
75             File rcsFile = getRCSFile(file);
76             try (InputStream in = new RCSget(rcsFile.getPath(), rev)) {
77                 copyBytes(out::write, in);
78             }
79             return true;
80         } catch (IOException ioe) {
81             LOGGER.log(Level.SEVERE,
82                     "Failed to retrieve revision " + rev + " of " + basename, ioe);
83             return false;
84         }
85     }
86 
87     @Override
fileHasAnnotation(File file)88     boolean fileHasAnnotation(File file) {
89         return fileHasHistory(file);
90     }
91 
92     @Override
annotate(File file, String revision)93     Annotation annotate(File file, String revision) throws IOException {
94         List<String> argv = new ArrayList<>();
95         ensureCommand(CMD_BLAME_PROPERTY_KEY, CMD_BLAME_FALLBACK);
96 
97         argv.add(RepoCommand);
98         if (revision != null) {
99             argv.add("-r");
100             argv.add(revision);
101         }
102         argv.add(file.getName());
103 
104         Executor executor = new Executor(argv, file.getParentFile(),
105                 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
106 
107         RCSAnnotationParser annotator = new RCSAnnotationParser(file);
108         executor.exec(true, annotator);
109 
110         return annotator.getAnnotation();
111     }
112 
113     /**
114      * Wrap a {@code Throwable} in an {@code IOException} and return it.
115      */
wrapInIOException(String message, Throwable t)116     static IOException wrapInIOException(String message, Throwable t) {
117         return new IOException(message + ": " + t.getMessage(), t);
118     }
119 
120     @Override
isRepositoryFor(File file, CommandTimeoutType cmdType)121     boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
122         File rcsDir = new File(file, "RCS");
123         if (!rcsDir.isDirectory()) {
124             return false;
125         }
126 
127         // If there is at least one entry with the ',v' suffix,
128         // consider this a RCS repository.
129         String[] list = rcsDir.list((dir, name) -> {
130             // Technically we should check whether the entry is a file
131             // however this would incur additional I/O. The pattern
132             // should be enough.
133             return name.matches(".*,v");
134         });
135 
136         return (list.length > 0);
137     }
138 
139     /**
140      * Get a {@code File} object that points to the file that contains
141      * RCS history for the specified file.
142      *
143      * @param file the file whose corresponding RCS file should be found
144      * @return the file which contains the RCS history, or {@code null} if it
145      * cannot be found
146      */
getRCSFile(File file)147     File getRCSFile(File file) {
148         File dir = new File(file.getParentFile(), "RCS");
149         String baseName = file.getName();
150         File rcsFile = new File(dir, baseName + ",v");
151         return rcsFile.exists() ? rcsFile : null;
152     }
153 
154     @Override
hasHistoryForDirectories()155     boolean hasHistoryForDirectories() {
156         return false;
157     }
158 
159     @Override
getHistory(File file)160     History getHistory(File file) throws HistoryException {
161         return new RCSHistoryParser().parse(file, this);
162     }
163 
164     @Override
determineParent(CommandTimeoutType cmdType)165     String determineParent(CommandTimeoutType cmdType) throws IOException {
166         return null;
167     }
168 
169     @Override
determineBranch(CommandTimeoutType cmdType)170     String determineBranch(CommandTimeoutType cmdType) throws IOException {
171         return null;
172     }
173 
174     @Override
determineCurrentVersion(CommandTimeoutType cmdType)175     String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException {
176         return null;
177     }
178 }
179