xref: /OpenGrok/opengrok-indexer/src/main/java/org/opengrok/indexer/history/MonotoneHistoryParser.java (revision c6f0939b1c668e9f8e1e276424439c3106b3a029)
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