add full support for commit references - fixes #276

Sat, 22 Jul 2023 22:32:04 +0200

author
Mike Becker <universe@uap-core.de>
date
Sat, 22 Jul 2023 22:32:04 +0200
changeset 284
671c1c8fbf1c
parent 283
ea6181255423
child 285
8da71efbaa35

add full support for commit references - fixes #276

setup/postgres/psql_create_tables.sql file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/RequestMapping.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/DataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/dao/PostgresDataAccessObject.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/entities/Project.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/servlet/ProjectServlet.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/types/VcsType.kt file | annotate | diff | comparison | revisions
src/main/kotlin/de/uapcore/lightpit/viewmodel/Issues.kt file | annotate | diff | comparison | revisions
src/main/resources/localization/strings.properties file | annotate | diff | comparison | revisions
src/main/resources/localization/strings_de.properties file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog-de.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/changelogs/changelog.jspf file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/issue-view.jsp file | annotate | diff | comparison | revisions
src/main/webapp/WEB-INF/jsp/project-form.jsp file | annotate | diff | comparison | revisions
--- 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("(/)", "&#9989;")
         .replace("(x)", "&#10060;")
--- 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">

mercurial