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