18
18
*/
19
19
20
20
/*
21
- * Copyright (c) 2006, 2021 , Oracle and/or its affiliates. All rights reserved.
21
+ * Copyright (c) 2006, 2022 , Oracle and/or its affiliates. All rights reserved.
22
22
* Portions Copyright (c) 2017, Chris Fraire <[email protected] >.
23
23
*/
24
24
package org .opengrok .indexer .history ;
33
33
import java .text .ParseException ;
34
34
import java .util .ArrayList ;
35
35
import java .util .Date ;
36
- import java .util .HashSet ;
37
36
import java .util .Iterator ;
38
37
import java .util .List ;
39
- import java .util .Set ;
40
38
import java .util .logging .Level ;
41
39
import java .util .logging .Logger ;
42
40
import org .opengrok .indexer .configuration .RuntimeEnvironment ;
@@ -54,67 +52,94 @@ class MercurialHistoryParser implements Executor.StreamHandler {
54
52
/** Prefix which identifies lines with the description of a commit. */
55
53
private static final String DESC_PREFIX = "description: " ;
56
54
57
- private List <HistoryEntry > entries = new ArrayList <>();
55
+ private List <RepositoryWithHistoryTraversal . ChangesetInfo > entries = new ArrayList <>();
58
56
private final MercurialRepository repository ;
59
57
private final String mydir ;
60
58
private boolean isDir ;
61
- private final Set < String > renamedFiles = new HashSet <>() ;
59
+ private final List < ChangesetVisitor > visitors ;
62
60
63
- MercurialHistoryParser (MercurialRepository repository ) {
61
+ MercurialHistoryParser (MercurialRepository repository , List < ChangesetVisitor > visitors ) {
64
62
this .repository = repository ;
63
+ this .visitors = visitors ;
65
64
mydir = repository .getDirectoryName () + File .separator ;
66
65
}
67
66
68
67
/**
69
68
* Parse the history for the specified file or directory. If a changeset is
70
- * specified, only return the history from the changeset right after the
71
- * specified one.
69
+ * specified, only return the history from the changeset right after the specified one.
72
70
*
73
71
* @param file the file or directory to get history for
74
72
* @param sinceRevision the changeset right before the first one to fetch, or
75
73
* {@code null} if all changesets should be fetched
76
74
* @param tillRevision end revision or {@code null}
77
75
* @param numCommits number of revisions to get
78
- * @return history for the specified file or directory
79
76
* @throws HistoryException if an error happens when parsing the history
80
77
*/
81
- History parse (File file , String sinceRevision , String tillRevision , Integer numCommits ) throws HistoryException {
78
+ void parse (File file , String sinceRevision , String tillRevision , Integer numCommits ) throws HistoryException {
82
79
isDir = file .isDirectory ();
83
80
try {
84
81
Executor executor = repository .getHistoryLogExecutor (file , sinceRevision , tillRevision , false ,
85
82
numCommits );
86
83
int status = executor .exec (true , this );
87
84
88
85
if (status != 0 ) {
89
- throw new HistoryException ("Failed to get history for: \" " +
90
- file .getAbsolutePath () +
86
+ throw new HistoryException ("Failed to get history for: \" " + file .getAbsolutePath () +
91
87
"\" Exit code: " + status );
92
88
}
93
89
} catch (IOException e ) {
94
- throw new HistoryException ("Failed to get history for: \" " +
95
- file .getAbsolutePath () + "\" " , e );
90
+ throw new HistoryException ("Failed to get history for: \" " + file .getAbsolutePath () + "\" " , e );
96
91
}
97
92
98
- // If a changeset to start from is specified, remove that changeset
99
- // from the list, since only the ones following it should be returned.
100
- // Also check that the specified changeset was found, otherwise throw
101
- // an exception.
93
+ // If a changeset to start from is specified, remove that changeset from the list,
94
+ // since only the ones following it should be returned.
95
+ // Also check that the specified changeset was found, otherwise throw an exception.
102
96
if (sinceRevision != null ) {
103
- repository . removeAndVerifyOldestChangeset (entries , sinceRevision );
97
+ removeAndVerifyOldestChangeset (entries , sinceRevision );
104
98
}
105
99
106
100
// See getHistoryLogExecutor() for explanation.
107
101
if (repository .isHandleRenamedFiles () && file .isFile () && tillRevision != null ) {
108
102
removeChangesets (entries , tillRevision );
109
103
}
110
104
111
- return new History (entries , renamedFiles );
105
+ // The visitors are fed with the ChangesetInfo instances here (as opposed to in parse()),
106
+ // because of the above manipulations with the entries.
107
+ for (RepositoryWithHistoryTraversal .ChangesetInfo info : entries ) {
108
+ for (ChangesetVisitor visitor : visitors ) {
109
+ visitor .accept (info );
110
+ }
111
+ }
112
+ }
113
+
114
+ /**
115
+ * Remove the oldest changeset from a list (assuming sorted with most recent
116
+ * changeset first) and verify that it is the changeset expected to find there.
117
+ *
118
+ * @param entries a list of {@code HistoryEntry} objects
119
+ * @param revision the revision we expect the oldest entry to have
120
+ * @throws HistoryException if the oldest entry was not the one we expected
121
+ */
122
+ private void removeAndVerifyOldestChangeset (List <RepositoryWithHistoryTraversal .ChangesetInfo > entries , String revision )
123
+ throws HistoryException {
124
+
125
+ RepositoryWithHistoryTraversal .ChangesetInfo entry = entries .isEmpty () ? null : entries .remove (entries .size () - 1 );
126
+
127
+ // TODO We should check more thoroughly that the changeset is the one
128
+ // we expected it to be, since some SCMs may change the revision
129
+ // numbers so that identical revision numbers does not always mean
130
+ // identical changesets. We could for example get the cached changeset
131
+ // and compare more fields, like author and date.
132
+ if (entry == null || !revision .equals (entry .commit .revision )) {
133
+ throw new HistoryException ("Cached revision '" + revision
134
+ + "' not found in the repository "
135
+ + repository .getDirectoryName ());
136
+ }
112
137
}
113
138
114
- private void removeChangesets (List <HistoryEntry > entries , String tillRevision ) {
115
- for (Iterator <HistoryEntry > iter = entries .listIterator (); iter .hasNext (); ) {
116
- HistoryEntry entry = iter .next ();
117
- if (entry .getRevision () .equals (tillRevision )) {
139
+ private void removeChangesets (List <RepositoryWithHistoryTraversal . ChangesetInfo > entries , String tillRevision ) {
140
+ for (Iterator <RepositoryWithHistoryTraversal . ChangesetInfo > iter = entries .listIterator (); iter .hasNext (); ) {
141
+ RepositoryWithHistoryTraversal . ChangesetInfo entry = iter .next ();
142
+ if (entry .commit . revision .equals (tillRevision )) {
118
143
break ;
119
144
}
120
145
iter .remove ();
@@ -123,7 +148,7 @@ private void removeChangesets(List<HistoryEntry> entries, String tillRevision) {
123
148
124
149
/**
125
150
* Process the output from the {@code hg log} command and collect
126
- * {@link HistoryEntry } elements.
151
+ * {@link org.opengrok.indexer.history.RepositoryWithHistoryTraversal.ChangesetInfo } elements.
127
152
*
128
153
* @param input The output from the process
129
154
* @throws java.io.IOException If an error occurs while reading the stream
@@ -134,15 +159,14 @@ public void processStream(InputStream input) throws IOException {
134
159
BufferedReader in = new BufferedReader (new InputStreamReader (input ));
135
160
entries = new ArrayList <>();
136
161
String s ;
137
- HistoryEntry entry = null ;
162
+ RepositoryWithHistoryTraversal . ChangesetInfo entry = null ;
138
163
while ((s = in .readLine ()) != null ) {
139
164
if (s .startsWith (MercurialRepository .CHANGESET )) {
140
- entry = new HistoryEntry ( );
165
+ entry = new RepositoryWithHistoryTraversal . ChangesetInfo ( new RepositoryWithHistoryTraversal . CommitInfo () );
141
166
entries .add (entry );
142
- entry .setActive (true );
143
- entry .setRevision (s .substring (MercurialRepository .CHANGESET .length ()).trim ());
167
+ entry .commit .revision = s .substring (MercurialRepository .CHANGESET .length ()).trim ();
144
168
} else if (s .startsWith (MercurialRepository .USER ) && entry != null ) {
145
- entry .setAuthor ( s .substring (MercurialRepository .USER .length ()).trim () );
169
+ entry .commit . authorName = s .substring (MercurialRepository .USER .length ()).trim ();
146
170
} else if (s .startsWith (MercurialRepository .DATE ) && entry != null ) {
147
171
Date date ;
148
172
try {
@@ -154,15 +178,15 @@ public void processStream(InputStream input) throws IOException {
154
178
//
155
179
throw new IOException ("Could not parse date: " + s , pe );
156
180
}
157
- entry .setDate ( date ) ;
181
+ entry .commit . date = date ;
158
182
} else if (s .startsWith (MercurialRepository .FILES ) && entry != null ) {
159
183
String [] strings = s .split (" " );
160
184
for (int ii = 1 ; ii < strings .length ; ++ii ) {
161
185
if (strings [ii ].length () > 0 ) {
162
186
File f = new File (mydir , strings [ii ]);
163
187
try {
164
188
String path = env .getPathRelativeToSourceRoot (f );
165
- entry .addFile (path .intern ());
189
+ entry .files . add (path .intern ());
166
190
} catch (ForbiddenSymlinkException e ) {
167
191
LOGGER .log (Level .FINER , e .getMessage ());
168
192
// ignore
@@ -189,11 +213,11 @@ public void processStream(InputStream input) throws IOException {
189
213
String [] move = part .split (" \\ (" );
190
214
File f = new File (mydir + move [0 ]);
191
215
if (!move [0 ].isEmpty () && f .exists ()) {
192
- renamedFiles .add (repository .getDirectoryNameRelative () + File .separator + move [0 ]);
216
+ entry . renamedFiles .add (repository .getDirectoryNameRelative () + File .separator + move [0 ]);
193
217
}
194
218
}
195
219
} else if (s .startsWith (DESC_PREFIX ) && entry != null ) {
196
- entry .setMessage ( decodeDescription (s ) );
220
+ entry .commit . message = decodeDescription (s );
197
221
} else if (s .equals (MercurialRepository .END_OF_ENTRY )
198
222
&& entry != null ) {
199
223
entry = null ;
0 commit comments