Skip to content

Commit 6e5db96

Browse files
author
Stephan Dilly
authored
support discard selected lines (#571)
1 parent f86faf6 commit 6e5db96

File tree

19 files changed

+763
-80
lines changed

19 files changed

+763
-80
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## Unreleased
99

1010
### Added
11+
- support discarding diff by lines ([#59](https://github.com/extrawurst/gitui/issues/59))
1112
- support for pushing tags ([#568](https://github.com/extrawurst/gitui/issues/568))
1213

1314
## [0.12.0] - 2020-03-03

assets/vim_style_key_config.ron

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,8 +50,8 @@
5050
edit_file: ( code: Char('I'), modifiers: ( bits: 1,),),
5151

5252
status_stage_all: ( code: Char('a'), modifiers: ( bits: 0,),),
53-
5453
status_reset_item: ( code: Char('U'), modifiers: ( bits: 1,),),
54+
status_reset_lines: ( code: Char('u'), modifiers: ( bits: 0,),),
5555
status_ignore_file: ( code: Char('i'), modifiers: ( bits: 0,),),
5656

5757
stashing_save: ( code: Char('w'), modifiers: ( bits: 0,),),

asyncgit/src/error.rs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
use std::string::FromUtf8Error;
1+
use std::{num::TryFromIntError, string::FromUtf8Error};
22
use thiserror::Error;
33

44
#[derive(Error, Debug)]
@@ -26,6 +26,9 @@ pub enum Error {
2626

2727
#[error("utf8 error:{0}")]
2828
Utf8Error(#[from] FromUtf8Error),
29+
30+
#[error("TryFromInt error:{0}")]
31+
IntError(#[from] TryFromIntError),
2932
}
3033

3134
pub type Result<T> = std::result::Result<T, Error>;

asyncgit/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
#![forbid(missing_docs)]
44
#![deny(unsafe_code)]
55
#![deny(unused_imports)]
6+
#![deny(unused_must_use)]
67
#![deny(clippy::all)]
78
#![deny(clippy::unwrap_used)]
89
#![deny(clippy::panic)]

asyncgit/src/sync/branch/merge_commit.rs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,13 @@ pub fn merge_upstream_commit(
9292

9393
#[cfg(test)]
9494
mod test {
95-
use super::super::merge_ff::test::write_commit_file;
9695
use super::*;
9796
use crate::sync::{
9897
branch_compare_upstream,
9998
remotes::{fetch_origin, push::push},
10099
tests::{
101100
debug_cmd_print, get_commit_ids, repo_clone,
102-
repo_init_bare,
101+
repo_init_bare, write_commit_file,
103102
},
104103
RepoState,
105104
};

asyncgit/src/sync/branch/merge_ff.rs

Lines changed: 1 addition & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -49,41 +49,12 @@ pub fn branch_merge_upstream_fastforward(
4949
pub mod test {
5050
use super::*;
5151
use crate::sync::{
52-
commit,
5352
remotes::{fetch_origin, push::push},
54-
stage_add_file,
5553
tests::{
5654
debug_cmd_print, get_commit_ids, repo_clone,
57-
repo_init_bare,
55+
repo_init_bare, write_commit_file,
5856
},
59-
CommitId,
6057
};
61-
use git2::Repository;
62-
use std::{fs::File, io::Write, path::Path};
63-
64-
// write, stage and commit a file
65-
pub fn write_commit_file(
66-
repo: &Repository,
67-
file: &str,
68-
content: &str,
69-
commit_name: &str,
70-
) -> CommitId {
71-
File::create(
72-
repo.workdir().unwrap().join(file).to_str().unwrap(),
73-
)
74-
.unwrap()
75-
.write_all(content.as_bytes())
76-
.unwrap();
77-
78-
stage_add_file(
79-
repo.workdir().unwrap().to_str().unwrap(),
80-
Path::new(file),
81-
)
82-
.unwrap();
83-
84-
commit(repo.workdir().unwrap().to_str().unwrap(), commit_name)
85-
.unwrap()
86-
}
8758

8859
#[test]
8960
fn test_merge_fastforward() {

asyncgit/src/sync/diff.rs

Lines changed: 37 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -39,14 +39,41 @@ pub struct DiffLine {
3939
pub content: String,
4040
///
4141
pub line_type: DiffLineType,
42+
///
43+
pub position: DiffLinePosition,
44+
}
45+
46+
///
47+
#[derive(Clone, Copy, Default, Hash, Debug, PartialEq, Eq)]
48+
pub struct DiffLinePosition {
49+
///
50+
pub old_lineno: Option<u32>,
51+
///
52+
pub new_lineno: Option<u32>,
53+
}
54+
55+
impl PartialEq<&git2::DiffLine<'_>> for DiffLinePosition {
56+
fn eq(&self, other: &&git2::DiffLine) -> bool {
57+
other.new_lineno() == self.new_lineno
58+
&& other.old_lineno() == self.old_lineno
59+
}
60+
}
61+
62+
impl From<&git2::DiffLine<'_>> for DiffLinePosition {
63+
fn from(line: &git2::DiffLine<'_>) -> Self {
64+
Self {
65+
old_lineno: line.old_lineno(),
66+
new_lineno: line.new_lineno(),
67+
}
68+
}
4269
}
4370

4471
#[derive(Debug, Default, Clone, Copy, PartialEq, Hash)]
4572
pub(crate) struct HunkHeader {
46-
old_start: u32,
47-
old_lines: u32,
48-
new_start: u32,
49-
new_lines: u32,
73+
pub old_start: u32,
74+
pub old_lines: u32,
75+
pub new_start: u32,
76+
pub new_lines: u32,
5077
}
5178

5279
impl From<DiffHunk<'_>> for HunkHeader {
@@ -89,10 +116,14 @@ pub(crate) fn get_diff_raw<'a>(
89116
p: &str,
90117
stage: bool,
91118
reverse: bool,
119+
context: Option<u32>,
92120
) -> Result<Diff<'a>> {
93121
// scope_time!("get_diff_raw");
94122

95123
let mut opt = DiffOptions::new();
124+
if let Some(context) = context {
125+
opt.context_lines(context);
126+
}
96127
opt.pathspec(p);
97128
opt.reverse(reverse);
98129

@@ -133,7 +164,7 @@ pub fn get_diff(
133164

134165
let repo = utils::repo(repo_path)?;
135166
let work_dir = work_dir(&repo)?;
136-
let diff = get_diff_raw(&repo, &p, stage, false)?;
167+
let diff = get_diff_raw(&repo, &p, stage, false, None)?;
137168

138169
raw_diff_to_file_diff(&diff, work_dir)
139170
}
@@ -209,6 +240,7 @@ fn raw_diff_to_file_diff<'a>(
209240
};
210241

211242
let diff_line = DiffLine {
243+
position: DiffLinePosition::from(&line),
212244
content: String::from_utf8_lossy(line.content())
213245
.to_string(),
214246
line_type,

asyncgit/src/sync/hunks.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ pub fn stage_hunk(
1919

2020
let repo = repo(repo_path)?;
2121

22-
let diff = get_diff_raw(&repo, &file_path, false, false)?;
22+
let diff = get_diff_raw(&repo, &file_path, false, false, None)?;
2323

2424
let mut opt = ApplyOptions::new();
2525
opt.hunk_callback(|hunk| {
@@ -46,7 +46,7 @@ pub fn reset_hunk(
4646

4747
let repo = repo(repo_path)?;
4848

49-
let diff = get_diff_raw(&repo, &file_path, false, false)?;
49+
let diff = get_diff_raw(&repo, &file_path, false, false, None)?;
5050

5151
let hunk_index = find_hunk_index(&diff, hunk_hash);
5252
if let Some(hunk_index) = hunk_index {
@@ -58,7 +58,8 @@ pub fn reset_hunk(
5858
res
5959
});
6060

61-
let diff = get_diff_raw(&repo, &file_path, false, true)?;
61+
let diff =
62+
get_diff_raw(&repo, &file_path, false, true, None)?;
6263

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

@@ -104,7 +105,7 @@ pub fn unstage_hunk(
104105

105106
let repo = repo(repo_path)?;
106107

107-
let diff = get_diff_raw(&repo, &file_path, true, false)?;
108+
let diff = get_diff_raw(&repo, &file_path, true, false, None)?;
108109
let diff_count_positive = diff.deltas().len();
109110

110111
let hunk_index = find_hunk_index(&diff, hunk_hash);
@@ -113,7 +114,7 @@ pub fn unstage_hunk(
113114
Ok,
114115
)?;
115116

116-
let diff = get_diff_raw(&repo, &file_path, true, true)?;
117+
let diff = get_diff_raw(&repo, &file_path, true, true, None)?;
117118

118119
if diff.deltas().len() != diff_count_positive {
119120
return Err(Error::Generic(format!(

asyncgit/src/sync/mod.rs

Lines changed: 25 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,10 @@ mod hooks;
1414
mod hunks;
1515
mod ignore;
1616
mod logwalker;
17+
mod patches;
1718
pub mod remotes;
1819
mod reset;
20+
mod staging;
1921
mod stash;
2022
mod state;
2123
pub mod status;
@@ -47,6 +49,7 @@ pub use remotes::{
4749
tags::PushTagsProgress,
4850
};
4951
pub use reset::{reset_stage, reset_workdir};
52+
pub use staging::discard_lines;
5053
pub use stash::{get_stashes, stash_apply, stash_drop, stash_save};
5154
pub use state::{repo_state, RepoState};
5255
pub use tags::{get_tags, CommitTags, Tags};
@@ -58,12 +61,14 @@ pub use utils::{
5861
#[cfg(test)]
5962
mod tests {
6063
use super::{
64+
commit, stage_add_file,
65+
staging::repo_write_file,
6166
status::{get_status, StatusType},
6267
CommitId, LogWalker,
6368
};
6469
use crate::error::Result;
6570
use git2::Repository;
66-
use std::process::Command;
71+
use std::{path::Path, process::Command};
6772
use tempfile::TempDir;
6873

6974
/// Calling `set_search_path` with an empty directory makes sure that there
@@ -88,6 +93,25 @@ mod tests {
8893
});
8994
}
9095

96+
/// write, stage and commit a file
97+
pub fn write_commit_file(
98+
repo: &Repository,
99+
file: &str,
100+
content: &str,
101+
commit_name: &str,
102+
) -> CommitId {
103+
repo_write_file(repo, file, content).unwrap();
104+
105+
stage_add_file(
106+
repo.workdir().unwrap().to_str().unwrap(),
107+
Path::new(file),
108+
)
109+
.unwrap();
110+
111+
commit(repo.workdir().unwrap().to_str().unwrap(), commit_name)
112+
.unwrap()
113+
}
114+
91115
///
92116
pub fn repo_init_empty() -> Result<(TempDir, Repository)> {
93117
sandbox_config_files();

asyncgit/src/sync/patches.rs

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
use super::diff::{get_diff_raw, HunkHeader};
2+
use crate::error::{Error, Result};
3+
use git2::{Diff, DiffLine, Patch, Repository};
4+
5+
//
6+
pub(crate) struct HunkLines<'a> {
7+
pub hunk: HunkHeader,
8+
pub lines: Vec<DiffLine<'a>>,
9+
}
10+
11+
///
12+
pub(crate) fn get_file_diff_patch_and_hunklines<'a>(
13+
repo: &'a Repository,
14+
file: &str,
15+
is_staged: bool,
16+
reverse: bool,
17+
) -> Result<(Patch<'a>, Vec<HunkLines<'a>>)> {
18+
let diff =
19+
get_diff_raw(&repo, file, is_staged, reverse, Some(1))?;
20+
let patches = get_patches(&diff)?;
21+
if patches.len() > 1 {
22+
return Err(Error::Generic(String::from("patch error")));
23+
}
24+
25+
let patch = patches.into_iter().next().ok_or_else(|| {
26+
Error::Generic(String::from("no patch found"))
27+
})?;
28+
29+
let lines = patch_get_hunklines(&patch)?;
30+
31+
Ok((patch, lines))
32+
}
33+
34+
//
35+
fn patch_get_hunklines<'a>(
36+
patch: &Patch<'a>,
37+
) -> Result<Vec<HunkLines<'a>>> {
38+
let count_hunks = patch.num_hunks();
39+
let mut res = Vec::with_capacity(count_hunks);
40+
for hunk_idx in 0..count_hunks {
41+
let (hunk, _) = patch.hunk(hunk_idx)?;
42+
43+
let count_lines = patch.num_lines_in_hunk(hunk_idx)?;
44+
45+
let mut hunk = HunkLines {
46+
hunk: HunkHeader::from(hunk),
47+
lines: Vec::with_capacity(count_lines),
48+
};
49+
50+
for line_idx in 0..count_lines {
51+
let line = patch.line_in_hunk(hunk_idx, line_idx)?;
52+
hunk.lines.push(line);
53+
}
54+
55+
res.push(hunk);
56+
}
57+
58+
Ok(res)
59+
}
60+
61+
//
62+
fn get_patches<'a>(diff: &Diff<'a>) -> Result<Vec<Patch<'a>>> {
63+
let count = diff.deltas().len();
64+
65+
let mut res = Vec::with_capacity(count);
66+
for idx in 0..count {
67+
let p = Patch::from_diff(&diff, idx)?;
68+
if let Some(p) = p {
69+
res.push(p);
70+
}
71+
}
72+
73+
Ok(res)
74+
}

0 commit comments

Comments
 (0)