Skip to content

Commit 5f466ff

Browse files
authored
Change diff renamed files (#1040)
1 parent 13afbf6 commit 5f466ff

22 files changed

+497
-122
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ This is was a immediate followup patch release to `0.20` see [release notes](htt
6262
- support inspecting annotation of tag ([#1076](https://github.com/extrawurst/gitui/issues/1076))
6363
- support deleting tag on remote ([#1074](https://github.com/extrawurst/gitui/issues/1074))
6464
- support git credentials helper (https) ([#800](https://github.com/extrawurst/gitui/issues/800))
65+
- Correct diff of renamed files [[@Mifom](https://github.com/Mifom)] ([#1038](https://github.com/extrawurst/gitui/issues/1038))
6566

6667
### Fixed
6768
- Keep commit message when pre-commit hook fails ([#1035](https://github.com/extrawurst/gitui/issues/1035))

asyncgit/src/diff.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -29,8 +29,10 @@ pub enum DiffType {
2929
///
3030
#[derive(Debug, Hash, Clone, PartialEq)]
3131
pub struct DiffParams {
32+
///
33+
pub src_path: String,
3234
/// path to the file to diff
33-
pub path: String,
35+
pub dst_path: String,
3436
/// what kind of diff
3537
pub diff_type: DiffType,
3638
/// diff options
@@ -161,26 +163,28 @@ impl AsyncDiff {
161163
let res = match params.diff_type {
162164
DiffType::Stage => sync::diff::get_diff(
163165
repo_path,
164-
&params.path,
166+
&params.src_path,
167+
&params.dst_path,
165168
true,
166169
Some(params.options),
167170
)?,
168171
DiffType::WorkDir => sync::diff::get_diff(
169172
repo_path,
170-
&params.path,
173+
&params.src_path,
174+
&params.dst_path,
171175
false,
172176
Some(params.options),
173177
)?,
174178
DiffType::Commit(id) => sync::diff::get_diff_commit(
175179
repo_path,
176180
id,
177-
params.path.clone(),
181+
params.dst_path.clone(),
178182
Some(params.options),
179183
)?,
180184
DiffType::Commits(ids) => sync::diff::get_diff_commits(
181185
repo_path,
182186
ids,
183-
params.path.clone(),
187+
params.dst_path.clone(),
184188
Some(params.options),
185189
)?,
186190
};

asyncgit/src/sync/commit_files.rs

Lines changed: 18 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -26,21 +26,24 @@ pub fn get_commit_files(
2626
get_commit_diff(repo_path, &repo, id, None, None)?
2727
};
2828

29-
let res = diff
30-
.deltas()
31-
.map(|delta| {
32-
let status = StatusItemType::from(delta.status());
33-
34-
StatusItem {
35-
path: delta
36-
.new_file()
37-
.path()
38-
.map(|p| p.to_str().unwrap_or("").to_string())
39-
.unwrap_or_default(),
40-
status,
41-
}
42-
})
43-
.collect::<Vec<_>>();
29+
let res =
30+
diff.deltas()
31+
.map(|delta| {
32+
let status = StatusItemType::from(delta.status());
33+
34+
StatusItem {
35+
old_path: delta.old_file().path().map(|p| {
36+
p.to_str().unwrap_or("").to_string()
37+
}),
38+
new_path: delta
39+
.new_file()
40+
.path()
41+
.map(|p| p.to_str().unwrap_or("").to_string())
42+
.unwrap_or_default(),
43+
status,
44+
}
45+
})
46+
.collect::<Vec<_>>();
4447

4548
Ok(res)
4649
}

asyncgit/src/sync/diff.rs

Lines changed: 78 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use crate::{
1010
};
1111
use easy_cast::Conv;
1212
use git2::{
13-
Delta, Diff, DiffDelta, DiffFormat, DiffHunk, Patch, Repository,
13+
Delta, Diff, DiffDelta, DiffFindOptions, DiffFormat, DiffHunk,
14+
Patch, Repository,
1415
};
1516
use scopetime::scope_time;
1617
use std::{cell::RefCell, fs, path::Path, rc::Rc};
@@ -149,7 +150,8 @@ impl Default for DiffOptions {
149150

150151
pub(crate) fn get_diff_raw<'a>(
151152
repo: &'a Repository,
152-
p: &str,
153+
src: &str,
154+
dst: &str,
153155
stage: bool,
154156
reverse: bool,
155157
options: Option<DiffOptions>,
@@ -162,10 +164,11 @@ pub(crate) fn get_diff_raw<'a>(
162164
opt.ignore_whitespace(options.ignore_whitespace);
163165
opt.interhunk_lines(options.interhunk_lines);
164166
}
165-
opt.pathspec(p);
167+
opt.pathspec(src);
168+
opt.pathspec(dst);
166169
opt.reverse(reverse);
167170

168-
let diff = if stage {
171+
let mut diff = if stage {
169172
// diff against head
170173
if let Ok(id) = get_head_repo(repo) {
171174
let parent = repo.find_commit(id.into())?;
@@ -189,21 +192,25 @@ pub(crate) fn get_diff_raw<'a>(
189192
repo.diff_index_to_workdir(None, Some(&mut opt))?
190193
};
191194

195+
diff.find_similar(Some(
196+
DiffFindOptions::new().renames(true).for_untracked(true),
197+
))?;
192198
Ok(diff)
193199
}
194200

195201
/// returns diff of a specific file either in `stage` or workdir
196202
pub fn get_diff(
197203
repo_path: &RepoPath,
198-
p: &str,
204+
src: &str,
205+
dst: &str,
199206
stage: bool,
200207
options: Option<DiffOptions>,
201208
) -> Result<FileDiff> {
202209
scope_time!("get_diff");
203210

204211
let repo = repo(repo_path)?;
205212
let work_dir = work_dir(&repo)?;
206-
let diff = get_diff_raw(&repo, p, stage, false, options)?;
213+
let diff = get_diff_raw(&repo, src, dst, stage, false, options)?;
207214

208215
raw_diff_to_file_diff(&diff, work_dir)
209216
}
@@ -250,8 +257,8 @@ pub fn get_diff_commits(
250257
///
251258
//TODO: refactor into helper type with the inline closures as dedicated functions
252259
#[allow(clippy::too_many_lines)]
253-
fn raw_diff_to_file_diff<'a>(
254-
diff: &'a Diff,
260+
fn raw_diff_to_file_diff(
261+
diff: &Diff,
255262
work_dir: &Path,
256263
) -> Result<FileDiff> {
257264
let res = Rc::new(RefCell::new(FileDiff::default()));
@@ -422,7 +429,7 @@ mod tests {
422429
},
423430
};
424431
use std::{
425-
fs::{self, File},
432+
fs::{self, remove_file, File},
426433
io::Write,
427434
path::Path,
428435
};
@@ -444,8 +451,14 @@ mod tests {
444451

445452
assert_eq!(get_statuses(repo_path), (1, 0));
446453

447-
let diff =
448-
get_diff(repo_path, "foo/bar.txt", false, None).unwrap();
454+
let diff = get_diff(
455+
repo_path,
456+
"foo/bar.txt",
457+
"foo/bar.txt",
458+
false,
459+
None,
460+
)
461+
.unwrap();
449462

450463
assert_eq!(diff.hunks.len(), 1);
451464
assert_eq!(&*diff.hunks[0].lines[1].content, "test");
@@ -475,6 +488,7 @@ mod tests {
475488
let diff = get_diff(
476489
repo_path,
477490
file_path.to_str().unwrap(),
491+
file_path.to_str().unwrap(),
478492
true,
479493
None,
480494
)
@@ -530,7 +544,8 @@ mod tests {
530544
let res = get_status(repo_path, StatusType::WorkingDir, None)
531545
.unwrap();
532546
assert_eq!(res.len(), 1);
533-
assert_eq!(res[0].path, "bar.txt");
547+
assert_eq!(res[0].new_path, "bar.txt");
548+
assert_eq!(res[0].old_path, Some("bar.txt".to_string()));
534549

535550
stage_add_file(repo_path, Path::new("bar.txt")).unwrap();
536551
assert_eq!(get_statuses(repo_path), (0, 1));
@@ -546,7 +561,8 @@ mod tests {
546561
assert_eq!(get_statuses(repo_path), (1, 1));
547562

548563
let res =
549-
get_diff(repo_path, "bar.txt", false, None).unwrap();
564+
get_diff(repo_path, "bar.txt", "bar.txt", false, None)
565+
.unwrap();
550566

551567
assert_eq!(res.hunks.len(), 2)
552568
}
@@ -568,6 +584,7 @@ mod tests {
568584
let diff = get_diff(
569585
&sub_path.to_str().unwrap().into(),
570586
file_path.to_str().unwrap(),
587+
file_path.to_str().unwrap(),
571588
false,
572589
None,
573590
)
@@ -596,6 +613,7 @@ mod tests {
596613
let diff = get_diff(
597614
repo_path,
598615
file_path.to_str().unwrap(),
616+
file_path.to_str().unwrap(),
599617
false,
600618
None,
601619
)
@@ -622,6 +640,7 @@ mod tests {
622640
let diff = get_diff(
623641
repo_path,
624642
file_path.to_str().unwrap(),
643+
file_path.to_str().unwrap(),
625644
false,
626645
None,
627646
)
@@ -665,4 +684,50 @@ mod tests {
665684

666685
Ok(())
667686
}
687+
688+
#[test]
689+
fn test_rename() {
690+
let (_td, repo) = repo_init().unwrap();
691+
let root = repo.path().parent().unwrap();
692+
let repo_path: &RepoPath =
693+
&root.as_os_str().to_str().unwrap().into();
694+
695+
assert_eq!(get_statuses(repo_path), (0, 0));
696+
697+
let file_path = root.join("bar.txt");
698+
699+
{
700+
File::create(&file_path)
701+
.unwrap()
702+
.write_all(HUNK_A.as_bytes())
703+
.unwrap();
704+
}
705+
706+
let res = get_status(repo_path, StatusType::WorkingDir, None)
707+
.unwrap();
708+
assert_eq!(res.len(), 1);
709+
assert_eq!(res[0].new_path, "bar.txt");
710+
assert_eq!(res[0].old_path, Some("bar.txt".to_string()));
711+
712+
stage_add_file(repo_path, Path::new("bar.txt")).unwrap();
713+
assert_eq!(get_statuses(repo_path), (0, 1));
714+
715+
// Move file
716+
let other_file_path = root.join("baz.txt");
717+
{
718+
File::create(&other_file_path)
719+
.unwrap()
720+
.write_all(HUNK_A.as_bytes())
721+
.unwrap();
722+
remove_file(&file_path).unwrap();
723+
}
724+
725+
assert_eq!(get_statuses(repo_path), (1, 1));
726+
727+
let res =
728+
get_diff(repo_path, "bar.txt", "baz.txt", false, None)
729+
.unwrap();
730+
731+
assert_eq!(res.hunks.len(), 0)
732+
}
668733
}

asyncgit/src/sync/hunks.rs

Lines changed: 35 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -13,14 +13,22 @@ use scopetime::scope_time;
1313
///
1414
pub fn stage_hunk(
1515
repo_path: &RepoPath,
16-
file_path: &str,
16+
src_file_path: &str,
17+
dst_file_path: &str,
1718
hunk_hash: u64,
1819
) -> Result<()> {
1920
scope_time!("stage_hunk");
2021

2122
let repo = repo(repo_path)?;
2223

23-
let diff = get_diff_raw(&repo, file_path, false, false, None)?;
24+
let diff = get_diff_raw(
25+
&repo,
26+
src_file_path,
27+
dst_file_path,
28+
false,
29+
false,
30+
None,
31+
)?;
2432

2533
let mut opt = ApplyOptions::new();
2634
opt.hunk_callback(|hunk| {
@@ -45,7 +53,9 @@ pub fn reset_hunk(
4553

4654
let repo = repo(repo_path)?;
4755

48-
let diff = get_diff_raw(&repo, file_path, false, false, None)?;
56+
let diff = get_diff_raw(
57+
&repo, file_path, file_path, false, false, None,
58+
)?;
4959

5060
let hunk_index = find_hunk_index(&diff, hunk_hash);
5161
if let Some(hunk_index) = hunk_index {
@@ -57,7 +67,9 @@ pub fn reset_hunk(
5767
res
5868
});
5969

60-
let diff = get_diff_raw(&repo, file_path, false, true, None)?;
70+
let diff = get_diff_raw(
71+
&repo, file_path, file_path, false, true, None,
72+
)?;
6173

6274
repo.apply(&diff, ApplyLocation::WorkDir, Some(&mut opt))?;
6375

@@ -96,14 +108,22 @@ fn find_hunk_index(diff: &Diff, hunk_hash: u64) -> Option<usize> {
96108
///
97109
pub fn unstage_hunk(
98110
repo_path: &RepoPath,
99-
file_path: &str,
111+
src_file_path: &str,
112+
dst_file_path: &str,
100113
hunk_hash: u64,
101114
) -> Result<bool> {
102115
scope_time!("revert_hunk");
103116

104117
let repo = repo(repo_path)?;
105118

106-
let diff = get_diff_raw(&repo, file_path, true, false, None)?;
119+
let diff = get_diff_raw(
120+
&repo,
121+
src_file_path,
122+
dst_file_path,
123+
true,
124+
false,
125+
None,
126+
)?;
107127
let diff_count_positive = diff.deltas().len();
108128

109129
let hunk_index = find_hunk_index(&diff, hunk_hash);
@@ -112,7 +132,14 @@ pub fn unstage_hunk(
112132
Ok,
113133
)?;
114134

115-
let diff = get_diff_raw(&repo, file_path, true, true, None)?;
135+
let diff = get_diff_raw(
136+
&repo,
137+
src_file_path,
138+
dst_file_path,
139+
true,
140+
true,
141+
None,
142+
)?;
116143

117144
if diff.deltas().len() != diff_count_positive {
118145
return Err(Error::Generic(format!(
@@ -174,6 +201,7 @@ mod tests {
174201
let diff = get_diff(
175202
sub_path,
176203
file_path.to_str().unwrap(),
204+
file_path.to_str().unwrap(),
177205
false,
178206
None,
179207
)?;

asyncgit/src/sync/merge.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ pub fn abort_pending_state(repo_path: &RepoPath) -> Result<()> {
4242
let repo = repo(repo_path)?;
4343

4444
reset_stage(repo_path, "*")?;
45-
reset_workdir(repo_path, "*")?;
45+
reset_workdir(repo_path, None, "*")?;
4646

4747
repo.cleanup_state()?;
4848

0 commit comments

Comments
 (0)