gitshark

Clone repository

git clone https://gitshark.de/git/workaround/Gitshark.git
git clone git@gitshark.de:workaround/Gitshark.git

← Commits

๐Ÿ’„ (merge-requests): Delete a comment via a hover icon instead of a text button

9c4c336ffaad79205ee6f1c38bd0c31d7b1b3514 ยท Phillip Souza Furtner ยท 2026-07-08T11:25:23Z

Changes

4 files changed, +152 -12

MODIFY src/main/java/de/workaround/dev/DevDataSeeder.java +79 -0
diff --git a/src/main/java/de/workaround/dev/DevDataSeeder.java b/src/main/java/de/workaround/dev/DevDataSeeder.java
index 760192c..a84073d 100644
--- a/src/main/java/de/workaround/dev/DevDataSeeder.java
+++ b/src/main/java/de/workaround/dev/DevDataSeeder.java
@@ -8,6 +8,9 @@
8 8 import org.jboss.logging.Logger;
9 9
10 10 import de.workaround.git.GitRepositoryService;
11 +import de.workaround.git.MergeRequestCommentService;
12 +import de.workaround.git.MergeRequestService;
13 +import de.workaround.model.MergeRequest;
11 14 import de.workaround.model.Repository;
12 15 import de.workaround.model.User;
13 16 import io.quarkus.runtime.StartupEvent;
@@ -29,6 +32,7 @@
29 32
30 33 private static final String DEMO_USER = "alice";
31 34 private static final String DEMO_REPO = "demo";
35 + private static final String DEMO_BRANCH = "feature";
32 36
33 37 @Inject
34 38 GitRepositoryService repositories;
@@ -36,6 +40,15 @@
36 40 @Inject
37 41 User.Repo users;
38 42
43 + @Inject
44 + MergeRequestService mergeRequests;
45 +
46 + @Inject
47 + MergeRequestCommentService mergeRequestCommentService;
48 +
49 + @Inject
50 + MergeRequest.Repo mergeRequestRepo;
51 +
39 52 @ConfigProperty(name = "gitshark.dev.seed-data", defaultValue = "false")
40 53 boolean enabled;
41 54
@@ -77,6 +90,31 @@
77 90 seedInitialCommit(bare);
78 91 LOG.infof("Seeded demo data: %s/%s with an initial commit", alice.username, demo.name);
79 92 }
93 +
94 + seedMergeRequest(alice, demo, bare);
95 + }
96 +
97 + /**
98 + * Ensures a demo merge request exists so the merge-request UI has something to show locally. Idempotent: the
99 + * {@value #DEMO_BRANCH} branch is pushed only when absent, and the merge request (plus a sample line comment) is
100 + * created only when the repository has none yet.
101 + */
102 + private void seedMergeRequest(User alice, Repository demo, Path bare)
103 + {
104 + if (!mergeRequestRepo.findByRepository(demo).isEmpty())
105 + {
106 + return;
107 + }
108 + if (!hasBranch(bare, DEMO_BRANCH))
109 + {
110 + seedFeatureBranch(bare);
111 + }
112 + MergeRequest mr = mergeRequests.create(alice, demo, "Add an overview section",
113 + "Adds a short overview file on the " + DEMO_BRANCH + " branch. Try reviewing the diff and merging it.",
114 + DEMO_BRANCH, "main");
115 + // anchor a sample comment to the first added line of the new file, so the comment UI is demoable too
116 + mergeRequestCommentService.add(alice, mr, "OVERVIEW.md", -1, 1, "Could we expand this a little?");
117 + LOG.infof("Seeded demo merge request !%d on %s/%s", mr.number, alice.username, demo.name);
80 118 }
81 119
82 120 private static boolean hasNoCommits(Path barePath)
@@ -91,6 +129,47 @@
91 129 }
92 130 }
93 131
132 + private static boolean hasBranch(Path barePath, String branch)
133 + {
134 + try (Git git = Git.open(barePath.toFile()))
135 + {
136 + return git.getRepository().resolve("refs/heads/" + branch) != null;
137 + }
138 + catch (Exception e)
139 + {
140 + throw new IllegalStateException("Failed to inspect demo repository at " + barePath, e);
141 + }
142 + }
143 +
144 + /** Branches off main, adds OVERVIEW.md and pushes the {@value #DEMO_BRANCH} branch, leaving main untouched. */
145 + private static void seedFeatureBranch(Path barePath)
146 + {
147 + Path work = null;
148 + try
149 + {
150 + work = Files.createTempDirectory("git-shark-seed-branch");
151 + try (Git git = Git.cloneRepository().setURI(barePath.toUri().toString()).setDirectory(work.toFile()).call())
152 + {
153 + git.checkout().setCreateBranch(true).setName(DEMO_BRANCH).call();
154 + Files.writeString(work.resolve("OVERVIEW.md"),
155 + "# Overview\n\nThis file was added on the " + DEMO_BRANCH + " branch to demo merge requests.\n");
156 + git.add().addFilepattern(".").call();
157 + git.commit().setMessage("Add overview section").setSign(false)
158 + .setAuthor(DEMO_USER, DEMO_USER + "@demo.local")
159 + .setCommitter(DEMO_USER, DEMO_USER + "@demo.local").call();
160 + git.push().setRefSpecs(new RefSpec(DEMO_BRANCH + ":refs/heads/" + DEMO_BRANCH)).call();
161 + }
162 + }
163 + catch (Exception e)
164 + {
165 + throw new IllegalStateException("Failed to seed feature branch into " + barePath, e);
166 + }
167 + finally
168 + {
169 + deleteRecursively(work);
170 + }
171 + }
172 +
94 173 private static void seedInitialCommit(Path barePath)
95 174 {
96 175 Path work = null;
MODIFY src/main/resources/META-INF/resources/shark.css +31 -5
diff --git a/src/main/resources/META-INF/resources/shark.css b/src/main/resources/META-INF/resources/shark.css
index 10c8a6f..1542788 100644
--- a/src/main/resources/META-INF/resources/shark.css
+++ b/src/main/resources/META-INF/resources/shark.css
@@ -1064,6 +1064,8 @@
1064 1064 }
1065 1065
1066 1066 .dl-comment-row {
1067 + display: flex;
1068 + align-items: center;
1067 1069 padding: var(--s2) var(--s3) var(--s2) 96px;
1068 1070 background: var(--surface);
1069 1071 border-top: 1px dashed var(--border);
@@ -1071,6 +1073,7 @@
1071 1073 }
1072 1074
1073 1075 .comment {
1076 + flex: 1 1 auto;
1074 1077 max-width: 640px;
1075 1078 }
1076 1079
@@ -1086,17 +1089,40 @@
1086 1089 font-size: 13px;
1087 1090 }
1088 1091
1092 +/* the form box is elided so the button aligns like the diff-line comment icon (sticky to the right edge) */
1093 +.comment-del-form {
1094 + display: contents;
1095 +}
1096 +
1089 1097 .comment-del {
1090 - border: none;
1091 - background: none;
1098 + flex: 0 0 auto;
1099 + margin-left: auto;
1100 + position: sticky;
1101 + right: 6px;
1102 + margin-right: 6px;
1103 + display: inline-flex;
1104 + align-items: center;
1105 + justify-content: center;
1106 + width: 28px;
1107 + height: 24px;
1092 1108 padding: 0;
1109 + border: none;
1110 + border-radius: 5px;
1093 1111 cursor: pointer;
1094 - color: var(--danger);
1095 - font: 500 12px/1 var(--font);
1112 + color: var(--muted);
1113 + background: none;
1114 + opacity: 0;
1115 +}
1116 +
1117 +/* reveal the delete icon only while hovering/focusing the comment */
1118 +.dl-comment-row:hover .comment-del,
1119 +.dl-comment-row:focus-within .comment-del {
1120 + opacity: 1;
1096 1121 }
1097 1122
1098 1123 .comment-del:hover {
1099 - text-decoration: underline;
1124 + color: #fff;
1125 + background: var(--danger);
1100 1126 }
1101 1127
1102 1128 .comment-body {
MODIFY src/main/resources/templates/MergeRequestResource/mergeRequest.html +8 -7
diff --git a/src/main/resources/templates/MergeRequestResource/mergeRequest.html b/src/main/resources/templates/MergeRequestResource/mergeRequest.html
index 690a51a..c7d4a57 100644
--- a/src/main/resources/templates/MergeRequestResource/mergeRequest.html
+++ b/src/main/resources/templates/MergeRequestResource/mergeRequest.html
@@ -73,16 +73,17 @@
73 73 <div class="comment">
74 74 <div class="comment-head">
75 75 <span class="who">{c.author.username}</span>
76 - {#if owner || c.author.id == currentUserId}
77 - <form class="inline" method="post"
78 - action="/repos/{repo.owner.username}/{repo.name}/merge-requests/{mr.id}/comments/{c.id}/delete"
79 - onsubmit="return confirm('Delete this comment?')">
80 - <button type="submit" class="comment-del">Delete</button>
81 - </form>
82 - {/if}
83 76 </div>
84 77 <div class="comment-body">{c.body}</div>
85 78 </div>
79 + {#if owner || c.author.id == currentUserId}
80 + <form class="comment-del-form" method="post"
81 + action="/repos/{repo.owner.username}/{repo.name}/merge-requests/{mr.id}/comments/{c.id}/delete">
82 + <button type="submit" class="comment-del" title="Delete comment" aria-label="Delete comment">
83 + <svg viewBox="0 0 24 24" width="17" height="17" aria-hidden="true"><path fill="currentColor" d="M9 3h6l1 2h4v2H4V5h4l1-2Zm-3 6h12l-1 11a2 2 0 0 1-2 2H9a2 2 0 0 1-2-2L6 9Z"/></svg>
84 + </button>
85 + </form>
86 + {/if}
86 87 </div>
87 88 {/for}
88 89 {/for}
MODIFY src/test/java/de/workaround/dev/DevDataSeederTest.java +34 -0
diff --git a/src/test/java/de/workaround/dev/DevDataSeederTest.java b/src/test/java/de/workaround/dev/DevDataSeederTest.java
index a1434e7..6e6cfe8 100644
--- a/src/test/java/de/workaround/dev/DevDataSeederTest.java
+++ b/src/test/java/de/workaround/dev/DevDataSeederTest.java
@@ -4,8 +4,12 @@
4 4
5 5 import org.junit.jupiter.api.Test;
6 6
7 +import java.util.List;
8 +
7 9 import de.workaround.git.GitBrowseService;
8 10 import de.workaround.git.GitRepositoryService;
11 +import de.workaround.git.MergeRequestCommentService;
12 +import de.workaround.model.MergeRequest;
9 13 import de.workaround.model.Repository;
10 14 import io.quarkus.test.junit.QuarkusTest;
11 15 import jakarta.inject.Inject;
@@ -26,6 +30,12 @@
26 30 @Inject
27 31 GitBrowseService browse;
28 32
33 + @Inject
34 + MergeRequest.Repo mergeRequests;
35 +
36 + @Inject
37 + MergeRequestCommentService comments;
38 +
29 39 @Test
30 40 void seedsAliceWithDemoRepoContainingACommit()
31 41 {
@@ -52,4 +62,28 @@
52 62 "re-seeding must not add duplicate commits or repositories");
53 63 }
54 64
65 + @Test
66 + void seedsOneOpenMergeRequestWithACommentAndIsIdempotent()
67 + {
68 + seeder.seed();
69 + Repository demo = repositories.find("alice", "demo").orElseThrow();
70 +
71 + List<MergeRequest> afterFirst = mergeRequests.findByRepository(demo);
72 + assertEquals(1, afterFirst.size(), "exactly one demo merge request must be seeded");
73 + MergeRequest mr = afterFirst.get(0);
74 + assertEquals("feature", mr.sourceBranch);
75 + assertEquals("main", mr.targetBranch);
76 + assertEquals(MergeRequest.Status.OPEN, mr.status);
77 + assertEquals(1, comments.list(mr).size(), "the demo merge request must carry one sample comment");
78 +
79 + int featureCommits = browse.commitCount(repositories.repositoryPath(demo), "feature");
80 +
81 + // re-seeding must not duplicate the branch commit, the merge request, or the comment
82 + seeder.seed();
83 + assertEquals(1, mergeRequests.findByRepository(demo).size(), "re-seeding must not add a second merge request");
84 + assertEquals(1, comments.list(mr).size(), "re-seeding must not add a duplicate comment");
85 + assertEquals(featureCommits, browse.commitCount(repositories.repositoryPath(demo), "feature"),
86 + "re-seeding must not add duplicate commits to the feature branch");
87 + }
88 +
55 89 }

Keyboard shortcuts

?Show this help
g hGo home
EscClose dialog