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) 2006, 2018, Oracle and/or its affiliates. All rights reserved.
22  */
23 package org.opengrok.indexer.history;
24 
25 import java.io.BufferedReader;
26 import java.io.File;
27 import java.io.FileReader;
28 import java.io.IOException;
29 import java.util.ArrayList;
30 import java.util.List;
31 import java.util.TreeMap;
32 import java.util.logging.Level;
33 import java.util.logging.Logger;
34 
35 import org.suigeneris.jrcs.rcs.Archive;
36 import org.suigeneris.jrcs.rcs.impl.Node;
37 import org.suigeneris.jrcs.rcs.parse.ParseException;
38 import org.suigeneris.jrcs.rcs.Version;
39 import org.opengrok.indexer.logger.LoggerFactory;
40 
41 /**
42  * Virtualise RCS file as a reader, getting a specified version.
43  */
44 class RCSHistoryParser {
45 
46     private static final Logger LOGGER = LoggerFactory.getLogger(RCSHistoryParser.class);
47 
readCVSRoot(File root, File CVSdir, String name)48     private static File readCVSRoot(File root, File CVSdir, String name) throws IOException {
49         String cvsroot = readFirstLine(root);
50 
51         if (cvsroot == null) {
52             return null;
53         }
54         if (cvsroot.charAt(0) != '/') {
55             return null;
56         }
57 
58         File repository = new File(CVSdir, "Repository");
59         String repo = readFirstLine(repository);
60         String dir = cvsroot + File.separatorChar + repo;
61         String filename = name + ",v";
62         File rcsFile = new File(dir, filename);
63         if (!rcsFile.exists()) {
64             File atticFile = new File(dir + File.separatorChar + "Attic", filename);
65             if (atticFile.exists()) {
66                 rcsFile = atticFile;
67             }
68         }
69         return rcsFile;
70     }
71 
parse(File file, Repository repos)72     History parse(File file, Repository repos) throws HistoryException {
73         try {
74             return parseFile(file);
75         } catch (IOException ioe) {
76             throw new HistoryException(ioe);
77         }
78     }
79 
parseFile(File file)80     private History parseFile(File file) throws IOException {
81         File rcsfile = getRCSFile(file);
82         if (rcsfile == null) {
83             return null;
84         }
85 
86         try {
87             Archive archive = new Archive(rcsfile.getPath());
88             Version ver = archive.getRevisionVersion();
89             Node n = archive.findNode(ver);
90             n = n.root();
91 
92             ArrayList<HistoryEntry> entries = new ArrayList<HistoryEntry>();
93             traverse(n, entries);
94 
95             History history = new History();
96             history.setHistoryEntries(entries);
97             return history;
98         } catch (ParseException pe) {
99             throw RCSRepository.wrapInIOException(
100                     "Could not parse file " + file.getPath(), pe);
101         }
102     }
103 
traverse(Node n, List<HistoryEntry> history)104     private void traverse(Node n, List<HistoryEntry> history) {
105         if (n == null) {
106             return;
107         }
108         traverse(n.getChild(), history);
109         TreeMap<?, ?> brt = n.getBranches();
110         if (brt != null) {
111             for (Object o : brt.values()) {
112                 Node b = (Node) o;
113                 traverse(b, history);
114             }
115         }
116         if (!n.isGhost()) {
117             HistoryEntry entry = new HistoryEntry();
118             entry.setRevision(n.getVersion().toString());
119             entry.setDate(n.getDate());
120             entry.setAuthor(n.getAuthor());
121             entry.setMessage(n.getLog());
122             entry.setActive(true);
123             history.add(entry);
124         }
125     }
126 
getRCSFile(File file)127     protected static File getRCSFile(File file) {
128         return getRCSFile(file.getParent(), file.getName());
129     }
130 
getRCSFile(String parent, String name)131     protected static File getRCSFile(String parent, String name) {
132         File rcsDir = new File(parent, "RCS");
133         File rcsFile = new File(rcsDir, name + ",v");
134         if (rcsFile.exists()) {
135             return rcsFile;
136         }
137         // not RCS, try CVS instead
138         return getCVSFile(parent, name);
139     }
140 
getCVSFile(String parent, String name)141     protected static File getCVSFile(String parent, String name) {
142         try {
143             File CVSdir = new File(parent, "CVS");
144             if (CVSdir.isDirectory() && CVSdir.canRead()) {
145                 File root = new File(CVSdir, "Root");
146                 if (root.canRead()) {
147                     return readCVSRoot(root, CVSdir, name);
148                 }
149             }
150         } catch (Exception e) {
151             LOGGER.log(Level.WARNING,
152                     "Failed to retrieve CVS file of parent: " + parent + ", name: " + name, e);
153         }
154         return null;
155     }
156 
157     /**
158      * Read the first line of a file.
159      * @param file the file from which to read
160      * @return the first line of the file, or {@code null} if the file is empty
161      * @throws IOException if an I/O error occurs while reading the file
162      */
readFirstLine(File file)163     private static String readFirstLine(File file) throws IOException {
164         try (BufferedReader in = new BufferedReader(new FileReader(file))) {
165             return in.readLine();
166         }
167     }
168 }
169