Skip to content

Commit 8605f57

Browse files
committed
Stage or unstage individual files from recursively generated directory file list
1 parent 1b065d6 commit 8605f57

5 files changed

Lines changed: 113 additions & 35 deletions

File tree

Cargo.lock

Lines changed: 29 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,3 +19,4 @@ indexmap = "2.12.1"
1919
dirs = "6.0.0"
2020
toml = "0.9.10"
2121
serde = "1.0.228"
22+
walkdir = "2.5.0"

src/app/app_input.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1153,7 +1153,7 @@ impl App {
11531153
}
11541154
Focus::StatusTop => {
11551155
let file: String = {
1156-
let mut idx = self.status_bottom_selected;
1156+
let mut idx = self.status_top_selected;
11571157
let modified = &self.uncommitted.staged.modified;
11581158
let added = &self.uncommitted.staged.added;
11591159
let deleted = &self.uncommitted.staged.deleted;

src/git/actions/commits.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -468,8 +468,13 @@ pub fn cherry_pick_commit(
468468
pub fn stage_file(repo: &Repository, path: &std::path::Path) -> Result<(), git2::Error> {
469469
let mut index = repo.index()?;
470470

471-
// Equivalent to: git add <path>
472-
index.add_path(path)?;
471+
// If the file exists, add it (new or modified)
472+
if path.exists() {
473+
index.add_path(path)?;
474+
} else {
475+
// File deleted: remove from index
476+
index.remove_path(path)?;
477+
}
473478

474479
index.write()?;
475480
Ok(())

src/git/queries/diffs.rs

Lines changed: 75 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -43,60 +43,103 @@ pub fn get_filenames_diff_at_workdir(repo: &Repository) -> Result<UncommittedCha
4343
.renames_head_to_index(false)
4444
.renames_index_to_workdir(false);
4545

46-
// Retrieve the current status of the working directory and index
4746
let statuses = repo.statuses(Some(&mut options))?;
4847
let mut changes = UncommittedChanges::default();
48+
let workdir = repo.workdir().expect("Bare repo not supported");
4949

50-
// Iterate through each file entry in the status list
5150
for entry in statuses.iter() {
52-
let status = entry.status();
53-
let path = entry.path().unwrap_or("").to_string();
54-
55-
// Skip unchanged files
56-
if status.is_empty() {
57-
continue;
58-
}
59-
60-
// Record staged changes (index vs HEAD)
61-
if status.is_index_modified() {
62-
changes.staged.modified.push(path.clone());
63-
}
64-
if status.is_index_new() {
65-
changes.staged.added.push(path.clone());
66-
}
67-
if status.is_index_deleted() {
68-
changes.staged.deleted.push(path.clone());
69-
}
70-
71-
// Record unstaged changes (workdir vs index)
72-
if status.is_wt_modified() {
73-
changes.unstaged.modified.push(path.clone());
74-
}
75-
if status.is_wt_new() {
76-
changes.unstaged.added.push(path.clone());
77-
}
78-
if status.is_wt_deleted() {
79-
changes.unstaged.deleted.push(path.clone());
51+
let rel_path = entry.path().unwrap_or("");
52+
let full_path = workdir.join(rel_path);
53+
54+
// Expand directories
55+
let files = if full_path.is_dir() {
56+
collect_files_for_status(repo, workdir, rel_path)
57+
} else {
58+
vec![rel_path.to_string()]
59+
};
60+
61+
for file in files {
62+
63+
// Ask git for this file’s individual status
64+
let file_status = repo.status_file(Path::new(&file))?;
65+
66+
// Now you can safely check staged vs unstaged per file
67+
if file_status.is_index_modified() {
68+
changes.staged.modified.push(file.clone());
69+
}
70+
if file_status.is_index_new() {
71+
changes.staged.added.push(file.clone());
72+
}
73+
if file_status.is_index_deleted() {
74+
changes.staged.deleted.push(file.clone());
75+
}
76+
77+
if file_status.is_wt_modified() {
78+
changes.unstaged.modified.push(file.clone());
79+
}
80+
if file_status.is_wt_new() {
81+
changes.unstaged.added.push(file.clone());
82+
}
83+
if file_status.is_wt_deleted() {
84+
changes.unstaged.deleted.push(file.clone());
85+
}
8086
}
8187
}
8288

83-
// Compute counts of deduplicated filenames
89+
// Counts
8490
changes.modified_count = deduplicate(&changes.staged.modified, &changes.unstaged.modified);
8591
changes.added_count = deduplicate(&changes.staged.added, &changes.unstaged.added);
8692
changes.deleted_count = deduplicate(&changes.staged.deleted, &changes.unstaged.deleted);
8793

88-
// Set flags for change states
8994
changes.is_staged = !changes.staged.modified.is_empty()
9095
|| !changes.staged.added.is_empty()
9196
|| !changes.staged.deleted.is_empty();
97+
9298
changes.is_unstaged = !changes.unstaged.modified.is_empty()
9399
|| !changes.unstaged.added.is_empty()
94100
|| !changes.unstaged.deleted.is_empty();
101+
95102
changes.is_clean = !changes.is_staged && !changes.is_unstaged;
96103

97104
Ok(changes)
98105
}
99106

107+
fn collect_files_for_status(repo: &Repository, workdir: &Path, rel_path: &str) -> Vec<String> {
108+
let full_path = workdir.join(rel_path);
109+
110+
if full_path.exists() {
111+
if full_path.is_file() {
112+
return vec![rel_path.to_string()];
113+
} else if full_path.is_dir() {
114+
let mut result = Vec::new();
115+
if let Ok(entries) = std::fs::read_dir(&full_path) {
116+
for entry in entries.flatten() {
117+
let path = entry.path();
118+
let child_rel = match path.strip_prefix(workdir) {
119+
Ok(p) => p.to_string_lossy().to_string(),
120+
Err(_) => continue,
121+
};
122+
123+
// Skip ignored files
124+
if repo.status_should_ignore(Path::new(&child_rel)).unwrap_or(false) {
125+
continue;
126+
}
127+
128+
if path.is_file() {
129+
result.push(child_rel);
130+
} else if path.is_dir() {
131+
result.extend(collect_files_for_status(repo, workdir, &child_rel));
132+
}
133+
}
134+
}
135+
return result;
136+
}
137+
}
138+
139+
// If path does not exist (deleted file), just return the rel_path itself
140+
vec![rel_path.to_string()]
141+
}
142+
100143
// Lists all files changed in a given commit compared to its parent
101144
pub fn get_filenames_diff_at_oid(repo: &Repository, oid: Oid) -> Vec<FileChange> {
102145
let commit = repo.find_commit(oid).unwrap();

0 commit comments

Comments
 (0)