Sat, 22 Jul 2023 22:32:04 +0200
add full support for commit references - fixes #276
--- a/setup/postgres/psql_create_tables.sql Sat Jul 22 15:07:23 2023 +0200 +++ b/setup/postgres/psql_create_tables.sql Sat Jul 22 22:32:04 2023 +0200 @@ -1,6 +1,3 @@ --- This script creates the module management tables --- - create table lpit_user ( userid serial primary key, @@ -10,6 +7,8 @@ givenname text ); +create type vcstype as enum ('None', 'Mercurial', 'Git'); + create table lpit_project ( projectid serial primary key, @@ -18,6 +17,7 @@ ordinal integer not null default 0, description text, repoUrl text, + vcs vcstype not null default 'None'::vcstype, owner integer references lpit_user (userid) ); @@ -168,3 +168,12 @@ ); create unique index lpit_issue_relation_unique on lpit_issue_relation (from_issue, to_issue, type); + +create table lpit_commit_ref +( + issueid integer not null references lpit_issue (issueid) on delete cascade, + commit_hash text not null, + commit_brief text not null +); + +create unique index lpit_commit_ref_unique on lpit_commit_ref (issueid, commit_hash);
--- a/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt Sat Jul 22 22:32:04 2023 +0200 @@ -173,6 +173,10 @@ } } + val body: String by lazy { + request.reader.lineSequence().joinToString("\n") + } + private fun forward(jsp: String) { request.getRequestDispatcher(jspPath(jsp)).forward(request, response) }
--- a/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt Sat Jul 22 22:32:04 2023 +0200 @@ -26,6 +26,7 @@ package de.uapcore.lightpit.dao import de.uapcore.lightpit.entities.* +import de.uapcore.lightpit.types.CommitRef import de.uapcore.lightpit.viewmodel.ComponentSummary import de.uapcore.lightpit.viewmodel.IssueSummary import de.uapcore.lightpit.viewmodel.VersionSummary @@ -70,6 +71,8 @@ fun collectIssueSummary(project: Project): IssueSummary fun collectIssueSummary(assignee: User): IssueSummary + fun mergeCommitRefs(refs: List<CommitRef>) + fun listIssues(project: Project, includeDone: Boolean): List<Issue> fun listIssues(project: Project, includeDone: Boolean, version: Version?, component: Component?): List<Issue> fun findIssue(id: Int): Issue? @@ -101,4 +104,5 @@ * Lists the issue comment history of the project with [projectId] for the past [days]. */ fun listIssueCommentHistory(projectId: Int, days: Int): List<IssueCommentHistoryEntry> + fun listCommitRefs(issue: Issue): List<CommitRef> }
--- a/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt Sat Jul 22 22:32:04 2023 +0200 @@ -26,6 +26,7 @@ package de.uapcore.lightpit.dao import de.uapcore.lightpit.entities.* +import de.uapcore.lightpit.types.CommitRef import de.uapcore.lightpit.types.IssueHistoryType import de.uapcore.lightpit.types.RelationType import de.uapcore.lightpit.types.WebColor @@ -358,7 +359,7 @@ //language=SQL private val projectQuery = """ - select projectid, name, node, ordinal, description, repourl, + select projectid, name, node, ordinal, description, vcs, repourl, userid, username, lastname, givenname, mail from lpit_project left join lpit_user owner on lpit_project.owner = owner.userid @@ -370,6 +371,7 @@ node = getString("node") ordinal = getInt("ordinal") description = getString("description") + vcs = getEnum("vcs") repoUrl = getString("repourl") owner = extractOptionalUser() } @@ -381,6 +383,7 @@ setStringSafe(i++, node) setInt(i++, ordinal) setStringOrNull(i++, description) + setEnum(i++, vcs) setStringOrNull(i++, repoUrl) setIntOrNull(i++, owner?.id) } @@ -405,14 +408,14 @@ } override fun insertProject(project: Project) { - withStatement("insert into lpit_project (name, node, ordinal, description, repourl, owner) values (?, ?, ?, ?, ?, ?)") { + withStatement("insert into lpit_project (name, node, ordinal, description, vcs, repourl, owner) values (?, ?, ?, ?, ?::vcstype, ?, ?)") { setProject(1, project) executeUpdate() } } override fun updateProject(project: Project) { - withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, repourl = ?, owner = ? where projectid = ?") { + withStatement("update lpit_project set name = ?, node = ?, ordinal = ?, description = ?, vcs = ?::vcstype, repourl = ?, owner = ? where projectid = ?") { val col = setProject(1, project) setInt(col, project.id) executeUpdate() @@ -471,6 +474,17 @@ } } + override fun mergeCommitRefs(refs: List<CommitRef>) { + withStatement("insert into lpit_commit_ref (issueid, commit_hash, commit_brief) values (?,?,?) on conflict do nothing") { + refs.forEach { ref -> + setInt(1, ref.issueId) + setString(2, ref.hash) + setString(3, ref.message) + executeUpdate() + } + } + } + //</editor-fold> //<editor-fold desc="Issue"> @@ -636,6 +650,18 @@ } } + override fun listCommitRefs(issue: Issue): List<CommitRef> = + withStatement("select commit_hash, commit_brief from lpit_commit_ref where issueid = ?") { + setInt(1, issue.id) + queryAll { + CommitRef( + issueId = issue.id, + hash = it.getString("commit_hash"), + message = it.getString("commit_brief") + ) + } + } + //</editor-fold> //<editor-fold desc="Issue Relations">
--- a/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/entities/Project.kt Sat Jul 22 22:32:04 2023 +0200 @@ -25,11 +25,14 @@ package de.uapcore.lightpit.entities +import de.uapcore.lightpit.types.VcsType + data class Project(override val id: Int) : Entity, HasNode { var name: String = "" override var node: String = name var ordinal = 0 var description: String? = null + var vcs: VcsType = VcsType.None var repoUrl: String? = null var owner: User? = null } \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt Sat Jul 22 22:32:04 2023 +0200 @@ -46,6 +46,7 @@ get("/%project/edit", this::projectForm) get("/-/create", this::projectForm) post("/-/commit", this::projectCommit) + post("/%project/vcs/analyze", this::vcsAnalyze) get("/%project/versions/", this::versions) get("/%project/versions/%version/edit", this::versionForm) @@ -243,6 +244,7 @@ description = http.param("description") ?: "" ordinal = http.param("ordinal")?.toIntOrNull() ?: 0 repoUrl = http.param("repoUrl") ?: "" + vcs = VcsType.valueOf(http.param("vcs") ?: "None") owner = (http.param("owner")?.toIntOrNull() ?: -1).let { if (it < 0) null else dao.findUser(it) } @@ -261,6 +263,26 @@ http.renderCommit("projects/${project.node}") } + private fun vcsAnalyze(http: HttpRequest, dao: DataAccessObject) { + val projectInfo = obtainProjectInfo(http, dao) + if (projectInfo == null) { + http.response.sendError(404) + return + } + + // if analysis is not configured, reject the request + if (projectInfo.project.vcs == VcsType.None) { + http.response.sendError(404) + return + } + + // obtain the list of issues for this project to filter cross-project references + val knownIds = dao.listIssues(projectInfo.project, true).map { it.id } + + // read the provided commit log and merge only the refs that relate issues from the current project + dao.mergeCommitRefs(parseCommitRefs(http.body).filter { knownIds.contains(it.issueId) }) + } + private fun versions(http: HttpRequest, dao: DataAccessObject) { val projectInfo = obtainProjectInfo(http, dao) if (projectInfo == null) { @@ -475,7 +497,8 @@ component, dao.listIssues(project, true), dao.listIssueRelations(issue), - relationError + relationError, + dao.listCommitRefs(issue) ) feedPath = feedPath(projectInfo.project) navigationMenu = activeProjectNavMenu(
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main/kotlin/de/uapcore/lightpit/types/VcsType.kt Sat Jul 22 22:32:04 2023 +0200 @@ -0,0 +1,29 @@ +/* + * Copyright 2023 Mike Becker. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +package de.uapcore.lightpit.types + +enum class VcsType {None, Mercurial, Git} \ No newline at end of file
--- a/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt Sat Jul 22 22:32:04 2023 +0200 @@ -99,6 +99,8 @@ } } +data class CommitLink(val url: String, val hash: String, val message: String) + class IssueDetailView( val issue: Issue, val comments: List<IssueComment>, @@ -110,10 +112,12 @@ /** * Optional resource key to an error message for the relation editor. */ - val relationError: String? + val relationError: String?, + commitRefs: List<CommitRef> ) : View() { val relationTypes = RelationType.values() val linkableIssues = projectIssues.filterNot { it.id == issue.id } + val commitLinks: List<CommitLink> private val parser: Parser private val renderer: HtmlRenderer @@ -131,8 +135,24 @@ for (comment in comments) { comment.commentFormatted = formatMarkdown(comment.comment) } + + val commitBaseUrl = project.repoUrl + commitLinks = (if (commitBaseUrl == null || project.vcs == VcsType.None) emptyList() else commitRefs.map { + CommitLink(buildCommitUrl(commitBaseUrl, project.vcs, it.hash), it.hash, it.message) + }) } + private fun buildCommitUrl(baseUrl: String, vcs: VcsType, hash: String): String = + with (StringBuilder(baseUrl)) { + if (!endsWith("/")) append('/') + when (vcs) { + VcsType.Mercurial -> append("rev/") + else -> append("commit/") + } + append(hash) + toString() + } + private fun formatEmojis(text: String) = text .replace("(/)", "✅") .replace("(x)", "❌")
--- a/src/main/resources/localization/strings.properties Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/resources/localization/strings.properties Sat Jul 22 22:32:04 2023 +0200 @@ -83,6 +83,9 @@ issue.comments.lastupdate=Last edited: issue.comments.updateCount=total edits issue.comments=Comments +issue.commits.hash=Hash +issue.commits.message=Brief +issue.commits=Commits issue.created=Created issue.description=Description issue.eta=ETA @@ -156,6 +159,8 @@ project.name=Name project.owner=Project Lead project.repoUrl=Repository +project.vcs=Version Control +project.vcs.none=Do not analyze repository project=Project user.displayname=Developer user.givenname=Given Name
--- a/src/main/resources/localization/strings_de.properties Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/resources/localization/strings_de.properties Sat Jul 22 22:32:04 2023 +0200 @@ -83,6 +83,9 @@ issue.comments.lastupdate=Zuletzt bearbeitet: issue.comments.updateCount=mal bearbeitet issue.comments=Kommentare +issue.commits.hash=Hash +issue.commits.message=Zusammenfassung +issue.commits=Commits issue.created=Erstellt issue.description=Beschreibung issue.eta=Zieldatum @@ -156,6 +159,8 @@ project.name=Name project.owner=Projektleitung project.repoUrl=Repository +project.vcs=Versionskontrolle +project.vcs.none=Keine Analyse durchf\u00fchren project=Projekt user.displayname=Entwickler user.givenname=Vorname
--- a/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog-de.jspf Sat Jul 22 22:32:04 2023 +0200 @@ -24,6 +24,12 @@ --%> <%@ page contentType="text/html;charset=UTF-8" %> +<h3>Version 1.1.0</h3> + +<ul> + <li>Integration von Commit-Logs für Mercurial und Git hinzugefügt.</li> +</ul> + <h3>Version 1.0.1</h3> <ul>
--- a/src/main/webapp/WEB-INF/changelogs/changelog.jspf Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/webapp/WEB-INF/changelogs/changelog.jspf Sat Jul 22 22:32:04 2023 +0200 @@ -24,6 +24,12 @@ --%> <%@ page contentType="text/html;charset=UTF-8" %> +<h3>Version 1.1.0</h3> + +<ul> + <li>Add integration of mercurial and git commit logs.</li> +</ul> + <h3>Version 1.0.1</h3> <ul>
--- a/src/main/webapp/WEB-INF/jsp/issue-view.jsp Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/webapp/WEB-INF/jsp/issue-view.jsp Sat Jul 22 22:32:04 2023 +0200 @@ -156,6 +156,31 @@ </div> <hr class="issue-view-separator"/> + +<c:if test="${not empty viewmodel.commitLinks}"> + <h2><fmt:message key="issue.commits" /></h2> + <table class="issue-view fullwidth"> + <colgroup> + <col> + <col class="fullwidth"> + </colgroup> + <thead> + <tr> + <th><fmt:message key="issue.commits.hash"/></th> + <th><fmt:message key="issue.commits.message"/></th> + </tr> + </thead> + <tbody> + <c:forEach var="commitLink" items="${viewmodel.commitLinks}"> + <tr> + <td><a href="${commitLink.url}" target="_blank">${commitLink.hash}</a></td> + <td><c:out value="${commitLink.message}"/> </td> + </tr> + </c:forEach> + </tbody> + </table> +</c:if> + <h2> <fmt:message key="issue.relations"/> </h2>
--- a/src/main/webapp/WEB-INF/jsp/project-form.jsp Sat Jul 22 15:07:23 2023 +0200 +++ b/src/main/webapp/WEB-INF/jsp/project-form.jsp Sat Jul 22 22:32:04 2023 +0200 @@ -55,6 +55,17 @@ <td><input name="repoUrl" type="url" maxlength="50" value="<c:out value="${project.repoUrl}"/>" /></td> </tr> <tr> + <th><fmt:message key="project.vcs"/></th> + <td> + <select name="vcs"> + <option value="None"><fmt:message key="project.vcs.none"/></option> + <c:forTokens var="vcs" items="Mercurial,Git" delims=","> + <option <c:if test="${project.vcs eq vcs}">selected</c:if> value="${vcs}">${vcs}</option> + </c:forTokens> + </select> + </td> + </tr> + <tr> <th><fmt:message key="project.owner"/></th> <td> <select name="owner">