Allow adding files to .gitignore (#38089)

This feature allows users to add a new, untracked file to `.gitignore`
by using the context menu in the git panel.

<img width="300" alt="Demo screen shot"
src="https://github.com/user-attachments/assets/3f2402fb-9337-42f8-939f-dac12ca09518"
/>

Release Notes:

- Added feature to add a new file to `.gitignore`
This commit is contained in:
robert7k
2025-10-08 17:49:06 -04:00
committed by GitHub
parent 439add3d23
commit 681c19899f
2 changed files with 83 additions and 2 deletions
+2
View File
@@ -94,6 +94,8 @@ actions!(
OpenModifiedFiles,
/// Clones a repository.
Clone,
/// Adds a file to .gitignore.
AddToGitignore,
]
);
+81 -2
View File
@@ -870,6 +870,77 @@ impl GitPanel {
});
}
fn add_to_gitignore(
&mut self,
_: &git::AddToGitignore,
_window: &mut Window,
cx: &mut Context<Self>,
) {
maybe!({
let list_entry = self.entries.get(self.selected_entry?)?.clone();
let entry = list_entry.status_entry()?.to_owned();
if !entry.status.is_created() {
return Some(());
}
let project = self.project.downgrade();
let repo_path = entry.repo_path;
let active_repository = self.active_repository.as_ref()?.downgrade();
cx.spawn(async move |_, cx| {
let file_path_str = repo_path.0.display(PathStyle::Posix);
let repo_root = active_repository.read_with(cx, |repository, _| {
repository.snapshot().work_directory_abs_path
})?;
let gitignore_abs_path = repo_root.join(".gitignore");
let buffer = project
.update(cx, |project, cx| {
project.open_local_buffer(gitignore_abs_path, cx)
})?
.await?;
let mut should_save = false;
buffer.update(cx, |buffer, cx| {
let existing_content = buffer.text();
if existing_content
.lines()
.any(|line| line.trim() == file_path_str)
{
return;
}
let insert_position = existing_content.len();
let new_entry = if existing_content.is_empty() {
format!("{}\n", file_path_str)
} else if existing_content.ends_with('\n') {
format!("{}\n", file_path_str)
} else {
format!("\n{}\n", file_path_str)
};
buffer.edit([(insert_position..insert_position, new_entry)], None, cx);
should_save = true;
})?;
if should_save {
project
.update(cx, |project, cx| project.save_buffer(buffer, cx))?
.await?;
}
anyhow::Ok(())
})
.detach_and_log_err(cx);
Some(())
});
}
fn revert_entry(
&mut self,
entry: &GitStatusEntry,
@@ -3817,10 +3888,17 @@ impl GitPanel {
"Restore File"
};
let context_menu = ContextMenu::build(window, cx, |context_menu, _, _| {
context_menu
let mut context_menu = context_menu
.context(self.focus_handle.clone())
.action(stage_title, ToggleStaged.boxed_clone())
.action(restore_title, git::RestoreFile::default().boxed_clone())
.action(restore_title, git::RestoreFile::default().boxed_clone());
if entry.status.is_created() {
context_menu =
context_menu.action("Add to .gitignore", git::AddToGitignore.boxed_clone());
}
context_menu
.separator()
.action("Open Diff", Confirm.boxed_clone())
.action("Open File", SecondaryConfirm.boxed_clone())
@@ -4243,6 +4321,7 @@ impl Render for GitPanel {
.on_action(cx.listener(Self::unstage_selected))
.on_action(cx.listener(Self::restore_tracked_files))
.on_action(cx.listener(Self::revert_selected))
.on_action(cx.listener(Self::add_to_gitignore))
.on_action(cx.listener(Self::clean_all))
.on_action(cx.listener(Self::generate_commit_message_action))
.on_action(cx.listener(Self::stash_all))