xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/ClearCaseRepository.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) 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.File;
27 import java.io.FileInputStream;
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 import java.util.regex.Pattern;
36 
37 import org.opengrok.indexer.configuration.CommandTimeoutType;
38 import org.opengrok.indexer.configuration.RuntimeEnvironment;
39 import org.opengrok.indexer.logger.LoggerFactory;
40 import org.opengrok.indexer.util.Executor;
41 
42 /**
43  * Access to a ClearCase repository.
44  *
45  */
46 public class ClearCaseRepository extends Repository {
47 
48     private static final Logger LOGGER = LoggerFactory.getLogger(ClearCaseRepository.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.ClearCase";
55     /**
56      * The command to use to access the repository if none was given explicitly.
57      */
58     public static final String CMD_FALLBACK = "cleartool";
59 
ClearCaseRepository()60     public ClearCaseRepository() {
61         type = "ClearCase";
62         datePatterns = new String[]{
63             "yyyyMMdd.HHmmss"
64         };
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      * @return An Executor ready to be started
73      */
getHistoryLogExecutor(final File file)74     Executor getHistoryLogExecutor(final File file) throws IOException {
75         String filename = getRepoRelativePath(file);
76 
77         List<String> cmd = new ArrayList<>();
78         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
79         cmd.add(RepoCommand);
80         cmd.add("lshistory");
81         if (file.isDirectory()) {
82             cmd.add("-dir");
83         }
84         cmd.add("-fmt");
85         cmd.add("%e\n%Nd\n%Fu (%u)\n%Vn\n%Nc\n.\n");
86         cmd.add(filename);
87 
88         return new Executor(cmd, new File(getDirectoryName()));
89     }
90 
91     @Override
getHistoryGet(OutputStream out, String parent, String basename, String rev)92     boolean getHistoryGet(OutputStream out, String parent, String basename, String rev) {
93 
94         File directory = new File(getDirectoryName());
95 
96         try {
97             String filename = (new File(parent, basename)).getCanonicalPath()
98                     .substring(getDirectoryName().length() + 1);
99             final File tmp = File.createTempFile("opengrok", "tmp");
100             String tmpName = tmp.getCanonicalPath();
101 
102             // cleartool can't get to a previously existing file
103             if (tmp.exists() && !tmp.delete()) {
104                 LOGGER.log(Level.WARNING,
105                         "Failed to remove temporary file used by history cache");
106             }
107 
108             String decorated = filename + "@@" + rev;
109             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
110             String[] argv = {RepoCommand, "get", "-to", tmpName, decorated};
111             Executor executor = new Executor(Arrays.asList(argv), directory,
112                     RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
113             int status = executor.exec();
114             if (status != 0) {
115                 LOGGER.log(Level.SEVERE, "Failed to get history: {0}",
116                         executor.getErrorString());
117                 return false;
118             }
119 
120             try (FileInputStream in = new FileInputStream(tmp)) {
121                 copyBytes(out::write, in);
122             } finally {
123                 // delete the temporary file on close
124                 if (!tmp.delete()) {
125                     // failed, lets do the next best thing then ..
126                     // delete it on JVM exit
127                     tmp.deleteOnExit();
128                 }
129             }
130             return true;
131         } catch (Exception exp) {
132             LOGGER.log(Level.WARNING,
133                     "Failed to get history: " + exp.getClass().toString(), exp);
134         }
135 
136         return false;
137     }
138 
139     /**
140      * Annotate the specified file/revision.
141      *
142      * @param file file to annotate
143      * @param revision revision to annotate
144      * @return file annotation
145      * @throws java.io.IOException if I/O exception occurred
146      */
147     @Override
annotate(File file, String revision)148     public Annotation annotate(File file, String revision) throws IOException {
149         ArrayList<String> argv = new ArrayList<>();
150 
151         ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
152         argv.add(RepoCommand);
153         argv.add("annotate");
154         argv.add("-nheader");
155         argv.add("-out");
156         argv.add("-");
157         argv.add("-f");
158         argv.add("-fmt");
159         argv.add("%u|%Vn|");
160 
161         if (revision != null) {
162             argv.add(revision);
163         }
164         argv.add(file.getName());
165 
166         Executor executor = new Executor(argv, file.getParentFile(),
167                 RuntimeEnvironment.getInstance().getInteractiveCommandTimeout());
168         ClearCaseAnnotationParser parser = new ClearCaseAnnotationParser(file.getName());
169         executor.exec(true, parser);
170 
171         return parser.getAnnotation();
172     }
173 
174     @Override
fileHasAnnotation(File file)175     public boolean fileHasAnnotation(File file) {
176         return true;
177     }
178 
179     @Override
fileHasHistory(File file)180     public boolean fileHasHistory(File file) {
181         // Todo: is there a cheap test for whether ClearCase has history
182         // available for a file?
183         // Otherwise, this is harmless, since ClearCase's commands will just
184         // print nothing if there is no history.
185         return true;
186     }
187 
188     @Override
isWorking()189     public boolean isWorking() {
190         if (working == null) {
191             ensureCommand(CMD_PROPERTY_KEY, CMD_FALLBACK);
192             working = checkCmd(RepoCommand, "-version");
193         }
194         return working;
195     }
196 
197     @Override
isRepositoryFor(File file, CommandTimeoutType cmdType)198     boolean isRepositoryFor(File file, CommandTimeoutType cmdType) {
199         // if the parent contains a file named "[.]view.dat" or
200         // the parent is named "vobs" or the canonical path
201         // is found in "cleartool lsvob -s"
202         File fWindows = new File(file, "view.dat");
203         File fUnix = new File(file, ".view.dat");
204         if (fWindows.exists() || fUnix.exists()) {
205             return true;
206         } else if (file.isDirectory() && file.getName().equalsIgnoreCase("vobs")) {
207             return true;
208         } else if (isWorking()) {
209             try {
210                 String canonicalPath = file.getCanonicalPath();
211                 for (String vob : getAllVobs()) {
212                     if (canonicalPath.equalsIgnoreCase(vob)) {
213                         return true;
214                     }
215                 }
216             } catch (IOException e) {
217                 LOGGER.log(Level.WARNING,
218                         "Could not get canonical path for \"" + file + "\"", e);
219             }
220         }
221         return false;
222     }
223 
224     @Override
determineCurrentVersion(CommandTimeoutType cmdType)225     String determineCurrentVersion(CommandTimeoutType cmdType) throws IOException {
226         return null;
227     }
228 
229     private static class VobsHolder {
230         static String[] vobs = runLsvob();
231     }
232 
getAllVobs()233     private static String[] getAllVobs() {
234         return VobsHolder.vobs;
235     }
236 
237     private static final ClearCaseRepository testRepo
238             = new ClearCaseRepository();
239 
runLsvob()240     private static String[] runLsvob() {
241         if (testRepo.isWorking()) {
242             Executor exec = new Executor(
243                     new String[]{testRepo.RepoCommand, "lsvob", "-s"});
244             int rc;
245             if ((rc = exec.exec(true)) == 0) {
246                 String output = exec.getOutputString();
247 
248                 if (output == null) {
249                     LOGGER.log(Level.SEVERE,
250                             "\"cleartool lsvob -s\" output was null");
251                     return new String[0];
252                 }
253                 String sep = System.getProperty("line.separator");
254                 String[] vobs = output.split(Pattern.quote(sep));
255                 LOGGER.log(Level.CONFIG, "Found VOBs: {0}",
256                         Arrays.asList(vobs));
257                 return vobs;
258             }
259             LOGGER.log(Level.SEVERE,
260                     "\"cleartool lsvob -s\" returned non-zero status: {0}", rc);
261         }
262         return new String[0];
263     }
264 
265     @Override
hasHistoryForDirectories()266     boolean hasHistoryForDirectories() {
267         return true;
268     }
269 
270     @Override
getHistory(File file)271     History getHistory(File file) throws HistoryException {
272         return new ClearCaseHistoryParser().parse(file, this);
273     }
274 
275     @Override
determineParent(CommandTimeoutType cmdType)276     String determineParent(CommandTimeoutType cmdType) throws IOException {
277         return null;
278     }
279 
280     @Override
determineBranch(CommandTimeoutType cmdType)281     String determineBranch(CommandTimeoutType cmdType) {
282         return null;
283     }
284 }
285