Add file diff view to staging page, fix title
Rename 'Changes' to 'Staging' in the page title and heading. File names in both modified and staged file lists are now clickable links that show the diff for that file. The diff view shows both unstaged (index vs working tree) and staged (HEAD vs index) differences. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
{
|
||||
|
||||
@@ -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<DiffEntry> 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<DiffEntry> staged = df.scan(headIter, indexIter);
|
||||
for (DiffEntry diff : staged)
|
||||
{
|
||||
df.format(diff);
|
||||
}
|
||||
}
|
||||
}
|
||||
return out.toString(StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
|
||||
public void stageFiles(String name, List<String> files) throws IOException, GitAPIException
|
||||
{
|
||||
try (Git git = openRepository(name))
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="'Changes - ' + ${name}">Changes</title>
|
||||
<title th:text="'Staging - ' + ${name}">Staging</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2>Changes</h2>
|
||||
<h2>Staging</h2>
|
||||
|
||||
<h3>Modified Files (unstaged)</h3>
|
||||
<form method="post" th:action="@{/repo/{name}/stage(name=${name})}" th:if="${!#lists.isEmpty(modifiedFiles)}">
|
||||
@@ -15,7 +15,7 @@
|
||||
</tr>
|
||||
<tr th:each="file : ${modifiedFiles}">
|
||||
<td><input type="checkbox" name="files" th:value="${file}"></td>
|
||||
<td th:text="${file}"></td>
|
||||
<td><a th:href="@{'/repo/' + ${name} + '/diff/' + ${file}}" th:text="${file}"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
@@ -32,7 +32,7 @@
|
||||
</tr>
|
||||
<tr th:each="file : ${stagedFiles}">
|
||||
<td><input type="checkbox" name="files" th:value="${file}"></td>
|
||||
<td th:text="${file}"></td>
|
||||
<td><a th:href="@{'/repo/' + ${name} + '/diff/' + ${file}}" th:text="${file}"></a></td>
|
||||
</tr>
|
||||
</table>
|
||||
<br>
|
||||
|
||||
16
src/main/resources/templates/file-diff.html
Normal file
16
src/main/resources/templates/file-diff.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html xmlns:th="http://www.thymeleaf.org">
|
||||
<head>
|
||||
<title th:text="'Diff - ' + ${filePath}">Diff</title>
|
||||
</head>
|
||||
<body>
|
||||
<h2 th:text="${filePath}">file.txt</h2>
|
||||
|
||||
<p><a th:href="@{/repo/{name}/changes(name=${name})}">< Back to staging</a></p>
|
||||
|
||||
<pre th:text="${diff}">Diff output here</pre>
|
||||
|
||||
<p th:if="${diff.isEmpty()}"><i>No differences found.</i></p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
@@ -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")));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user