Add delete repository feature

GitService.deleteRepository() removes both worktree and git-dir.
Exposed via POST /repo/{name}/delete, a 'Danger Zone' section on
the repo page, and telnet main menu option 4 with confirmation
prompt. Includes unit tests for all layers.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-26 09:26:15 +01:00
parent 8f4ef970f5
commit 7fa68da521
9 changed files with 187 additions and 17 deletions
@@ -78,4 +78,11 @@ public class RepoController
gitService.pull(name);
return "redirect:/repo/" + name;
}
@PostMapping("/repo/{name}/delete")
public String delete(@PathVariable String name) throws IOException
{
gitService.deleteRepository(name);
return "redirect:/";
}
}
@@ -56,6 +56,37 @@ public class GitService
}
}
public void deleteRepository(String name) throws IOException
{
Path worktree = properties.getWorktreePath().resolve(name);
Path gitDir = properties.getGitDirPath().resolve(name);
deleteRecursively(gitDir);
deleteRecursively(worktree);
}
private void deleteRecursively(Path path) throws IOException
{
if (!Files.exists(path))
return;
try (Stream<Path> walk = Files.walk(path))
{
walk.sorted(java.util.Comparator.reverseOrder())
.forEach(p ->
{
try
{
Files.delete(p);
}
catch (IOException e)
{
throw new java.io.UncheckedIOException(e);
}
});
}
}
public List<String> listBranches(String name) throws IOException, GitAPIException
{
try (Git git = openRepository(name))
@@ -40,6 +40,7 @@ public class TelnetSession implements Runnable
out.println(" 1. List repositories");
out.println(" 2. Clone a repository");
out.println(" 3. Open a repository");
out.println(" 4. Delete a repository");
out.println(" q. Quit");
out.print("> ");
out.flush();
@@ -56,6 +57,7 @@ public class TelnetSession implements Runnable
case "1" -> listRepositories();
case "2" -> cloneRepository();
case "3" -> openRepository();
case "4" -> deleteRepository();
default -> out.println("Invalid choice.");
}
}
@@ -96,6 +98,59 @@ public class TelnetSession implements Runnable
out.println("Cloned successfully.");
}
private void deleteRepository() throws IOException
{
List<String> repos = gitService.listRepositories();
if (repos.isEmpty())
{
out.println("No repositories to delete.");
return;
}
out.println("Repositories:");
for (int i = 0; i < repos.size(); i++)
{
out.println(" " + (i + 1) + ". " + repos.get(i));
}
out.print("Enter number to delete: ");
out.flush();
String input = in.readLine();
if (input == null || input.isBlank())
return;
int index;
try
{
index = Integer.parseInt(input.trim()) - 1;
}
catch (NumberFormatException e)
{
out.println("Invalid number.");
return;
}
if (index < 0 || index >= repos.size())
{
out.println("Invalid selection.");
return;
}
String name = repos.get(index);
out.print("Are you sure you want to delete '" + name + "'? (y/n): ");
out.flush();
String confirm = in.readLine();
if (confirm != null && "y".equalsIgnoreCase(confirm.trim()))
{
gitService.deleteRepository(name);
out.println("Deleted '" + name + "'.");
}
else
{
out.println("Cancelled.");
}
}
private void openRepository() throws IOException, GitAPIException
{
List<String> repos = gitService.listRepositories();
+7
View File
@@ -99,5 +99,12 @@ New branch: <input type="text" name="branch" size="20">
</tr>
</table>
<hr>
<h3>Danger Zone</h3>
<form method="post" th:action="@{/repo/{name}/delete(name=${name})}">
<input type="submit" value="Delete Repository">
</form>
</body>
</html>