From 6e98901b9c07e77068f2a93106b3117b70bb9cf7 Mon Sep 17 00:00:00 2001 From: Sebastiaan de Schaetzen Date: Fri, 27 Feb 2026 07:41:33 +0100 Subject: [PATCH] Auto-fill repository name from URL when left blank Derive the repository name from the clone URL by extracting the last path segment and stripping the .git suffix. Applied to the web controller, GitService, and telnet interface. The telnet prompt now shows the default name in brackets. Also fix flaky contextLoads test by using RANDOM_PORT. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../webgit/controller/HomeController.java | 4 +- .../seeseepuff/webgit/service/GitService.java | 12 +++++ .../webgit/telnet/TelnetSession.java | 5 +- .../webgit/WebgitApplicationTests.java | 4 +- .../webgit/controller/HomeControllerTest.java | 27 ++++++++++ .../webgit/service/GitServiceTest.java | 49 +++++++++++++++++++ .../webgit/telnet/TelnetSessionTest.java | 17 +++++-- 7 files changed, 109 insertions(+), 9 deletions(-) diff --git a/src/main/java/be/seeseepuff/webgit/controller/HomeController.java b/src/main/java/be/seeseepuff/webgit/controller/HomeController.java index a3a2c6e..d9118f1 100644 --- a/src/main/java/be/seeseepuff/webgit/controller/HomeController.java +++ b/src/main/java/be/seeseepuff/webgit/controller/HomeController.java @@ -52,8 +52,10 @@ public class HomeController } @PostMapping("/clone") - public String cloneRepo(@RequestParam String url, @RequestParam String name) throws GitAPIException, IOException + public String cloneRepo(@RequestParam String url, @RequestParam(required = false) String name) throws GitAPIException, IOException { + if (name == null || name.isBlank()) + name = gitService.deriveRepositoryName(url); gitService.cloneRepository(url, name); return "redirect:/?repo=" + name; } diff --git a/src/main/java/be/seeseepuff/webgit/service/GitService.java b/src/main/java/be/seeseepuff/webgit/service/GitService.java index 117d61f..09b3d2e 100644 --- a/src/main/java/be/seeseepuff/webgit/service/GitService.java +++ b/src/main/java/be/seeseepuff/webgit/service/GitService.java @@ -22,8 +22,20 @@ public class GitService { private final WebgitProperties properties; + public String deriveRepositoryName(String url) + { + String path = url.replaceAll("[/\\\\]+$", ""); + int lastSep = Math.max(path.lastIndexOf('/'), path.lastIndexOf('\\')); + String base = lastSep >= 0 ? path.substring(lastSep + 1) : path; + if (base.endsWith(".git")) + base = base.substring(0, base.length() - 4); + return base; + } + public void cloneRepository(String url, String name) throws GitAPIException, IOException { + if (name == null || name.isBlank()) + name = deriveRepositoryName(url); Path worktree = properties.getWorktreePath().resolve(name); Path gitDir = properties.getGitDirPath().resolve(name); diff --git a/src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java b/src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java index df2c21f..f5b1c2b 100644 --- a/src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java +++ b/src/main/java/be/seeseepuff/webgit/telnet/TelnetSession.java @@ -88,11 +88,12 @@ public class TelnetSession implements Runnable if (url == null || url.isBlank()) return; - out.print("Name: "); + String defaultName = gitService.deriveRepositoryName(url.trim()); + out.print("Name [" + defaultName + "]: "); out.flush(); String name = in.readLine(); if (name == null || name.isBlank()) - return; + name = defaultName; gitService.cloneRepository(url.trim(), name.trim()); out.println("Cloned successfully."); diff --git a/src/test/java/be/seeseepuff/webgit/WebgitApplicationTests.java b/src/test/java/be/seeseepuff/webgit/WebgitApplicationTests.java index 2c38a47..bbf4d16 100644 --- a/src/test/java/be/seeseepuff/webgit/WebgitApplicationTests.java +++ b/src/test/java/be/seeseepuff/webgit/WebgitApplicationTests.java @@ -2,8 +2,10 @@ package be.seeseepuff.webgit; import org.junit.jupiter.api.Test; import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.test.context.TestPropertySource; -@SpringBootTest +@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) +@TestPropertySource(properties = "webgit.telnet.enabled=false") class WebgitApplicationTests { @Test diff --git a/src/test/java/be/seeseepuff/webgit/controller/HomeControllerTest.java b/src/test/java/be/seeseepuff/webgit/controller/HomeControllerTest.java index 2706e6c..b95e0a8 100644 --- a/src/test/java/be/seeseepuff/webgit/controller/HomeControllerTest.java +++ b/src/test/java/be/seeseepuff/webgit/controller/HomeControllerTest.java @@ -115,4 +115,31 @@ class HomeControllerTest verify(gitService).cloneRepository("https://example.com/repo.git", "myrepo"); } + + @Test + void cloneWithEmptyNameDerivesFromUrl() throws Exception + { + when(gitService.deriveRepositoryName("https://example.com/repo.git")).thenReturn("repo"); + + mockMvc.perform(post("/clone") + .param("url", "https://example.com/repo.git") + .param("name", "")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/?repo=repo")); + + verify(gitService).cloneRepository("https://example.com/repo.git", "repo"); + } + + @Test + void cloneWithNoNameParamDerivesFromUrl() throws Exception + { + when(gitService.deriveRepositoryName("https://example.com/project.git")).thenReturn("project"); + + mockMvc.perform(post("/clone") + .param("url", "https://example.com/project.git")) + .andExpect(status().is3xxRedirection()) + .andExpect(redirectedUrl("/?repo=project")); + + verify(gitService).cloneRepository("https://example.com/project.git", "project"); + } } diff --git a/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java b/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java index 88986e3..fe54186 100644 --- a/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java +++ b/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java @@ -250,4 +250,53 @@ class GitServiceTest assertDoesNotThrow(() -> gitService.pull("myrepo")); } + + @Test + void deriveNameFromHttpsUrl() + { + assertEquals("my-project", gitService.deriveRepositoryName("https://github.com/user/my-project.git")); + } + + @Test + void deriveNameFromUrlWithoutGitSuffix() + { + assertEquals("repo", gitService.deriveRepositoryName("https://github.com/user/repo")); + } + + @Test + void deriveNameFromUrlWithTrailingSlash() + { + assertEquals("repo", gitService.deriveRepositoryName("https://github.com/user/repo/")); + } + + @Test + void deriveNameFromSshUrl() + { + assertEquals("project", gitService.deriveRepositoryName("git@github.com:user/project.git")); + } + + @Test + void deriveNameFromBareName() + { + assertEquals("foo", gitService.deriveRepositoryName("foo.git")); + } + + @Test + void cloneWithBlankNameDerivesFromUrl() throws GitAPIException, IOException + { + gitService.cloneRepository(bareRemote.toUri().toString(), ""); + + String expectedName = gitService.deriveRepositoryName(bareRemote.toUri().toString()); + assertTrue(Files.exists(worktreePath.resolve(expectedName))); + assertTrue(Files.exists(gitDirPath.resolve(expectedName))); + } + + @Test + void cloneWithNullNameDerivesFromUrl() throws GitAPIException, IOException + { + gitService.cloneRepository(bareRemote.toUri().toString(), null); + + String expectedName = gitService.deriveRepositoryName(bareRemote.toUri().toString()); + assertTrue(Files.exists(worktreePath.resolve(expectedName))); + } } diff --git a/src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java b/src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java index bac7330..26ea8c9 100644 --- a/src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java +++ b/src/test/java/be/seeseepuff/webgit/telnet/TelnetSessionTest.java @@ -72,7 +72,9 @@ class TelnetSessionTest @Test void cloneRepository() throws IOException, GitAPIException { + when(gitService.deriveRepositoryName("https://example.com/repo.git")).thenReturn("repo"); String output = runSession("2\nhttps://example.com/repo.git\nmyrepo\nq\n"); + assertTrue(output.contains("Name [repo]:")); assertTrue(output.contains("Cloned successfully.")); verify(gitService).cloneRepository("https://example.com/repo.git", "myrepo"); } @@ -85,10 +87,13 @@ class TelnetSessionTest } @Test - void cloneRepositoryEmptyName() + void cloneRepositoryEmptyNameUsesDefault() throws IOException, GitAPIException { + when(gitService.deriveRepositoryName("https://example.com/repo.git")).thenReturn("repo"); String output = runSession("2\nhttps://example.com/repo.git\n\nq\n"); - assertFalse(output.contains("Cloned successfully.")); + assertTrue(output.contains("Name [repo]:")); + assertTrue(output.contains("Cloned successfully.")); + verify(gitService).cloneRepository("https://example.com/repo.git", "repo"); } @Test @@ -521,10 +526,12 @@ class TelnetSessionTest } @Test - void cloneNullName() + void cloneNullName() throws IOException, GitAPIException { - // URL is provided but name input stream ends + // URL is provided but name input stream ends — uses default + when(gitService.deriveRepositoryName("https://example.com/repo.git")).thenReturn("repo"); String output = runSession("2\nhttps://example.com/repo.git\n"); - assertTrue(output.contains("Goodbye!")); + assertTrue(output.contains("Cloned successfully.")); + verify(gitService).cloneRepository("https://example.com/repo.git", "repo"); } }