xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/SCCSRepository.java (revision 0d465416a59151755658364a508610ce410b7cb6)
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, 2022, 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.BufferedReader;
27 import java.io.File;
28 import java.io.FileNotFoundException;
29 import java.io.FileReader;
30 import java.io.IOException;
31 import java.io.InputStream;
32 import java.io.OutputStream;
33 import java.nio.file.Paths;
34 import java.util.ArrayList;
35 import java.util.Map;
36 import java.util.logging.Level;
37 import java.util.logging.Logger;
38 
39 import org.jetbrains.annotations.VisibleForTesting;
40 import org.opengrok.indexer.configuration.CommandTimeoutType;
41 import org.opengrok.indexer.configuration.RuntimeEnvironment;
42 import org.opengrok.indexer.logger.LoggerFactory;
43 import org.opengrok.indexer.util.Executor;
44 
45 /**
46  * This class gives access to repositories built on top of SCCS (including TeamWare).
47  */
48 public class SCCSRepository extends Repository {
49 
50     private static final Logger LOGGER = LoggerFactory.getLogger(SCCSRepository.class);
51 
52     private static final long serialVersionUID = 1L;
53     /**
54      * The property name used to obtain the client command for this repository.
55      */
56     public static final String CMD_PROPERTY_KEY = "org.opengrok.indexer.history.SCCS";
57     /**
58      * The command to use to access the repository if none was given explicitly.
59      */
60     public static final String CMD_FALLBACK = "sccs";
61 
62     @VisibleForTesting
63     static final String CODEMGR_WSDATA = "Codemgr_wsdata";
64 
SCCSRepository()65     public SCCSRepository() {
66         type = "SCCS";
67         /*
68          * Originally there was only the "yy/MM/dd" pattern however the newer SCCS implementations seem
69          * to use the time as well. Some historical SCCS versions might still use that, so it is left there
70          * for potential compatibility.
71          */
72         datePatterns = new String[]{
73             "yy/MM/dd HH:mm:ss",
74             "yy/MM/dd"
75         };
76 
77         ignoredDirs.add("SCCS");
78     }
79 
80     @Override
getHistoryGet(OutputStream out, String parent, String basename, String rev)81     boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) {
82         try {
83             File history = SCCSHistoryParser.getSCCSFile(parent, basename);
84             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
85             try (InputStream in = SCCSget.getRevision(RepoCommand, history, rev)) {
86                 copyBytes(out::write, in);
87             }
88             return true;
89         } catch (FileNotFoundException ex) {
90             // continue below
91         } catch (IOException ex) {
92             LOGGER.log(Level.WARNING, "An error occurred while getting revision", ex);
93         }
94         return false;
95     }
96 
getAuthors(File file)97     private Map<String, String> getAuthors(File file) throws IOException {
98         ArrayList<String> argv = new ArrayList<>();
99         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
100         argv.add(RepoCommand);
101         argv.add("prs");
102         argv.add("-e");
103         argv.add("-d:I: :P:");
104         argv.add(file.getCanonicalPath());
105 
106         Executor executor = new Executor(argv, file.getCanonicalFile().getParentFile(),
107             RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
108         SCCSRepositoryAuthorParser parser = new SCCSRepositoryAuthorParser();
109         executor.exec(true, parser);
110 
111         return parser.getAuthors();
112     }
113 
114     /**
115      * Annotate the specified file/revision.
116      *
117      * @param file file to annotate
118      * @param revision revision to annotate
119      * @return file annotation
120      * @throws java.io.IOException if I/O exception occurs
121      */
122     @Override
annotate(File file, String revision)123     public Annotation annotate(File file, String revision) throws IOException {
124         Map<String, String> authors = getAuthors(file);
125 
126         ArrayList<String> argv = new ArrayList<>();
127         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
128         argv.add(RepoCommand);
129         argv.add("get");
130         argv.add("-m");
131         argv.add("-p");
132         if (revision != null) {
133             argv.add("-r" + revision);
134         }
135         argv.add(file.getCanonicalPath());
136         Executor executor = new Executor(argv, file.getCanonicalFile().getParentFile(),
137                 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
138         SCCSRepositoryAnnotationParser parser = new SCCSRepositoryAnnotationParser(file, authors);
139         executor.exec(true, parser);
140         return parser.getAnnotation();
141     }
142 
143     @Override
fileHasAnnotation(File file)144     public boolean fileHasAnnotation(File file) {
145         return true;
146     }
147 
148     @Override
fileHasHistory(File file)149     public boolean fileHasHistory(File file) {
150         String parentFile = file.getParent();
151         String name = file.getName();
152         File f = SCCSHistoryParser.getSCCSFile(parentFile, name);
153         if (f == null) {
154             return false;
155         }
156         return f.exists();
157     }
158 
159     @Override
isRepositoryFor(File file, CommandTimeoutType cmdType)160     boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
161         if (file.isDirectory()) {
162             File f = new File(file, CODEMGR_WSDATA.toLowerCase()); // OK no ROOT
163             if (f.isDirectory()) {
164                 return true;
165             }
166             f = new File(file, CODEMGR_WSDATA);
167             if (f.isDirectory()) {
168                 return true;
169             }
170             return new File(file, SCCSHistoryParser.SCCS_DIR_NAME).isDirectory();
171         }
172         return false;
173     }
174 
175     @Override
isWorking()176     public boolean isWorking() {
177         if (working == null) {
178             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
179             working = checkCmd(RepoCommand, "help", "help");
180             if (!working) {
181                 working = checkCmd(RepoCommand, "--version");
182             }
183         }
184         return working;
185     }
186 
187     @Override
hasHistoryForDirectories()188     boolean hasHistoryForDirectories() {
189         return false;
190     }
191 
192     @Override
getHistory(File file)193     History getHistory(File file) throws HistoryException {
194         return new SCCSHistoryParser(this).parse(file);
195     }
196 
197     @Override
determineParent(CommandTimeoutType cmdType)198     String determineParent(CommandTimeoutType cmdType) throws IOException {
199         File parentFile = Paths.get(getDirectoryName(), CODEMGR_WSDATA, "parent").toFile();
200         String parent = null;
201 
202         if (parentFile.isFile()) {
203             String line;
204             try (BufferedReader in = new BufferedReader(new FileReader(parentFile))) {
205                 if ((line = in.readLine()) == null) {
206                     LOGGER.log(Level.WARNING,
207                             "Failed to get parent for {0} (cannot read first line of {1})",
208                             new Object[]{getDirectoryName(), parentFile.getName()});
209                     return null;
210                 }
211                 if (!line.startsWith("VERSION")) {
212                     LOGGER.log(Level.WARNING,
213                             "Failed to get parent for {0} (first line does not start with VERSION)",
214                             getDirectoryName());
215                 }
216                 if ((parent = in.readLine()) == null) {
217                     LOGGER.log(Level.WARNING,
218                             "Failed to get parent for {0} (cannot read second line of {1})",
219                             new Object[]{getDirectoryName(), parentFile.getName()});
220                 }
221             }
222         }
223 
224         return parent;
225     }
226 
227     @Override
determineBranch(CommandTimeoutType cmdType)228     String determineBranch(CommandTimeoutType cmdType) {
229         return null;
230     }
231 
232     @Override
determineCurrentVersion(CommandTimeoutType cmdType)233     String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException {
234         return null;
235     }
236 }
237