1<%--
2$Id$
3
4CDDL HEADER START
5
6The contents of this file are subject to the terms of the
7Common Development and Distribution License (the "License").
8You may not use this file except in compliance with the License.
9
10See LICENSE.txt included in this distribution for the specific
11language governing permissions and limitations under the License.
12
13When distributing Covered Code, include this CDDL HEADER in each
14file and include the License file at LICENSE.txt.
15If applicable, add the following below this CDDL HEADER, with the
16fields enclosed by brackets "[]" replaced with your own identifying
17information: Portions Copyright [yyyy] [name of copyright owner]
18
19CDDL HEADER END
20
21Copyright (c) 2005, 2018, Oracle and/or its affiliates. All rights reserved.
22Portions Copyright 2011 Jens Elkner.
23Portions Copyright (c) 2018-2020, Chris Fraire <cfraire@me.com>.
24--%>
25<%@page contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
26<%@page errorPage="error.jsp" import="
27java.io.IOException,
28java.io.File,
29java.text.Format,
30java.text.SimpleDateFormat,
31java.util.Date,
32java.util.logging.Level,
33java.util.logging.Logger,
34java.util.Objects,
35java.util.Set,
36java.util.regex.Pattern,
37
38org.opengrok.indexer.configuration.RuntimeEnvironment,
39org.opengrok.indexer.history.History,
40org.opengrok.indexer.history.HistoryEntry,
41org.opengrok.indexer.history.HistoryGuru,
42org.opengrok.indexer.logger.LoggerFactory,
43org.opengrok.indexer.util.ForbiddenSymlinkException,
44org.opengrok.indexer.web.QueryParameters,
45org.opengrok.indexer.web.SearchHelper,
46org.opengrok.indexer.web.Util"
47%>
48<%/* ---------------------- history.jsp start --------------------- */
49{
50    final Logger LOGGER = LoggerFactory.getLogger(getClass());
51
52    PageConfig cfg = PageConfig.get(request);
53    cfg.checkSourceRootExistence();
54
55    // Need to set the title before including httpheader.jspf
56    cfg.setTitle(cfg.getHistoryTitle());
57
58    String path = cfg.getPath();
59
60    if (path.length() > 0) {
61        String primePath = path;
62        Project project = cfg.getProject();
63        if (project != null) {
64            SearchHelper searchHelper = cfg.prepareInternalSearch();
65            /*
66             * N.b. searchHelper.destroy() is called via
67             * WebappListener.requestDestroyed() on presence of the following
68             * REQUEST_ATTR.
69             */
70            request.setAttribute(SearchHelper.REQUEST_ATTR, searchHelper);
71            searchHelper.prepareExec(project);
72
73            try {
74                primePath = searchHelper.getPrimeRelativePath(project.getName(), path);
75            } catch (IOException | ForbiddenSymlinkException ex) {
76                LOGGER.log(Level.WARNING, String.format(
77                        "Error getting prime relative for %s", path), ex);
78            }
79        }
80
81        File file = cfg.getResourceFile(primePath);
82        History hist;
83        try {
84            hist = HistoryGuru.getInstance().getHistoryUI(file);
85        } catch (Exception e) {
86            // should not happen
87            response.sendError(HttpServletResponse.SC_NOT_FOUND, e.getMessage());
88            return;
89        }
90
91        if (hist == null) {
92            /*
93             * The history is not available even for a renamed file.
94             * Send 404 Not Found.
95             */
96            response.sendError(HttpServletResponse.SC_NOT_FOUND);
97            return;
98        }
99        request.setAttribute("history.jsp-hist", hist);
100    }
101}
102%>
103<%@
104
105include file="httpheader.jspf"
106
107%>
108<%
109{
110    if ((request.getAttribute("history.jsp-hist")) != null) {
111%>
112<body>
113<script type="text/javascript">/* <![CDATA[ */
114    document.rev = function() { return getParameter("<%= QueryParameters.REVISION_PARAM %>"); };
115    document.annotate = <%= PageConfig.get(request).annotate() %>;
116    document.domReady.push(function() { domReadyMast(); });
117    document.pageReady.push(function() { pageReadyMast(); });
118/* ]]> */</script>
119<div id="page">
120    <div id="whole_header">
121        <div id="header">
122<%
123    }
124}
125{
126    if (request.getAttribute("history.jsp-hist") != null) {
127%><%@
128
129include file="pageheader.jspf"
130
131%>
132<%
133    }
134}
135{
136    PageConfig cfg = PageConfig.get(request);
137    String context = request.getContextPath();
138    String path = cfg.getPath();
139
140    History hist;
141    if ((hist = (History) request.getAttribute("history.jsp-hist")) != null) {
142
143        int start = cfg.getSearchStart();
144        int max = cfg.getSearchMaxItems();
145        long totalHits = hist.getHistoryEntries().size();
146        long thispage = Math.min(totalHits - start, max);
147
148        // We have a lots of results to show: create a slider for them
149        request.setAttribute("history.jsp-slider", Util.createSlider(start, max, totalHits, request));
150%>
151        </div>
152        <div id="Masthead">History log of
153        <%= Util.breadcrumbPath(context + Prefix.XREF_P, path,'/',"",true,cfg.isDir()) %>
154        (Results <b> <%= totalHits != 0 ? start + 1 : 0 %> – <%= thispage + start
155            %></b> of <b><%= totalHits %></b>)
156        </div>
157<%
158    }
159}
160{
161    if (request.getAttribute("history.jsp-hist") != null) {
162%>
163        <%@
164
165include file="minisearch.jspf"
166
167%>
168<%
169    }
170}
171{
172    PageConfig cfg = PageConfig.get(request);
173    String context = request.getContextPath();
174    String path = cfg.getPath();
175    History hist;
176    if ((hist = (History) request.getAttribute("history.jsp-hist")) != null) {
177        RuntimeEnvironment env = cfg.getEnv();
178        String uriEncodedName = cfg.getUriEncodedPath();
179
180        boolean striked = false;
181        String userPage = env.getUserPage();
182        String userPageSuffix = env.getUserPageSuffix();
183        String bugPage = env.getBugPage();
184        String bugRegex = env.getBugPattern();
185        Pattern bugPattern = Pattern.compile(bugRegex);
186        String reviewPage = env.getReviewPage();
187        String reviewRegex = env.getReviewPattern();
188        Pattern reviewPattern = Pattern.compile(reviewRegex);
189
190        Format df = new SimpleDateFormat("dd-MMM-yyyy");
191
192        int revision2 = Math.max(cfg.getIntParam(QueryParameters.REVISION_2_PARAM, -1), 0);
193        int revision1 = cfg.getIntParam(QueryParameters.REVISION_1_PARAM, -1) < revision2 ?
194                revision2 + 1 : cfg.getIntParam(QueryParameters.REVISION_1_PARAM, -1);
195        revision2 = revision2 >= hist.getHistoryEntries().size() ? hist.getHistoryEntries().size() - 1 : revision2;
196
197        int start = cfg.getSearchStart();
198        int max = cfg.getSearchMaxItems();
199%>
200<script type="text/javascript">/* <![CDATA[ */
201document.domReady.push(function() {domReadyHistory();});
202/* ]]> */</script>
203<!--[if IE]>
204<style type="text/css">
205  table#revisions tbody tr td p {
206        word-break: break-all;
207    }
208</style>
209<![endif]-->
210<form action="<%= context + Prefix.DIFF_P + uriEncodedName %>">
211<table class="src" id="revisions">
212    <thead>
213        <tr>
214            <th>Revision <%
215            if (hist.hasTags()) {
216                %><a href="#" onclick="javascript: toggle_revtags(); return false;">
217                    <span class="revtags-hidden">
218                    (&lt;&lt;&lt; Hide revision tags)</span>
219                    <span class="revtags">
220                    (Show revision tags &gt;&gt;&gt;)</span></a><%
221            }
222            %></th><%
223            if (!cfg.isDir()) {
224            %>
225            <th><input type="submit" value=" Compare "/>
226            <% if (hist.getHistoryEntries().size() > revision1 && revision1 >= 0) { %>
227                <input type="hidden" id="input_r1" name="<%= QueryParameters.REVISION_1_PARAM %>"
228                value="<%= path + '@' + hist.getHistoryEntries().get(revision1).getRevision() %>"/>
229            <% } %>
230            <% if (hist.getHistoryEntries().size() > revision2 && revision2 >= 0) { %>
231                <input type="hidden" id="input_r2" name="<%= QueryParameters.REVISION_2_PARAM %>"
232                value="<%= path + '@' + hist.getHistoryEntries().get(revision2).getRevision() %>"/>
233            <% } %>
234            </th><%
235            }
236            %>
237            <th>Date</th>
238            <th>Author</th>
239            <th>Comments <%
240            if (hist.hasFileList()) {
241                %><a href="#" onclick="javascript: toggle_filelist(); return false;">
242                    <div class="filelist-hidden">
243                    (&lt;&lt;&lt; Hide modified files)</div>
244                    <div class="filelist">
245                    (Show modified files &gt;&gt;&gt;)</div></a><%
246            }
247            %>
248            </th>
249        </tr>
250    </thead>
251    <tbody>
252            <%
253            int count=0;
254            for (HistoryEntry entry : hist.getHistoryEntries(max, start)) {
255                String rev = entry.getRevision();
256                if (rev == null || rev.length() == 0) {
257                    rev = "";
258                }
259                String tags = entry.getTags();
260
261                if (tags != null) {
262			int colspan;
263			if (cfg.isDir())
264				colspan = 4;
265			else
266				colspan = 5;
267                    %>
268        <tr class="revtags-hidden">
269            <td colspan="<%= colspan %>" class="revtags">
270                <b>Revision tags:</b> <%= tags %>
271            </td>
272        </tr><tr style="display: none;"></tr><%
273                }
274    %>
275        <tr><%
276                if (cfg.isDir()) {
277            %>
278            <td><%= rev %></td><%
279                } else {
280                    if (entry.isActive()) {
281                        StringBuffer urlBuffer = request.getRequestURL();
282                        if (request.getQueryString() != null) {
283                            urlBuffer.append('?').append(request.getQueryString());
284                        }
285                        urlBuffer.append('#').append(rev);
286            %>
287            <td><a href="<%= urlBuffer %>"
288                title="link to revision line">#</a>
289                <a href="<%= context + Prefix.XREF_P + uriEncodedName + "?" +
290                        QueryParameters.REVISION_PARAM_EQ + Util.URIEncode(rev) %>"><%= rev %>
291                </a></td>
292            <td><%
293                %><input type="radio"
294                        data-revision-1="<%= (start + count) %>"
295                        data-revision-2="<%= revision2 %>"
296                        data-diff-revision="<%= QueryParameters.REVISION_1_PARAM %>"
297                        data-revision-path="<%= path + '@' + hist.getHistoryEntries().get(start + count).getRevision()%>"
298                <%
299                if (count + start > revision1 || (count + start > revision2 && count + start <= revision1 - 1)) {
300                    // revision1 enabled
301                } else if (count + start == revision1 ) {
302                    // revision1 selected
303                    %> checked="checked"<%
304                } else if( count + start <= revision2 ) {
305                    // revision1 disabled
306                    %> disabled="disabled" <%
307                }
308                %>/><%
309
310                %><input type="radio"
311                        data-revision-1="<%= revision1 %>"
312                        data-revision-2="<%= (start + count) %>"
313                        data-diff-revision="<%= QueryParameters.REVISION_2_PARAM %>"
314                        data-revision-path="<%= path + '@' + hist.getHistoryEntries().get(start + count).getRevision() %>"
315                <%
316                if( count + start < revision2 || (count + start > revision2 && count + start <= revision1 - 1) ) {
317                    // revision2 enabled
318                } else if( count + start == revision2 ) {
319                    // revision2 selected
320                    %> checked="checked" <%
321                } else if (count + start >= revision1 ) {
322                    // revision2 disabled
323                    %> disabled="disabled" <%
324                }
325                %>/><%
326                %></td><%
327                    } else {
328                        striked = true;
329                %>
330            <td><del><%= rev %></del></td>
331            <td></td><%
332                    }
333                }
334            %>
335            <td><%
336                Date date = entry.getDate();
337                if (date != null) {
338            %><%= df.format(date) %><%
339                }
340                %></td>
341            <td><%
342                String author = entry.getAuthor();
343                if (author == null) {
344                %>(no author)<%
345                } else if (userPage != null && userPage.length() > 0) {
346		String alink = Util.getEmail(author);
347                %><a href="<%= userPage + Util.htmlize(alink) + userPageSuffix
348                %>"><%= Util.htmlize(author)%></a><%
349                } else {
350                %><%= author %><%
351                }
352                %></td>
353            <td><a name="<%= rev %>"></a><%
354                // revision message collapse threshold minimum of 10
355                int summaryLength = Math.max(10, cfg.getRevisionMessageCollapseThreshold());
356                String cout = Util.htmlize(entry.getMessage());
357
358                if (bugPage != null && bugPage.length() > 0) {
359                    cout = Util.linkifyPattern(cout, bugPattern, "$1", Util.completeUrl(bugPage + "$1", request));
360                }
361                if (reviewPage != null && reviewPage.length() > 0) {
362                    cout = Util.linkifyPattern(cout, reviewPattern, "$1", Util.completeUrl(reviewPage + "$1", request));
363                }
364
365                boolean showSummary = false;
366                String coutSummary = entry.getMessage();
367                if (coutSummary.length() > summaryLength) {
368                    showSummary = true;
369                    coutSummary = coutSummary.substring(0, summaryLength - 1);
370                    coutSummary = Util.htmlize(coutSummary);
371                    if (bugPage != null && bugPage.length() > 0) {
372                        coutSummary = Util.linkifyPattern(coutSummary, bugPattern, "$1", Util.completeUrl(bugPage + "$1", request));
373                    }
374                    if (reviewPage != null && reviewPage.length() > 0) {
375                        coutSummary = Util.linkifyPattern(coutSummary, reviewPattern, "$1", Util.completeUrl(reviewPage + "$1", request));
376                    }
377                }
378
379                if (showSummary) {
380                    %>
381                    <p class="rev-message-summary"><%= coutSummary %></p>
382                    <p class="rev-message-full rev-message-hidden"><%= cout %></p>
383                    <p class="rev-message-toggle" data-toggle-state="less"><a class="rev-toggle-a" href="#">show more ... </a></p>
384                    <%
385                }
386                else {
387                     %><p class="rev-message-full"><%= cout %></p><%
388                }
389
390                Set<String> files = entry.getFiles();
391                if (files != null) {
392                %><div class="filelist-hidden"><br/><%
393                    for (String ifile : files) {
394                        String jfile = Util.stripPathPrefix(path, ifile);
395                        if (Objects.equals(rev, "")) {
396                %>
397<a class="h" href="<%= context + Prefix.XREF_P + ifile %>"><%= jfile %></a><br/><%
398                        } else {
399                %>
400<a class="h" href="<%= context + Prefix.XREF_P + ifile %>?<%= QueryParameters.REVISION_PARAM_EQ %>
401<%= rev %>"><%= jfile %></a><br/><%
402                        }
403                    }
404                %></div><%
405                }
406                %></td>
407        </tr><%
408                count++;
409            }
410        %>
411    </tbody>
412    <tfoot>
413        <tr>
414            <td colspan="5">
415<%
416    String slider;
417    if ((slider = (String) request.getAttribute("history.jsp-slider")) != null) {
418        // NOTE: shouldn't happen that it doesn't have this attribute
419        %><p class="slider"><%= slider %></p><%
420    }
421%>
422            </td>
423        </tr>
424    </tfoot>
425</table>
426
427</form><%
428            if (striked) {
429%><p><b>Note:</b> No associated file changes are available for
430revisions with strike-through numbers (eg. <del>1.45</del>)</p><%
431            }
432%>
433<p class="rssbadge"><a href="<%=context + Prefix.RSS_P + uriEncodedName
434%>" title="RSS XML Feed of latest changes"><span id="rssi"></span></a></p>
435
436<%
437
438    }
439}
440/* ---------------------- history.jsp end --------------------- */
441%><%@
442
443include file="foot.jspf"
444
445%>
446