Compare commits
4 Commits
ba3bf697f0
...
383864469d
| Author | SHA1 | Date | |
|---|---|---|---|
| 383864469d | |||
| 4458eb204b | |||
| 005e0c7d23 | |||
| be130582fc |
@@ -22,7 +22,7 @@ public class RepoController
|
|||||||
@GetMapping("/repo/{name}")
|
@GetMapping("/repo/{name}")
|
||||||
public String repo(@PathVariable String name)
|
public String repo(@PathVariable String name)
|
||||||
{
|
{
|
||||||
return "redirect:/repo/" + name + "/branches";
|
return "redirect:/repo/" + name + "/changes";
|
||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/repo/{name}/branches")
|
@GetMapping("/repo/{name}/branches")
|
||||||
@@ -44,9 +44,10 @@ public class RepoController
|
|||||||
}
|
}
|
||||||
|
|
||||||
@GetMapping("/repo/{name}/remote")
|
@GetMapping("/repo/{name}/remote")
|
||||||
public String remote(@PathVariable String name, Model model)
|
public String remote(@PathVariable String name, Model model) throws IOException
|
||||||
{
|
{
|
||||||
model.addAttribute("name", name);
|
model.addAttribute("name", name);
|
||||||
|
model.addAttribute("remotes", gitService.listRemotes(name));
|
||||||
return "remote";
|
return "remote";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -106,6 +107,20 @@ public class RepoController
|
|||||||
return "redirect:/repo/" + name + "/remote";
|
return "redirect:/repo/" + name + "/remote";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@PostMapping("/repo/{name}/update-remote")
|
||||||
|
public String updateRemote(@PathVariable String name, @RequestParam String remote, @RequestParam String url) throws IOException
|
||||||
|
{
|
||||||
|
gitService.updateRemoteUrl(name, remote, url);
|
||||||
|
return "redirect:/repo/" + name + "/remote";
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/repo/{name}/confirm-delete")
|
||||||
|
public String confirmDelete(@PathVariable String name, Model model)
|
||||||
|
{
|
||||||
|
model.addAttribute("name", name);
|
||||||
|
return "confirm-delete";
|
||||||
|
}
|
||||||
|
|
||||||
@PostMapping("/repo/{name}/delete")
|
@PostMapping("/repo/{name}/delete")
|
||||||
public String delete(@PathVariable String name) throws IOException
|
public String delete(@PathVariable String name) throws IOException
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,7 +13,9 @@ import org.springframework.stereotype.Service;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.Path;
|
import java.nio.file.Path;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
@@ -216,6 +218,32 @@ public class GitService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> listRemotes(String name) throws IOException
|
||||||
|
{
|
||||||
|
try (Git git = openRepository(name))
|
||||||
|
{
|
||||||
|
StoredConfig config = git.getRepository().getConfig();
|
||||||
|
var remoteNames = config.getSubsections("remote");
|
||||||
|
Map<String, String> remotes = new LinkedHashMap<>();
|
||||||
|
for (String remote : remoteNames)
|
||||||
|
{
|
||||||
|
String url = config.getString("remote", remote, "url");
|
||||||
|
remotes.put(remote, url != null ? url : "");
|
||||||
|
}
|
||||||
|
return remotes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void updateRemoteUrl(String name, String remote, String url) throws IOException
|
||||||
|
{
|
||||||
|
try (Git git = openRepository(name))
|
||||||
|
{
|
||||||
|
StoredConfig config = git.getRepository().getConfig();
|
||||||
|
config.setString("remote", remote, "url", url);
|
||||||
|
config.save();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void push(String name) throws IOException, GitAPIException
|
public void push(String name) throws IOException, GitAPIException
|
||||||
{
|
{
|
||||||
try (Git git = openRepository(name))
|
try (Git git = openRepository(name))
|
||||||
|
|||||||
22
src/main/resources/templates/confirm-delete.html
Normal file
22
src/main/resources/templates/confirm-delete.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html xmlns:th="http://www.thymeleaf.org">
|
||||||
|
<head>
|
||||||
|
<title th:text="'Confirm Delete - ' + ${name}">Confirm Delete</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h2>Are you sure?</h2>
|
||||||
|
<p>This will permanently delete the repository <b th:text="${name}"></b> and its working tree.</p>
|
||||||
|
<table border="0" cellpadding="4" cellspacing="0">
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
<form method="post" th:action="@{/repo/{name}/delete(name=${name})}" target="_top">
|
||||||
|
<input type="submit" value="Yes">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
<a th:href="@{/repo/{name}/manage(name=${name})}">No</a>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</table>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -4,7 +4,7 @@
|
|||||||
</head>
|
</head>
|
||||||
<frameset cols="180,*">
|
<frameset cols="180,*">
|
||||||
<frame th:src="${selectedRepo != null} ? '/nav?repo=' + ${selectedRepo} : '/nav'" name="nav">
|
<frame th:src="${selectedRepo != null} ? '/nav?repo=' + ${selectedRepo} : '/nav'" name="nav">
|
||||||
<frame th:src="${showCloneForm} ? '/clone-form' : (${selectedRepo != null} ? '/repo/' + ${selectedRepo} + '/branches' : '/welcome')" name="content">
|
<frame th:src="${showCloneForm} ? '/clone-form' : (${selectedRepo != null} ? '/repo/' + ${selectedRepo} + '/changes' : '/welcome')" name="content">
|
||||||
<noframes>
|
<noframes>
|
||||||
<body>
|
<body>
|
||||||
<p>Your browser does not support frames. <a href="/repos">Click here</a> to continue.</p>
|
<p>Your browser does not support frames. <a href="/repos">Click here</a> to continue.</p>
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
|
|
||||||
<h3>Danger Zone</h3>
|
<h3>Danger Zone</h3>
|
||||||
<p>This will permanently delete the repository and its working tree.</p>
|
<p>This will permanently delete the repository and its working tree.</p>
|
||||||
<form method="post" th:action="@{/repo/{name}/delete(name=${name})}" target="_top">
|
<form method="get" th:action="@{/repo/{name}/confirm-delete(name=${name})}">
|
||||||
<input type="submit" value="Delete Repository">
|
<input type="submit" value="Delete Repository">
|
||||||
</form>
|
</form>
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,8 @@
|
|||||||
<th:block th:if="${selectedRepo != null}">
|
<th:block th:if="${selectedRepo != null}">
|
||||||
<hr>
|
<hr>
|
||||||
<b th:text="${selectedRepo}"></b><br>
|
<b th:text="${selectedRepo}"></b><br>
|
||||||
|
<a th:href="@{/repo/{name}/changes(name=${selectedRepo})}" target="content">Staging</a><br>
|
||||||
<a th:href="@{/repo/{name}/branches(name=${selectedRepo})}" target="content">Branches</a><br>
|
<a th:href="@{/repo/{name}/branches(name=${selectedRepo})}" target="content">Branches</a><br>
|
||||||
<a th:href="@{/repo/{name}/changes(name=${selectedRepo})}" target="content">Changes</a><br>
|
|
||||||
<a th:href="@{/repo/{name}/remote(name=${selectedRepo})}" target="content">Remote</a><br>
|
<a th:href="@{/repo/{name}/remote(name=${selectedRepo})}" target="content">Remote</a><br>
|
||||||
<a th:href="@{/repo/{name}/manage(name=${selectedRepo})}" target="content">Manage</a><br>
|
<a th:href="@{/repo/{name}/manage(name=${selectedRepo})}" target="content">Manage</a><br>
|
||||||
</th:block>
|
</th:block>
|
||||||
|
|||||||
@@ -4,10 +4,24 @@
|
|||||||
<title th:text="'Remote - ' + ${name}">Remote</title>
|
<title th:text="'Remote - ' + ${name}">Remote</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<h2>Remote</h2>
|
<h2>Remotes</h2>
|
||||||
|
|
||||||
<table border="0" cellpadding="4" cellspacing="0">
|
<table border="1" cellpadding="4" cellspacing="0">
|
||||||
<tr>
|
<tr>
|
||||||
|
<th>Name</th>
|
||||||
|
<th>URL</th>
|
||||||
|
<th></th>
|
||||||
|
<th></th>
|
||||||
|
</tr>
|
||||||
|
<tr th:each="entry : ${remotes}">
|
||||||
|
<td th:text="${entry.key}"></td>
|
||||||
|
<td>
|
||||||
|
<form method="post" th:action="@{/repo/{name}/update-remote(name=${name})}">
|
||||||
|
<input type="hidden" name="remote" th:value="${entry.key}">
|
||||||
|
<input type="text" name="url" th:value="${entry.value}" size="40">
|
||||||
|
<input type="submit" value="Save">
|
||||||
|
</form>
|
||||||
|
</td>
|
||||||
<td>
|
<td>
|
||||||
<form method="post" th:action="@{/repo/{name}/push(name=${name})}">
|
<form method="post" th:action="@{/repo/{name}/push(name=${name})}">
|
||||||
<input type="submit" value="Push">
|
<input type="submit" value="Push">
|
||||||
@@ -21,5 +35,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</table>
|
</table>
|
||||||
|
|
||||||
|
<p th:if="${remotes.isEmpty()}">No remotes configured.</p>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -25,11 +25,11 @@ class RepoControllerTest
|
|||||||
private GitService gitService;
|
private GitService gitService;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void repoRedirectsToBranches() throws Exception
|
void repoRedirectsToChanges() throws Exception
|
||||||
{
|
{
|
||||||
mockMvc.perform(get("/repo/myrepo"))
|
mockMvc.perform(get("/repo/myrepo"))
|
||||||
.andExpect(status().is3xxRedirection())
|
.andExpect(status().is3xxRedirection())
|
||||||
.andExpect(redirectedUrl("/repo/myrepo/branches"));
|
.andExpect(redirectedUrl("/repo/myrepo/changes"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -74,12 +74,26 @@ class RepoControllerTest
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void remotePageLoads() throws Exception
|
void remotePageShowsRemotes() throws Exception
|
||||||
{
|
{
|
||||||
|
when(gitService.listRemotes("myrepo")).thenReturn(java.util.Map.of("origin", "https://example.com/repo.git"));
|
||||||
|
|
||||||
mockMvc.perform(get("/repo/myrepo/remote"))
|
mockMvc.perform(get("/repo/myrepo/remote"))
|
||||||
.andExpect(status().isOk())
|
.andExpect(status().isOk())
|
||||||
.andExpect(view().name("remote"))
|
.andExpect(view().name("remote"))
|
||||||
.andExpect(model().attribute("name", "myrepo"));
|
.andExpect(model().attribute("name", "myrepo"))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("origin")))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("https://example.com/repo.git")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void remotePageShowsNoRemotes() throws Exception
|
||||||
|
{
|
||||||
|
when(gitService.listRemotes("myrepo")).thenReturn(java.util.Map.of());
|
||||||
|
|
||||||
|
mockMvc.perform(get("/repo/myrepo/remote"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("No remotes configured.")));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -168,6 +182,28 @@ class RepoControllerTest
|
|||||||
verify(gitService).pull("myrepo");
|
verify(gitService).pull("myrepo");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateRemoteRedirectsToRemote() throws Exception
|
||||||
|
{
|
||||||
|
mockMvc.perform(post("/repo/myrepo/update-remote")
|
||||||
|
.param("remote", "origin")
|
||||||
|
.param("url", "https://new-url.com/repo.git"))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andExpect(redirectedUrl("/repo/myrepo/remote"));
|
||||||
|
|
||||||
|
verify(gitService).updateRemoteUrl("myrepo", "origin", "https://new-url.com/repo.git");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void confirmDeleteShowsConfirmation() throws Exception
|
||||||
|
{
|
||||||
|
mockMvc.perform(get("/repo/myrepo/confirm-delete"))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(view().name("confirm-delete"))
|
||||||
|
.andExpect(model().attribute("name", "myrepo"))
|
||||||
|
.andExpect(content().string(org.hamcrest.Matchers.containsString("Are you sure?")));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void deleteRedirectsToRoot() throws Exception
|
void deleteRedirectsToRoot() throws Exception
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -311,4 +311,26 @@ class GitServiceTest
|
|||||||
{
|
{
|
||||||
assertThrows(IllegalArgumentException.class, () -> gitService.cloneRepository(".git", null));
|
assertThrows(IllegalArgumentException.class, () -> gitService.cloneRepository(".git", null));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listRemotesReturnsConfiguredRemotes() throws GitAPIException, IOException
|
||||||
|
{
|
||||||
|
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
|
||||||
|
|
||||||
|
var remotes = gitService.listRemotes("myrepo");
|
||||||
|
assertEquals(1, remotes.size());
|
||||||
|
assertTrue(remotes.containsKey("origin"));
|
||||||
|
assertEquals(bareRemote.toUri().toString(), remotes.get("origin"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void updateRemoteUrlChangesUrl() throws GitAPIException, IOException
|
||||||
|
{
|
||||||
|
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
|
||||||
|
|
||||||
|
gitService.updateRemoteUrl("myrepo", "origin", "https://new-url.com/repo.git");
|
||||||
|
|
||||||
|
var remotes = gitService.listRemotes("myrepo");
|
||||||
|
assertEquals("https://new-url.com/repo.git", remotes.get("origin"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user