Clone in a terminal:
Commit ed0c0af
Johan Mattsson - 2026-11-02T15:13:25+00:00
ed0c0af44ad395d28edfd8b7517358b6ae0ed6b4johan.mattsson.m@gmail.com

Check if headers file is in .gitignore for new projects

Changed files (4)

Diff

diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lockindex 7452d87..0c8dd7a 100644--- a/src-tauri/Cargo.lock+++ b/src-tauri/Cargo.lock@@ -323,6 +323,16 @@ dependencies = [ ]  [[package]]+name = "bstr"+version = "1.13.1"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "6bb31b46c14244e20ee9984b11bf5c992b91fb6939fea616e3512c8baecdbe5f"+dependencies = [+ "memchr",+ "serde_core",+]++[[package]] name = "bumpalo" version = "3.20.3" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -595,6 +605,25 @@ dependencies = [ ]  [[package]]+name = "crossbeam-deque"+version = "0.8.8"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "622f3fc73690be383c7214310406f28a90e6edeadc3cea882f9d71e495b9711a"+dependencies = [+ "crossbeam-epoch",+ "crossbeam-utils",+]++[[package]]+name = "crossbeam-epoch"+version = "0.9.21"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "dc74980687109a3b14c72fd458107bf0baa1da1a1a805e178d15501ba9b86d9d"+dependencies = [+ "crossbeam-utils",+]++[[package]] name = "crossbeam-utils" version = "0.8.23" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -1375,6 +1404,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4eba85ea1d0a966a983acd07deee566e67395d2d96b6fb39e62b5a833f1eb0b"  [[package]]+name = "globset"+version = "0.4.21"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "f98b0d85522dc0a824cfebbc9594ff4a9b289c568bea0370319dfbdf20ccdd2d"+dependencies = [+ "aho-corasick",+ "bstr",+ "log",+ "regex-automata",+ "regex-syntax",+]++[[package]] name = "gobject-sys" version = "0.18.0" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -1726,6 +1768,22 @@ dependencies = [ ]  [[package]]+name = "ignore"+version = "0.4.34"+source = "registry+https://github.com/rust-lang/crates.io-index"+checksum = "484be5657551d7868d67587df967fb4fcbe9d266a761cc855aaa148fda0b3cc7"+dependencies = [+ "crossbeam-deque",+ "globset",+ "log",+ "memchr",+ "regex-automata",+ "same-file",+ "walkdir",+ "winapi-util",+]++[[package]] name = "indexmap" version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index"@@ -3688,6 +3746,7 @@ dependencies = [ name = "tauri-app" version = "0.1.0" dependencies = [+ "ignore",  "reqwest 0.12.28",  "serde",  "serde_json",diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.tomlindex b02bfe1..6064b18 100644--- a/src-tauri/Cargo.toml+++ b/src-tauri/Cargo.toml@@ -23,6 +23,7 @@ tauri-plugin-opener = "2" tauri-plugin-dialog = "2" serde = { version = "1", features = ["derive"] } serde_json = "1"+ignore = "0.4" reqwest = { version = "0.12", default-features = false, features = ["rustls-tls"] }  diff --git a/src-tauri/src/lib.rs b/src-tauri/src/lib.rsindex 3e894d5..d7e26dc 100644--- a/src-tauri/src/lib.rs+++ b/src-tauri/src/lib.rs@@ -1,5 +1,6 @@ use std::{collections::BTreeMap, fs, io, path::PathBuf}; +use ignore::gitignore::GitignoreBuilder; use reqwest::{     Client, Method, Url,     header::{HeaderMap, HeaderName, HeaderValue},@@ -114,6 +115,50 @@ fn write_projects(app: &AppHandle, projects: &[Project]) -> Result<(), String> {         .map_err(|error| format!("Could not write projects: {error}")) } +fn headers_file_is_ignored(project_path: &str) -> Result<bool, String> {+    let project_path = fs::canonicalize(project_path.trim())+        .map_err(|error| format!("Could not access the project folder: {error}"))?;+    if !project_path.is_dir() {+        return Err("Choose an existing folder for the project.".to_owned());+    }++    let headers_path = project_path.join(HEADERS_FILE_NAME);+    let mut directories = Vec::new();+    let mut directory = Some(project_path.as_path());+    while let Some(path) = directory {+        directories.push(path);+        directory = path.parent();+    }+    directories.reverse();++    let mut ignored = false;+    for directory in directories {+        let gitignore_path = directory.join(".gitignore");+        if !gitignore_path.is_file() {+            continue;+        }++        let mut builder = GitignoreBuilder::new(directory);+        if let Some(error) = builder.add(&gitignore_path) {+            return Err(format!(+                "Could not read {}: {error}",+                gitignore_path.display()+            ));+        }+        let gitignore = builder+            .build()+            .map_err(|error| format!("Could not parse {}: {error}", gitignore_path.display()))?;+        let matched = gitignore.matched_path_or_any_parents(&headers_path, false);+        if matched.is_ignore() {+            ignored = true;+        } else if matched.is_whitelist() {+            ignored = false;+        }+    }++    Ok(ignored)+}+ fn project_summary(project: &Project) -> ProjectSummary {     let path = PathBuf::from(&project.path);     let name = path@@ -204,6 +249,11 @@ async fn pick_project_folder(app: AppHandle) -> Option<String> { }  #[tauri::command]+fn check_headers_gitignore(path: String) -> Result<bool, String> {+    headers_file_is_ignored(&path)+}++#[tauri::command] fn add_project(app: AppHandle, path: String) -> Result<ProjectSummary, String> {     let canonical_path = fs::canonicalize(path.trim())         .map_err(|error| format!("Could not access the project folder: {error}"))?;@@ -410,6 +460,7 @@ pub fn run() {         .invoke_handler(tauri::generate_handler![             load_project_list,             pick_project_folder,+            check_headers_gitignore,             add_project,             load_request_list,             load_request,diff --git a/src/lib.rs b/src/lib.rsindex aaeed3d..0e516be 100644--- a/src/lib.rs+++ b/src/lib.rs@@ -7,7 +7,10 @@ use wasm_bindgen_futures::spawn_local;  mod tauri; -use tauri::{AddProjectArgs, LoadRequestArgs, LoadRequestListArgs, SaveRequestArgs};+use tauri::{+    AddProjectArgs, CheckHeadersGitignoreArgs, LoadRequestArgs, LoadRequestListArgs,+    SaveRequestArgs,+};  thread_local! {     static HEADER_ROW_COUNT: Cell<usize> = const { Cell::new(1) };@@ -167,6 +170,18 @@ impl AppComponent {                     </div>                 </div>             </div>++            <div id="gitignore-warning-dialog" class="leave-dialog" role="dialog" aria-modal="true" aria-labelledby="gitignore-warning-title" hidden>+                <div class="leave-dialog-panel">+                    <p class="eyebrow">Git warning</p>+                    <h2 id="gitignore-warning-title">headers.json is not ignored</h2>+                    <p>This project does not ignore <code>headers.json</code>. It is highly recommended to have it in your <code>.gitignore</code> file if it contains http headers keys.</p>+                    <div class="leave-dialog-actions">+                        <button type="button" onclick="confirmAddProject()">Add project anyway</button>+                        <button class="button-secondary" type="button" onclick="cancelGitignoreWarning()">Go back</button>+                    </div>+                </div>+            </div>         }};         self.set_content(content.as_str());     }@@ -747,27 +762,34 @@ fn browse_project_folder() {     }); } -#[callback(addProject)]-fn add_project() {-    let path = try_get_value("#project-folder-input").trim().to_owned();-    if path.is_empty() {-        set_project_folder_error("Enter a folder path.");-        return;-    }--    let Some(dialog) = get_element("#project-folder-dialog") else {+fn show_gitignore_warning(path: &str, start_new_request: bool) {+    let Some(dialog) = get_element("#gitignore-warning-dialog") else {         return;     }; -    let start_new_request =-        dialog.get_attribute("data-start-new-request").as_deref() == Some("true");+    let _ = dialog.set_attribute("data-project-path", path);+    let _ = dialog.set_attribute(+        "data-start-new-request",+        if start_new_request { "true" } else { "false" },+    );+    let _ = dialog.remove_attribute("hidden");+}++fn hide_gitignore_warning() {+    if let Some(dialog) = get_element("#gitignore-warning-dialog") {+        let _ = dialog.set_attribute("hidden", "");+    }+} -    let project_args = AddProjectArgs { path };+fn invoke_add_project(path: String, start_new_request: bool) {+    let project_args = AddProjectArgs { path: path.clone() };      spawn_local(async move {         let args = match tauri_wasm::args(&project_args) {             Ok(args) => args,             Err(error) => {+                show_project_folder_dialog(start_new_request);+                set_project_folder_path(&path);                 set_project_folder_error(&format!("Could not prepare project folder: {error}"));                 return;             }@@ -778,19 +800,99 @@ fn add_project() {                 Ok(project) => {                     select_project(&project);                     hide_project_folder_dialog();+                    hide_gitignore_warning();                     if start_new_request {                         new_request();                     } else {                         load_projects();                     }                 }-                Err(error) => set_project_folder_error(&format!("Could not read project: {error}")),+                Err(error) => {+                    show_project_folder_dialog(start_new_request);+                    set_project_folder_path(&path);+                    set_project_folder_error(&format!("Could not read project: {error}"));+                }+            },+            Err(error) => {+                show_project_folder_dialog(start_new_request);+                set_project_folder_path(&path);+                set_project_folder_error(&error.to_string());+            }+        }+    });+}++#[callback(addProject)]+fn add_project() {+    let path = try_get_value("#project-folder-input").trim().to_owned();+    if path.is_empty() {+        set_project_folder_error("Enter a folder path.");+        return;+    }++    let Some(dialog) = get_element("#project-folder-dialog") else {+        return;+    };+    let start_new_request =+        dialog.get_attribute("data-start-new-request").as_deref() == Some("true");+    let gitignore_args = CheckHeadersGitignoreArgs { path: path.clone() };++    spawn_local(async move {+        let args = match tauri_wasm::args(&gitignore_args) {+            Ok(args) => args,+            Err(error) => {+                set_project_folder_error(&format!("Could not prepare project folder: {error}"));+                return;+            }+        };++        match tauri_wasm::invoke("check_headers_gitignore")+            .with_args(args)+            .await+        {+            Ok(value) => match serde_wasm_bindgen::from_value::<bool>(value) {+                Ok(true) => invoke_add_project(path, start_new_request),+                Ok(false) => {+                    hide_project_folder_dialog();+                    show_gitignore_warning(&path, start_new_request);+                }+                Err(error) => {+                    set_project_folder_error(&format!("Could not read .gitignore files: {error}"))+                }             },-            Err(error) => set_project_folder_error(&format!("{error}")),+            Err(error) => {+                set_project_folder_error(&format!("Could not read .gitignore files: {error}"))+            }         }     }); } +#[callback(confirmAddProject)]+fn confirm_add_project() {+    let Some(dialog) = get_element("#gitignore-warning-dialog") else {+        return;+    };+    let Some(path) = dialog.get_attribute("data-project-path") else {+        return;+    };+    let start_new_request =+        dialog.get_attribute("data-start-new-request").as_deref() == Some("true");++    invoke_add_project(path, start_new_request);+}++#[callback(cancelGitignoreWarning)]+fn cancel_gitignore_warning() {+    let Some(dialog) = get_element("#gitignore-warning-dialog") else {+        return;+    };+    let start_new_request =+        dialog.get_attribute("data-start-new-request").as_deref() == Some("true");++    hide_gitignore_warning();+    show_project_folder_dialog(start_new_request);+}+ #[callback(newRequest)] fn new_request() {     if selected_project_path().is_empty() {

Contact

Email: johan.mattsson.m@gmail.com
Mastodon: typo.social/@birdfont
GitHub: johanmattssonm
LinkedIn: johan-mattsson-2ab324112