Sat, 22 Jul 2023 22:32:04 +0200
add full support for commit references - fixes #276
1.1 --- a/setup/postgres/psql_create_tables.sql Sat Jul 22 15:07:23 2023 +0200 1.2 +++ b/setup/postgres/psql_create_tables.sql Sat Jul 22 22:32:04 2023 +0200 1.3 @@ -1,6 +1,3 @@ 1.4 --- This script creates the module management tables 1.5 --- 1.6 - 1.7 create table lpit_user 1.8 ( 1.9 userid serial primary key, 1.10 @@ -10,6 +7,8 @@ 1.11 givenname text 1.12 ); 1.13 1.14 +create type vcstype as enum ('None', 'Mercurial', 'Git'); 1.15 + 1.16 create table lpit_project 1.17 ( 1.18 projectid serial primary key, 1.19 @@ -18,6 +17,7 @@ 1.20 ordinal integer not null default 0, 1.21 description text, 1.22 repoUrl text, 1.23 + vcs vcstype not null default 'None'::vcstype, 1.24 owner integer references lpit_user (userid) 1.25 ); 1.26 1.27 @@ -168,3 +168,12 @@ 1.28 ); 1.29 1.30 create unique index lpit_issue_relation_unique on lpit_issue_relation (from_issue, to_issue, type); 1.31 + 1.32 +create table lpit_commit_ref 1.33 +( 1.34 + issueid integer not null references lpit_issue (issueid) on delete cascade, 1.35 + commit_hash text not null, 1.36 + commit_brief text not null 1.37 +); 1.38 + 1.39 +create unique index lpit_commit_ref_unique on lpit_commit_ref (issueid, commit_hash);
2.1 --- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat Jul 22 15:07:23 2023 +0200 2.2 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat Jul 22 22:32:04 2023 +0200 2.3 @@ -173,6 +173,10 @@ 2.4 } 2.5 } 2.6 2.7 + val body: String by lazy { 2.8 + request.reader.lineSequence().joinToString("\n") 2.9 + } 2.10 + 2.11 private fun forward(jsp: String) { 2.12 request.getRequestDispatcher(jspPath(jsp)).forward(request, response) 2.13 }
3.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sat Jul 22 15:07:23 2023 +0200 3.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sat Jul 22 22:32:04 2023 +0200 3.3 @@ -26,6 +26,7 @@ 3.4 package de.uapcore.lightpit.dao 3.5 3.6 import de.uapcore.lightpit.entities.* 3.7 +import de.uapcore.lightpit.types.CommitRef 3.8 import de.uapcore.lightpit.viewmodel.ComponentSummary 3.9 import de.uapcore.lightpit.viewmodel.IssueSummary 3.10 import de.uapcore.lightpit.viewmodel.VersionSummary 3.11 @@ -70,6 +71,8 @@ 3.12 fun collectIssueSummary(project: Project): IssueSummary 3.13 fun collectIssueSummary(assignee: User): IssueSummary 3.14 3.15 + fun mergeCommitRefs(refs: List<CommitRef>) 3.16 + 3.17 fun listIssues(project: Project, includeDone: Boolean): List<Issue> 3.18 fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue> 3.19 fun findIssue(id: Int): Issue? 3.20 @@ -101,4 +104,5 @@ 3.21 * Lists the issue comment history of the project with [projectId] for the past [days]. 3.22 */ 3.23 fun listIssueCommentHistory(projectId: Int, days: Int): List<IssueCommentHistoryEntry> 3.24 + fun listCommitRefs(issue: Issue): List<CommitRef> 3.25 }
4.1 --- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sat Jul 22 15:07:23 2023 +0200 4.2 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sat Jul 22 22:32:04 2023 +0200 4.3 @@ -26,6 +26,7 @@ 4.4 package de.uapcore.lightpit.dao 4.5 4.6 import de.uapcore.lightpit.entities.* 4.7 +import de.uapcore.lightpit.types.CommitRef 4.8 import de.uapcore.lightpit.types.IssueHistoryType 4.9 import de.uapcore.lightpit.types.RelationType 4.10 import de.uapcore.lightpit.types.WebColor 4.11 @@ -358,7 +359,7 @@ 4.12 //language=SQL 4.13 private val projectQuery = 4.14 """ 4.15 - select projectid, name, node, ordinal, description, repourl, 4.16 + select projectid, name, node, ordinal, description, vcs, repourl, 4.17 userid, username, lastname, givenname, mail 4.18 from lpit_project 4.19 left join lpit_user owner on lpit_project.owner = owner.userid 4.20 @@ -370,6 +371,7 @@ 4.21 node = getString("node") 4.22 ordinal = getInt("ordinal") 4.23 description = getString("description") 4.24 + vcs = getEnum("vcs") 4.25 repoUrl = getString("repourl") 4.26 owner = extractOptionalUser() 4.27 } 4.28 @@ -381,6 +383,7 @@ 4.29 setStringSafe(i++, node) 4.30 setInt(i++, ordinal) 4.31 setStringOrNull(i++, description) 4.32 + setEnum(i++, vcs) 4.33 setStringOrNull(i++, repoUrl) 4.34 setIntOrNull(i++, owner?.id) 4.35 } 4.36 @@ -405,14 +408,14 @@ 4.37 } 4.38 4.39 override fun insertProject(project: Project) { 4.40 - withStatement("insert into lpit_project (name, node, ordinal, description, repourl, owner) values (?, ?, ?, ?, ?, ?)") { 4.41 + withStatement("insert into lpit_project (name, node, ordinal, description, vcs, repourl, owner) values (?, ?, ?, ?, ?::vcstype, ?, ?)") { 4.42 setProject(1, project) 4.43 executeUpdate() 4.44 } 4.45 } 4.46 4.47 override fun updateProject(project: Project) { 4.48 - withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, repourl = ?, owner = ? where projectid = ?") { 4.49 + withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, vcs = ?::vcstype, repourl = ?, owner = ? where projectid = ?") { 4.50 val col = setProject(1, project) 4.51 setInt(col, project.id) 4.52 executeUpdate() 4.53 @@ -471,6 +474,17 @@ 4.54 } 4.55 } 4.56 4.57 + override fun mergeCommitRefs(refs: List<CommitRef>) { 4.58 + withStatement("insert into lpit_commit_ref (issueid, commit_hash, commit_brief) values (?,?,?) on conflict do nothing") { 4.59 + refs.forEach { ref -> 4.60 + setInt(1, ref.issueId) 4.61 + setString(2, ref.hash) 4.62 + setString(3, ref.message) 4.63 + executeUpdate() 4.64 + } 4.65 + } 4.66 + } 4.67 + 4.68 //</editor-fold> 4.69 4.70 //<editor-fold desc="Issue"> 4.71 @@ -636,6 +650,18 @@ 4.72 } 4.73 } 4.74 4.75 + override fun listCommitRefs(issue: Issue): List<CommitRef> = 4.76 + withStatement("select commit_hash, commit_brief from lpit_commit_ref where issueid = ?") { 4.77 + setInt(1, issue.id) 4.78 + queryAll { 4.79 + CommitRef( 4.80 + issueId = issue.id, 4.81 + hash = it.getString("commit_hash"), 4.82 + message = it.getString("commit_brief") 4.83 + ) 4.84 + } 4.85 + } 4.86 + 4.87 //</editor-fold> 4.88 4.89 //<editor-fold desc="Issue Relations">
5.1 --- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sat Jul 22 15:07:23 2023 +0200 5.2 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sat Jul 22 22:32:04 2023 +0200 5.3 @@ -25,11 +25,14 @@ 5.4 5.5 package de.uapcore.lightpit.entities 5.6 5.7 +import de.uapcore.lightpit.types.VcsType 5.8 + 5.9 data class Project(override val id: Int) : Entity, HasNode { 5.10 var name: String = "" 5.11 override var node: String = name 5.12 var ordinal = 0 5.13 var description: String? = null 5.14 + var vcs: VcsType = VcsType.None 5.15 var repoUrl: String? = null 5.16 var owner: User? = null 5.17 } 5.18 \ No newline at end of file
6.1 --- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Sat Jul 22 15:07:23 2023 +0200 6.2 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Sat Jul 22 22:32:04 2023 +0200 6.3 @@ -46,6 +46,7 @@ 6.4 get("/%project/edit", this::projectForm) 6.5 get("/-/create", this::projectForm) 6.6 post("/-/commit", this::projectCommit) 6.7 + post("/%project/vcs/analyze", this::vcsAnalyze) 6.8 6.9 get("/%project/versions/", this::versions) 6.10 get("/%project/versions/%version/edit", this::versionForm) 6.11 @@ -243,6 +244,7 @@ 6.12 description = http.param("description") ?: "" 6.13 ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 6.14 repoUrl = http.param("repoUrl") ?: "" 6.15 + vcs = VcsType.valueOf(http.param("vcs") ?: "None") 6.16 owner = (http.param("owner")?.toIntOrNull() ?: -1).let { 6.17 if (it < 0) null else dao.findUser(it) 6.18 } 6.19 @@ -261,6 +263,26 @@ 6.20 http.renderCommit("projects/${project.node}") 6.21 } 6.22 6.23 + private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) { 6.24 + val projectInfo = obtainProjectInfo(http, dao) 6.25 + if (projectInfo == null) { 6.26 + http.response.sendError(404) 6.27 + return 6.28 + } 6.29 + 6.30 + // if analysis is not configured, reject the request 6.31 + if (projectInfo.project.vcs == VcsType.None) { 6.32 + http.response.sendError(404) 6.33 + return 6.34 + } 6.35 + 6.36 + // obtain the list of issues for this project to filter cross-project references 6.37 + val knownIds = dao.listIssues(projectInfo.project, true).map { it.id } 6.38 + 6.39 + // read the provided commit log and merge only the refs that relate issues from the current project 6.40 + dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) 6.41 + } 6.42 + 6.43 private fun versions(http: HttpRequest, dao: DataAccessObject) { 6.44 val projectInfo = obtainProjectInfo(http, dao) 6.45 if (projectInfo == null) { 6.46 @@ -475,7 +497,8 @@ 6.47 component, 6.48 dao.listIssues(project, true), 6.49 dao.listIssueRelations(issue), 6.50 - relationError 6.51 + relationError, 6.52 + dao.listCommitRefs(issue) 6.53 ) 6.54 feedPath = feedPath(projectInfo.project) 6.55 navigationMenu = activeProjectNavMenu(
7.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 7.2 +++ b/src/main/kotlin/de/uapcore/lightpit/types/VcsType.kt Sat Jul 22 22:32:04 2023 +0200 7.3 @@ -0,0 +1,29 @@ 7.4 +/* 7.5 + * Copyright 2023 Mike Becker. All rights reserved. 7.6 + * 7.7 + * Redistribution and use in source and binary forms, with or without 7.8 + * modification, are permitted provided that the following conditions are met: 7.9 + * 7.10 + * 1. Redistributions of source code must retain the above copyright 7.11 + * notice, this list of conditions and the following disclaimer. 7.12 + * 7.13 + * 2. Redistributions in binary form must reproduce the above copyright 7.14 + * notice, this list of conditions and the following disclaimer in the 7.15 + * documentation and/or other materials provided with the distribution. 7.16 + * 7.17 + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 7.18 + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 7.19 + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 7.20 + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 7.21 + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 7.22 + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 7.23 + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 7.24 + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 7.25 + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 7.26 + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 7.27 + * 7.28 + */ 7.29 + 7.30 +package de.uapcore.lightpit.types 7.31 + 7.32 +enum class VcsType {None, Mercurial, Git} 7.33 \ No newline at end of file
8.1 --- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Sat Jul 22 15:07:23 2023 +0200 8.2 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Sat Jul 22 22:32:04 2023 +0200 8.3 @@ -99,6 +99,8 @@ 8.4 } 8.5 } 8.6 8.7 +data class CommitLink(val url: String, val hash: String, val message: String) 8.8 + 8.9 class IssueDetailView( 8.10 val issue: Issue, 8.11 val comments: List<IssueComment>, 8.12 @@ -110,10 +112,12 @@ 8.13 /** 8.14 * Optional resource key to an error message for the relation editor. 8.15 */ 8.16 - val relationError: String? 8.17 + val relationError: String?, 8.18 + commitRefs: List<CommitRef> 8.19 ) : View() { 8.20 val relationTypes = RelationType.values() 8.21 val linkableIssues = projectIssues.filterNot { it.id == issue.id } 8.22 + val commitLinks: List<CommitLink> 8.23 8.24 private val parser: Parser 8.25 private val renderer: HtmlRenderer 8.26 @@ -131,8 +135,24 @@ 8.27 for (comment in comments) { 8.28 comment.commentFormatted = formatMarkdown(comment.comment) 8.29 } 8.30 + 8.31 + val commitBaseUrl = project.repoUrl 8.32 + commitLinks = (if (commitBaseUrl == null || project.vcs == VcsType.None) emptyList() else commitRefs.map { 8.33 + CommitLink(buildCommitUrl(commitBaseUrl, project.vcs, it.hash), it.hash, it.message) 8.34 + }) 8.35 } 8.36 8.37 + private fun buildCommitUrl(baseUrl: String, vcs: VcsType, hash: String): String = 8.38 + with (StringBuilder(baseUrl)) { 8.39 + if (!endsWith("/")) append('/') 8.40 + when (vcs) { 8.41 + VcsType.Mercurial -> append("rev/") 8.42 + else -> append("commit/") 8.43 + } 8.44 + append(hash) 8.45 + toString() 8.46 + } 8.47 + 8.48 private fun formatEmojis(text: String) = text 8.49 .replace("(/)", "✅") 8.50 .replace("(x)", "❌")
9.1 --- a/src/main/resources/localization/strings.properties Sat Jul 22 15:07:23 2023 +0200 9.2 +++ b/src/main/resources/localization/strings.properties Sat Jul 22 22:32:04 2023 +0200 9.3 @@ -83,6 +83,9 @@ 9.4 issue.comments.lastupdate=Last edited: 9.5 issue.comments.updateCount=total edits 9.6 issue.comments=Comments 9.7 +issue.commits.hash=Hash 9.8 +issue.commits.message=Brief 9.9 +issue.commits=Commits 9.10 issue.created=Created 9.11 issue.description=Description 9.12 issue.eta=ETA 9.13 @@ -156,6 +159,8 @@ 9.14 project.name=Name 9.15 project.owner=Project Lead 9.16 project.repoUrl=Repository 9.17 +project.vcs=Version Control 9.18 +project.vcs.none=Do not analyze repository 9.19 project=Project 9.20 user.displayname=Developer 9.21 user.givenname=Given Name
10.1 --- a/src/main/resources/localization/strings_de.properties Sat Jul 22 15:07:23 2023 +0200 10.2 +++ b/src/main/resources/localization/strings_de.properties Sat Jul 22 22:32:04 2023 +0200 10.3 @@ -83,6 +83,9 @@ 10.4 issue.comments.lastupdate=Zuletzt bearbeitet: 10.5 issue.comments.updateCount=mal bearbeitet 10.6 issue.comments=Kommentare 10.7 +issue.commits.hash=Hash 10.8 +issue.commits.message=Zusammenfassung 10.9 +issue.commits=Commits 10.10 issue.created=Erstellt 10.11 issue.description=Beschreibung 10.12 issue.eta=Zieldatum 10.13 @@ -156,6 +159,8 @@ 10.14 project.name=Name 10.15 project.owner=Projektleitung 10.16 project.repoUrl=Repository 10.17 +project.vcs=Versionskontrolle 10.18 +project.vcs.none=Keine Analyse durchf\u00fchren 10.19 project=Projekt 10.20 user.displayname=Entwickler 10.21 user.givenname=Vorname
11.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Sat Jul 22 15:07:23 2023 +0200 11.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Sat Jul 22 22:32:04 2023 +0200 11.3 @@ -24,6 +24,12 @@ 11.4 --%> 11.5 <%@ page contentType="text/html;charset=UTF-8" %> 11.6 11.7 +<h3>Version 1.1.0</h3> 11.8 + 11.9 +<ul> 11.10 + <li>Integration von Commit-Logs für Mercurial und Git hinzugefügt.</li> 11.11 +</ul> 11.12 + 11.13 <h3>Version 1.0.1</h3> 11.14 11.15 <ul>
12.1 --- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Sat Jul 22 15:07:23 2023 +0200 12.2 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Sat Jul 22 22:32:04 2023 +0200 12.3 @@ -24,6 +24,12 @@ 12.4 --%> 12.5 <%@ page contentType="text/html;charset=UTF-8" %> 12.6 12.7 +<h3>Version 1.1.0</h3> 12.8 + 12.9 +<ul> 12.10 + <li>Add integration of mercurial and git commit logs.</li> 12.11 +</ul> 12.12 + 12.13 <h3>Version 1.0.1</h3> 12.14 12.15 <ul>
13.1 --- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Sat Jul 22 15:07:23 2023 +0200 13.2 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Sat Jul 22 22:32:04 2023 +0200 13.3 @@ -156,6 +156,31 @@ 13.4 </div> 13.5 13.6 <hr class="issue-view-separator"/> 13.7 + 13.8 +<c:if test="${not empty viewmodel.commitLinks}"> 13.9 + <h2><fmt:message key="issue.commits" /></h2> 13.10 + <table class="issue-view fullwidth"> 13.11 + <colgroup> 13.12 + <col> 13.13 + <col class="fullwidth"> 13.14 + </colgroup> 13.15 + <thead> 13.16 + <tr> 13.17 + <th><fmt:message key="issue.commits.hash"/></th> 13.18 + <th><fmt:message key="issue.commits.message"/></th> 13.19 + </tr> 13.20 + </thead> 13.21 + <tbody> 13.22 + <c:forEach var="commitLink" items="${viewmodel.commitLinks}"> 13.23 + <tr> 13.24 + <td><a href="${commitLink.url}" target="_blank">${commitLink.hash}</a></td> 13.25 + <td><c:out value="${commitLink.message}"/> </td> 13.26 + </tr> 13.27 + </c:forEach> 13.28 + </tbody> 13.29 + </table> 13.30 +</c:if> 13.31 + 13.32 <h2> 13.33 <fmt:message key="issue.relations"/> 13.34 </h2>
14.1 --- a/src/main/webapp/WEB-INF/jsp/project-form.jsp Sat Jul 22 15:07:23 2023 +0200 14.2 +++ b/src/main/webapp/WEB-INF/jsp/project-form.jsp Sat Jul 22 22:32:04 2023 +0200 14.3 @@ -55,6 +55,17 @@ 14.4 <td><input name="repoUrl" type="url" maxlength="50" value="<c:out value="${project.repoUrl}"/>" /></td> 14.5 </tr> 14.6 <tr> 14.7 + <th><fmt:message key="project.vcs"/></th> 14.8 + <td> 14.9 + <select name="vcs"> 14.10 + <option value="None"><fmt:message key="project.vcs.none"/></option> 14.11 + <c:forTokens var="vcs" items="Mercurial,Git" delims=","> 14.12 + <option <c:if test="${project.vcs eq vcs}">selected</c:if> value="${vcs}">${vcs}</option> 14.13 + </c:forTokens> 14.14 + </select> 14.15 + </td> 14.16 + </tr> 14.17 + <tr> 14.18 <th><fmt:message key="project.owner"/></th> 14.19 <td> 14.20 <select name="owner">