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) 2009, 2021, Oracle and/or its affiliates. All rights reserved. 22 * Portions Copyright (c) 2017, 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.IOException; 30 import java.io.InputStream; 31 import java.io.InputStreamReader; 32 import java.nio.file.InvalidPathException; 33 import java.text.ParseException; 34 import java.util.ArrayList; 35 import java.util.Date; 36 import java.util.List; 37 import java.util.logging.Level; 38 import java.util.logging.Logger; 39 import org.opengrok.indexer.configuration.RuntimeEnvironment; 40 import org.opengrok.indexer.logger.LoggerFactory; 41 import org.opengrok.indexer.util.Executor; 42 import org.opengrok.indexer.util.ForbiddenSymlinkException; 43 44 /** 45 * Class used to parse the history log from Monotone. 46 * 47 * @author Trond Norbye 48 */ 49 class MonotoneHistoryParser implements Executor.StreamHandler { 50 51 private static final Logger LOGGER = LoggerFactory.getLogger(MonotoneHistoryParser.class); 52 53 private final List<HistoryEntry> entries = new ArrayList<>(); //NOPMD 54 private final MonotoneRepository repository; 55 private final String mydir; 56 MonotoneHistoryParser(MonotoneRepository repository)57 MonotoneHistoryParser(MonotoneRepository repository) { 58 this.repository = repository; 59 mydir = repository.getDirectoryName() + File.separator; 60 } 61 62 /** 63 * Parse the history for the specified file or directory. If a changeset is 64 * specified, only return the history from the changeset right after the 65 * specified one. 66 * 67 * @param file the file or directory to get history for 68 * @param changeset the changeset right before the first one to fetch, or 69 * {@code null} if all changesets should be fetched 70 * @return history for the specified file or directory 71 * @throws HistoryException if an error happens when parsing the history 72 */ parse(File file, String changeset)73 History parse(File file, String changeset) throws HistoryException { 74 try { 75 Executor executor = repository.getHistoryLogExecutor(file, changeset); 76 int status = executor.exec(true, this); 77 78 if (status != 0) { 79 throw new HistoryException("Failed to get history for: \"" + 80 file.getAbsolutePath() + "\" Exit code: " + status); 81 } 82 } catch (IOException e) { 83 throw new HistoryException("Failed to get history for: \"" + 84 file.getAbsolutePath() + "\"", e); 85 } 86 87 return new History(entries); 88 } 89 90 /** 91 * Process the output from the hg log command and insert the HistoryEntries 92 * into the history field. 93 * 94 * @param input The output from the process 95 * @throws java.io.IOException If an error occurs while reading the stream 96 */ 97 @Override processStream(InputStream input)98 public void processStream(InputStream input) throws IOException { 99 RuntimeEnvironment env = RuntimeEnvironment.getInstance(); 100 BufferedReader in = new BufferedReader(new InputStreamReader(input)); 101 String s; 102 103 HistoryEntry entry = null; 104 int state = 0; 105 while ((s = in.readLine()) != null) { 106 s = s.trim(); 107 // Later versions of monotone (such as 1.0) output even more dashes so lets require 108 // the minimum amount for maximum compatibility between monotone versions. 109 if (s.startsWith("-----------------------------------------------------------------")) { 110 if (entry != null && state > 2) { 111 entries.add(entry); 112 } 113 entry = new HistoryEntry(); 114 entry.setActive(true); 115 state = 0; 116 117 continue; 118 } 119 120 switch (state) { 121 case 0: 122 if (s.startsWith("Revision:")) { 123 String rev = s.substring("Revision:".length()).trim(); 124 entry.setRevision(rev); 125 ++state; 126 } 127 break; 128 case 1: 129 if (s.startsWith("Author:")) { 130 entry.setAuthor(s.substring("Author:".length()).trim()); 131 ++state; 132 } 133 break; 134 case 2: 135 if (s.startsWith("Date:")) { 136 Date date = new Date(); 137 try { 138 date = repository.parse(s.substring("date:".length()).trim()); 139 } catch (ParseException pe) { 140 // 141 // Overriding processStream() thus need to comply with the 142 // set of exceptions it can throw. 143 // 144 throw new IOException("Could not parse date: " + s, pe); 145 } 146 entry.setDate(date); 147 ++state; 148 } 149 break; 150 case 3: 151 if (s.startsWith("Modified ") || s.startsWith("Added ") || s.startsWith("Deleted ")) { 152 ++state; 153 } else if (s.equalsIgnoreCase("ChangeLog:")) { 154 state = 5; 155 } 156 break; 157 case 4: 158 if (s.startsWith("Modified ") || s.startsWith("Added ") || s.startsWith("Deleted ")) { 159 continue; 160 } else if (s.equalsIgnoreCase("ChangeLog:")) { 161 state = 5; 162 } else { 163 String[] files = s.split(" "); 164 for (String f : files) { 165 File file = new File(mydir, f); 166 try { 167 String path = env.getPathRelativeToSourceRoot( 168 file); 169 entry.addFile(path.intern()); 170 } catch (ForbiddenSymlinkException e) { 171 LOGGER.log(Level.FINER, e.getMessage()); 172 // ignore 173 } catch (FileNotFoundException e) { // NOPMD 174 // If the file is not located under the source root, ignore it 175 } catch (InvalidPathException e) { 176 LOGGER.log(Level.WARNING, e.getMessage()); 177 } 178 } 179 } 180 break; 181 case 5: 182 entry.appendMessage(s); 183 break; 184 default: 185 LOGGER.warning("Unknown parser state: " + state); 186 break; 187 } 188 } 189 190 if (entry != null && state > 2) { 191 entries.add(entry); 192 } 193 } 194 } 195