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, 2021, 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<>(); 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