Skip to content

Commit 048e490

Browse files
committed
feat: support reversing (v) file/hunk/line patch
1 parent 479c10d commit 048e490

11 files changed

Lines changed: 353 additions & 0 deletions

src/default_config.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,7 @@ root.discard = ["K"]
119119
root.stage = ["s"]
120120
root.unstage = ["u"]
121121
root.apply = ["a"]
122+
root.reverse = ["v"]
122123
root.copy_hash = ["y"]
123124

124125
picker.next = ["down", "ctrl+n", "tab"]

src/ops/mod.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ pub(crate) mod rebase;
2525
pub(crate) mod remote;
2626
pub(crate) mod reset;
2727
pub(crate) mod revert;
28+
pub(crate) mod reverse;
2829
pub(crate) mod show;
2930
pub(crate) mod show_refs;
3031
pub(crate) mod stage;
@@ -107,6 +108,7 @@ pub(crate) enum Op {
107108
Show,
108109
Discard,
109110
Apply,
111+
Reverse,
110112
CopyHash,
111113

112114
ToggleSection,
@@ -199,6 +201,7 @@ impl Op {
199201
Op::Stage => Box::new(stage::Stage),
200202
Op::Unstage => Box::new(unstage::Unstage),
201203
Op::Apply => Box::new(apply::Apply),
204+
Op::Reverse => Box::new(reverse::Reverse),
202205
Op::CopyHash => Box::new(copy_hash::CopyHash),
203206

204207
Op::AddRemote => Box::new(remote::AddRemote),

src/ops/reverse.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
use super::OpTrait;
2+
use crate::{
3+
Action,
4+
app::{App, State},
5+
git::diff::{Diff, PatchMode},
6+
item_data::ItemData,
7+
term::Term,
8+
};
9+
use std::{process::Command, rc::Rc};
10+
11+
pub(crate) struct Reverse;
12+
impl OpTrait for Reverse {
13+
fn get_action(&self, target: &ItemData) -> Option<Action> {
14+
let action = match target {
15+
ItemData::Delta { diff, file_i } => reverse_patch(diff.format_file_patch(*file_i)),
16+
ItemData::Hunk {
17+
diff,
18+
file_i,
19+
hunk_i,
20+
} => reverse_patch(diff.format_hunk_patch(*file_i, *hunk_i)),
21+
ItemData::HunkLine {
22+
diff,
23+
file_i,
24+
hunk_i,
25+
line_i,
26+
..
27+
} => reverse_line(diff, *file_i, *hunk_i, *line_i),
28+
_ => return None,
29+
};
30+
31+
Some(action)
32+
}
33+
34+
fn is_target_op(&self) -> bool {
35+
true
36+
}
37+
38+
fn display(&self, _state: &State) -> String {
39+
"Reverse".into()
40+
}
41+
}
42+
43+
fn reverse_patch(patch: String) -> Action {
44+
let patch = patch.into_bytes();
45+
46+
Rc::new(move |app: &mut App, term: &mut Term| {
47+
let mut cmd = Command::new("git");
48+
cmd.args(["apply", "--reverse"]);
49+
app.run_cmd(term, &patch, cmd)
50+
})
51+
}
52+
53+
fn reverse_line(diff: &Rc<Diff>, file_i: usize, hunk_i: usize, line_i: usize) -> Action {
54+
let patch = diff
55+
.format_line_patch(file_i, hunk_i, line_i..(line_i + 1), PatchMode::Reverse)
56+
.into_bytes();
57+
58+
Rc::new(move |app: &mut App, term: &mut Term| {
59+
let mut cmd = Command::new("git");
60+
cmd.args(["apply", "--reverse", "--recount"]);
61+
app.run_cmd(term, &patch, cmd)
62+
})
63+
}

src/tests/mod.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ mod quit;
2828
mod rebase;
2929
mod remote;
3030
mod reset;
31+
mod reverse;
3132
mod stage;
3233
mod stash;
3334
mod unstage;

src/tests/reverse.rs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
use super::*;
2+
3+
fn snapshot_with_file(snapshot_name: &str, mut ctx: TestContext, filename: &str, keys_input: &str) {
4+
let before = fs::read_to_string(ctx.dir.join(filename)).unwrap();
5+
6+
let mut app = ctx.init_app();
7+
ctx.update(&mut app, keys(keys_input));
8+
9+
let after = fs::read_to_string(ctx.dir.join(filename)).unwrap();
10+
11+
let mut out = ctx.redact_buffer();
12+
out.push_str("\n\n[file before]\n");
13+
out.push_str(&before);
14+
out.push_str("\n[file after]\n");
15+
out.push_str(&after);
16+
17+
insta::assert_snapshot!(snapshot_name, out);
18+
}
19+
20+
fn setup(ctx: TestContext) -> TestContext {
21+
commit(&ctx.dir, "file-one", "FOO\nBAR\nBAZ\n");
22+
fs::write(ctx.dir.join("file-one"), "blahonga\nBAR\nBAZ\n").unwrap();
23+
ctx
24+
}
25+
26+
#[test]
27+
pub(crate) fn reverse_unstaged_delta() {
28+
let ctx = setup(setup_clone!());
29+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
30+
snapshot_with_file(snapshot_name, ctx, "file-one", "jjv");
31+
}
32+
33+
#[test]
34+
pub(crate) fn reverse_unstaged_hunk() {
35+
let ctx = setup(setup_clone!());
36+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
37+
snapshot_with_file(snapshot_name, ctx, "file-one", "jj<tab>jv");
38+
}
39+
40+
#[test]
41+
pub(crate) fn reverse_unstaged_line() {
42+
let ctx = setup(setup_clone!());
43+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
44+
snapshot_with_file(snapshot_name, ctx, "file-one", "jj<tab>j<ctrl+j>v");
45+
}
46+
47+
#[test]
48+
pub(crate) fn reverse_staged_delta() {
49+
let ctx = setup_staged(setup_clone!());
50+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
51+
snapshot_with_file(snapshot_name, ctx, "file-one", "jjv");
52+
}
53+
54+
#[test]
55+
pub(crate) fn reverse_staged_hunk() {
56+
let ctx = setup_staged(setup_clone!());
57+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
58+
snapshot_with_file(snapshot_name, ctx, "file-one", "jj<tab>jv");
59+
}
60+
61+
#[test]
62+
pub(crate) fn reverse_staged_line() {
63+
let ctx = setup_staged(setup_clone!());
64+
let snapshot_name = function_name!().rsplit("::").next().unwrap();
65+
snapshot_with_file(snapshot_name, ctx, "file-one", "jj<tab>j<ctrl+j>v");
66+
}
67+
68+
fn setup_staged(ctx: TestContext) -> TestContext {
69+
commit(&ctx.dir, "file-one", "FOO\nBAR\nBAZ\n");
70+
fs::write(ctx.dir.join("file-one"), "blahonga\nBAR\nBAZ\n").unwrap();
71+
run(&ctx.dir, &["git", "add", "file-one"]);
72+
ctx
73+
}
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/reverse.rs
3+
expression: out
4+
---
5+
On branch main |
6+
Your branch is ahead of 'origin/main' by 1 commit(s). |
7+
|
8+
Unstaged changes (1) |
9+
modified file-one |
10+
▌@@ -1,3 +1,3 @@ |
11+
-blahonga |
12+
+FOO |
13+
BAR |
14+
BAZ |
15+
|
16+
Staged changes (1) |
17+
modified file-one|
18+
|
19+
Recent commits |
20+
9f4a45e main add file-one |
21+
b66a0bf origin/main add initial-file |
22+
|
23+
────────────────────────────────────────────────────────────────────────────────|
24+
$ git apply --reverse |
25+
styles_hash: c3e27c3c64b57280
26+
27+
[file before]
28+
blahonga
29+
BAR
30+
BAZ
31+
32+
[file after]
33+
FOO
34+
BAR
35+
BAZ
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/reverse.rs
3+
expression: out
4+
---
5+
On branch main |
6+
Your branch is ahead of 'origin/main' by 1 commit(s). |
7+
|
8+
Unstaged changes (1) |
9+
modified file-one |
10+
▌@@ -1,3 +1,3 @@ |
11+
-blahonga |
12+
+FOO |
13+
BAR |
14+
BAZ |
15+
|
16+
Staged changes (1) |
17+
modified file-one |
18+
@@ -1,3 +1,3 @@ |
19+
-FOO |
20+
+blahonga |
21+
BAR |
22+
BAZ |
23+
────────────────────────────────────────────────────────────────────────────────|
24+
$ git apply --reverse |
25+
styles_hash: a5e24bbded675128
26+
27+
[file before]
28+
blahonga
29+
BAR
30+
BAZ
31+
32+
[file after]
33+
FOO
34+
BAR
35+
BAZ
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
source: src/tests/reverse.rs
3+
expression: out
4+
---
5+
On branch main |
6+
Your branch is ahead of 'origin/main' by 1 commit(s). |
7+
|
8+
Unstaged changes (1) |
9+
modified file-one |
10+
@@ -1,3 +1,4 @@ |
11+
+FOO |
12+
blahonga |
13+
BAR |
14+
BAZ |
15+
|
16+
Staged changes (1) |
17+
modified file-one |
18+
@@ -1,3 +1,3 @@ |
19+
-FOO |
20+
+blahonga |
21+
BAR |
22+
BAZ |
23+
────────────────────────────────────────────────────────────────────────────────|
24+
$ git apply --reverse --recount |
25+
styles_hash: d8e20f905aae1d59
26+
27+
[file before]
28+
blahonga
29+
BAR
30+
BAZ
31+
32+
[file after]
33+
FOO
34+
blahonga
35+
BAR
36+
BAZ
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/reverse.rs
3+
expression: out
4+
---
5+
On branch main |
6+
Your branch is ahead of 'origin/main' by 1 commit(s). |
7+
|
8+
Recent commits |
9+
▌9f4a45e main add file-one |
10+
b66a0bf origin/main add initial-file |
11+
|
12+
|
13+
|
14+
|
15+
|
16+
|
17+
|
18+
|
19+
|
20+
|
21+
|
22+
|
23+
────────────────────────────────────────────────────────────────────────────────|
24+
$ git apply --reverse |
25+
styles_hash: 127ac9ef0aee93e
26+
27+
[file before]
28+
blahonga
29+
BAR
30+
BAZ
31+
32+
[file after]
33+
FOO
34+
BAR
35+
BAZ
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
source: src/tests/reverse.rs
3+
expression: out
4+
---
5+
On branch main |
6+
Your branch is ahead of 'origin/main' by 1 commit(s). |
7+
|
8+
Recent commits |
9+
9f4a45e main add file-one |
10+
b66a0bf origin/main add initial-file |
11+
|
12+
|
13+
|
14+
|
15+
|
16+
|
17+
|
18+
|
19+
|
20+
|
21+
|
22+
|
23+
────────────────────────────────────────────────────────────────────────────────|
24+
$ git apply --reverse |
25+
styles_hash: 80fde90219deddc2
26+
27+
[file before]
28+
blahonga
29+
BAR
30+
BAZ
31+
32+
[file after]
33+
FOO
34+
BAR
35+
BAZ

0 commit comments

Comments
 (0)