From e3f3d0c6ae3e43a5f359f73a8dc1445ee070194e Mon Sep 17 00:00:00 2001 From: "slominskir-coding-agent[bot]" <335168828+slominskir-coding-agent[bot]@users.noreply.github.com> Date: Mon, 9 Nov 2026 15:17:33 -0500 Subject: [PATCH] Match GitHub and GitLab descriptions with line breaks after a sync GitLab returns a project description entered in its web form with CRLF line endings. The Sync page passes the remote description to the save request through an HTML data attribute, and the browser's HTML parser reads CRLF (and a lone CR) there as LF, so sim saves LF. The next sync compared GitLab's CRLF with the saved LF and listed the software for update again, however often the update was applied. Read GitHub and GitLab descriptions with LF line endings, as the browser would, so the saved description matches and the Sync page's description comparison agrees. Fixes #31 Co-Authored-By: Claude Opus 5.5 --- .../sim/business/service/SyncService.java | 15 +++++++- .../service/SyncServiceParseTest.java | 35 +++++++++++++++++++ 2 files changed, 49 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/jlab/sim/business/service/SyncService.java b/src/main/java/org/jlab/sim/business/service/SyncService.java index 6eff2db..84a9894 100644 --- a/src/main/java/org/jlab/sim/business/service/SyncService.java +++ b/src/main/java/org/jlab/sim/business/service/SyncService.java @@ -150,7 +150,7 @@ private static List parseProjects( String description = null; if (!item.isNull(DESCRIPTION)) { - description = item.getString(DESCRIPTION); + description = normalizeLineEndings(item.getString(DESCRIPTION)); } JsonArray topics = item.getJsonArray("topics"); @@ -180,6 +180,19 @@ private static List parseProjects( return softwareList; } + /** + * Replaces CRLF and CR line endings with LF. GitLab returns a description entered in its web form + * with CRLF line endings, but the sync page passes it through an HTML attribute, which the + * browser reads with LF line endings, so sim saves LF. Without this a synced description with a + * line break never matches. + * + * @param text The text + * @return The text with LF line endings + */ + static String normalizeLineEndings(String text) { + return text.replace("\r\n", "\n").replace('\r', '\n'); + } + private static final Pattern LINK_PATTERN = Pattern.compile("<([^>]+)>\\s*;\\s*rel=\"([^\"]+)\""); /** The next page's URL from a response's first Link header, or null on the last page. */ diff --git a/src/test/java/org/jlab/sim/business/service/SyncServiceParseTest.java b/src/test/java/org/jlab/sim/business/service/SyncServiceParseTest.java index b30cfb7..a26d507 100644 --- a/src/test/java/org/jlab/sim/business/service/SyncServiceParseTest.java +++ b/src/test/java/org/jlab/sim/business/service/SyncServiceParseTest.java @@ -12,6 +12,7 @@ import org.jlab.sim.persistence.entity.Repository; import org.jlab.sim.persistence.entity.Software; import org.jlab.sim.persistence.enumeration.SoftwareType; +import org.jlab.sim.persistence.view.SoftwareDiff; import org.jlab.smoothness.business.exception.UserFriendlyException; import org.jsoup.Jsoup; import org.junit.jupiter.api.Test; @@ -89,6 +90,40 @@ void gitLabProjectsBecomeSoftware() throws Exception { assertTrue(notes.isArchived()); } + @Test + void multiLineDescriptionMatchesWhatSimSaved() throws Exception { + // GitLab returns a description entered in its web form with CRLF line endings; the sync page + // passes it through an HTML attribute, which the browser reads with LF line endings (sim#31) + String json = + """ + [ + {"name": "fsdlib", "web_url": "https://code.jlab.org/acc/ops/fsdlib", + "description": "FSD library\\r\\nfor apps\\rand scripts\\n", "topics": [], + "archived": false} + ] + """; + + List remoteList = SyncService.parseGitLabProjects(json, REPOSITORY); + + assertEquals("FSD library\nfor apps\nand scripts\n", remoteList.get(0).getDescription()); + + Software saved = + new Software( + REPOSITORY, + "fsdlib", + SoftwareType.APP, + "FSD library\nfor apps\nand scripts\n", + null, + "https://code.jlab.org/acc/ops/fsdlib", + false, + null); + + SoftwareDiff diff = new SyncService().diff(List.of(saved), Map.of("fsdlib", remoteList.get(0))); + + assertEquals(1, diff.getMatchList().size(), "matched, not updated again"); + assertFalse(diff.hasChanges()); + } + @Test void emptyResultsAreNoSoftware() throws Exception { assertEquals(