Add Rollback Selected button to staging page

Rollback restores selected unstaged files to their HEAD state
using git checkout. The button shares the form with Stage Selected,
distinguished by the submit button's name/value pair.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
2026-02-27 10:05:41 +01:00
parent 1b6f007eea
commit 321e268530
5 changed files with 45 additions and 3 deletions

View File

@@ -146,8 +146,12 @@ public class RepoController
} }
@PostMapping("/repo/{name}/stage") @PostMapping("/repo/{name}/stage")
public String stage(@PathVariable String name, @RequestParam List<String> files) throws IOException, GitAPIException public String stage(@PathVariable String name, @RequestParam List<String> files,
@RequestParam(defaultValue = "Stage Selected") String action) throws IOException, GitAPIException
{ {
if ("Rollback Selected".equals(action))
gitService.rollbackFiles(name, files);
else
gitService.stageFiles(name, files); gitService.stageFiles(name, files);
return "redirect:/repo/" + name + "/changes"; return "redirect:/repo/" + name + "/changes";
} }

View File

@@ -266,6 +266,19 @@ public class GitService
} }
} }
public void rollbackFiles(String name, List<String> files) throws IOException, GitAPIException
{
try (Git git = openRepository(name))
{
var checkout = git.checkout().setStartPoint("HEAD");
for (String file : files)
{
checkout.addPath(file);
}
checkout.call();
}
}
public void commit(String name, String message) throws IOException, GitAPIException public void commit(String name, String message) throws IOException, GitAPIException
{ {
try (Git git = openRepository(name)) try (Git git = openRepository(name))

View File

@@ -19,7 +19,8 @@
</tr> </tr>
</table> </table>
<br> <br>
<input type="submit" value="Stage Selected"> <input type="submit" name="action" value="Stage Selected">
<input type="submit" name="action" value="Rollback Selected">
</form> </form>
<p th:if="${#lists.isEmpty(modifiedFiles)}"><i>No modified files.</i></p> <p th:if="${#lists.isEmpty(modifiedFiles)}"><i>No modified files.</i></p>

View File

@@ -139,6 +139,18 @@ class RepoControllerTest
verify(gitService).stageFiles("myrepo", List.of("a.txt", "b.txt")); verify(gitService).stageFiles("myrepo", List.of("a.txt", "b.txt"));
} }
@Test
void rollbackRedirectsToChanges() throws Exception
{
mockMvc.perform(post("/repo/myrepo/stage")
.param("files", "a.txt")
.param("action", "Rollback Selected"))
.andExpect(status().is3xxRedirection())
.andExpect(redirectedUrl("/repo/myrepo/changes"));
verify(gitService).rollbackFiles("myrepo", List.of("a.txt"));
}
@Test @Test
void unstageRedirectsToChanges() throws Exception void unstageRedirectsToChanges() throws Exception
{ {

View File

@@ -451,4 +451,16 @@ class GitServiceTest
String diff = gitService.getWorkingTreeDiff("myrepo", "README.md"); String diff = gitService.getWorkingTreeDiff("myrepo", "README.md");
assertTrue(diff.isEmpty()); assertTrue(diff.isEmpty());
} }
@Test
void rollbackFilesRestoresContent() throws GitAPIException, IOException
{
gitService.cloneRepository(bareRemote.toUri().toString(), "myrepo");
Path readme = worktreePath.resolve("myrepo").resolve("README.md");
Files.writeString(readme, "modified");
gitService.rollbackFiles("myrepo", List.of("README.md"));
assertEquals("# Test", Files.readString(readme));
}
} }