Commit ed0c0af
Check if headers file is in .gitignore for new projects
Changed files (4)
modifiedsrc-tauri/Cargo.lockmodifiedsrc-tauri/Cargo.tomlmodifiedsrc-tauri/src/lib.rsmodifiedsrc/lib.rs
Diff
@@ -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",@@ -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"] } @@ -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,@@ -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() {