make the Commit restore more safe #6
@@ -31,7 +31,10 @@
|
|||||||
"Bash(grep -n \"input,\\\\|select,\\\\|input {\\\\|select {\\\\|.repo-form input\\\\|input:focus\\\\|::placeholder\" src/app.css)",
|
"Bash(grep -n \"input,\\\\|select,\\\\|input {\\\\|select {\\\\|.repo-form input\\\\|input:focus\\\\|::placeholder\" src/app.css)",
|
||||||
"Bash(sudo -n true)",
|
"Bash(sudo -n true)",
|
||||||
"Bash(rustc --version)",
|
"Bash(rustc --version)",
|
||||||
"Read(//mnt/c/Users/cbr/Desktop/src-tauri/src/**)"
|
"Read(//mnt/c/Users/cbr/Desktop/src-tauri/src/**)",
|
||||||
|
"Bash(sudo apt install -y libdbus-1-dev pkg-config)",
|
||||||
|
"Bash(dpkg -l)",
|
||||||
|
"Bash(apt list *)"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+30
-4
@@ -988,7 +988,21 @@ pub fn restore_to_commit(path: String, commit: String) -> Result<GitStatus, Stri
|
|||||||
let repo = resolve_repo(&path)?;
|
let repo = resolve_repo(&path)?;
|
||||||
let commit_hash = verify_commit(&repo, &commit)?;
|
let commit_hash = verify_commit(&repo, &commit)?;
|
||||||
|
|
||||||
run_git(&repo, ["reset", "--hard", commit_hash.as_str()])?;
|
// Non-destructive: bring the working tree back to how it looked at `commit` without
|
||||||
|
// moving the branch pointer (unlike `git reset --hard`, which would rewrite history and
|
||||||
|
// hide any newer commits from the log). The result lands as ordinary unstaged changes
|
||||||
|
// that the user reviews in the status panel and stages/commits or discards explicitly.
|
||||||
|
run_git(
|
||||||
|
&repo,
|
||||||
|
[
|
||||||
|
"restore",
|
||||||
|
"--source",
|
||||||
|
commit_hash.as_str(),
|
||||||
|
"--worktree",
|
||||||
|
"--",
|
||||||
|
".",
|
||||||
|
],
|
||||||
|
)?;
|
||||||
status_for_repo(&repo)
|
status_for_repo(&repo)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3913,7 +3927,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn restore_to_commit_resets_branch_to_selected_commit() {
|
fn restore_to_commit_leaves_branch_untouched_and_stages_change_as_unstaged() {
|
||||||
let repo = init_temp_repo("restore_to_commit");
|
let repo = init_temp_repo("restore_to_commit");
|
||||||
commit_initial_file(&repo.path);
|
commit_initial_file(&repo.path);
|
||||||
let first_commit = git_output_test(&repo.path, ["rev-parse", "HEAD"]);
|
let first_commit = git_output_test(&repo.path, ["rev-parse", "HEAD"]);
|
||||||
@@ -3921,6 +3935,7 @@ mod tests {
|
|||||||
fs::write(repo.path.join("old.txt"), "second\n").expect("second file should be written");
|
fs::write(repo.path.join("old.txt"), "second\n").expect("second file should be written");
|
||||||
run_git_test(&repo.path, ["add", "old.txt"]);
|
run_git_test(&repo.path, ["add", "old.txt"]);
|
||||||
run_git_test(&repo.path, ["commit", "-q", "-m", "second"]);
|
run_git_test(&repo.path, ["commit", "-q", "-m", "second"]);
|
||||||
|
let second_commit = git_output_test(&repo.path, ["rev-parse", "HEAD"]);
|
||||||
|
|
||||||
let status = restore_to_commit(
|
let status = restore_to_commit(
|
||||||
repo.path.to_string_lossy().to_string(),
|
repo.path.to_string_lossy().to_string(),
|
||||||
@@ -3930,9 +3945,20 @@ mod tests {
|
|||||||
let current_commit = git_output_test(&repo.path, ["rev-parse", "HEAD"]);
|
let current_commit = git_output_test(&repo.path, ["rev-parse", "HEAD"]);
|
||||||
let contents = fs::read_to_string(repo.path.join("old.txt")).unwrap();
|
let contents = fs::read_to_string(repo.path.join("old.txt")).unwrap();
|
||||||
|
|
||||||
assert_eq!(current_commit, first_commit);
|
// The branch must stay exactly where it was: no commit is rewritten or hidden.
|
||||||
|
assert_eq!(current_commit, second_commit);
|
||||||
|
assert_ne!(current_commit, first_commit);
|
||||||
|
// The old content lands in the worktree as a reviewable, unstaged change.
|
||||||
assert_eq!(contents.replace("\r\n", "\n"), "original\n");
|
assert_eq!(contents.replace("\r\n", "\n"), "original\n");
|
||||||
assert!(status.clean, "{:?}", status.files);
|
assert!(!status.clean, "{:?}", status.files);
|
||||||
|
assert_eq!(
|
||||||
|
status
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.find(|f| f.path == "old.txt")
|
||||||
|
.and_then(|f| f.unstaged),
|
||||||
|
Some(FileStatusKind::Modified)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
+1
-1
@@ -1051,7 +1051,7 @@
|
|||||||
|
|
||||||
async function restoreCommit(target: GitCommit) {
|
async function restoreCommit(target: GitCommit) {
|
||||||
if (!activeRepoPath) return;
|
if (!activeRepoPath) return;
|
||||||
const confirmed = window.confirm(`Reset current branch to ${target.short_hash}?\n\nThis moves the current branch and discards tracked local changes.`);
|
const confirmed = window.confirm(`Restore working tree to ${target.short_hash}?\n\nThis brings back the files from that commit as unstaged changes so you can review and commit them. No commit is removed and the branch stays where it is.`);
|
||||||
if (!confirmed) return;
|
if (!confirmed) return;
|
||||||
await runOperation(`Restoring ${target.short_hash}`, async () => {
|
await runOperation(`Restoring ${target.short_hash}`, async () => {
|
||||||
applyStatus(await restoreToCommit(activeRepoPath, target.hash));
|
applyStatus(await restoreToCommit(activeRepoPath, target.hash));
|
||||||
|
|||||||
@@ -226,7 +226,7 @@
|
|||||||
<GitBranch size={15} aria-hidden="true" />
|
<GitBranch size={15} aria-hidden="true" />
|
||||||
Branch
|
Branch
|
||||||
</button>
|
</button>
|
||||||
<button class="btn-sm" type="button" onclick={() => onRestoreCommit(item)} disabled={isBusy} title="Reset current branch to this commit">
|
<button class="btn-sm" type="button" onclick={() => onRestoreCommit(item)} disabled={isBusy} title="Bring this commit's files into your working tree as unstaged changes (no history is changed)">
|
||||||
<RotateCcw size={15} aria-hidden="true" />
|
<RotateCcw size={15} aria-hidden="true" />
|
||||||
Restore
|
Restore
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
Reference in New Issue
Block a user