Line data Source code
1 : use std::collections::HashSet;
2 : use std::hash::{Hash, Hasher};
3 : use std::path::{Path, PathBuf};
4 : use std::time::SystemTime;
5 :
6 : #[derive(Debug, Clone)]
7 : pub struct DiffSnapshot {
8 : #[allow(dead_code)]
9 : pub timestamp: SystemTime,
10 : pub files: Vec<FileChange>,
11 : }
12 :
13 : /// Metadata about a git commit for the review mode commit picker
14 : #[derive(Debug, Clone)]
15 : pub struct CommitInfo {
16 : pub sha: String,
17 : pub short_sha: String,
18 : pub summary: String,
19 : pub author: String,
20 : }
21 :
22 : #[derive(Debug, Clone)]
23 : pub struct FileChange {
24 : pub path: PathBuf,
25 : pub status: String,
26 : pub hunks: Vec<Hunk>,
27 : }
28 :
29 : #[derive(Debug, Clone)]
30 : pub struct Hunk {
31 : pub old_start: usize,
32 : pub new_start: usize,
33 : pub lines: Vec<String>,
34 : pub seen: bool,
35 : pub staged: bool,
36 : /// Track which individual lines are staged (by index in lines vec)
37 : pub staged_line_indices: HashSet<usize>,
38 : /// In-memory tracking for review mode: whether this hunk has been accepted
39 : pub accepted: bool,
40 : #[allow(dead_code)]
41 : pub id: HunkId,
42 : }
43 :
44 : impl Hunk {
45 : #[allow(dead_code)]
46 1 : pub fn format(&self) -> String {
47 1 : self.lines.join("")
48 1 : }
49 :
50 1 : pub fn count_changes(&self) -> usize {
51 1 : let mut add_lines = 0;
52 1 : let mut remove_lines = 0;
53 :
54 3 : for line in &self.lines {
55 3 : if line.starts_with('+') && !line.starts_with("+++") {
56 2 : add_lines += 1;
57 2 : } else if line.starts_with('-') && !line.starts_with("---") {
58 1 : remove_lines += 1;
59 1 : }
60 : }
61 :
62 : // Count pairs of add/remove as 1 change, plus any unpaired lines
63 1 : let pairs = add_lines.min(remove_lines);
64 1 : let unpaired = (add_lines + remove_lines) - (2 * pairs);
65 1 : pairs + unpaired
66 1 : }
67 :
68 67 : pub fn new(old_start: usize, new_start: usize, lines: Vec<String>, file_path: &Path) -> Self {
69 67 : let id = HunkId::new(file_path, old_start, new_start, &lines);
70 67 : Self {
71 67 : old_start,
72 67 : new_start,
73 67 : lines,
74 67 : seen: false,
75 67 : staged: false,
76 67 : staged_line_indices: HashSet::new(),
77 67 : accepted: false,
78 67 : id,
79 67 : }
80 67 : }
81 : }
82 :
83 : /// Unique identifier for a hunk based on file path, line numbers, and content hash
84 : #[derive(Debug, Clone, PartialEq, Eq, Hash)]
85 : pub struct HunkId {
86 : pub index_only: bool,
87 : pub file_path: PathBuf,
88 : pub old_start: usize,
89 : pub new_start: usize,
90 : pub content_hash: u64,
91 : }
92 :
93 : impl HunkId {
94 71 : pub fn new(file_path: &Path, old_start: usize, new_start: usize, lines: &[String]) -> Self {
95 : use std::collections::hash_map::DefaultHasher;
96 :
97 71 : let mut hasher = DefaultHasher::new();
98 200 : for line in lines {
99 200 : line.hash(&mut hasher);
100 200 : }
101 71 : let content_hash = hasher.finish();
102 :
103 71 : Self {
104 71 : index_only: false,
105 71 : file_path: file_path.to_path_buf(),
106 71 : old_start,
107 71 : new_start,
108 71 : content_hash,
109 71 : }
110 71 : }
111 : }
112 :
113 : /// Tracks which hunks have been seen by the user
114 : #[derive(Debug, Clone)]
115 : pub struct SeenTracker {
116 : seen_hunks: HashSet<HunkId>,
117 : }
118 :
119 : #[allow(dead_code)]
120 : impl SeenTracker {
121 2 : pub fn new() -> Self {
122 2 : Self {
123 2 : seen_hunks: HashSet::new(),
124 2 : }
125 2 : }
126 :
127 3 : pub fn mark_seen(&mut self, hunk_id: &HunkId) {
128 3 : self.seen_hunks.insert(hunk_id.clone());
129 3 : }
130 :
131 6 : pub fn is_seen(&self, hunk_id: &HunkId) -> bool {
132 6 : self.seen_hunks.contains(hunk_id)
133 6 : }
134 :
135 1 : pub fn clear(&mut self) {
136 1 : self.seen_hunks.clear();
137 1 : }
138 :
139 : #[allow(dead_code)]
140 1 : pub fn remove_file_hunks(&mut self, file_path: &PathBuf) {
141 1 : self.seen_hunks
142 1 : .retain(|hunk_id| &hunk_id.file_path != file_path);
143 1 : }
144 : }
145 :
146 : impl Default for SeenTracker {
147 1 : fn default() -> Self {
148 1 : Self::new()
149 1 : }
150 : }
151 :
152 : #[cfg(test)]
153 : #[path = "../tests/diff.rs"]
154 : mod tests;
|