diff --git a/src/main/java/be/seeseepuff/webgit/controller/RepoController.java b/src/main/java/be/seeseepuff/webgit/controller/RepoController.java index b456e2b..9e8570c 100644 --- a/src/main/java/be/seeseepuff/webgit/controller/RepoController.java +++ b/src/main/java/be/seeseepuff/webgit/controller/RepoController.java @@ -43,6 +43,19 @@ public class RepoController return "changes"; } + @GetMapping("/repo/{name}/diff/{filePath}/**") + public String fileDiff(@PathVariable String name, + jakarta.servlet.http.HttpServletRequest request, Model model) throws IOException + { + String fullPath = request.getRequestURI(); + String prefix = "/repo/" + name + "/diff/"; + String filePath = fullPath.substring(prefix.length()); + model.addAttribute("name", name); + model.addAttribute("filePath", filePath); + model.addAttribute("diff", gitService.getWorkingTreeDiff(name, filePath)); + return "file-diff"; + } + @GetMapping("/repo/{name}/remote") public String remote(@PathVariable String name, Model model) throws IOException { diff --git a/src/main/java/be/seeseepuff/webgit/service/GitService.java b/src/main/java/be/seeseepuff/webgit/service/GitService.java index be2734a..b6f5890 100644 --- a/src/main/java/be/seeseepuff/webgit/service/GitService.java +++ b/src/main/java/be/seeseepuff/webgit/service/GitService.java @@ -209,6 +209,45 @@ public class GitService } } + public String getWorkingTreeDiff(String name, String filePath) throws IOException + { + try (Git git = openRepository(name)) + { + Repository repo = git.getRepository(); + ByteArrayOutputStream out = new ByteArrayOutputStream(); + try (DiffFormatter df = new DiffFormatter(out)) + { + df.setRepository(repo); + df.setPathFilter(PathFilter.create(filePath)); + // Unstaged: index vs working tree + var dirCache = new org.eclipse.jgit.dircache.DirCacheIterator(repo.readDirCache()); + var workTree = new org.eclipse.jgit.treewalk.FileTreeIterator(repo); + List unstaged = df.scan(dirCache, workTree); + for (DiffEntry diff : unstaged) + { + df.format(diff); + } + // Staged: HEAD vs index + ObjectId headTree = repo.resolve("HEAD^{tree}"); + if (headTree != null) + { + var headIter = new org.eclipse.jgit.treewalk.CanonicalTreeParser(); + try (var reader = repo.newObjectReader()) + { + headIter.reset(reader, headTree); + } + var indexIter = new org.eclipse.jgit.dircache.DirCacheIterator(repo.readDirCache()); + List staged = df.scan(headIter, indexIter); + for (DiffEntry diff : staged) + { + df.format(diff); + } + } + } + return out.toString(StandardCharsets.UTF_8); + } + } + public void stageFiles(String name, List files) throws IOException, GitAPIException { try (Git git = openRepository(name)) diff --git a/src/main/resources/templates/changes.html b/src/main/resources/templates/changes.html index d93bcb1..52a2bfa 100644 --- a/src/main/resources/templates/changes.html +++ b/src/main/resources/templates/changes.html @@ -1,10 +1,10 @@ -Changes +Staging -

Changes

+

Staging

Modified Files (unstaged)

@@ -15,7 +15,7 @@ - +
@@ -32,7 +32,7 @@ - +
diff --git a/src/main/resources/templates/file-diff.html b/src/main/resources/templates/file-diff.html new file mode 100644 index 0000000..f2c00ec --- /dev/null +++ b/src/main/resources/templates/file-diff.html @@ -0,0 +1,16 @@ + + + +Diff + + +

file.txt

+ +

< Back to staging

+ +
Diff output here
+ +

No differences found.

+ + + diff --git a/src/test/java/be/seeseepuff/webgit/controller/RepoControllerTest.java b/src/test/java/be/seeseepuff/webgit/controller/RepoControllerTest.java index 94be4f4..d9bf246 100644 --- a/src/test/java/be/seeseepuff/webgit/controller/RepoControllerTest.java +++ b/src/test/java/be/seeseepuff/webgit/controller/RepoControllerTest.java @@ -290,4 +290,16 @@ class RepoControllerTest verify(gitService).checkoutFilesFromCommit("myrepo", "abc1234", List.of("a.txt", "b.txt")); } + + @Test + void fileDiffShowsDiff() throws Exception + { + when(gitService.getWorkingTreeDiff("myrepo", "src/main.txt")).thenReturn("diff --git a/src/main.txt\n+hello"); + + mockMvc.perform(get("/repo/myrepo/diff/src/main.txt")) + .andExpect(status().isOk()) + .andExpect(view().name("file-diff")) + .andExpect(model().attribute("filePath", "src/main.txt")) + .andExpect(content().string(org.hamcrest.Matchers.containsString("+hello"))); + } } diff --git a/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java b/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java index 9c0d9ff..7e11665 100644 --- a/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java +++ b/src/test/java/be/seeseepuff/webgit/service/GitServiceTest.java @@ -419,4 +419,36 @@ class GitServiceTest assertEquals("# Test", Files.readString(readme)); } + + @Test + void getWorkingTreeDiffShowsUnstagedChanges() throws GitAPIException, IOException + { + gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo"); + Files.writeString(worktreePath.resolve("myrepo").resolve("README.md"), "modified content"); + + String diff = gitService.getWorkingTreeDiff("myrepo", "README.md"); + assertFalse(diff.isEmpty()); + assertTrue(diff.contains("README.md")); + } + + @Test + void getWorkingTreeDiffShowsStagedChanges() throws GitAPIException, IOException + { + gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo"); + Files.writeString(worktreePath.resolve("myrepo").resolve("README.md"), "staged content"); + gitService.stageFiles("myrepo", List.of("README.md")); + + String diff = gitService.getWorkingTreeDiff("myrepo", "README.md"); + assertFalse(diff.isEmpty()); + assertTrue(diff.contains("README.md")); + } + + @Test + void getWorkingTreeDiffReturnsEmptyForUnchangedFile() throws GitAPIException, IOException + { + gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo"); + + String diff = gitService.getWorkingTreeDiff("myrepo", "README.md"); + assertTrue(diff.isEmpty()); + } }